diff --git a/README.md b/README.md index 7b53a15..3a257ec 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Options: ``` --format -f Whether to output the Arazzo Specification as json or yaml. Default: json --output -o The name of the Arazzo Specification file. Default: arazzo.json ---source -s The default openAPI source file. Default: openapi.json +--source -s The default OpenAPI Description source file. Default: openapi.json ``` ### Configuration @@ -125,7 +125,7 @@ Mostly everything is optional in the `info` object. If you don't provide a `tit #### sourceDescriptions -This section is optional. It allows you to document any extra OpenAPI or Arazzo Specification files that your workflows and steps may require. If you do not document this section, it will end up with a default of: +This section is optional. It allows you to document any extra OpenAPI descriptions or Arazzo Specification files that your workflows and steps may require. If you do not document this section, it will end up with a default of: ``` sourceDescriptions: @@ -136,7 +136,7 @@ sourceDescriptions: That is, that it generates the `name` property from the `title` property of the `info` object (or the one that is generated for you if you omitted the `info` object). -The `url` will be that of a local openapi.json file, this is what the [Serverless OpenAPI Documenter](https://github.com/JaredCE/serverless-openapi-documenter) generates by default. This can be changed by the CLI by providing a source argument with the path to a different OpenAPI file. To make this usable, this should really be an accessible URL, so you should switch the `--source` CLI input to be the final resting place of the specification (an S3 bucket perhaps), for running locally it will be fine to keep it as a local location. +The `url` will be that of a local openapi.json file, this is what the [Serverless OpenAPI Documenter](https://github.com/JaredCE/serverless-openapi-documenter) generates by default. This can be changed by the CLI by providing a source argument with the path to a different OpenAPI Description file. To make this usable, this should really be an accessible URL, so you should switch the `--source` CLI input to be the final resting place of the specification (an S3 bucket perhaps), for running locally it will be fine to keep it as a local location. If you do provide this section, then any further additions will be added to that of the default `sourceDescription`. This is useful if you need to incorporate a step or workflow that resides in a different API (perhaps a Login service). @@ -183,7 +183,7 @@ The `inputs` here will be used in a login step and can be verified by this JSON #### steps -Describes a single workflow step which MAY be a call to an API operation (OpenAPI Operation Object) or another Workflow Object. +Describes a single workflow step which MAY be a call to an API operation (OpenAPI Description Operation Object) or another Workflow Object. ```yml steps: @@ -201,7 +201,7 @@ steps: - condition: ``` -Each step object requires a `stepId` which conforms to the Regex `[A-Za-z0-9_\-]+`. The `operationId` should point to an `operationId` within an OpenAPI document that is registered within the `sourceDescriptions` array. If you are using multiple OpenAPI files within the `sourceDescriptions` array, then you will need to reference the `operationId` via: `$sourceDescriptions..` e.g. +Each step object requires a `stepId` which conforms to the Regex `[A-Za-z0-9_\-]+`. The `operationId` should point to an `operationId` within an OpenAPI Description that is registered within the `sourceDescriptions` array. If you are using multiple OpenAPI files within the `sourceDescriptions` array, then you will need to reference the `operationId` via: `$sourceDescriptions..` e.g. ```yml sourceDescriptions: @@ -221,7 +221,7 @@ workflows: operationId: $sourceDescriptions.contactOpenAPI.updateUser ``` -`parameters` map to what must be passed into the referenced operation of the OpenAPI document, they map to the inputs described in the workflow section` e.g. +`parameters` map to what must be passed into the referenced operation of the OpenAPI Description, they map to the inputs described in the workflow section` e.g. ```yml - workflowId: loginUserWorkflow @@ -242,7 +242,7 @@ workflows: value: $inputs.username ``` -`requestBody` is very similar, the contentType should map to that of the operationId that is referenced in the OpenAPI document and the value map to the `inputs` referenced in the `workflow`. +`requestBody` is very similar, the contentType should map to that of the operationId that is referenced in the OpenAPI Description and the value map to the `inputs` referenced in the `workflow`. For `successCriteria`, it is probably worth reading through the [Arazzo Specification for Criterion objects](https://spec.openapis.org/arazzo/v1.0.1.html#criterion-object), but this can be as simple as diff --git a/package-lock.json b/package-lock.json index 69b1785..17688b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,12 @@ "dependencies": { "@redocly/cli": "^2.14.0", "@swaggerexpert/arazzo-runtime-expression": "^1.0.1", + "@swaggerexpert/json-pointer": "^2.10.2", "ajv": "^8.17.1", "chalk": "^4.1.2", "js-yaml": "^4.1.1", "jsonpath": "^1.1.1", + "openapi-params": "^0.0.4", "peggy": "^5.0.6", "stream-chain": "^3.4.0", "stream-json": "^1.9.1", @@ -889,6 +891,18 @@ "node": ">=12.20.0" } }, + "node_modules/@swaggerexpert/json-pointer": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@swaggerexpert/json-pointer/-/json-pointer-2.10.2.tgz", + "integrity": "sha512-qMx1nOrzoB+PF+pzb26Q4Tc2sOlrx9Ba2UBNX9hB31Omrq+QoZ2Gly0KLrQWw4Of1AQ4J9lnD+XOdwOdcdXqqw==", + "license": "Apache-2.0", + "dependencies": { + "apg-lite": "^1.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3499,6 +3513,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/openapi-params": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/openapi-params/-/openapi-params-0.0.4.tgz", + "integrity": "sha512-EIEUmB2K8qbsUP+BSVgV38oBkyhIRNh2eRRTJQTfddX6tn2khVV/Twh90om8jcrH8I/BMsKkbHoCbmvcWjaioA==", + "license": "MIT" + }, "node_modules/openapi-sampler": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.6.2.tgz", diff --git a/package.json b/package.json index 53daa70..18dba44 100644 --- a/package.json +++ b/package.json @@ -36,10 +36,12 @@ "dependencies": { "@redocly/cli": "^2.14.0", "@swaggerexpert/arazzo-runtime-expression": "^1.0.1", + "@swaggerexpert/json-pointer": "^2.10.2", "ajv": "^8.17.1", "chalk": "^4.1.2", "js-yaml": "^4.1.1", "jsonpath": "^1.1.1", + "openapi-params": "^0.0.4", "peggy": "^5.0.6", "stream-chain": "^3.4.0", "stream-json": "^1.9.1", diff --git a/src/Arazzo.js b/src/Arazzo.js index 736e67d..4059a21 100644 --- a/src/Arazzo.js +++ b/src/Arazzo.js @@ -1,476 +1,907 @@ -'use strict'; +"use strict"; -const { parse, test } = require('@swaggerexpert/arazzo-runtime-expression'); -const jp = require('jsonpath'); -const traverse = require('traverse'); +const { parse, test } = require("@swaggerexpert/arazzo-runtime-expression"); +const URLParams = require("openapi-params"); -const path = require('node:path') +const path = require("node:path"); -const Document = require('./Document'); -const docFactory = require('./DocFactory'); +const Document = require("./Document"); +const docFactory = require("./DocFactory"); +const Expression = require("./Expression"); +const Rules = require("./Rules"); class Arazzo extends Document { - constructor(url, name, options) { - super(url, name, options); + constructor(url, name, options) { + super(url, name, options); + + this.type = "arazzo"; + this.outputs = {}; + this.loadedSourceDescriptions = {}; + this.expression = new Expression(); + // this.pathToArazzoSpecification = path.resolve(arazzoPath); + this.stepRunRules = {}; + this.workflowRunRules = {}; + this.retrySet = new Set(); + this.retryLimits = {}; + } + + setMainArazzo() { + this.filePath = path.resolve(this.url); + } + + async runWorkflows(inputFile) { + this.inputFile = inputFile; + await this.getSourceDescriptions(); + await this.getWorkflows(); + + console.log("Starting Workflows"); + + await this.startWorkflows(); + + console.log("All Workflows run"); + } + + async startWorkflows(index = 0) { + this.workflowIndex = index; + if (index <= this.workflows.length - 1) { + this.abortWorkflowController = new AbortController(); + + console.log("Running workflow index", index); + try { + await this.runWorkflow(index); + await this.startWorkflows(index + 1); + } catch (err) { + console.log("Caught"); + // console.error(err); + + if (err.name === "AbortError") { + if (err.goto) { + console.log("goto error"); + await this.handleGotoRule(err.goto); + } + } else { + throw err; + } + } + // await this.runWorkflow(index).catch((err) => { + // console.log("caught", err); + // if (err.name === "AbortError") { + // } else { + // throw err; + // } + // }); + + // await this.startWorkflows(index + 1); + } else { + console.log("no more workflows"); + } + // this.workflowIndex = index; + // const continueRunning = await this.runWorkflow(index); - this.type = 'arazzo'; - this.outputs = {}; - this.loadedSourceDescriptions = {}; - // this.pathToArazzoSpecification = path.resolve(arazzoPath); + // if (continueRunning.noMoreWorkflows === false) { + // await this.startWorkflows(index + 1); + // } + } + async runWorkflow(index) { + if (this.abortWorkflowController.signal.aborted) { + throw new DOMException("Aborted", "AbortError"); } - setMainArazzo() { - this.filePath = path.resolve(this.url); - } + const rules = new Rules(this.expression); + const workflow = await this.JSONPickerToIndex("workflows", index); - async runWorkflows(inputFile) { - this.inputFile = inputFile; - await this.getSourceDescriptions(); - await this.getWorkflows(); + if (workflow) { + console.log(`Running Workflow: ${workflow.workflowId}`); + this.logger.notice(`Running Workflow: ${workflow.workflowId}`); - await this.startWorkflows(); - } + this.inputs = await this.inputFile.getWorkflowInputs( + workflow.workflowId, + workflow.inputs, + ); - async startWorkflows(index = 0) { - const continueRunning = await this.runWorkflow(index); - if (continueRunning.noMoreWorkflows === false) { - await this.startWorkflows(index+1); - } - } + this.expression.addToContext("inputs", this.inputs); - async runWorkflow(index) { - const workflow = await this.JSONPickerToIndex('workflows', index); + this.workflow = workflow; + this.workflow.rules = rules; - if (workflow) { - this.logger.notice(`Running Workflow: ${workflow.workflowId}`); - this.inputs = await this.inputFile.getWorkflowInputs(workflow.workflowId, workflow.inputs); - this.workflow = workflow; - await this.runSteps(); - return {noMoreWorkflows: false}; - } else { - this.logger.notice(`All workflows have run`); - return {noMoreWorkflows: true}; - } - } + if (this.workflow.onSuccess) { + this.workflow.rules.setWorkflowSuccess(this.workflow.onSuccess); + } - async runSteps(index = 0) { - const contineuRunning = await this.runStep(index); + if (this.workflow.onFailure) { + this.workflow.rules.setWorkflowFailures(this.workflow.onFailure); + } - if (contineuRunning.noMoreSteps === false) { - await this.runSteps(index+1); + await this.runSteps(); + + if (this.workflow.outputs) { + const outputs = {}; + for (const key in this.workflow.outputs) { + const value = this.expression.resolveExpression( + this.workflow.outputs[key], + ); + Object.assign(outputs, { [key]: value }); } + this.expression.addToContext("workflows", { + [this.workflow.workflowId]: { outputs: outputs }, + }); + } + + return { noMoreWorkflows: false }; + } else { + this.logger.notice(`All workflows have run`); + return { noMoreWorkflows: true }; } + } - async runStep(index) { - const step = this.workflow.steps[index]; - if (step) { - this.step = step; - this.logger.notice(`Running Step: ${this.step.stepId}`); + async runStepByIdFromRetry(stepId) { + const stepIndex = this.workflow.steps.findIndex( + (step) => step.stepId === stepId, + ); - await this.loadOperationData(); + return await this.runStep(stepIndex); + } - if (this.openAPISteps) { - await this.runOpenAPIStep(); - } + async runSteps(index = 0) { + if (this.abortWorkflowController.signal.aborted) { + throw new DOMException("Aborted", "AbortError"); + } - return {noMoreSteps: false}; - } else { - this.logger.notice(`All steps in ${this.workflow.workflowId} have run`); - return {noMoreSteps: true}; - } + this.stepIndex = index; + if (index <= this.workflow?.steps?.length - 1) { + console.log("Running Step Index:", index); + await this.runStep(index); + await this.runSteps(index + 1); } + } + + async runStep(index) { + if (this.abortWorkflowController.signal.aborted) { + throw new DOMException("Aborted", "AbortError"); + } + + const step = this.workflow.steps[index]; - async runOpenAPIStep() { - this.operations = await this.sourceDescriptionFile.buildOperation(this.inputs, this.step); + if (step) { + this.step = step; + console.log(`running step: ${step.stepId}`); + if (this.step.onSuccess) { + this.workflow.rules.setStepSuccesses(this.step.onSuccess); + } - this.mapInputs(); + if (this.step.onFailure) { + this.workflow.rules.setStepFailures(this.step.onFailure); + } - await this.runOperation(); + this.logger.notice(`Running Step: ${this.step.stepId}`); + + await this.loadOperationData(); + + if (this.openAPISteps) { + await this.runOpenAPIStep(); + } + + return { noMoreSteps: false }; + } else { + this.logger.notice(`All steps in ${this.workflow.workflowId} have run`); + + return { noMoreSteps: true }; } + } - async runOperation(retry = 0, retryAfter = 0) { - const sleep = function (ms) { - return new Promise(resolve => setTimeout(resolve, ms)); - } + async runOpenAPIStep() { + this.operations = await this.sourceDescriptionFile.buildOperation( + this.inputs, + this.step, + ); - for (const operation of this.operations) { - let url = operation.url; + this.mapInputs(); - if (operation.queryParams.size) { - url += `?${operation.queryParams}` - } + await this.runOperation(); + } - const options = { - method: operation.operation, - headers: operation.headers, - } + async runOperation(retry = 0, retryAfter = 0) { + const sleep = function (ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); + }; - if (operation.data) { - options.body = data; - } + const parseRetryAfter = function (retryAfter) { + if (!retryAfter || typeof retryAfter !== "string") { + return null; + } + + const trimmed = retryAfter.trim(); + + // Try parsing as a number (seconds format) + const asNumber = parseInt(trimmed, 10); + if (!isNaN(asNumber) && asNumber >= 0 && String(asNumber) === trimmed) { + return asNumber; + } + + // Try parsing as HTTP date format + try { + const date = new Date(trimmed); + + // Check if date is valid + if (isNaN(date.getTime())) { + return null; + } - this.logger.notice(`Making a ${operation.operation.toUpperCase()} call to ${operation.url}`); + // Calculate seconds from now until the date + const now = new Date(); + const secondsUntil = Math.ceil((date.getTime() - now.getTime()) / 1000); - const response = await fetch(url, options); + // Return the delay, but don't return negative values + return secondsUntil >= 0 ? secondsUntil : 0; + } catch (err) { + return null; + } + }; - await this.dealWithResponse(response); - // if (response.ok === false) { - // this.logger.error(`Call to ${operation.operation.toUpperCase()} ${operation.url} failed`); + for (const operation of this.operations) { + let url = operation.url; - // if (retry > 0) { - // let retryCount = retry--; - // this.logger.notice(`Making attempt number: ${retryCount}`); - // let retryAfterSeconds = retryAfter; - // if (response.headers.has('retry-after')) { - // retryAfterSeconds = response.headers['retry-after']; - // } + if (operation.queryParams.size) { + url += `?${operation.queryParams}`; + } - // if (retryAfterSeconds > 0) { - // await sleep(retryAfterSeconds*1000); - // } + const options = { + method: operation.operation, + headers: operation.headers, + }; - // await this.runOperation(retryCount, retryAfterSeconds); - // } else { - // throw new Error(`Call to ${operation.operation.toUpperCase()} ${operation.url} failed with a ${response.status}`); - // } - // } + if (operation.data) { + options.body = operation.data; + } - // if (this.step.successCriteria) { - // const hasMatchedSuccessCriteria = await this.determineSuccessCriteria(response); + this.logger.notice( + `Making a ${operation.operation.toUpperCase()} call to ${operation.url}`, + ); - // if (hasMatchedSuccessCriteria) { - // this.logger.success(`Making a ${operation.operation.toUpperCase()} call to ${operation.url} matched all the successCriteria`); - // } - // } + if (this.retryAfter) await sleep(this.retryAfter * 1000); - // if (this.step.outputs) { + console.log(`fetching: ${url}`); + const response = await fetch(url, options); - // } + if (response.headers.has("retry-after")) { + // assume seconds for now + // this.retryAfter = response.headers.get("retry-after"); + const retryAfter = parseRetryAfter(response.headers.get("retry-after")); + if (retryAfter !== null) { + this.retryAfter = retryAfter; } + } + + this.addParamsToContext(response.headers, "headers", "response"); + this.expression.addToContext("statusCode", response.status); + + await this.dealWithResponse(response); } + } + + async dealWithResponse(response) { + this.doNotProcessStep = false; + this.alreadyProcessingOnFailure = false; + + if (this.step.successCriteria) { + if (this.step.successCriteria) { + const passedSuccessCriteria = this.hasPassedSuccessCriteria(); + console.log("did it pass criteria", passedSuccessCriteria); + if (passedSuccessCriteria) { + if (this.currentRetryRule) { + if (this.retryContext.doNotDeleteRetryLimits) { + console.log("running", this.retryLimits); + this.retryLimits[this.currentRetryRule] = 0; + console.log("now", this.retryLimits[this.currentRetryRule]); + } + } - async dealWithResponse(response) { - if (response.ok === false) { - await this.dealWithFailedResponse(response); + await this.dealWithPassedRule(response); } else { - // const passed = await this.dealWithSuccessfulResponse(response); + if (this.step.onFailure) { + await this.dealWithFailedRule(); + } else { + throw new Error( + `${this.step.stepId} step of the ${this.workflow.workflowId} workflow failed the successCriteria`, + ); + } + } + } + } else { + if (this.step?.outputs) { + await this.dealWithStepOutputs(response); + } + } + } + + hasPassedSuccessCriteria() { + const hasPassed = []; + for (const criteriaObject of this.step.successCriteria) { + if (criteriaObject?.type) { + } else { + const hasPassedCheck = this.expression.checkSimpleExpression( + criteriaObject.condition, + ); + if (hasPassedCheck) hasPassed.push(true); + } + } - // if (!passed) { - // await this.dealWithFailedResponse(response); - // } + return hasPassed.length === this.step.successCriteria.length; + } - await this.dealWithOutputs(response) - } + async dealWithPassedRule(response) { + if (this.step?.outputs) { + await this.dealWithStepOutputs(response); } - async dealWithOutputs(response) { - const json = await response.json(); - if (this.step?.outputs) { - const outputs = {} - for (const key in this.step.outputs) { - // console.log(key) - - const isARuntimeValue = this.matchesExpectedRunTimeExpression(this.step.outputs[key], '$response.'); - - if (isARuntimeValue) { - const parseResult = parse(this.step.outputs[key]); - const parts = []; - - parseResult.ast.translate(parts); - for (const result of parts) { - if (result.at(0) === 'source') { - // if (result.at(1) === 'body') { - // outputs[key] = json; - // // console.log(JSON.stringify(json)) - // } - if (result.at(1).startsWith('body')) { - console.log(result.at(1)) - let path; - if (result.at(1) === 'body') { - path = '$' - } else { - const splitArr = result.at(1).split('body#/') - path = `$..${splitArr[1]}`; - } - console.log(path) - const value1 = jp.query(json, path); - console.log(value1); - } - - if (result.at(1).startsWith('header')) { - const headerName = parts[3][1] - outputs[key] = response.headers.get(headerName); - } - } - // console.log(result) - // if (result.at(1).at(0) === 'body' && !result?.at(3)) { - // outputs[key] = json; - // } - - // if (result[1].startsWith('header')) { - // outputs[key] = response.headers[result[3][1]]; - // } - } - } - } + console.log("checking onSuccess rules"); + const whatNext = this.workflow.rules.runRules(true); + console.log(whatNext); + if (whatNext.endWorkflow) { + this.workflowIndex += 1; + // const index = this.workflowIndex + 1; + + console.log("ending workflow"); + this.abortWorkflowController.abort(); + throw new DOMException("Aborted", "AbortError"); + console.log("still here though"); + // this.abortStep = new AbortController(); + // this.abortSignal = this.abortStep.signal; + + // this.startWorkflows(index); + // this.abortSignal.addEventListener("abort", () => { + // console.log("in the listener"); + // }); + // console.log(this.abortSignal.aborted); + // console.log("back here"); + } else if (whatNext.goto) { + console.log("goto command onSuccess"); + await this.gotoRule(whatNext); + console.log("onSuccess"); + // if (whatNext.stepId) { + // // const stepIndex = this.workflow.steps.findIndex( + // // (step) => step.stepId === whatNext.stepId, + // // ); + + // // if (stepIndex === -1) { + // // throw new Error(`goto Step does not exist within current workflow`); + // // } + // const stepIndex = this.findStepIndexInWorkflowByStepId(whatNext.stepId); + + // await this.runSteps(stepIndex); + // } else { + // // const workflowId = this.expression.resolveExpression( + // // whatNext.workflowId, + // // ); + + // // const workflowIndex = this.workflows.findIndex( + // // (workflow) => workflow.workflowId === workflowId, + // // ); + + // // if (workflowIndex === -1) { + // // throw new Error( + // // `goto Workflow does not exist within current workflows`, + // // ); + // // } + // const workflowIndex = this.findWorkflowIndexByWorkflowId( + // whatNext.workflowId, + // ); + // console.log("skipping to ", workflowIndex); + // await this.runWorkflow(workflowIndex); + // console.log("back at goto onSuccess"); + // } + } + } - Object.assign(this.outputs, {[this.step.stepId]: outputs}) - } + findStepIndexInWorkflowByStepId(stepId) { + const stepIndex = this.workflow.steps.findIndex( + (step) => step.stepId === stepId, + ); - console.log(this.outputs); + if (stepIndex === -1) { + throw new Error(`goto Step does not exist within current workflow`); } - async dealWithFailedResponse(response) { - if (this.workflow.failureActions) { + return stepIndex; + } - } + findWorkflowIndexByWorkflowId(workflowId) { + const resolvedWorkflowId = this.expression.resolveExpression(workflowId); + + const workflowIndex = this.workflows.findIndex( + (workflow) => workflow.workflowId === resolvedWorkflowId, + ); - if (this.step.failureActions) { + if (workflowIndex === -1) { + throw new Error(`goto Workflow does not exist within current workflows`); + } + return workflowIndex; + } + + async dealWithFailedRule(response) { + // if (this.step?.outputs) { + // await this.dealWithStepOutputs(response); + // } + + console.log("checking onFailed rules"); + const whatNext = this.workflow.rules.runRules(); + if (whatNext.endWorkflow) { + this.workflowIndex += 1; + // const index = this.workflowIndex + 1; + + console.log("ending workflow"); + this.abortWorkflowController.abort(); + throw new DOMException("Aborted", "AbortError"); + console.log("still here though"); + } else if (whatNext.goto) { + console.log("goto command onFailure"); + await this.gotoRule(whatNext); + console.log("onFailure"); + // if (whatNext.stepId) { + // // const stepIndex = this.workflow.steps.findIndex( + // // (step) => step.stepId === whatNext.stepId, + // // ); + + // // if (stepIndex === -1) { + // // throw new Error(`goto Step does not exist within current workflow`); + // // } + // // + // const stepIndex = this.findStepIndexInWorkflowByStepId(whatNext.stepId); + + // await this.runSteps(stepIndex); + // } else { + // const workflowIndex = this.findWorkflowIndexByWorkflowId( + // whatNext.workflowId, + // ); + // console.log("skipping to ", workflowIndex); + // await this.runWorkflow(workflowIndex); + // console.log("back at goto onFailure"); + // } + } else { + console.log("we retry"); + await this.retryProcessing(whatNext); + } + } + + async gotoRule(gotoRule) { + if (gotoRule.stepId) { + console.log("goto stepId"); + this.abortWorkflowController.abort(); + + // Attach goto to the error so we can handle it + const abortError = new DOMException("Aborted", "AbortError"); + abortError.goto = gotoRule; + throw abortError; + + // const stepIndex = this.workflow.steps.findIndex( + // (step) => step.stepId === whatNext.stepId, + // ); + + // if (stepIndex === -1) { + // throw new Error(`goto Step does not exist within current workflow`); + // } + + // comment at 8:11am 12 Jan + // const stepIndex = this.findStepIndexInWorkflowByStepId(gotoRule.stepId); + // console.log("skipping to step", stepIndex); + // await this.runSteps(stepIndex); + + // this.abortStepsController.abort(); + // throw new DOMException("Aborted", "AbortError"); + // this.abortStepsController.abort(); + // throw new DOMException("Aborted", "AbortError"); + + // comment at 8:11am 12 Jan + // console.log("back at goto step"); + } else { + const abortError = new DOMException("Aborted", "AbortError"); + abortError.goto = gotoRule; + throw abortError; + + // const workflowId = this.expression.resolveExpression( + // whatNext.workflowId, + // ); + + // const workflowIndex = this.workflows.findIndex( + // (workflow) => workflow.workflowId === workflowId, + // ); + + // if (workflowIndex === -1) { + // throw new Error( + // `goto Workflow does not exist within current workflows`, + // ); + // } + + // comment at 8:11am 12 Jan + // const workflowIndex = this.findWorkflowIndexByWorkflowId( + // gotoRule.workflowId, + // ); + // console.log("skipping to workflow", workflowIndex); + // await this.runWorkflow(workflowIndex); + + // this.abortStepsController.abort(); + // throw new DOMException("Aborted", "AbortError"); + // this.abortWorkflowController.abort(); + // throw new DOMException("Aborted", "AbortError"); + + // comment at 8:11am 12 Jan + // console.log("back at goto workflow"); + } + } + + async handleGotoRule(gotoRule) { + if (gotoRule.stepId) { + const stepIndex = this.workflow.steps.findIndex( + (step) => step.stepId === gotoRule.stepId, + ); + + if (stepIndex === -1) { + throw new Error(`goto Step does not exist within current workflow`); + } + + this.abortWorkflowController = new AbortController(); + try { + await this.runSteps(stepIndex); + // After finishing this workflow, continue to next + await this.startWorkflows(this.workflowIndex + 1); + } catch (err) { + if (err.name === "AbortError") { + if (err.goto) { + await this.handleGotoRule(err.goto); + } else { + await this.startWorkflows(this.workflowIndex + 1); + } + } else { + throw err; } + } + } else { + const workflowIndex = this.workflows.findIndex( + (workflow) => workflow.workflowId === gotoRule.workflowId, + ); + + if (workflowIndex === -1) { + throw new Error( + `goto Workflow does not exist within current workflows`, + ); + } + + await this.startWorkflows(workflowIndex); + } + } + + async retryProcessing(whatNext) { + console.log(whatNext); + this.retryContext = { + doNotDeleteRetryLimits: true, + }; + + let shouldRunRule = true; + this.currentRetryRule = whatNext.name; + + if (this.retrySet.has(whatNext.name)) { + console.log("we are currently retrying this", whatNext.name); + shouldRunRule = false; + } else { + console.log("never retried this", whatNext.name); + this.retrySet.add(whatNext.name); } - async dealWithSuccessfulResponse(response) { - let successCriteriaPassed = false; + if (shouldRunRule) { + Object.assign(this.retryLimits, { + [whatNext.name]: whatNext.retryLimit, + }); + + if (whatNext.stepId || whatNext.workflowId) { + console.log("need to run a workflow or step first"); + this.retryContext.doNotDeleteRetryLimits = false; + if (whatNext.stepId) { + console.log("need to run a step first"); + const stepIndex = this.findStepIndexInWorkflowByStepId( + whatNext.stepId, + ); + + await this.runStep(stepIndex); + console.log("step has run"); + } else { + const workflowIndex = this.findWorkflowIndexByWorkflowId( + whatNext.workflowId, + ); - if (this.step.successCriteria) { - successCriteriaPassed = this.dealWithCriteria(response, this.step.successCriteria); + console.log("need to run a workflow first"); + await this.runWorkflow(workflowIndex); + console.log("workflow has run"); } + } - return successCriteriaPassed; - } + // this.retryContext.doNotDeleteRetryLimits = true; - safeEval(expression, context) { - try { - const func = new Function('data', `return ${expression}`); - return func(context); - } catch (e) { - console.error('Error evaluating expression:', expression, e); - return false; + if (!this.retryAfter && whatNext.retryAfter) + this.retryAfter = whatNext.retryAfter; + + // for (let i = 0; i < whatNext.retryLimit; i++) { + do { + console.log("retrying", this.retryLimits[whatNext.name]); + let count = this.retryLimits[whatNext.name]; + console.log("calling runStep"); + await this.runStep(this.stepIndex); + console.log("I am back here"); + + if (this.retryLimits[whatNext.name] !== 0) { + count--; + this.retryLimits[whatNext.name] = count; } + } while (this.retryLimits[whatNext.name] > 0); + // } } - dealWithCriteria(response, criteriaArr) { - let hasPassed = false; - const passes = []; - const failures = []; - for (const successCriteria of criteriaArr) { - if (Object.hasOwn(successCriteria, 'type') === false || successCriteria?.type === 'simple') { - let condition = successCriteria.condition; - for (const key in response) { - const passed = this.safeEval(condition, response); - if (passed) passes.push(true); - } - } else { - if (successCriteria?.type === 'regex') { + if (this.retryLimits[whatNext.name] === 0) + this.retrySet.delete(whatNext.name); - } else { + console.log("I need to return here after retrying"); + } - } - } - } + async dealWithStepOutputs(response) { + const json = await response?.json().catch((err) => { + console.error(err); + this.logger.error(`Error trying to resolve ${this.step.stepId} outputs`); + throw new Error(err); + }); - if (passes === this.step.successCriteria.length) { - hasPassed = true; - } + this.expression.addToContext("response.body", json); - return hasPassed; - } - - async determineSuccessCriteria(response) { - let matchesAllSuccessCriteria = false; - const successCriteriaMatches = [] - for (const criterionObject of this.step.successCriteria) { - if (Object.entries(criterionObject).length === 1) { - if (test(criterionObject.condition)) { - const parseResult = parse(criterionObject.condition); - parseResult.ast.translate(parts); - - console.log(parseResult) - } else { - if (criterionObject.condition.startsWith('$statusCode')) { - if (response.status == 200) { - successCriteriaMatches.push(true) - } - } - } - } - } + const outputs = {}; + for (const key in this.step.outputs) { + const value = this.expression.resolveExpression(this.step.outputs[key]); - if (successCriteriaMatches.every(value => value === true)) { - matchesAllSuccessCriteria = true; - } + Object.assign(outputs, { [key]: value }); + } + this.expression.addToContext("steps", { + [this.step.stepId]: { outputs: outputs }, + }); + } + + mapInputs() { + this.mapParameters(); + this.mapRequestBody(); + + for (const operation of this.operations) { + this.addParamsToContext(operation.headers, "headers", "request"); + this.addParamsToContext(operation.queryParams, "query", "request"); + } + } + + mapParameters() { + const headers = new Headers(); + const queryParams = new URLSearchParams(); + const pathParams = {}; + + for (const param of this.step?.parameters || []) { + const operationDetailParam = + this.sourceDescription.operationDetails?.parameters + .filter((obj) => obj.name === param.name && obj.in === param.in) + .at(0); + // console.log(operationDetailParam); + const value = this.expression.resolveExpression(param.value); + + switch (param.in) { + case "header": + headers.append(param.name, value); + + break; + + case "path": + for (const operation of this.operations) { + operation.url = operation.url.replace(`{${param.name}}`, value); + Object.assign(pathParams, { [param.name]: value }); + } + break; + + case "query": + queryParams.append(param.name, value); + break; + } + } + + this.expression.addToContext("request.path", pathParams); - return matchesAllSuccessCriteria; + for (const operation of this.operations) { + operation.headers = headers; + operation.queryParams = queryParams; } + } - mapInputs() { - this.mapParameters(); - this.mapRequestBody(); + addParamsToContext(params, paramType, contextType) { + const parameters = {}; + for (const [key, value] of params.entries()) { + Object.assign(parameters, { [key]: value }); } - mapParameters() { - const headers = new Headers(); - const queryParams = new URLSearchParams(); + this.expression.addToContext(contextType, { [paramType]: parameters }); + } - for (const param of this.step?.parameters) { - const value = this.parseRunTimeExpression(param.value); + mapRequestBody() { + if (this.step?.requestBody) { + const payload = this.expression.resolveExpression( + this.step.requestBody.payload, + ); - switch(param.in) { - case 'header': - headers.append(param.name, value); - break; + for (const operation of this.operations) { + if (this.step.requestBody.contentType) { + operation.headers.append("accept", this.step.requestBody.contentType); + } - case 'path': - for (const operation of this.operations) { - operation.url = operation.url.replace(`{${param.name}}`, value) - } - break; + operation.data = payload; + } - case 'query': - queryParams.append(param.name, value); - break; - } - } + // let payload; - for (const operation of this.operations) { - operation.headers = headers; - operation.queryParams = queryParams; - } - } + // if (this.isARuntimeExpression(this.step.requestBody.payload)) { + // if (this.step.requestBody.payload.startsWith('$inputs')) { + // payload = this.getValueByPath(this.inputs, this.step.requestBody.payload.slice(8)) + // } + // } - mapRequestBody() { - if (this.step?.requestBody) { - const payload = structuredClone(this.step.requestBody.payload); - traverse(payload).forEach((requestValue) => { - const value = this.parseRunTimeExpression(requestValue); - this.update(value); - }); + // for (const operation of this.operations) { + // operation.data = payload; + // } + } + } - for (const operation of this.operations) { - operation.data = payload; - } - } + getValueByPath(obj, path, defaultValue = undefined) { + if (typeof path !== "string" || !path.trim()) { + throw new Error("Path must be a non-empty string."); } - parseRunTimeExpression(expression) { - let value = expression; - if (test(value)) { - const parts = [] - const parseResult = parse(value); - parseResult.ast.translate(parts); - console.log(parts); - for (const part of parts) { - if (part[0] === 'name') { - value = this.inputs[part[1]]; - } - } - } + // Convert array indexes to dot notation: users[0].name -> users.0.name + const normalizedPath = path.replace(/\[(\d+)\]/g, ".$1"); + + // Split into keys + const keys = normalizedPath.split("."); - return value; + // Traverse the object + let result = obj; + for (const key of keys) { + if (result && Object.prototype.hasOwnProperty.call(result, key)) { + result = result[key]; + } else { + return defaultValue; + } + } + return result; + } + + async loadOperationData() { + this.sourceDescription = this.getOperationIdSourceDescription(); + + if (!this.loadedSourceDescriptions[this.sourceDescription.name]) { + this.logger.notice( + `Getting Source Description for: ${this.sourceDescription.name}`, + ); + + this.sourceDescriptionFile = await docFactory.buildDocument( + this.sourceDescription.type, + this.sourceDescription.url, + this.sourceDescription.name, + { parser: this.parser, logger: this.logger }, + ); + + Object.assign(this.loadedSourceDescriptions, { + [this.sourceDescription.name]: true, + }); } - async loadOperationData() { - this.sourceDescription = this.getOperationIdSourceDescription(); + if (this.isAnOperationId) { + // this.logger.notice(`Getting OperationId: ${this.step.operationId}`); + let operationId = this.step.operationId; + operationId = operationId.split(".").at(-1); + await this.sourceDescriptionFile.getOperationById(operationId); + } + } - if (!this.loadedSourceDescriptions[this.sourceDescription.name]) { - this.logger.notice(`Getting Source Description for: ${this.sourceDescription.name}`); - this.sourceDescriptionFile = await docFactory.buildDocument( - this.sourceDescription.type, - this.sourceDescription.url, - this.sourceDescription.name, - {parser: this.parser, logger: this.logger} - ); - Object.assign(this.loadedSourceDescriptions, {[this.sourceDescription.name]: true}); - } + getOperationIdSourceDescription() { + const operationOrWorkflowPointer = this.getOperationType(); - if (this.isAnOperationId) { - // this.logger.notice(`Getting OperationId: ${this.step.operationId}`); - await this.sourceDescriptionFile.getOperationById(this.step.operationId); - } - } + // if there's only one, then all pointers must point to this + if (this.sourceDescriptions.length === 1) { + return this.sourceDescriptions[0]; + } else { + const operationOrWorkflowPointerArr = + operationOrWorkflowPointer.split("."); - getOperationIdSourceDescription() { - const operationOrWorkflowPointer = this.getOperationType(); + const joinedoperationOrWorkflowPointer = `${operationOrWorkflowPointerArr[0]}.${operationOrWorkflowPointerArr[1]}`; - if (this.sourceDescriptions.length === 1) { - return this.sourceDescriptions[0] - } else { - if (this.matchesExpectedRunTimeExpression(operationOrWorkflowPointer, '$sourceDescriptions.')) { - const sourceDescription = this.sourceDescriptions.filter((sourceDescription) => { - if (sourceDescription.name === operationOrWorkflowPointer.split('.')[1]) { - return sourceDescription; - } - }); - if (sourceDescription.length === 1) { - return sourceDescription; - } - } - } + const sourceDescription = this.expression.resolveExpression( + joinedoperationOrWorkflowPointer, + ); - throw new Error(`No known matching source description for ${this.step.operationId}`); + if (sourceDescription) { + return sourceDescription; + } } - getOperationType() { - let operationOrWorkflowPointer; - if (this.step.operationId) { - operationOrWorkflowPointer = this.step.operationId; - this.isAnOperationId = true; - this.openAPISteps = true; - } else if (this.step.workflowId) { - operationOrWorkflowPointer = this.step.workflowId; - this.isAWorkflowId = true; - } else { - operationOrWorkflowPointer = this.step.operationPath; - this.isAnOperationPath = true; - this.openAPISteps = true; - } - return operationOrWorkflowPointer; + throw new Error( + `No known matching source description for ${this.step.operationId}`, + ); + } + + getOperationType() { + let operationOrWorkflowPointer; + + if (this.step.operationId) { + operationOrWorkflowPointer = this.step.operationId; + this.isAnOperationId = true; + this.openAPISteps = true; + } else if (this.step.workflowId) { + operationOrWorkflowPointer = this.step.workflowId; + this.isAWorkflowId = true; + } else { + operationOrWorkflowPointer = this.step.operationPath; + this.isAnOperationPath = true; + this.openAPISteps = true; } + return operationOrWorkflowPointer; + } - matchesExpectedRunTimeExpression(string, runtimeExpression) { - const result = this.parser.parse(string, { peg$library: true }); + isARuntimeExpression(runtimeExpression) { + return test(runtimeExpression); + } - if (result.peg$success) { - if (result.peg$result[0] === runtimeExpression) { - return true; - } - } + matchesExpectedRunTimeExpression(string, runtimeExpression) { + const result = this.parser.parse(string, { peg$library: true }); - return false; + if (result.peg$success) { + if (result.peg$result[0] === runtimeExpression) { + return true; + } } - async getSourceDescriptions() { - const pipeline = this.JSONPicker('sourceDescriptions', this.filePath); + return false; + } - let sourceDescriptions = []; - for await (const { value } of pipeline) { - sourceDescriptions = value.flat(); - } + async getSourceDescriptions() { + const pipeline = this.JSONPicker("sourceDescriptions", this.filePath); - if (sourceDescriptions.length === 0) { - throw new Error('Missing Source Descriptions'); - } + let sourceDescriptions = []; + for await (const { value } of pipeline) { + sourceDescriptions = value.flat(); + } - this.sourceDescriptions = sourceDescriptions; + if (sourceDescriptions.length === 0) { + throw new Error("Missing Source Descriptions"); } - async getWorkflows() { - const pipeline = this.JSONPicker('workflows', this.filePath); + this.sourceDescriptions = sourceDescriptions; + for (const sourceDescription of sourceDescriptions) { + this.expression.addToContext("sourceDescriptions", { + [sourceDescription.name]: { + name: sourceDescription.name, + url: sourceDescription.url, + type: sourceDescription.type, + }, + }); + } + // console.log(this.expression.context) + // this.expression.addToContext('sourceDescriptions', sourceDescriptions) + } - let workflows = []; - for await (const { value } of pipeline) { - workflows = value.flat(); - } + async getWorkflows() { + const pipeline = this.JSONPicker("workflows", this.filePath); - if (workflows.length === 0) { - throw new Error('Missing Workflows'); - } + let workflows = []; + for await (const { value } of pipeline) { + workflows = value.flat(); + } - this.workflows = workflows; + if (workflows.length === 0) { + throw new Error("Missing Workflows"); } + + this.workflows = workflows; + } } module.exports = Arazzo; diff --git a/src/Document.js b/src/Document.js index d68a7e9..54eb558 100644 --- a/src/Document.js +++ b/src/Document.js @@ -104,15 +104,13 @@ class Document { JSONPicker(key, file) { let pipeline; - // if (this.httpPath) { - // pipeline = this.readStreamFromURL(key) - // } else { - pipeline = chain([ - fs.createReadStream(path.resolve(file)), - Pick.withParser({filter: key}), - streamValues() - ]); - // } + + pipeline = chain([ + fs.createReadStream(path.resolve(file)), + Pick.withParser({filter: key}), + streamValues() + ]); + return pipeline; } diff --git a/src/Expression.js b/src/Expression.js new file mode 100644 index 0000000..512530c --- /dev/null +++ b/src/Expression.js @@ -0,0 +1,565 @@ +"use strict"; + +const { + parse, + test, + extract, +} = require("@swaggerexpert/arazzo-runtime-expression"); +const { evaluate } = require("@swaggerexpert/json-pointer"); + +/** + * Handles resolution of Arazzo runtime expressions to context values. + * + * Supports expressions like: + * - Simple: $inputs.user, $statusCode + * - Complex: $response.body#/data/id, $response.header.Content-Type + * - Templated: "User {$inputs.username} logged in" + */ +class Expression { + constructor() { + this.context = {}; + + this.simpleExpressions = [ + "$url", + "$method", + "$statusCode", + "$inputs", + "$outputs", + "$steps", + "$workflows", + "$sourceDescriptions", + ]; + + this.expressionMap = { + $url: "url", + $method: "method", + $statusCode: "statusCode", + $request: "request", + "$request.query": "request.query", + "$request.header": "request.header", + "$request.path": "request.path", + "$request.body": "request.body", + $response: "response", + "$response.body": "response.body", + "$response.header": "response.header", + $inputs: "inputs", + $outputs: "outputs", + $steps: "steps", + $workflows: "workflows", + $sourceDescriptions: "sourceDescriptions", + }; + } + + /** + * Runs a check on a runtime expression from a simple Criterion Object + * @public + * @param {string} expression - The runtime expression to resolve + */ + checkSimpleExpression(expression) { + try { + const normalisedExpression = this.normalisedExpression(expression); + return this.safeEvaluate(normalisedExpression); + } catch (e) { + console.error("Error evaluating expression:", expression, e); + return false; + } + } + + /** + * Safely evaluates an expression by resolving runtime expressions first + * @private + * @param {string} expression - The normalized expression to evaluate + * @returns {boolean} Result of evaluation + */ + safeEvaluate(expression) { + let resolvedExpression = expression; + + // Match runtime expressions, but stop at brackets for array access + const runtimeExprRegex = + /\$[a-zA-Z0-9._#/-]+(?=\[|\.name|\.|\s|==|!=|<|>|$)/g; + const matches = expression.match(runtimeExprRegex); + + if (matches) { + const uniqueMatches = [...new Set(matches)]; + + for (const match of uniqueMatches) { + try { + let originalExpr = match; + + // Handle JSON pointer expressions + if (match.includes("#")) { + const [basePart, pointerPart] = match.split("#"); + const parts = basePart.split("."); + + if (parts.length > 0) { + parts[0] = parts[0].replace(/_/g, "."); + } + + for (let i = 1; i < parts.length; i++) { + parts[i] = parts[i].replace(/_/g, "-"); + } + + originalExpr = parts.join(".") + "#" + pointerPart; + } else { + const parts = match.split("."); + + if (parts.length > 0) { + parts[0] = parts[0].replace(/_/g, "."); + } + + for (let i = 1; i < parts.length; i++) { + parts[i] = parts[i].replace(/_/g, "-"); + } + + originalExpr = parts.join("."); + } + + const value = this.resolveExpression(originalExpr); + + // Convert string representations to actual types + let actualValue = value; + if (typeof value === "string") { + if (value === "true") actualValue = true; + else if (value === "false") actualValue = false; + else if (value === "null") actualValue = null; + else if (!isNaN(value) && value.trim() !== "") { + actualValue = Number(value); + } + } + + const escapedMatch = match.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + resolvedExpression = resolvedExpression.replace( + new RegExp(escapedMatch, "g"), + JSON.stringify(actualValue), + ); + } catch (err) { + console.warn(`Could not resolve ${match}:`, err.message); + } + } + } + + // Handle case-insensitive string comparisons with == + resolvedExpression = resolvedExpression.replace( + /"([^"]*)"\s*==\s*'([^']*)'/g, + (match, p1, p2) => `"${p1}".toLowerCase() == '${p2}'.toLowerCase()`, + ); + resolvedExpression = resolvedExpression.replace( + /"([^"]*)"\s*==\s*"([^"]*)"/g, + (match, p1, p2) => `"${p1}".toLowerCase() == "${p2}".toLowerCase()`, + ); + resolvedExpression = resolvedExpression.replace( + /'([^']*)'\s*==\s*'([^']*)'/g, + (match, p1, p2) => `'${p1}'.toLowerCase() == '${p2}'.toLowerCase()`, + ); + resolvedExpression = resolvedExpression.replace( + /'([^']*)'\s*==\s*"([^"]*)"/g, + (match, p1, p2) => `'${p1}'.toLowerCase() == "${p2}".toLowerCase()`, + ); + + try { + // eslint-disable-next-line no-eval + return eval(resolvedExpression); + } catch (err) { + throw new Error(`Failed to evaluate expression: ${err.message}`); + } + } + + /** + * Runs a check on a runtime expression from a regex Criterion Object + * @public + * @param {string} expression - The runtime expression to resolve + */ + checkRegexExpression(expression, pattern) { + try { + const value = this.resolveExpression(expression); + const regex = new RegExp(pattern); + return regex.test(String(value)); + } catch (e) { + console.error("Error evaluating regex expression:", expression, e); + return false; + } + } + + /** + * Runs a check on a runtime expression from a JSON Path Criterion Object + * @public + * @param {string} expression - The runtime expression to resolve + */ + checkJSONPathExpression(expression, jsonPath) { + try { + const value = this.resolveExpression(expression); + const jp = require("jsonpath"); + return jp.query(value, jsonPath); + } catch (e) { + console.error("Error evaluating JSONPath expression:", expression, e); + return null; + } + } + + /** + * Resolves a runtime expression to its value in the context + * @public + * @param {string|Object|Array} expression - The runtime expression to resolve + * @returns {*} The resolved value + * @throws {Error} If expression is invalid or context path doesn't exist + */ + resolveExpression(expression) { + // Handle arrays recursively + if (Array.isArray(expression)) { + return expression.map((item) => this.resolveExpression(item)); + } + + // Handle objects recursively + if (typeof expression === "object" && expression !== null) { + const resolved = {}; + for (const [key, value] of Object.entries(expression)) { + resolved[key] = this.resolveExpression(value); + } + return resolved; + } + + // Handle strings (runtime expressions) + if (typeof expression !== "string") { + return expression; + } + + this.expression = expression; + + if (this.isARunTimeExpression()) { + return this.mapToContext(); + } + + const extractedExpression = extract(expression); + if (extractedExpression !== expression && test(extractedExpression)) { + this.expression = extractedExpression; + return this.mapToContext(); + } + + return expression; + } + + /** + * Adds data to the context under a specific type + * @public + * @param {string} type - The context type (e.g., 'inputs', 'response') + * @param {*} obj - The data to add + */ + addToContext(type, obj) { + if (Object.hasOwn(this.context, type)) { + if (Array.isArray(this.context[type])) { + if (Array.isArray(obj)) { + this.context[type].push(...obj); + } else { + this.context[type].push(obj); + } + } else if ( + typeof this.context[type] === "object" && + typeof obj === "object" + ) { + Object.assign(this.context[type], obj); + } else { + this.context[type] = obj; + } + } else { + this.context[type] = obj; + } + } + + /** + * @private + * @param {string} expression - The Criteria Condition expression + * @returns {string} + */ + normalisedExpression(expression) { + const normalisedSymbolsExpression = + this.normaliseSymbolsExpression(expression); + const cleanedJsExpression = normalisedSymbolsExpression.replace( + /{(.*?)}/g, + "$1", + ); + const expressionWithBrackets = + this.convertNumericIndices(cleanedJsExpression); + const headerParameterNameRegex = /\.header\.([a-zA-Z0-9._-]+)/g; + const normalisedExpression = expressionWithBrackets.replace( + headerParameterNameRegex, + (_match, p1) => { + return `.header.${p1.toLowerCase()}`; + }, + ); + + return normalisedExpression; + } + + /** + * Alters the expression by replacing hyphens with underscores + * @private + * @param {string} expression - The Criteria Condition expression + * @returns {string} + */ + normaliseSymbolsExpression(expression) { + return expression.replace(/\$([a-zA-Z0-9._-]+)/g, (_match, variable) => { + const normalisedKey = variable.replace(/-/g, "_"); + return `$${normalisedKey}`; + }); + } + + /** + * Alters the expression to match a dot followed by a number (.1) and change to [1] + * @private + * @param {string} expression - The Criteria Condition expression + * @returns {string} + */ + convertNumericIndices(expression) { + return expression.replace(/\.(\d+)/g, (match, num, offset, str) => { + const charBeforeDot = str[offset - 1]; + const isFloat = /\d/.test(charBeforeDot); + return isFloat ? match : `[${num}]`; + }); + } + + /** + * @private + * @returns {*} + */ + normaliseContext() { + const normalised = {}; + + for (const [key, value] of Object.entries(this.context)) { + const normalisedKey = `$${key.replace(/-/g, "_")}`; + normalised[normalisedKey] = this.normaliseValue(value); + } + + return normalised; + } + + /** + * Normalise values recursively, handling objects and primitives + * @private + * @param {any} value + * @returns {*} + */ + normaliseValue(value) { + if (Array.isArray(value)) { + return value; + } else if (typeof value === "object" && value !== null) { + return this.normaliseObject(value); + } + return value; + } + + /** + * Normalise an object by replacing hyphens with underscores in keys + * @private + * @param {*} obj + * @returns + */ + normaliseObject(obj) { + return Object.keys(obj).reduce((acc, key) => { + const normalisedKey = key.replace(/-/g, "_"); + acc[normalisedKey] = this.normaliseValue(obj[key]); + return acc; + }, {}); + } + + /** + * Maps the parsed expression to the corresponding context value + * @private + * @returns {*} The value from context + * @throws {Error} If context path is missing or invalid + */ + mapToContext() { + const { normalised, contextName, pointer, token } = this.mapParts(); + + if (!normalised) { + throw new Error(`Unable to resolve expression: ${this.expression}`); + } + + let contextData = this.context[normalised]; + let foundInContext = contextData !== undefined; + + // If not found as flat key and key has dots, try nested access + if (!foundInContext && normalised.includes(".")) { + const parts = normalised.split("."); + contextData = this.context[parts[0]]; + + if (contextData !== undefined) { + foundInContext = true; + for (let i = 1; i < parts.length; i++) { + const nextValue = contextData?.[parts[i]]; + + // Check if property exists (not just undefined value) + if (nextValue === undefined && !(parts[i] in contextData)) { + foundInContext = false; + contextData = undefined; + break; + } + + contextData = nextValue; + } + } else { + foundInContext = false; + } + } + + if (!foundInContext) { + throw new Error( + `Context '${normalised}' not found for expression: ${this.expression}`, + ); + } + + if (this.isSimple) { + const propName = contextName?.split("#")[0]; + + if (!propName) { + return contextData; + } + + const propertyPath = propName.split("."); + let value = contextData; + + for (const prop of propertyPath) { + if (value === undefined || value === null) { + return undefined; + } + value = value[prop]; + } + + if (pointer) { + try { + return evaluate(value, pointer); + } catch (err) { + throw new Error(`Invalid JSON pointer '${pointer}': ${err.message}`); + } + } + + return value; + } + + if (pointer) { + if (!contextData) { + throw new Error(`Context path '${normalised}' not found`); + } + + try { + return evaluate(contextData, pointer); + } catch (err) { + throw new Error(`Invalid JSON pointer '${pointer}': ${err.message}`); + } + } + + if (token) { + if (!contextData) { + throw new Error(`Context path '${normalised}' not found`); + } + + if (typeof contextData.get === "function") { + return contextData.get(token); + } + + return contextData[token]; + } + + return contextData; + } + + /** + * Tests if the expression is a runtime expression + * @private + * @returns {boolean} + */ + isARunTimeExpression() { + try { + return test(this.expression); + } catch (err) { + return false; + } + } + + /** + * Parses the expression into its component parts + * @private + * @returns {{normalised: string, contextName: string, pointer: string, token: string}} + * @throws {Error} If parsing fails + */ + mapParts() { + let parsedExpression; + try { + parsedExpression = parse(this.expression); + } catch (err) { + throw new Error( + `Failed to parse expression '${this.expression}': ${err.message}`, + ); + } + + const parts = []; + parsedExpression.ast.translate(parts); + + if (!parts.length) { + throw new Error(`No parts found in expression: ${this.expression}`); + } + + this.isSimple = false; + let expressionType; + let contextName = ""; + let pointer = null; + let token = null; + + for (const partType of parts) { + const [type, value] = partType; + + if (type === "expression") { + const firstPart = value.split(".")[0].split("#")[0]; + this.isSimple = this.simpleExpressions.includes(firstPart); + + if (this.isSimple) { + expressionType = firstPart; + } else { + const baseExpression = value.split("#")[0]; + const parts = baseExpression.split("."); + if ( + parts.length === 3 && + ["query", "header", "path"].includes(parts[1]) + ) { + expressionType = parts.slice(0, 2).join("."); + } else { + expressionType = baseExpression; + } + } + } + + if (this.isSimple) { + if (type === "name") { + contextName = value; + } + } else { + if (type === "source") { + contextName = value; + } + + if (type === "json-pointer") { + pointer = value; + } + + if (type === "token") { + token = value; + } + } + } + + if (this.isSimple && contextName?.includes("#")) { + const [name, ptr] = contextName.split("#"); + contextName = name; + pointer = ptr; + } + + const normalised = this.expressionMap[expressionType]; + + if (!normalised) { + throw new Error(`Unknown expression type: ${expressionType}`); + } + + return { normalised, contextName, pointer, token }; + } +} + +module.exports = Expression; diff --git a/src/OpenAPI.js b/src/OpenAPI.js index 465ef93..7c4e17c 100644 --- a/src/OpenAPI.js +++ b/src/OpenAPI.js @@ -47,61 +47,23 @@ class OpenAPI extends Document { } } - // mapInputs(inputs, step) { - // if (step.parameters) { - // this.mapParamsToInputs(inputs, step); - // } - - // if (step.requestBody) { - // this.mapRequestBodyToInputs(inputs, step); - // } - // } - - // mapParamsToInputs(inputs, step) { - // const headers = new Headers(); - // const queryParams = new URLSearchParams(); - - // for (const param of step.parameters) { - // if (this.matchesExpectedRunTimeExpression(param.value, '$inputs.')) { - // const inputName = param.value.split('.')[1]; - // const inputValue = inputs[inputName]; - - // if (param.in === 'header') { - // headers.append(param.name, inputValue); - // } else if (param.in === 'query') { - // queryParams.append(param.name, inputValue); - // } else if (param.in === 'path') { - // this.path = this.path.replace(`{${inputName}}`, inputValue) - // } - // } - // } - - // this.headers = headers; - // this.queryParams = queryParams; - // } - - // mapRequestBodyToInputs(inputs, step) { - // if (step.requestBody.contentType || Object.keys(this.operationDetails.requestBody.content) === 1) { - // for (const contentType in this.operationDetails.requestBody.content) { - // if (step.requestBody.contentType === contentType) { - // if (contentType === 'application/json') { - // const payload = structuredClonestep.requestBody.payload; - // traverse(payload).forEach(function(value) { - // if (this.matchesExpectedRunTimeExpression(value, '$inputs.')) { - // const inputName = param.value.split('.')[1]; - // const inputValue = inputs[inputName]; - - // this.update(inputValue); - // } - // }); - // this.payload = payload; - // } - // } - // } - // } else { - // throw new Error(`Too many contentTypes on ${this.operationDetails.operationId}, please add the targeted contentType to the Arazzo Documentation`); - // } - // } + async getSecurity() { + const pipeline = await this.JSONPicker('security', this.filePath); + + for await (const { value } of pipeline) { + if (value) + this.security = value; + } + + if (this.security) { + const componentPipeline = await this.JSONPicker('components', this.filePath); + for await (const { value } of componentPipeline) { + if (value.securitySchemes) { + console.log(value.securitySchemes) + } + } + } + } buildOperations() { this.operations = [] diff --git a/src/Rules.js b/src/Rules.js new file mode 100644 index 0000000..774f7f5 --- /dev/null +++ b/src/Rules.js @@ -0,0 +1,94 @@ +"use strict"; + +class Rules { + constructor(expression) { + this.expression = expression; + this.failureRules = []; + this.successRules = []; + } + + setWorkflowFailures(failureActions) { + this.workflowFailures = failureActions; + this.failureRules.push(...failureActions); + } + + setStepFailures(failureActions) { + this.stepFailures = failureActions; + this.failureRules.push(...failureActions); + } + + setWorkflowSuccess(successActions) { + this.workflowSuccesses = successActions; + this.successRules.push(...successActions); + } + + setStepSuccesses(successActions) { + this.stepSuccesses = successActions; + this.successRules.push(...successActions); + } + + runRules(successRules = false) { + if (successRules) { + this.buildSuccessRules(); + } else { + this.buildFailureRules(); + } + + const obj = {}; + + for (const rule of this.rules) { + if (rule.criteria) { + const passedCriteria = this.criteriaChecks(rule); + + if (passedCriteria.length === rule.criteria.length) { + if (rule.type === "end") { + obj.endWorkflow = true; + break; + } else if (rule.type === "goto") { + obj.goto = true; + if (rule.stepId) obj.stepId = rule.stepId; + else obj.workflowId = rule.workflowId; + } else { + obj.name = rule.name; + obj.retry = true; + if (rule.stepId) obj.stepId = rule.stepId; + if (rule.workflowId) obj.workflowId = rule.workflowId; + obj.retryLimit = rule?.retryLimit || 1; + if (rule.retryAfter) obj.retryAfter = rule.retryAfter; + } + } + } + } + + return obj; + } + + criteriaChecks(rule) { + const passedCriteria = []; + + for (const criteriaObject of rule.criteria) { + if (criteriaObject?.type) { + } else { + const hasPassedCheck = this.expression.checkSimpleExpression( + criteriaObject.condition, + ); + // console.log(criteriaObject.condition); + if (hasPassedCheck) passedCriteria.push(hasPassedCheck); + } + } + + return passedCriteria; + } + + buildFailureRules() { + this.failureRules.reverse(); + this.rules = this.failureRules; + } + + buildSuccessRules() { + this.successRules.reverse(); + this.rules = this.successRules; + } +} + +module.exports = Rules; diff --git a/test/.mocharc.js b/test/.mocharc.js index 34bfb59..ffe7a23 100644 --- a/test/.mocharc.js +++ b/test/.mocharc.js @@ -1,9 +1,9 @@ -'use strict' +"use strict"; module.exports = { - recursive: true, - reporter: 'spec', - spec: 'test/unit/*.spec.js', - watch: false, - 'watch-files': ['src/**/*.js', 'test/**/*.spec.js'], -} + recursive: true, + reporter: "spec", + spec: "test/unit/*.spec.js", + watch: false, + "watch-files": ["src/**/*.js", "test/**/*.spec.js"], +}; diff --git a/test/mocks/correctSourceDescriptionReference-pets-arazzo.json b/test/mocks/correctSourceDescriptionReference-pets-arazzo.json new file mode 100644 index 0000000..3733033 --- /dev/null +++ b/test/mocks/correctSourceDescriptionReference-pets-arazzo.json @@ -0,0 +1,51 @@ +{ + "arazzo": "1.0.1", + "info": { + "title": "pets", + "description": "This is an example of an Arazzo Specification", + "summary": "Example Arazzo Specification", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "pets-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-pets/openapi.json", + "type": "openapi" + }, + { + "name": "usersOpenAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "loginUserAndRetrievePet", + "summary": "Login User and then retrieve pets", + "description": "This workflow lays out the steps to login a user and then retrieve pets", + "inputs": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "password": { "type": "string" }, + "petId": { "type": "integer" } + } + }, + "steps": [ + { + "stepId": "loginStep", + "description": "This step demonstrates the user login step", + "operationId": "$sourceDescriptions.usersOpenAPI.loginUser", + "requestBody": { + "contentType": "application/json", + "payload": { + "username": "$inputs.username", + "password": "$inputs.password" + } + }, + "outputs": { "AccessToken": "$response.body/AccessToken" } + } + ] + } + ] +} diff --git a/test/mocks/incorrectSourceDescriptionReference-pets-arazzo.json b/test/mocks/incorrectSourceDescriptionReference-pets-arazzo.json new file mode 100644 index 0000000..2555b9a --- /dev/null +++ b/test/mocks/incorrectSourceDescriptionReference-pets-arazzo.json @@ -0,0 +1,59 @@ +{ + "arazzo": "1.0.1", + "info": { + "title": "pets", + "description": "This is an example of an Arazzo Specification", + "summary": "Example Arazzo Specification", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "pets-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-pets/openapi.json", + "type": "openapi" + }, + { + "name": "usersOpenAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "loginUserAndRetrievePet", + "summary": "Login User and then retrieve pets", + "description": "This workflow lays out the steps to login a user and then retrieve pets", + "inputs": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "password": { "type": "string" }, + "petId": { "type": "integer" } + } + }, + "steps": [ + { + "stepId": "loginStep", + "description": "This step demonstrates the user login step", + "operationId": "$sourceDescriptions.usersOpenAP.loginUser", + "requestBody": { + "contentType": "application/json", + "payload": { + "username": "$inputs.username", + "password": "$inputs.password" + } + }, + "outputs": { "AccessToken": "$response.body/AccessToken" } + }, + { + "stepId": "getPet", + "description": "This step brings the pet back by id", + "operationId": "getPetById", + "parameters": [ + { "name": "petId", "in": "path", "value": "$inputs.petId" } + ] + } + ] + } + ] +} diff --git a/test/mocks/invalidInput.json b/test/mocks/inputs/invalidInput.json similarity index 100% rename from test/mocks/invalidInput.json rename to test/mocks/inputs/invalidInput.json diff --git a/test/mocks/inputs/multiInput.json b/test/mocks/inputs/multiInput.json new file mode 100644 index 0000000..fe75ae8 --- /dev/null +++ b/test/mocks/inputs/multiInput.json @@ -0,0 +1,18 @@ +{ + "createUserMultiStep": { + "user": { + "username": "DannyB", + "firstName": "Danny", + "lastName": "Brown", + "email": "dannyb@example.com", + "phone": "+443333333333", + "userStatus": 1 + } + }, + "loginUser": { + "password": "--p4ssW0rd" + }, + "findPetByTag": { + "tags": "tag1" + } +} diff --git a/test/mocks/inputs/petsInput.json b/test/mocks/inputs/petsInput.json new file mode 100644 index 0000000..1e80a10 --- /dev/null +++ b/test/mocks/inputs/petsInput.json @@ -0,0 +1,7 @@ +{ + "loginUserAndRetrievePet": { + "username": "FatBoyS", + "password": "--p4ssW0rd", + "petId": 12345 + } +} diff --git a/test/mocks/inputs/userInput.json b/test/mocks/inputs/userInput.json new file mode 100644 index 0000000..acb88f7 --- /dev/null +++ b/test/mocks/inputs/userInput.json @@ -0,0 +1,130 @@ +{ + "createUser": { + "user": { + "username": "DannyB", + "firstName": "Danny", + "lastName": "Brown", + "email": "dannyb@example.com", + "phone": "+443333333333", + "userStatus": 1 + } + }, + "createUserMultiStep": { + "user": { + "username": "DannyB", + "firstName": "Danny", + "lastName": "Brown", + "email": "dannyb@example.com", + "phone": "+443333333333", + "userStatus": 1 + }, + "username": "DannyB" + }, + "LoginNewUser": { + "user": { + "username": "FatboyS", + "firstName": "Norman", + "lastName": "Cook", + "email": "NormanC@example.com", + "phone": "+441111111111", + "userStatus": 1 + }, + "username": "FatboyS", + "password": "--p4ssW0rd" + }, + "createUsersByArray": { + "username": "FatboyS", + "password": "--p4ssW0rd", + "userArray": [ + { + "username": "SlimS", + "firstName": "Marshall", + "lastName": "Mathers", + "email": "MarshamM@example.com", + "phone": "+442222222222", + "userStatus": 1 + }, + { + "username": "IceC", + "firstName": "O'Shea", + "lastName": "Jackson", + "email": "OSheaJ@example.com", + "phone": "+444444444444", + "userStatus": 1 + } + ] + }, + "createUsersByList": { + "username": "FatboyS", + "password": "--p4ssW0rd", + "userArray": [ + { + "username": "DrDre", + "firstName": "Andre", + "lastName": "Young", + "email": "AndreY@example.com", + "phone": "+445555555555", + "userStatus": 1 + }, + { + "username": "KDot", + "firstName": "Kendrick", + "lastName": "Lamar", + "email": "KendrickL@example.com", + "phone": "+446666666666", + "userStatus": 1 + } + ] + }, + "loginExistingUser": { + "username": "DannyB", + "password": "--p4ssW0rd" + }, + "loginAndOutUser": { + "username": "DannyB", + "password": "--p4ssW0rd" + }, + "getUser": { + "username": "KDot" + }, + "deleteCurrentUser": { + "username": "DrDre", + "password": "--p4ssW0rd" + }, + "updateCurrentUser": { + "username": "SlimS", + "password": "--p4ssW0rd", + "firstName": "Slim" + }, + "loginUser": { + "password": "--p4ssW0rd" + }, + "createUserWithMoreSteps": { + "user": { + "username": "DannyB", + "firstName": "Danny", + "lastName": "Brown", + "email": "dannyb@example.com", + "phone": "+443333333333", + "userStatus": 1 + }, + "userArray": [ + { + "username": "SlimS", + "firstName": "Marshall", + "lastName": "Mathers", + "email": "MarshamM@example.com", + "phone": "+442222222222", + "userStatus": 1 + }, + { + "username": "IceC", + "firstName": "O'Shea", + "lastName": "Jackson", + "email": "OSheaJ@example.com", + "phone": "+444444444444", + "userStatus": 1 + } + ] + } +} diff --git a/test/mocks/validInput.json b/test/mocks/inputs/validInput.json similarity index 100% rename from test/mocks/validInput.json rename to test/mocks/inputs/validInput.json diff --git a/test/mocks/multiple-workflows/multiple-sourceDescription/arazzoMock-user-multiple-workflow.json b/test/mocks/multiple-workflows/multiple-sourceDescription/arazzoMock-user-multiple-workflow.json new file mode 100644 index 0000000..c7d3894 --- /dev/null +++ b/test/mocks/multiple-workflows/multiple-sourceDescription/arazzoMock-user-multiple-workflow.json @@ -0,0 +1,123 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + }, + { + "name": "pets-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-pets/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUserMultiStep", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "$sourceDescriptions.users-openAPI.createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "outputs": { "username": "$response.body#/username" }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ] + } + ], + "outputs": { + "username": "$steps.createAUser.outputs.username" + } + }, + { + "workflowId": "loginUser", + "summary": "Logs a user in", + "description": "Logs a user in", + "inputs": { + "type": "object", + "properties": { + "password": { "type": "string" } + } + }, + "steps": [ + { + "stepId": "LogUserIn", + "operationId": "$sourceDescriptions.users-openAPI.loginUser", + "requestBody": { + "contentType": "application/json", + "payload": { + "username": "$workflows.createUserMultiStep.outputs.username", + "password": "$inputs.password" + } + }, + "successCriteria": [ + { + "condition": "$statusCode == 200" + } + ] + } + ] + }, + { + "workflowId": "findPetByTag", + "summary": "Finds a pet by a tag", + "description": "Finds a pet by a tag", + "inputs": { + "type": "object", + "properties": { + "tags": { "type": "string" } + } + }, + "steps": [ + { + "stepId": "findAPetByTag", + "operationId": "$sourceDescriptions.pets-openAPI.findPetsByTags", + "parameters": [ + { + "in": "query", + "name": "tags", + "value": "$inputs.tags" + } + ], + "successCriteria": [ + { + "condition": "$statusCode == 200" + } + ] + } + ] + } + ] +} diff --git a/test/mocks/multiple-workflows/single-sourceDescription/arazzoMock-user-multiple-workflow-multiple-step.json b/test/mocks/multiple-workflows/single-sourceDescription/arazzoMock-user-multiple-workflow-multiple-step.json new file mode 100644 index 0000000..e808d93 --- /dev/null +++ b/test/mocks/multiple-workflows/single-sourceDescription/arazzoMock-user-multiple-workflow-multiple-step.json @@ -0,0 +1,89 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUserMultiStep", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "outputs": { "username": "$response.body#/username" }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ] + } + ], + "outputs": { + "username": "$steps.createAUser.outputs.username" + } + }, + { + "workflowId": "loginUser", + "summary": "Logs a user in", + "description": "Logs a user in", + "inputs": { + "type": "object", + "properties": { + "password": { "type": "string" } + } + }, + "steps": [ + { + "stepId": "LogUserIn", + "operationId": "loginUser", + "requestBody": { + "contentType": "application/json", + "payload": { + "username": "$workflows.createUserMultiStep.outputs.username", + "password": "$inputs.password" + } + }, + "successCriteria": [ + { + "condition": "$statusCode == 200" + } + ] + } + ] + } + ] +} diff --git a/test/mocks/multiple-workflows/single-sourceDescription/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-different-workflow.json b/test/mocks/multiple-workflows/single-sourceDescription/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-different-workflow.json new file mode 100644 index 0000000..0fb2b50 --- /dev/null +++ b/test/mocks/multiple-workflows/single-sourceDescription/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-different-workflow.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "onSuccess": [ + { + "name": "gotoLoginUser", + "type": "goto", + "workflowId": "loginUser", + "criteria": [ + { + "condition": "$statusCode == 201" + } + ] + } + ], + "outputs": { "username": "$response.body#/username" } + } + ], + "outputs": { + "username": "$steps.createAUser.outputs.username" + } + }, + { + "workflowId": "loginUser", + "summary": "Logs a user in", + "description": "Logs a user in", + "inputs": { + "type": "object", + "properties": { + "password": { "type": "string" } + } + }, + "steps": [ + { + "stepId": "LogUserIn", + "operationId": "loginUser", + "requestBody": { + "contentType": "application/json", + "payload": { + "username": "$workflows.createUser.outputs.username", + "password": "$inputs.password" + } + }, + "successCriteria": [ + { + "condition": "$statusCode == 200" + } + ] + } + ] + } + ] +} diff --git a/test/mocks/multiple-workflows/single-sourceDescription/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-execute-a-step-and-retry-workflow.json b/test/mocks/multiple-workflows/single-sourceDescription/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-execute-a-step-and-retry-workflow.json new file mode 100644 index 0000000..eb89441 --- /dev/null +++ b/test/mocks/multiple-workflows/single-sourceDescription/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-execute-a-step-and-retry-workflow.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUserMultiStep", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "outputs": { "username": "$response.body#/username" }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ] + } + ], + "outputs": { + "username": "$steps.createAUser.outputs.username" + } + }, + { + "workflowId": "loginUser", + "summary": "Logs a user in", + "description": "Logs a user in", + "inputs": { + "type": "object", + "properties": { + "password": { "type": "string" } + } + }, + "steps": [ + { + "stepId": "LogUserIn", + "operationId": "loginUser", + "requestBody": { + "contentType": "application/json", + "payload": { + "username": "$workflows.createUserMultiStep.outputs.username", + "password": "$inputs.password" + } + }, + "successCriteria": [ + { + "condition": "$statusCode == 200" + } + ], + "onFailure": [ + { + "name": "retryStepOnce", + "workflowId": "createUserMultiStep", + "type": "retry", + "criteria": [ + { + "condition": "$statusCode == 404" + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/mocks/multiple-workflows/single-sourceDescription/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-goto-different-workflow.json b/test/mocks/multiple-workflows/single-sourceDescription/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-goto-different-workflow.json new file mode 100644 index 0000000..bacd41e --- /dev/null +++ b/test/mocks/multiple-workflows/single-sourceDescription/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-goto-different-workflow.json @@ -0,0 +1,113 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "onSuccess": [ + { + "name": "gotoLoginUser", + "type": "goto", + "workflowId": "loginUser", + "criteria": [ + { + "condition": "$statusCode == 201" + } + ] + } + ], + "outputs": { "username": "$response.body#/username" } + } + ], + "outputs": { + "username": "$steps.createAUser.outputs.username" + } + }, + { + "workflowId": "loginUser", + "summary": "Logs a user in", + "description": "Logs a user in", + "inputs": { + "type": "object", + "properties": { + "password": { "type": "string" } + } + }, + "steps": [ + { + "stepId": "LogUserIn", + "operationId": "loginUser", + "requestBody": { + "contentType": "application/json", + "payload": { + "username": "$workflow.createUser.outputs.username", + "password": "$inputs.password" + } + }, + "successCriteria": [ + { + "condition": "$statusCode == 200" + } + ], + "onFailure": [ + { + "name": "gotoCreateUser", + "type": "goto", + "workflowId": "createUser", + "criteria": [ + { + "condition": "$statusCode == 400" + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/multiple-steps/arazzoMock-user-single-workflow-multiple-step-using-output-from-oneStep.json b/test/mocks/single-workflow/multiple-steps/arazzoMock-user-single-workflow-multiple-step-using-output-from-oneStep.json new file mode 100644 index 0000000..944322f --- /dev/null +++ b/test/mocks/single-workflow/multiple-steps/arazzoMock-user-single-workflow-multiple-step-using-output-from-oneStep.json @@ -0,0 +1,64 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUserMultiStep", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "outputs": { "username": "$response.body#/username" } + }, + { + "stepId": "getAUser", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "value": "$steps.createAUser.outputs.username" + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/multiple-steps/arazzoMock-user-single-workflow-multiple-step-with-successCriteria.json b/test/mocks/single-workflow/multiple-steps/arazzoMock-user-single-workflow-multiple-step-with-successCriteria.json new file mode 100644 index 0000000..ee3470e --- /dev/null +++ b/test/mocks/single-workflow/multiple-steps/arazzoMock-user-single-workflow-multiple-step-with-successCriteria.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUserMultiStep", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "outputs": { "username": "$response.body#/username" }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ] + }, + { + "stepId": "getAUser", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "value": "$steps.createAUser.outputs.username" + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/multiple-steps/arazzoMock-user-single-workflow-multiple-step.json b/test/mocks/single-workflow/multiple-steps/arazzoMock-user-single-workflow-multiple-step.json new file mode 100644 index 0000000..6177190 --- /dev/null +++ b/test/mocks/single-workflow/multiple-steps/arazzoMock-user-single-workflow-multiple-step.json @@ -0,0 +1,64 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUserMultiStep", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "outputs": { "id": "$response.body#/id" } + }, + { + "stepId": "getAUser", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "value": "$inputs.username" + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-end.json b/test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-end.json new file mode 100644 index 0000000..2b8ba08 --- /dev/null +++ b/test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-end.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "onSuccess": [ + { + "name": "endGame", + "type": "end", + "criteria": [ + { + "condition": "$statusCode == 201" + } + ] + } + ], + "outputs": { "id": "$response.body#/id" } + }, + { + "stepId": "getAUser", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "value": "$steps.createAUser.outputs.username" + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-sourceDescription-workflow.json b/test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-sourceDescription-workflow.json new file mode 100644 index 0000000..4c6b96e --- /dev/null +++ b/test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-sourceDescription-workflow.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "onSuccess": [ + { + "name": "noWhereToGo", + "type": "goto", + "workflowId": "$sourceDescriptions.nonExistant.nonExistant", + "criteria": [ + { + "condition": "$statusCode == 201" + } + ] + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-step.json b/test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-step.json new file mode 100644 index 0000000..34f62fc --- /dev/null +++ b/test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-step.json @@ -0,0 +1,81 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "onSuccess": [ + { + "name": "noWhereToGo", + "type": "goto", + "stepId": "nonExistant", + "criteria": [ + { + "condition": "$statusCode == 201" + } + ] + } + ], + "outputs": { "id": "$response.body#/id" } + }, + { + "stepId": "getAUser", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "value": "$steps.createAUser.outputs.username" + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-workflow.json b/test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-workflow.json new file mode 100644 index 0000000..93ca7c0 --- /dev/null +++ b/test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-workflow.json @@ -0,0 +1,81 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "onSuccess": [ + { + "name": "noWhereToGo", + "type": "goto", + "workflowId": "nonExistant", + "criteria": [ + { + "condition": "$statusCode == 201" + } + ] + } + ], + "outputs": { "id": "$response.body#/id" } + }, + { + "stepId": "getAUser", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "value": "$steps.createAUser.outputs.username" + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-self-referential.json b/test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-self-referential.json new file mode 100644 index 0000000..2c2d27f --- /dev/null +++ b/test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-self-referential.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "outputs": { "username": "$response.body#/username" } + }, + { + "stepId": "getAUser", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "value": "$steps.createAUser.outputs.username" + } + ], + "outputs": { "id": "$response.body#/id" }, + "successCriteria": [ + { + "condition": "$statusCode == 200" + } + ], + "onSuccess": [ + { + "name": "infiniteLoop", + "type": "goto", + "stepId": "getAUser", + "criteria": [ + { + "condition": "$statusCode == 200" + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/multiple-steps/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-execute-a-step-and-retry-do-not-execute-sequential-step.json b/test/mocks/single-workflow/multiple-steps/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-execute-a-step-and-retry-do-not-execute-sequential-step.json new file mode 100644 index 0000000..65f74f8 --- /dev/null +++ b/test/mocks/single-workflow/multiple-steps/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-execute-a-step-and-retry-do-not-execute-sequential-step.json @@ -0,0 +1,115 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUserWithMoreSteps", + "summary": "Create a few Users", + "description": "A Workflow to create a few Users", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + }, + "userArray": { + "type": "array", + "items": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "outputs": { "username": "$response.body#/username" } + }, + { + "stepId": "createAUserWithArray", + "operationId": "createUsersWithArrayInput", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.userArray" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "outputs": { "username": "$response.body#/username" } + }, + { + "stepId": "getAUser", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "value": "$steps.createAUser.outputs.username" + } + ], + "outputs": { "id": "$response.body#/id" }, + "successCriteria": [ + { + "condition": "$statusCode == 200" + } + ], + "onFailure": [ + { + "name": "retryStepOnce", + "stepId": "createAUser", + "type": "retry", + "criteria": [ + { + "condition": "$statusCode == 404" + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/multiple-steps/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-execute-a-step-and-retry.json b/test/mocks/single-workflow/multiple-steps/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-execute-a-step-and-retry.json new file mode 100644 index 0000000..c1007c0 --- /dev/null +++ b/test/mocks/single-workflow/multiple-steps/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-execute-a-step-and-retry.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "outputs": { "username": "$response.body#/username" } + }, + { + "stepId": "getAUser", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "value": "$steps.createAUser.outputs.username" + } + ], + "outputs": { "id": "$response.body#/id" }, + "successCriteria": [ + { + "condition": "$statusCode == 200" + } + ], + "onFailure": [ + { + "name": "retryStepOnce", + "stepId": "createAUser", + "type": "retry", + "criteria": [ + { + "condition": "$statusCode == 404" + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/single-step/arazzoMock-user-single-workflow-single-step copy.json b/test/mocks/single-workflow/single-step/arazzoMock-user-single-workflow-single-step copy.json new file mode 100644 index 0000000..d52efa7 --- /dev/null +++ b/test/mocks/single-workflow/single-step/arazzoMock-user-single-workflow-single-step copy.json @@ -0,0 +1,54 @@ +{ + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUsersByArray", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "userArray": { + "type": "array", + "items": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUsersWithArrayInput", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.userArray" + }, + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/single-step/arazzoMock-user-single-workflow-single-step-with-successCriteria-and-onSuccess-set-to-end.json b/test/mocks/single-workflow/single-step/arazzoMock-user-single-workflow-single-step-with-successCriteria-and-onSuccess-set-to-end.json new file mode 100644 index 0000000..71fca4f --- /dev/null +++ b/test/mocks/single-workflow/single-step/arazzoMock-user-single-workflow-single-step-with-successCriteria-and-onSuccess-set-to-end.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "onSuccess": [ + { + "name": "endGame", + "type": "end", + "criteria": [ + { + "condition": "$statusCode == 201" + } + ] + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/single-step/arazzoMock-user-single-workflow-single-step-with-successCriteria.json b/test/mocks/single-workflow/single-step/arazzoMock-user-single-workflow-single-step-with-successCriteria.json new file mode 100644 index 0000000..a73c3f1 --- /dev/null +++ b/test/mocks/single-workflow/single-step/arazzoMock-user-single-workflow-single-step-with-successCriteria.json @@ -0,0 +1,56 @@ +{ + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 200" + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/single-step/arazzoMock-user-single-workflow-single-step.json b/test/mocks/single-workflow/single-step/arazzoMock-user-single-workflow-single-step.json new file mode 100644 index 0000000..e5d3cbb --- /dev/null +++ b/test/mocks/single-workflow/single-step/arazzoMock-user-single-workflow-single-step.json @@ -0,0 +1,51 @@ +{ + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/single-step/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-sourceDescription-workflow.json b/test/mocks/single-workflow/single-step/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-sourceDescription-workflow.json new file mode 100644 index 0000000..73cc288 --- /dev/null +++ b/test/mocks/single-workflow/single-step/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-sourceDescription-workflow.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "outputs": { "id": "$response.body#/id" } + }, + { + "stepId": "getAUser", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "value": "$steps.createAUser.outputs.username" + } + ], + "successCriteria": [ + { + "condition": "$statusCode == 200" + } + ], + "outputs": { "id": "$response.body#/id" }, + "onSuccess": [ + { + "name": "noWhereToGo", + "type": "goto", + "workflowId": "$sourceDescriptions.nonExistant.nonExistant", + "criteria": [ + { + "condition": "$statusCode == 200" + } + ] + } + ] + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/single-step/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-step.json b/test/mocks/single-workflow/single-step/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-step.json new file mode 100644 index 0000000..046b982 --- /dev/null +++ b/test/mocks/single-workflow/single-step/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-step.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "onSuccess": [ + { + "name": "noWhereToGo", + "type": "goto", + "stepId": "nonExistant", + "criteria": [ + { + "condition": "$statusCode == 201" + } + ] + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/single-step/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-workflow.json b/test/mocks/single-workflow/single-step/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-workflow.json new file mode 100644 index 0000000..2f24371 --- /dev/null +++ b/test/mocks/single-workflow/single-step/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-workflow.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "onSuccess": [ + { + "name": "noWhereToGo", + "type": "goto", + "workflowId": "nonExistant", + "criteria": [ + { + "condition": "$statusCode == 201" + } + ] + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/single-step/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-self-referential.json b/test/mocks/single-workflow/single-step/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-self-referential.json new file mode 100644 index 0000000..a8e262c --- /dev/null +++ b/test/mocks/single-workflow/single-step/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-self-referential.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "onSuccess": [ + { + "name": "infiniteLoop", + "type": "goto", + "stepId": "createAUser", + "criteria": [ + { + "condition": "$statusCode == 201" + } + ] + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-end.json b/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-end.json new file mode 100644 index 0000000..5474084 --- /dev/null +++ b/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-end.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 200" + } + ], + "onFailure": [ + { + "name": "endGame", + "type": "end", + "criteria": [ + { + "condition": "$statusCode == 400" + } + ] + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-goto-non-existant-step.json b/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-goto-non-existant-step.json new file mode 100644 index 0000000..8c8b878 --- /dev/null +++ b/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-goto-non-existant-step.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "onFailure": [ + { + "name": "noWhereToGo", + "type": "goto", + "stepId": "nonExistant", + "criteria": [ + { + "condition": "$statusCode == 400" + } + ] + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-goto-non-existant-workflow.json b/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-goto-non-existant-workflow.json new file mode 100644 index 0000000..2e1df49 --- /dev/null +++ b/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-goto-non-existant-workflow.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "onSuccess": [ + { + "name": "noWhereToGo", + "type": "goto", + "workflowId": "nonExistant", + "criteria": [ + { + "condition": "$statusCode == 400" + } + ] + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-goto-self-referential.json b/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-goto-self-referential.json new file mode 100644 index 0000000..fc3d36a --- /dev/null +++ b/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-goto-self-referential.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "onFailure": [ + { + "name": "infiniteLoop", + "type": "goto", + "stepId": "createAUser", + "criteria": [ + { + "condition": "$statusCode == 400" + } + ] + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry-multiple-times.json b/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry-multiple-times.json new file mode 100644 index 0000000..1f0e65d --- /dev/null +++ b/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry-multiple-times.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "onFailure": [ + { + "name": "retryStepMultipleTimes", + "type": "retry", + "retryLimit": 3, + "criteria": [ + { + "condition": "$statusCode == 400" + } + ] + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry-with-delay.json b/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry-with-delay.json new file mode 100644 index 0000000..019ce70 --- /dev/null +++ b/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry-with-delay.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "onFailure": [ + { + "name": "retryStepWithDelay", + "type": "retry", + "retryAfter": 5, + "criteria": [ + { + "condition": "$statusCode == 400" + } + ] + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry-with-different-types.json b/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry-with-different-types.json new file mode 100644 index 0000000..8275f57 --- /dev/null +++ b/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry-with-different-types.json @@ -0,0 +1,78 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "onFailure": [ + { + "name": "retry400", + "type": "retry", + "retryLimit": 3, + "criteria": [ + { + "condition": "$statusCode == 400" + } + ] + }, + { + "name": "retry404", + "type": "retry", + "criteria": [ + { + "condition": "$statusCode == 404" + } + ] + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry.json b/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry.json new file mode 100644 index 0000000..f0ac1ab --- /dev/null +++ b/test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://spec.openapis.org/arazzo/1.0/schema/2025-10-15", + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "successCriteria": [ + { + "condition": "$statusCode == 201" + } + ], + "onFailure": [ + { + "name": "retryStepOnce", + "type": "retry", + "criteria": [ + { + "condition": "$statusCode == 400" + } + ] + } + ], + "outputs": { "id": "$response.body#/id" } + } + ] + } + ] +} diff --git a/test/mocks/validInputById.json b/test/mocks/validInputById.json deleted file mode 100644 index 89a90fc..0000000 --- a/test/mocks/validInputById.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "getById": { - "id": 123 - } -} diff --git a/test/serverless-pets/input.json b/test/serverless-pets/input.json index aab9853..03a4a60 100644 --- a/test/serverless-pets/input.json +++ b/test/serverless-pets/input.json @@ -1,5 +1,6 @@ { - "findPetByStatus": { - "status": "pending" + "loginUserAndRetrievePet": { + "username": "FatBoyS", + "petId": "12345" } } diff --git a/test/serverless-pets/serverless.yml b/test/serverless-pets/serverless.yml index 823a0b5..78342bb 100644 --- a/test/serverless-pets/serverless.yml +++ b/test/serverless-pets/serverless.yml @@ -274,7 +274,7 @@ custom: # type: string sourceDescriptions: - name: usersOpenAPI - url: ./openapi.json + url: https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json type: openapi workflows: - workflowId: loginUserAndRetrievePet diff --git a/test/serverless-users/arazzo.json b/test/serverless-users/arazzo.json index 381c27c..6764fe0 100644 --- a/test/serverless-users/arazzo.json +++ b/test/serverless-users/arazzo.json @@ -1 +1,325 @@ -{"arazzo":"1.0.1","info":{"title":"users","description":"The Arazzo Workflow for a Pet Store User","summary":"Araazo Workflow for Pet Store User","version":"1.0.0"},"sourceDescriptions":[{"name":"users-openAPI","url":"openapi.json","type":"openapi"}],"workflows":[{"workflowId":"createUser","summary":"Create a new User","description":"A Workflow to create a new User","inputs":{"type":"object","properties":{"user":{"type":"object","properties":{"username":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"},"phone":{"type":"string"},"userStatus":{"type":"integer"}}}}},"steps":[{"stepId":"createAUser","operationId":"createUser","requestBody":{"contentType":"application/json","payload":"$inputs.user"},"outputs":{"id":"$response.body#/id"}}]},{"workflowId":"LoginNewUser","summary":"Create and Login a new User","description":"Create a new User and Log Them in","inputs":{"type":"object","properties":{"user":{"type":"object","properties":{"username":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"},"phone":{"type":"string"},"userStatus":{"type":"integer"}}},"username":{"type":"string"},"password":{"type":"string"}}},"steps":[{"stepId":"createUser","workflowId":"$sourceDescriptions.users-openAPI.createUser"},{"stepId":"LoginNewUser","operationId":"loginUser","requestBody":{"contentType":"application/json","payload":{"username":"$inputs.username","password":"$inputs.password"}}}]},{"workflowId":"createUsersByArray","summary":"Create new users from an Array","description":"Create New Users from an Array","inputs":{"type":"object","properties":{"userArray":{"type":"array","items":{"type":"object","properties":{"username":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"},"phone":{"type":"string"},"userStatus":{"type":"integer"}}}}}},"steps":[{"stepId":"Login","workflowId":"$sourceDescriptions.users-openAPI.LoginExistingUser"},{"stepId":"createAUserArray","operationId":"createWithArray","parameters":[{"name":"Authorization","in":"header","value":"$workflows.LoginExistingUser.outputs.AccessToken"}],"requestBody":{"contentType":"application/json","payload":"$inputs.userArray"}}]},{"workflowId":"createUsersByList","summary":"Create new users from a list","description":"Creates new users from a List","inputs":{"type":"object","properties":{"userList":{"type":"array","items":{"type":"object","properties":{"username":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"},"phone":{"type":"string"},"userStatus":{"type":"integer"}}}}}},"steps":[{"stepId":"Login","workflowId":"$sourceDescriptions.users-openAPI.LoginExistingUser"},{"stepId":"createAUserList","operationId":"createWithList","parameters":[{"name":"Authorization","in":"header","value":"$workflows.LoginExistingUser.outputs.AccessToken"}],"requestBody":{"contentType":"application/json","payload":"$inputs.userList"}}]},{"workflowId":"loginExistingUser","summary":"Logs in an existing user","description":"Logs in an existing user","inputs":{"type":"object","properties":{"username":{"type":"string"},"password":{"type":"string"}}},"outputs":{"AccessToken":"$steps.LoginExistingUser.outputs.AccessToken"},"steps":[{"stepId":"LoginExistingUser","operationId":"loginUser","requestBody":{"contentType":"application/json","payload":{"username":"$inputs.username","password":"$inputs.password"}},"outputs":{"AccessToken":"$response.body#/AccessToken"}}]},{"workflowId":"loginAndOutUser","summary":"Logs a user in and then logs them out","description":"Logs a user in and then logs them out","inputs":{"type":"object","properties":{"username":{"type":"string"},"password":{"type":"string"}}},"steps":[{"stepId":"Login","workflowId":"$sourceDescriptions.users-openAPI.LoginExistingUser"},{"stepId":"LogoutUser","operationId":"logout","parameters":[{"name":"Authorization","in":"header","value":"$workflows.LoginExistingUser.outputs.AccessToken"}]}]},{"workflowId":"getUser","summary":"Gets a user by username","description":"Gets a user by username","inputs":{"type":"object","properties":{"username":{"type":"string"}}},"steps":[{"stepId":"getAUser","operationId":"getUserByName","parameters":[{"name":"username","in":"path","value":"$inputs.username"}]}]},{"workflowId":"deleteCurrentUser","summary":"Deletes the current user","description":"Logs the user in and then deletes them","inputs":{"type":"object","properties":{"username":{"type":"string"},"password":{"type":"string"}}},"steps":[{"stepId":"LoginUser","workflowId":"$sourceDescriptions.users-openAPI.loginExistingUser"},{"stepId":"deleteUser","operationId":"deleteUser","parameters":[{"name":"Authorization","in":"header","value":"$workflows.LoginExistingUser.outputs.AccessToken"},{"name":"username","in":"path","value":"$inputs.username"}]}]},{"workflowId":"updateCurrentUser","summary":"Update the current user","description":"Login and update the current user","inputs":{"type":"object","properties":{"username":{"type":"string"},"password":{"type":"string"},"firstName":{"type":"string"}}},"steps":[{"stepId":"Login","workflowId":"$sourceDescriptions.users-openAPI.LoginExistingUser"},{"stepId":"deleteUser","operationId":"updateUser","parameters":[{"name":"Authorization","in":"header","value":"$workflows.LoginExistingUser.outputs.AccessToken"},{"name":"username","in":"path","value":"$inputs.username"}],"requestBody":{"contentType":"application/json","payload":{"firstName":"$inputs.firstName"}}}]}]} \ No newline at end of file +{ + "arazzo": "1.0.1", + "info": { + "title": "users", + "description": "The Arazzo Workflow for a Pet Store User", + "summary": "Araazo Workflow for Pet Store User", + "version": "1.0.0" + }, + "sourceDescriptions": [ + { + "name": "users-openAPI", + "url": "https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + "type": "openapi" + } + ], + "workflows": [ + { + "workflowId": "createUser", + "summary": "Create a new User", + "description": "A Workflow to create a new User", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + }, + "steps": [ + { + "stepId": "createAUser", + "operationId": "createUser", + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.user" + }, + "outputs": { "id": "$response.body#/id" } + } + ] + }, + { + "workflowId": "LoginNewUser", + "summary": "Create and Login a new User", + "description": "Create a new User and Log Them in", + "inputs": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + }, + "username": { "type": "string" }, + "password": { "type": "string" } + } + }, + "steps": [ + { + "stepId": "createUser", + "workflowId": "$sourceDescriptions.users-openAPI.createUser" + }, + { + "stepId": "LoginNewUser", + "operationId": "loginUser", + "requestBody": { + "contentType": "application/json", + "payload": { + "username": "$inputs.username", + "password": "$inputs.password" + } + } + } + ] + }, + { + "workflowId": "createUsersByArray", + "summary": "Create new users from an Array", + "description": "Create New Users from an Array", + "inputs": { + "type": "object", + "properties": { + "userArray": { + "type": "array", + "items": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + } + }, + "steps": [ + { + "stepId": "Login", + "workflowId": "$sourceDescriptions.users-openAPI.LoginExistingUser" + }, + { + "stepId": "createAUserArray", + "operationId": "createWithArray", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "value": "$workflows.LoginExistingUser.outputs.AccessToken" + } + ], + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.userArray" + } + } + ] + }, + { + "workflowId": "createUsersByList", + "summary": "Create new users from a list", + "description": "Creates new users from a List", + "inputs": { + "type": "object", + "properties": { + "userList": { + "type": "array", + "items": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "firstName": { "type": "string" }, + "lastName": { "type": "string" }, + "email": { "type": "string" }, + "password": { "type": "string" }, + "phone": { "type": "string" }, + "userStatus": { "type": "integer" } + } + } + } + } + }, + "steps": [ + { + "stepId": "Login", + "workflowId": "$sourceDescriptions.users-openAPI.LoginExistingUser" + }, + { + "stepId": "createAUserList", + "operationId": "createWithList", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "value": "$workflows.LoginExistingUser.outputs.AccessToken" + } + ], + "requestBody": { + "contentType": "application/json", + "payload": "$inputs.userList" + } + } + ] + }, + { + "workflowId": "loginExistingUser", + "summary": "Logs in an existing user", + "description": "Logs in an existing user", + "inputs": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "password": { "type": "string" } + } + }, + "outputs": { + "AccessToken": "$steps.LoginExistingUser.outputs.AccessToken" + }, + "steps": [ + { + "stepId": "LoginExistingUser", + "operationId": "loginUser", + "requestBody": { + "contentType": "application/json", + "payload": { + "username": "$inputs.username", + "password": "$inputs.password" + } + }, + "outputs": { "AccessToken": "$response.body#/AccessToken" } + } + ] + }, + { + "workflowId": "loginAndOutUser", + "summary": "Logs a user in and then logs them out", + "description": "Logs a user in and then logs them out", + "inputs": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "password": { "type": "string" } + } + }, + "steps": [ + { + "stepId": "Login", + "workflowId": "$sourceDescriptions.users-openAPI.LoginExistingUser" + }, + { + "stepId": "LogoutUser", + "operationId": "logout", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "value": "$workflows.LoginExistingUser.outputs.AccessToken" + } + ] + } + ] + }, + { + "workflowId": "getUser", + "summary": "Gets a user by username", + "description": "Gets a user by username", + "inputs": { + "type": "object", + "properties": { "username": { "type": "string" } } + }, + "steps": [ + { + "stepId": "getAUser", + "operationId": "getUserByName", + "parameters": [ + { "name": "username", "in": "path", "value": "$inputs.username" } + ] + } + ] + }, + { + "workflowId": "deleteCurrentUser", + "summary": "Deletes the current user", + "description": "Logs the user in and then deletes them", + "inputs": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "password": { "type": "string" } + } + }, + "steps": [ + { + "stepId": "LoginUser", + "workflowId": "$sourceDescriptions.users-openAPI.loginExistingUser" + }, + { + "stepId": "deleteUser", + "operationId": "deleteUser", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "value": "$workflows.LoginExistingUser.outputs.AccessToken" + }, + { "name": "username", "in": "path", "value": "$inputs.username" } + ] + } + ] + }, + { + "workflowId": "updateCurrentUser", + "summary": "Update the current user", + "description": "Login and update the current user", + "inputs": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "password": { "type": "string" }, + "firstName": { "type": "string" } + } + }, + "steps": [ + { + "stepId": "Login", + "workflowId": "$sourceDescriptions.users-openAPI.LoginExistingUser" + }, + { + "stepId": "deleteUser", + "operationId": "updateUser", + "parameters": [ + { + "name": "Authorization", + "in": "header", + "value": "$workflows.LoginExistingUser.outputs.AccessToken" + }, + { "name": "username", "in": "path", "value": "$inputs.username" } + ], + "requestBody": { + "contentType": "application/json", + "payload": { "firstName": "$inputs.firstName" } + } + } + ] + } + ] +} diff --git a/test/serverless-users/input.json b/test/serverless-users/input.json new file mode 100644 index 0000000..be3097a --- /dev/null +++ b/test/serverless-users/input.json @@ -0,0 +1,81 @@ +{ + "createUser": { + "user": { + "username": "DannyB", + "firstName": "Danny", + "lastName": "Brown", + "email": "dannyb@example.com", + "phone": "+443333333333", + "userStatus": 1 + } + }, + "LoginNewUser": { + "user": { + "username": "FatboyS", + "firstName": "Norman", + "lastName": "Cook", + "email": "NormanC@example.com", + "phone": "+441111111111", + "userStatus": 1 + }, + "username": "FatboyS" + }, + "createUsersByArray": { + "username": "FatboyS", + "userArray": [ + { + "username": "SlimS", + "firstName": "Marshall", + "lastName": "Mathers", + "email": "MarshamM@example.com", + "phone": "+442222222222", + "userStatus": 1 + }, + { + "username": "IceC", + "firstName": "O'Shea", + "lastName": "Jackson", + "email": "OSheaJ@example.com", + "phone": "+444444444444", + "userStatus": 1 + } + ] + }, + "createUsersByList": { + "username": "FatboyS", + "userArray": [ + { + "username": "DrDre", + "firstName": "Andre", + "lastName": "Young", + "email": "AndreY@example.com", + "phone": "+445555555555", + "userStatus": 1 + }, + { + "username": "KDot", + "firstName": "Kendrick", + "lastName": "Lamar", + "email": "KendrickL@example.com", + "phone": "+446666666666", + "userStatus": 1 + } + ] + }, + "loginExistingUser": { + "username": "DannyB" + }, + "loginAndOutUser": { + "username": "DannyB" + }, + "getUser": { + "username": "KDot" + }, + "deleteCurrentUser": { + "username": "DrDre" + }, + "updateCurrentUser": { + "username": "SlimS", + "firstName": "Slim" + } +} diff --git a/test/unit/Arazzo.spec.js b/test/unit/Arazzo.spec.js index ceb3e74..bbf9794 100644 --- a/test/unit/Arazzo.spec.js +++ b/test/unit/Arazzo.spec.js @@ -1,544 +1,5599 @@ -'use strict'; +"use strict"; -const { - bundleFromString, -} = require("@redocly/openapi-core"); +const { bundleFromString } = require("@redocly/openapi-core"); const expect = require("chai").expect; -const peggy = require('peggy'); -const nock = require('nock'); -const sinon = require('sinon'); +const peggy = require("peggy"); +const nock = require("nock"); +const sinon = require("sinon"); -const path = require('node:path'); -const fs = require('node:fs'); -const fsp = require('node:fs/promises'); +const path = require("node:path"); +const fs = require("node:fs"); +const fsp = require("node:fs/promises"); -const openAPIMock = require('../mocks/petStoreOpenAPI.json'); +const openAPIMock = require("../mocks/petStoreOpenAPI.json"); -const Input = require('../../src/Input.js'); -const Logger = require('../../src/Logger.js'); -const OpenAPI = require('../../src/OpenAPI.js'); +const Expression = require("../../src/Expression.js"); +const Input = require("../../src/Input.js"); +const Logger = require("../../src/Logger.js"); +const OpenAPI = require("../../src/OpenAPI.js"); -const Arazzo = require('../../src/Arazzo'); +const Arazzo = require("../../src/Arazzo"); +const petsArazzo = require("../serverless-pets/arazzo.json"); +const userArazzo = require("../serverless-users/arazzo.json"); describe(`Arazzo Document`, function () { - let parser; - before(async () => { - const peggyPath = path.join(__dirname, '..', '..', 'resources', 'rules.peggy'); - const peggyRuleSet = await fsp.readFile(peggyPath); + let parser; + before(async () => { + const peggyPath = path.join( + __dirname, + "..", + "..", + "resources", + "rules.peggy", + ); + const peggyRuleSet = await fsp.readFile(peggyPath); + + parser = peggy.generate(peggyRuleSet.toString()); + }); + + const logger = new Logger("3", { + notice: (str) => {}, + error: (str) => {}, + success: (str) => {}, + verbose: (str) => {}, + }); + + describe(`constructor`, function () { + it(`should set the type to arazzo`, function () { + const expected = new Arazzo("./arazzo.json", "arazzo", { + logger: logger, + parser, + }); + + expect(expected).to.be.instanceOf(Arazzo); + expect(expected.type).to.be.equal("arazzo"); + }); + }); + + describe(`setMainArazzo`, function () { + it(`should correctly set the filePath`, function () { + const arazzo = new Arazzo("./arazzo.json", "arazzo", { + logger: logger, + parser, + }); + arazzo.setMainArazzo(); + + expect(arazzo.filePath).to.be.equal( + `${path.resolve(__dirname, "..", "..")}/arazzo.json`, + ); + }); + }); + + describe(`runWorkflows`, function () { + describe(`OpenAPI SourceDescriptions`, function () { + describe(`single sourceDescription`, function () { + describe(`single workflow`, function () { + describe(`single step`, function () { + describe(`without successCriteria`, function () { + it(`should throw an error when the operation does not respond with json`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply( + 405, + 'unknown', + { + "access-control-allow-headers": + "Content-Type, api_key, Authorization", + "access-control-allow-methods": "GET, POST, DELETE, PUT", + "access-control-allow-origin": "*", + connection: "keep-alive", + "content-length": "102", + "content-type": "application/xml", + date: "Tue, 06 Jan 2026 19:48:54 GMT", + server: "Jetty(9.2.9.v20150224)", + }, + ); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/single-step/arazzoMock-user-single-workflow-single-step.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + throw new Error("Expected promise to reject but it resolved"); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + `SyntaxError: Unexpected token '<', " { + describe(`end`, function () { + it(`should end the workflow when an onSuccess action is met with a type of end`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { id: 123 }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/single-step/arazzoMock-user-single-workflow-single-step-with-successCriteria-and-onSuccess-set-to-end.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + } catch (err) { + console.error(err); + expect(err).to.not.be.instanceOf(Error); + } + }); + }); + + describe(`goto`, function () { + it(`should handle a non existant step`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { id: 123 }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/single-step/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-step.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + } catch (err) { + console.error(err); + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + "goto Step does not exist within current workflow", + ); + } + }); + + it(`should handle a non existant workflow`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { id: 123 }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/single-step/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-workflow.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + } catch (err) { + console.error(err); + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + "goto Workflow does not exist within current workflows", + ); + } + }); + + xit(`should handle a non existant workflow referencing a non-existant sourceDescription`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { id: 123 }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/single-step/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-sourceDescription-workflow.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + } catch (err) { + console.error(err); + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + "goto Workflow does not exist within current workflows", + ); + } + }); + + it(`should handle a non self referential infinite loop`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .times(3) + .reply(201, { id: 123 }); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(400); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/single-step/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-self-referential.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + const spy = sinon.spy(arazzo, "runStep"); + + try { + await arazzo.runWorkflows(inputFile); + } catch (err) { + console.error(err); + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + "createAUser step of the createUser workflow failed the successCriteria", + ); + } + + expect(spy.callCount).to.be.equal(4); + spy.restore(); + }); + }); + }); + }); + }); + + describe(`multiple steps`, function () { + describe(`without successCriteria`, function () { + it(`should throw an error when the operation does not respond with json`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply( + 405, + 'unknown', + { + "access-control-allow-headers": + "Content-Type, api_key, Authorization", + "access-control-allow-methods": "GET, POST, DELETE, PUT", + "access-control-allow-origin": "*", + connection: "keep-alive", + "content-length": "102", + "content-type": "application/xml", + date: "Tue, 06 Jan 2026 19:48:54 GMT", + server: "Jetty(9.2.9.v20150224)", + }, + ); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/multiple-steps/arazzoMock-user-single-workflow-multiple-step.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + throw new Error("Expected promise to reject but it resolved"); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + `SyntaxError: Unexpected token '<', " {}, - error: (str) => {}, - success: (str) => {}, - verbose: (str) => {}, - } - ); + const arazzo = new Arazzo( + "./test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-goto-self-referential.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); - describe(`constructor`, function () { - it(`should set the type to arazzo`, function() { - const expected = new Arazzo('./arazzo.json', 'arazzo', {logger: logger, parser}); + const spy = sinon.spy(arazzo, "runStep"); - expect(expected).to.be.instanceOf(Arazzo); - expect(expected.type).to.be.equal('arazzo'); - }); - }); + try { + await arazzo.runWorkflows(inputFile); + expect(spy.callCount).to.be.equal(4); + } catch (err) { + console.error(err); + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + "createAUser step of the createUser workflow failed the successCriteria", + ); + } - describe(`setMainArazzo`, function () { - it(`should correctly set the filePath`, function() { - const arazzo = new Arazzo('./arazzo.json', 'arazzo', {logger: logger, parser}); - arazzo.setMainArazzo(); + spy.restore(); + }); + }); - expect(arazzo.filePath).to.be.equal(`${path.resolve(__dirname, '..', '..')}/arazzo.json`); - }); - }); + describe(`retry`, function () { + it(`retries calls the relevant step first and then retries the current failing step`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .times(2) + .reply(201, { username: "MarshallM" }); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .get("/v2/user/MarshallM") + .reply(404); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .get("/v2/user/MarshallM") + .reply(200, { id: 123 }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/multiple-steps/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-execute-a-step-and-retry.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + const spy = sinon.spy(arazzo, "runStep"); + + try { + await arazzo.runWorkflows(inputFile); + + expect(spy.callCount).to.be.equal(4); + } catch (err) { + expect(err).to.not.be.instanceOf(Error); + } + + spy.restore(); + }); + + it(`retries calls the relevant step first and then retries the current failing step Skipping other steps inbetween`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .times(2) + .reply(201, { username: "MarshallM" }); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post( + "/v2/user/createWithArray", + "[object Object],[object Object]", + ) + .reply(201, { username: "FatBoyS" }); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .get("/v2/user/MarshallM") + .reply(404); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .get("/v2/user/MarshallM") + .reply(200, { id: 123 }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/multiple-steps/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-execute-a-step-and-retry-do-not-execute-sequential-step.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + const spy = sinon.spy(arazzo, "runStep"); + + try { + await arazzo.runWorkflows(inputFile); + + expect(spy.callCount).to.be.equal(5); + } catch (err) { + expect(err).to.not.be.instanceOf(Error); + } + + spy.restore(); + }); + + xit(`retries the step once when no retry options are added`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(400); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { id: 123 }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + const spy = sinon.spy(arazzo, "runStep"); + + try { + await arazzo.runWorkflows(inputFile); + + expect(spy.callCount).to.be.equal(2); + } catch (err) { + expect(err).to.not.be.instanceOf(Error); + } + + spy.restore(); + }); + + xit(`retries the step multiple times when a retrylimit is set and all retries fail`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .times(4) + .reply(400); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry-multiple-times.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + const spy = sinon.spy(arazzo, "runStep"); + + try { + await arazzo.runWorkflows(inputFile); + expect(spy.callCount).to.be.equal(4); + + throw new Error( + "createAUser step of the createUser workflow failed the successCriteria", + ); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + `createAUser step of the createUser workflow failed the successCriteria`, + ); + } + + spy.restore(); + }); + + xit(`retries the step multiple times when a retrylimit is set and breaks the retry circle if a retry passes`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .times(2) + .reply(400); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { id: 123 }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry-multiple-times.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + const spy = sinon.spy(arazzo, "runStep"); + + try { + await arazzo.runWorkflows(inputFile); + expect(spy.callCount).to.be.equal(3); + } catch (err) { + expect(err).to.not.be.instanceOf(Error); + } + + spy.restore(); + }); + + xit(`retries can deal with different types of retry rules being triggered`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(400); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(404); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { id: 123 }); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(400); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { id: 123 }); + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + const arazzo = new Arazzo( + "./test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry-with-different-types.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); - xdescribe(`runWorkflows`, function () { - describe(`OpenAPI SourceDescriptions`, function () { - xdescribe(`singular step`, function () { - it(`return successfully when there are no more steps to run`, async function() { - nock('https://raw.githubusercontent.com:443', {"encodedQueryParams":true}) - .get('/readmeio/oas-examples/refs/heads/main/3.1/json/petstore.json') - .reply(200, ["1f8b0800000000000013ed5ddd73dbb8117ff75fb1c3eb436fc696ec5cda074faf53274e3a9a4b134f1cb7d7c964ee607245e142020c004a5633fedf3b00488ae0b7be1c39c93df8241104f60bbf5d2c16c8e723008f27c84842bd73f07e1a9d8d4ebd63fd2b6553ee9dc3e72300002f40e90b9a28ca996ef76e46255009042489930841a298a3802b545271917f1f01fc97a7e0130653ca02e0a982583f26b7fae3f58284210a200adecf944acec763697f1a51fee1cfb59f7e042e8033784f853f9a0a44c6031c3154c7f043d6aae1ad3115fef8c711c04b2e4069c22dcdc7b0cc684b25829a219084c2475cc2ef32419f92e8e4232e7f07c541a154b645aa665cd0ff112d0898d248a190232330006f8e4266023a1b9d668204f0145511ea5f73867331150d50c4f2cdf41ac59cfaa6659d0bd3669cbfe173a688af0a05017818131ae97749421592f81fab973dd3e63e7b37a23e3289e5771989cdb01709f167084f0ada01bc5444258a168bc5889856232ec271d6971cbf9a3c7ff1fafac5c993d1e968a6e2281bf2281bd6b30621bd73786f9e7c6eee3ec924332a713e7fb2eaec83e90cef140a46a24beecb561b7dd96e71b914ddb15d7959b215091b68cec595a05ac9a932fc8b398aa59a511666632f796a35bf7aa5918f5e5e8af77b38c8655668be467fd908eb835ef83e4aa9edbf98d65c045a899d9da6b21070bdcf37090a3379642615b7f56602c9bad2f275391a2ea0c2b412a266259bd206599e280997ca252cb710630c1f4a23cb348e89581a4906011060b8800495019419d649adb0587ec473b14d0233c383e0aa647a866c416254d90c2b932150265c4f51876c00efe9e95f2a3fd56998b0398968009425a9f24a4def8bcff76596d14f0555cb62c6d8ff2a63e473fc378da646740b41159e27666e688a4960bf7c681cd1e5ee538a523de3c1b2cadf9f044e350f3f8c7d1e279c215372bc6a4f518eb5108faa03140c799ae7b5757d9304442110067847a5d2f33f7175355ccfa9e96b17aa3e1daceac925c83449228a81a3f063b7c3a7bd1d5ea102c6154c79ca3abbea37c37f6bcaaccfc53b1fedefdf88353aae5bc3d1588752cf96d78aa8b4ac692fc40dcc5503a934000fb74b90b6d37663fd571a296a023ed312e6244a519a28ea1621117c4e030c6041d50c7c1ec704246a835518805482b25076d8bbe64c935270d76af6edfa5cf9b64a0fe629355c7c4a512cab8f2a9c5e3b0caa1951c010038ddeb7083e6792062830802917592458ed51ab970ad4ac299162e529de25110fb0f9a1f4671893dac4d04a5d26863f2204a9f2a019541857e77fe54dab87daab9a2496c6c660c89cd088dc46a86740822c302f80277914942da924bc29492365082bdead34bb3f6afbd6369b5ae0ecc9003893a9895ea66904859155f5a3836864aa49ca4423a06fde1adfc551b3405b9504fd8a826e65412b66d8412b68d12cd6e65fee6b365366f60f69e4f7f570db6e74153734dc4596b1ef60dc509fbf78679dc12ebd8579a3d3571857a19bade521467023cd6b67c7faef13f3f72703b47a254e59381ae444de55e85bd78554f883f51c881e5d7b0beb1be0b6d6feb0ddc377f03e4838fb0ede7539ac05de8a840786dc0e8226027d8d85d9bc6f47f5cf09aa4970bf1348376989db254c2e3be0fc2daa543093f3a52c8cb0baacadc07088ea0ad5b3e524d806820d93cd18ac33353d103cb9043ecd732ec2d0bf1e08f7e32c650ac35ae80fe04db98889ca9afcf5a9d76eca5f237eae8f070f83743ba06b0f98f4d03997d56a6db01de5f6b82bd42409fded239a463d01ed2a1db751eed5e6e3346c691ca06c957bb531a89ea7101045b64cd1fd87aad94b2ee28340bb3c5b21b37485a5b2d6f5a303bfaf3d67dd06ca2e249f2c168b132de2935444c87c1e1815d6d4d40185850af9ed1fe8aba6645022b4a92b5ad342d12233db9690b0a2163b0f03d02f693bd5d3d08d201ae96b5e8d409383b03c57d3a2c3a8ca96f43ba16b1b177254fdb482bf002354b83e005e9af73200dc0ce4ecd09dfb10fde096237e23bccd90049d09d42989e4fa00d5a4a55697bd1750d61e999aac71a6bfc70fc10715d77c494c3f2affd2b4381ba749c449308949e8945b6c16c9d8cea4de5aa4a6c70d0316ddcb4b1ae141442a3c8b4d1efdbc78e075d95e164017097ddb10e7d76553ffdee2c51e75a0c57d85ea442a8124de34be6adf6c5b19e22d657a8a6f1325b810642438a66c8e4c715166be234764eb515ab0274ffed83594ed97e2b03de355e22826899ef859b0a5e356b340f99412a6a889343bf3499382a1edca211ec954ed0dd1c15402513d0889aefac2f501e098357320f2a7277b0c72f79836689a11a67c6da023ee9c0e5711f14d8d8fe9d1ec4c6d115e27bab737c20d801f8161ef233768c5d06f5d0f9f1d1c4ad9b0c5c65a71746de8e629b4adbf6b115ba79f5b5f6443055491859d6966aad89a9b2415fe8ce8bd88c6357b35821ce633cd28e3cfe67f8377573aa1c2eeaf585ab302dadead165da96e359f4f75506209196cc3e452dadca5d9c782bfff0c674058007ffb19ce4e47f046cd50e4854b0b1a45102243bbc35e94cff5b85aa3b96d376f3229ee23a13945e5cfbe6042b3d620a68cc6a67aeaacfe8cdce5cf4e1fe972e33bd43fb6ad20c3436fd2e4a8d27d7796b113e86c9e715f50977049159d63f1c4e0db085e6348ccef5c33cb4e9cc715f8838bab09a0105c74a19f95404f38f650e0a73d9b15640d022d9d870981bb42b9473073dc28c21c6719b6c0304d5be6d27381da5e9dc3312da7f074611d67d1d2980467680af36608110f43d4bb60a697ae8239df0c7623b75e7e6cb0adbcf24b8f2fba3512db4d706bf51d185541c37a7fdd6056f733b67ad5fbd217a6ae6b57662921a2526974d26d337c0ee91c99dd73856a19d91a4be1952dca82f089d9c87da4a639e0dc87e6d66a684db5bea28e161f8d5635dddf95ea2835e2212da353c76ab34b95af78282d8850961f335c4a85f1667a3354753b86fe404893633e374642832aa967d615dada01bdf6b7e2da71e4b393cd6222e5828b96b06f28b3792f2b5eb51ff72324ba02feae9a0bde3fe7eb456dbb5a9bdaa280a67cb6f7ebc95ba2f0e4158d69d3dab53e9e4fa2486f5f0898e973c2248af802833c58aa045afda273c4d7954eef4fa6f7af777f3d7971975081f2e462aa9cf8b29d5f73089532b879f71c163364a0f847d4c7274d479bf3daba9fe5b0aa473f51345e7f1bf141d3137dc53c0f5b7d3f988a8131e63a8ba71ca4c705f434aea586fa32ee9c9cdec699e983fd7e2a04325559d28044295dd458cfb5f1541de6a2a743b69f73450d4c4d77c9f79f68af5fd02058b8d7cda419a211e5b3e5eb4a17bb0d1606a44db4fb3461425bcad81e2ed3e39c3927cae01b71aa5f30e1dbb4603e887cef40c2f688bb3b4a5dddc84d72beedf75c74c1475e3afb30492a5b25b6dfb5c8007871a165bfd5f53b069075cdf2214df21b49f395a7cc6669be61fb345dd336dba67998596ba9fce2b3b63d28d8d326ca179eba5fcaa31ce57fede569abc9540c93c9ceb989e04da52aababf4adfd548a47eb67607a56e85db5c0aea46cb9f3debacf0a20ab80b7e6086e72c11d41ce6872492a80010d26db3a40e392be3248f3d99b9e211a77df6af7ff98a6c5fd34b60c48d70393445f29613f0718d1b9be93c7ad0d7668d43659434e87ca5bce2324b5b879b5ce3347517afd583d802ef0ac525e5007f7e74461e816ee1ee0a4683cfed501816bcba9104387a86ee4a16347e1bdd61195d3c3940aa95e6fd54544b6ed21bf5e74c3d78bc4f8e63dcc38db827ead86da9d69953e06a26c3780198f998db48df5bb7167ddf2df91f0b00d7feff8a025d021a0ab5a56ac5540a578efbdedfed8189ce23722924e28bd5759d6c25012bc61d1b2ba1ca839b5267f01bd2b9b3abe6ea4c2aa93be33d72b9b98818721ed081856126e1da3e94a9a069b28535b745b2f515a089224f5e5558db28e0b6f36db2eccd7663be45291fa36cc4e18ecb41967ce75b3bc93705057ff66e785ca7737b44686dd37176e83363d97c596cfce6d0dcbbebdf96b4f8b81ac9f0d1d698c52bae759fb3ba8cbeda8d4b1e7944e94978635086f4e01f527803ad6eceb5d09d36eee7dd9f3fd90d068c50da7a7adf5d5132024086c1eb3f14aeae67c941b7d578bcbf6a3a5cdefb5db65726f88b45f95caa8ea49be36913af3213f0b77ada9746744f57c6c13d0e847e528d59b467c510f5762ad8d86f209cff9570e6e7aefe737e38d034a225e4373e9f3a4f14462f958ef3978310fe874a9cf7a189c3777e513dfe729ab1f7c2c9d013eb75f6c7bf3cb10ddd5cc7875aab0419c24a1bf38d75674dc68e1de66e1ea56a7c58eee8ffe0fa561a61773630000"], { - 'accept-ranges': 'bytes', - 'access-control-allow-origin': '*', - 'cache-control': 'max-age=300', - connection: 'keep-alive', - 'content-encoding': 'gzip', - 'content-length': '3189', - 'content-security-policy': "default-src 'none'; style-src 'unsafe-inline'; sandbox", - 'content-type': 'text/plain; charset=utf-8', - 'cross-origin-resource-policy': 'cross-origin', - date: 'Fri, 02 Jan 2026 12:03:42 GMT', - etag: 'W/"6f618be854ed64cd6a2cb850eefecf34866e637d9e73287a9d9d64a8ca3d54b3"', - expires: 'Fri, 02 Jan 2026 12:08:42 GMT', - 'source-age': '0', - 'strict-transport-security': 'max-age=31536000', - vary: 'Authorization,Accept-Encoding', - via: '1.1 varnish', - 'x-cache': 'HIT', - 'x-cache-hits': '1', - 'x-content-type-options': 'nosniff', - 'x-fastly-request-id': 'e357bbd9481ed9b72541f1d5e4d6c921c6dbf9be', - 'x-frame-options': 'deny', - 'x-github-request-id': '9733:2549FA:1BB33B8:30A9114:6957ADA3', - 'x-served-by': 'cache-lhr-egll1980089-LHR', - 'x-timer': 'S1767355422.386688,VS0,VE1', - 'x-xss-protection': '1; mode=block' + const spy = sinon.spy(arazzo, "runStep"); + + try { + await arazzo.runWorkflows(inputFile); + expect(spy.callCount).to.be.equal(5); + } catch (err) { + console.error(err); + expect(err).to.not.be.instanceOf(Error); + } + + spy.restore(); + }); + + xit(`retries the step with a delay if a retryAfter is set`, async function () { + this.timeout(7000); + + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(400); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { id: 123 }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry-with-delay.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + } catch (err) { + expect(err).to.not.be.instanceOf(Error); + } + }); + + xit(`retries the step with ignoring the retryAfter if the response returns a retryAfter header`, async function () { + this.timeout(7000); + + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(400, {}, { "retry-after": 3 }); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { id: 123 }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry-with-delay.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + } catch (err) { + expect(err).to.not.be.instanceOf(Error); + } }); - nock('http://petstore.swagger.io:80', {"encodedQueryParams":true}) - .get('/v2/pet/findByStatus') - .query({"status":"pending"}) - .reply(200, [{"id":9515,"category":{"id":7792,"name":"string"},"name":"doggie","photoUrls":["string","string"],"tags":[{"id":7284,"name":"string"},{"id":9936,"name":"string"}],"status":"pending"},{"id":1109,"name":"Test9_02012026","photoUrls":[],"tags":[],"status":"pending"},{"id":1003,"name":"Lucy","photoUrls":["url1"],"tags":[],"status":"pending"},{"id":5619,"category":{"id":5381,"name":"string"},"name":"doggie","photoUrls":["string","string"],"tags":[{"id":9192,"name":"string"},{"id":8297,"name":"string"}],"status":"pending"},{"id":100002,"name":"Simple Cat","photoUrls":["https://example.com/cat.jpg"],"tags":[],"status":"pending"},{"id":922337203685477600,"name":"chedder-89811222","photoUrls":["./dog.png"],"tags":[],"status":"pending"},{"id":1088293,"category":{"id":1,"name":"Dogs"},"name":"UpdatedWorkflowDog-1088293","photoUrls":["https://example.com/photo1.jpg"],"tags":[{"id":1,"name":"test-tag"}],"status":"pending"},{"id":745215,"category":{"id":1,"name":"Dogs"},"name":"UpdatedWorkflowDog-745215","photoUrls":["https://example.com/photo1.jpg"],"tags":[{"id":1,"name":"test-tag"}],"status":"pending"}], { - 'access-control-allow-headers': 'Content-Type, api_key, Authorization', - 'access-control-allow-methods': 'GET, POST, DELETE, PUT', - 'access-control-allow-origin': '*', - connection: 'keep-alive', - 'content-type': 'application/json', - date: 'Fri, 02 Jan 2026 13:05:43 GMT', - server: 'Jetty(9.2.9.v20150224)', - 'transfer-encoding': 'chunked' + xit(`retries the step with ignoring the retryAfter if the response returns a retryAfter header in date format`, async function () { + this.timeout(7000); + + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + let timeObject = new Date(); + const milliseconds = 3 * 1000; // 10 seconds = 10000 milliseconds + timeObject = new Date( + timeObject.getTime() + milliseconds, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply( + 400, + {}, + { "retry-after": timeObject.toUTCString() }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { id: 123 }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/single-step/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-retry-with-delay.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + } catch (err) { + expect(err).to.not.be.instanceOf(Error); + } }); + }); + }); + + xit(`resolves when onFailure is set to end and matches the criteria`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { id: 123 }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/single-step/arazzoMock-user-single-workflow-single-step-with-successCriteria.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + throw new Error( + "Expected promise to reject but it resolved", + ); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + `createAUser step of the createUser workflow failed the successCriteria`, + ); + } + }); - const inputFile = new Input('./test/mocks/validInput.json', 'inputs'); + xit(`retries when onFailure is set to retry and matches the criteria`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); - const arazzo = new Arazzo('./test/mocks/workingArazzoMock.json', 'arazzo', {logger: logger, parser}); + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { id: 123 }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/single-step/arazzoMock-user-single-workflow-single-step-with-successCriteria-and-onFailure-set-to-retry.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + throw new Error( + "Expected promise to reject but it resolved", + ); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + `createAUser step of the createUser workflow failed the successCriteria`, + ); + } + }); + }); + }); + + describe(`multiple onFailure`, function () {}); + }); + + describe(`with onSuccess`, () => { + describe(`end`, function () { + it(`should end the workflow when an onSuccess action is met with a type of end`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { id: 123 }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-end.json", + "arazzo", + { logger: logger, parser }, + ); arazzo.setMainArazzo(); + const spy = sinon.spy(arazzo, "runStep"); + try { - const expected = await arazzo.runWorkflows(inputFile); - console.log(expected) + await arazzo.runWorkflows(inputFile); } catch (err) { - expect(err).to.not.be.instanceOf(Error); + console.error(err); + expect(err).to.not.be.instanceOf(Error); } + + expect(spy.callCount).to.be.equal(1); + + spy.restore(); + }); }); - }); - xdescribe(`multiple Steps in one workflow`, function () { - it(`returns successfully when there are no more steps to run`, async function() { - nock.recorder.rec(); - - // nock('https://raw.githubusercontent.com:443', {"encodedQueryParams":true}) - // .get('/readmeio/oas-examples/refs/heads/main/3.1/json/petstore.json') - // .reply(200, ["1f8b0800000000000013ed5ddd73dbb8117ff75fb1c3eb436fc696ec5cda074faf53274e3a9a4b134f1cb7d7c964ee607245e142020c004a5633fedf3b00488ae0b7be1c39c93df8241104f60bbf5d2c16c8e723008f27c84842bd73f07e1a9d8d4ebd63fd2b6553ee9dc3e72300002f40e90b9a28ca996ef76e46255009042489930841a298a3802b545271917f1f01fc97a7e0130653ca02e0a982583f26b7fae3f58284210a200adecf944acec763697f1a51fee1cfb59f7e042e8033784f853f9a0a44c6031c3154c7f043d6aae1ad3115fef8c711c04b2e4069c22dcdc7b0cc684b25829a219084c2475cc2ef32419f92e8e4232e7f07c541a154b645aa665cd0ff112d0898d248a190232330006f8e4266023a1b9d668204f0145511ea5f73867331150d50c4f2cdf41ac59cfaa6659d0bd3669cbfe173a688af0a05017818131ae97749421592f81fab973dd3e63e7b37a23e3289e5771989cdb01709f167084f0ada01bc5444258a168bc5889856232ec271d6971cbf9a3c7ff1fafac5c993d1e968a6e2281bf2281bd6b30621bd73786f9e7c6eee3ec924332a713e7fb2eaec83e90cef140a46a24beecb561b7dd96e71b914ddb15d7959b215091b68cec595a05ac9a932fc8b398aa59a511666632f796a35bf7aa5918f5e5e8af77b38c8655668be467fd908eb835ef83e4aa9edbf98d65c045a899d9da6b21070bdcf37090a3379642615b7f56602c9bad2f275391a2ea0c2b412a266259bd206599e280997ca252cb710630c1f4a23cb348e89581a4906011060b8800495019419d649adb0587ec473b14d0233c383e0aa647a866c416254d90c2b932150265c4f51876c00efe9e95f2a3fd56998b0398968009425a9f24a4def8bcff76596d14f0555cb62c6d8ff2a63e473fc378da646740b41159e27666e688a4960bf7c681cd1e5ee538a523de3c1b2cadf9f044e350f3f8c7d1e279c215372bc6a4f518eb5108faa03140c799ae7b5757d9304442110067847a5d2f33f7175355ccfa9e96b17aa3e1daceac925c83449228a81a3f063b7c3a7bd1d5ea102c6154c79ca3abbea37c37f6bcaaccfc53b1fedefdf88353aae5bc3d1588752cf96d78aa8b4ac692fc40dcc5503a934000fb74b90b6d37663fd571a296a023ed312e6244a519a28ea1621117c4e030c6041d50c7c1ec704246a835518805482b25076d8bbe64c935270d76af6edfa5cf9b64a0fe629355c7c4a512cab8f2a9c5e3b0caa1951c010038ddeb7083e6792062830802917592458ed51ab970ad4ac299162e529de25110fb0f9a1f4671893dac4d04a5d26863f2204a9f2a019541857e77fe54dab87daab9a2496c6c660c89cd088dc46a86740822c302f80277914942da924bc29492365082bdead34bb3f6afbd6369b5ae0ecc9003893a9895ea66904859155f5a3836864aa49ca4423a06fde1adfc551b3405b9504fd8a826e65412b66d8412b68d12cd6e65fee6b365366f60f69e4f7f570db6e74153734dc4596b1ef60dc509fbf78679dc12ebd8579a3d3571857a19bade521467023cd6b67c7faef13f3f72703b47a254e59381ae444de55e85bd78554f883f51c881e5d7b0beb1be0b6d6feb0ddc377f03e4838fb0ede7539ac05de8a840786dc0e8226027d8d85d9bc6f47f5cf09aa4970bf1348376989db254c2e3be0fc2daa543093f3a52c8cb0baacadc07088ea0ad5b3e524d806820d93cd18ac33353d103cb9043ecd732ec2d0bf1e08f7e32c650ac35ae80fe04db98889ca9afcf5a9d76eca5f237eae8f070f83743ba06b0f98f4d03997d56a6db01de5f6b82bd42409fded239a463d01ed2a1db751eed5e6e3346c691ca06c957bb531a89ea7101045b64cd1fd87aad94b2ee28340bb3c5b21b37485a5b2d6f5a303bfaf3d67dd06ca2e249f2c168b132de2935444c87c1e1815d6d4d40185850af9ed1fe8aba6645022b4a92b5ad342d12233db9690b0a2163b0f03d02f693bd5d3d08d201ae96b5e8d409383b03c57d3a2c3a8ca96f43ba16b1b177254fdb482bf002354b83e005e9af73200dc0ce4ecd09dfb10fde096237e23bccd90049d09d42989e4fa00d5a4a55697bd1750d61e999aac71a6bfc70fc10715d77c494c3f2affd2b4381ba749c449308949e8945b6c16c9d8cea4de5aa4a6c70d0316ddcb4b1ae141442a3c8b4d1efdbc78e075d95e164017097ddb10e7d76553ffdee2c51e75a0c57d85ea442a8124de34be6adf6c5b19e22d657a8a6f1325b810642438a66c8e4c715166be234764eb515ab0274ffed83594ed97e2b03de355e22826899ef859b0a5e356b340f99412a6a889343bf3499382a1edca211ec954ed0dd1c15402513d0889aefac2f501e098357320f2a7277b0c72f79836689a11a67c6da023ee9c0e5711f14d8d8fe9d1ec4c6d115e27bab737c20d801f8161ef233768c5d06f5d0f9f1d1c4ad9b0c5c65a71746de8e629b4adbf6b115ba79f5b5f6443055491859d6966aad89a9b2415fe8ce8bd88c6357b35821ce633cd28e3cfe67f8377573aa1c2eeaf585ab302dadead165da96e359f4f75506209196cc3e452dadca5d9c782bfff0c674058007ffb19ce4e47f046cd50e4854b0b1a45102243bbc35e94cff5b85aa3b96d376f3229ee23a13945e5cfbe6042b3d620a68cc6a67aeaacfe8cdce5cf4e1fe972e33bd43fb6ad20c3436fd2e4a8d27d7796b113e86c9e715f50977049159d63f1c4e0db085e6348ccef5c33cb4e9cc715f8838bab09a0105c74a19f95404f38f650e0a73d9b15640d022d9d870981bb42b9473073dc28c21c6719b6c0304d5be6d27381da5e9dc3312da7f074611d67d1d2980467680af36608110f43d4bb60a697ae8239df0c7623b75e7e6cb0adbcf24b8f2fba3512db4d706bf51d185541c37a7fdd6056f733b67ad5fbd217a6ae6b57662921a2526974d26d337c0ee91c99dd73856a19d91a4be1952dca82f089d9c87da4a639e0dc87e6d66a684db5bea28e161f8d5635dddf95ea2835e2212da353c76ab34b95af78282d8850961f335c4a85f1667a3354753b86fe404893633e374642832aa967d615dada01bdf6b7e2da71e4b393cd6222e5828b96b06f28b3792f2b5eb51ff72324ba02feae9a0bde3fe7eb456dbb5a9bdaa280a67cb6f7ebc95ba2f0e4158d69d3dab53e9e4fa2486f5f0898e973c2248af802833c58aa045afda273c4d7954eef4fa6f7af777f3d7971975081f2e462aa9cf8b29d5f73089532b879f71c163364a0f847d4c7274d479bf3daba9fe5b0aa473f51345e7f1bf141d3137dc53c0f5b7d3f988a8131e63a8ba71ca4c705f434aea586fa32ee9c9cdec699e983fd7e2a04325559d28044295dd458cfb5f1541de6a2a743b69f73450d4c4d77c9f79f68af5fd02058b8d7cda419a211e5b3e5eb4a17bb0d1606a44db4fb3461425bcad81e2ed3e39c3927cae01b71aa5f30e1dbb4603e887cef40c2f688bb3b4a5dddc84d72beedf75c74c1475e3afb30492a5b25b6dfb5c8007871a165bfd5f53b069075cdf2214df21b49f395a7cc6669be61fb345dd336dba67998596ba9fce2b3b63d28d8d326ca179eba5fcaa31ce57fede569abc9540c93c9ceb989e04da52aababf4adfd548a47eb67607a56e85db5c0aea46cb9f3debacf0a20ab80b7e6086e72c11d41ce6872492a80010d26db3a40e392be3248f3d99b9e211a77df6af7ff98a6c5fd34b60c48d70393445f29613f0718d1b9be93c7ad0d7668d43659434e87ca5bce2324b5b879b5ce3347517afd583d802ef0ac525e5007f7e74461e816ee1ee0a4683cfed501816bcba9104387a86ee4a16347e1bdd61195d3c3940aa95e6fd54544b6ed21bf5e74c3d78bc4f8e63dcc38db827ead86da9d69953e06a26c3780198f998db48df5bb7167ddf2df91f0b00d7feff8a025d021a0ab5a56ac5540a578efbdedfed8189ce23722924e28bd5759d6c25012bc61d1b2ba1ca839b5267f01bd2b9b3abe6ea4c2aa93be33d72b9b98818721ed081856126e1da3e94a9a069b28535b745b2f515a089224f5e5558db28e0b6f36db2eccd7663be45291fa36cc4e18ecb41967ce75b3bc93705057ff66e785ca7737b44686dd37176e83363d97c596cfce6d0dcbbebdf96b4f8b81ac9f0d1d698c52bae759fb3ba8cbeda8d4b1e7944e94978635086f4e01f527803ad6eceb5d09d36eee7dd9f3fd90d068c50da7a7adf5d5132024086c1eb3f14aeae67c941b7d578bcbf6a3a5cdefb5db65726f88b45f95caa8ea49be36913af3213f0b77ada9746744f57c6c13d0e847e528d59b467c510f5762ad8d86f209cff9570e6e7aefe737e38d034a225e4373e9f3a4f14462f958ef3978310fe874a9cf7a189c3777e513dfe729ab1f7c2c9d013eb75f6c7bf3cb10ddd5cc7875aab0419c24a1bf38d75674dc68e1de66e1ea56a7c58eee8ffe0fa561a61773630000"], { - // 'accept-ranges': 'bytes', - // 'access-control-allow-origin': '*', - // 'cache-control': 'max-age=300', - // connection: 'keep-alive', - // 'content-encoding': 'gzip', - // 'content-length': '3189', - // 'content-security-policy': "default-src 'none'; style-src 'unsafe-inline'; sandbox", - // 'content-type': 'text/plain; charset=utf-8', - // 'cross-origin-resource-policy': 'cross-origin', - // date: 'Fri, 02 Jan 2026 12:03:42 GMT', - // etag: 'W/"6f618be854ed64cd6a2cb850eefecf34866e637d9e73287a9d9d64a8ca3d54b3"', - // expires: 'Fri, 02 Jan 2026 12:08:42 GMT', - // 'source-age': '0', - // 'strict-transport-security': 'max-age=31536000', - // vary: 'Authorization,Accept-Encoding', - // via: '1.1 varnish', - // 'x-cache': 'HIT', - // 'x-cache-hits': '1', - // 'x-content-type-options': 'nosniff', - // 'x-fastly-request-id': 'e357bbd9481ed9b72541f1d5e4d6c921c6dbf9be', - // 'x-frame-options': 'deny', - // 'x-github-request-id': '9733:2549FA:1BB33B8:30A9114:6957ADA3', - // 'x-served-by': 'cache-lhr-egll1980089-LHR', - // 'x-timer': 'S1767355422.386688,VS0,VE1', - // 'x-xss-protection': '1; mode=block' - // }); - - // nock('http://petstore.swagger.io:80', {"encodedQueryParams":true}) - // .get('/v2/pet/findByStatus') - // .query({"status":"pending"}) - // .reply(200, [{"id":9515,"category":{"id":7792,"name":"string"},"name":"doggie","photoUrls":["string","string"],"tags":[{"id":7284,"name":"string"},{"id":9936,"name":"string"}],"status":"pending"},{"id":1109,"name":"Test9_02012026","photoUrls":[],"tags":[],"status":"pending"},{"id":1003,"name":"Lucy","photoUrls":["url1"],"tags":[],"status":"pending"},{"id":5619,"category":{"id":5381,"name":"string"},"name":"doggie","photoUrls":["string","string"],"tags":[{"id":9192,"name":"string"},{"id":8297,"name":"string"}],"status":"pending"},{"id":100002,"name":"Simple Cat","photoUrls":["https://example.com/cat.jpg"],"tags":[],"status":"pending"},{"id":922337203685477600,"name":"chedder-89811222","photoUrls":["./dog.png"],"tags":[],"status":"pending"},{"id":1088293,"category":{"id":1,"name":"Dogs"},"name":"UpdatedWorkflowDog-1088293","photoUrls":["https://example.com/photo1.jpg"],"tags":[{"id":1,"name":"test-tag"}],"status":"pending"},{"id":745215,"category":{"id":1,"name":"Dogs"},"name":"UpdatedWorkflowDog-745215","photoUrls":["https://example.com/photo1.jpg"],"tags":[{"id":1,"name":"test-tag"}],"status":"pending"}], { - // 'access-control-allow-headers': 'Content-Type, api_key, Authorization', - // 'access-control-allow-methods': 'GET, POST, DELETE, PUT', - // 'access-control-allow-origin': '*', - // connection: 'keep-alive', - // 'content-type': 'application/json', - // date: 'Fri, 02 Jan 2026 13:05:43 GMT', - // server: 'Jetty(9.2.9.v20150224)', - // 'transfer-encoding': 'chunked' - // }); - - const inputFile = new Input('./test/mocks/validInput.json', 'inputs'); - - const arazzo = new Arazzo('./test/mocks/workingArazzoMockWithTwoSteps.json', 'arazzo', {logger: logger, parser}); + describe(`goto`, function () { + it(`should handle a non existant step`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { id: 123 }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-step.json", + "arazzo", + { logger: logger, parser }, + ); arazzo.setMainArazzo(); try { - const expected = await arazzo.runWorkflows(inputFile); - console.log(expected) + await arazzo.runWorkflows(inputFile); } catch (err) { - expect(err).to.not.be.instanceOf(Error); + console.error(err); + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + "goto Step does not exist within current workflow", + ); + } + }); + + it(`should handle a non existant workflow`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { id: 123 }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-workflow.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + } catch (err) { + console.error(err); + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + "goto Workflow does not exist within current workflows", + ); + } + }); + + xit(`should handle a non existant workflow referencing a non-existant sourceDescription`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { id: 123 }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/single-step/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-non-existant-sourceDescription-workflow.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + } catch (err) { + console.error(err); + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + "goto Workflow does not exist within current workflows", + ); + } + }); + + it(`should handle a non self referential infinite loop`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { username: "DannyB" }); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .get("/v2/user/DannyB") + .times(2) + .reply(200, { id: 123 }); + + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .get("/v2/user/DannyB") + .reply(404); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/single-workflow/multiple-steps/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-self-referential.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + const spy = sinon.spy(arazzo, "runStep"); + + try { + await arazzo.runWorkflows(inputFile); + } catch (err) { + console.error(err); + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + "getAUser step of the createUser workflow failed the successCriteria", + ); } + + expect(spy.callCount).to.be.equal(4); + spy.restore(); + }); }); + }); }); + }); + }); + describe(`multiple workflows`, function () { + it(`resolve all the outputs resolve as expected`, async function () { + // nock.recorder.rec() + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); - xit(`should throw an error when a step fails onFailure at step level is included and is set to retry and all retries are exhausted`, async function() { - nock('https://raw.githubusercontent.com:443', {"encodedQueryParams":true}) - .get('/readmeio/oas-examples/refs/heads/main/3.1/json/petstore.json') - .reply(200, ["1f8b0800000000000013ed5ddd73dbb8117ff75fb1c3eb436fc696ec5cda074faf53274e3a9a4b134f1cb7d7c964ee607245e142020c004a5633fedf3b00488ae0b7be1c39c93df8241104f60bbf5d2c16c8e723008f27c84842bd73f07e1a9d8d4ebd63fd2b6553ee9dc3e72300002f40e90b9a28ca996ef76e46255009042489930841a298a3802b545271917f1f01fc97a7e0130653ca02e0a982583f26b7fae3f58284210a200adecf944acec763697f1a51fee1cfb59f7e042e8033784f853f9a0a44c6031c3154c7f043d6aae1ad3115fef8c711c04b2e4069c22dcdc7b0cc684b25829a219084c2475cc2ef32419f92e8e4232e7f07c541a154b645aa665cd0ff112d0898d248a190232330006f8e4266023a1b9d668204f0145511ea5f73867331150d50c4f2cdf41ac59cfaa6659d0bd3669cbfe173a688af0a05017818131ae97749421592f81fab973dd3e63e7b37a23e3289e5771989cdb01709f167084f0ada01bc5444258a168bc5889856232ec271d6971cbf9a3c7ff1fafac5c993d1e968a6e2281bf2281bd6b30621bd73786f9e7c6eee3ec924332a713e7fb2eaec83e90cef140a46a24beecb561b7dd96e71b914ddb15d7959b215091b68cec595a05ac9a932fc8b398aa59a511666632f796a35bf7aa5918f5e5e8af77b38c8655668be467fd908eb835ef83e4aa9edbf98d65c045a899d9da6b21070bdcf37090a3379642615b7f56602c9bad2f275391a2ea0c2b412a266259bd206599e280997ca252cb710630c1f4a23cb348e89581a4906011060b8800495019419d649adb0587ec473b14d0233c383e0aa647a866c416254d90c2b932150265c4f51876c00efe9e95f2a3fd56998b0398968009425a9f24a4def8bcff76596d14f0555cb62c6d8ff2a63e473fc378da646740b41159e27666e688a4960bf7c681cd1e5ee538a523de3c1b2cadf9f044e350f3f8c7d1e279c215372bc6a4f518eb5108faa03140c799ae7b5757d9304442110067847a5d2f33f7175355ccfa9e96b17aa3e1daceac925c83449228a81a3f063b7c3a7bd1d5ea102c6154c79ca3abbea37c37f6bcaaccfc53b1fedefdf88353aae5bc3d1588752cf96d78aa8b4ac692fc40dcc5503a934000fb74b90b6d37663fd571a296a023ed312e6244a519a28ea1621117c4e030c6041d50c7c1ec704246a835518805482b25076d8bbe64c935270d76af6edfa5cf9b64a0fe629355c7c4a512cab8f2a9c5e3b0caa1951c010038ddeb7083e6792062830802917592458ed51ab970ad4ac299162e529de25110fb0f9a1f4671893dac4d04a5d26863f2204a9f2a019541857e77fe54dab87daab9a2496c6c660c89cd088dc46a86740822c302f80277914942da924bc29492365082bdead34bb3f6afbd6369b5ae0ecc9003893a9895ea66904859155f5a3836864aa49ca4423a06fde1adfc551b3405b9504fd8a826e65412b66d8412b68d12cd6e65fee6b365366f60f69e4f7f570db6e74153734dc4596b1ef60dc509fbf78679dc12ebd8579a3d3571857a19bade521467023cd6b67c7faef13f3f72703b47a254e59381ae444de55e85bd78554f883f51c881e5d7b0beb1be0b6d6feb0ddc377f03e4838fb0ede7539ac05de8a840786dc0e8226027d8d85d9bc6f47f5cf09aa4970bf1348376989db254c2e3be0fc2daa543093f3a52c8cb0baacadc07088ea0ad5b3e524d806820d93cd18ac33353d103cb9043ecd732ec2d0bf1e08f7e32c650ac35ae80fe04db98889ca9afcf5a9d76eca5f237eae8f070f83743ba06b0f98f4d03997d56a6db01de5f6b82bd42409fded239a463d01ed2a1db751eed5e6e3346c691ca06c957bb531a89ea7101045b64cd1fd87aad94b2ee28340bb3c5b21b37485a5b2d6f5a303bfaf3d67dd06ca2e249f2c168b132de2935444c87c1e1815d6d4d40185850af9ed1fe8aba6645022b4a92b5ad342d12233db9690b0a2163b0f03d02f693bd5d3d08d201ae96b5e8d409383b03c57d3a2c3a8ca96f43ba16b1b177254fdb482bf002354b83e005e9af73200dc0ce4ecd09dfb10fde096237e23bccd90049d09d42989e4fa00d5a4a55697bd1750d61e999aac71a6bfc70fc10715d77c494c3f2affd2b4381ba749c449308949e8945b6c16c9d8cea4de5aa4a6c70d0316ddcb4b1ae141442a3c8b4d1efdbc78e075d95e164017097ddb10e7d76553ffdee2c51e75a0c57d85ea442a8124de34be6adf6c5b19e22d657a8a6f1325b810642438a66c8e4c715166be234764eb515ab0274ffed83594ed97e2b03de355e22826899ef859b0a5e356b340f99412a6a889343bf3499382a1edca211ec954ed0dd1c15402513d0889aefac2f501e098357320f2a7277b0c72f79836689a11a67c6da023ee9c0e5711f14d8d8fe9d1ec4c6d115e27bab737c20d801f8161ef233768c5d06f5d0f9f1d1c4ad9b0c5c65a71746de8e629b4adbf6b115ba79f5b5f6443055491859d6966aad89a9b2415fe8ce8bd88c6357b35821ce633cd28e3cfe67f8377573aa1c2eeaf585ab302dadead165da96e359f4f75506209196cc3e452dadca5d9c782bfff0c674058007ffb19ce4e47f046cd50e4854b0b1a45102243bbc35e94cff5b85aa3b96d376f3229ee23a13945e5cfbe6042b3d620a68cc6a67aeaacfe8cdce5cf4e1fe972e33bd43fb6ad20c3436fd2e4a8d27d7796b113e86c9e715f50977049159d63f1c4e0db085e6348ccef5c33cb4e9cc715f8838bab09a0105c74a19f95404f38f650e0a73d9b15640d022d9d870981bb42b9473073dc28c21c6719b6c0304d5be6d27381da5e9dc3312da7f074611d67d1d2980467680af36608110f43d4bb60a697ae8239df0c7623b75e7e6cb0adbcf24b8f2fba3512db4d706bf51d185541c37a7fdd6056f733b67ad5fbd217a6ae6b57662921a2526974d26d337c0ee91c99dd73856a19d91a4be1952dca82f089d9c87da4a639e0dc87e6d66a684db5bea28e161f8d5635dddf95ea2835e2212da353c76ab34b95af78282d8850961f335c4a85f1667a3354753b86fe404893633e374642832aa967d615dada01bdf6b7e2da71e4b393cd6222e5828b96b06f28b3792f2b5eb51ff72324ba02feae9a0bde3fe7eb456dbb5a9bdaa280a67cb6f7ebc95ba2f0e4158d69d3dab53e9e4fa2486f5f0898e973c2248af802833c58aa045afda273c4d7954eef4fa6f7af777f3d7971975081f2e462aa9cf8b29d5f73089532b879f71c163364a0f847d4c7274d479bf3daba9fe5b0aa473f51345e7f1bf141d3137dc53c0f5b7d3f988a8131e63a8ba71ca4c705f434aea586fa32ee9c9cdec699e983fd7e2a04325559d28044295dd458cfb5f1541de6a2a743b69f73450d4c4d77c9f79f68af5fd02058b8d7cda419a211e5b3e5eb4a17bb0d1606a44db4fb3461425bcad81e2ed3e39c3927cae01b71aa5f30e1dbb4603e887cef40c2f688bb3b4a5dddc84d72beedf75c74c1475e3afb30492a5b25b6dfb5c8007871a165bfd5f53b069075cdf2214df21b49f395a7cc6669be61fb345dd336dba67998596ba9fce2b3b63d28d8d326ca179eba5fcaa31ce57fede569abc9540c93c9ceb989e04da52aababf4adfd548a47eb67607a56e85db5c0aea46cb9f3debacf0a20ab80b7e6086e72c11d41ce6872492a80010d26db3a40e392be3248f3d99b9e211a77df6af7ff98a6c5fd34b60c48d70393445f29613f0718d1b9be93c7ad0d7668d43659434e87ca5bce2324b5b879b5ce3347517afd583d802ef0ac525e5007f7e74461e816ee1ee0a4683cfed501816bcba9104387a86ee4a16347e1bdd61195d3c3940aa95e6fd54544b6ed21bf5e74c3d78bc4f8e63dcc38db827ead86da9d69953e06a26c3780198f998db48df5bb7167ddf2df91f0b00d7feff8a025d021a0ab5a56ac5540a578efbdedfed8189ce23722924e28bd5759d6c25012bc61d1b2ba1ca839b5267f01bd2b9b3abe6ea4c2aa93be33d72b9b98818721ed081856126e1da3e94a9a069b28535b745b2f515a089224f5e5558db28e0b6f36db2eccd7663be45291fa36cc4e18ecb41967ce75b3bc93705057ff66e785ca7737b44686dd37176e83363d97c596cfce6d0dcbbebdf96b4f8b81ac9f0d1d698c52bae759fb3ba8cbeda8d4b1e7944e94978635086f4e01f527803ad6eceb5d09d36eee7dd9f3fd90d068c50da7a7adf5d5132024086c1eb3f14aeae67c941b7d578bcbf6a3a5cdefb5db65726f88b45f95caa8ea49be36913af3213f0b77ada9746744f57c6c13d0e847e528d59b467c510f5762ad8d86f209cff9570e6e7aefe737e38d034a225e4373e9f3a4f14462f958ef3978310fe874a9cf7a189c3777e513dfe729ab1f7c2c9d013eb75f6c7bf3cb10ddd5cc7875aab0419c24a1bf38d75674dc68e1de66e1ea56a7c58eee8ffe0fa561a61773630000"], { - 'accept-ranges': 'bytes', - 'access-control-allow-origin': '*', - 'cache-control': 'max-age=300', - connection: 'keep-alive', - 'content-encoding': 'gzip', - 'content-length': '3189', - 'content-security-policy': "default-src 'none'; style-src 'unsafe-inline'; sandbox", - 'content-type': 'text/plain; charset=utf-8', - 'cross-origin-resource-policy': 'cross-origin', - date: 'Fri, 02 Jan 2026 12:03:42 GMT', - etag: 'W/"6f618be854ed64cd6a2cb850eefecf34866e637d9e73287a9d9d64a8ca3d54b3"', - expires: 'Fri, 02 Jan 2026 12:08:42 GMT', - 'source-age': '0', - 'strict-transport-security': 'max-age=31536000', - vary: 'Authorization,Accept-Encoding', - via: '1.1 varnish', - 'x-cache': 'HIT', - 'x-cache-hits': '1', - 'x-content-type-options': 'nosniff', - 'x-fastly-request-id': 'e357bbd9481ed9b72541f1d5e4d6c921c6dbf9be', - 'x-frame-options': 'deny', - 'x-github-request-id': '9733:2549FA:1BB33B8:30A9114:6957ADA3', - 'x-served-by': 'cache-lhr-egll1980089-LHR', - 'x-timer': 'S1767355422.386688,VS0,VE1', - 'x-xss-protection': '1; mode=block' - }); + nock("http://petstore.swagger.io:80", { encodedQueryParams: true }) + .post("/v2/user", "[object Object]") + .reply(201, { username: "FatBoyS" }); - nock('http://petstore.swagger.io:80', {"encodedQueryParams":true}) - .get('/v2/pet/findByStatus') - .query({"status":"pending"}) - .times(3) - .reply(404); + nock("http://petstore.swagger.io:80", { encodedQueryParams: true }) + .post("/v2/user/login", "[object Object]") + .reply(200, { AccessToken: "abc-def.123" }); - const inputFile = new Input('./test/mocks/validInput.json', 'inputs'); + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); - const arazzo = new Arazzo('./test/mocks/workingArazzoMockWithRetryOnFailure.json', 'arazzo', {logger: logger, parser}); - arazzo.setMainArazzo(); + const arazzo = new Arazzo( + "./test/mocks/multiple-workflows/single-sourceDescription/arazzoMock-user-multiple-workflow-multiple-step.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); - const spy = sinon.spy(arazzo, 'runOperation'); + const spy = sinon.spy(arazzo, "runOperation"); - try { - await arazzo.runWorkflows(inputFile); - throw new Error('Expected promise to reject but it resolved'); - } catch (err) { - expect(spy.callCount).to.be.equal(3); - expect(err).to.be.instanceOf(Error); - expect(err.message).to.be.equal(`Call to GET http://petstore.swagger.io/v2/pet/findByStatus failed with a 404`); - } + try { + await arazzo.runWorkflows(inputFile); + expect(spy.callCount).to.be.equal(2); + } catch (err) { + console.error(err); + expect(err).to.not.be.instanceOf(Error); + } - spy.restore(); - }); + spy.restore(); + }); - xit(`should throw an error when a step fails and onFailure is omitted`, async function() { - nock('https://raw.githubusercontent.com:443', {"encodedQueryParams":true}) - .get('/readmeio/oas-examples/refs/heads/main/3.1/json/petstore.json') - .reply(200, ["1f8b0800000000000013ed5ddd73dbb8117ff75fb1c3eb436fc696ec5cda074faf53274e3a9a4b134f1cb7d7c964ee607245e142020c004a5633fedf3b00488ae0b7be1c39c93df8241104f60bbf5d2c16c8e723008f27c84842bd73f07e1a9d8d4ebd63fd2b6553ee9dc3e72300002f40e90b9a28ca996ef76e46255009042489930841a298a3802b545271917f1f01fc97a7e0130653ca02e0a982583f26b7fae3f58284210a200adecf944acec763697f1a51fee1cfb59f7e042e8033784f853f9a0a44c6031c3154c7f043d6aae1ad3115fef8c711c04b2e4069c22dcdc7b0cc684b25829a219084c2475cc2ef32419f92e8e4232e7f07c541a154b645aa665cd0ff112d0898d248a190232330006f8e4266023a1b9d668204f0145511ea5f73867331150d50c4f2cdf41ac59cfaa6659d0bd3669cbfe173a688af0a05017818131ae97749421592f81fab973dd3e63e7b37a23e3289e5771989cdb01709f167084f0ada01bc5444258a168bc5889856232ec271d6971cbf9a3c7ff1fafac5c993d1e968a6e2281bf2281bd6b30621bd73786f9e7c6eee3ec924332a713e7fb2eaec83e90cef140a46a24beecb561b7dd96e71b914ddb15d7959b215091b68cec595a05ac9a932fc8b398aa59a511666632f796a35bf7aa5918f5e5e8af77b38c8655668be467fd908eb835ef83e4aa9edbf98d65c045a899d9da6b21070bdcf37090a3379642615b7f56602c9bad2f275391a2ea0c2b412a266259bd206599e280997ca252cb710630c1f4a23cb348e89581a4906011060b8800495019419d649adb0587ec473b14d0233c383e0aa647a866c416254d90c2b932150265c4f51876c00efe9e95f2a3fd56998b0398968009425a9f24a4def8bcff76596d14f0555cb62c6d8ff2a63e473fc378da646740b41159e27666e688a4960bf7c681cd1e5ee538a523de3c1b2cadf9f044e350f3f8c7d1e279c215372bc6a4f518eb5108faa03140c799ae7b5757d9304442110067847a5d2f33f7175355ccfa9e96b17aa3e1daceac925c83449228a81a3f063b7c3a7bd1d5ea102c6154c79ca3abbea37c37f6bcaaccfc53b1fedefdf88353aae5bc3d1588752cf96d78aa8b4ac692fc40dcc5503a934000fb74b90b6d37663fd571a296a023ed312e6244a519a28ea1621117c4e030c6041d50c7c1ec704246a835518805482b25076d8bbe64c935270d76af6edfa5cf9b64a0fe629355c7c4a512cab8f2a9c5e3b0caa1951c010038ddeb7083e6792062830802917592458ed51ab970ad4ac299162e529de25110fb0f9a1f4671893dac4d04a5d26863f2204a9f2a019541857e77fe54dab87daab9a2496c6c660c89cd088dc46a86740822c302f80277914942da924bc29492365082bdead34bb3f6afbd6369b5ae0ecc9003893a9895ea66904859155f5a3836864aa49ca4423a06fde1adfc551b3405b9504fd8a826e65412b66d8412b68d12cd6e65fee6b365366f60f69e4f7f570db6e74153734dc4596b1ef60dc509fbf78679dc12ebd8579a3d3571857a19bade521467023cd6b67c7faef13f3f72703b47a254e59381ae444de55e85bd78554f883f51c881e5d7b0beb1be0b6d6feb0ddc377f03e4838fb0ede7539ac05de8a840786dc0e8226027d8d85d9bc6f47f5cf09aa4970bf1348376989db254c2e3be0fc2daa543093f3a52c8cb0baacadc07088ea0ad5b3e524d806820d93cd18ac33353d103cb9043ecd732ec2d0bf1e08f7e32c650ac35ae80fe04db98889ca9afcf5a9d76eca5f237eae8f070f83743ba06b0f98f4d03997d56a6db01de5f6b82bd42409fded239a463d01ed2a1db751eed5e6e3346c691ca06c957bb531a89ea7101045b64cd1fd87aad94b2ee28340bb3c5b21b37485a5b2d6f5a303bfaf3d67dd06ca2e249f2c168b132de2935444c87c1e1815d6d4d40185850af9ed1fe8aba6645022b4a92b5ad342d12233db9690b0a2163b0f03d02f693bd5d3d08d201ae96b5e8d409383b03c57d3a2c3a8ca96f43ba16b1b177254fdb482bf002354b83e005e9af73200dc0ce4ecd09dfb10fde096237e23bccd90049d09d42989e4fa00d5a4a55697bd1750d61e999aac71a6bfc70fc10715d77c494c3f2affd2b4381ba749c449308949e8945b6c16c9d8cea4de5aa4a6c70d0316ddcb4b1ae141442a3c8b4d1efdbc78e075d95e164017097ddb10e7d76553ffdee2c51e75a0c57d85ea442a8124de34be6adf6c5b19e22d657a8a6f1325b810642438a66c8e4c715166be234764eb515ab0274ffed83594ed97e2b03de355e22826899ef859b0a5e356b340f99412a6a889343bf3499382a1edca211ec954ed0dd1c15402513d0889aefac2f501e098357320f2a7277b0c72f79836689a11a67c6da023ee9c0e5711f14d8d8fe9d1ec4c6d115e27bab737c20d801f8161ef233768c5d06f5d0f9f1d1c4ad9b0c5c65a71746de8e629b4adbf6b115ba79f5b5f6443055491859d6966aad89a9b2415fe8ce8bd88c6357b35821ce633cd28e3cfe67f8377573aa1c2eeaf585ab302dadead165da96e359f4f75506209196cc3e452dadca5d9c782bfff0c674058007ffb19ce4e47f046cd50e4854b0b1a45102243bbc35e94cff5b85aa3b96d376f3229ee23a13945e5cfbe6042b3d620a68cc6a67aeaacfe8cdce5cf4e1fe972e33bd43fb6ad20c3436fd2e4a8d27d7796b113e86c9e715f50977049159d63f1c4e0db085e6348ccef5c33cb4e9cc715f8838bab09a0105c74a19f95404f38f650e0a73d9b15640d022d9d870981bb42b9473073dc28c21c6719b6c0304d5be6d27381da5e9dc3312da7f074611d67d1d2980467680af36608110f43d4bb60a697ae8239df0c7623b75e7e6cb0adbcf24b8f2fba3512db4d706bf51d185541c37a7fdd6056f733b67ad5fbd217a6ae6b57662921a2526974d26d337c0ee91c99dd73856a19d91a4be1952dca82f089d9c87da4a639e0dc87e6d66a684db5bea28e161f8d5635dddf95ea2835e2212da353c76ab34b95af78282d8850961f335c4a85f1667a3354753b86fe404893633e374642832aa967d615dada01bdf6b7e2da71e4b393cd6222e5828b96b06f28b3792f2b5eb51ff72324ba02feae9a0bde3fe7eb456dbb5a9bdaa280a67cb6f7ebc95ba2f0e4158d69d3dab53e9e4fa2486f5f0898e973c2248af802833c58aa045afda273c4d7954eef4fa6f7af777f3d7971975081f2e462aa9cf8b29d5f73089532b879f71c163364a0f847d4c7274d479bf3daba9fe5b0aa473f51345e7f1bf141d3137dc53c0f5b7d3f988a8131e63a8ba71ca4c705f434aea586fa32ee9c9cdec699e983fd7e2a04325559d28044295dd458cfb5f1541de6a2a743b69f73450d4c4d77c9f79f68af5fd02058b8d7cda419a211e5b3e5eb4a17bb0d1606a44db4fb3461425bcad81e2ed3e39c3927cae01b71aa5f30e1dbb4603e887cef40c2f688bb3b4a5dddc84d72beedf75c74c1475e3afb30492a5b25b6dfb5c8007871a165bfd5f53b069075cdf2214df21b49f395a7cc6669be61fb345dd336dba67998596ba9fce2b3b63d28d8d326ca179eba5fcaa31ce57fede569abc9540c93c9ceb989e04da52aababf4adfd548a47eb67607a56e85db5c0aea46cb9f3debacf0a20ab80b7e6086e72c11d41ce6872492a80010d26db3a40e392be3248f3d99b9e211a77df6af7ff98a6c5fd34b60c48d70393445f29613f0718d1b9be93c7ad0d7668d43659434e87ca5bce2324b5b879b5ce3347517afd583d802ef0ac525e5007f7e74461e816ee1ee0a4683cfed501816bcba9104387a86ee4a16347e1bdd61195d3c3940aa95e6fd54544b6ed21bf5e74c3d78bc4f8e63dcc38db827ead86da9d69953e06a26c3780198f998db48df5bb7167ddf2df91f0b00d7feff8a025d021a0ab5a56ac5540a578efbdedfed8189ce23722924e28bd5759d6c25012bc61d1b2ba1ca839b5267f01bd2b9b3abe6ea4c2aa93be33d72b9b98818721ed081856126e1da3e94a9a069b28535b745b2f515a089224f5e5558db28e0b6f36db2eccd7663be45291fa36cc4e18ecb41967ce75b3bc93705057ff66e785ca7737b44686dd37176e83363d97c596cfce6d0dcbbebdf96b4f8b81ac9f0d1d698c52bae759fb3ba8cbeda8d4b1e7944e94978635086f4e01f527803ad6eceb5d09d36eee7dd9f3fd90d068c50da7a7adf5d5132024086c1eb3f14aeae67c941b7d578bcbf6a3a5cdefb5db65726f88b45f95caa8ea49be36913af3213f0b77ada9746744f57c6c13d0e847e528d59b467c510f5762ad8d86f209cff9570e6e7aefe737e38d034a225e4373e9f3a4f14462f958ef3978310fe874a9cf7a189c3777e513dfe729ab1f7c2c9d013eb75f6c7bf3cb10ddd5cc7875aab0419c24a1bf38d75674dc68e1de66e1ea56a7c58eee8ffe0fa561a61773630000"], { - 'accept-ranges': 'bytes', - 'access-control-allow-origin': '*', - 'cache-control': 'max-age=300', - connection: 'keep-alive', - 'content-encoding': 'gzip', - 'content-length': '3189', - 'content-security-policy': "default-src 'none'; style-src 'unsafe-inline'; sandbox", - 'content-type': 'text/plain; charset=utf-8', - 'cross-origin-resource-policy': 'cross-origin', - date: 'Fri, 02 Jan 2026 12:03:42 GMT', - etag: 'W/"6f618be854ed64cd6a2cb850eefecf34866e637d9e73287a9d9d64a8ca3d54b3"', - expires: 'Fri, 02 Jan 2026 12:08:42 GMT', - 'source-age': '0', - 'strict-transport-security': 'max-age=31536000', - vary: 'Authorization,Accept-Encoding', - via: '1.1 varnish', - 'x-cache': 'HIT', - 'x-cache-hits': '1', - 'x-content-type-options': 'nosniff', - 'x-fastly-request-id': 'e357bbd9481ed9b72541f1d5e4d6c921c6dbf9be', - 'x-frame-options': 'deny', - 'x-github-request-id': '9733:2549FA:1BB33B8:30A9114:6957ADA3', - 'x-served-by': 'cache-lhr-egll1980089-LHR', - 'x-timer': 'S1767355422.386688,VS0,VE1', - 'x-xss-protection': '1; mode=block' - }); + describe(`with successCriteria`, function () { + describe(`with onSuccess`, function () { + xdescribe(`goto`, function () { + it(`should handle going to a different workflow`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); - nock('http://petstore.swagger.io:80', {"encodedQueryParams":true}) - .get('/v2/pet/findByStatus') - .query({"status":"pending"}) - .reply(404); + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .reply(201, { username: "DannyB" }); - const inputFile = new Input('./test/mocks/validInput.json', 'inputs'); + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user/login", "[object Object]") + .reply(200, { username: "DannyB" }); - const arazzo = new Arazzo('./test/mocks/workingArazzoMock.json', 'arazzo', {logger: logger, parser}); - arazzo.setMainArazzo(); + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); - try { + const arazzo = new Arazzo( + "./test/mocks/multiple-workflows/single-sourceDescription/arazzoMock-user-with-successCriteria-and-onSuccess-set-to-goto-different-workflow.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { await arazzo.runWorkflows(inputFile); - throw new Error('Expected promise to reject but it resolved'); - } catch (err) { - expect(err).to.be.instanceOf(Error); - expect(err.message).to.be.equal(`Call to GET http://petstore.swagger.io/v2/pet/findByStatus failed with a 404`); - } + } catch (err) { + console.error(err); + expect(err).to.not.be.instanceOf(Error); + } + }); + }); }); - it(`should throw an error when the operationId does not exist in the OpenAPI document`, async function() { - nock('https://raw.githubusercontent.com:443', {"encodedQueryParams":true}) - .get('/readmeio/oas-examples/refs/heads/main/3.1/json/petstore.json') - .reply(200, ["1f8b0800000000000013ed5ddd73dbb8117ff75fb1c3eb436fc696ec5cda074faf53274e3a9a4b134f1cb7d7c964ee607245e142020c004a5633fedf3b00488ae0b7be1c39c93df8241104f60bbf5d2c16c8e723008f27c84842bd73f07e1a9d8d4ebd63fd2b6553ee9dc3e72300002f40e90b9a28ca996ef76e46255009042489930841a298a3802b545271917f1f01fc97a7e0130653ca02e0a982583f26b7fae3f58284210a200adecf944acec763697f1a51fee1cfb59f7e042e8033784f853f9a0a44c6031c3154c7f043d6aae1ad3115fef8c711c04b2e4069c22dcdc7b0cc684b25829a219084c2475cc2ef32419f92e8e4232e7f07c541a154b645aa665cd0ff112d0898d248a190232330006f8e4266023a1b9d668204f0145511ea5f73867331150d50c4f2cdf41ac59cfaa6659d0bd3669cbfe173a688af0a05017818131ae97749421592f81fab973dd3e63e7b37a23e3289e5771989cdb01709f167084f0ada01bc5444258a168bc5889856232ec271d6971cbf9a3c7ff1fafac5c993d1e968a6e2281bf2281bd6b30621bd73786f9e7c6eee3ec924332a713e7fb2eaec83e90cef140a46a24beecb561b7dd96e71b914ddb15d7959b215091b68cec595a05ac9a932fc8b398aa59a511666632f796a35bf7aa5918f5e5e8af77b38c8655668be467fd908eb835ef83e4aa9edbf98d65c045a899d9da6b21070bdcf37090a3379642615b7f56602c9bad2f275391a2ea0c2b412a266259bd206599e280997ca252cb710630c1f4a23cb348e89581a4906011060b8800495019419d649adb0587ec473b14d0233c383e0aa647a866c416254d90c2b932150265c4f51876c00efe9e95f2a3fd56998b0398968009425a9f24a4def8bcff76596d14f0555cb62c6d8ff2a63e473fc378da646740b41159e27666e688a4960bf7c681cd1e5ee538a523de3c1b2cadf9f044e350f3f8c7d1e279c215372bc6a4f518eb5108faa03140c799ae7b5757d9304442110067847a5d2f33f7175355ccfa9e96b17aa3e1daceac925c83449228a81a3f063b7c3a7bd1d5ea102c6154c79ca3abbea37c37f6bcaaccfc53b1fedefdf88353aae5bc3d1588752cf96d78aa8b4ac692fc40dcc5503a934000fb74b90b6d37663fd571a296a023ed312e6244a519a28ea1621117c4e030c6041d50c7c1ec704246a835518805482b25076d8bbe64c935270d76af6edfa5cf9b64a0fe629355c7c4a512cab8f2a9c5e3b0caa1951c010038ddeb7083e6792062830802917592458ed51ab970ad4ac299162e529de25110fb0f9a1f4671893dac4d04a5d26863f2204a9f2a019541857e77fe54dab87daab9a2496c6c660c89cd088dc46a86740822c302f80277914942da924bc29492365082bdead34bb3f6afbd6369b5ae0ecc9003893a9895ea66904859155f5a3836864aa49ca4423a06fde1adfc551b3405b9504fd8a826e65412b66d8412b68d12cd6e65fee6b365366f60f69e4f7f570db6e74153734dc4596b1ef60dc509fbf78679dc12ebd8579a3d3571857a19bade521467023cd6b67c7faef13f3f72703b47a254e59381ae444de55e85bd78554f883f51c881e5d7b0beb1be0b6d6feb0ddc377f03e4838fb0ede7539ac05de8a840786dc0e8226027d8d85d9bc6f47f5cf09aa4970bf1348376989db254c2e3be0fc2daa543093f3a52c8cb0baacadc07088ea0ad5b3e524d806820d93cd18ac33353d103cb9043ecd732ec2d0bf1e08f7e32c650ac35ae80fe04db98889ca9afcf5a9d76eca5f237eae8f070f83743ba06b0f98f4d03997d56a6db01de5f6b82bd42409fded239a463d01ed2a1db751eed5e6e3346c691ca06c957bb531a89ea7101045b64cd1fd87aad94b2ee28340bb3c5b21b37485a5b2d6f5a303bfaf3d67dd06ca2e249f2c168b132de2935444c87c1e1815d6d4d40185850af9ed1fe8aba6645022b4a92b5ad342d12233db9690b0a2163b0f03d02f693bd5d3d08d201ae96b5e8d409383b03c57d3a2c3a8ca96f43ba16b1b177254fdb482bf002354b83e005e9af73200dc0ce4ecd09dfb10fde096237e23bccd90049d09d42989e4fa00d5a4a55697bd1750d61e999aac71a6bfc70fc10715d77c494c3f2affd2b4381ba749c449308949e8945b6c16c9d8cea4de5aa4a6c70d0316ddcb4b1ae141442a3c8b4d1efdbc78e075d95e164017097ddb10e7d76553ffdee2c51e75a0c57d85ea442a8124de34be6adf6c5b19e22d657a8a6f1325b810642438a66c8e4c715166be234764eb515ab0274ffed83594ed97e2b03de355e22826899ef859b0a5e356b340f99412a6a889343bf3499382a1edca211ec954ed0dd1c15402513d0889aefac2f501e098357320f2a7277b0c72f79836689a11a67c6da023ee9c0e5711f14d8d8fe9d1ec4c6d115e27bab737c20d801f8161ef233768c5d06f5d0f9f1d1c4ad9b0c5c65a71746de8e629b4adbf6b115ba79f5b5f6443055491859d6966aad89a9b2415fe8ce8bd88c6357b35821ce633cd28e3cfe67f8377573aa1c2eeaf585ab302dadead165da96e359f4f75506209196cc3e452dadca5d9c782bfff0c674058007ffb19ce4e47f046cd50e4854b0b1a45102243bbc35e94cff5b85aa3b96d376f3229ee23a13945e5cfbe6042b3d620a68cc6a67aeaacfe8cdce5cf4e1fe972e33bd43fb6ad20c3436fd2e4a8d27d7796b113e86c9e715f50977049159d63f1c4e0db085e6348ccef5c33cb4e9cc715f8838bab09a0105c74a19f95404f38f650e0a73d9b15640d022d9d870981bb42b9473073dc28c21c6719b6c0304d5be6d27381da5e9dc3312da7f074611d67d1d2980467680af36608110f43d4bb60a697ae8239df0c7623b75e7e6cb0adbcf24b8f2fba3512db4d706bf51d185541c37a7fdd6056f733b67ad5fbd217a6ae6b57662921a2526974d26d337c0ee91c99dd73856a19d91a4be1952dca82f089d9c87da4a639e0dc87e6d66a684db5bea28e161f8d5635dddf95ea2835e2212da353c76ab34b95af78282d8850961f335c4a85f1667a3354753b86fe404893633e374642832aa967d615dada01bdf6b7e2da71e4b393cd6222e5828b96b06f28b3792f2b5eb51ff72324ba02feae9a0bde3fe7eb456dbb5a9bdaa280a67cb6f7ebc95ba2f0e4158d69d3dab53e9e4fa2486f5f0898e973c2248af802833c58aa045afda273c4d7954eef4fa6f7af777f3d7971975081f2e462aa9cf8b29d5f73089532b879f71c163364a0f847d4c7274d479bf3daba9fe5b0aa473f51345e7f1bf141d3137dc53c0f5b7d3f988a8131e63a8ba71ca4c705f434aea586fa32ee9c9cdec699e983fd7e2a04325559d28044295dd458cfb5f1541de6a2a743b69f73450d4c4d77c9f79f68af5fd02058b8d7cda419a211e5b3e5eb4a17bb0d1606a44db4fb3461425bcad81e2ed3e39c3927cae01b71aa5f30e1dbb4603e887cef40c2f688bb3b4a5dddc84d72beedf75c74c1475e3afb30492a5b25b6dfb5c8007871a165bfd5f53b069075cdf2214df21b49f395a7cc6669be61fb345dd336dba67998596ba9fce2b3b63d28d8d326ca179eba5fcaa31ce57fede569abc9540c93c9ceb989e04da52aababf4adfd548a47eb67607a56e85db5c0aea46cb9f3debacf0a20ab80b7e6086e72c11d41ce6872492a80010d26db3a40e392be3248f3d99b9e211a77df6af7ff98a6c5fd34b60c48d70393445f29613f0718d1b9be93c7ad0d7668d43659434e87ca5bce2324b5b879b5ce3347517afd583d802ef0ac525e5007f7e74461e816ee1ee0a4683cfed501816bcba9104387a86ee4a16347e1bdd61195d3c3940aa95e6fd54544b6ed21bf5e74c3d78bc4f8e63dcc38db827ead86da9d69953e06a26c3780198f998db48df5bb7167ddf2df91f0b00d7feff8a025d021a0ab5a56ac5540a578efbdedfed8189ce23722924e28bd5759d6c25012bc61d1b2ba1ca839b5267f01bd2b9b3abe6ea4c2aa93be33d72b9b98818721ed081856126e1da3e94a9a069b28535b745b2f515a089224f5e5558db28e0b6f36db2eccd7663be45291fa36cc4e18ecb41967ce75b3bc93705057ff66e785ca7737b44686dd37176e83363d97c596cfce6d0dcbbebdf96b4f8b81ac9f0d1d698c52bae759fb3ba8cbeda8d4b1e7944e94978635086f4e01f527803ad6eceb5d09d36eee7dd9f3fd90d068c50da7a7adf5d5132024086c1eb3f14aeae67c941b7d578bcbf6a3a5cdefb5db65726f88b45f95caa8ea49be36913af3213f0b77ada9746744f57c6c13d0e847e528d59b467c510f5762ad8d86f209cff9570e6e7aefe737e38d034a225e4373e9f3a4f14462f958ef3978310fe874a9cf7a189c3777e513dfe729ab1f7c2c9d013eb75f6c7bf3cb10ddd5cc7875aab0419c24a1bf38d75674dc68e1de66e1ea56a7c58eee8ffe0fa561a61773630000"], { - 'accept-ranges': 'bytes', - 'access-control-allow-origin': '*', - 'cache-control': 'max-age=300', - connection: 'keep-alive', - 'content-encoding': 'gzip', - 'content-length': '3189', - 'content-security-policy': "default-src 'none'; style-src 'unsafe-inline'; sandbox", - 'content-type': 'text/plain; charset=utf-8', - 'cross-origin-resource-policy': 'cross-origin', - date: 'Fri, 02 Jan 2026 12:03:42 GMT', - etag: 'W/"6f618be854ed64cd6a2cb850eefecf34866e637d9e73287a9d9d64a8ca3d54b3"', - expires: 'Fri, 02 Jan 2026 12:08:42 GMT', - 'source-age': '0', - 'strict-transport-security': 'max-age=31536000', - vary: 'Authorization,Accept-Encoding', - via: '1.1 varnish', - 'x-cache': 'HIT', - 'x-cache-hits': '1', - 'x-content-type-options': 'nosniff', - 'x-fastly-request-id': 'e357bbd9481ed9b72541f1d5e4d6c921c6dbf9be', - 'x-frame-options': 'deny', - 'x-github-request-id': '9733:2549FA:1BB33B8:30A9114:6957ADA3', - 'x-served-by': 'cache-lhr-egll1980089-LHR', - 'x-timer': 'S1767355422.386688,VS0,VE1', - 'x-xss-protection': '1; mode=block' - }); + describe(`with onFalure`, function () { + describe(`single onFailure`, function () { + describe(`onFailure with criteria`, function () { + describe(`goto`, function () { + it(`should handle skipping over workflows`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); - const missingOperationIdOpenAPIFile = structuredClone(openAPIMock); - delete missingOperationIdOpenAPIFile.paths["/pet/findByStatus"]; + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .times(2) + .reply(201, { username: "FatBoyS" }); - const stub = sinon.stub(OpenAPI.prototype, 'writeDocument').resolves(); - OpenAPI.prototype.fileName = 'Arazzo-Workflow-for-Petstore-openAPI.json'; - OpenAPI.prototype.filePath = path.resolve(__dirname, '../..', 'Arazzo-Workflow-for-Petstore-openAPI.json'); - await fsp.writeFile('./Arazzo-Workflow-for-Petstore-openAPI.json', JSON.stringify(missingOperationIdOpenAPIFile)); + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user/login", "[object Object]") + .reply(400); - const inputFile = new Input('./test/mocks/validInput.json', 'inputs'); + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user/login", "[object Object]") + .reply(200, { AccessToken: "abc-def.123" }); - const arazzo = new Arazzo('./test/mocks/workingArazzoMock.json', 'arazzo', {logger: logger, parser}); - arazzo.setMainArazzo(); + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); - try { - await arazzo.runWorkflows(inputFile); - throw new Error('Expected promise to reject but it resolved'); - } catch (err) { - expect(err).to.be.instanceOf(Error); - expect(err.message).to.be.equal(`The OperationId: findPetsByStatus does not exist`); - } + const arazzo = new Arazzo( + "./test/mocks/multiple-workflows/single-sourceDescription/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-goto-different-workflow.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); - stub.restore(); - }); + const spy = sinon.spy(arazzo, "runOperation"); + + try { + await arazzo.runWorkflows(inputFile); + expect(spy.callCount).to.be.equal(4); + } catch (err) { + console.error(err); + expect(err).to.not.be.instanceOf(Error); + } - xit(`should throw an error if redocly can not bundle the document`, async function() { - nock('https://raw.githubusercontent.com:443', {"encodedQueryParams":true}) - .get('/readmeio/oas-examples/refs/heads/main/3.1/json/petstore.json') - .reply(200, ["1f8b0800000000000013ed5ddd73dbb8117ff75fb1c3eb436fc696ec5cda074faf53274e3a9a4b134f1cb7d7c964ee607245e142020c004a5633fedf3b00488ae0b7be1c39c93df8241104f60bbf5d2c16c8e723008f27c84842bd73f07e1a9d8d4ebd63fd2b6553ee9dc3e72300002f40e90b9a28ca996ef76e46255009042489930841a298a3802b545271917f1f01fc97a7e0130653ca02e0a982583f26b7fae3f58284210a200adecf944acec763697f1a51fee1cfb59f7e042e8033784f853f9a0a44c6031c3154c7f043d6aae1ad3115fef8c711c04b2e4069c22dcdc7b0cc684b25829a219084c2475cc2ef32419f92e8e4232e7f07c541a154b645aa665cd0ff112d0898d248a190232330006f8e4266023a1b9d668204f0145511ea5f73867331150d50c4f2cdf41ac59cfaa6659d0bd3669cbfe173a688af0a05017818131ae97749421592f81fab973dd3e63e7b37a23e3289e5771989cdb01709f167084f0ada01bc5444258a168bc5889856232ec271d6971cbf9a3c7ff1fafac5c993d1e968a6e2281bf2281bd6b30621bd73786f9e7c6eee3ec924332a713e7fb2eaec83e90cef140a46a24beecb561b7dd96e71b914ddb15d7959b215091b68cec595a05ac9a932fc8b398aa59a511666632f796a35bf7aa5918f5e5e8af77b38c8655668be467fd908eb835ef83e4aa9edbf98d65c045a899d9da6b21070bdcf37090a3379642615b7f56602c9bad2f275391a2ea0c2b412a266259bd206599e280997ca252cb710630c1f4a23cb348e89581a4906011060b8800495019419d649adb0587ec473b14d0233c383e0aa647a866c416254d90c2b932150265c4f51876c00efe9e95f2a3fd56998b0398968009425a9f24a4def8bcff76596d14f0555cb62c6d8ff2a63e473fc378da646740b41159e27666e688a4960bf7c681cd1e5ee538a523de3c1b2cadf9f044e350f3f8c7d1e279c215372bc6a4f518eb5108faa03140c799ae7b5757d9304442110067847a5d2f33f7175355ccfa9e96b17aa3e1daceac925c83449228a81a3f063b7c3a7bd1d5ea102c6154c79ca3abbea37c37f6bcaaccfc53b1fedefdf88353aae5bc3d1588752cf96d78aa8b4ac692fc40dcc5503a934000fb74b90b6d37663fd571a296a023ed312e6244a519a28ea1621117c4e030c6041d50c7c1ec704246a835518805482b25076d8bbe64c935270d76af6edfa5cf9b64a0fe629355c7c4a512cab8f2a9c5e3b0caa1951c010038ddeb7083e6792062830802917592458ed51ab970ad4ac299162e529de25110fb0f9a1f4671893dac4d04a5d26863f2204a9f2a019541857e77fe54dab87daab9a2496c6c660c89cd088dc46a86740822c302f80277914942da924bc29492365082bdead34bb3f6afbd6369b5ae0ecc9003893a9895ea66904859155f5a3836864aa49ca4423a06fde1adfc551b3405b9504fd8a826e65412b66d8412b68d12cd6e65fee6b365366f60f69e4f7f570db6e74153734dc4596b1ef60dc509fbf78679dc12ebd8579a3d3571857a19bade521467023cd6b67c7faef13f3f72703b47a254e59381ae444de55e85bd78554f883f51c881e5d7b0beb1be0b6d6feb0ddc377f03e4838fb0ede7539ac05de8a840786dc0e8226027d8d85d9bc6f47f5cf09aa4970bf1348376989db254c2e3be0fc2daa543093f3a52c8cb0baacadc07088ea0ad5b3e524d806820d93cd18ac33353d103cb9043ecd732ec2d0bf1e08f7e32c650ac35ae80fe04db98889ca9afcf5a9d76eca5f237eae8f070f83743ba06b0f98f4d03997d56a6db01de5f6b82bd42409fded239a463d01ed2a1db751eed5e6e3346c691ca06c957bb531a89ea7101045b64cd1fd87aad94b2ee28340bb3c5b21b37485a5b2d6f5a303bfaf3d67dd06ca2e249f2c168b132de2935444c87c1e1815d6d4d40185850af9ed1fe8aba6645022b4a92b5ad342d12233db9690b0a2163b0f03d02f693bd5d3d08d201ae96b5e8d409383b03c57d3a2c3a8ca96f43ba16b1b177254fdb482bf002354b83e005e9af73200dc0ce4ecd09dfb10fde096237e23bccd90049d09d42989e4fa00d5a4a55697bd1750d61e999aac71a6bfc70fc10715d77c494c3f2affd2b4381ba749c449308949e8945b6c16c9d8cea4de5aa4a6c70d0316ddcb4b1ae141442a3c8b4d1efdbc78e075d95e164017097ddb10e7d76553ffdee2c51e75a0c57d85ea442a8124de34be6adf6c5b19e22d657a8a6f1325b810642438a66c8e4c715166be234764eb515ab0274ffed83594ed97e2b03de355e22826899ef859b0a5e356b340f99412a6a889343bf3499382a1edca211ec954ed0dd1c15402513d0889aefac2f501e098357320f2a7277b0c72f79836689a11a67c6da023ee9c0e5711f14d8d8fe9d1ec4c6d115e27bab737c20d801f8161ef233768c5d06f5d0f9f1d1c4ad9b0c5c65a71746de8e629b4adbf6b115ba79f5b5f6443055491859d6966aad89a9b2415fe8ce8bd88c6357b35821ce633cd28e3cfe67f8377573aa1c2eeaf585ab302dadead165da96e359f4f75506209196cc3e452dadca5d9c782bfff0c674058007ffb19ce4e47f046cd50e4854b0b1a45102243bbc35e94cff5b85aa3b96d376f3229ee23a13945e5cfbe6042b3d620a68cc6a67aeaacfe8cdce5cf4e1fe972e33bd43fb6ad20c3436fd2e4a8d27d7796b113e86c9e715f50977049159d63f1c4e0db085e6348ccef5c33cb4e9cc715f8838bab09a0105c74a19f95404f38f650e0a73d9b15640d022d9d870981bb42b9473073dc28c21c6719b6c0304d5be6d27381da5e9dc3312da7f074611d67d1d2980467680af36608110f43d4bb60a697ae8239df0c7623b75e7e6cb0adbcf24b8f2fba3512db4d706bf51d185541c37a7fdd6056f733b67ad5fbd217a6ae6b57662921a2526974d26d337c0ee91c99dd73856a19d91a4be1952dca82f089d9c87da4a639e0dc87e6d66a684db5bea28e161f8d5635dddf95ea2835e2212da353c76ab34b95af78282d8850961f335c4a85f1667a3354753b86fe404893633e374642832aa967d615dada01bdf6b7e2da71e4b393cd6222e5828b96b06f28b3792f2b5eb51ff72324ba02feae9a0bde3fe7eb456dbb5a9bdaa280a67cb6f7ebc95ba2f0e4158d69d3dab53e9e4fa2486f5f0898e973c2248af802833c58aa045afda273c4d7954eef4fa6f7af777f3d7971975081f2e462aa9cf8b29d5f73089532b879f71c163364a0f847d4c7274d479bf3daba9fe5b0aa473f51345e7f1bf141d3137dc53c0f5b7d3f988a8131e63a8ba71ca4c705f434aea586fa32ee9c9cdec699e983fd7e2a04325559d28044295dd458cfb5f1541de6a2a743b69f73450d4c4d77c9f79f68af5fd02058b8d7cda419a211e5b3e5eb4a17bb0d1606a44db4fb3461425bcad81e2ed3e39c3927cae01b71aa5f30e1dbb4603e887cef40c2f688bb3b4a5dddc84d72beedf75c74c1475e3afb30492a5b25b6dfb5c8007871a165bfd5f53b069075cdf2214df21b49f395a7cc6669be61fb345dd336dba67998596ba9fce2b3b63d28d8d326ca179eba5fcaa31ce57fede569abc9540c93c9ceb989e04da52aababf4adfd548a47eb67607a56e85db5c0aea46cb9f3debacf0a20ab80b7e6086e72c11d41ce6872492a80010d26db3a40e392be3248f3d99b9e211a77df6af7ff98a6c5fd34b60c48d70393445f29613f0718d1b9be93c7ad0d7668d43659434e87ca5bce2324b5b879b5ce3347517afd583d802ef0ac525e5007f7e74461e816ee1ee0a4683cfed501816bcba9104387a86ee4a16347e1bdd61195d3c3940aa95e6fd54544b6ed21bf5e74c3d78bc4f8e63dcc38db827ead86da9d69953e06a26c3780198f998db48df5bb7167ddf2df91f0b00d7feff8a025d021a0ab5a56ac5540a578efbdedfed8189ce23722924e28bd5759d6c25012bc61d1b2ba1ca839b5267f01bd2b9b3abe6ea4c2aa93be33d72b9b98818721ed081856126e1da3e94a9a069b28535b745b2f515a089224f5e5558db28e0b6f36db2eccd7663be45291fa36cc4e18ecb41967ce75b3bc93705057ff66e785ca7737b44686dd37176e83363d97c596cfce6d0dcbbebdf96b4f8b81ac9f0d1d698c52bae759fb3ba8cbeda8d4b1e7944e94978635086f4e01f527803ad6eceb5d09d36eee7dd9f3fd90d068c50da7a7adf5d5132024086c1eb3f14aeae67c941b7d578bcbf6a3a5cdefb5db65726f88b45f95caa8ea49be36913af3213f0b77ada9746744f57c6c13d0e847e528d59b467c510f5762ad8d86f209cff9570e6e7aefe737e38d034a225e4373e9f3a4f14462f958ef3978310fe874a9cf7a189c3777e513dfe729ab1f7c2c9d013eb75f6c7bf3cb10ddd5cc7875aab0419c24a1bf38d75674dc68e1de66e1ea56a7c58eee8ffe0fa561a61773630000"], { - 'accept-ranges': 'bytes', - 'access-control-allow-origin': '*', - 'cache-control': 'max-age=300', - connection: 'keep-alive', - 'content-encoding': 'gzip', - 'content-length': '3189', - 'content-security-policy': "default-src 'none'; style-src 'unsafe-inline'; sandbox", - 'content-type': 'text/plain; charset=utf-8', - 'cross-origin-resource-policy': 'cross-origin', - date: 'Fri, 02 Jan 2026 12:03:42 GMT', - etag: 'W/"6f618be854ed64cd6a2cb850eefecf34866e637d9e73287a9d9d64a8ca3d54b3"', - expires: 'Fri, 02 Jan 2026 12:08:42 GMT', - 'source-age': '0', - 'strict-transport-security': 'max-age=31536000', - vary: 'Authorization,Accept-Encoding', - via: '1.1 varnish', - 'x-cache': 'HIT', - 'x-cache-hits': '1', - 'x-content-type-options': 'nosniff', - 'x-fastly-request-id': 'e357bbd9481ed9b72541f1d5e4d6c921c6dbf9be', - 'x-frame-options': 'deny', - 'x-github-request-id': '9733:2549FA:1BB33B8:30A9114:6957ADA3', - 'x-served-by': 'cache-lhr-egll1980089-LHR', - 'x-timer': 'S1767355422.386688,VS0,VE1', - 'x-xss-protection': '1; mode=block' + spy.restore(); }); + }); - const stub = sinon.stub(bundleFromString).rejects(new Error('sinon threw an error when bundling the document')); + describe(`retry`, function () { + it(`retries the step once and the referenced workflowId`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:19:56 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:24:56 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "6345964a5ff2dfaf90e7f710c781377e2f0b2a7e", + "x-frame-options": "deny", + "x-github-request-id": + "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980052-LHR", + "x-timer": "S1767727197.761065,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); - const inputFile = new Input('./test/mocks/validInput.json', 'inputs'); + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user", "[object Object]") + .times(2) + .reply(201, { username: "FatBoyS" }); - const arazzo = new Arazzo('./test/mocks/workingArazzoMock.json', 'arazzo', {logger: logger, parser}); - arazzo.setMainArazzo(); + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user/login", "[object Object]") + .reply(404); - try { - await arazzo.runWorkflows(inputFile); - throw new Error('Expected promise to reject but it resolved'); - } catch (err) { - expect(err).to.be.instanceOf(Error); - expect(err.message).to.be.equal(`sinon threw an error when bundling the document`); - } + nock("http://petstore.swagger.io:80", { + encodedQueryParams: true, + }) + .post("/v2/user/login", "[object Object]") + .reply(200, { AccessToken: "abc-def.123" }); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/multiple-workflows/single-sourceDescription/onFailure/arazzoMock-user-with-successCriteria-and-onFailure-set-to-execute-a-step-and-retry-workflow.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + const spy = sinon.spy(arazzo, "runOperation"); + + try { + await arazzo.runWorkflows(inputFile); + expect(spy.callCount).to.be.equal(3); + } catch (err) { + console.error(err); + expect(err).to.not.be.instanceOf(Error); + } - stub.restore(); + spy.restore(); + }); + }); + }); + }); }); + }); }); + }); - it(`should throw an error when writing a downloaded sourceDescription file fails`, async function() { - nock('https://raw.githubusercontent.com:443', {"encodedQueryParams":true}) - .get('/readmeio/oas-examples/refs/heads/main/3.1/json/petstore.json') - .reply(200, ["1f8b0800000000000013ed5ddd73dbb8117ff75fb1c3eb436fc696ec5cda074faf53274e3a9a4b134f1cb7d7c964ee607245e142020c004a5633fedf3b00488ae0b7be1c39c93df8241104f60bbf5d2c16c8e723008f27c84842bd73f07e1a9d8d4ebd63fd2b6553ee9dc3e72300002f40e90b9a28ca996ef76e46255009042489930841a298a3802b545271917f1f01fc97a7e0130653ca02e0a982583f26b7fae3f58284210a200adecf944acec763697f1a51fee1cfb59f7e042e8033784f853f9a0a44c6031c3154c7f043d6aae1ad3115fef8c711c04b2e4069c22dcdc7b0cc684b25829a219084c2475cc2ef32419f92e8e4232e7f07c541a154b645aa665cd0ff112d0898d248a190232330006f8e4266023a1b9d668204f0145511ea5f73867331150d50c4f2cdf41ac59cfaa6659d0bd3669cbfe173a688af0a05017818131ae97749421592f81fab973dd3e63e7b37a23e3289e5771989cdb01709f167084f0ada01bc5444258a168bc5889856232ec271d6971cbf9a3c7ff1fafac5c993d1e968a6e2281bf2281bd6b30621bd73786f9e7c6eee3ec924332a713e7fb2eaec83e90cef140a46a24beecb561b7dd96e71b914ddb15d7959b215091b68cec595a05ac9a932fc8b398aa59a511666632f796a35bf7aa5918f5e5e8af77b38c8655668be467fd908eb835ef83e4aa9edbf98d65c045a899d9da6b21070bdcf37090a3379642615b7f56602c9bad2f275391a2ea0c2b412a266259bd206599e280997ca252cb710630c1f4a23cb348e89581a4906011060b8800495019419d649adb0587ec473b14d0233c383e0aa647a866c416254d90c2b932150265c4f51876c00efe9e95f2a3fd56998b0398968009425a9f24a4def8bcff76596d14f0555cb62c6d8ff2a63e473fc378da646740b41159e27666e688a4960bf7c681cd1e5ee538a523de3c1b2cadf9f044e350f3f8c7d1e279c215372bc6a4f518eb5108faa03140c799ae7b5757d9304442110067847a5d2f33f7175355ccfa9e96b17aa3e1daceac925c83449228a81a3f063b7c3a7bd1d5ea102c6154c79ca3abbea37c37f6bcaaccfc53b1fedefdf88353aae5bc3d1588752cf96d78aa8b4ac692fc40dcc5503a934000fb74b90b6d37663fd571a296a023ed312e6244a519a28ea1621117c4e030c6041d50c7c1ec704246a835518805482b25076d8bbe64c935270d76af6edfa5cf9b64a0fe629355c7c4a512cab8f2a9c5e3b0caa1951c010038ddeb7083e6792062830802917592458ed51ab970ad4ac299162e529de25110fb0f9a1f4671893dac4d04a5d26863f2204a9f2a019541857e77fe54dab87daab9a2496c6c660c89cd088dc46a86740822c302f80277914942da924bc29492365082bdead34bb3f6afbd6369b5ae0ecc9003893a9895ea66904859155f5a3836864aa49ca4423a06fde1adfc551b3405b9504fd8a826e65412b66d8412b68d12cd6e65fee6b365366f60f69e4f7f570db6e74153734dc4596b1ef60dc509fbf78679dc12ebd8579a3d3571857a19bade521467023cd6b67c7faef13f3f72703b47a254e59381ae444de55e85bd78554f883f51c881e5d7b0beb1be0b6d6feb0ddc377f03e4838fb0ede7539ac05de8a840786dc0e8226027d8d85d9bc6f47f5cf09aa4970bf1348376989db254c2e3be0fc2daa543093f3a52c8cb0baacadc07088ea0ad5b3e524d806820d93cd18ac33353d103cb9043ecd732ec2d0bf1e08f7e32c650ac35ae80fe04db98889ca9afcf5a9d76eca5f237eae8f070f83743ba06b0f98f4d03997d56a6db01de5f6b82bd42409fded239a463d01ed2a1db751eed5e6e3346c691ca06c957bb531a89ea7101045b64cd1fd87aad94b2ee28340bb3c5b21b37485a5b2d6f5a303bfaf3d67dd06ca2e249f2c168b132de2935444c87c1e1815d6d4d40185850af9ed1fe8aba6645022b4a92b5ad342d12233db9690b0a2163b0f03d02f693bd5d3d08d201ae96b5e8d409383b03c57d3a2c3a8ca96f43ba16b1b177254fdb482bf002354b83e005e9af73200dc0ce4ecd09dfb10fde096237e23bccd90049d09d42989e4fa00d5a4a55697bd1750d61e999aac71a6bfc70fc10715d77c494c3f2affd2b4381ba749c449308949e8945b6c16c9d8cea4de5aa4a6c70d0316ddcb4b1ae141442a3c8b4d1efdbc78e075d95e164017097ddb10e7d76553ffdee2c51e75a0c57d85ea442a8124de34be6adf6c5b19e22d657a8a6f1325b810642438a66c8e4c715166be234764eb515ab0274ffed83594ed97e2b03de355e22826899ef859b0a5e356b340f99412a6a889343bf3499382a1edca211ec954ed0dd1c15402513d0889aefac2f501e098357320f2a7277b0c72f79836689a11a67c6da023ee9c0e5711f14d8d8fe9d1ec4c6d115e27bab737c20d801f8161ef233768c5d06f5d0f9f1d1c4ad9b0c5c65a71746de8e629b4adbf6b115ba79f5b5f6443055491859d6966aad89a9b2415fe8ce8bd88c6357b35821ce633cd28e3cfe67f8377573aa1c2eeaf585ab302dadead165da96e359f4f75506209196cc3e452dadca5d9c782bfff0c674058007ffb19ce4e47f046cd50e4854b0b1a45102243bbc35e94cff5b85aa3b96d376f3229ee23a13945e5cfbe6042b3d620a68cc6a67aeaacfe8cdce5cf4e1fe972e33bd43fb6ad20c3436fd2e4a8d27d7796b113e86c9e715f50977049159d63f1c4e0db085e6348ccef5c33cb4e9cc715f8838bab09a0105c74a19f95404f38f650e0a73d9b15640d022d9d870981bb42b9473073dc28c21c6719b6c0304d5be6d27381da5e9dc3312da7f074611d67d1d2980467680af36608110f43d4bb60a697ae8239df0c7623b75e7e6cb0adbcf24b8f2fba3512db4d706bf51d185541c37a7fdd6056f733b67ad5fbd217a6ae6b57662921a2526974d26d337c0ee91c99dd73856a19d91a4be1952dca82f089d9c87da4a639e0dc87e6d66a684db5bea28e161f8d5635dddf95ea2835e2212da353c76ab34b95af78282d8850961f335c4a85f1667a3354753b86fe404893633e374642832aa967d615dada01bdf6b7e2da71e4b393cd6222e5828b96b06f28b3792f2b5eb51ff72324ba02feae9a0bde3fe7eb456dbb5a9bdaa280a67cb6f7ebc95ba2f0e4158d69d3dab53e9e4fa2486f5f0898e973c2248af802833c58aa045afda273c4d7954eef4fa6f7af777f3d7971975081f2e462aa9cf8b29d5f73089532b879f71c163364a0f847d4c7274d479bf3daba9fe5b0aa473f51345e7f1bf141d3137dc53c0f5b7d3f988a8131e63a8ba71ca4c705f434aea586fa32ee9c9cdec699e983fd7e2a04325559d28044295dd458cfb5f1541de6a2a743b69f73450d4c4d77c9f79f68af5fd02058b8d7cda419a211e5b3e5eb4a17bb0d1606a44db4fb3461425bcad81e2ed3e39c3927cae01b71aa5f30e1dbb4603e887cef40c2f688bb3b4a5dddc84d72beedf75c74c1475e3afb30492a5b25b6dfb5c8007871a165bfd5f53b069075cdf2214df21b49f395a7cc6669be61fb345dd336dba67998596ba9fce2b3b63d28d8d326ca179eba5fcaa31ce57fede569abc9540c93c9ceb989e04da52aababf4adfd548a47eb67607a56e85db5c0aea46cb9f3debacf0a20ab80b7e6086e72c11d41ce6872492a80010d26db3a40e392be3248f3d99b9e211a77df6af7ff98a6c5fd34b60c48d70393445f29613f0718d1b9be93c7ad0d7668d43659434e87ca5bce2324b5b879b5ce3347517afd583d802ef0ac525e5007f7e74461e816ee1ee0a4683cfed501816bcba9104387a86ee4a16347e1bdd61195d3c3940aa95e6fd54544b6ed21bf5e74c3d78bc4f8e63dcc38db827ead86da9d69953e06a26c3780198f998db48df5bb7167ddf2df91f0b00d7feff8a025d021a0ab5a56ac5540a578efbdedfed8189ce23722924e28bd5759d6c25012bc61d1b2ba1ca839b5267f01bd2b9b3abe6ea4c2aa93be33d72b9b98818721ed081856126e1da3e94a9a069b28535b745b2f515a089224f5e5558db28e0b6f36db2eccd7663be45291fa36cc4e18ecb41967ce75b3bc93705057ff66e785ca7737b44686dd37176e83363d97c596cfce6d0dcbbebdf96b4f8b81ac9f0d1d698c52bae759fb3ba8cbeda8d4b1e7944e94978635086f4e01f527803ad6eceb5d09d36eee7dd9f3fd90d068c50da7a7adf5d5132024086c1eb3f14aeae67c941b7d578bcbf6a3a5cdefb5db65726f88b45f95caa8ea49be36913af3213f0b77ada9746744f57c6c13d0e847e528d59b467c510f5762ad8d86f209cff9570e6e7aefe737e38d034a225e4373e9f3a4f14462f958ef3978310fe874a9cf7a189c3777e513dfe729ab1f7c2c9d013eb75f6c7bf3cb10ddd5cc7875aab0419c24a1bf38d75674dc68e1de66e1ea56a7c58eee8ffe0fa561a61773630000"], { - 'accept-ranges': 'bytes', - 'access-control-allow-origin': '*', - 'cache-control': 'max-age=300', - connection: 'keep-alive', - 'content-encoding': 'gzip', - 'content-length': '3189', - 'content-security-policy': "default-src 'none'; style-src 'unsafe-inline'; sandbox", - 'content-type': 'text/plain; charset=utf-8', - 'cross-origin-resource-policy': 'cross-origin', - date: 'Fri, 02 Jan 2026 12:03:42 GMT', - etag: 'W/"6f618be854ed64cd6a2cb850eefecf34866e637d9e73287a9d9d64a8ca3d54b3"', - expires: 'Fri, 02 Jan 2026 12:08:42 GMT', - 'source-age': '0', - 'strict-transport-security': 'max-age=31536000', - vary: 'Authorization,Accept-Encoding', - via: '1.1 varnish', - 'x-cache': 'HIT', - 'x-cache-hits': '1', - 'x-content-type-options': 'nosniff', - 'x-fastly-request-id': 'e357bbd9481ed9b72541f1d5e4d6c921c6dbf9be', - 'x-frame-options': 'deny', - 'x-github-request-id': '9733:2549FA:1BB33B8:30A9114:6957ADA3', - 'x-served-by': 'cache-lhr-egll1980089-LHR', - 'x-timer': 'S1767355422.386688,VS0,VE1', - 'x-xss-protection': '1; mode=block' - }); + describe(`multiple sourceDescription`, function () { + describe(`multiple workflows`, function () { + it(`resolves as expected`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Sat, 10 Jan 2026 17:11:02 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Sat, 10 Jan 2026 17:16:02 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "MISS", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "15432efddde1b3c540e88b716507dd6599c23a3e", + "x-frame-options": "deny", + "x-github-request-id": "5253:1741DC:3E3EE3:75E143:69628824", + "x-served-by": "cache-lhr-egll1980077-LHR", + "x-timer": "S1768065062.991461,VS0,VE107", + "x-xss-protection": "1; mode=block", + }, + ); - const stub = sinon.stub(fsp, 'writeFile').rejects(new Error('sinon threw an error when writing a sourceDescription file')); + nock("http://petstore.swagger.io:80", { encodedQueryParams: true }) + .post("/v2/user", "[object Object]") + .reply( + 201, + { username: "DannyB" }, + { + "access-control-allow-headers": + "Content-Type, api_key, Authorization", + "access-control-allow-methods": "GET, POST, DELETE, PUT", + "access-control-allow-origin": "*", + connection: "keep-alive", + "content-type": "application/json", + date: "Sat, 10 Jan 2026 17:12:23 GMT", + server: "Jetty(9.2.9.v20150224)", + "transfer-encoding": "chunked", + }, + ); - const inputFile = new Input('./test/mocks/validInput.json', 'inputs'); + nock("http://petstore.swagger.io:80", { encodedQueryParams: true }) + .post("/v2/user/login", "[object Object]") + .reply(200, { AccessToken: "abc-def.123" }); - const arazzo = new Arazzo('./test/mocks/workingArazzoMock.json', 'arazzo', {logger: logger, parser}); + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-pets/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5add6fdb38127fcf5f31d0dec32d90c869dabb873e5dba6980e03e5a6cdb5d1c7ac52d2d8d6d6e25924b52767c45fff70329d996f825c749b169d7790812491c0e8733bfdf0c879f4e00322e901141b3e7903dcdcff3a7d9a9795af05a70864cabec397c3a0100c854b1c09aec1e98cf24128daf51f71e02647a2dd008e4d35fb1d05662f746482e506a8a6a30c288221ae75cae9de729696989f62d2d034f7b3229d33847e908b59fccb8ac89ee3efaebb3ccf9e2b33b2463a4c6f46c4a4bcae69ea4c1ff8edcecb6ae420beb26cb7ed8986d28a4f7df4060584957414705bc25b5a8ec07259fcf296651f162c1357f272b7f33b6731029c9da9d826aac831b9832dd1d4db5d1cddbec6c258910685c45cb06f7b4a426f3075f65d0c5c79c3ceee67b39fab8ab079c3de5eea30eefba7c2898c27bd9dbcdb7e461034913d7ef0f750ca5896e12ae110eb2125521a9d09433ebaca8a1150494815e2028cd257aa1c99a3a7b0eef5dc5c992d08a4c2b7780f12564a53fbfd19b57e5d0a41ffa6bdcfedd5b6d26f1b7864a6ba1be0ead5523c8b07dfaa127c8df9dedce187ad94d7ee228d17150f920241488a39108f262c7792d9194af58b5f65de873624d5fdf62067b7de4f2a0dc23971fb9fcc8e5feb4472e87239743c77b9736d882e4e7c661300653f1978abd70dc8dc6dc08652649d3f7eb28711e71e5105c0913ea18b844944ec7fc18b5fad3c4e975847a12e4339e9fdcdd8851aa4d63ea98958394fb202b8f04c87888a48264cf30d9275082a1920e963dc2c50f987048c6f63b4dc70f128e215abe9f1345e8798f603d88a2e3249da4e9045107a97a48d6a9f42442d921d20ed3f680b8237b1826efd102f69acbfafe45ec21358eb3b7ef44690e08c088023eb3bb2b9ca53c7cd2b799b5f3aac8bc0913be1315272596d7b4c2fbdbb1e065c28efb1c073cbd885bac9393b65774748d4a91f9dd048c1a2eee7edef6f5d639a58cecf2859d4c8fad0e4c4add95f8da0bd43765782a7f976264b3134704fdef478c24d4436d5a5dbaa199c2a29154afdf98aecfc09fb2cb462fb8a4ff239daf87ac22e8df7160960d880c07f7ed66c36681a444e9a874d2a9955136e3bb8694a6ba4db7deacc87c8e125ea3eec3b61b926f175401554040d94c0d14ca656f58f77f0ef06fde404118cc282b81371a6af39a4ccd9f9bc98886f70badc5f3c944b58f72ca3ffcd97bf43d70099cc17b2a8b7c2611192f3167a84fe1bbeeabc0a80995c5e4fb1ce09a4bd046f156e7535877ba350a2da41041e123aee11725b0a0a43afb88eb5f4073d0a874fb45dfe430a39546a9f2ff6cac9f2d51aace444ff2f3fc7cf35ca3acd5abd91b944b5a5843fb6ada6f269b1105679a14fdb3cbedbe6fb73ac39ad0aa73128da4fedb4e5c3670c18a16c81486a45d0a522c102eb6ca02648dac7a3aae56ab9cd8af722ee7934e969afce3e68797ff7af3f2ec223fcf17baae32c7c5367ebfa5d4a8dbbfffb01d6a493413442f7afdd289189ce266822be7545735754d6cb5975d96251060b8321461772f9083b80edd7f6580df6a66d12323a53d11ef730391a446b3f556f7de9b0edc0645ff80a8fa1f7bf6697f1ce48ed8aa1fd5ae6093cba0d22f78e996bfeeb25fa38696fb402f880686582a63b32902294b2c63f61b664c26db1c1e5b73a691699f878810152dec5a26bf2a07f33676b1ddf17002fc278933a3f877935d5f7dd2b5d327bb1e7a3af50db3dfc0804a70e3e3aef92ece9ff86b0ad8b4eba4b85940cc2cfb19266d9afd8c5306ac13a84892a5424b2cd636f17cfad9f95f462d75c396a4a22550261aaf83119b2545f84d1c15da24120803bca54a5336b719e44188d058594750388202dc05145ab7f96383c2f9dea0707305aa31ebf72db6e75ccff6da15c635cc78c30e9d651ce67e32eb693346bc2d5038e97a72321fed06799d498c2626b57eb17e3bacabb2b9dbf8ee81e13565a5b2c93a4cd760f1290e84ff6c2a6a527cf3994d97a70842f2253548b0a27a0105af6b020a0d04b685ba2986540eef941df6e4d4fcbeb0bf9fc2cca4e16821384f80ac5995d1b05b591469e320d93b2c53aebddb12e9b706a5d78e744b1db36ccdbb641fa6def709c8b347d9a26a0f0c022fa3919b8e5abba03dd0eb60122a51482c2c58b97aa760703cb8555314a8d4aca960bbdd8f090fb75db3c786869acc6149aac63b89bc0f707cb227249fef821ab6a69aaee1e62a81183fa26e24b3a70394cd2b74332d27d2e7a85fa37eb1be29ef13e5ed694f30cc4d353912e53757e65cb12b18a5d5ff6e717e6028b75a7fc958fe8209e5b70d048f0e031e6d46e463cdae244b9dd4b43599c1091378fd6e519b57982359288926f72cd37ea67a610fb18ff0f235c1cb9ef52a6baa2a5683ce48a50e28426fcf56abd59971bfb34656c84ccb2778a7e5e0aa74d3d54b234a38c8f6c1e0dfe3f8a5c40a3546a3fdcabeeea2fdb0886e67481ebcfc5123f9747ce19b065670e95db728bdf8c44abd483b7ca91b3dbf52d88a46e437c9f42710a92a268d6d1edfd4c37e743a2368c728734c4bedc00389dfb6ade9e002c91127e0d187ceefc7f8bcd0a8cf949648ea0725fade158a2fc5f5df42bd35b8a2e38d7e98c22b065ebd86b5b9b4a0fc7ef5b01f2eba4b0e79af79bfbcd875bfdb16f620a6bcae7b3f0572f7eae512e55a2f4c8baabd27b1e64d7bb5a2d7f9bfd52819a9ae78e1dca619eadabb101087d2ebfedd8ce01d164fff61ebc71578699dcd20dff6420897764f92421bd5cb405c99af366eab3aab0cbf8e1b24b5d84e94b1af7743206648e55fa969773ca8c23ed377f76036573fa23b681cf5e4f3ff01bd7b64136c3e0000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "2071", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Sat, 10 Jan 2026 17:17:37 GMT", + etag: 'W/"d6e36862dd911cce065e1743245638158293d3d29b41be7182d8d2721a8116d4"', + expires: "Sat, 10 Jan 2026 17:22:37 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "9bf191f42207d4117da8abef9fc1e2b6cdafa1e5", + "x-frame-options": "deny", + "x-github-request-id": "20FD:1E51F4:3D72CA:745DBD:69628091", + "x-served-by": "cache-lhr-egll1980076-LHR", + "x-timer": "S1768065457.093817,VS0,VE93", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { encodedQueryParams: true }) + .get("/v2/pet/findByTags") + .query({ tags: "tag1" }) + .reply(200, [], { + "access-control-allow-headers": + "Content-Type, api_key, Authorization", + "access-control-allow-methods": "GET, POST, DELETE, PUT", + "access-control-allow-origin": "*", + connection: "keep-alive", + "content-type": "application/json", + date: "Sat, 10 Jan 2026 17:45:37 GMT", + server: "Jetty(9.2.9.v20150224)", + "transfer-encoding": "chunked", + }); + + const inputFile = new Input( + "./test/mocks/inputs/multiInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/multiple-workflows/multiple-sourceDescription/arazzoMock-user-multiple-workflow.json", + "arazzo", + { logger: logger, parser }, + ); arazzo.setMainArazzo(); try { - await arazzo.runWorkflows(inputFile); - throw new Error('Expected promise to reject but it resolved'); + await arazzo.runWorkflows(inputFile); } catch (err) { - expect(err).to.be.instanceOf(Error); - expect(err.message).to.be.equal(`sinon threw an error when writing a sourceDescription file`); + console.error(err); + expect(err).to.not.be.instanceOf(Error); } - - stub.restore(); + }); }); + }); - it(`should throw an error when there is more than one sourceDescription and it is incorrectly referenced`, async function() { - nock('https://raw.githubusercontent.com:443', {"encodedQueryParams":true}) - .get('/readmeio/oas-examples/refs/heads/main/3.1/json/petstore-simple.json') - .reply(404); + xit(`should throw an error when the operationId does not exist in the OpenAPI document`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get("/readmeio/oas-examples/refs/heads/main/3.1/json/petstore.json") + .reply( + 200, + [ + "1f8b0800000000000013ed5ddd73dbb8117ff75fb1c3eb436fc696ec5cda074faf53274e3a9a4b134f1cb7d7c964ee607245e142020c004a5633fedf3b00488ae0b7be1c39c93df8241104f60bbf5d2c16c8e723008f27c84842bd73f07e1a9d8d4ebd63fd2b6553ee9dc3e72300002f40e90b9a28ca996ef76e46255009042489930841a298a3802b545271917f1f01fc97a7e0130653ca02e0a982583f26b7fae3f58284210a200adecf944acec763697f1a51fee1cfb59f7e042e8033784f853f9a0a44c6031c3154c7f043d6aae1ad3115fef8c711c04b2e4069c22dcdc7b0cc684b25829a219084c2475cc2ef32419f92e8e4232e7f07c541a154b645aa665cd0ff112d0898d248a190232330006f8e4266023a1b9d668204f0145511ea5f73867331150d50c4f2cdf41ac59cfaa6659d0bd3669cbfe173a688af0a05017818131ae97749421592f81fab973dd3e63e7b37a23e3289e5771989cdb01709f167084f0ada01bc5444258a168bc5889856232ec271d6971cbf9a3c7ff1fafac5c993d1e968a6e2281bf2281bd6b30621bd73786f9e7c6eee3ec924332a713e7fb2eaec83e90cef140a46a24beecb561b7dd96e71b914ddb15d7959b215091b68cec595a05ac9a932fc8b398aa59a511666632f796a35bf7aa5918f5e5e8af77b38c8655668be467fd908eb835ef83e4aa9edbf98d65c045a899d9da6b21070bdcf37090a3379642615b7f56602c9bad2f275391a2ea0c2b412a266259bd206599e280997ca252cb710630c1f4a23cb348e89581a4906011060b8800495019419d649adb0587ec473b14d0233c383e0aa647a866c416254d90c2b932150265c4f51876c00efe9e95f2a3fd56998b0398968009425a9f24a4def8bcff76596d14f0555cb62c6d8ff2a63e473fc378da646740b41159e27666e688a4960bf7c681cd1e5ee538a523de3c1b2cadf9f044e350f3f8c7d1e279c215372bc6a4f518eb5108faa03140c799ae7b5757d9304442110067847a5d2f33f7175355ccfa9e96b17aa3e1daceac925c83449228a81a3f063b7c3a7bd1d5ea102c6154c79ca3abbea37c37f6bcaaccfc53b1fedefdf88353aae5bc3d1588752cf96d78aa8b4ac692fc40dcc5503a934000fb74b90b6d37663fd571a296a023ed312e6244a519a28ea1621117c4e030c6041d50c7c1ec704246a835518805482b25076d8bbe64c935270d76af6edfa5cf9b64a0fe629355c7c4a512cab8f2a9c5e3b0caa1951c010038ddeb7083e6792062830802917592458ed51ab970ad4ac299162e529de25110fb0f9a1f4671893dac4d04a5d26863f2204a9f2a019541857e77fe54dab87daab9a2496c6c660c89cd088dc46a86740822c302f80277914942da924bc29492365082bdead34bb3f6afbd6369b5ae0ecc9003893a9895ea66904859155f5a3836864aa49ca4423a06fde1adfc551b3405b9504fd8a826e65412b66d8412b68d12cd6e65fee6b365366f60f69e4f7f570db6e74153734dc4596b1ef60dc509fbf78679dc12ebd8579a3d3571857a19bade521467023cd6b67c7faef13f3f72703b47a254e59381ae444de55e85bd78554f883f51c881e5d7b0beb1be0b6d6feb0ddc377f03e4838fb0ede7539ac05de8a840786dc0e8226027d8d85d9bc6f47f5cf09aa4970bf1348376989db254c2e3be0fc2daa543093f3a52c8cb0baacadc07088ea0ad5b3e524d806820d93cd18ac33353d103cb9043ecd732ec2d0bf1e08f7e32c650ac35ae80fe04db98889ca9afcf5a9d76eca5f237eae8f070f83743ba06b0f98f4d03997d56a6db01de5f6b82bd42409fded239a463d01ed2a1db751eed5e6e3346c691ca06c957bb531a89ea7101045b64cd1fd87aad94b2ee28340bb3c5b21b37485a5b2d6f5a303bfaf3d67dd06ca2e249f2c168b132de2935444c87c1e1815d6d4d40185850af9ed1fe8aba6645022b4a92b5ad342d12233db9690b0a2163b0f03d02f693bd5d3d08d201ae96b5e8d409383b03c57d3a2c3a8ca96f43ba16b1b177254fdb482bf002354b83e005e9af73200dc0ce4ecd09dfb10fde096237e23bccd90049d09d42989e4fa00d5a4a55697bd1750d61e999aac71a6bfc70fc10715d77c494c3f2affd2b4381ba749c449308949e8945b6c16c9d8cea4de5aa4a6c70d0316ddcb4b1ae141442a3c8b4d1efdbc78e075d95e164017097ddb10e7d76553ffdee2c51e75a0c57d85ea442a8124de34be6adf6c5b19e22d657a8a6f1325b810642438a66c8e4c715166be234764eb515ab0274ffed83594ed97e2b03de355e22826899ef859b0a5e356b340f99412a6a889343bf3499382a1edca211ec954ed0dd1c15402513d0889aefac2f501e098357320f2a7277b0c72f79836689a11a67c6da023ee9c0e5711f14d8d8fe9d1ec4c6d115e27bab737c20d801f8161ef233768c5d06f5d0f9f1d1c4ad9b0c5c65a71746de8e629b4adbf6b115ba79f5b5f6443055491859d6966aad89a9b2415fe8ce8bd88c6357b35821ce633cd28e3cfe67f8377573aa1c2eeaf585ab302dadead165da96e359f4f75506209196cc3e452dadca5d9c782bfff0c674058007ffb19ce4e47f046cd50e4854b0b1a45102243bbc35e94cff5b85aa3b96d376f3229ee23a13945e5cfbe6042b3d620a68cc6a67aeaacfe8cdce5cf4e1fe972e33bd43fb6ad20c3436fd2e4a8d27d7796b113e86c9e715f50977049159d63f1c4e0db085e6348ccef5c33cb4e9cc715f8838bab09a0105c74a19f95404f38f650e0a73d9b15640d022d9d870981bb42b9473073dc28c21c6719b6c0304d5be6d27381da5e9dc3312da7f074611d67d1d2980467680af36608110f43d4bb60a697ae8239df0c7623b75e7e6cb0adbcf24b8f2fba3512db4d706bf51d185541c37a7fdd6056f733b67ad5fbd217a6ae6b57662921a2526974d26d337c0ee91c99dd73856a19d91a4be1952dca82f089d9c87da4a639e0dc87e6d66a684db5bea28e161f8d5635dddf95ea2835e2212da353c76ab34b95af78282d8850961f335c4a85f1667a3354753b86fe404893633e374642832aa967d615dada01bdf6b7e2da71e4b393cd6222e5828b96b06f28b3792f2b5eb51ff72324ba02feae9a0bde3fe7eb456dbb5a9bdaa280a67cb6f7ebc95ba2f0e4158d69d3dab53e9e4fa2486f5f0898e973c2248af802833c58aa045afda273c4d7954eef4fa6f7af777f3d7971975081f2e462aa9cf8b29d5f73089532b879f71c163364a0f847d4c7274d479bf3daba9fe5b0aa473f51345e7f1bf141d3137dc53c0f5b7d3f988a8131e63a8ba71ca4c705f434aea586fa32ee9c9cdec699e983fd7e2a04325559d28044295dd458cfb5f1541de6a2a743b69f73450d4c4d77c9f79f68af5fd02058b8d7cda419a211e5b3e5eb4a17bb0d1606a44db4fb3461425bcad81e2ed3e39c3927cae01b71aa5f30e1dbb4603e887cef40c2f688bb3b4a5dddc84d72beedf75c74c1475e3afb30492a5b25b6dfb5c8007871a165bfd5f53b069075cdf2214df21b49f395a7cc6669be61fb345dd336dba67998596ba9fce2b3b63d28d8d326ca179eba5fcaa31ce57fede569abc9540c93c9ceb989e04da52aababf4adfd548a47eb67607a56e85db5c0aea46cb9f3debacf0a20ab80b7e6086e72c11d41ce6872492a80010d26db3a40e392be3248f3d99b9e211a77df6af7ff98a6c5fd34b60c48d70393445f29613f0718d1b9be93c7ad0d7668d43659434e87ca5bce2324b5b879b5ce3347517afd583d802ef0ac525e5007f7e74461e816ee1ee0a4683cfed501816bcba9104387a86ee4a16347e1bdd61195d3c3940aa95e6fd54544b6ed21bf5e74c3d78bc4f8e63dcc38db827ead86da9d69953e06a26c3780198f998db48df5bb7167ddf2df91f0b00d7feff8a025d021a0ab5a56ac5540a578efbdedfed8189ce23722924e28bd5759d6c25012bc61d1b2ba1ca839b5267f01bd2b9b3abe6ea4c2aa93be33d72b9b98818721ed081856126e1da3e94a9a069b28535b745b2f515a089224f5e5558db28e0b6f36db2eccd7663be45291fa36cc4e18ecb41967ce75b3bc93705057ff66e785ca7737b44686dd37176e83363d97c596cfce6d0dcbbebdf96b4f8b81ac9f0d1d698c52bae759fb3ba8cbeda8d4b1e7944e94978635086f4e01f527803ad6eceb5d09d36eee7dd9f3fd90d068c50da7a7adf5d5132024086c1eb3f14aeae67c941b7d578bcbf6a3a5cdefb5db65726f88b45f95caa8ea49be36913af3213f0b77ada9746744f57c6c13d0e847e528d59b467c510f5762ad8d86f209cff9570e6e7aefe737e38d034a225e4373e9f3a4f14462f958ef3978310fe874a9cf7a189c3777e513dfe729ab1f7c2c9d013eb75f6c7bf3cb10ddd5cc7875aab0419c24a1bf38d75674dc68e1de66e1ea56a7c58eee8ffe0fa561a61773630000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "3189", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Fri, 02 Jan 2026 12:03:42 GMT", + etag: 'W/"6f618be854ed64cd6a2cb850eefecf34866e637d9e73287a9d9d64a8ca3d54b3"', + expires: "Fri, 02 Jan 2026 12:08:42 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "1", + "x-content-type-options": "nosniff", + "x-fastly-request-id": "e357bbd9481ed9b72541f1d5e4d6c921c6dbf9be", + "x-frame-options": "deny", + "x-github-request-id": "9733:2549FA:1BB33B8:30A9114:6957ADA3", + "x-served-by": "cache-lhr-egll1980089-LHR", + "x-timer": "S1767355422.386688,VS0,VE1", + "x-xss-protection": "1; mode=block", + }, + ); - const inputFile = new Input('./test/mocks/validInputById.json', 'inputs'); + const missingOperationIdOpenAPIFile = structuredClone(openAPIMock); + delete missingOperationIdOpenAPIFile.paths["/pet/findByStatus"]; - const arazzo = new Arazzo('./test/mocks/arazzoMockIncorrectlyReferencedSourceDescription.json', 'arazzo', {logger: logger, parser}); - arazzo.setMainArazzo(); + const stub = sinon.stub(OpenAPI.prototype, "writeDocument").resolves(); + OpenAPI.prototype.fileName = + "Arazzo-Workflow-for-Petstore-openAPI.json"; + OpenAPI.prototype.filePath = path.resolve( + __dirname, + "../..", + "Arazzo-Workflow-for-Petstore-openAPI.json", + ); + await fsp.writeFile( + "./Arazzo-Workflow-for-Petstore-openAPI.json", + JSON.stringify(missingOperationIdOpenAPIFile), + ); - try { - await arazzo.runWorkflows(inputFile); - throw new Error('Expected promise to reject but it resolved'); - } catch (err) { - expect(err).to.be.instanceOf(Error); - expect(err.message).to.be.equal(`No known matching source description for $sourceDescriptions.Arazzo-Workflow-for-simple-petstore-openAP.getById`); - } - }); + const inputFile = new Input( + "./test/mocks/inputs/validInput.json", + "inputs", + ); - it(`should throw an error when loading a sourceDescription from a URL fails`, async function() { - nock('https://raw.githubusercontent.com:443', {"encodedQueryParams":true}) - .get('/readmeio/oas-examples/refs/heads/main/3.1/json/petstore.json') - .reply(404); + const arazzo = new Arazzo( + "./test/mocks/workingArazzoMock.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); - const inputFile = new Input('./test/mocks/validInput.json', 'inputs'); + try { + await arazzo.runWorkflows(inputFile); + throw new Error("Expected promise to reject but it resolved"); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + `The OperationId: findPetsByStatus does not exist`, + ); + } - const arazzo = new Arazzo('./test/mocks/workingArazzoMock.json', 'arazzo', {logger: logger, parser}); - arazzo.setMainArazzo(); + stub.restore(); + }); - try { - await arazzo.runWorkflows(inputFile); - throw new Error('Expected promise to reject but it resolved'); - } catch (err) { - expect(err).to.be.instanceOf(Error); - expect(err.message).to.be.equal(`Error fetching document from https://raw.githubusercontent.com/readmeio/oas-examples/refs/heads/main/3.1/json/petstore.json`); - } - }); + // not working for now + xit( + `should throw an error if redocly can not bundle the document`, + async function () { + // nock.recorder.rec(); + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 19:06:53 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:11:53 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": + "afc0e2abf3e0f1ee55fdb9786c251fd30aba07d6", + "x-frame-options": "deny", + "x-github-request-id": "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980099-LHR", + "x-timer": "S1767726413.324350,VS0,VE102", + "x-xss-protection": "1; mode=block", + }, + ); - it(`should throw an error when a workflow does not have steps`, async function() { - const inputFile = new Input('./test/mocks/validInput.json', 'inputs'); + const stub = sinon + .stub(bundleFromString) + .rejects( + new Error("sinon threw an error when bundling the document"), + ); - const arazzo = new Arazzo('./test/mocks/arazzoMockMissingSteps.json', 'arazzo', {logger: logger, parser}); - arazzo.setMainArazzo(); + // const inputFile = new Input('./test/mocks/inputs/validInput.json', 'inputs'); - try { - await arazzo.runWorkflows(inputFile); - throw new Error('Expected promise to reject but it resolved'); - } catch (err) { - expect(err).to.be.instanceOf(Error); - expect(err.message).to.be.equal(`Cannot read properties of undefined (reading '0')`); - } - }); + // const arazzo = new Arazzo('./test/mocks/workingArazzoMock.json', 'arazzo', {logger: logger, parser}); + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); - it(`should throw an error when an invalid input file is attached and does not conform to the workflow schema`, async function() { - const inputFile = new Input('./test/mocks/invalidInput.json', 'inputs'); + const arazzo = new Arazzo( + "./test/serverless-users/arazzo.json", + "arazzo", + { logger: logger, parser }, + ); - const arazzo = new Arazzo('./test/mocks/arazzoMockWithInvalidInputs.json', 'arazzo', {logger: logger, parser}); - arazzo.setMainArazzo(); + arazzo.setMainArazzo(); - try { - await arazzo.runWorkflows(inputFile); - throw new Error('Expected promise to reject but it resolved'); - } catch (err) { - expect(err).to.be.instanceOf(Error); - expect(err.message).to.be.equal('Input values do not match Input schema'); - } - }); + try { + await arazzo.runWorkflows(inputFile); + throw new Error("Expected promise to reject but it resolved"); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + `sinon threw an error when bundling the document`, + ); + } - it(`should throw an error when workflows are omitted`, async function() { - const arazzo = new Arazzo('./test/mocks/arazzoMockMissingWorkflows.json', 'arazzo', {logger: logger, parser}); - arazzo.setMainArazzo(); + stub.restore(); + }, + "does not work?", + ); + }); - try { - await arazzo.runWorkflows(); - throw new Error('Expected promise to reject but it resolved'); - } catch (err) { - expect(err).to.be.instanceOf(Error); - expect(err.message).to.be.equal('Missing Workflows'); - } - }); + it(`should throw an error when writing a downloaded sourceDescription file fails`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Tue, 06 Jan 2026 18:57:25 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Tue, 06 Jan 2026 19:02:25 GMT", + "source-age": "0", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "0", + "x-content-type-options": "nosniff", + "x-fastly-request-id": "90122fbfe305d58e86c2231dd5780b0c34096559", + "x-frame-options": "deny", + "x-github-request-id": "8DCE:156854:683A3:BD766:695D41E9", + "x-served-by": "cache-lhr-egll1980038-LHR", + "x-timer": "S1767725845.132358,VS0,VE97", + "x-xss-protection": "1; mode=block", + }, + ); - it(`should throw an error when sourceDescriptions are omitted`, async function() { - const arazzo = new Arazzo('./test/mocks/arazzoMockMissingSourceDescriptions.json', 'arazzo', {logger: logger, parser}); - arazzo.setMainArazzo(); + const stub = sinon + .stub(fsp, "writeFile") + .rejects( + new Error( + "sinon threw an error when writing a sourceDescription file", + ), + ); - try { - await arazzo.runWorkflows(); - throw new Error('Expected promise to reject but it resolved'); - } catch (err) { - expect(err).to.be.instanceOf(Error); - expect(err.message).to.be.equal('Missing Source Descriptions'); - } - }); + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/serverless-users/arazzo.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + throw new Error("Expected promise to reject but it resolved"); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + `sinon threw an error when writing a sourceDescription file`, + ); + } + + stub.restore(); + }); + + it(`handles more than oe source description correctly `, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply( + 200, + [ + "1f8b0800000000000013ed5a5b6fdb36147ecfaf3850f7b00289eca6dd1ef2b4346d8760415b2c29b6212b50463ab6d84aa4461ec5f18afcf781d4c51265c9d724ee9a3c38b6481e9ecb773e9247fcba07e0c914054bb97704de737fe83ff7f6cdd34026a91428487b47f0750f00c0d34184099b3d30dd1432c20f1a55ed298047d3148d4479f51903b2228b9654c9141571d48d11005ea6510996a0f3bc264d93e262ecd51a6ff7eb12465c697abb9188986d2a0113c6e3f587a74ceb8954e10612222936d0df84e19c18656e806a32b8201ca3f2f69bcd23a912464587e7876e73883a503c252e85e9635003c54c756daaef35bdbc9bc4f5a95760c5ca990928875783bdeda093f744641977fcfca2dfe58fc87f44feb6916f0c3ab10c1dee5c022c505bf7eacd9462d3bada9c306969dc69659f9df32d5d686bbfb50ed23a73a517ac75a7f53bd0e0e2d8bae801bcd749668b8c73c1d349692b0aea24b615e5ac1732474827c9ad2a672ed5ad28a487f056067c8bf496a63d07d70da0b5c9af93fe1673ca991c73b10bfbd4b556ba1ee36263d8efa85329346e6ee07110a0d617f20b8a6d697873a018e141cc134ef3152c51d61afae7c1eb9b942bd407c723eadac935b56986bd15abae517bb5b19ec620539ca6e7e6d0d3f091779c512415ff9715c09e47b129ff0d1b1c5b82b639b84ec23647226461cd0b7be5a755cbe3622467e731e2145b99ef913449856033a230c049be8b886be01a186896a4318246758d0aaab1f96f1fe02f9941c0048cb80841660489696657e6ebf9848dc7a880115c4644e9d160a0f3473e971f7f6c3d7a0a52811470c955e08f14a29021fa02691f9e14bde68c1a70150c9efa006fa402328ae73aefc3b4d02dd3081421b094c3179cc2279d62c0597cf005a79f8024106aca7bd4fd0d231e132aedff5dbadebb46a50b173df387feb07c4ea812fd6e748eea9a07d6cb6d356d9f41392290825850877715f42aced522621042c8925f66e2bc06fe621e60339b67104a5910211c56ca1aa0abb8a6e36432f199ede54b351e14b2f4e0ecf4e4f5dbf3d70787fed08f28893d075f25e8bd23b8b44d9d98bffc580dfd6887a68ca25aad6090354f5d5e2ab593f93a4b12a6cc5c5ebed9834c379699b91036d197229ec215422805c2d5d4c63996e33186c02d36945f176378ceea7d6a18b75eb3a8d321532c41030e6b5dad85d858570ea9f1ca8cfbeabd15fe93a1a697329cbabcea9853ec70adbe306f9765447165f7c0a4326cb419b0a1a0363fb3348d7960cd1d7cd6b2cde05521674e0b80f783c291d1eec96056021a14959f41cd75cec8dbbdae5ff34f35aa58ad5a8bcfe1f059dba8791b89a03820387b9a2ec72ce79a7ee72c724ffdbcd51a7cdbef31c78c7c1db0dee9de1dbd18feb4d057a7e29ac5dc24469a91ebacae59da2b79839baca10518fee014b9678d65925d43cc35811c59fc6b98708a60ccaf51e4aa42eb94e218b65486eb4abd53c7fead257c8b33f33f272a1dfce9fa7b3d2639ab79f21b6192d911f5a18944c3c96e3289fe1ea9c440797799c468f748248f44f248243b4a24b153e2e9e78f3339d639d6b93067c608414f3561b21e59d8c91ffe5c6177c7b6d605a65e606a74cd52c34ee6f1ac407777793c5c884c9dd9dad7288ba10aee2ea573b3d2b7bd7c760de9acd775392e6071ac21450591cc14b03896130ccbc3b973b0df86271a1a2e7644ab36dd5756ec323234450a2ee0c3c5094c221440a6460a980bdab281ae82ab06bb87a117e741c9d065fd745096ac416706e3ed456e53da965963df37c66eda5e9b9de537b771bb632adb28685f4b70dc2e1bb85f91f2e5f66a9affb715cdb5a2394632ebdccbe95b474433a8dde1288ba9d51b02c7357939de543517bccc779b7b96d63e4258bc3a5a2d97580cef1bc81dd0b957203fe49a3caf1c7a475bebd5897b65be76667cb15c3156488291ccc406cb42f52a23cdba19e4431a5635f3bb7f5390d9d9fa77f4f7c030368e14310281189ab75b5708b96e2d7f7f77ecb3d5fa471d5e79fda3f3d83462b1de8573d39dbe8cf94e1827c418093b49e7956dbe27cec9757970ceb988109abca30be2c9157c249e4df6efffefbcda2b3fcb4b05e662896edf2968de59488b8b287eed82c5f5e1ec86427ecda011d9d6cd88468abae6bd2b134d1797599abdf1868ca3e3573268c6cb95f366cebd1853f3b0dad793b669dfcc2cddbede931b37578565a62faee5943751ba2636f3deeeddfe076e0f2055f7310000", + ], + { + "accept-ranges": "bytes", + "access-control-allow-origin": "*", + "cache-control": "max-age=300", + connection: "keep-alive", + "content-encoding": "gzip", + "content-length": "1638", + "content-security-policy": + "default-src 'none'; style-src 'unsafe-inline'; sandbox", + "content-type": "text/plain; charset=utf-8", + "cross-origin-resource-policy": "cross-origin", + date: "Thu, 08 Jan 2026 15:03:45 GMT", + etag: 'W/"d38379461bc9571f3e57ed61b7c4f2b6d189a3b3cf79f0449c1fde6e9379637e"', + expires: "Thu, 08 Jan 2026 15:08:45 GMT", + "source-age": "169", + "strict-transport-security": "max-age=31536000", + vary: "Authorization,Accept-Encoding", + via: "1.1 varnish", + "x-cache": "HIT", + "x-cache-hits": "1", + "x-content-type-options": "nosniff", + "x-fastly-request-id": "a7213c75fb95ab820529bcc3c9206b3601e1d15e", + "x-frame-options": "deny", + "x-github-request-id": "66E9:203639:9AF45:10475A:695FC2E0", + "x-served-by": "cache-lhr-egll1980097-LHR", + "x-timer": "S1767884625.451409,VS0,VE1", + "x-xss-protection": "1; mode=block", + }, + ); + + nock("http://petstore.swagger.io:80", { encodedQueryParams: true }) + .post("/v2/user/login", "[object Object]") + .reply( + 200, + { + code: 200, + type: "unknown", + message: "logged in user session:1767885054568", + }, + { + "access-control-allow-headers": + "Content-Type, api_key, Authorization", + "access-control-allow-methods": "GET, POST, DELETE, PUT", + "access-control-allow-origin": "*", + connection: "keep-alive", + "content-type": "application/json", + date: "Thu, 08 Jan 2026 15:10:54 GMT", + server: "Jetty(9.2.9.v20150224)", + "transfer-encoding": "chunked", + "x-expires-after": "Thu Jan 08 16:10:54 UTC 2026", + "x-rate-limit": "5000", + }, + ); + const inputFile = new Input( + "./test/mocks/inputs/petsInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/correctSourceDescriptionReference-pets-arazzo.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + } catch (err) { + expect(err).to.not.be.instanceOf(Error); + } + }); + + it(`should throw an error when there is more than one sourceDescription and it is incorrectly referenced`, async function () { + const inputFile = new Input( + "./test/mocks/inputs/petsInput.json", + "inputs", + ); + + // const arazzo = new Arazzo('./test/mocks/arazzoMockIncorrectlyReferencedSourceDescription.json', 'arazzo', {logger: logger, parser}); + const arazzo = new Arazzo( + "./test/mocks/incorrectSourceDescriptionReference-pets-arazzo.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + throw new Error("Expected promise to reject but it resolved"); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + `No known matching source description for $sourceDescriptions.usersOpenAP.loginUser`, + ); + } + }); + + it(`should throw an error when loading a sourceDescription from a URL fails`, async function () { + nock("https://raw.githubusercontent.com:443", { + encodedQueryParams: true, + }) + .get( + "/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json", + ) + .reply(404); + + const inputFile = new Input( + "./test/mocks/inputs/userInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/serverless-users/arazzo.json", + "arazzo", + { logger: logger, parser }, + ); + + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + throw new Error("Expected promise to reject but it resolved"); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + `Error fetching document from https://raw.githubusercontent.com/JaredCE/serverless-arazzo-workflows/refs/heads/main/test/serverless-users/openapi.json`, + ); + } + }); + + xit(`should throw an error when a workflow does not have steps`, async function () { + const inputFile = new Input( + "./test/mocks/inputs/validInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/arazzoMockMissingSteps.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + throw new Error("Expected promise to reject but it resolved"); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + `Cannot read properties of undefined (reading '0')`, + ); + } + }); + + it(`should throw an error when an invalid input file is attached and does not conform to the workflow schema`, async function () { + const inputFile = new Input( + "./test/mocks/inputs/invalidInput.json", + "inputs", + ); + + const arazzo = new Arazzo( + "./test/mocks/arazzoMockWithInvalidInputs.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(inputFile); + throw new Error("Expected promise to reject but it resolved"); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal( + "Input values do not match Input schema", + ); + } + }); + + it(`should throw an error when workflows are omitted`, async function () { + const arazzo = new Arazzo( + "./test/mocks/arazzoMockMissingWorkflows.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(); + throw new Error("Expected promise to reject but it resolved"); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal("Missing Workflows"); + } + }); + + it(`should throw an error when sourceDescriptions are omitted`, async function () { + const arazzo = new Arazzo( + "./test/mocks/arazzoMockMissingSourceDescriptions.json", + "arazzo", + { logger: logger, parser }, + ); + arazzo.setMainArazzo(); + + try { + await arazzo.runWorkflows(); + throw new Error("Expected promise to reject but it resolved"); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal("Missing Source Descriptions"); + } }); + }); }); diff --git a/test/unit/Expression.spec.js b/test/unit/Expression.spec.js new file mode 100644 index 0000000..051e419 --- /dev/null +++ b/test/unit/Expression.spec.js @@ -0,0 +1,559 @@ +"use strict"; + +const expect = require("chai").expect; + +const Expression = require("../../src/Expression.js"); + +describe(`Expression`, function () { + describe(`constructor`, function () { + it(`returns an instance of Expression`, function () { + const expected = new Expression(); + + expect(expected).to.be.instanceOf(Expression); + }); + }); + + describe(`checkSimpleExpression`, function () { + describe(`boolean`, function () { + it(`returns true when an expression matches a boolean true`, function () { + const expression = new Expression(); + + expression.addToContext("response", { body: true }); + + const expected = expression.checkSimpleExpression( + "$response.body == true", + ); + + expect(expected).to.be.true; + }); + + it(`returns true when an expression matches a boolean false`, function () { + const expression = new Expression(); + + expression.addToContext("response", { body: false }); + + const expected = expression.checkSimpleExpression( + "$response.body == false", + ); + + expect(expected).to.be.true; + }); + }); + + describe(`null`, function () { + it(`returns true when an expression matches a null`, function () { + const expression = new Expression(); + + // expression.addToContext("response", { body: null }); + expression.addToContext("response.body", null); + + const expected = expression.checkSimpleExpression( + "$response.body == null", + ); + + expect(expected).to.be.true; + }); + }); + + describe(`number`, function () { + describe(`equality`, function () { + it(`returns true when an expression matches`, function () { + const expression = new Expression(); + + expression.addToContext("statusCode", 200); + + const expected = + expression.checkSimpleExpression("$statusCode == 200"); + + expect(expected).to.be.true; + }); + + it(`returns true when an expression matches`, function () { + const expression = new Expression(); + + expression.addToContext("statusCode", 201); + + const expected = + expression.checkSimpleExpression("$statusCode != 200"); + + expect(expected).to.be.true; + }); + + it(`returns false when an expression does not match`, function () { + const expression = new Expression(); + + expression.addToContext("statusCode", 201); + + const expected = + expression.checkSimpleExpression("$statusCode == 200"); + + expect(expected).to.be.false; + }); + }); + + describe(`Less than`, function () { + it(`returns true when an expression matches`, function () { + const expression = new Expression(); + + expression.addToContext("statusCode", 200); + + const expected = + expression.checkSimpleExpression("$statusCode < 201"); + + expect(expected).to.be.true; + }); + }); + + describe(`Less than equal`, function () { + it(`returns true when an expression matches`, function () { + const expression = new Expression(); + + expression.addToContext("statusCode", 201); + + const expected = + expression.checkSimpleExpression("$statusCode <= 201"); + + expect(expected).to.be.true; + }); + }); + + describe(`Greater than`, function () { + it(`returns true when an expression matches`, function () { + const expression = new Expression(); + + expression.addToContext("statusCode", 404); + + const expected = + expression.checkSimpleExpression("$statusCode > 400"); + + expect(expected).to.be.true; + }); + }); + + describe(`Greater than equal`, function () { + it(`returns true when an expression matches`, function () { + const expression = new Expression(); + + expression.addToContext("statusCode", 404); + + const expected = + expression.checkSimpleExpression("$statusCode >= 404"); + + expect(expected).to.be.true; + }); + }); + }); + + describe(`string`, function () { + describe(`equality`, function () { + it(`returns true when an expression matches`, function () { + const expression = new Expression(); + const headers = new Headers(); + headers.append("x-rate-limit", "500"); + const contextHeaders = {}; + for (const [header, value] of headers.entries()) { + Object.assign(contextHeaders, { [header]: value }); + } + + expression.addToContext("response.header", contextHeaders); + + const expected = expression.checkSimpleExpression( + "$response.header.x-rate-limit == '500'", + ); + + expect(expected).to.be.true; + }); + + it(`returns true when an expression matches`, function () { + const expression = new Expression(); + const headers = new Headers(); + headers.append("x-rate-limit", "501"); + const contextHeaders = {}; + for (const [header, value] of headers.entries()) { + Object.assign(contextHeaders, { [header]: value }); + } + + expression.addToContext("response.header", contextHeaders); + + const expected = expression.checkSimpleExpression( + '$response.header.x-rate-limit != "500"', + ); + + expect(expected).to.be.true; + }); + + it(`returns true when an expression matches case insensitive`, function () { + const expression = new Expression(); + const headers = new Headers(); + headers.append("x-rate-limit", "HELLO"); + const contextHeaders = {}; + for (const [header, value] of headers.entries()) { + Object.assign(contextHeaders, { [header]: value }); + } + + expression.addToContext("response.header", contextHeaders); + + const expected = expression.checkSimpleExpression( + "$response.header.x-rate-limit == 'hello'", + ); + + expect(expected).to.be.true; + }); + + it(`returns true when an expression matches to a response body`, function () { + const expression = new Expression(); + + expression.addToContext("response", { body: "john" }); + + const expected = expression.checkSimpleExpression( + '$response.body == "john"', + ); + + expect(expected).to.be.true; + }); + }); + + describe(`jsonPointer`, function () { + it(`returns true when an expression matches to a response body using json pointer`, function () { + const expression = new Expression(); + + expression.addToContext("response", { body: { name: "John" } }); + + const expected = expression.checkSimpleExpression( + '$response.body#/name == "John"', + ); + + expect(expected).to.be.true; + }); + }); + + describe(`index based`, function () { + it(`returns true when an expression matches`, function () { + const expression = new Expression(); + + expression.addToContext("response.body", [ + { name: "Jack" }, + { name: "John" }, + ]); + + const expected = expression.checkSimpleExpression( + "$response.body[1].name == 'John'", + ); + + expect(expected).to.be.true; + }); + }); + }); + + describe(`multiple test`, function () { + describe(`AND`, function () { + it(`returns true when an expression matches`, function () { + const expression = new Expression(); + const headers = new Headers(); + headers.append("x-rate-limit", "500"); + const contextHeaders = {}; + for (const [header, value] of headers.entries()) { + Object.assign(contextHeaders, { [header]: value }); + } + + expression.addToContext("response.header", contextHeaders); + expression.addToContext("statusCode", 200); + + const expected = expression.checkSimpleExpression( + '$response.header.x-rate-limit == "500" && $statusCode == 200', + ); + + expect(expected).to.be.true; + }); + + describe(`logical grouping`, function () { + it(`returns true when an expression matches`, function () { + const expression = new Expression(); + const headers = new Headers(); + headers.append("x-rate-limit", "500"); + headers.append("x-rate-timeout", "30"); + const contextHeaders = {}; + for (const [header, value] of headers.entries()) { + Object.assign(contextHeaders, { [header]: value }); + } + + expression.addToContext("response.header", contextHeaders); + expression.addToContext("statusCode", 200); + + const expected = expression.checkSimpleExpression( + "($response.header.x-rate-limit == '500' && $response.header.x-rate-timeout == '30') && $statusCode == 200", + ); + + expect(expected).to.be.true; + }); + }); + }); + + describe(`OR`, function () { + it(`returns true when an expression matches`, function () { + const expression = new Expression(); + const headers = new Headers(); + headers.append("x-rate-limit", "600"); + const contextHeaders = {}; + for (const [header, value] of headers.entries()) { + Object.assign(contextHeaders, { [header]: value }); + } + + expression.addToContext("response.header", contextHeaders); + expression.addToContext("statusCode", 200); + + const expected = expression.checkSimpleExpression( + '$response.header.x-rate-limit == "500" || $statusCode == 200', + ); + + expect(expected).to.be.true; + }); + + describe(`logical grouping`, function () { + it(`returns true when an expression matches`, function () { + const expression = new Expression(); + const headers = new Headers(); + headers.append("x-rate-limit", "500"); + const contextHeaders = {}; + for (const [header, value] of headers.entries()) { + Object.assign(contextHeaders, { [header]: value }); + } + + expression.addToContext("response.header", contextHeaders); + expression.addToContext("statusCode", 200); + + const expected = expression.checkSimpleExpression( + "($response.header.x-rate-limit == '500' || $response.header.x-rate-limit == 400) && $statusCode == 200", + ); + + expect(expected).to.be.true; + }); + }); + }); + }); + + it(`returns true when an expression matches to a response header`, function () { + const expression = new Expression(); + const headers = new Headers(); + headers.append("x-rate-limit", "500"); + const contextHeaders = {}; + for (const [header, value] of headers.entries()) { + Object.assign(contextHeaders, { [header]: value }); + } + + expression.addToContext("response.header", contextHeaders); + + const expected = expression.checkSimpleExpression( + '$response.header.x-rate-limit == "500"', + ); + + expect(expected).to.be.true; + }); + + it(`returns false when an expression does not match to a response header`, function () { + const expression = new Expression(); + const headers = new Headers(); + headers.append("x-rate-limit", "500"); + const contextHeaders = {}; + for (const [header, value] of headers.entries()) { + Object.assign(contextHeaders, { [header]: value }); + } + + expression.addToContext("response", { header: contextHeaders }); + + const expected = expression.checkSimpleExpression( + '$response.header.x-rate-limit == "600"', + ); + + expect(expected).to.be.false; + }); + }); + + describe(`resolveExpression`, function () { + it(`can resolve a simple expression`, function () { + const expression = new Expression(); + + expression.addToContext("inputs", { + user: { name: "john" }, + petId: 1224, + }); + + const expected = expression.resolveExpression("$inputs.user"); + + expect(expected).to.be.eql({ name: "john" }); + }); + + describe(`dotted expressions`, function () { + it(`should resolve a dotted expression for a response body`, function () { + const expression = new Expression(); + + expression.addToContext("response.body", { name: "john" }); + + const expected = expression.resolveExpression("$response.body"); + + expect(expected).to.be.eql({ name: "john" }); + }); + + it(`should resolve a dotted expression for a steps output`, function () { + const expression = new Expression(); + + // expression.addToContext('steps.createAUser.outputs.user', { name: 'john' }); + expression.addToContext("steps", { + createAUser: { outputs: { user: { name: "john" } } }, + }); + + const expected = expression.resolveExpression( + "$steps.createAUser.outputs.user", + ); + + expect(expected).to.be.eql({ name: "john" }); + }); + + it(`should resolve a dotted expression with a json pointer for a response body`, function () { + const expression = new Expression(); + + expression.addToContext("response.body", { name: "john" }); + + const expected = expression.resolveExpression("$response.body#/name"); + + expect(expected).to.be.eql("john"); + }); + + it(`should resolve a dotted expression for a specific response header`, function () { + const expression = new Expression(); + const headers = new Headers(); + headers.append("x-rate-limit", "500"); + const contextHeaders = {}; + for (const [header, value] of headers.entries()) { + Object.assign(contextHeaders, { [header]: value }); + } + + expression.addToContext("response.header", contextHeaders); + + const expected = expression.resolveExpression( + "$response.header.x-rate-limit", + ); + + expect(expected).to.be.eql("500"); + }); + + it(`should resolve a dotted expression for a specific request QueryParams`, function () { + const expression = new Expression(); + const queryParams = new URLSearchParams(); + queryParams.append("username", "bob"); + const contextQueryParams = {}; + for (const [queryParam, value] of queryParams.entries()) { + Object.assign(contextQueryParams, { [queryParam]: value }); + } + + expression.addToContext("request.query", contextQueryParams); + + const expected = expression.resolveExpression( + "$request.query.username", + ); + + expect(expected).to.be.eql({ username: "bob" }); + }); + }); + + it(`can resolve a templated expression`, function () { + const expression = new Expression(); + + expression.addToContext("inputs", { + user: { name: "john" }, + petId: 1224, + }); + + const expected = expression.resolveExpression("{$inputs.user}"); + + expect(expected).to.be.eql({ name: "john" }); + }); + + it(`can resolve a json pointer expression`, function () { + const expression = new Expression(); + + expression.addToContext("inputs", { + user: { name: "john" }, + petId: 1224, + }); + + const expected = expression.resolveExpression("$inputs.user#/name"); + + expect(expected).to.be.eql("john"); + }); + + it(`can resolve a templated expression to a json Pointer`, function () { + const expression = new Expression(); + + expression.addToContext("inputs", { + user: { name: "john" }, + petId: 1224, + }); + + const expected = expression.resolveExpression("{$inputs.user#/name}"); + + expect(expected).to.be.eql("john"); + }); + + it(`can resolve all expressions in an object`, function () { + const expression = new Expression(); + + expression.addToContext("inputs", { + user: { name: "john" }, + petId: 1224, + }); + + const expected = expression.resolveExpression({ + user: { name: "$inputs.user#/name" }, + petId: "$inputs.petId", + }); + + expect(expected).to.be.eql({ user: { name: "john" }, petId: 1224 }); + }); + + it(`can resolve all expressions in an array`, function () { + const expression = new Expression(); + + expression.addToContext("inputs", { + user: { name: "john" }, + petId: 1224, + }); + + const expected = expression.resolveExpression(["$inputs.user#/name"]); + + expect(expected).to.be.eql(["john"]); + }); + }); + + describe(`addToContext`, function () { + it(`adds a type to the context if it does not already exist`, function () { + const expression = new Expression(); + + expression.addToContext("sourceDescriptions", [{ name: "abc" }]); + + expect(expression.context).to.have.property("sourceDescriptions"); + expect(expression.context.sourceDescriptions).to.be.an("array"); + expect(expression.context.sourceDescriptions).to.have.lengthOf(1); + }); + + it(`adds a type to the context if it does already exist`, function () { + const expression = new Expression(); + + expression.addToContext("sourceDescriptions", [{ name: "abc" }]); + + expect(expression.context).to.have.property("sourceDescriptions"); + expect(expression.context.sourceDescriptions).to.be.an("array"); + expect(expression.context.sourceDescriptions).to.have.lengthOf(1); + + expression.addToContext("sourceDescriptions", [{ name: "123" }]); + }); + + it(`adds a dotted type`, function () { + const expression = new Expression(); + + expression.addToContext("body.response", { name: "john" }); + + expect(expression.context).to.have.property("body.response"); + }); + }); +}); diff --git a/test/unit/Rules.spec.js b/test/unit/Rules.spec.js new file mode 100644 index 0000000..88a0144 --- /dev/null +++ b/test/unit/Rules.spec.js @@ -0,0 +1,347 @@ +"use strict"; + +const expect = require("chai").expect; + +const Expression = require("../../src/Expression"); + +const Rules = require("../../src/Rules"); + +describe(`Rules`, function () { + describe(`constructor`, function () { + it(`returns an instance of Rules`, function () { + const expression = new Expression(); + + const expected = new Rules(expression); + + expect(expected).to.be.an.instanceOf(Rules); + expect(expected.failureRules).to.be.an("array"); + expect(expected.failureRules).to.have.lengthOf(0); + expect(expected.successRules).to.be.an("array"); + expect(expected.successRules).to.have.lengthOf(0); + }); + }); + + describe(`setWorkflowFailures`, function () { + it(`should take a list of Workflow Failure Actions`, function () { + const rules = new Rules(); + + const onFailures = [ + { + name: "404Failure", + type: "end", + criteria: [{ condition: "$statusCode == 404" }], + }, + ]; + + rules.setWorkflowFailures(onFailures); + + expect(rules).to.have.property("workflowFailures"); + expect(rules.workflowFailures).to.be.an("array"); + expect(rules.workflowFailures).to.have.lengthOf(1); + expect(rules.failureRules).to.be.an("array"); + expect(rules.failureRules).to.have.lengthOf(1); + }); + }); + + describe(`setStepFailures`, function () { + it(`should take a list of Step Failure Actions and combine with Workflow Failure Actions`, function () { + const rules = new Rules(); + + const workflowOnFailures = [ + { + name: "404Failure", + type: "end", + criteria: [{ condition: "$statusCode == 404" }], + }, + ]; + + rules.setWorkflowFailures(workflowOnFailures); + + const stepOnFailures = [ + { + name: "404Failure", + type: "end", + criteria: [{ condition: "$statusCode == 404" }], + }, + ]; + + rules.setStepFailures(stepOnFailures); + + expect(rules).to.have.property("workflowFailures"); + expect(rules).to.have.property("stepFailures"); + expect(rules.workflowFailures).to.be.an("array"); + expect(rules.workflowFailures).to.have.lengthOf(1); + expect(rules.stepFailures).to.be.an("array"); + expect(rules.stepFailures).to.have.lengthOf(1); + expect(rules.failureRules).to.be.an("array"); + expect(rules.failureRules).to.have.lengthOf(2); + }); + }); + + describe(`setWorkflowSuccess`, function () { + it(`should take a list of Workflow Success Actions`, function () { + const rules = new Rules(); + + const onSuccess = [ + { + name: "200Success", + type: "end", + criteria: [{ condition: "$statusCode == 200" }], + }, + ]; + + rules.setWorkflowSuccess(onSuccess); + + expect(rules).to.have.property("workflowSuccesses"); + expect(rules.workflowSuccesses).to.be.an("array"); + expect(rules.workflowSuccesses).to.have.lengthOf(1); + expect(rules.successRules).to.be.an("array"); + expect(rules.successRules).to.have.lengthOf(1); + }); + }); + + describe(`setStepSuccess`, function () { + it(`should take a list of Step Success Actions and combine with Workflow Success Actions`, function () { + const rules = new Rules(); + + const workflowOnSuccesses = [ + { + name: "200Success", + type: "end", + criteria: [{ condition: "$statusCode == 200" }], + }, + ]; + + rules.setWorkflowSuccess(workflowOnSuccesses); + + const stepOnSuccesses = [ + { + name: "200Success", + type: "end", + criteria: [{ condition: "$statusCode == 200" }], + }, + ]; + + rules.setStepSuccesses(stepOnSuccesses); + + expect(rules).to.have.property("workflowSuccesses"); + expect(rules).to.have.property("stepSuccesses"); + expect(rules.workflowSuccesses).to.be.an("array"); + expect(rules.workflowSuccesses).to.have.lengthOf(1); + expect(rules.stepSuccesses).to.be.an("array"); + expect(rules.stepSuccesses).to.have.lengthOf(1); + expect(rules.successRules).to.be.an("array"); + expect(rules.successRules).to.have.lengthOf(2); + }); + }); + + describe(`runRules`, function () { + describe(`run rules for a success`, function () { + describe(`no matches`, function () { + it(`returns an empty object when there are no rules to run`, function () { + const expression = new Expression(); + const rules = new Rules(expression); + + expression.addToContext("statusCode", 201); + + const expected = rules.runRules(true); + + expect(expected).to.be.an("object"); + expect(Object.keys(expected)).to.have.lengthOf(0); + }); + + it(`returns an empty object when there are no matches to successRules`, function () { + const expression = new Expression(); + const rules = new Rules(expression); + + const stepOnSuccesses = [ + { + name: "200Success", + type: "end", + criteria: [{ condition: "$statusCode == 200" }], + }, + ]; + + rules.setStepSuccesses(stepOnSuccesses); + + expression.addToContext("statusCode", 201); + + const expected = rules.runRules(true); + + expect(expected).to.be.an("object"); + expect(Object.keys(expected)).to.have.lengthOf(0); + }); + + it(`returns an empty object when there are no matches to successRules but partial criteria checks`, function () { + const expression = new Expression(); + const rules = new Rules(expression); + + const stepOnSuccesses = [ + { + name: "200Success", + type: "end", + criteria: [{ condition: "$statusCode == 201" }], + }, + { + name: "Success", + type: "end", + criteria: [ + { condition: "$statusCode == 200" }, + { condition: '$response.header.x-amz-tkn == "abc123"' }, + ], + }, + ]; + + rules.setStepSuccesses(stepOnSuccesses); + + expression.addToContext("statusCode", 200); + + expression.addToContext("response.header", { "x-amz-tkn": "abc" }); + + const expected = rules.runRules(true); + + expect(expected).to.be.an("object"); + expect(Object.keys(expected)).to.have.lengthOf(0); + }); + + it(`returns an empty object when none of the successRules have criteria`, function () { + const expression = new Expression(); + const rules = new Rules(expression); + + const stepOnSuccesses = [ + { + name: "200Success", + type: "end", + }, + ]; + + rules.setStepSuccesses(stepOnSuccesses); + + expression.addToContext("statusCode", 201); + + const expected = rules.runRules(true); + + expect(expected).to.be.an("object"); + expect(Object.keys(expected)).to.have.lengthOf(0); + }); + }); + + describe(`matches a rule of type end`, function () { + it(`returns an object telling the workflow to end when there are matches to successRule with an end type`, function () { + const expression = new Expression(); + const rules = new Rules(expression); + + const stepOnSuccesses = [ + { + name: "200Success", + type: "end", + criteria: [{ condition: "$statusCode == 200" }], + }, + ]; + + rules.setStepSuccesses(stepOnSuccesses); + + expression.addToContext("statusCode", 200); + + const expected = rules.runRules(true); + + expect(expected).to.be.an("object"); + expect(expected).to.be.eql({ endWorkflow: true }); + }); + }); + + describe(`matches a rule of type goto`, function () { + it(`returns an object telling the workflow to end when there are matches to successRule with a goto type`, function () { + const expression = new Expression(); + const rules = new Rules(expression); + + const stepOnSuccesses = [ + { + name: "200Success", + type: "goto", + stepId: "stepTwo", + criteria: [{ condition: "$statusCode == 201" }], + }, + ]; + + rules.setStepSuccesses(stepOnSuccesses); + + expression.addToContext("statusCode", 201); + + const expected = rules.runRules(true); + + expect(expected).to.be.an("object"); + expect(expected).to.be.eql({ goto: true, stepId: "stepTwo" }); + }); + }); + }); + }); + + describe(`buildFailureRules`, function () { + it(`reverses the rules, so step rules are first and workflow rules are last`, function () { + const rules = new Rules(); + + const workflowOnFailures = [ + { + name: "404Failure", + type: "end", + criteria: [{ condition: "$statusCode == 404" }], + }, + ]; + + rules.setWorkflowFailures(workflowOnFailures); + + const stepOnFailures = [ + { + name: "404Failure", + type: "goto", + criteria: [{ condition: "$statusCode == 404" }], + }, + ]; + + rules.setStepFailures(stepOnFailures); + + rules.buildFailureRules(); + + expect(rules.rules.at(0)).to.be.eql({ + name: "404Failure", + type: "goto", + criteria: [{ condition: "$statusCode == 404" }], + }); + }); + }); + + describe(`buildSuccessRules`, function () { + it(`reverses the rules, so step rules are first and workflow rules are last`, function () { + const rules = new Rules(); + + const workflowOnSuccesses = [ + { + name: "200Success", + type: "end", + criteria: [{ condition: "$statusCode == 200" }], + }, + ]; + + rules.setWorkflowSuccess(workflowOnSuccesses); + + const stepOnSuccesses = [ + { + name: "200Success", + type: "end", + criteria: [{ condition: "$statusCode == 200" }], + }, + ]; + + rules.setStepSuccesses(stepOnSuccesses); + + rules.buildFailureRules(); + + expect(rules.successRules.at(0)).to.be.eql({ + name: "200Success", + type: "end", + criteria: [{ condition: "$statusCode == 200" }], + }); + }); + }); +});