2022-11-28 16:04:53 -08:00
import { Diagnostic , DiagnosticSeverity } from "vscode-languageserver-types" ;
2025-12-18 13:35:48 -06:00
import { createDocument } from "./test-utils/document.js" ;
import { validate } from "./validate.js" ;
import { defaultValueProviders } from "./value-providers/default.js" ;
import { clearCache } from "./utils/workflow-cache.js" ;
import { ValueProviderConfig , ValueProviderKind } from "./value-providers/config.js" ;
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-08 17:00:59 -08:00
describe ( "validation" , () => {
2022-11-21 17:55:24 -08:00
it ( "valid workflow" , async () => {
const result = await validate ( createDocument ( "wf.yaml" , "on: push\njobs:\n build:\n runs-on: ubuntu-latest" ));
2022-11-08 17:00:59 -08:00
2022-11-21 17:55:24 -08:00
expect ( result . length ). toBe ( 0 );
2022-11-08 17:00:59 -08:00
});
2022-11-21 17:55:24 -08:00
it ( "missing jobs key" , async () => {
const result = await validate ( createDocument ( "wf.yaml" , "on: push" ));
2022-11-08 17:00:59 -08:00
2022-11-21 17:55:24 -08:00
expect ( result . length ). toBe ( 1 );
expect ( result [ 0 ]). toEqual ({
message : "Required property is missing: jobs" ,
range : {
start : {
line : 0 ,
character : 0
},
end : {
line : 0 ,
character : 8
}
}
} as Diagnostic );
2022-11-08 17:00:59 -08:00
});
2022-11-21 17:55:24 -08:00
it ( "extraneous key" , async () => {
const result = await validate (
createDocument (
"wf.yaml" ,
`on: push
2022-11-08 17:00:59 -08:00
unknown-key: foo
jobs:
build:
runs-on: ubuntu-latest
steps:
2022-11-15 14:08:12 -05:00
- run: echo`
2022-11-21 17:55:24 -08:00
)
2022-11-08 17:00:59 -08:00
);
2022-11-21 17:55:24 -08:00
expect ( result . length ). toBe ( 1 );
expect ( result [ 0 ]). toEqual ({
message : "Unexpected value 'unknown-key'" ,
range : {
end : {
character : 11 ,
line : 1
},
start : {
character : 0 ,
line : 1
}
}
} as Diagnostic );
2022-11-08 17:00:59 -08:00
});
2022-11-28 16:04:53 -08:00
2022-11-28 16:15:57 -08:00
it ( "single value not returned by suggested value provider" , async () => {
2022-11-28 16:04:53 -08:00
const result = await validate (
createDocument (
"wf.yaml" ,
`on: push
jobs:
build:
runs-on: does-not-exist
steps:
- run: echo`
),
2022-12-13 15:14:57 -05:00
{ valueProviderConfig : defaultValueProviders }
2022-11-28 16:04:53 -08:00
);
2023-01-23 23:11:29 -05:00
expect ( result . length ). toBe ( 0 );
2022-11-28 16:04:53 -08:00
});
it ( "value in sequence not returned by value provider" , async () => {
const result = await validate (
createDocument (
"wf.yaml" ,
`on: push
jobs:
build:
runs-on:
- ubuntu-latest
- does-not-exist
steps:
- run: echo`
),
2022-12-13 15:14:57 -05:00
{ valueProviderConfig : defaultValueProviders }
2022-11-28 16:04:53 -08:00
);
2023-01-23 23:11:29 -05:00
expect ( result . length ). toBe ( 0 );
2022-11-28 16:04:53 -08:00
});
2022-11-28 16:15:57 -08:00
it ( "single value not returned by allowed value provider" , async () => {
const result = await validate (
createDocument (
"wf.yaml" ,
`on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo
build:
runs-on: ubuntu-latest
needs: test2
steps:
- run: echo`
),
2022-12-13 15:14:57 -05:00
{ valueProviderConfig : defaultValueProviders }
2022-11-28 16:15:57 -08:00
);
expect ( result [ 0 ]). toEqual ({
message : "Value 'test2' is not valid" ,
severity : DiagnosticSeverity.Error ,
range : {
end : {
character : 16 ,
line : 8
},
start : {
character : 11 ,
line : 8
}
}
} as Diagnostic );
});
2022-12-01 11:37:50 -05:00
it ( "unknown event type" , async () => {
const result = await validate (
createDocument (
"wf.yaml" ,
`on: [push, check_run, pr]
jobs:
build:
runs-on:
- ubuntu-latest`
),
2022-12-13 15:14:57 -05:00
{ valueProviderConfig : defaultValueProviders }
2022-12-01 11:37:50 -05:00
);
expect ( result . length ). toBe ( 1 );
expect ( result [ 0 ]). toEqual ({
message : "Unexpected value 'pr'" ,
range : {
end : {
character : 24 ,
line : 0
},
start : {
character : 22 ,
line : 0
}
}
} as Diagnostic );
});
2023-01-10 09:57:51 -08:00
it ( "invalid cron string" , async () => {
const result = await validate (
createDocument (
"wf.yaml" ,
`on:
schedule:
- cron: '0 0 * *'
jobs:
build:
runs-on: ubuntu-latest`
),
{ valueProviderConfig : defaultValueProviders }
);
expect ( result . length ). toBe ( 1 );
expect ( result [ 0 ]). toEqual ({
2025-12-04 11:25:15 -06:00
message : "Invalid cron expression. Expected format: '* * * * *' (minute hour day month weekday)" ,
2023-01-10 09:57:51 -08:00
range : {
end : {
character : 21 ,
line : 2
},
start : {
character : 12 ,
line : 2
}
}
} as Diagnostic );
});
2023-03-08 15:52:33 -05:00
2025-12-04 11:25:15 -06:00
it ( "cron with interval less than 5 minutes shows warning" , async () => {
const result = await validate (
createDocument (
"wf.yaml" ,
`on:
schedule:
- cron: '*/1 * * * *'
jobs:
build:
runs-on: ubuntu-latest`
),
{ valueProviderConfig : defaultValueProviders }
);
expect ( result . length ). toBe ( 1 );
expect ( result [ 0 ]). toEqual ({
2025-12-04 13:31:20 -06:00
message :
'Actions schedules run at most every 5 minutes. "*/1 * * * *" (runs every minute) will not run as frequently as specified.' ,
2025-12-04 11:25:15 -06:00
severity : DiagnosticSeverity.Warning ,
code : "on-schedule" ,
codeDescription : {
href : "https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#onschedule"
},
range : {
end : {
character : 25 ,
line : 2
},
start : {
character : 12 ,
line : 2
}
}
} as Diagnostic );
});
2025-12-29 13:47:30 -06:00
it ( "cron with interval of 5 minutes or more shows no diagnostic" , async () => {
2025-12-04 11:25:15 -06:00
const result = await validate (
createDocument (
"wf.yaml" ,
`on:
schedule:
- cron: '*/5 * * * *'
jobs:
build:
runs-on: ubuntu-latest`
),
{ valueProviderConfig : defaultValueProviders }
);
2025-12-29 13:47:30 -06:00
expect ( result . length ). toBe ( 0 );
2025-12-04 11:25:15 -06:00
});
it ( "cron with comma-separated minutes less than 5 apart shows warning" , async () => {
const result = await validate (
createDocument (
"wf.yaml" ,
`on:
schedule:
- cron: '0,2 * * * *'
jobs:
build:
runs-on: ubuntu-latest`
),
{ valueProviderConfig : defaultValueProviders }
);
expect ( result . length ). toBe ( 1 );
expect ( result [ 0 ] ? . severity ). toBe ( DiagnosticSeverity . Warning );
2025-12-04 13:31:20 -06:00
expect ( result [ 0 ] ? . message ). toContain ( "Actions schedules run at most every 5 minutes." );
2025-12-04 11:25:15 -06:00
});
2023-03-14 11:43:28 -04:00
it ( "invalid YAML" , async () => {
// This YAML has some mismatched single-quotes, which causes the string to be terminated early
// within the fromJSON() expression.
// Using double-quotes would make it valid:
// "Run a \${{ inputs.test }} one-line script \${{ fromJSON('test') == inputs.name }}"
const workflow = `
on: push
jobs:
build:
runs-on: ubuntu-latest
environment: TEST
steps:
- name: 'Run a \ ${ { inputs . test } } one-line script \ ${ { fromJSON ( 'test' ) == inputs . name } }'
run: echo
` ;
const result = await validate ( createDocument ( "wf.yaml" , workflow ), { valueProviderConfig : defaultValueProviders });
expect ( result ). toEqual ([
{
message :
"Unexpected scalar at node end at line 8, column 73:\n\n…un a ${{ inputs.test }} one-line script ${{ fromJSON('test') == inputs.name }}'\n ^^^^^^^^^^^^^^^^^^^^^^^^^\n" ,
range : {
start : {
line : 7 ,
character : 72
},
end : {
line : 7 ,
character : 97
}
}
}
]);
2023-03-14 11:46:44 -04:00
const cachedResult = await validate ( createDocument ( "wf.yaml" , workflow ), {
valueProviderConfig : defaultValueProviders
});
expect ( cachedResult ). toEqual ( result );
2023-03-14 11:43:28 -04:00
});
2023-03-08 15:52:33 -05:00
describe ( "value provider case sensitivity" , () => {
it ( "value with a different case and case sensitive provider" , async () => {
const workflow = `
on: push
jobs:
build:
runs-on: ubuntu-latest
environment: TEST
steps:
- run: echo
` ;
const valueProviderConfig : ValueProviderConfig = {
"job-environment" : {
kind : ValueProviderKind.AllowedValues ,
2023-03-20 13:33:58 -04:00
get : () => Promise . resolve ([{ label : "test" }]),
2023-03-08 15:52:33 -05:00
caseInsensitive : false
}
};
const result = await validate ( createDocument ( "wf.yaml" , workflow ), { valueProviderConfig });
expect ( result ). toEqual ([
{
message : "Value 'TEST' is not valid" ,
severity : DiagnosticSeverity.Error ,
range : {
start : {
line : 5 ,
character : 19
},
end : {
line : 5 ,
character : 23
}
}
}
]);
});
it ( "value with a different case and case insensitive provider" , async () => {
const workflow = `
on: push
jobs:
build:
runs-on: ubuntu-latest
environment: TEST
steps:
- run: echo
` ;
const valueProviderConfig : ValueProviderConfig = {
"job-environment" : {
kind : ValueProviderKind.AllowedValues ,
2023-03-20 13:33:58 -04:00
get : () => Promise . resolve ([{ label : "test" }]),
2023-03-08 15:52:33 -05:00
caseInsensitive : true
}
};
const result = await validate ( createDocument ( "wf.yaml" , workflow ), { valueProviderConfig });
expect ( result ). toEqual ([]);
});
});
2025-12-08 09:25:51 -06:00
describe ( "workflow_dispatch" , () => {
it ( "allows empty string in choice options" , async () => {
const result = await validate (
createDocument (
"wf.yaml" ,
`on:
workflow_dispatch:
inputs:
plugin-name:
description: Specific plugin to build
type: choice
options:
- ''
- foo
- bar
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo`
)
);
expect ( result ). toEqual ([]);
});
});
2022-11-08 17:00:59 -08:00
});