Skip to content

Commit

Permalink
Merge pull request #895 from matthias-pichler-warrify/matthias-pichle…
Browse files Browse the repository at this point in the history
…r/invalid-do-switch-894

Make do an array
  • Loading branch information
cdavernas authored Jun 13, 2024
2 parents 3a36e02 + 57cf769 commit f574808
Show file tree
Hide file tree
Showing 32 changed files with 1,281 additions and 989 deletions.
14 changes: 13 additions & 1 deletion .ci/validation/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion .ci/validation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"dependencies": {
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"js-yaml": "^4.1.0"
"js-yaml": "^4.1.0",
"marked": "^13.0.0"
}
}
53 changes: 53 additions & 0 deletions .ci/validation/src/dsl.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2023-Present The Serverless Workflow Specification Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { SWSchemaValidator } from "./index";
import fs from "node:fs";
import path from "node:path";
import marked from "marked";

SWSchemaValidator.prepareSchemas();

const dslReferencePath = path.join(
__dirname,
"..",
"..",
"..",
"dsl-reference.md"
);

describe(`Verify every example in the dsl docs`, () => {
const workflows = marked
.lexer(fs.readFileSync(dslReferencePath, SWSchemaValidator.defaultEncoding))
.filter((item): item is marked.Tokens.Code => item.type === "code")
.filter((item) => item.lang === "yaml")
.map((item) => item.text)
.map((text) => SWSchemaValidator.yamlToJSON(text))
.filter((workflow) => typeof workflow === "object")
.filter((workflow) => "document" in workflow)
.filter((workflow) => "dsl" in workflow.document);

test.each(workflows)("$document.name", (workflow) => {
const results = SWSchemaValidator.validateSchema(workflow);
if (results?.errors) {
console.warn(
`Schema validation on workflow ${workflow.document.name} failed with: `,
JSON.stringify(results.errors, null, 2)
);
}
expect(results?.valid).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,35 @@
*/

import { SWSchemaValidator } from "./index";
import fs from "fs";
import { join } from "path";
import fs from "node:fs";
import path from "node:path";

SWSchemaValidator.prepareSchemas();

const examplePath = "../../../examples";

describe(`Verify every example in the repository`, () => {
fs.readdirSync(join(__dirname, examplePath), {
encoding: SWSchemaValidator.defaultEncoding,
recursive: false,
withFileTypes: true,
}).forEach((file) => {
if (file.isFile() && file.name.endsWith(".yaml")) {
test(`Example ${file.name}`, () => {
const workflow = SWSchemaValidator.toJSON(
join(__dirname, `${examplePath}/${file.name}`)
);
const results = SWSchemaValidator.validateSchema(workflow);
if (results?.errors != null) {
console.warn(
`Schema validation on ${file.name} failed with: `,
JSON.stringify(results.errors, null, 2)
);
}
expect(results?.valid).toBeTruthy();
});
const examples = fs
.readdirSync(path.join(__dirname, examplePath), {
encoding: SWSchemaValidator.defaultEncoding,
recursive: false,
withFileTypes: true,
})
.filter((file) => file.isFile())
.filter((file) => file.name.endsWith(".yaml"))
.map((file) => file.name);

test.each(examples)("Example %s", (file) => {
const workflow = SWSchemaValidator.loadAsJSON(
path.join(__dirname, `${examplePath}/${file}`)
);
const results = SWSchemaValidator.validateSchema(workflow);
if (results?.errors) {
console.warn(
`Schema validation on ${file} failed with: `,
JSON.stringify(results.errors, null, 2)
);
}
expect(results?.valid).toBeTruthy();
});
});
52 changes: 30 additions & 22 deletions .ci/validation/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,54 +14,62 @@
* limitations under the License.
*/

import fs from "fs";
import Ajv from "ajv";
import fs from "node:fs";
import Ajv from "ajv/dist/2020";
import addFormats from "ajv-formats";
import { join } from "path";
import path from "node:path";
import yaml = require("js-yaml");

export module SWSchemaValidator {
const ajv = new Ajv({ strict: false, allowUnionTypes: true });
addFormats(ajv);

const workflowSchemaId =
"https://serverlessworkflow.io/schemas/1.0.0-alpha1/workflow.json";
"https://serverlessworkflow.io/schemas/1.0.0-alpha1/workflow.yaml";
const schemaPath = "../../../schema";
export const defaultEncoding = "utf-8";

export function prepareSchemas() {
fs.readdirSync(join(__dirname, schemaPath), {
const files = fs.readdirSync(path.join(__dirname, schemaPath), {
encoding: defaultEncoding,
recursive: false,
withFileTypes: true,
}).forEach((file) => {
if (file.isFile()) {
ajv.addSchema(syncReadSchema(file.name));
}
});

files
.filter((file) => file.isFile())
.forEach((file) => {
ajv.addSchema(syncReadSchema(file.name));
});
}

function syncReadSchema(filename: string) {
return toJSON(join(__dirname, `${schemaPath}/${filename}`));
function syncReadSchema(filename: string): any {
return loadAsJSON(path.join(__dirname, `${schemaPath}/${filename}`));
}

export function toJSON(filename: string) {
const yamlObj = yaml.load(fs.readFileSync(filename, defaultEncoding), {
export function loadAsJSON(filename: string): any {
return yamlToJSON(fs.readFileSync(filename, defaultEncoding));
}

export function yamlToJSON(yamlStr: string): any {
const yamlObj = yaml.load(yamlStr, {
json: true,
});
return JSON.parse(JSON.stringify(yamlObj, null, 2));
return structuredClone(yamlObj);
}

export function validateSchema(workflow: JSON) {
export function validateSchema(workflow: Record<string, unknown>) {
const validate = ajv.getSchema(workflowSchemaId);
if (validate != undefined) {
const isValid = validate(workflow);
return {
valid: isValid,
errors: validate.errors,
};

if (!validate) {
throw new Error(`Failed to validate schema on workflow`);
}
throw new Error(`Failed to validate schema on workflow`);

const isValid = validate(workflow);
return {
valid: isValid,
errors: validate.errors,
};
}
}

Expand Down
43 changes: 43 additions & 0 deletions .ci/validation/src/invalid.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2023-Present The Serverless Workflow Specification Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { SWSchemaValidator } from "./index";
import fs from "node:fs";
import path from "node:path";

SWSchemaValidator.prepareSchemas();

const invalidPath = "../test/fixtures/invalid";

describe(`Check that invalid workflows are rejected`, () => {
const examples = fs
.readdirSync(path.join(__dirname, invalidPath), {
encoding: SWSchemaValidator.defaultEncoding,
recursive: false,
withFileTypes: true,
})
.filter((file) => file.isFile())
.filter((file) => file.name.endsWith(".yaml"))
.map((file) => file.name);

test.each(examples)("Example %s", (file) => {
const workflow = SWSchemaValidator.loadAsJSON(
path.join(__dirname, `${invalidPath}/${file}`)
);
const results = SWSchemaValidator.validateSchema(workflow);
expect(results?.valid).toBeFalsy();
});
});
12 changes: 12 additions & 0 deletions .ci/validation/test/fixtures/invalid/extra-property-in-call.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
document:
dsl: 1.0.0-alpha1
namespace: examples
name: two-tasks-in-one-item
version: 1.0.0-alpha1
do:
- getPet:
call: http
with:
method: get
endpoint: https://petstore.swagger.io/v2/pet/{petId}
foo: bar
14 changes: 14 additions & 0 deletions .ci/validation/test/fixtures/invalid/two-tasks-in-one-item.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
document:
dsl: 1.0.0-alpha1
namespace: examples
name: two-tasks-in-one-item
version: 1.0.0-alpha1
do:
- getPet:
call: http
with:
method: get
endpoint: https://petstore.swagger.io/v2/pet/{petId}
setMessage:
set:
message: "Looking for {petId}"
31 changes: 31 additions & 0 deletions ctk/features/branch.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Feature: Composite Task
As an implementer of the workflow DSL
I want to ensure that composite tasks can be executed within the workflow
So that my implementation conforms to the expected behavior

# Tests composite tasks With competing concurrent sub tasks
Scenario: Fork Task With Competing Concurrent Sub Tasks
Given a workflow with definition:
"""yaml
document:
dsl: 1.0.0-alpha1
namespace: default
name: fork
do:
- branchWithCompete:
fork:
compete: true
branches:
- setRed:
set:
colors: ${ .colors + ["red"] }
- setGreen:
set:
colors: ${ .colors + ["green"] }
- setBlue:
set:
colors: ${ .colors + ["blue"] }
"""
When the workflow is executed
Then the workflow should complete
And the workflow output should have a 'colors' property containing 1 items
Loading

0 comments on commit f574808

Please sign in to comment.