@shipfox/api-definitions 13.1.0 → 14.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +35 -0
- package/dist/core/entities/index.d.ts +1 -1
- package/dist/core/entities/index.d.ts.map +1 -1
- package/dist/core/entities/index.js +1 -1
- package/dist/core/entities/index.js.map +1 -1
- package/dist/core/entities/integration-context.d.ts +4 -0
- package/dist/core/entities/integration-context.d.ts.map +1 -1
- package/dist/core/entities/integration-context.js.map +1 -1
- package/dist/core/entities/sync-state.d.ts +10 -4
- package/dist/core/entities/sync-state.d.ts.map +1 -1
- package/dist/core/entities/sync-state.js +26 -7
- package/dist/core/entities/sync-state.js.map +1 -1
- package/dist/core/entities/validation-diagnostic.d.ts +8 -0
- package/dist/core/entities/validation-diagnostic.d.ts.map +1 -0
- package/dist/core/entities/validation-diagnostic.js +3 -0
- package/dist/core/entities/validation-diagnostic.js.map +1 -0
- package/dist/core/entities/workflow-definition.d.ts +2 -0
- package/dist/core/entities/workflow-definition.d.ts.map +1 -1
- package/dist/core/entities/workflow-definition.js.map +1 -1
- package/dist/core/errors.d.ts +10 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +9 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/integrations.d.ts +3 -1
- package/dist/core/integrations.d.ts.map +1 -1
- package/dist/core/integrations.js +10 -2
- package/dist/core/integrations.js.map +1 -1
- package/dist/core/parse-definition.d.ts +4 -4
- package/dist/core/parse-definition.d.ts.map +1 -1
- package/dist/core/parse-definition.js +5 -5
- package/dist/core/parse-definition.js.map +1 -1
- package/dist/core/resolve-definition-at-ref.d.ts +66 -0
- package/dist/core/resolve-definition-at-ref.d.ts.map +1 -0
- package/dist/core/resolve-definition-at-ref.js +374 -0
- package/dist/core/resolve-definition-at-ref.js.map +1 -0
- package/dist/core/sync-definitions.d.ts +2 -2
- package/dist/core/sync-definitions.d.ts.map +1 -1
- package/dist/core/sync-definitions.js +5 -5
- package/dist/core/sync-definitions.js.map +1 -1
- package/dist/core/validate-definition.d.ts +3 -3
- package/dist/core/validate-definition.d.ts.map +1 -1
- package/dist/core/validate-definition.js +17 -12
- package/dist/core/validate-definition.js.map +1 -1
- package/dist/core/workflow-model/cron-trigger.d.ts +1 -1
- package/dist/core/workflow-model/cron-trigger.d.ts.map +1 -1
- package/dist/core/workflow-model/cron-trigger.js +9 -5
- package/dist/core/workflow-model/cron-trigger.js.map +1 -1
- package/dist/core/workflow-model/index.d.ts +1 -1
- package/dist/core/workflow-model/index.d.ts.map +1 -1
- package/dist/core/workflow-model/index.js.map +1 -1
- package/dist/core/workflow-model/invalid-workflow-model-error.d.ts +4 -2
- package/dist/core/workflow-model/invalid-workflow-model-error.d.ts.map +1 -1
- package/dist/core/workflow-model/invalid-workflow-model-error.js.map +1 -1
- package/dist/core/workflow-model/normalize-job-listening.d.ts.map +1 -1
- package/dist/core/workflow-model/normalize-job-listening.js +51 -30
- package/dist/core/workflow-model/normalize-job-listening.js.map +1 -1
- package/dist/core/workflow-model/normalize-triggers.d.ts +1 -1
- package/dist/core/workflow-model/normalize-triggers.d.ts.map +1 -1
- package/dist/core/workflow-model/normalize-triggers.js +48 -18
- package/dist/core/workflow-model/normalize-triggers.js.map +1 -1
- package/dist/core/workflow-model/normalize-workflow-document.d.ts +1 -1
- package/dist/core/workflow-model/normalize-workflow-document.d.ts.map +1 -1
- package/dist/core/workflow-model/normalize-workflow-document.js +3 -3
- package/dist/core/workflow-model/normalize-workflow-document.js.map +1 -1
- package/dist/core/workflow-model/parse-interpolation-field.d.ts.map +1 -1
- package/dist/core/workflow-model/parse-interpolation-field.js +1 -15
- package/dist/core/workflow-model/parse-interpolation-field.js.map +1 -1
- package/dist/core/workflow-model/validate-predicate-expression.d.ts +3 -1
- package/dist/core/workflow-model/validate-predicate-expression.d.ts.map +1 -1
- package/dist/core/workflow-model/validate-predicate-expression.js +3 -0
- package/dist/core/workflow-model/validate-predicate-expression.js.map +1 -1
- package/dist/core/workflow-model/validation-issue.d.ts +2 -1
- package/dist/core/workflow-model/validation-issue.d.ts.map +1 -1
- package/dist/core/workflow-model/validation-issue.js +12 -15
- package/dist/core/workflow-model/validation-issue.js.map +1 -1
- package/dist/db/db.d.ts +196 -4
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +3 -1
- package/dist/db/db.js.map +1 -1
- package/dist/db/definition-triggers.js +3 -1
- package/dist/db/definition-triggers.js.map +1 -1
- package/dist/db/definitions.d.ts +17 -0
- package/dist/db/definitions.d.ts.map +1 -1
- package/dist/db/definitions.js +190 -23
- package/dist/db/definitions.js.map +1 -1
- package/dist/db/index.d.ts +2 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +2 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/schema/definitions.d.ts +17 -0
- package/dist/db/schema/definitions.d.ts.map +1 -1
- package/dist/db/schema/definitions.js +8 -0
- package/dist/db/schema/definitions.js.map +1 -1
- package/dist/db/schema/sync-states.d.ts +7 -3
- package/dist/db/schema/sync-states.d.ts.map +1 -1
- package/dist/db/schema/sync-states.js +5 -1
- package/dist/db/schema/sync-states.js.map +1 -1
- package/dist/db/schema/workflows.d.ts +78 -0
- package/dist/db/schema/workflows.d.ts.map +1 -0
- package/dist/db/schema/workflows.js +17 -0
- package/dist/db/schema/workflows.js.map +1 -0
- package/dist/db/sync-states.d.ts +2 -2
- package/dist/db/sync-states.d.ts.map +1 -1
- package/dist/db/sync-states.js +4 -4
- package/dist/db/sync-states.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +3 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instance.d.ts +4 -0
- package/dist/metrics/instance.d.ts.map +1 -0
- package/dist/metrics/instance.js +19 -0
- package/dist/metrics/instance.js.map +1 -0
- package/dist/presentation/dto/definition.d.ts.map +1 -1
- package/dist/presentation/dto/definition.js +11 -4
- package/dist/presentation/dto/definition.js.map +1 -1
- package/dist/presentation/inter-module.d.ts +9 -1
- package/dist/presentation/inter-module.d.ts.map +1 -1
- package/dist/presentation/inter-module.js +66 -2
- package/dist/presentation/inter-module.js.map +1 -1
- package/dist/presentation/routes/create-definition.d.ts.map +1 -1
- package/dist/presentation/routes/create-definition.js +16 -3
- package/dist/presentation/routes/create-definition.js.map +1 -1
- package/dist/presentation/routes/get-definition.d.ts.map +1 -1
- package/dist/presentation/routes/get-definition.js +18 -4
- package/dist/presentation/routes/get-definition.js.map +1 -1
- package/dist/presentation/routes/project-access.d.ts +1 -0
- package/dist/presentation/routes/project-access.d.ts.map +1 -1
- package/dist/presentation/routes/validate-definition.js +3 -3
- package/dist/presentation/routes/validate-definition.js.map +1 -1
- package/dist/temporal/activities/sync-activities.d.ts +3 -3
- package/dist/temporal/activities/sync-activities.d.ts.map +1 -1
- package/dist/temporal/activities/sync-activities.js +7 -4
- package/dist/temporal/activities/sync-activities.js.map +1 -1
- package/dist/temporal/workflows/definition-sync.js +1 -1
- package/dist/temporal/workflows/definition-sync.js.map +1 -1
- package/dist/temporal/workflows/index.bundle.js +50 -22
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0003_nebulous_nemesis.sql +11 -0
- package/drizzle/0004_workflow_ref_lookup.sql +1 -0
- package/drizzle/meta/0003_snapshot.json +627 -0
- package/drizzle/meta/0004_snapshot.json +649 -0
- package/drizzle/meta/_journal.json +14 -0
- package/drizzle.config.ts +1 -0
- package/package.json +7 -7
- package/src/core/entities/index.ts +1 -1
- package/src/core/entities/integration-context.ts +4 -0
- package/src/core/entities/sync-state.test.ts +49 -0
- package/src/core/entities/sync-state.ts +35 -12
- package/src/core/entities/validation-diagnostic.ts +8 -0
- package/src/core/entities/workflow-definition.ts +2 -0
- package/src/core/errors.ts +26 -0
- package/src/core/index.ts +12 -0
- package/src/core/integrations.test.ts +38 -0
- package/src/core/integrations.ts +10 -1
- package/src/core/parse-definition-diagnostics.test.ts +47 -0
- package/src/core/parse-definition.test.ts +29 -5
- package/src/core/parse-definition.ts +7 -7
- package/src/core/resolve-definition-at-ref.test.ts +792 -0
- package/src/core/resolve-definition-at-ref.ts +551 -0
- package/src/core/sync-definitions.test.ts +61 -2
- package/src/core/sync-definitions.ts +7 -7
- package/src/core/validate-definition.test.ts +31 -22
- package/src/core/validate-definition.ts +21 -13
- package/src/core/workflow-model/cron-trigger.ts +6 -2
- package/src/core/workflow-model/index.ts +1 -0
- package/src/core/workflow-model/invalid-workflow-model-error.ts +5 -1
- package/src/core/workflow-model/normalize-job-listening.ts +55 -23
- package/src/core/workflow-model/normalize-triggers.ts +44 -15
- package/src/core/workflow-model/normalize-workflow-document.test.ts +444 -36
- package/src/core/workflow-model/normalize-workflow-document.ts +8 -4
- package/src/core/workflow-model/parse-interpolation-field.ts +1 -23
- package/src/core/workflow-model/validate-predicate-expression.ts +6 -0
- package/src/core/workflow-model/validation-issue.ts +8 -13
- package/src/db/db.ts +7 -1
- package/src/db/definition-triggers.test.ts +44 -0
- package/src/db/definition-triggers.ts +1 -1
- package/src/db/definitions.test.ts +300 -1
- package/src/db/definitions.ts +282 -28
- package/src/db/index.ts +2 -0
- package/src/db/schema/definitions.test.ts +1 -0
- package/src/db/schema/definitions.ts +11 -0
- package/src/db/schema/sync-states.ts +12 -3
- package/src/db/schema/workflows.ts +23 -0
- package/src/db/sync-states.test.ts +87 -18
- package/src/db/sync-states.ts +6 -6
- package/src/db/workflow-lineage-migration.test.ts +158 -0
- package/src/index.ts +3 -1
- package/src/metrics/index.ts +1 -0
- package/src/metrics/instance.test.ts +50 -0
- package/src/metrics/instance.ts +23 -0
- package/src/presentation/dto/definition.test.ts +89 -1
- package/src/presentation/dto/definition.ts +8 -5
- package/src/presentation/inter-module.test.ts +177 -0
- package/src/presentation/inter-module.ts +77 -4
- package/src/presentation/routes/create-definition.test.ts +38 -0
- package/src/presentation/routes/create-definition.ts +20 -3
- package/src/presentation/routes/get-definition.test.ts +125 -11
- package/src/presentation/routes/get-definition.ts +21 -4
- package/src/presentation/routes/list-definitions.test.ts +1 -1
- package/src/presentation/routes/validate-definition.test.ts +1 -1
- package/src/presentation/routes/validate-definition.ts +3 -3
- package/src/presentation/subscribers/on-integration-connection-available.test.ts +3 -0
- package/src/temporal/activities/sync-activities.test.ts +46 -2
- package/src/temporal/activities/sync-activities.ts +9 -7
- package/src/temporal/workflows/definition-sync.ts +1 -1
- package/test/factories/definition.ts +1 -0
- package/test/globalSetup.ts +2 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/core/entities/validation-warning.d.ts +0 -6
- package/dist/core/entities/validation-warning.d.ts.map +0 -1
- package/dist/core/entities/validation-warning.js +0 -3
- package/dist/core/entities/validation-warning.js.map +0 -1
- package/src/core/entities/validation-warning.ts +0 -5
- package/src/core/parse-definition-warnings.test.ts +0 -33
|
@@ -2,7 +2,11 @@ import type {AgentValidationCatalog} from '@shipfox/api-agent-dto/inter-module';
|
|
|
2
2
|
import type {WorkflowDocument} from '@shipfox/workflow-document';
|
|
3
3
|
import {agentValidationCatalog} from '#test/agent-validation-catalog.js';
|
|
4
4
|
import type {IntegrationValidationContext} from '../entities/integration-context.js';
|
|
5
|
-
import {
|
|
5
|
+
import type {WorkflowModel} from '../entities/workflow-model.js';
|
|
6
|
+
import {
|
|
7
|
+
InvalidWorkflowModelError,
|
|
8
|
+
type WorkflowModelValidationIssue,
|
|
9
|
+
} from './invalid-workflow-model-error.js';
|
|
6
10
|
import {DEFAULT_JOB_CHECKOUT} from './normalize-job-checkout.js';
|
|
7
11
|
import {normalizeWorkflowDocument as normalizeWorkflowDocumentBase} from './normalize-workflow-document.js';
|
|
8
12
|
|
|
@@ -34,6 +38,16 @@ function expectInvalid(
|
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
|
|
41
|
+
function normalizeWithDiagnostics(
|
|
42
|
+
document: WorkflowDocument,
|
|
43
|
+
options?: Omit<Parameters<typeof normalizeWorkflowDocumentBase>[1], 'agentValidationCatalog'> &
|
|
44
|
+
Partial<Pick<Parameters<typeof normalizeWorkflowDocumentBase>[1], 'agentValidationCatalog'>>,
|
|
45
|
+
): {model: WorkflowModel; diagnostics: WorkflowModelValidationIssue[]} {
|
|
46
|
+
const diagnostics: WorkflowModelValidationIssue[] = [];
|
|
47
|
+
const model = normalizeWorkflowDocument(document, {...options, diagnostics});
|
|
48
|
+
return {model, diagnostics};
|
|
49
|
+
}
|
|
50
|
+
|
|
37
51
|
function interpolation(source: string): string {
|
|
38
52
|
return '$'.concat('{{ ', source, ' }}');
|
|
39
53
|
}
|
|
@@ -86,6 +100,13 @@ const integrationValidationContext = {
|
|
|
86
100
|
['linear-main', {id: 'conn_3', provider: 'linear', capabilities: ['agent_tools']}],
|
|
87
101
|
['jira-main', {id: 'conn_4', provider: 'jira', capabilities: ['agent_tools']}],
|
|
88
102
|
]),
|
|
103
|
+
eventCatalogs: new Map([
|
|
104
|
+
['github', new Set(['push', 'pull_request.opened'])],
|
|
105
|
+
['sentry', new Set(['issue.created'])],
|
|
106
|
+
['linear', new Set(['Issue'])],
|
|
107
|
+
['jira', new Set(['jira:issue_created'])],
|
|
108
|
+
]),
|
|
109
|
+
fixedEventProviders: new Set(['webhook']),
|
|
89
110
|
defaultConnectionSlug: 'github-main',
|
|
90
111
|
} satisfies IntegrationValidationContext;
|
|
91
112
|
|
|
@@ -779,6 +800,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
779
800
|
message: 'Provider "github-copilot" is not supported.',
|
|
780
801
|
path: ['jobs', 'fix', 'steps', 0, 'provider'],
|
|
781
802
|
details: {provider: 'github-copilot'},
|
|
803
|
+
severity: 'error',
|
|
804
|
+
scope: 'definition',
|
|
782
805
|
},
|
|
783
806
|
]);
|
|
784
807
|
});
|
|
@@ -829,6 +852,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
829
852
|
'Agent model "not-a-model" is not available for harness "pi" and provider "anthropic".',
|
|
830
853
|
path: ['jobs', 'fix', 'steps', 0, 'model'],
|
|
831
854
|
details: {harness: 'pi', provider: 'anthropic', model: 'not-a-model'},
|
|
855
|
+
severity: 'error',
|
|
856
|
+
scope: 'definition',
|
|
832
857
|
},
|
|
833
858
|
]);
|
|
834
859
|
});
|
|
@@ -902,6 +927,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
902
927
|
'Harness "claude" does not support provider: openai. Supported providers: anthropic.',
|
|
903
928
|
path: ['jobs', 'fix', 'steps', 0, 'provider'],
|
|
904
929
|
details: {harness: 'claude', provider: 'openai', supportedProviders: ['anthropic']},
|
|
930
|
+
severity: 'error',
|
|
931
|
+
scope: 'definition',
|
|
905
932
|
},
|
|
906
933
|
]);
|
|
907
934
|
});
|
|
@@ -940,6 +967,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
940
967
|
'get_search_content',
|
|
941
968
|
],
|
|
942
969
|
},
|
|
970
|
+
severity: 'error',
|
|
971
|
+
scope: 'definition',
|
|
943
972
|
},
|
|
944
973
|
]);
|
|
945
974
|
});
|
|
@@ -982,6 +1011,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
982
1011
|
'Agent step tools require an explicit harness because tool names are harness-specific.',
|
|
983
1012
|
path: ['jobs', 'fix', 'steps', 0, 'tools'],
|
|
984
1013
|
details: {tools: ['Read']},
|
|
1014
|
+
severity: 'error',
|
|
1015
|
+
scope: 'definition',
|
|
985
1016
|
},
|
|
986
1017
|
]);
|
|
987
1018
|
});
|
|
@@ -1069,6 +1100,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
1069
1100
|
thinking: 'off',
|
|
1070
1101
|
supportedLevels: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
1071
1102
|
},
|
|
1103
|
+
severity: 'error',
|
|
1104
|
+
scope: 'definition',
|
|
1072
1105
|
});
|
|
1073
1106
|
});
|
|
1074
1107
|
|
|
@@ -1434,6 +1467,78 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
1434
1467
|
]);
|
|
1435
1468
|
});
|
|
1436
1469
|
|
|
1470
|
+
it('reports missing resolution sources when every until matcher is inert', () => {
|
|
1471
|
+
const document: WorkflowDocument = {
|
|
1472
|
+
name: 'listen forever',
|
|
1473
|
+
jobs: {
|
|
1474
|
+
review: {
|
|
1475
|
+
listening: {
|
|
1476
|
+
on: [{source: 'github', event: 'pull_request_review'}],
|
|
1477
|
+
until: [{source: 'github', event: 'pull_request', filter: 'event.action'}],
|
|
1478
|
+
},
|
|
1479
|
+
steps: [{run: 'echo ok'}],
|
|
1480
|
+
},
|
|
1481
|
+
},
|
|
1482
|
+
};
|
|
1483
|
+
|
|
1484
|
+
const diagnostics: WorkflowModelValidationIssue[] = [];
|
|
1485
|
+
let error: InvalidWorkflowModelError | undefined;
|
|
1486
|
+
try {
|
|
1487
|
+
normalizeWorkflowDocument(document, {diagnostics});
|
|
1488
|
+
expect.fail('Expected InvalidWorkflowModelError');
|
|
1489
|
+
} catch (caught) {
|
|
1490
|
+
expect(caught).toBeInstanceOf(InvalidWorkflowModelError);
|
|
1491
|
+
error = caught as InvalidWorkflowModelError;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
expect(error?.issues).toEqual([
|
|
1495
|
+
expect.objectContaining({
|
|
1496
|
+
code: 'listening-job-missing-resolution-source',
|
|
1497
|
+
path: ['jobs', 'review', 'listening'],
|
|
1498
|
+
}),
|
|
1499
|
+
]);
|
|
1500
|
+
expect(diagnostics).toEqual([
|
|
1501
|
+
expect.objectContaining({
|
|
1502
|
+
code: 'invalid-listener-filter',
|
|
1503
|
+
path: ['jobs', 'review', 'listening', 'until', 0, 'filter'],
|
|
1504
|
+
severity: 'error',
|
|
1505
|
+
scope: 'trigger',
|
|
1506
|
+
}),
|
|
1507
|
+
]);
|
|
1508
|
+
});
|
|
1509
|
+
|
|
1510
|
+
it('drops inert until matchers from the model when a timeout resolves the job', () => {
|
|
1511
|
+
const document: WorkflowDocument = {
|
|
1512
|
+
name: 'listen with timeout',
|
|
1513
|
+
jobs: {
|
|
1514
|
+
review: {
|
|
1515
|
+
listening: {
|
|
1516
|
+
on: [{source: 'github', event: 'pull_request_review'}],
|
|
1517
|
+
until: [{source: 'github', event: 'pull_request', filter: 'event.action'}],
|
|
1518
|
+
timeout: '1h',
|
|
1519
|
+
},
|
|
1520
|
+
steps: [{run: 'echo ok'}],
|
|
1521
|
+
},
|
|
1522
|
+
},
|
|
1523
|
+
};
|
|
1524
|
+
|
|
1525
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
1526
|
+
|
|
1527
|
+
expect(diagnostics).toEqual([
|
|
1528
|
+
expect.objectContaining({
|
|
1529
|
+
code: 'invalid-listener-filter',
|
|
1530
|
+
path: ['jobs', 'review', 'listening', 'until', 0, 'filter'],
|
|
1531
|
+
severity: 'error',
|
|
1532
|
+
scope: 'trigger',
|
|
1533
|
+
}),
|
|
1534
|
+
]);
|
|
1535
|
+
expect(model.jobs[0]?.listening).toMatchObject({
|
|
1536
|
+
on: [{source: 'github', event: 'pull_request_review'}],
|
|
1537
|
+
timeoutMs: 60 * 60 * 1000,
|
|
1538
|
+
});
|
|
1539
|
+
expect(model.jobs[0]?.listening).not.toHaveProperty('until');
|
|
1540
|
+
});
|
|
1541
|
+
|
|
1437
1542
|
it('reports listening timeouts above the run timeout', () => {
|
|
1438
1543
|
const document: WorkflowDocument = {
|
|
1439
1544
|
name: 'too long',
|
|
@@ -1503,7 +1608,6 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
1503
1608
|
['executions root', 'executions.size() > 0', 'context-unavailable-at-predicate-site'],
|
|
1504
1609
|
['matrix root', 'matrix.os == "linux"', 'context-unavailable-at-predicate-site'],
|
|
1505
1610
|
['runner root', 'runner.os == "linux"', 'runner-context-in-server-predicate'],
|
|
1506
|
-
['non-boolean source', 'event.action', 'invalid-listener-filter'],
|
|
1507
1611
|
] as const)('reports invalid listener on filters for %s', (_label, filter, code) => {
|
|
1508
1612
|
const document: WorkflowDocument = {
|
|
1509
1613
|
name: 'invalid listener filter',
|
|
@@ -1532,6 +1636,70 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
1532
1636
|
]);
|
|
1533
1637
|
});
|
|
1534
1638
|
|
|
1639
|
+
it('rejects a listening job whose only on matcher is inert', () => {
|
|
1640
|
+
const document: WorkflowDocument = {
|
|
1641
|
+
name: 'invalid listener filter',
|
|
1642
|
+
jobs: {
|
|
1643
|
+
review: {
|
|
1644
|
+
listening: {
|
|
1645
|
+
on: [{source: 'github', event: 'pull_request_review', filter: 'event.action'}],
|
|
1646
|
+
max_executions: 1,
|
|
1647
|
+
},
|
|
1648
|
+
steps: [{run: 'echo ok'}],
|
|
1649
|
+
},
|
|
1650
|
+
},
|
|
1651
|
+
};
|
|
1652
|
+
|
|
1653
|
+
const error = expectInvalid(document);
|
|
1654
|
+
|
|
1655
|
+
expect(error.issues).toEqual([
|
|
1656
|
+
expect.objectContaining({
|
|
1657
|
+
code: 'listening-job-no-active-matcher',
|
|
1658
|
+
path: ['jobs', 'review', 'listening', 'on'],
|
|
1659
|
+
}),
|
|
1660
|
+
]);
|
|
1661
|
+
});
|
|
1662
|
+
|
|
1663
|
+
it('makes an invalid listener on matcher inert and keeps the others active', () => {
|
|
1664
|
+
const document: WorkflowDocument = {
|
|
1665
|
+
name: 'invalid listener filter',
|
|
1666
|
+
jobs: {
|
|
1667
|
+
review: {
|
|
1668
|
+
listening: {
|
|
1669
|
+
on: [
|
|
1670
|
+
{source: 'github', event: 'pull_request_review', filter: 'event.action'},
|
|
1671
|
+
{
|
|
1672
|
+
source: 'github',
|
|
1673
|
+
event: 'pull_request_review',
|
|
1674
|
+
filter: 'event.action == "submitted"',
|
|
1675
|
+
},
|
|
1676
|
+
],
|
|
1677
|
+
max_executions: 1,
|
|
1678
|
+
},
|
|
1679
|
+
steps: [{run: 'echo ok'}],
|
|
1680
|
+
},
|
|
1681
|
+
},
|
|
1682
|
+
};
|
|
1683
|
+
|
|
1684
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
1685
|
+
|
|
1686
|
+
expect(diagnostics).toEqual([
|
|
1687
|
+
expect.objectContaining({
|
|
1688
|
+
code: 'invalid-listener-filter',
|
|
1689
|
+
path: ['jobs', 'review', 'listening', 'on', 0, 'filter'],
|
|
1690
|
+
details: expect.objectContaining({
|
|
1691
|
+
field: 'listener.on',
|
|
1692
|
+
source: 'event.action',
|
|
1693
|
+
}),
|
|
1694
|
+
severity: 'error',
|
|
1695
|
+
scope: 'trigger',
|
|
1696
|
+
}),
|
|
1697
|
+
]);
|
|
1698
|
+
expect(model.jobs[0]?.listening?.on).toEqual([
|
|
1699
|
+
{source: 'github', event: 'pull_request_review', filter: 'event.action == "submitted"'},
|
|
1700
|
+
]);
|
|
1701
|
+
});
|
|
1702
|
+
|
|
1535
1703
|
it('rejects listener filters that reference jobs without a direct needs edge', () => {
|
|
1536
1704
|
const document: WorkflowDocument = {
|
|
1537
1705
|
name: 'listener missing needs',
|
|
@@ -3175,6 +3343,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3175
3343
|
message: 'Duration must be an integer followed by ms, s, m, h, or d.',
|
|
3176
3344
|
path: ['jobs', 'build', 'execution_timeout'],
|
|
3177
3345
|
details: {source: 'ten minutes'},
|
|
3346
|
+
severity: 'error',
|
|
3347
|
+
scope: 'definition',
|
|
3178
3348
|
},
|
|
3179
3349
|
]);
|
|
3180
3350
|
});
|
|
@@ -3198,6 +3368,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3198
3368
|
message: 'Duration must be between 1s and 24h.',
|
|
3199
3369
|
path: ['jobs', 'build', 'execution_timeout'],
|
|
3200
3370
|
details: {source: '999ms', min_ms: 1000, max_ms: 24 * 60 * 60 * 1000},
|
|
3371
|
+
severity: 'error',
|
|
3372
|
+
scope: 'definition',
|
|
3201
3373
|
},
|
|
3202
3374
|
]);
|
|
3203
3375
|
});
|
|
@@ -3221,6 +3393,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3221
3393
|
message: 'Duration must be between 1s and 24h.',
|
|
3222
3394
|
path: ['jobs', 'build', 'execution_timeout'],
|
|
3223
3395
|
details: {source: '25h', min_ms: 1000, max_ms: 24 * 60 * 60 * 1000},
|
|
3396
|
+
severity: 'error',
|
|
3397
|
+
scope: 'definition',
|
|
3224
3398
|
},
|
|
3225
3399
|
]);
|
|
3226
3400
|
});
|
|
@@ -3244,6 +3418,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3244
3418
|
message: 'Duration must be between 1s and 24h.',
|
|
3245
3419
|
path: ['jobs', 'build', 'execution_timeout'],
|
|
3246
3420
|
details: {source: '30d', min_ms: 1000, max_ms: 24 * 60 * 60 * 1000},
|
|
3421
|
+
severity: 'error',
|
|
3422
|
+
scope: 'definition',
|
|
3247
3423
|
},
|
|
3248
3424
|
]);
|
|
3249
3425
|
});
|
|
@@ -3336,6 +3512,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3336
3512
|
message: 'Step "build-review" must restart from an earlier keyed step; found "producer".',
|
|
3337
3513
|
path: ['jobs', 'build', 'steps', 0, 'gate', 'on_failure'],
|
|
3338
3514
|
details: {stepId: 'build-review', restartFrom: 'producer'},
|
|
3515
|
+
severity: 'error',
|
|
3516
|
+
scope: 'definition',
|
|
3339
3517
|
},
|
|
3340
3518
|
]);
|
|
3341
3519
|
});
|
|
@@ -3364,6 +3542,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3364
3542
|
message: 'Step "build-review" must restart from an earlier keyed step; found "review".',
|
|
3365
3543
|
path: ['jobs', 'build', 'steps', 0, 'gate', 'on_failure'],
|
|
3366
3544
|
details: {stepId: 'build-review', restartFrom: 'review'},
|
|
3545
|
+
severity: 'error',
|
|
3546
|
+
scope: 'definition',
|
|
3367
3547
|
},
|
|
3368
3548
|
]);
|
|
3369
3549
|
});
|
|
@@ -3393,6 +3573,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3393
3573
|
message: 'Step "build-review" must restart from an earlier keyed step; found "producer".',
|
|
3394
3574
|
path: ['jobs', 'build', 'steps', 0, 'gate', 'on_failure'],
|
|
3395
3575
|
details: {stepId: 'build-review', restartFrom: 'producer'},
|
|
3576
|
+
severity: 'error',
|
|
3577
|
+
scope: 'definition',
|
|
3396
3578
|
},
|
|
3397
3579
|
]);
|
|
3398
3580
|
});
|
|
@@ -3416,6 +3598,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3416
3598
|
message: 'Job "test" depends on unknown job "build".',
|
|
3417
3599
|
path: ['jobs', 'test', 'needs'],
|
|
3418
3600
|
details: {job: 'test', dependency: 'build'},
|
|
3601
|
+
severity: 'error',
|
|
3602
|
+
scope: 'definition',
|
|
3419
3603
|
},
|
|
3420
3604
|
]);
|
|
3421
3605
|
});
|
|
@@ -3439,6 +3623,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3439
3623
|
message: 'Job "test" depends on itself.',
|
|
3440
3624
|
path: ['jobs', 'test', 'needs'],
|
|
3441
3625
|
details: {job: 'test'},
|
|
3626
|
+
severity: 'error',
|
|
3627
|
+
scope: 'definition',
|
|
3442
3628
|
},
|
|
3443
3629
|
]);
|
|
3444
3630
|
});
|
|
@@ -3466,6 +3652,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3466
3652
|
message: 'Circular dependency detected among jobs: build, test.',
|
|
3467
3653
|
path: ['jobs'],
|
|
3468
3654
|
details: {cycleSourceNames: ['build', 'test'], cycleJobIds: ['build', 'test']},
|
|
3655
|
+
severity: 'error',
|
|
3656
|
+
scope: 'definition',
|
|
3469
3657
|
},
|
|
3470
3658
|
]);
|
|
3471
3659
|
});
|
|
@@ -3497,6 +3685,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3497
3685
|
message: 'Circular dependency detected among jobs: build, test.',
|
|
3498
3686
|
path: ['jobs'],
|
|
3499
3687
|
details: {cycleSourceNames: ['build', 'test'], cycleJobIds: ['build', 'test']},
|
|
3688
|
+
severity: 'error',
|
|
3689
|
+
scope: 'definition',
|
|
3500
3690
|
},
|
|
3501
3691
|
]);
|
|
3502
3692
|
});
|
|
@@ -3522,6 +3712,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3522
3712
|
message: 'Job keys "build app" and "build-app" resolve to the same stable id "build-app".',
|
|
3523
3713
|
path: ['jobs', 'build-app'],
|
|
3524
3714
|
details: {id: 'build-app', sourceKeys: ['build app', 'build-app']},
|
|
3715
|
+
severity: 'error',
|
|
3716
|
+
scope: 'definition',
|
|
3525
3717
|
},
|
|
3526
3718
|
]);
|
|
3527
3719
|
});
|
|
@@ -3547,7 +3739,7 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3547
3739
|
]);
|
|
3548
3740
|
});
|
|
3549
3741
|
|
|
3550
|
-
it('reports stable trigger id collisions', () => {
|
|
3742
|
+
it('reports stable trigger id collisions with the later trigger inert', () => {
|
|
3551
3743
|
const document: WorkflowDocument = {
|
|
3552
3744
|
name: 'trigger collision',
|
|
3553
3745
|
triggers: {
|
|
@@ -3561,17 +3753,20 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3561
3753
|
},
|
|
3562
3754
|
};
|
|
3563
3755
|
|
|
3564
|
-
const
|
|
3756
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
3565
3757
|
|
|
3566
|
-
expect(
|
|
3758
|
+
expect(diagnostics).toEqual([
|
|
3567
3759
|
{
|
|
3568
3760
|
code: 'duplicate-trigger-id',
|
|
3569
3761
|
message:
|
|
3570
3762
|
'Trigger keys "main_push" and "main push" resolve to the same stable id "main-push".',
|
|
3571
3763
|
path: ['triggers', 'main push'],
|
|
3572
3764
|
details: {id: 'main-push', sourceKeys: ['main_push', 'main push']},
|
|
3765
|
+
severity: 'error',
|
|
3766
|
+
scope: 'trigger',
|
|
3573
3767
|
},
|
|
3574
3768
|
]);
|
|
3769
|
+
expect(model.triggers.map((trigger) => trigger.key)).toEqual(['main_push']);
|
|
3575
3770
|
});
|
|
3576
3771
|
|
|
3577
3772
|
it('reports stable step id collisions inside a job', () => {
|
|
@@ -3592,6 +3787,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3592
3787
|
message: 'Steps 0 and 1 in job "build" resolve to the same stable id "build-step-1".',
|
|
3593
3788
|
path: ['jobs', 'build', 'steps', 1],
|
|
3594
3789
|
details: {id: 'build-step-1', indexes: [0, 1]},
|
|
3790
|
+
severity: 'error',
|
|
3791
|
+
scope: 'definition',
|
|
3595
3792
|
},
|
|
3596
3793
|
]);
|
|
3597
3794
|
});
|
|
@@ -3675,17 +3872,6 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3675
3872
|
site: 'ingest',
|
|
3676
3873
|
},
|
|
3677
3874
|
],
|
|
3678
|
-
[
|
|
3679
|
-
'non-boolean shape',
|
|
3680
|
-
'event.ref',
|
|
3681
|
-
'invalid-trigger-filter',
|
|
3682
|
-
{
|
|
3683
|
-
field: 'trigger.filter',
|
|
3684
|
-
source: 'event.ref',
|
|
3685
|
-
contextRoots: ['event'],
|
|
3686
|
-
reason: 'Predicate source must be boolean-shaped.',
|
|
3687
|
-
},
|
|
3688
|
-
],
|
|
3689
3875
|
] as const)('rejects trigger filters with %s', (_label, filter, code, details) => {
|
|
3690
3876
|
const document: WorkflowDocument = {
|
|
3691
3877
|
name: 'invalid trigger filter',
|
|
@@ -3711,11 +3897,50 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3711
3897
|
message: expect.any(String),
|
|
3712
3898
|
path: ['triggers', 'main', 'filter'],
|
|
3713
3899
|
details,
|
|
3900
|
+
severity: 'error',
|
|
3901
|
+
scope: 'definition',
|
|
3714
3902
|
},
|
|
3715
3903
|
]);
|
|
3716
3904
|
});
|
|
3717
3905
|
|
|
3718
|
-
it
|
|
3906
|
+
it('makes a trigger with a non-boolean filter inert', () => {
|
|
3907
|
+
const document: WorkflowDocument = {
|
|
3908
|
+
name: 'invalid trigger filter',
|
|
3909
|
+
triggers: {
|
|
3910
|
+
main: {
|
|
3911
|
+
source: 'github',
|
|
3912
|
+
event: 'push',
|
|
3913
|
+
filter: 'event.ref',
|
|
3914
|
+
},
|
|
3915
|
+
},
|
|
3916
|
+
jobs: {
|
|
3917
|
+
build: {
|
|
3918
|
+
steps: [{run: 'npm run build'}],
|
|
3919
|
+
},
|
|
3920
|
+
},
|
|
3921
|
+
};
|
|
3922
|
+
|
|
3923
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
3924
|
+
|
|
3925
|
+
expect(diagnostics).toEqual([
|
|
3926
|
+
{
|
|
3927
|
+
code: 'invalid-trigger-filter',
|
|
3928
|
+
message: expect.any(String),
|
|
3929
|
+
path: ['triggers', 'main', 'filter'],
|
|
3930
|
+
details: {
|
|
3931
|
+
field: 'trigger.filter',
|
|
3932
|
+
source: 'event.ref',
|
|
3933
|
+
contextRoots: ['event'],
|
|
3934
|
+
reason: 'Predicate source must be boolean-shaped.',
|
|
3935
|
+
},
|
|
3936
|
+
severity: 'error',
|
|
3937
|
+
scope: 'trigger',
|
|
3938
|
+
},
|
|
3939
|
+
]);
|
|
3940
|
+
expect(model.triggers).toEqual([]);
|
|
3941
|
+
});
|
|
3942
|
+
|
|
3943
|
+
it.each(['manual', 'cron'] as const)('makes a %s trigger with a filter inert', (source) => {
|
|
3719
3944
|
const document: WorkflowDocument = {
|
|
3720
3945
|
name: 'unsupported trigger filter',
|
|
3721
3946
|
triggers: {
|
|
@@ -3733,16 +3958,19 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3733
3958
|
},
|
|
3734
3959
|
};
|
|
3735
3960
|
|
|
3736
|
-
const
|
|
3961
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
3737
3962
|
|
|
3738
|
-
expect(
|
|
3963
|
+
expect(diagnostics).toEqual([
|
|
3739
3964
|
{
|
|
3740
3965
|
code: 'invalid-trigger-filter',
|
|
3741
3966
|
message: `A ${source} trigger cannot define a filter because it does not receive an event payload.`,
|
|
3742
3967
|
path: ['triggers', 'main', 'filter'],
|
|
3743
3968
|
details: {source: 'event.ref == "refs/heads/main"', triggerSource: source},
|
|
3969
|
+
severity: 'error',
|
|
3970
|
+
scope: 'trigger',
|
|
3744
3971
|
},
|
|
3745
3972
|
]);
|
|
3973
|
+
expect(model.triggers).toEqual([]);
|
|
3746
3974
|
});
|
|
3747
3975
|
|
|
3748
3976
|
it('maps trigger with values to model inputs', () => {
|
|
@@ -3844,7 +4072,64 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3844
4072
|
]);
|
|
3845
4073
|
});
|
|
3846
4074
|
|
|
3847
|
-
it('
|
|
4075
|
+
it('materializes fire for a manual trigger with the event omitted', () => {
|
|
4076
|
+
const document: WorkflowDocument = {
|
|
4077
|
+
name: 'manual trigger',
|
|
4078
|
+
triggers: {
|
|
4079
|
+
manual: {source: 'manual'},
|
|
4080
|
+
},
|
|
4081
|
+
jobs: {
|
|
4082
|
+
build: {
|
|
4083
|
+
steps: [{run: 'npm run build'}],
|
|
4084
|
+
},
|
|
4085
|
+
},
|
|
4086
|
+
};
|
|
4087
|
+
|
|
4088
|
+
const model = normalizeWorkflowDocument(document);
|
|
4089
|
+
|
|
4090
|
+
expect(model.triggers).toEqual([
|
|
4091
|
+
{
|
|
4092
|
+
id: 'manual',
|
|
4093
|
+
key: 'manual',
|
|
4094
|
+
source: 'manual',
|
|
4095
|
+
event: 'fire',
|
|
4096
|
+
},
|
|
4097
|
+
]);
|
|
4098
|
+
});
|
|
4099
|
+
|
|
4100
|
+
it('materializes tick for a cron trigger with the event omitted', () => {
|
|
4101
|
+
const document: WorkflowDocument = {
|
|
4102
|
+
name: 'nightly trigger',
|
|
4103
|
+
triggers: {
|
|
4104
|
+
nightly: {
|
|
4105
|
+
source: 'cron',
|
|
4106
|
+
config: {schedule: '0 2 * * *'},
|
|
4107
|
+
},
|
|
4108
|
+
},
|
|
4109
|
+
jobs: {
|
|
4110
|
+
build: {
|
|
4111
|
+
steps: [{run: 'npm run build'}],
|
|
4112
|
+
},
|
|
4113
|
+
},
|
|
4114
|
+
};
|
|
4115
|
+
|
|
4116
|
+
const model = normalizeWorkflowDocument(document);
|
|
4117
|
+
|
|
4118
|
+
expect(model.triggers).toEqual([
|
|
4119
|
+
{
|
|
4120
|
+
id: 'nightly',
|
|
4121
|
+
key: 'nightly',
|
|
4122
|
+
source: 'cron',
|
|
4123
|
+
event: 'tick',
|
|
4124
|
+
config: {
|
|
4125
|
+
schedule: '0 2 * * *',
|
|
4126
|
+
timezone: 'UTC',
|
|
4127
|
+
},
|
|
4128
|
+
},
|
|
4129
|
+
]);
|
|
4130
|
+
});
|
|
4131
|
+
|
|
4132
|
+
it('makes a cron trigger with a non-tick event inert', () => {
|
|
3848
4133
|
const document: WorkflowDocument = {
|
|
3849
4134
|
name: 'nightly trigger',
|
|
3850
4135
|
triggers: {
|
|
@@ -3861,19 +4146,58 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3861
4146
|
},
|
|
3862
4147
|
};
|
|
3863
4148
|
|
|
3864
|
-
const
|
|
4149
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
3865
4150
|
|
|
3866
|
-
expect(
|
|
4151
|
+
expect(diagnostics).toEqual([
|
|
3867
4152
|
{
|
|
3868
4153
|
code: 'invalid-cron-event',
|
|
3869
4154
|
message: 'A cron trigger must use event "tick"; found "push".',
|
|
3870
4155
|
path: ['triggers', 'nightly', 'event'],
|
|
3871
4156
|
details: {event: 'push'},
|
|
4157
|
+
severity: 'error',
|
|
4158
|
+
scope: 'trigger',
|
|
3872
4159
|
},
|
|
3873
4160
|
]);
|
|
4161
|
+
expect(model.triggers).toEqual([]);
|
|
3874
4162
|
});
|
|
3875
4163
|
|
|
3876
|
-
it('
|
|
4164
|
+
it('keeps the event absent for integration triggers with the event omitted', () => {
|
|
4165
|
+
const document: WorkflowDocument = {
|
|
4166
|
+
name: 'source subscriptions',
|
|
4167
|
+
triggers: {
|
|
4168
|
+
on_any_deploy: {
|
|
4169
|
+
source: 'deploy_hook',
|
|
4170
|
+
},
|
|
4171
|
+
on_any_github_event: {
|
|
4172
|
+
source: 'github_acme',
|
|
4173
|
+
},
|
|
4174
|
+
},
|
|
4175
|
+
jobs: {
|
|
4176
|
+
build: {
|
|
4177
|
+
steps: [{run: 'npm run build'}],
|
|
4178
|
+
},
|
|
4179
|
+
},
|
|
4180
|
+
};
|
|
4181
|
+
|
|
4182
|
+
const model = normalizeWorkflowDocument(document);
|
|
4183
|
+
|
|
4184
|
+
expect(model.triggers).toEqual([
|
|
4185
|
+
{
|
|
4186
|
+
id: 'on-any-deploy',
|
|
4187
|
+
key: 'on_any_deploy',
|
|
4188
|
+
source: 'deploy_hook',
|
|
4189
|
+
},
|
|
4190
|
+
{
|
|
4191
|
+
id: 'on-any-github-event',
|
|
4192
|
+
key: 'on_any_github_event',
|
|
4193
|
+
source: 'github_acme',
|
|
4194
|
+
},
|
|
4195
|
+
]);
|
|
4196
|
+
expect(model.triggers[0]).not.toHaveProperty('event');
|
|
4197
|
+
expect(model.triggers[1]).not.toHaveProperty('event');
|
|
4198
|
+
});
|
|
4199
|
+
|
|
4200
|
+
it('makes a cron trigger without a schedule inert', () => {
|
|
3877
4201
|
const document: WorkflowDocument = {
|
|
3878
4202
|
name: 'nightly trigger',
|
|
3879
4203
|
triggers: {
|
|
@@ -3889,22 +4213,25 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3889
4213
|
},
|
|
3890
4214
|
};
|
|
3891
4215
|
|
|
3892
|
-
const
|
|
4216
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
3893
4217
|
|
|
3894
|
-
expect(
|
|
4218
|
+
expect(diagnostics).toEqual([
|
|
3895
4219
|
{
|
|
3896
4220
|
code: 'missing-cron-schedule',
|
|
3897
4221
|
message: 'A cron trigger requires a schedule.',
|
|
3898
4222
|
path: ['triggers', 'nightly', 'config', 'schedule'],
|
|
4223
|
+
severity: 'error',
|
|
4224
|
+
scope: 'trigger',
|
|
3899
4225
|
},
|
|
3900
4226
|
]);
|
|
4227
|
+
expect(model.triggers).toEqual([]);
|
|
3901
4228
|
});
|
|
3902
4229
|
|
|
3903
4230
|
it.each([
|
|
3904
4231
|
['malformed', 'not a cron'],
|
|
3905
4232
|
['6-field', '0 0 2 * * *'],
|
|
3906
4233
|
['preset', '@daily'],
|
|
3907
|
-
])('
|
|
4234
|
+
])('makes a cron trigger with an invalid %s schedule inert', (_label, schedule) => {
|
|
3908
4235
|
const document: WorkflowDocument = {
|
|
3909
4236
|
name: 'nightly trigger',
|
|
3910
4237
|
triggers: {
|
|
@@ -3921,19 +4248,22 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3921
4248
|
},
|
|
3922
4249
|
};
|
|
3923
4250
|
|
|
3924
|
-
const
|
|
4251
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
3925
4252
|
|
|
3926
|
-
expect(
|
|
4253
|
+
expect(diagnostics).toEqual([
|
|
3927
4254
|
{
|
|
3928
4255
|
code: 'invalid-cron-schedule',
|
|
3929
4256
|
message: 'Cron trigger schedule must be a valid 5-field cron expression.',
|
|
3930
4257
|
path: ['triggers', 'nightly', 'config', 'schedule'],
|
|
3931
4258
|
details: {schedule},
|
|
4259
|
+
severity: 'error',
|
|
4260
|
+
scope: 'trigger',
|
|
3932
4261
|
},
|
|
3933
4262
|
]);
|
|
4263
|
+
expect(model.triggers).toEqual([]);
|
|
3934
4264
|
});
|
|
3935
4265
|
|
|
3936
|
-
it('
|
|
4266
|
+
it('makes a cron trigger with an invalid timezone inert', () => {
|
|
3937
4267
|
const document: WorkflowDocument = {
|
|
3938
4268
|
name: 'nightly trigger',
|
|
3939
4269
|
triggers: {
|
|
@@ -3953,16 +4283,19 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3953
4283
|
},
|
|
3954
4284
|
};
|
|
3955
4285
|
|
|
3956
|
-
const
|
|
4286
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
3957
4287
|
|
|
3958
|
-
expect(
|
|
4288
|
+
expect(diagnostics).toEqual([
|
|
3959
4289
|
{
|
|
3960
4290
|
code: 'invalid-cron-timezone',
|
|
3961
4291
|
message: 'Cron trigger timezone must be a valid IANA time zone.',
|
|
3962
4292
|
path: ['triggers', 'nightly', 'config', 'timezone'],
|
|
3963
4293
|
details: {timezone: 'Not/A/Zone'},
|
|
4294
|
+
severity: 'error',
|
|
4295
|
+
scope: 'trigger',
|
|
3964
4296
|
},
|
|
3965
4297
|
]);
|
|
4298
|
+
expect(model.triggers).toEqual([]);
|
|
3966
4299
|
});
|
|
3967
4300
|
|
|
3968
4301
|
it('allows multiple cron triggers', () => {
|
|
@@ -4010,7 +4343,47 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
4010
4343
|
expect(model.triggers).toMatchObject([{id: 'manual', source: 'manual', event: 'fire'}]);
|
|
4011
4344
|
});
|
|
4012
4345
|
|
|
4013
|
-
it('
|
|
4346
|
+
it('keeps other triggers active when a cron trigger is inert', () => {
|
|
4347
|
+
const document: WorkflowDocument = {
|
|
4348
|
+
name: 'partially broken triggers',
|
|
4349
|
+
triggers: {
|
|
4350
|
+
nightly: {
|
|
4351
|
+
source: 'cron',
|
|
4352
|
+
event: 'tick',
|
|
4353
|
+
config: {schedule: 'not a cron'},
|
|
4354
|
+
},
|
|
4355
|
+
on_demand: {source: 'manual', event: 'fire'},
|
|
4356
|
+
},
|
|
4357
|
+
jobs: {
|
|
4358
|
+
build: {
|
|
4359
|
+
steps: [{run: 'npm run build'}],
|
|
4360
|
+
},
|
|
4361
|
+
},
|
|
4362
|
+
};
|
|
4363
|
+
|
|
4364
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
4365
|
+
|
|
4366
|
+
expect(diagnostics).toEqual([
|
|
4367
|
+
{
|
|
4368
|
+
code: 'invalid-cron-schedule',
|
|
4369
|
+
message: 'Cron trigger schedule must be a valid 5-field cron expression.',
|
|
4370
|
+
path: ['triggers', 'nightly', 'config', 'schedule'],
|
|
4371
|
+
details: {schedule: 'not a cron'},
|
|
4372
|
+
severity: 'error',
|
|
4373
|
+
scope: 'trigger',
|
|
4374
|
+
},
|
|
4375
|
+
]);
|
|
4376
|
+
expect(model.triggers).toEqual([
|
|
4377
|
+
{
|
|
4378
|
+
id: 'on-demand',
|
|
4379
|
+
key: 'on_demand',
|
|
4380
|
+
source: 'manual',
|
|
4381
|
+
event: 'fire',
|
|
4382
|
+
},
|
|
4383
|
+
]);
|
|
4384
|
+
});
|
|
4385
|
+
|
|
4386
|
+
it('keeps the first manual trigger active and makes later ones inert', () => {
|
|
4014
4387
|
const document: WorkflowDocument = {
|
|
4015
4388
|
name: 'manual triggers',
|
|
4016
4389
|
triggers: {
|
|
@@ -4024,16 +4397,51 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
4024
4397
|
},
|
|
4025
4398
|
};
|
|
4026
4399
|
|
|
4027
|
-
const
|
|
4400
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
4028
4401
|
|
|
4029
|
-
expect(
|
|
4402
|
+
expect(diagnostics).toEqual([
|
|
4030
4403
|
{
|
|
4031
4404
|
code: 'multiple-manual-triggers',
|
|
4032
|
-
message:
|
|
4033
|
-
|
|
4405
|
+
message:
|
|
4406
|
+
'A workflow may declare at most one manual trigger; found 2: one, two. This trigger is inert because it is not the first manual trigger in document order.',
|
|
4407
|
+
path: ['triggers', 'two'],
|
|
4034
4408
|
details: {manualTriggerKeys: ['one', 'two']},
|
|
4409
|
+
severity: 'error',
|
|
4410
|
+
scope: 'trigger',
|
|
4035
4411
|
},
|
|
4036
4412
|
]);
|
|
4413
|
+
expect(model.triggers.map((trigger) => trigger.key)).toEqual(['one']);
|
|
4414
|
+
});
|
|
4415
|
+
|
|
4416
|
+
it('promotes the first valid manual trigger when an earlier one is inert', () => {
|
|
4417
|
+
const document: WorkflowDocument = {
|
|
4418
|
+
name: 'manual triggers with an inert first trigger',
|
|
4419
|
+
triggers: {
|
|
4420
|
+
broken: {
|
|
4421
|
+
source: 'manual',
|
|
4422
|
+
event: 'fire',
|
|
4423
|
+
filter: 'event.ref == "refs/heads/main"',
|
|
4424
|
+
},
|
|
4425
|
+
working: {source: 'manual', event: 'fire'},
|
|
4426
|
+
},
|
|
4427
|
+
jobs: {
|
|
4428
|
+
build: {
|
|
4429
|
+
steps: [{run: 'npm run build'}],
|
|
4430
|
+
},
|
|
4431
|
+
},
|
|
4432
|
+
};
|
|
4433
|
+
|
|
4434
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
4435
|
+
|
|
4436
|
+
expect(diagnostics).toEqual([
|
|
4437
|
+
expect.objectContaining({
|
|
4438
|
+
code: 'invalid-trigger-filter',
|
|
4439
|
+
path: ['triggers', 'broken', 'filter'],
|
|
4440
|
+
severity: 'error',
|
|
4441
|
+
scope: 'trigger',
|
|
4442
|
+
}),
|
|
4443
|
+
]);
|
|
4444
|
+
expect(model.triggers.map((trigger) => trigger.key)).toEqual(['working']);
|
|
4037
4445
|
});
|
|
4038
4446
|
|
|
4039
4447
|
it('accumulates independent semantic issues in one pass', () => {
|