Files
languageservices/languageservice/src/validate.expressions.test.ts
T

1509 lines
32 KiB
TypeScript
Raw Normal View History

2023-02-24 08:53:51 -08:00
import {DescriptionDictionary} from "@actions/expressions/.";
2022-11-21 17:55:24 -08:00
import {DiagnosticSeverity} from "vscode-languageserver-types";
import {ContextProviderConfig} from "./context-providers/config";
2022-12-08 10:17:29 -08:00
import {registerLogger} from "./log";
2022-11-21 17:55:24 -08:00
import {createDocument} from "./test-utils/document";
2022-12-08 10:17:29 -08:00
import {TestLogger} from "./test-utils/logger";
2023-03-06 15:16:26 -08:00
import {clearCache} from "./utils/workflow-cache";
2023-02-24 08:53:51 -08:00
import {validate, ValidationConfig} from "./validate";
2022-11-21 17:55:24 -08:00
2022-12-08 10:17:29 -08:00
registerLogger(new TestLogger());
2023-03-06 11:52:21 -08:00
beforeEach(() => {
2023-03-06 15:16:26 -08:00
clearCache();
2023-03-06 11:52:21 -08:00
});
2022-11-21 17:55:24 -08:00
describe("expression validation", () => {
it("access invalid context field", async () => {
const result = await validate(
createDocument(
"wf.yaml",
2022-12-02 10:48:49 -08:00
`on: push
run-name: name-\${{ github.does-not-exist }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo`
2022-11-21 17:55:24 -08:00
)
);
expect(result).toEqual([
{
2022-11-22 10:59:55 -08:00
message: "Context access might be invalid: does-not-exist",
2022-11-21 17:55:24 -08:00
range: {
end: {
character: 43,
line: 1
},
start: {
2022-11-22 10:59:55 -08:00
character: 15,
2022-11-21 17:55:24 -08:00
line: 1
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
it("access invalid context field in short-circuited expression", async () => {
const result = await validate(
createDocument(
"wf.yaml",
`on: push
run-name: name-\${{ github.does-not-exist || github.does-not-exist2 }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo`
)
);
expect(result).toEqual([
{
message: "Context access might be invalid: does-not-exist",
range: {
end: {
character: 69,
line: 1
},
start: {
character: 15,
line: 1
}
},
severity: DiagnosticSeverity.Warning
},
{
message: "Context access might be invalid: does-not-exist2",
range: {
end: {
character: 69,
line: 1
},
start: {
character: 15,
line: 1
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
it("partial skip access invalid context on incomplete", async () => {
const contextProviderConfig: ContextProviderConfig = {
2023-03-20 13:33:58 -04:00
getContext: (context: string) => {
switch (context) {
2023-03-20 13:33:58 -04:00
case "secrets": {
const dict = new DescriptionDictionary();
dict.complete = false;
2023-03-20 13:33:58 -04:00
return Promise.resolve(dict);
}
}
2023-03-20 13:33:58 -04:00
return Promise.resolve(undefined);
}
};
const validationConfig: ValidationConfig = {
contextProviderConfig: contextProviderConfig
};
const result = await validate(
createDocument(
"wf.yaml",
`on: push
run-name: name-\${{ github.does-not-exist }}
env:
secret: \${{ secrets.secret-not-exist }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo`
),
validationConfig
);
expect(result).toEqual([
{
message: "Context access might be invalid: does-not-exist",
range: {
end: {
character: 43,
line: 1
},
start: {
character: 15,
line: 1
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
2023-03-13 17:24:05 -04:00
it("no secret validation with workflow_call", async () => {
const input = `
on:
workflow_call:
secrets:
my_secret:
env:
my_secret: \${{ secrets.my_secret }}
secret: \${{ secrets.secret-not-exist }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
2022-11-21 17:55:24 -08:00
it("access invalid nested context field", async () => {
const result = await validate(
createDocument(
"wf.yaml",
"on: push\nrun-name: name-${{ github.does-not-exist.again }}\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo"
)
);
expect(result).toEqual([
{
2022-11-22 10:59:55 -08:00
message: "Context access might be invalid: does-not-exist",
2022-11-21 17:55:24 -08:00
range: {
end: {
character: 49,
line: 1
},
start: {
2022-11-22 10:59:55 -08:00
character: 15,
2022-11-21 17:55:24 -08:00
line: 1
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
2022-12-01 15:10:18 -05:00
it("needs.<job_id>", async () => {
const input = `
on: push
jobs:
a:
runs-on: ubuntu-latest
steps:
- run: echo hello a
b:
needs: [a]
runs-on: ubuntu-latest
steps:
- run: echo "hello \${{ needs.a }}"
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
2022-12-02 10:48:49 -08:00
describe("expressions without markers", () => {
it("access invalid context field", async () => {
const result = await validate(
createDocument(
"wf.yaml",
`on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo
if: github.does-not-exist`
)
);
expect(result).toEqual([
{
message: "Context access might be invalid: does-not-exist",
range: {
start: {
character: 12,
line: 6
},
end: {
character: 33,
line: 6
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
});
describe("steps context", () => {
it("steps.<step_id>", async () => {
const input = `
on: push
jobs:
a:
runs-on: ubuntu-latest
steps:
- id: a
run: echo hello a
- id: b
run: echo \${{ steps.a }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("steps.<step_id>.outputs.<output_id>", async () => {
const input = `
on: push
jobs:
a:
runs-on: ubuntu-latest
steps:
- id: a
run: echo hello a
- id: b
run: echo \${{ steps.a.outputs.anything }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("invalid reference of later step", async () => {
const input = `
on: push
jobs:
a:
runs-on: ubuntu-latest
steps:
- id: a
run: echo hello a
- id: b
run: echo \${{ steps.c }}
- id: c
run: echo hello c
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: c",
range: {
end: {
character: 36,
line: 9
},
start: {
character: 22,
line: 9
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
2022-12-20 15:27:34 -08:00
it("reference of invalid step in job outputs", async () => {
const input = `
on: push
jobs:
a:
outputs:
environment: \${{ steps.foo }}
runs-on: ubuntu-latest
steps:
- id: a
run: echo hello a
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
2023-01-09 10:38:47 -05:00
message: "Context access might be invalid: foo",
range: {
end: {
character: 41,
line: 5
2022-12-20 15:27:34 -08:00
},
2023-01-09 10:38:47 -05:00
start: {
character: 25,
line: 5
}
2022-12-20 15:27:34 -08:00
},
2023-01-09 10:38:47 -05:00
severity: 2
}
2022-12-20 15:27:34 -08:00
]);
});
it("invalid reference of generated step name", async () => {
const input = `
on: push
jobs:
a:
runs-on: ubuntu-latest
steps:
- id: a
run: echo hello a
- id: b
run: echo \${{ steps.__run }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: __run",
range: {
end: {
character: 40,
line: 9
},
start: {
character: 22,
line: 9
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
});
2022-12-06 18:02:54 -05:00
2022-12-12 15:10:28 -08:00
describe("job context", () => {
it("job.status", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
2022-12-15 14:31:38 -08:00
container:
image: node:14.16
2022-12-12 15:10:28 -08:00
steps:
2022-12-15 14:31:38 -08:00
- run: echo \${{ job.container }}
- run: echo \${{ job.container.id }}
- run: echo \${{ job.container.network }}
2022-12-12 15:10:28 -08:00
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
2022-12-15 14:31:38 -08:00
it("job.status", async () => {
2022-12-12 15:10:28 -08:00
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
2022-12-15 14:31:38 -08:00
- run: echo \${{ job.status }}
2022-12-12 15:10:28 -08:00
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("job.services.<service_id>", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
services:
nginx:
2022-12-15 14:31:38 -08:00
image: node:14.16
volumes:
- my_docker_volume:/volume_mount
2022-12-12 15:10:28 -08:00
ports:
- 80:8080
2022-12-12 15:10:28 -08:00
steps:
- run: echo \${{ job.services.nginx }}
2022-12-15 15:13:29 -08:00
- run: echo \${{ job.services.nginx.id }}
- run: echo \${{ job.services.nginx.network }}
2022-12-15 14:31:38 -08:00
- run: echo \${{ job.services.nginx.ports }}
2022-12-12 15:10:28 -08:00
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
2022-12-16 10:58:09 -08:00
it("job.services.<service_id>", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
container:
image: node:14.16
steps:
- run: echo \${{ job.container.tupperware }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: tupperware",
range: {
end: {
character: 49,
line: 9
},
start: {
character: 18,
line: 9
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
2022-12-12 15:10:28 -08:00
});
2023-02-23 11:19:07 -08:00
describe("jobs context", () => {
it("jobs.<job_id>.result", async () => {
const input = `
on:
workflow_call:
# Map the workflow outputs to job outputs
outputs:
successful:
description: "Was job successful"
value: \${{ jobs.example_job.result }}
jobs:
example_job:
runs-on: ubuntu-latest
steps:
- id: a
run: echo hello world`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("jobs.<job_id>.outputs.<output_name>", async () => {
const input = `
on:
workflow_call:
outputs:
output1:
description: "A greeting"
value: \${{ jobs.example_job.outputs.output1 }}
jobs:
example_job:
name: Generate output
runs-on: ubuntu-latest
# Map the job outputs to step outputs
outputs:
output1: "\${{ steps.a.outputs.greeting }}"
steps:
- id: a
run: echo "greeting=hello" >> $GITHUB_OUTPUT`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
});
2022-12-20 15:10:19 -08:00
describe("env context", () => {
it("references env within scope", async () => {
const input = `
on: push
jobs:
a:
runs-on: ubuntu-latest
steps:
- name: step a
env:
2022-12-20 15:10:19 -08:00
step_env: job_a_env
run: echo "hello \${{ env.step_env }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("inherits parent env", async () => {
const input = `
on: push
env:
envwf: workflow_env
jobs:
a:
runs-on: ubuntu-latest
env:
2022-12-20 15:10:19 -08:00
envjoba: job_a_env
steps:
- name: step a
run: echo "hello \${{ env.envwf }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("references env outside of scope", async () => {
const input = `
on: push
env:
envwf: workflow_env
jobs:
a:
runs-on: ubuntu-latest
env:
2022-12-20 15:10:19 -08:00
envjoba: job_a_env
steps:
- name: step a
run: echo "hello"
env:
envstepa: step_a_env
- name: step b
run: echo "hello \${{ env.envstepa }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: envstepa",
range: {
end: {
character: 42,
line: 15
},
start: {
character: 23,
line: 15
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
});
2022-12-06 18:02:54 -05:00
describe("strategy context", () => {
it("reference within a matrix job", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
test-group: [1, 2]
node: [14, 16]
steps:
- uses: actions/checkout@v3
- run: echo \${{ strategy.fail-fast }}
- run: echo \${{ strategy.job-index }}
- run: echo \${{ strategy.job-total }}
- run: echo \${{ strategy.max-parallel }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("reference strategy in reusable workflow", async () => {
const input = `
on: push
2023-02-24 08:53:51 -08:00
jobs:
test:
strategy:
fail-fast: true
matrix:
node: [14, 16]
uses: ./reusable-workflow-with-inputs.yaml
with:
username: User-\${{ strategy.fail-fast }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("reference matrix in reusable workflow", async () => {
const input = `
on: push
2023-02-24 08:53:51 -08:00
jobs:
test:
strategy:
matrix:
node: [14, 16]
uses: ./reusable-workflow-with-inputs.yaml
with:
username: \${{ matrix.node }}
`;
const result = await validate(createDocument("wf.yaml", input));
2022-12-06 18:02:54 -05:00
expect(result).toEqual([]);
});
it("reference outside of a matrix job", async () => {
2022-12-06 18:02:54 -05:00
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: echo \${{ strategy.fail-fast }}
- run: echo \${{ strategy.job-index }}
- run: echo \${{ strategy.job-total }}
- run: echo \${{ strategy.max-parallel }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).not.toEqual([]);
});
it("invalid strategy property", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
test-group: [1, 2]
node: [14, 16]
steps:
- uses: actions/checkout@v3
- run: echo \${{ strategy.fail-faster-than-fast }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: fail-faster-than-fast",
range: {
end: {
character: 55,
line: 12
},
start: {
character: 18,
line: 12
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
});
2022-12-08 13:55:58 -05:00
describe("multi-line strings warnings", () => {
2022-12-09 08:38:09 -08:00
it("indented |", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |
first line
test \${{ github.does-not-exist }}
test2`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: does-not-exist",
range: {
end: {
character: 43,
line: 7
},
start: {
character: 15,
line: 7
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
it("indented |+", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |+
first line
test \${{ github.does-not-exist }}
test2`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: does-not-exist",
range: {
end: {
character: 43,
line: 7
},
start: {
character: 15,
line: 7
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
it("indented >", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: >
first line
test \${{ github.does-not-exist }}
test2`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: does-not-exist",
range: {
end: {
character: 43,
line: 7
},
start: {
character: 15,
line: 7
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
it("indented >+", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: >+
first line
test \${{ github.does-not-exist }}
test2`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: does-not-exist",
range: {
end: {
character: 43,
line: 7
},
start: {
character: 15,
line: 7
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
});
describe("multi-line strings errors", () => {
it("indented |", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |
first line
test \${{ fromJSON2('') }}
test2`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Unrecognized function: 'fromJSON2'",
range: {
end: {
character: 35,
line: 7
},
start: {
character: 15,
line: 7
}
}
}
]);
});
it("indented |+", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |+
first line
test \${{ fromJSON2('') }}
test2`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Unrecognized function: 'fromJSON2'",
range: {
end: {
character: 35,
line: 7
},
start: {
character: 15,
line: 7
}
}
}
]);
});
it("indented >", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: >
first line
test \${{ fromJSON2('') }}
test2`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Unrecognized function: 'fromJSON2'",
range: {
end: {
character: 35,
line: 7
},
start: {
character: 15,
line: 7
}
}
}
]);
});
it("indented >+", async () => {
const input = `on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: >+
first line
test \${{ fromJSON2('') }}
test2`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Unrecognized function: 'fromJSON2'",
range: {
end: {
character: 35,
line: 7
},
start: {
character: 15,
line: 7
}
}
}
]);
});
});
2022-12-08 13:55:58 -05:00
describe("matrix context", () => {
it("reference within a matrix job", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node: [14, 16]
steps:
- uses: actions/checkout@v3
- run: echo \${{ matrix.node }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("reference outside of a matrix job", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: echo \${{ matrix.node }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: matrix",
range: {
end: {
character: 36,
line: 8
},
start: {
character: 18,
line: 8
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
it("basic matrix", async () => {
const input = `
on: push
jobs:
test:
runs-on: \${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [14, 16]
steps:
- uses: actions/checkout@v3
- run: echo \${{ matrix.node }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("invalid property reference", async () => {
const input = `
on: push
jobs:
test:
runs-on: \${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [14, 16]
include:
- os: macos-latest
node: 14
exclude:
- os: windows-latest
node: 14
steps:
- uses: actions/checkout@v3
- run: echo \${{ matrix.goversion }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: goversion",
range: {
end: {
character: 41,
line: 18
},
start: {
character: 18,
line: 18
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
it("matrix with include", async () => {
const input = `
on: push
jobs:
test:
runs-on: \${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [14, 16]
include:
- os: macos-latest
node: 14
steps:
- uses: actions/checkout@v3
- run: echo \${{ matrix.node }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("matrix with only include", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- os: windows-latest
node: 14
steps:
- uses: actions/checkout@v3
- run: echo \${{ matrix.os }}
- run: echo \${{ matrix.node }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("matrix with exclude", async () => {
const input = `
on: push
jobs:
test:
runs-on: \${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [14, 16]
include:
- os: macos-latest
node: 14
exclude:
- os: windows-latest
node: 14
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: \${{ matrix.node }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("matrix with only exclude", async () => {
const input = `
on: push
jobs:
test:
runs-on: \${{ matrix.os }}
strategy:
matrix:
exclude:
- os: windows-latest
node: 14
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: \${{ matrix.node }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: os",
range: {
end: {
character: 29,
line: 5
},
start: {
character: 13,
line: 5
}
},
severity: DiagnosticSeverity.Warning
},
{
message: "Context access might be invalid: node",
range: {
end: {
character: 42,
line: 15
},
start: {
character: 24,
line: 15
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
it("matrix from expression", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix: \${{ fromJSON('{"color":["green","blue"]}') }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: \${{ matrix.ANYVALUE }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("matrix with include expression", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
fruit: [apple, pear]
animal: [cat, dog]
include: \${{ fromJSON('{"color":"green"}') }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: \${{ matrix.ANYVALUE }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("matrix with property expression", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
color: \${{ fromJSON('["green","blue"]') }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: \${{ matrix.color }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("matrix with property expression and invalid property reference", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
color: \${{ fromJSON('["green","blue"]') }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: \${{ matrix.shape }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: shape",
range: {
end: {
character: 43,
line: 13
},
start: {
character: 24,
line: 13
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
});
2022-12-13 15:24:54 -05:00
describe("github context", () => {
it("includes only expected keys", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo \${{ github.action }}
- run: echo \${{ github.steps }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: steps",
range: {
end: {
character: 37,
line: 8
},
start: {
character: 18,
line: 8
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
2022-12-19 12:11:40 -05:00
it("validates event payload", async () => {
const input = `
2023-01-09 10:38:47 -05:00
on: [push, pull_request]
2022-12-19 12:11:40 -05:00
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo \${{ github.event.forced }}
2023-01-09 10:38:47 -05:00
- run: echo \${{ github.event.pull_request }}
2022-12-19 12:11:40 -05:00
- run: echo \${{ github.event.schedule }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: schedule",
range: {
end: {
character: 46,
2023-01-09 10:38:47 -05:00
line: 9
2022-12-19 12:11:40 -05:00
},
start: {
character: 18,
2023-01-09 10:38:47 -05:00
line: 9
2022-12-19 12:11:40 -05:00
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
2023-02-01 11:01:22 -05:00
it("validates event inputs via github.event", async () => {
const input = `
on:
workflow_dispatch:
inputs:
name:
type: string
default: some value
another-name:
type: string
jobs:
a:
runs-on: ubuntu-latest
steps:
- run: echo "hello \${{ github.event.inputs.name }}"
- run: echo "hello \${{ github.event.inputs.another-name }}"
- run: echo "hello \${{ github.event.inputs.random }}"
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
{
message: "Context access might be invalid: random",
range: {
start: {
character: 23,
line: 15
},
end: {
character: 56,
line: 15
}
},
severity: 2
}
]);
});
it("validates event inputs via inputs context", async () => {
2022-12-13 15:24:54 -05:00
const input = `
on:
workflow_dispatch:
inputs:
name:
type: string
default: some value
another-name:
type: string
workflow_call:
inputs:
third-name:
type: boolean
jobs:
a:
runs-on: ubuntu-latest
steps:
2023-01-19 10:02:13 -08:00
- run: echo \${{ fromJSON(inputs.random2) }}
- run: echo "hello \${{ inputs.random }}"
name: "\${{ fromJSON('test') == inputs.name }}"
2022-12-13 15:24:54 -05:00
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([
2023-02-01 11:01:22 -05:00
{
message: "Context access might be invalid: random2",
range: {
start: {
character: 16,
line: 17
},
end: {
character: 47,
line: 17
}
},
severity: 2
},
2022-12-13 15:24:54 -05:00
{
message: "Context access might be invalid: random",
range: {
start: {
character: 23,
2023-02-01 11:01:22 -05:00
line: 18
},
end: {
character: 43,
line: 18
2022-12-13 15:24:54 -05:00
}
},
severity: DiagnosticSeverity.Warning
}
]);
});
it("allows any property in client_payload", async () => {
const input = `
on:
repository_dispatch:
types: [test]
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo \${{ github.event.client_payload.anything }}
2023-01-23 16:25:30 -08:00
- run: echo \${{ github.event.client_payload.branch }}`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("allows any event property for workflow_call", async () => {
const input = `
on:
workflow_call:
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo \${{ github.event.anything }}`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
2022-12-13 15:24:54 -05:00
});
2022-11-21 17:55:24 -08:00
});