From d63b50ff1642e6bcd45344e205ba033aaad38a19 Mon Sep 17 00:00:00 2001 From: Kaspar Peterson Date: Mon, 27 Nov 2023 15:17:10 +0200 Subject: [PATCH 1/8] Uses 'prettier . -w' to format the project as per husky pre-commit hook. --- .github/workflows/js-sdk-publish.yaml | 4 +- .github/workflows/python-client-publish.yaml | 2 +- .github/workflows/python-sdk-publish.yaml | 2 +- .github/workflows/update-docs.yaml | 4 +- CODE_OF_CONDUCT.md | 21 +- .../agent_protocol_client/docs/AgentApi.md | 4 +- packages/sdk/js/README.md | 1 + packages/sdk/js/src/agent.ts | 59 +- packages/sdk/js/src/api.ts | 57 +- packages/sdk/js/src/index.ts | 2 +- packages/sdk/js/src/models.ts | 14 +- packages/sdk/python/README.md | 3 +- rfcs/2023-08-28-Pagination-RFC.md | 13 +- rfcs/2023-08-28-agent-created-RFC-.md | 13 +- rfcs/2023-08-28-list-entities-RFC.md | 14 +- rfcs/2023-09-15-endpoint-schema.md | 15 +- testing_suite/agent_protocol_v1.json | 2995 ++++++++-------- testing_suite/contract_tests.json | 3067 ++++++++--------- 18 files changed, 3116 insertions(+), 3174 deletions(-) diff --git a/.github/workflows/js-sdk-publish.yaml b/.github/workflows/js-sdk-publish.yaml index a0f991f1..2a2c4ef6 100644 --- a/.github/workflows/js-sdk-publish.yaml +++ b/.github/workflows/js-sdk-publish.yaml @@ -20,8 +20,8 @@ jobs: - name: Setup Node uses: actions/setup-node@v3 with: - node-version: "16.x" - registry-url: "https://registry.npmjs.org" + node-version: '16.x' + registry-url: 'https://registry.npmjs.org' cache: npm cache-dependency-path: package-lock.json diff --git a/.github/workflows/python-client-publish.yaml b/.github/workflows/python-client-publish.yaml index 0b2e8221..6c6a67ef 100644 --- a/.github/workflows/python-client-publish.yaml +++ b/.github/workflows/python-client-publish.yaml @@ -21,7 +21,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: '3.10' - name: Install and configure Poetry uses: snok/install-poetry@v1 diff --git a/.github/workflows/python-sdk-publish.yaml b/.github/workflows/python-sdk-publish.yaml index 921105c6..60ea0d5f 100644 --- a/.github/workflows/python-sdk-publish.yaml +++ b/.github/workflows/python-sdk-publish.yaml @@ -21,7 +21,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: '3.10' - name: Install and configure Poetry uses: snok/install-poetry@v1 diff --git a/.github/workflows/update-docs.yaml b/.github/workflows/update-docs.yaml index f8b0a950..a9202e0a 100644 --- a/.github/workflows/update-docs.yaml +++ b/.github/workflows/update-docs.yaml @@ -31,7 +31,7 @@ jobs: git commit -am "Update endpoint docs" || echo "No changes to commit" git push env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Install swagger-cli run: | @@ -43,7 +43,7 @@ jobs: cd .. npx openapi-format schemas/openapi.yml --output schemas/openapi.yml swagger-cli bundle -r schemas/openapi.yml -o schemas/openapi.json -t json - + git config --global user.email "no-reply@github.com" git config --global user.name "GitHub Actions" git add schemas/openapi.yml diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 6fe00ace..14dd214a 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,3 @@ - # Contributor Covenant Code of Conduct ## Our Pledge @@ -18,23 +17,23 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall +- Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: -* The use of sexualized language or imagery, and sexual attention or advances of +- The use of sexualized language or imagery, and sexual attention or advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities diff --git a/packages/client/python/agent_protocol_client/docs/AgentApi.md b/packages/client/python/agent_protocol_client/docs/AgentApi.md index da18a12c..ba3a9c70 100644 --- a/packages/client/python/agent_protocol_client/docs/AgentApi.md +++ b/packages/client/python/agent_protocol_client/docs/AgentApi.md @@ -2,8 +2,8 @@ All URIs are relative to _http://localhost_ -| Method | HTTP request | Description | -| ---------------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------- | +| Method | HTTP request | Description | +| ---------------------------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------- | | [**create_agent_task**](AgentApi.md#create_agent_task) | **POST** /ap/v1/agent/tasks | Creates a task for the agent. | | [**download_agent_task_artifact**](AgentApi.md#download_agent_task_artifact) | **GET** /ap/v1/agent/tasks/{task_id}/artifacts/{artifact_id} | Download a specified artifact. | | [**execute_agent_task_step**](AgentApi.md#execute_agent_task_step) | **POST** /ap/v1/agent/tasks/{task_id}/steps | Execute a step in the specified agent task. | diff --git a/packages/sdk/js/README.md b/packages/sdk/js/README.md index cf8e8feb..b5368fb0 100644 --- a/packages/sdk/js/README.md +++ b/packages/sdk/js/README.md @@ -43,6 +43,7 @@ Agent.handleTask(taskHandler).start() You can find more info and examples in the [docs](https://agentprotocol.ai/sdks/js). ## Contributing + ```bash git clone https://github.com/AI-Engineers-Foundation/agent-protocol cd agent-protocol/sdk/js diff --git a/packages/sdk/js/src/agent.ts b/packages/sdk/js/src/agent.ts index 42f06f2c..91828822 100644 --- a/packages/sdk/js/src/agent.ts +++ b/packages/sdk/js/src/agent.ts @@ -1,4 +1,3 @@ - import { v4 as uuid } from 'uuid' import * as fs from 'fs' import * as path from 'path' @@ -21,8 +20,8 @@ import { ApiConfig, RouteRegisterFn, RouteContext, -} from "./api"; -import { Router } from 'express'; +} from './api' +import { Router } from 'express' /** * A function that handles a step in a task. @@ -84,7 +83,7 @@ const registerCreateAgentTask: RouteRegisterFn = (router: Router) => { res.status(500).json({ error: err.message }) } })() - }); + }) } /** @@ -251,8 +250,8 @@ const registerGetAgentTaskStep: RouteRegisterFn = (router: Router) => { export const getArtifacts = async ( taskId: string ): Promise => { - const task = await getAgentTask(taskId); - return task.artifacts; + const task = await getAgentTask(taskId) + return task.artifacts } const registerGetArtifacts: RouteRegisterFn = (router: Router) => { router.get('/agent/tasks/:task_id/artifacts', (req, res) => { @@ -262,7 +261,7 @@ const registerGetArtifacts: RouteRegisterFn = (router: Router) => { const artifacts = await getArtifacts(taskId) const current_page = Number(req.query['current_page']) || 1 const page_size = Number(req.query['page_size']) || 10 - + if (!artifacts) { res.status(200).send({ artifacts: [], @@ -310,9 +309,9 @@ export const getArtifactPath = ( workspace: string, artifact: Artifact ): string => { - const rootDir = path.isAbsolute(workspace) ? - workspace : - path.join(process.cwd(), workspace); + const rootDir = path.isAbsolute(workspace) + ? workspace + : path.join(process.cwd(), workspace) return path.join( rootDir, @@ -345,18 +344,17 @@ export const createArtifact = async ( task.artifacts = task.artifacts || [] task.artifacts.push(artifact) - const artifactFolderPath = getArtifactPath( - task.task_id, - workspace, - artifact - ) + const artifactFolderPath = getArtifactPath(task.task_id, workspace, artifact) // Save file to server's file system fs.mkdirSync(path.join(artifactFolderPath, '..'), { recursive: true }) fs.writeFileSync(artifactFolderPath, file.buffer) return artifact } -const registerCreateArtifact: RouteRegisterFn = (router: Router, context: RouteContext) => { +const registerCreateArtifact: RouteRegisterFn = ( + router: Router, + context: RouteContext +) => { router.post('/agent/tasks/:task_id/artifacts', (req, res) => { void (async () => { try { @@ -406,14 +404,21 @@ export const getTaskArtifact = async ( } return artifact } -const registerGetTaskArtifact: RouteRegisterFn = (router: Router, context: RouteContext) => { +const registerGetTaskArtifact: RouteRegisterFn = ( + router: Router, + context: RouteContext +) => { router.get('/agent/tasks/:task_id/artifacts/:artifact_id', (req, res) => { void (async () => { const taskId = req.params.task_id const artifactId = req.params.artifact_id try { const artifact = await getTaskArtifact(taskId, artifactId) - const artifactPath = getArtifactPath(taskId, context.workspace, artifact) + const artifactPath = getArtifactPath( + taskId, + context.workspace, + artifact + ) res.status(200).sendFile(artifactPath) } catch (err: Error | any) { console.error(err) @@ -424,20 +429,20 @@ const registerGetTaskArtifact: RouteRegisterFn = (router: Router, context: Route } export interface AgentConfig { - port: number; - workspace: string; + port: number + workspace: string } export const defaultAgentConfig: AgentConfig = { port: 8000, - workspace: "./workspace" -}; + workspace: './workspace', +} export class Agent { constructor( public taskHandler: TaskHandler, public config: AgentConfig - ) { } + ) {} static handleTask( _taskHandler: TaskHandler, @@ -446,8 +451,8 @@ export class Agent { taskHandler = _taskHandler return new Agent(_taskHandler, { workspace: config.workspace || defaultAgentConfig.workspace, - port: config.port || defaultAgentConfig.port - }); + port: config.port || defaultAgentConfig.port, + }) } start(port?: number): void { @@ -462,13 +467,13 @@ export class Agent { registerGetAgentTaskStep, registerGetArtifacts, registerCreateArtifact, - registerGetTaskArtifact + registerGetTaskArtifact, ], callback: () => { console.log(`Agent listening at http://localhost:${this.config.port}`) }, context: { - workspace: this.config.workspace + workspace: this.config.workspace, }, } diff --git a/packages/sdk/js/src/api.ts b/packages/sdk/js/src/api.ts index dcde6458..a9346845 100644 --- a/packages/sdk/js/src/api.ts +++ b/packages/sdk/js/src/api.ts @@ -1,36 +1,33 @@ -import * as OpenApiValidator from "express-openapi-validator"; -import yaml from "js-yaml"; -import express, { Router } from "express"; // <-- Import Router -import * as core from "express-serve-static-core"; +import * as OpenApiValidator from 'express-openapi-validator' +import yaml from 'js-yaml' +import express, { Router } from 'express' // <-- Import Router +import * as core from 'express-serve-static-core' -import spec from "../agent-protocol/schemas/openapi.yml"; +import spec from '../agent-protocol/schemas/openapi.yml' -export type ApiApp = core.Express; +export type ApiApp = core.Express export interface RouteContext { - workspace: string; + workspace: string } -export type RouteRegisterFn = ( - app: Router, - context: RouteContext -) => void; +export type RouteRegisterFn = (app: Router, context: RouteContext) => void export interface ApiConfig { - context: RouteContext; - port: number; - callback?: () => void; - routes: RouteRegisterFn[]; + context: RouteContext + port: number + callback?: () => void + routes: RouteRegisterFn[] } export const createApi = (config: ApiConfig) => { - const app = express(); + const app = express() - app.use(express.json()); - app.use(express.text()); - app.use(express.urlencoded({ extended: false })); + app.use(express.json()) + app.use(express.text()) + app.use(express.urlencoded({ extended: false })) - const parsedSpec = yaml.load(spec); + const parsedSpec = yaml.load(spec) app.use( OpenApiValidator.middleware({ @@ -38,18 +35,18 @@ export const createApi = (config: ApiConfig) => { validateRequests: true, // (default) validateResponses: true, // false by default }) - ); + ) - app.get("/openapi.yaml", (_, res) => { - res.setHeader("Content-Type", "text/yaml").status(200).send(spec); - }); + app.get('/openapi.yaml', (_, res) => { + res.setHeader('Content-Type', 'text/yaml').status(200).send(spec) + }) - const router = Router(); + const router = Router() config.routes.map((route) => { - route(router, config.context); - }); + route(router, config.context) + }) - app.use("/ap/v1", router); - app.listen(config.port, config.callback); -}; + app.use('/ap/v1', router) + app.listen(config.port, config.callback) +} diff --git a/packages/sdk/js/src/index.ts b/packages/sdk/js/src/index.ts index c0b39a84..d52b14fd 100644 --- a/packages/sdk/js/src/index.ts +++ b/packages/sdk/js/src/index.ts @@ -41,6 +41,6 @@ export { getAgentTaskStep, } -export { v4 } from "uuid" +export { v4 } from 'uuid' export default Agent diff --git a/packages/sdk/js/src/models.ts b/packages/sdk/js/src/models.ts index 1030ec96..194ddd2e 100644 --- a/packages/sdk/js/src/models.ts +++ b/packages/sdk/js/src/models.ts @@ -7,10 +7,10 @@ export type TaskInput = any * Artifact that the task has produced. Any value is allowed. */ export type Artifact = { - artifact_id: string, - agent_created: boolean, - file_name: string, - relative_path: string | null, + artifact_id: string + agent_created: boolean + file_name: string + relative_path: string | null created_at: string } @@ -25,9 +25,9 @@ export type StepInput = any export type StepOutput = any export enum StepStatus { - CREATED = "created", - RUNNING = "running", - COMPLETED = "completed" + CREATED = 'created', + RUNNING = 'running', + COMPLETED = 'completed', } export interface Step { diff --git a/packages/sdk/python/README.md b/packages/sdk/python/README.md index fc4a5f60..e95a0551 100644 --- a/packages/sdk/python/README.md +++ b/packages/sdk/python/README.md @@ -71,6 +71,7 @@ Agent.setup_agent(task_handler, step_handler).start(router=my_router) ### Testing You can test the compliance of your agent using the following script: + ```bash URL=http://127.0.0.1:8000 bash -c "$(curl -fsSL https://agentprotocol.ai/test.sh)" ``` @@ -92,4 +93,4 @@ poetry install poetry run python examples/minimal.py ``` -Feel free to open [an issue](https://github.com/AI-Engineers-Foundation/agent-protocol/issues) if you run into any problems! \ No newline at end of file +Feel free to open [an issue](https://github.com/AI-Engineers-Foundation/agent-protocol/issues) if you run into any problems! diff --git a/rfcs/2023-08-28-Pagination-RFC.md b/rfcs/2023-08-28-Pagination-RFC.md index 0df023c4..bdeea4f3 100644 --- a/rfcs/2023-08-28-Pagination-RFC.md +++ b/rfcs/2023-08-28-Pagination-RFC.md @@ -1,11 +1,11 @@ # List tasks, artifacts and steps in a paginated way. -| Feature name | Support Pagination | -| :------------ |:-----------------------------------------| -| **Author(s)** | Merwane Hamadi (merwanehamadi@gmail.com) Craig Swift (craigswift13@gmail.com)| -| **RFC PR:** | [PR 53](https://github.com/e2b-dev/agent-protocol/pull/53) | -| **Updated** | 2023-08-28 | -| **Obsoletes** | | +| Feature name | Support Pagination | +| :------------ | :---------------------------------------------------------------------------- | +| **Author(s)** | Merwane Hamadi (merwanehamadi@gmail.com) Craig Swift (craigswift13@gmail.com) | +| **RFC PR:** | [PR 53](https://github.com/e2b-dev/agent-protocol/pull/53) | +| **Updated** | 2023-08-28 | +| **Obsoletes** | | ## Summary @@ -24,6 +24,7 @@ Every app needs this. It's not really farfetched Query parameters for now. ### Alternatives Considered + - query parameter is the simplest, leanest design. We can add more later (body, headers, etc) => let's start lean. - for now, we won't add the pages in the response of the requests, this is another RFC. diff --git a/rfcs/2023-08-28-agent-created-RFC-.md b/rfcs/2023-08-28-agent-created-RFC-.md index 8d9e2b79..e9f0e4a0 100644 --- a/rfcs/2023-08-28-agent-created-RFC-.md +++ b/rfcs/2023-08-28-agent-created-RFC-.md @@ -1,18 +1,19 @@ # Tell the user whether the artifact is agent generated or not -| Feature name | Artifact Created At | -|:--------------|:-----------------------------------------| +| Feature name | Artifact Created At | +| :------------ | :---------------------------------------------------------------------------- | | **Author(s)** | Merwane Hamadi (merwanehamadi@gmail.com) Craig Swift (craigswift13@gmail.com) | -| **RFC PR:** | | -| **Created** | 2023-08-28 | -| **Obsoletes** | | +| **RFC PR:** | | +| **Created** | 2023-08-28 | +| **Obsoletes** | | ## Summary + Add agent_created to the artifact response body. ## Motivation -If we don't know whether an artifact is generated by the agent or not, it's hard to know what the agent did or did not do. +If we don't know whether an artifact is generated by the agent or not, it's hard to know what the agent did or did not do. ## Agent Builders Benefit diff --git a/rfcs/2023-08-28-list-entities-RFC.md b/rfcs/2023-08-28-list-entities-RFC.md index 013149d9..b5e6dbca 100644 --- a/rfcs/2023-08-28-list-entities-RFC.md +++ b/rfcs/2023-08-28-list-entities-RFC.md @@ -1,11 +1,11 @@ # List tasks, artifacts and steps in a paginated way. -| Feature name | Support Pagination | -| :------------ |:-----------------------------------------| -| **Author(s)** | Merwane Hamadi (merwanehamadi@gmail.com) Craig Swift (craigswift13@gmail.com)| -| **RFC PR:** | | -| **Updated** | 2023-08-28 | -| **Obsoletes** | | +| Feature name | Support Pagination | +| :------------ | :---------------------------------------------------------------------------- | +| **Author(s)** | Merwane Hamadi (merwanehamadi@gmail.com) Craig Swift (craigswift13@gmail.com) | +| **RFC PR:** | | +| **Updated** | 2023-08-28 | +| **Obsoletes** | | ## Summary @@ -18,7 +18,6 @@ It's like a table. Currently to build that you need to get the list of task ids. And if you want to display 20 tasks. You will make 20 GET calls to show them. - ## Agent Builders Benefit - They can allow their users to list things: Currently they get a list of ids, that's not useful. @@ -28,6 +27,7 @@ Currently to build that you need to get the list of task ids. And if you want to Just do what everyone does: return an array of objects that represent the resource ### Alternatives Considered + - Just make 26 calls when you need to display a table of 25 tasks (1 call to get an id and then 25 calls to get the information of each task) ### Compatibility diff --git a/rfcs/2023-09-15-endpoint-schema.md b/rfcs/2023-09-15-endpoint-schema.md index f32be8b3..0f7ae97c 100644 --- a/rfcs/2023-09-15-endpoint-schema.md +++ b/rfcs/2023-09-15-endpoint-schema.md @@ -1,10 +1,10 @@ # Standardized Endpoint Schema -| Feature name | Example name | -| :------------ | :------------------------------------------ | -| **Author(s)** | J. Zane Cook (jzanecook@z90.studio) | -| **RFC PR:** | [PR 66](https://github.com/AI-Engineer-Foundation/agent-protocol/pull/66) | -| **Updated** | 2023-09-18 | +| Feature name | Example name | +| :------------ | :------------------------------------------------------------------------ | +| **Author(s)** | J. Zane Cook (jzanecook@z90.studio) | +| **RFC PR:** | [PR 66](https://github.com/AI-Engineer-Foundation/agent-protocol/pull/66) | +| **Updated** | 2023-09-18 | ## Summary @@ -23,6 +23,7 @@ The motivation for the changes is to simplify the protocol for users while makin ## Design Proposal #### Endpoint Schema Update + Change the current `/agent/` endpoint schema to `/ap/v1/agent/`. This brings clarity in versioning and separates the agent-specific endpoints under a versioned umbrella. Additionally, the `ap` solidifies the `agent-protocol` URL for clear identification of its usage. ### Alternatives Considered @@ -31,7 +32,9 @@ Change the current `/agent/` endpoint schema to `/ap/v1/agent/`. This brings cla - Considered not enforcing the full path, but enforcing the full path not only looks better but also creates a better standard for future improvements. ### Compatibility + These changes are not backwards compatible for the following reasons: + - The change in the endpoint schema will break existing client implementations tied to the old URL structure. Clients will need to update their integrations to accomodate these changes, necessitating a major version bump. @@ -39,4 +42,4 @@ Clients will need to update their integrations to accomodate these changes, nece ## Questions and Discussion Topics - Should the endpoint be enforced after the hostname/port? -- Should the versioning be an integer or a decimal? \ No newline at end of file +- Should the versioning be an integer or a decimal? diff --git a/testing_suite/agent_protocol_v1.json b/testing_suite/agent_protocol_v1.json index 300b13c2..8c9561ed 100644 --- a/testing_suite/agent_protocol_v1.json +++ b/testing_suite/agent_protocol_v1.json @@ -1,1513 +1,1484 @@ { - "info": { - "_postman_id": "84d6bd70-4a9f-4470-be02-f2f9089ec69b", - "name": "Agent Protocol - REST v1", - "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" - }, - "item": [ - { - "name": "Basic User Experience", - "item": [ - { - "name": "Cleanup Previous Run Copy", - "event": [ - { - "listen": "prerequest", - "script": { - "id": "6e0312e1-1276-47b2-92be-b481545de5fb", - "exec": [ - "// See https://blog.postman.com/2019/05/28/pro-tip-dynamically-unset-postman-environment-variables/\r", - "// for more details on what we're doing here. \r", - "\r", - "cleanupCollectionVariables();\r", - "\r", - "function cleanupCollectionVariables() {\r", - " const clean = _.keys(pm.collectionVariables.toObject());\r", - "\r", - " _.each(clean, (arrItem) => {\r", - " pm.collectionVariables.unset(arrItem);\r", - " });\r", - "}" - ], - "type": "text/javascript" - } - }, - { - "listen": "test", - "script": { - "id": "acaabbd4-94fc-4444-8718-b9ca2c087721", - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "id": "5da26248-515e-4feb-b7b5-c7de25a03857", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "url": "https://postman-echo.com/delay/0" - }, - "response": [] - }, - { - "name": "Get all the tasks", - "event": [ - { - "listen": "test", - "script": { - "id": "32b75035-2164-4ec1-b61c-a86515847037", - "exec": [ - "" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "id": "98c48159-7553-4f9b-a1e7-dd66b961ec11", - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "id": "52fdb36d-66c1-41e9-b81a-1084ae813a2d", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [ - { - "key": "mock-match", - "value": "19", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks" - }, - "response": [ - { - "id": "98f96c46-c680-4377-a681-27b93d8425cf", - "name": "mock response", - "originalRequest": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - }, - { - "key": "mock-match", - "value": "19", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks" - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Date", - "value": "Thu, 17 Aug 2023 18:03:12 GMT", - "enabled": true - }, - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - }, - { - "key": "Content-Length", - "value": "150", - "enabled": true - }, - { - "key": "Connection", - "value": "keep-alive", - "enabled": true - }, - { - "key": "x-srv-trace", - "value": "v=1;t=1c95cd08c248d38f", - "enabled": true - }, - { - "key": "x-srv-span", - "value": "v=1;s=2527ca982b2b7c75", - "enabled": true - }, - { - "key": "Access-Control-Allow-Origin", - "value": "*", - "enabled": true - }, - { - "key": "X-RateLimit-Limit", - "value": "120", - "enabled": true - }, - { - "key": "X-RateLimit-Remaining", - "value": "117", - "enabled": true - }, - { - "key": "X-RateLimit-Reset", - "value": "1692295416", - "enabled": true - }, - { - "key": "ETag", - "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", - "enabled": true - }, - { - "key": "Vary", - "value": "Accept-Encoding", - "enabled": true - } - ], - "cookie": [], - "responseTime": null, - "body": "[\n {\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"step_id\": \"1a379290-3abc-11ee-be56-0242ac120002\",\n \"status\": \"completed\",\n \"output\": \"I am going to use the write_to_file method to write the word 'Washington' to a .txt file\",\n \"artifacts\": [],\n \"is_last\": false\n }\n]" - } - ] - }, - { - "name": "Create a new task", - "event": [ - { - "listen": "prerequest", - "script": { - "id": "c5e90382-a818-4e98-9f9b-4a2877e0f129", - "exec": [ - "" - ], - "type": "text/javascript" - } - }, - { - "listen": "test", - "script": { - "id": "683fd614-1610-4dab-bf88-6ae830186dac", - "exec": [ - "var jsonData = pm.response.json();", - "pm.globals.set(\"task_id\", jsonData.task_id);", - "", - "pm.globals.set(", - " \"step_body\",", - " JSON.stringify(", - " {", - " \"input\": JSON.parse(pm.request.body.raw).input", - " } ", - " )", - ");" - ], - "type": "text/javascript" - } - } - ], - "id": "090b65b7-80e6-4a20-81d7-2a6f72644ad5", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "POST", - "header": [ - { - "key": "mock-match", - "value": "34", - "type": "text" - }, - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\",\n \"additional_input\": {\"test_run_id\": \"123\"}\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks" - }, - "response": [ - { - "id": "5aa13fc7-8099-43ac-81d4-87af8cdbb6fe", - "name": "mock response", - "originalRequest": { - "method": "POST", - "header": [ - { - "key": "mock-match", - "value": "34" - }, - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks" - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Date", - "value": "Sun, 13 Aug 2023 23:23:05 GMT", - "enabled": true - }, - { - "key": "Content-Type", - "value": "application/json; charset=utf-8", - "enabled": true - }, - { - "key": "Content-Length", - "value": "28", - "enabled": true - }, - { - "key": "Connection", - "value": "keep-alive", - "enabled": true - }, - { - "key": "x-srv-trace", - "value": "v=1;t=daef761e243c402b", - "enabled": true - }, - { - "key": "x-srv-span", - "value": "v=1;s=2c0bd88502372360", - "enabled": true - }, - { - "key": "Access-Control-Allow-Origin", - "value": "*", - "enabled": true - }, - { - "key": "X-RateLimit-Limit", - "value": "120", - "enabled": true - }, - { - "key": "X-RateLimit-Remaining", - "value": "115", - "enabled": true - }, - { - "key": "X-RateLimit-Reset", - "value": "1691968334", - "enabled": true - }, - { - "key": "ETag", - "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", - "enabled": true - }, - { - "key": "Vary", - "value": "Accept-Encoding", - "enabled": true - } - ], - "cookie": [], - "responseTime": null, - "body": "{\n \"input\": \"Write the word 'Washington' to a .txt file\",\n \"additional_input\": {},\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"artifacts\": []\n}" - } - ] - }, - { - "name": "Execute a step", - "event": [ - { - "listen": "test", - "script": { - "id": "26426e07-115e-4841-9887-24fd9dccca2a", - "exec": [ - "if (pm.request.url.path[0] == \"agent\" && pm.response.headers.has(\"Content-Type\",`application/json`)) {", - " var artifacts = pm.response.json().artifacts;", - "", - " if (artifacts && artifacts.length > 0) {", - " pm.globals.set(\"artifactId\", artifacts[0].artifact_id);", - " }", - "", - " var artifacts = pm.response.json().artifacts;", - " var existingArtifactId = pm.globals.get(\"artifactId\");", - "/* Commented out artifact checking code and max step code because the SDK doesn't implement simple agents", - " if (artifacts && artifacts.length > 0) {", - " if (artifacts.length > 1) {", - " pm.test(\"This task should only create 1 artifact\", function () {", - " pm.expect.fail(\"More than one artifact was created.\");", - " });", - " } else {", - " pm.globals.set(\"artifactId\", artifacts[0].artifact_id);", - " }", - " }*/", - " stepNumber = pm.collectionVariables.get(\"step-number\") ?? 1", - " const maxSteps = 10", - " if(!pm.response.json().is_last) {", - " /*if (stepNumber > maxSteps) {", - " console.log(`Max steps reached (${maxSteps})`);", - " pm.test(`This task should be completed after ${maxSteps} steps`, function () {", - " pm.expect.fail(`is_last should be true before max steps reached (${maxSteps})`);", - " });", - " } else {", - " console.log(`Steps reached (${stepNumber})`);", - "", - " pm.collectionVariables.set('step-number', stepNumber + 1);", - " pm.globals.set(", - " \"step_body\",", - " JSON.stringify(", - " {", - " \"input\": \"y\"", - " }", - " )", - " );", - "", - " pm.collectionVariables.set('previous-step', pm.response.json().step_id)", - " postman.setNextRequest('Execute the steps until completion');", - " }*/", - " ", - " }", - "}" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "id": "3bdd2e35-1745-439e-9ef1-ea2e003ddc42", - "exec": [ - "stepNumber = pm.collectionVariables.get(\"step-number\") ?? 1", - "console.log(\"Step number:\" + stepNumber)", - "console.log(\"Task Input:\" + pm.globals.get(\"taskInput\"))", - "if (stepNumber) {", - " pm.request.headers.upsert({ key: 'mock-match', value: stepNumber.toString() });", - " console.log(pm.request)", - "}" - ], - "type": "text/javascript" - } - } - ], - "id": "c001b340-752c-45ea-aaa2-22cb52e47297", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{{step_body}}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" - }, - "response": [ - { - "id": "1def41e7-04a8-41d6-bcef-b0149882510d", - "name": "mock response", - "originalRequest": { - "method": "POST", - "header": [ - { - "key": "mock-match", - "value": "1", - "type": "text" - }, - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Date", - "value": "Mon, 14 Aug 2023 16:28:55 GMT", - "enabled": true - }, - { - "key": "Content-Type", - "value": "application/json; charset=utf-8", - "enabled": true - }, - { - "key": "Content-Length", - "value": "275", - "enabled": true - }, - { - "key": "Connection", - "value": "keep-alive", - "enabled": true - }, - { - "key": "x-srv-trace", - "value": "v=1;t=daef761e243c402b", - "enabled": true - }, - { - "key": "x-srv-span", - "value": "v=1;s=2c0bd88502372360", - "enabled": true - }, - { - "key": "Access-Control-Allow-Origin", - "value": "*", - "enabled": true - }, - { - "key": "X-RateLimit-Limit", - "value": "120", - "enabled": true - }, - { - "key": "X-RateLimit-Remaining", - "value": "115", - "enabled": true - }, - { - "key": "X-RateLimit-Reset", - "value": "1691968334", - "enabled": true - }, - { - "key": "ETag", - "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", - "enabled": true - }, - { - "key": "Vary", - "value": "Accept-Encoding", - "enabled": true - } - ], - "cookie": [], - "responseTime": null, - "body": "{\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"step_id\": \"1a379290-3abc-11ee-be56-0242ac120002\",\n \"input\": \"y\",\n \"status\": \"completed\",\n \"output\": \"I am going to use the write_to_file method to write the word 'Washington' to a .txt file\",\n \"artifacts\": [\n ],\n \"is_last\": false\n}" - }, - { - "id": "0bca46fd-7401-48fa-b413-6357e53ae19d", - "name": "mock response", - "originalRequest": { - "method": "POST", - "header": [ - { - "key": "mock-match", - "value": "2", - "type": "text" - }, - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"y\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Date", - "value": "Mon, 14 Aug 2023 16:28:55 GMT", - "enabled": true - }, - { - "key": "Content-Type", - "value": "application/json; charset=utf-8", - "enabled": true - }, - { - "key": "Content-Length", - "value": "275", - "enabled": true - }, - { - "key": "Connection", - "value": "keep-alive", - "enabled": true - }, - { - "key": "x-srv-trace", - "value": "v=1;t=daef761e243c402b", - "enabled": true - }, - { - "key": "x-srv-span", - "value": "v=1;s=2c0bd88502372360", - "enabled": true - }, - { - "key": "Access-Control-Allow-Origin", - "value": "*", - "enabled": true - }, - { - "key": "X-RateLimit-Limit", - "value": "120", - "enabled": true - }, - { - "key": "X-RateLimit-Remaining", - "value": "115", - "enabled": true - }, - { - "key": "X-RateLimit-Reset", - "value": "1691968334", - "enabled": true - }, - { - "key": "ETag", - "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", - "enabled": true - }, - { - "key": "Vary", - "value": "Accept-Encoding", - "enabled": true - } - ], - "cookie": [], - "responseTime": null, - "body": "{\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"step_id\": \"2a479290-3abc-11ee-be56-0242ac1209c1\",\n \"input\": \"y\",\n \"status\": \"completed\",\n \"output\": \"I used the write_to_file method to write the file test_output.txt\",\n \"artifacts\": [\n {\n \"artifact_id\": \"2ba79290-3abc-11ee-be56-0242ac1209d3\",\n \"agent_created\": true,\n \"uri\": \"file://test_output.txt\"\n }\n ],\n \"is_last\": true\n}" - } - ] - }, - { - "name": "Execute step after completion", - "event": [ - { - "listen": "test", - "script": { - "id": "26426e07-115e-4841-9887-24fd9dccca2a", - "exec": [ - "" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "id": "3bdd2e35-1745-439e-9ef1-ea2e003ddc42", - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "id": "2db71ce6-f370-4e1e-83de-1990be2026ee", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - }, - { - "key": "mock-match", - "value": "34", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"y\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" - }, - "response": [ - { - "id": "768252c5-a6aa-4ae5-91a1-253e653a287d", - "name": "mock response", - "originalRequest": { - "method": "POST", - "header": [ - { - "key": "mock-match", - "value": "34", - "type": "text" - }, - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"y\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Date", - "value": "Mon, 14 Aug 2023 16:28:55 GMT", - "enabled": true - }, - { - "key": "Content-Type", - "value": "application/json; charset=utf-8", - "enabled": true - }, - { - "key": "Content-Length", - "value": "275", - "enabled": true - }, - { - "key": "Connection", - "value": "keep-alive", - "enabled": true - }, - { - "key": "x-srv-trace", - "value": "v=1;t=daef761e243c402b", - "enabled": true - }, - { - "key": "x-srv-span", - "value": "v=1;s=2c0bd88502372360", - "enabled": true - }, - { - "key": "Access-Control-Allow-Origin", - "value": "*", - "enabled": true - }, - { - "key": "X-RateLimit-Limit", - "value": "120", - "enabled": true - }, - { - "key": "X-RateLimit-Remaining", - "value": "115", - "enabled": true - }, - { - "key": "X-RateLimit-Reset", - "value": "1691968334", - "enabled": true - }, - { - "key": "ETag", - "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", - "enabled": true - }, - { - "key": "Vary", - "value": "Accept-Encoding", - "enabled": true - } - ], - "cookie": [], - "responseTime": null, - "body": "{\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"step_id\": \"2d479290-3abc-11ee-be56-0242ac120b95\",\n \"status\": \"completed\",\n \"output\": \"I am already done with my work.\",\n \"artifacts\": [\n ],\n \"is_last\": true\n}" - } - ] - } - ], - "id": "5ba23d82-afe2-41a2-b782-f41c903d45d6", - "description": "We ask the agent to write a file in his workspace.", - "event": [ - { - "listen": "prerequest", - "script": { - "id": "1f74f7f2-ed1c-43d4-88ee-e9688e6caf45", - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "id": "fbbe4b1f-0708-452d-99b7-fe8ad882a7ba", - "type": "text/javascript", - "exec": [ - "pm.test(\"Response status code is 200\", function () {", - " pm.response.to.have.status(200);", - "});" - ] - } - } - ] - }, - { - "name": "Tasks", - "item": [ - { - "name": "Create a new task", - "event": [ - { - "listen": "test", - "script": { - "id": "46ca258a-c625-4c17-b413-0c86023e705e", - "exec": [ - "var jsonData = pm.response.json();", - "pm.globals.set(\"taskId\", jsonData.task_id);", - "pm.globals.set(\"taskInput\", JSON.parse(pm.request.body.raw).input);" - ], - "type": "text/javascript" - } - } - ], - "id": "eb9d0c96-18a1-4e95-8948-9c1894d56409", - "protocolProfileBehavior": { - "disableBodyPruning": true, - "disabledSystemHeaders": {} - }, - "request": { - "method": "POST", - "header": [ - { - "key": "mock-match", - "value": "34" - }, - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks" - }, - "response": [] - }, - { - "name": "Get the task", - "event": [ - { - "listen": "test", - "script": { - "id": "dae8e511-e80d-478b-81ab-2d66dd6fd5b3", - "exec": [ - "pm.test(\"Response time is less than 500ms\", function () {\r", - " pm.expect(pm.response.responseTime).to.be.below(500);\r", - "});\r", - "\r", - "pm.test(\"Content-Type is present\", function () {\r", - " pm.response.to.have.header(\"Content-Type\");\r", - "});\r", - "\r", - "pm.test(\"Content-Type is application/json\", function () {\r", - " var contentType = pm.response.headers.get('Content-Type');\r", - " pm.expect(contentType).to.include('application/json');\r", - "});\r", - "\r", - "pm.test(\"Response has all required properties\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('task_id');\r", - " pm.expect(jsonData).to.have.property('input');\r", - " pm.expect(jsonData).to.have.property('additional_input');\r", - " pm.expect(jsonData).to.have.property('artifacts');\r", - "});\r", - "\r", - "" - ], - "type": "text/javascript" - } - } - ], - "id": "f84ed7b1-c97a-4968-a14c-88c231b187fa", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}" - }, - "response": [ - { - "id": "ea9d8d69-ed12-4d2c-85d9-f56028bbc628", - "name": "mock response", - "originalRequest": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{taskId}}" - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Date", - "value": "Sun, 13 Aug 2023 23:23:05 GMT", - "enabled": true - }, - { - "key": "Content-Type", - "value": "application/json; charset=utf-8", - "enabled": true - }, - { - "key": "Content-Length", - "value": "28", - "enabled": true - }, - { - "key": "Connection", - "value": "keep-alive", - "enabled": true - }, - { - "key": "x-srv-trace", - "value": "v=1;t=daef761e243c402b", - "enabled": true - }, - { - "key": "x-srv-span", - "value": "v=1;s=2c0bd88502372360", - "enabled": true - }, - { - "key": "Access-Control-Allow-Origin", - "value": "*", - "enabled": true - }, - { - "key": "X-RateLimit-Limit", - "value": "120", - "enabled": true - }, - { - "key": "X-RateLimit-Remaining", - "value": "115", - "enabled": true - }, - { - "key": "X-RateLimit-Reset", - "value": "1691968334", - "enabled": true - }, - { - "key": "ETag", - "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", - "enabled": true - }, - { - "key": "Vary", - "value": "Accept-Encoding", - "enabled": true - } - ], - "cookie": [], - "responseTime": null, - "body": "{\n \"input\": \"Write the word 'Washington' to a .txt file\",\n \"additional_input\": null,\n \"task_id\": \"121\",\n \"artifacts\": []\n}" - } - ] - }, - { - "name": "Get all the tasks", - "event": [ - { - "listen": "test", - "script": { - "id": "2e4ee6d8-e5d8-4112-be5c-ec8404f38ef0", - "exec": [ - "pm.test(\"Response time is less than 500ms\", function () {", - " pm.expect(pm.response.responseTime).to.be.below(500);", - "});", - "", - "pm.test(\"Content-Type is present\", function () {", - " pm.response.to.have.header(\"Content-Type\");", - "});", - "", - "pm.test(\"Content-Type is application/json\", function () {", - " var contentType = pm.response.headers.get('Content-Type');", - " pm.expect(contentType).to.include('application/json');", - "});" - ], - "type": "text/javascript" - } - } - ], - "id": "ca583fa3-bec4-4c73-b989-47f3414a8d51", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks" - }, - "response": [] - }, - { - "name": "Create a second task", - "event": [ - { - "listen": "test", - "script": { - "id": "2c8c047b-eec5-4015-86a8-0640f3315ce3", - "exec": [ - "var jsonData = pm.response.json();", - "pm.globals.set(\"lastTaskId\", jsonData.task_id);" - ], - "type": "text/javascript" - } - } - ], - "id": "0de5f275-995e-4eb5-b63c-0f2e8996fab1", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "POST", - "header": [ - { - "key": "mock-match", - "value": "34" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks" - }, - "response": [] - }, - { - "name": "Get all the tasks with Pagination", - "event": [ - { - "listen": "test", - "script": { - "id": "697349ba-26c9-431c-a68d-a1c0efeeaeae", - "exec": [ - "var jsonData = pm.response.json();", - "", - "pm.test(\"Response time is less than 500ms\", function () {", - " pm.expect(pm.response.responseTime).to.be.below(500);", - "});", - "", - "pm.test(\"Content-Type is present\", function () {", - " pm.response.to.have.header(\"Content-Type\");", - "});", - "", - "pm.test(\"Content-Type is application/json\", function () {", - " var contentType = pm.response.headers.get('Content-Type');", - " pm.expect(contentType).to.include('application/json');", - "});", - "", - "pm.test(\"Pagination is set\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.pagination).to.be.an('object');", - "});", - "", - "pm.test(\"Page size is 1\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.pagination.page_size).to.eql(1);", - "});", - "", - "pm.test(\"Items is an array with one item\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.tasks).to.be.an('array').that.has.lengthOf(1);", - "});", - "", - "", - "pm.test(\"Response length respects page_size\", function() {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.tasks.length).to.be.at.most(1);", - "});", - "", - "if (jsonData.items && jsonData.items.length > 0) {", - " if (pm.variables.has(\"lastTaskId\") && pm.variables.get(\"page\") > 1) {", - " pm.test(\"First task of page \" + pm.variables.get(\"page\") + \" is not the last task of page \" + (pm.variables.get(\"page\") - 1), function() {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.items[0].task_id).to.not.equal(pm.variables.get(\"lastTaskId\"));", - " });", - " }", - " if (jsonData.items.length > 0) {", - " pm.variables.set(\"lastTaskId\", jsonData.items[jsonData.items.length - 1].task_id);", - " }", - "}" - ], - "type": "text/javascript" - } - } - ], - "id": "f41e04c1-b266-4e90-909e-ebb0657958d9", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{url}}/ap/v1/agent/tasks?page_size=1¤t_page=1", - "host": [ - "{{url}}/ap/v1" - ], - "path": [ - "agent", - "tasks" - ], - "query": [ - { - "key": "page_size", - "value": "1" - }, - { - "key": "current_page", - "value": "1" - } - ] - } - }, - "response": [] - } - ], - "id": "4b743f46-e582-4565-ac7d-c6446a710ee1", - "description": "Create tasks and consumes them.", - "event": [ - { - "listen": "prerequest", - "script": { - "id": "7f174c78-2068-4351-a64c-bd4c115470e8", - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "id": "ef296737-8c06-47ed-90c3-bfe882c1aa40", - "type": "text/javascript", - "exec": [ - "pm.test(\"Response status code is 200\", function () {", - " pm.response.to.have.status(200);", - "});" - ] - } - } - ] - }, - { - "name": "Artifacts", - "item": [ - { - "name": "Create a new task", - "event": [ - { - "listen": "test", - "script": { - "id": "65ad57a3-b776-4d7c-86f9-be6fbf17cac6", - "exec": [ - "var jsonData = pm.response.json();", - "pm.globals.set(\"task_id\", jsonData.task_id);" - ], - "type": "text/javascript" - } - } - ], - "id": "1ee1eee8-cc2d-43a2-a6e1-7168b0559c45", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks" - }, - "response": [] - }, - { - "name": "Upload Artifact", - "event": [ - { - "listen": "test", - "script": { - "id": "6cf7a2c4-1b8a-4c11-8c1a-6b56b2eb80e6", - "exec": [ - "var jsonData = pm.response.json();", - "pm.globals.set(\"artifact_id\", jsonData.artifact_id);" - ], - "type": "text/javascript" - } - } - ], - "id": "e71fee13-11cb-4b08-a4bb-c9c0f971efed", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "multipart/form-data" - } - ], - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "file", - "type": "file", - "src": "test_output.txt" - } - ] - }, - "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/artifacts" - }, - "response": [] - }, - { - "name": "Download Artifact", - "event": [ - { - "listen": "test", - "script": { - "id": "82829e0a-f8c7-4925-b28c-0513b018e83e", - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "id": "b002580c-0d48-42c9-83e0-5fb5eaed38ce", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [ - { - "key": "mock-match", - "value": "11", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/artifacts/{{artifact_id}}" - }, - "response": [ - { - "id": "ade2dd37-0f84-45f0-881e-2224636a4956", - "name": "mock response", - "originalRequest": { - "method": "GET", - "header": [ - { - "key": "mock-match", - "value": "11", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{taskId}}/artifacts/{{artifactId}}" - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "text", - "header": [ - { - "key": "Date", - "value": "Mon, 14 Aug 2023 16:28:55 GMT", - "enabled": true - }, - { - "key": "Content-Type", - "value": "application/json; charset=utf-8", - "enabled": true - }, - { - "key": "Content-Length", - "value": "275", - "enabled": true - }, - { - "key": "Connection", - "value": "keep-alive", - "enabled": true - }, - { - "key": "x-srv-trace", - "value": "v=1;t=daef761e243c402b", - "enabled": true - }, - { - "key": "x-srv-span", - "value": "v=1;s=2c0bd88502372360", - "enabled": true - }, - { - "key": "Access-Control-Allow-Origin", - "value": "*", - "enabled": true - }, - { - "key": "X-RateLimit-Limit", - "value": "120", - "enabled": true - }, - { - "key": "X-RateLimit-Remaining", - "value": "115", - "enabled": true - }, - { - "key": "X-RateLimit-Reset", - "value": "1691968334", - "enabled": true - }, - { - "key": "ETag", - "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", - "enabled": true - }, - { - "key": "Vary", - "value": "Accept-Encoding", - "enabled": true - } - ], - "cookie": [], - "responseTime": null, - "body": "Washington" - } - ] - } - ], - "id": "48e93b55-d463-452f-9d56-f59ee5e95060", - "description": "Create Artifacts and consumes them.", - "event": [ - { - "listen": "prerequest", - "script": { - "id": "61be92ef-c1cd-4f2a-a77c-78e5913ea299", - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "id": "7dcedb1e-4f42-48b1-ac9b-fd3b842b428b", - "type": "text/javascript", - "exec": [ - "pm.test(\"Response status code is 200\", function () {", - " pm.response.to.have.status(200);", - "});" - ] - } - } - ] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "id": "7dd8bbee-357a-44fd-b891-46bcc3a8a41c", - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "id": "b5f22749-138a-45fe-b6c7-79fe5cf06017", - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] -} \ No newline at end of file + "info": { + "_postman_id": "84d6bd70-4a9f-4470-be02-f2f9089ec69b", + "name": "Agent Protocol - REST v1", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" + }, + "item": [ + { + "name": "Basic User Experience", + "item": [ + { + "name": "Cleanup Previous Run Copy", + "event": [ + { + "listen": "prerequest", + "script": { + "id": "6e0312e1-1276-47b2-92be-b481545de5fb", + "exec": [ + "// See https://blog.postman.com/2019/05/28/pro-tip-dynamically-unset-postman-environment-variables/\r", + "// for more details on what we're doing here. \r", + "\r", + "cleanupCollectionVariables();\r", + "\r", + "function cleanupCollectionVariables() {\r", + " const clean = _.keys(pm.collectionVariables.toObject());\r", + "\r", + " _.each(clean, (arrItem) => {\r", + " pm.collectionVariables.unset(arrItem);\r", + " });\r", + "}" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "id": "acaabbd4-94fc-4444-8718-b9ca2c087721", + "exec": [""], + "type": "text/javascript" + } + } + ], + "id": "5da26248-515e-4feb-b7b5-c7de25a03857", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "url": "https://postman-echo.com/delay/0" + }, + "response": [] + }, + { + "name": "Get all the tasks", + "event": [ + { + "listen": "test", + "script": { + "id": "32b75035-2164-4ec1-b61c-a86515847037", + "exec": [""], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "98c48159-7553-4f9b-a1e7-dd66b961ec11", + "exec": [""], + "type": "text/javascript" + } + } + ], + "id": "52fdb36d-66c1-41e9-b81a-1084ae813a2d", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "mock-match", + "value": "19", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks" + }, + "response": [ + { + "id": "98f96c46-c680-4377-a681-27b93d8425cf", + "name": "mock response", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "mock-match", + "value": "19", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks" + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Thu, 17 Aug 2023 18:03:12 GMT", + "enabled": true + }, + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + }, + { + "key": "Content-Length", + "value": "150", + "enabled": true + }, + { + "key": "Connection", + "value": "keep-alive", + "enabled": true + }, + { + "key": "x-srv-trace", + "value": "v=1;t=1c95cd08c248d38f", + "enabled": true + }, + { + "key": "x-srv-span", + "value": "v=1;s=2527ca982b2b7c75", + "enabled": true + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*", + "enabled": true + }, + { + "key": "X-RateLimit-Limit", + "value": "120", + "enabled": true + }, + { + "key": "X-RateLimit-Remaining", + "value": "117", + "enabled": true + }, + { + "key": "X-RateLimit-Reset", + "value": "1692295416", + "enabled": true + }, + { + "key": "ETag", + "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", + "enabled": true + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "enabled": true + } + ], + "cookie": [], + "responseTime": null, + "body": "[\n {\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"step_id\": \"1a379290-3abc-11ee-be56-0242ac120002\",\n \"status\": \"completed\",\n \"output\": \"I am going to use the write_to_file method to write the word 'Washington' to a .txt file\",\n \"artifacts\": [],\n \"is_last\": false\n }\n]" + } + ] + }, + { + "name": "Create a new task", + "event": [ + { + "listen": "prerequest", + "script": { + "id": "c5e90382-a818-4e98-9f9b-4a2877e0f129", + "exec": [""], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "id": "683fd614-1610-4dab-bf88-6ae830186dac", + "exec": [ + "var jsonData = pm.response.json();", + "pm.globals.set(\"task_id\", jsonData.task_id);", + "", + "pm.globals.set(", + " \"step_body\",", + " JSON.stringify(", + " {", + " \"input\": JSON.parse(pm.request.body.raw).input", + " } ", + " )", + ");" + ], + "type": "text/javascript" + } + } + ], + "id": "090b65b7-80e6-4a20-81d7-2a6f72644ad5", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "POST", + "header": [ + { + "key": "mock-match", + "value": "34", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\",\n \"additional_input\": {\"test_run_id\": \"123\"}\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks" + }, + "response": [ + { + "id": "5aa13fc7-8099-43ac-81d4-87af8cdbb6fe", + "name": "mock response", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "mock-match", + "value": "34" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks" + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Sun, 13 Aug 2023 23:23:05 GMT", + "enabled": true + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "enabled": true + }, + { + "key": "Content-Length", + "value": "28", + "enabled": true + }, + { + "key": "Connection", + "value": "keep-alive", + "enabled": true + }, + { + "key": "x-srv-trace", + "value": "v=1;t=daef761e243c402b", + "enabled": true + }, + { + "key": "x-srv-span", + "value": "v=1;s=2c0bd88502372360", + "enabled": true + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*", + "enabled": true + }, + { + "key": "X-RateLimit-Limit", + "value": "120", + "enabled": true + }, + { + "key": "X-RateLimit-Remaining", + "value": "115", + "enabled": true + }, + { + "key": "X-RateLimit-Reset", + "value": "1691968334", + "enabled": true + }, + { + "key": "ETag", + "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", + "enabled": true + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "enabled": true + } + ], + "cookie": [], + "responseTime": null, + "body": "{\n \"input\": \"Write the word 'Washington' to a .txt file\",\n \"additional_input\": {},\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"artifacts\": []\n}" + } + ] + }, + { + "name": "Execute a step", + "event": [ + { + "listen": "test", + "script": { + "id": "26426e07-115e-4841-9887-24fd9dccca2a", + "exec": [ + "if (pm.request.url.path[0] == \"agent\" && pm.response.headers.has(\"Content-Type\",`application/json`)) {", + " var artifacts = pm.response.json().artifacts;", + "", + " if (artifacts && artifacts.length > 0) {", + " pm.globals.set(\"artifactId\", artifacts[0].artifact_id);", + " }", + "", + " var artifacts = pm.response.json().artifacts;", + " var existingArtifactId = pm.globals.get(\"artifactId\");", + "/* Commented out artifact checking code and max step code because the SDK doesn't implement simple agents", + " if (artifacts && artifacts.length > 0) {", + " if (artifacts.length > 1) {", + " pm.test(\"This task should only create 1 artifact\", function () {", + " pm.expect.fail(\"More than one artifact was created.\");", + " });", + " } else {", + " pm.globals.set(\"artifactId\", artifacts[0].artifact_id);", + " }", + " }*/", + " stepNumber = pm.collectionVariables.get(\"step-number\") ?? 1", + " const maxSteps = 10", + " if(!pm.response.json().is_last) {", + " /*if (stepNumber > maxSteps) {", + " console.log(`Max steps reached (${maxSteps})`);", + " pm.test(`This task should be completed after ${maxSteps} steps`, function () {", + " pm.expect.fail(`is_last should be true before max steps reached (${maxSteps})`);", + " });", + " } else {", + " console.log(`Steps reached (${stepNumber})`);", + "", + " pm.collectionVariables.set('step-number', stepNumber + 1);", + " pm.globals.set(", + " \"step_body\",", + " JSON.stringify(", + " {", + " \"input\": \"y\"", + " }", + " )", + " );", + "", + " pm.collectionVariables.set('previous-step', pm.response.json().step_id)", + " postman.setNextRequest('Execute the steps until completion');", + " }*/", + " ", + " }", + "}" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "3bdd2e35-1745-439e-9ef1-ea2e003ddc42", + "exec": [ + "stepNumber = pm.collectionVariables.get(\"step-number\") ?? 1", + "console.log(\"Step number:\" + stepNumber)", + "console.log(\"Task Input:\" + pm.globals.get(\"taskInput\"))", + "if (stepNumber) {", + " pm.request.headers.upsert({ key: 'mock-match', value: stepNumber.toString() });", + " console.log(pm.request)", + "}" + ], + "type": "text/javascript" + } + } + ], + "id": "c001b340-752c-45ea-aaa2-22cb52e47297", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{{step_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" + }, + "response": [ + { + "id": "1def41e7-04a8-41d6-bcef-b0149882510d", + "name": "mock response", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "mock-match", + "value": "1", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Mon, 14 Aug 2023 16:28:55 GMT", + "enabled": true + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "enabled": true + }, + { + "key": "Content-Length", + "value": "275", + "enabled": true + }, + { + "key": "Connection", + "value": "keep-alive", + "enabled": true + }, + { + "key": "x-srv-trace", + "value": "v=1;t=daef761e243c402b", + "enabled": true + }, + { + "key": "x-srv-span", + "value": "v=1;s=2c0bd88502372360", + "enabled": true + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*", + "enabled": true + }, + { + "key": "X-RateLimit-Limit", + "value": "120", + "enabled": true + }, + { + "key": "X-RateLimit-Remaining", + "value": "115", + "enabled": true + }, + { + "key": "X-RateLimit-Reset", + "value": "1691968334", + "enabled": true + }, + { + "key": "ETag", + "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", + "enabled": true + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "enabled": true + } + ], + "cookie": [], + "responseTime": null, + "body": "{\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"step_id\": \"1a379290-3abc-11ee-be56-0242ac120002\",\n \"input\": \"y\",\n \"status\": \"completed\",\n \"output\": \"I am going to use the write_to_file method to write the word 'Washington' to a .txt file\",\n \"artifacts\": [\n ],\n \"is_last\": false\n}" + }, + { + "id": "0bca46fd-7401-48fa-b413-6357e53ae19d", + "name": "mock response", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "mock-match", + "value": "2", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"y\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Mon, 14 Aug 2023 16:28:55 GMT", + "enabled": true + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "enabled": true + }, + { + "key": "Content-Length", + "value": "275", + "enabled": true + }, + { + "key": "Connection", + "value": "keep-alive", + "enabled": true + }, + { + "key": "x-srv-trace", + "value": "v=1;t=daef761e243c402b", + "enabled": true + }, + { + "key": "x-srv-span", + "value": "v=1;s=2c0bd88502372360", + "enabled": true + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*", + "enabled": true + }, + { + "key": "X-RateLimit-Limit", + "value": "120", + "enabled": true + }, + { + "key": "X-RateLimit-Remaining", + "value": "115", + "enabled": true + }, + { + "key": "X-RateLimit-Reset", + "value": "1691968334", + "enabled": true + }, + { + "key": "ETag", + "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", + "enabled": true + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "enabled": true + } + ], + "cookie": [], + "responseTime": null, + "body": "{\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"step_id\": \"2a479290-3abc-11ee-be56-0242ac1209c1\",\n \"input\": \"y\",\n \"status\": \"completed\",\n \"output\": \"I used the write_to_file method to write the file test_output.txt\",\n \"artifacts\": [\n {\n \"artifact_id\": \"2ba79290-3abc-11ee-be56-0242ac1209d3\",\n \"agent_created\": true,\n \"uri\": \"file://test_output.txt\"\n }\n ],\n \"is_last\": true\n}" + } + ] + }, + { + "name": "Execute step after completion", + "event": [ + { + "listen": "test", + "script": { + "id": "26426e07-115e-4841-9887-24fd9dccca2a", + "exec": [""], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "3bdd2e35-1745-439e-9ef1-ea2e003ddc42", + "exec": [""], + "type": "text/javascript" + } + } + ], + "id": "2db71ce6-f370-4e1e-83de-1990be2026ee", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "mock-match", + "value": "34", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"y\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" + }, + "response": [ + { + "id": "768252c5-a6aa-4ae5-91a1-253e653a287d", + "name": "mock response", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "mock-match", + "value": "34", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"y\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Mon, 14 Aug 2023 16:28:55 GMT", + "enabled": true + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "enabled": true + }, + { + "key": "Content-Length", + "value": "275", + "enabled": true + }, + { + "key": "Connection", + "value": "keep-alive", + "enabled": true + }, + { + "key": "x-srv-trace", + "value": "v=1;t=daef761e243c402b", + "enabled": true + }, + { + "key": "x-srv-span", + "value": "v=1;s=2c0bd88502372360", + "enabled": true + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*", + "enabled": true + }, + { + "key": "X-RateLimit-Limit", + "value": "120", + "enabled": true + }, + { + "key": "X-RateLimit-Remaining", + "value": "115", + "enabled": true + }, + { + "key": "X-RateLimit-Reset", + "value": "1691968334", + "enabled": true + }, + { + "key": "ETag", + "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", + "enabled": true + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "enabled": true + } + ], + "cookie": [], + "responseTime": null, + "body": "{\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"step_id\": \"2d479290-3abc-11ee-be56-0242ac120b95\",\n \"status\": \"completed\",\n \"output\": \"I am already done with my work.\",\n \"artifacts\": [\n ],\n \"is_last\": true\n}" + } + ] + } + ], + "id": "5ba23d82-afe2-41a2-b782-f41c903d45d6", + "description": "We ask the agent to write a file in his workspace.", + "event": [ + { + "listen": "prerequest", + "script": { + "id": "1f74f7f2-ed1c-43d4-88ee-e9688e6caf45", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "fbbe4b1f-0708-452d-99b7-fe8ad882a7ba", + "type": "text/javascript", + "exec": [ + "pm.test(\"Response status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] + }, + { + "name": "Tasks", + "item": [ + { + "name": "Create a new task", + "event": [ + { + "listen": "test", + "script": { + "id": "46ca258a-c625-4c17-b413-0c86023e705e", + "exec": [ + "var jsonData = pm.response.json();", + "pm.globals.set(\"taskId\", jsonData.task_id);", + "pm.globals.set(\"taskInput\", JSON.parse(pm.request.body.raw).input);" + ], + "type": "text/javascript" + } + } + ], + "id": "eb9d0c96-18a1-4e95-8948-9c1894d56409", + "protocolProfileBehavior": { + "disableBodyPruning": true, + "disabledSystemHeaders": {} + }, + "request": { + "method": "POST", + "header": [ + { + "key": "mock-match", + "value": "34" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks" + }, + "response": [] + }, + { + "name": "Get the task", + "event": [ + { + "listen": "test", + "script": { + "id": "dae8e511-e80d-478b-81ab-2d66dd6fd5b3", + "exec": [ + "pm.test(\"Response time is less than 500ms\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(500);\r", + "});\r", + "\r", + "pm.test(\"Content-Type is present\", function () {\r", + " pm.response.to.have.header(\"Content-Type\");\r", + "});\r", + "\r", + "pm.test(\"Content-Type is application/json\", function () {\r", + " var contentType = pm.response.headers.get('Content-Type');\r", + " pm.expect(contentType).to.include('application/json');\r", + "});\r", + "\r", + "pm.test(\"Response has all required properties\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('task_id');\r", + " pm.expect(jsonData).to.have.property('input');\r", + " pm.expect(jsonData).to.have.property('additional_input');\r", + " pm.expect(jsonData).to.have.property('artifacts');\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript" + } + } + ], + "id": "f84ed7b1-c97a-4968-a14c-88c231b187fa", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}" + }, + "response": [ + { + "id": "ea9d8d69-ed12-4d2c-85d9-f56028bbc628", + "name": "mock response", + "originalRequest": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{taskId}}" + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Sun, 13 Aug 2023 23:23:05 GMT", + "enabled": true + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "enabled": true + }, + { + "key": "Content-Length", + "value": "28", + "enabled": true + }, + { + "key": "Connection", + "value": "keep-alive", + "enabled": true + }, + { + "key": "x-srv-trace", + "value": "v=1;t=daef761e243c402b", + "enabled": true + }, + { + "key": "x-srv-span", + "value": "v=1;s=2c0bd88502372360", + "enabled": true + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*", + "enabled": true + }, + { + "key": "X-RateLimit-Limit", + "value": "120", + "enabled": true + }, + { + "key": "X-RateLimit-Remaining", + "value": "115", + "enabled": true + }, + { + "key": "X-RateLimit-Reset", + "value": "1691968334", + "enabled": true + }, + { + "key": "ETag", + "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", + "enabled": true + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "enabled": true + } + ], + "cookie": [], + "responseTime": null, + "body": "{\n \"input\": \"Write the word 'Washington' to a .txt file\",\n \"additional_input\": null,\n \"task_id\": \"121\",\n \"artifacts\": []\n}" + } + ] + }, + { + "name": "Get all the tasks", + "event": [ + { + "listen": "test", + "script": { + "id": "2e4ee6d8-e5d8-4112-be5c-ec8404f38ef0", + "exec": [ + "pm.test(\"Response time is less than 500ms\", function () {", + " pm.expect(pm.response.responseTime).to.be.below(500);", + "});", + "", + "pm.test(\"Content-Type is present\", function () {", + " pm.response.to.have.header(\"Content-Type\");", + "});", + "", + "pm.test(\"Content-Type is application/json\", function () {", + " var contentType = pm.response.headers.get('Content-Type');", + " pm.expect(contentType).to.include('application/json');", + "});" + ], + "type": "text/javascript" + } + } + ], + "id": "ca583fa3-bec4-4c73-b989-47f3414a8d51", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks" + }, + "response": [] + }, + { + "name": "Create a second task", + "event": [ + { + "listen": "test", + "script": { + "id": "2c8c047b-eec5-4015-86a8-0640f3315ce3", + "exec": [ + "var jsonData = pm.response.json();", + "pm.globals.set(\"lastTaskId\", jsonData.task_id);" + ], + "type": "text/javascript" + } + } + ], + "id": "0de5f275-995e-4eb5-b63c-0f2e8996fab1", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "POST", + "header": [ + { + "key": "mock-match", + "value": "34" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks" + }, + "response": [] + }, + { + "name": "Get all the tasks with Pagination", + "event": [ + { + "listen": "test", + "script": { + "id": "697349ba-26c9-431c-a68d-a1c0efeeaeae", + "exec": [ + "var jsonData = pm.response.json();", + "", + "pm.test(\"Response time is less than 500ms\", function () {", + " pm.expect(pm.response.responseTime).to.be.below(500);", + "});", + "", + "pm.test(\"Content-Type is present\", function () {", + " pm.response.to.have.header(\"Content-Type\");", + "});", + "", + "pm.test(\"Content-Type is application/json\", function () {", + " var contentType = pm.response.headers.get('Content-Type');", + " pm.expect(contentType).to.include('application/json');", + "});", + "", + "pm.test(\"Pagination is set\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.pagination).to.be.an('object');", + "});", + "", + "pm.test(\"Page size is 1\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.pagination.page_size).to.eql(1);", + "});", + "", + "pm.test(\"Items is an array with one item\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.tasks).to.be.an('array').that.has.lengthOf(1);", + "});", + "", + "", + "pm.test(\"Response length respects page_size\", function() {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.tasks.length).to.be.at.most(1);", + "});", + "", + "if (jsonData.items && jsonData.items.length > 0) {", + " if (pm.variables.has(\"lastTaskId\") && pm.variables.get(\"page\") > 1) {", + " pm.test(\"First task of page \" + pm.variables.get(\"page\") + \" is not the last task of page \" + (pm.variables.get(\"page\") - 1), function() {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.items[0].task_id).to.not.equal(pm.variables.get(\"lastTaskId\"));", + " });", + " }", + " if (jsonData.items.length > 0) {", + " pm.variables.set(\"lastTaskId\", jsonData.items[jsonData.items.length - 1].task_id);", + " }", + "}" + ], + "type": "text/javascript" + } + } + ], + "id": "f41e04c1-b266-4e90-909e-ebb0657958d9", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{url}}/ap/v1/agent/tasks?page_size=1¤t_page=1", + "host": ["{{url}}/ap/v1"], + "path": ["agent", "tasks"], + "query": [ + { + "key": "page_size", + "value": "1" + }, + { + "key": "current_page", + "value": "1" + } + ] + } + }, + "response": [] + } + ], + "id": "4b743f46-e582-4565-ac7d-c6446a710ee1", + "description": "Create tasks and consumes them.", + "event": [ + { + "listen": "prerequest", + "script": { + "id": "7f174c78-2068-4351-a64c-bd4c115470e8", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "ef296737-8c06-47ed-90c3-bfe882c1aa40", + "type": "text/javascript", + "exec": [ + "pm.test(\"Response status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] + }, + { + "name": "Artifacts", + "item": [ + { + "name": "Create a new task", + "event": [ + { + "listen": "test", + "script": { + "id": "65ad57a3-b776-4d7c-86f9-be6fbf17cac6", + "exec": [ + "var jsonData = pm.response.json();", + "pm.globals.set(\"task_id\", jsonData.task_id);" + ], + "type": "text/javascript" + } + } + ], + "id": "1ee1eee8-cc2d-43a2-a6e1-7168b0559c45", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks" + }, + "response": [] + }, + { + "name": "Upload Artifact", + "event": [ + { + "listen": "test", + "script": { + "id": "6cf7a2c4-1b8a-4c11-8c1a-6b56b2eb80e6", + "exec": [ + "var jsonData = pm.response.json();", + "pm.globals.set(\"artifact_id\", jsonData.artifact_id);" + ], + "type": "text/javascript" + } + } + ], + "id": "e71fee13-11cb-4b08-a4bb-c9c0f971efed", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "multipart/form-data" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "test_output.txt" + } + ] + }, + "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/artifacts" + }, + "response": [] + }, + { + "name": "Download Artifact", + "event": [ + { + "listen": "test", + "script": { + "id": "82829e0a-f8c7-4925-b28c-0513b018e83e", + "exec": [""], + "type": "text/javascript" + } + } + ], + "id": "b002580c-0d48-42c9-83e0-5fb5eaed38ce", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "mock-match", + "value": "11", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/artifacts/{{artifact_id}}" + }, + "response": [ + { + "id": "ade2dd37-0f84-45f0-881e-2224636a4956", + "name": "mock response", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "mock-match", + "value": "11", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{taskId}}/artifacts/{{artifactId}}" + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "text", + "header": [ + { + "key": "Date", + "value": "Mon, 14 Aug 2023 16:28:55 GMT", + "enabled": true + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "enabled": true + }, + { + "key": "Content-Length", + "value": "275", + "enabled": true + }, + { + "key": "Connection", + "value": "keep-alive", + "enabled": true + }, + { + "key": "x-srv-trace", + "value": "v=1;t=daef761e243c402b", + "enabled": true + }, + { + "key": "x-srv-span", + "value": "v=1;s=2c0bd88502372360", + "enabled": true + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*", + "enabled": true + }, + { + "key": "X-RateLimit-Limit", + "value": "120", + "enabled": true + }, + { + "key": "X-RateLimit-Remaining", + "value": "115", + "enabled": true + }, + { + "key": "X-RateLimit-Reset", + "value": "1691968334", + "enabled": true + }, + { + "key": "ETag", + "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", + "enabled": true + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "enabled": true + } + ], + "cookie": [], + "responseTime": null, + "body": "Washington" + } + ] + } + ], + "id": "48e93b55-d463-452f-9d56-f59ee5e95060", + "description": "Create Artifacts and consumes them.", + "event": [ + { + "listen": "prerequest", + "script": { + "id": "61be92ef-c1cd-4f2a-a77c-78e5913ea299", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "7dcedb1e-4f42-48b1-ac9b-fd3b842b428b", + "type": "text/javascript", + "exec": [ + "pm.test(\"Response status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "7dd8bbee-357a-44fd-b891-46bcc3a8a41c", + "type": "text/javascript", + "exec": [""] + } + }, + { + "listen": "test", + "script": { + "id": "b5f22749-138a-45fe-b6c7-79fe5cf06017", + "type": "text/javascript", + "exec": [""] + } + } + ] +} diff --git a/testing_suite/contract_tests.json b/testing_suite/contract_tests.json index fc62fe70..b2dcab21 100644 --- a/testing_suite/contract_tests.json +++ b/testing_suite/contract_tests.json @@ -1,1553 +1,1516 @@ { - "info": { - "_postman_id": "03c5c4b6-9071-4ab4-b5a1-4bda97a3b3aa", - "name": "Contract Test Generator", - "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" - }, - "item": [ - { - "name": "API Validation", - "item": [ - { - "name": "Cleanup Previous Run", - "event": [ - { - "listen": "prerequest", - "script": { - "id": "6e0312e1-1276-47b2-92be-b481545de5fb", - "exec": [ - "// See https://blog.postman.com/2019/05/28/pro-tip-dynamically-unset-postman-environment-variables/\r", - "// for more details on what we're doing here. \r", - "\r", - "cleanupCollectionVariables();\r", - "\r", - "function cleanupCollectionVariables() {\r", - " const clean = _.keys(pm.collectionVariables.toObject());\r", - "\r", - " _.each(clean, (arrItem) => {\r", - " pm.collectionVariables.unset(arrItem);\r", - " });\r", - "}" - ], - "type": "text/javascript" - } - }, - { - "listen": "test", - "script": { - "id": "acaabbd4-94fc-4444-8718-b9ca2c087721", - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "id": "30368860-aef9-46d6-ad44-8fac61b8f842", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "url": "https://postman-echo.com/delay/0" - }, - "response": [] - }, - { - "name": "Initialize", - "event": [ - { - "listen": "test", - "script": { - "id": "d15a1e94-1145-4006-b514-b5300671da90", - "exec": [ - "var envSchema = null\r", - "if (pm.environment.get(\"env-openapi-json-url\")){\r", - " envSchema = JSON.stringify(pm.response.json());\r", - "}\r", - "\r", - "const providedSchema = pm.environment.get('env-schema') || envSchema;\r", - "if(providedSchema){\r", - " let success = true;\r", - " try{\r", - " const yaml = pm.environment.get('env-jsonToYaml');\r", - " (new Function(yaml))();\r", - "\r", - " const schema = jsyaml.load(providedSchema);\r", - " pm.collectionVariables.set('coll-schema', JSON.stringify(schema));\r", - " postman.setNextRequest('Get API Base Url');\r", - " }\r", - " catch(err){\r", - " console.log(err);\r", - " success = false;\r", - " postman.setNextRequest(null);\r", - " }\r", - "\r", - " pm.test('Successfully converted provided schema', function(){\r", - " pm.expect(success).to.be.true;\r", - " }); \r", - "}" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "id": "9e1c1f12-58f5-4cea-b6c0-58be6133c033", - "exec": [ - "if (pm.environment.get(\"env-openapi-json-url\")){", - " pm.request.url = pm.environment.get(\"env-openapi-json-url\");", - "}" - ], - "type": "text/javascript" - } - } - ], - "id": "367b4112-337b-4bc0-821d-4687694559ad", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "url": "https://postman-echo.com/delay/0" - }, - "response": [] - }, - { - "name": "Validate API In Workspace", - "event": [ - { - "listen": "test", - "script": { - "id": "c7c74561-6423-4fbe-ab30-bd66746c6cdf", - "exec": [ - "const minApiCount = Number(pm.environment.get('env-minApiCount'));\r", - "const maxApiCount = Number(pm.environment.get('env-maxApiCount'));\r", - "const jsonData = pm.response.json();\r", - "\r", - "pm.test(`Workspace API count is between ${minApiCount} and ${maxApiCount}. (Count: ${jsonData.apis.length})`, function () { \r", - " pm.expect(jsonData.apis.length).to.be.at.least(minApiCount); \r", - " pm.expect(jsonData.apis.length).to.be.at.most(maxApiCount);\r", - "});\r", - "\r", - "let apiIds = [];\r", - "_.forEach(jsonData.apis, function(api){\r", - " apiIds.push(api.id);\r", - "});\r", - "\r", - "pm.collectionVariables.set('coll-apiIds', JSON.stringify(apiIds));" - ], - "type": "text/javascript" - } - } - ], - "id": "1baa5ceb-08b6-47c7-9112-8f54c039805d", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "X-Api-Key", - "value": "{{env-apiKey}}", - "type": "text" - } - ], - "url": { - "raw": "https://api.getpostman.com/apis?workspace={{env-workspaceId}}", - "protocol": "https", - "host": [ - "api", - "getpostman", - "com" - ], - "path": [ - "apis" - ], - "query": [ - { - "key": "workspace", - "value": "{{env-workspaceId}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get Current API Version", - "event": [ - { - "listen": "test", - "script": { - "id": "683d7e4f-8336-41a1-b14c-5893b3e49fba", - "exec": [ - "const jsonData = pm.response.json();\r", - "\r", - "pm.test('API has one or more versions', function(){\r", - " pm.expect(jsonData).to.have.property('versions').and.to.be.an('array');\r", - " pm.expect(jsonData.versions.length).to.be.above(0);\r", - "});\r", - "\r", - "const version = jsonData.versions[0];\r", - "pm.collectionVariables.set('coll-versionId', version.id);" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "id": "ae6bfbce-aad4-4f31-9b82-ba6f666658a5", - "exec": [ - "let apiIds = pm.collectionVariables.get('coll-apiIds');\r", - "if(apiIds){\r", - " apiIds = JSON.parse(apiIds);\r", - " const apiId = apiIds.pop();\r", - "\r", - " pm.collectionVariables.set('coll-apiId', apiId);\r", - " pm.collectionVariables.set('coll-apiIds', JSON.stringify(apiIds));\r", - "}\r", - "else {\r", - " pm.request.url = 'https://postman-echo.com/delay/0'\r", - " pm.request.name = 'No APIs found in the workspace. Skipping execution';\r", - " postman.setNextRequest(null);\r", - "}" - ], - "type": "text/javascript" - } - } - ], - "id": "37810e31-7b06-4ca4-a422-d0012834d1e4", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "X-Api-Key", - "value": "{{env-apiKey}}", - "type": "text" - } - ], - "url": { - "raw": "https://api.getpostman.com/apis/:apiId/versions", - "protocol": "https", - "host": [ - "api", - "getpostman", - "com" - ], - "path": [ - "apis", - ":apiId", - "versions" - ], - "query": [ - { - "key": null, - "value": "", - "disabled": true - } - ], - "variable": [ - { - "id": "66de574b-c196-407e-9be7-ff53c0dac927", - "key": "apiId", - "value": "{{coll-apiId}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get Current API Schema", - "event": [ - { - "listen": "test", - "script": { - "id": "38beef82-f213-44cc-9a11-c326fb5b003d", - "exec": [ - "const jsonData = pm.response.json();\r", - "\r", - "pm.test('Has schema for current version', function(){\r", - " pm.expect(jsonData).to.have.property('version');\r", - " pm.expect(jsonData.version).to.have.property('schema').and.to.be.an('array');\r", - " pm.expect(jsonData.version.schema.length).to.be.above(0);\r", - "\r", - " pm.collectionVariables.set('coll-schemaId', jsonData.version.schema[0]);\r", - "});" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "id": "00af0146-94da-4674-96cc-9feff4ba493f", - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "id": "4ecaeaa0-5908-497b-ae55-045465cb47e4", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "X-Api-Key", - "type": "text", - "value": "{{env-apiKey}}" - } - ], - "url": { - "raw": "https://api.getpostman.com/apis/:apiId/versions/:versionId", - "protocol": "https", - "host": [ - "api", - "getpostman", - "com" - ], - "path": [ - "apis", - ":apiId", - "versions", - ":versionId" - ], - "query": [ - { - "key": null, - "value": "", - "disabled": true - } - ], - "variable": [ - { - "id": "ab65af1f-47bf-463c-b68b-b2a2f1d70081", - "key": "apiId", - "value": "{{coll-apiId}}" - }, - { - "id": "82912cc7-9ae0-4090-9758-42c81f4b6924", - "key": "versionId", - "value": "{{coll-versionId}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get API Schema", - "event": [ - { - "listen": "test", - "script": { - "id": "6f74f9f2-f92e-4993-b0be-f4181d16e01e", - "exec": [ - "try {\r", - " const jsonData = pm.response.json();\r", - " if(jsonData.schema.language.toLowerCase() == 'json'){\r", - " pm.test('Schema is JSON', function(){\r", - " pm.expect(1).to.equal(1);\r", - " pm.collectionVariables.set('coll-schema', jsonData.schema.schema);\r", - " });\r", - " } else {\r", - " pm.test('Schema translates to JSON', function(){\r", - " try{\r", - " const yaml = pm.environment.get('env-jsonToYaml');\r", - " (new Function(yaml))();\r", - "\r", - " const schema = jsyaml.load(jsonData.schema.schema);\r", - " pm.collectionVariables.set('coll-schema', JSON.stringify(schema));\r", - " pm.expect(1).to.equal(1);\r", - " }\r", - " catch(err){\r", - " pm.expect(`${err.name} - ${err.message}`).to.equal(undefined);\r", - " } \r", - " });\r", - " }\r", - "}\r", - "catch(err) {\r", - " console.log(err);\r", - " pm.test('Unable to load schema', function(){\r", - " pm.expect(0).to.equal(1);\r", - " postman.setNextRequest(null);\r", - " })\r", - "}" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "id": "7fb06c94-c343-47f8-8308-59170e3c56b8", - "exec": [ - "if (pm.environment.get(\"env-openapi-json-url\")){", - " pm.request.url = pm.environment.get(\"env-openapi-json-url\");", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "id": "1b67ec27-0a68-4755-b118-43190677c99d", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "X-Api-Key", - "value": "{{env-apiKey}}", - "type": "text" - } - ], - "url": { - "raw": "https://api.getpostman.com/apis/:apiId/versions/:apiVersionId/schemas/:schemaId", - "protocol": "https", - "host": [ - "api", - "getpostman", - "com" - ], - "path": [ - "apis", - ":apiId", - "versions", - ":apiVersionId", - "schemas", - ":schemaId" - ], - "variable": [ - { - "id": "ebe1d781-f0eb-4e96-847e-0f42e2ac15ac", - "key": "apiId", - "value": "{{coll-apiId}}" - }, - { - "id": "3bd638bb-503b-4e2b-8da2-ebd9f5d8a4d4", - "key": "apiVersionId", - "value": "{{coll-versionId}}" - }, - { - "id": "521e1c1e-e161-478c-89ba-1b079435201b", - "key": "schemaId", - "value": "{{coll-schemaId}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get API Base Url", - "event": [ - { - "listen": "test", - "script": { - "id": "5876b636-91d2-405b-a0e6-8bd4e2132969", - "exec": [ - "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", - "const server = pm.environment.get('env-server');\r", - "\r", - "pm.test('Environment has test server defined', function () {\r", - " pm.expect(server).to.not.be.undefined;\r", - "});\r", - "\r", - "pm.test('Schema has server/baseUrl defined', function () {\r", - " const servers = schema.servers;\r", - " pm.expect(servers).to.not.be.undefined;\r", - " const serverToTest = servers.find(s => s.description.toLowerCase() == server.toLowerCase());\r", - " pm.expect(serverToTest).to.not.be.undefined;\r", - "\r", - " pm.expect(serverToTest).to.have.property('url');\r", - " pm.collectionVariables.set('coll-baseUrl', serverToTest.url);\r", - "});\r", - "\r", - "const runComponentTests = pm.environment.get('env-runComponentTests') == 'true';\r", - "if(!runComponentTests){ \r", - " const runContractTests = pm.environment.get('env-runContractTests') == 'true';\r", - " if(runContractTests){\r", - " postman.setNextRequest('Build Schema Tests');\r", - " } else {\r", - " postman.setNextRequest('More APIs to Process?');\r", - " } \r", - "}" - ], - "type": "text/javascript" - } - } - ], - "id": "c8711439-c69a-4420-b034-cb58dc21e110", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [], - "url": "https://postman-echo.com/delay/0" - }, - "response": [] - } - ], - "id": "2778dde1-f785-4aba-84cc-57e1102bff23" - }, - { - "name": "Components", - "item": [ - { - "name": "Verify Component Adherence", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", - "\r", - "const requireParamDescription = Boolean(pm.environment.get('env-requireParamDescription'));\r", - "const requireParamExample = Boolean(pm.environment.get('env-requireParamExample'));\r", - "\r", - "let paramDescriptionMinLength = pm.environment.get('env-paramDescriptionMinLength');\r", - "if (paramDescriptionMinLength) {\r", - " paramDescriptionMinLength = Number(paramDescriptionMinLength);\r", - "}\r", - "\r", - "let paramDescriptionMaxLength = pm.environment.get('env-paramDesciptionMaxLength');\r", - "if (paramDescriptionMaxLength) {\r", - " paramDescriptionMaxLength = Number(paramDescriptionMaxLength);\r", - "}\r", - "\r", - "var testedSchemaRefs = [];\r", - "\r", - "if (schema.components.parameters) {\r", - " for (let prop in schema.components.parameters) {\r", - " let parameter = schema.components.parameters[prop];\r", - "\r", - " pm.test(`Parameter '${prop}' starts with a lowercase letter`, function () {\r", - " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toLowerCase());\r", - " });\r", - "\r", - " if (requireParamDescription) {\r", - " pm.test(`Parameter '${prop}' has a description between ${paramDescriptionMinLength} and ${paramDescriptionMaxLength} characters`, function () {\r", - " pm.expect(parameter).to.have.property('description').and.to.be.a('string');\r", - " pm.expect(parameter.description.length).to.be.at.least(paramDescriptionMinLength);\r", - " pm.expect(parameter.description.length).to.be.at.most(paramDescriptionMaxLength);\r", - " });\r", - " }\r", - "\r", - " if (requireParamExample) {\r", - " pm.test(`Parameter '${prop}' has an example`, function () {\r", - " pm.expect(parameter).to.have.property('schema');\r", - " pm.expect(parameter.schema).to.have.property('example');\r", - " });\r", - " }\r", - " }\r", - "}\r", - "\r", - "if (schema.components.schemas) {\r", - " for (let prop in schema.components.schemas) {\r", - " pm.test(`Schema '${prop}' begins with an uppercase letter`, function () {\r", - " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toUpperCase());\r", - " });\r", - "\r", - " const testedSchema = testedSchemaRefs.find(tsr => tsr == prop);\r", - " if (!testedSchema) {\r", - " const schemaObject = schema.components.schemas[prop];\r", - " testSchemaObject(schema, schemaObject, prop);\r", - " testedSchemaRefs.push(prop);\r", - " }\r", - " }\r", - "}\r", - "\r", - "if (schema.components.responses) {\r", - " for (let prop in schema.components.responses) {\r", - " pm.test(`Response '${prop}' begins with an uppercase letter`, function () {\r", - " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toUpperCase());\r", - " });\r", - "\r", - " if (requireParamDescription) {\r", - " const response = schema.components.responses[prop];\r", - " pm.test(`Response '${prop}' has a description between ${paramDescriptionMinLength} and ${paramDescriptionMaxLength} characters`, function () {\r", - " pm.expect(response).to.have.property('description').and.to.be.a('string');\r", - " pm.expect(response.description.length).to.be.at.least(paramDescriptionMinLength);\r", - " pm.expect(response.description.length).to.be.at.most(paramDescriptionMaxLength);\r", - " });\r", - " }\r", - " }\r", - "}\r", - "\r", - "const runContractTests = pm.environment.get('env-runContractTests') == 'true';\r", - "if (runContractTests) {\r", - " postman.setNextRequest('Build Schema Tests');\r", - "} else {\r", - " postman.setNextRequest('More APIs to Process?');\r", - "}\r", - "\r", - "\r", - "function testSchemaObject(schema, object, objectName) {\r", - " if (object.type && object.type.toLowerCase() == 'object') {\r", - " if (object.required) {\r", - " for (let i = 0; i < object.required.length; i++) {\r", - " const requiredProp = object.required[i];\r", - " pm.test(`Schema '${objectName}' has required property '${requiredProp}' defined`, function () {\r", - " pm.expect(object.properties).to.have.property(requiredProp);\r", - " });\r", - " }\r", - " }\r", - "\r", - " let schemaPropertyExceptions = [];\r", - " if (pm.environment.has('env-schemaPropertyExceptions')) {\r", - " schemaPropertyExceptions = JSON.parse(pm.environment.get('env-schemaPropertyExceptions'));\r", - " }\r", - "\r", - " for (let prop in object.properties) {\r", - " const property = object.properties[prop];\r", - "\r", - " if (!schemaPropertyExceptions.some(pe => pe === prop)) {\r", - " pm.test(`Schema property '${objectName}.${prop}' is lowercase`, function () {\r", - " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toLowerCase());\r", - " });\r", - " }\r", - "\r", - " if (property.type && property.type.toLowerCase() == 'object') {\r", - " testSchemaObject(schema, property, `${objectName}.${prop}`);\r", - " }\r", - " else if (property.type && property.type.toLowerCase() == 'array') {\r", - " testSchemaObject(schema, property, `${objectName}.${prop}(list)`);\r", - " }\r", - " else if (property.oneOf) {\r", - " _.forEach(property.oneOf, (oneOf, i) => {\r", - " testSchemaObject(schema, oneOf, `${objectName}.${prop}(oneOf).${i}`)\r", - " });\r", - " }\r", - " else if (property.allOf) {\r", - " _.forEach(property.allOf, (allOf, i) => {\r", - " testSchemaObject(schema, allOf, `${objectName}.${prop}(allOf).${i}`)\r", - " });\r", - " }\r", - " else if (property.anyOf) {\r", - " _.forEach(property.anyOf, (anyOf, i) => {\r", - " testSchemaObject(schema, anyOf, `${objectName}.${prop}(anyOf).${i}`)\r", - " });\r", - " }\r", - " else {\r", - " if (requireParamDescription && !property.$ref) {\r", - " pm.test(`Schema property '${objectName}.${prop}' has a description between ${paramDescriptionMinLength} and ${paramDescriptionMaxLength} characters`, function () {\r", - " pm.expect(property).to.have.property('description').and.to.be.a('string');\r", - " pm.expect(property.description.length).to.be.at.least(paramDescriptionMinLength);\r", - " pm.expect(property.description.length).to.be.at.most(paramDescriptionMaxLength);\r", - " });\r", - "\r", - " if (property.description) {\r", - " pm.test(`Schema property '${objectName}.${prop}' description is not just the name`, function () {\r", - " pm.expect(prop.toLowerCase()).to.not.equal(property.description.toLowerCase());\r", - " });\r", - " }\r", - " }\r", - "\r", - " if (requireParamExample && !property.$ref) {\r", - " pm.test(`Schema property '${objectName}.${prop}' has an example`, function () {\r", - " pm.expect(property).to.have.property('example');\r", - " });\r", - " }\r", - " }\r", - " }\r", - " }\r", - " else if (object.type && object.type.toLowerCase() == 'array') {\r", - " pm.test(`Schema '${objectName}' has items defined`, function () {\r", - " pm.expect(object).to.have.property('items');\r", - " });\r", - "\r", - " testSchemaObject(schema, object.items, `${objectName}.list`);\r", - " }\r", - " else if (object.oneOf) {\r", - " handleSchemaArray(schema, object, objectName, 'oneOf');\r", - " } else if (object.allOf) {\r", - " handleSchemaArray(schema, object, objectName, 'allOf');\r", - " }\r", - " else if (object.anyOf) {\r", - " handleSchemaArray(schema, object, objectName, 'anyOf');\r", - " }\r", - " else if (object.$ref) {\r", - " const name = getName(object.$ref);\r", - " const testedRef = testedSchemaRefs.find(tsr => tsr == name);\r", - " if (!testedRef) {\r", - " testSchemaObject(schema, schema.components.schemas[name], objectName);\r", - " testedSchemaRefs.push(name);\r", - " }\r", - " }\r", - " else {\r", - " pm.test(`Schema '${objectName}' has a declared type`, function () {\r", - " pm.expect(object).to.have.property('type');\r", - " });\r", - " }\r", - "}\r", - "\r", - "function handleSchemaArray(schema, object, objectName, arrayType) {\r", - " for (let i = 0; i < object[arrayType].length; i++) {\r", - " const arraySchema = object[arrayType][i];\r", - " if (arraySchema.$ref) {\r", - " const name = getName(arraySchema.$ref);\r", - " const testedRef = testedSchemaRefs.find(tsr => tsr == name);\r", - " if (!testedRef) {\r", - " testSchemaObject(schema, schema.components.schemas[name], `${objectName}[${i}](ref ${name})`);\r", - " testedSchemaRefs.push(name);\r", - " }\r", - " }\r", - " else {\r", - " testSchemaObject(schema, arraySchema, `${objectName}[${i}]`);\r", - " }\r", - " }\r", - "}\r", - "\r", - "function getName(ref) {\r", - " let pieces = ref.split('/');\r", - " return pieces[pieces.length - 1];\r", - "}\r", - "" - ], - "type": "text/javascript", - "id": "968b2ac8-c423-42d7-ab68-809e0292ab31" - } - } - ], - "id": "46526b39-701b-4f22-a0a3-03ec53319450", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [], - "url": "https://postman-echo.com/delay/0" - }, - "response": [] - } - ], - "id": "18e4b0a6-d641-4661-9a29-918247d63f5f" - }, - { - "name": "Contract Tests", - "item": [ - { - "name": "Build Schema Tests", - "event": [ - { - "listen": "prerequest", - "script": { - "exec": [ - "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", - "\r", - "let schemaTests = [];\r", - "for (let prop in schema.paths) {\r", - " const pathName = prop;\r", - " let path = {\r", - " path: `${pm.collectionVariables.get('coll-baseUrl')}${pathName}`,\r", - " parameters: schema.paths[prop].parameters,\r", - " };\r", - "\r", - " for (let method in schema.paths[prop]) {\r", - " if (method.toLowerCase() == 'parameters' || isMockEndpoint(schema.paths[prop][method])) {\r", - " continue;\r", - " }\r", - "\r", - " let currentPath = _.cloneDeep(path);\r", - " currentPath.method = method.toUpperCase();\r", - " let pathMethod = schema.paths[prop][method];\r", - " currentPath.parameters = combineParameters(currentPath.parameters, pathMethod.parameters);\r", - " let securityExtension = pm.environment.get('env-securityExtensionName');\r", - " if (securityExtension && pathMethod[securityExtension] && pathMethod[securityExtension].length > 0) {\r", - " currentPath.allowedRole = pathMethod[securityExtension][0];\r", - " }\r", - "\r", - " const expectedResponses = getExpectedResponses(pathMethod);\r", - " currentPath.responses = expectedResponses;\r", - "\r", - " if (pathMethod.requestBody) {\r", - " let bodyModel;\r", - " if (pathMethod.requestBody.content['application/json']?.schema?.$ref) {\r", - " bodyModel = getSchemaReference(schema, pathMethod.requestBody.content['application/json'].schema.$ref);\r", - " }\r", - " else if (pathMethod.requestBody.content['application/json']?.schema) {\r", - " bodyModel = pathMethod.requestBody.content['application/json'].schema;\r", - " }\r", - " else {\r", - " continue;\r", - " }\r", - "\r", - " const models = buildModels(schema, bodyModel);\r", - " const mutations = buildModelMutations(models);\r", - "\r", - " mutations.forEach((mutation) => {\r", - " let schemaTest = _.cloneDeep(currentPath);\r", - " Object.assign(schemaTest, mutation);\r", - " schemaTest.name = `${schemaTest.method} - ${pathName} - ${schemaTest.description} - SUCCESS: ${schemaTest.success}`;\r", - " schemaTests.push(schemaTest);\r", - " });\r", - " }\r", - " else {\r", - " currentPath.name = `${currentPath.method} - ${pathName} - No Request Body - SUCCESS: true`;\r", - " currentPath.success = true;\r", - " schemaTests.push(currentPath);\r", - " }\r", - " }\r", - "}\r", - "schemaTests = moveDeleteEndpointsToEnd(schemaTests);\r", - "pm.collectionVariables.set('coll-schemaTests', JSON.stringify(schemaTests));\r", - "\r", - "// \r", - "// Move delete endpoints to the end for cleanup\r", - "//\r", - "function moveDeleteEndpointsToEnd(schemaTests) {\r", - " let sortedTests = [...schemaTests];\r", - " try {\r", - " let successfulDeletes = sortedTests.filter(schemaTest => schemaTest.method == 'DELETE' && schemaTest.success);\r", - "\r", - " if (successfulDeletes) {\r", - " // order deletes from the deepest entity to highest level entity based on path\r", - " successfulDeletes.sort((a, b) => b.path.split('/').length - a.path.split('/').length);\r", - " sortedTests = sortedTests.filter(schemaTest => !successfulDeletes.find(sd => sd == schemaTest));\r", - " sortedTests = sortedTests.concat(successfulDeletes);\r", - " }\r", - " }\r", - " catch (err) {\r", - " console.log('An error occurred when sorting delete tests', err);\r", - " }\r", - "\r", - " return sortedTests;\r", - "}\r", - "\r", - "//\r", - "// Supporting Methods Below\r", - "//\r", - "function buildModels(schema, object) {\r", - " let models = [];\r", - "\r", - " if (object['$ref']) {\r", - " object = getSchemaReference(schema, object['$ref']);\r", - " }\r", - "\r", - " if (object.type && object.type.toLowerCase() == 'object') {\r", - " if (object.required && object.required.length > 0) {\r", - " models.push({});\r", - " _.forEach(object.required, function (param) {\r", - " const property = object.properties[param];\r", - "\r", - " if (property.type && ['string', 'number', 'integer', 'boolean'].includes(property.type.toLowerCase())) {\r", - " for (let modelIndex = 0; modelIndex < models.length; modelIndex++) {\r", - " let model = models[modelIndex];\r", - " model[param] = property.example;\r", - " }\r", - " }\r", - " else {\r", - " const nestedObjects = buildModels(schema, property);\r", - " models = addToModels(models, nestedObjects, param);\r", - " }\r", - " });\r", - " }\r", - "\r", - " if (object.minProperties) {\r", - " _.forEach(models, function (model) {\r", - " if (Object.keys(model).length < object.minProperties) {\r", - " for (let i = Object.keys(model).length; i < object.minProperties; i++) {\r", - " for (const [key, value] of Object.entries(object.properties)) {\r", - " if (['string', 'number', 'integer', 'boolean'].includes(value.type.toLowerCase()) && model[key] == undefined) {\r", - " model[key] = value.example;\r", - " break;\r", - " }\r", - " }\r", - " }\r", - " }\r", - " })\r", - " }\r", - " }\r", - " else if (object.type && object.type.toLowerCase() == 'array') {\r", - " let items = buildModels(schema, object.items);\r", - " if (Array.isArray(items)) {\r", - " for (let i = 0; i < items.length; i++) {\r", - " models.push([items[i]]);\r", - " }\r", - " }\r", - " else {\r", - " models.push([items]);\r", - " }\r", - " }\r", - " else if (object.oneOf) {\r", - " _.forEach(object.oneOf, function (component) {\r", - " let items = buildModels(schema, component);\r", - " models = models.concat(items);\r", - " });\r", - " }\r", - " else if (object.allOf) {\r", - " let pieces = [{}];\r", - " _.forEach(object.allOf, function (component) {\r", - " let componentModels = buildModels(schema, component);\r", - " pieces = addToModels(pieces, componentModels);\r", - " });\r", - "\r", - " models = pieces;\r", - " }\r", - " else if (object.anyOf) {\r", - " let pieces = [];\r", - " let combinedPieces = [{}];\r", - " _.forEach(object.anyOf, function (component) {\r", - " let componentModels = buildModels(schema, component);\r", - " combinedPieces = addToModels(combinedPieces, componentModels);\r", - " pieces = pieces.concat(componentModels);\r", - " });\r", - "\r", - " models = pieces.concat(combinedPieces);\r", - " }\r", - " else {\r", - " // All other options are primitive values\r", - " return object.example;\r", - " }\r", - " return models;\r", - "}\r", - "\r", - "function getSchemaReference(schema, referenceName) {\r", - " const refPieces = referenceName.split('/');\r", - " let reference = schema;\r", - " for (let i = 1; i < refPieces.length; i++) {\r", - " reference = reference[refPieces[i]];\r", - " }\r", - "\r", - " return reference;\r", - "}\r", - "\r", - "function addToModels(models, newPieces, name) {\r", - " let newModels = [];\r", - " _.forEach(models, function (model) {\r", - " _.forEach(newPieces, function (newPiece) {\r", - " let newModel = _.cloneDeep(model);\r", - " if (name) {\r", - " newModel[name] = newPiece;\r", - " }\r", - " else {\r", - " Object.assign(newModel, newPiece);\r", - " }\r", - " newModels.push(newModel);\r", - " });\r", - " });\r", - "\r", - " return newModels;\r", - "}\r", - "\r", - "function buildModelMutations(models) {\r", - " let modelMutations = [];\r", - " _.forEach(models, function (model) {\r", - " addMutation(true, 'Has all required fields', model, modelMutations);\r", - " let mutations = buildMutation(model);\r", - " modelMutations = modelMutations.concat(mutations);\r", - " });\r", - "\r", - " return modelMutations;\r", - "}\r", - "\r", - "function buildMutation(model) {\r", - " let mutations = [];\r", - "\r", - " for (const [key, value] of Object.entries(model)) {\r", - " if (typeof value == 'object') {\r", - " let nestedMutations = buildMutation(value);\r", - " nestedMutations.forEach((nestedMutation) => {\r", - " let mutation = _.cloneDeep(model);\r", - " mutation[key] = nestedMutation.body;\r", - " addMutation(false, `${nestedMutation.description} in ${key} object`, mutation, mutations);\r", - " });\r", - "\r", - " let mutation = _.cloneDeep(model);\r", - " delete mutation[key];\r", - " addMutation(false, `Missing ${key} object`, mutation, mutations);\r", - "\r", - " let emptyMutation = _.cloneDeep(model);\r", - " emptyMutation[key] = {};\r", - " addMutation(false, `Empty ${key} object`, emptyMutation, mutations);\r", - " }\r", - " else {\r", - " if (Array.isArray(value)) {\r", - " console.log('probably an error');\r", - " }\r", - " let mutation = _.cloneDeep(model);\r", - " delete mutation[key];\r", - " addMutation(false, `Missing ${key} property`, mutation, mutations);\r", - "\r", - " let blankMutation = _.cloneDeep(model);\r", - " blankMutation[key] = '';\r", - " addMutation(false, `Blank ${key} property`, blankMutation, mutations);\r", - " }\r", - " }\r", - "\r", - " return mutations;\r", - "}\r", - "\r", - "function addMutation(isSuccess, description, mutation, mutations) {\r", - " mutations.push({\r", - " success: isSuccess,\r", - " description: description,\r", - " body: mutation\r", - " });\r", - "}\r", - "\r", - "function getExpectedResponses(pathMethod) {\r", - " const responses = [];\r", - " for (const [statusCode, value] of Object.entries(pathMethod.responses)) {\r", - " let response = {\r", - " statusCode: Number(statusCode)\r", - " };\r", - "\r", - " if (value['x-postman-variables'] && Array.isArray(value['x-postman-variables'])) {\r", - " response.variables = value['x-postman-variables'].filter(variable => variable.type.toLowerCase() === 'save');\r", - " }\r", - "\r", - " if (value.$ref) {\r", - " response.$ref = value.$ref;\r", - " }\r", - " else {\r", - " if (value.content?.['application/json']?.schema) {\r", - " if (value.content['application/json'].schema.$ref) {\r", - " response.$ref = value.content['application/json'].schema.$ref;\r", - " }\r", - " else {\r", - " response.schema = value.content['application/json'].schema;\r", - " }\r", - " }\r", - " }\r", - "\r", - " responses.push(response);\r", - " }\r", - " return responses;\r", - "}\r", - "\r", - "function isMockEndpoint(pathMethod) {\r", - " let isMock = false;\r", - " if (pathMethod && pathMethod['x-amazon-apigateway-integration'] && pathMethod['x-amazon-apigateway-integration'].type\r", - " && pathMethod['x-amazon-apigateway-integration'].type.toLowerCase() == 'mock') {\r", - " isMock = true;\r", - " }\r", - "\r", - " return isMock;\r", - "}\r", - "\r", - "function combineParameters(endpointParameters, methodParameters) {\r", - " if (!endpointParameters && !methodParameters) {\r", - " return;\r", - " }\r", - " let parameters = [];\r", - " if (endpointParameters && endpointParameters.length) {\r", - " parameters = [...endpointParameters];\r", - " }\r", - "\r", - " if (methodParameters && methodParameters.length) {\r", - " parameters = [...parameters, ...methodParameters];\r", - " }\r", - "\r", - " return parameters;\r", - "}" - ], - "type": "text/javascript", - "id": "3017117f-1119-4cda-8de9-f181be051dff" - } - }, - { - "listen": "test", - "script": { - "exec": [ - "let schemaTests = pm.collectionVariables.get('coll-schemaTests');\r", - "if(schemaTests){\r", - " schemaTests = JSON.parse(schemaTests);\r", - " if(!schemaTests || !schemaTests.length){\r", - " postman.setNextRequest('More APIs to Process?');\r", - " }\r", - "}" - ], - "type": "text/javascript", - "id": "161ed627-1e43-4d10-923c-308e9b039bd9" - } - } - ], - "id": "bd6e30f9-999f-4ace-91ca-270b0bd1f1a5", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [], - "url": "https://postman-echo.com/delay/0" - }, - "response": [] - }, - { - "name": "Test Request", - "event": [ - { - "listen": "prerequest", - "script": { - "id": "49ce35ea-de86-4a14-b90e-3d396e2feb2d", - "exec": [ - "const url = require('url');\r", - "\r", - "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", - "let schemaTests = JSON.parse(pm.collectionVariables.get('coll-schemaTests'));\r", - "\r", - "const schemaTest = schemaTests.shift();\r", - "pm.collectionVariables.set('coll-schemaTests', JSON.stringify(schemaTests));\r", - "pm.variables.set('currentSchemaTest', JSON.stringify(schemaTest));\r", - "\r", - "const path = replacePathParameters(schema, schemaTest.path, schemaTest.parameters);\r", - "pm.request.url.update(path);\r", - "delete pm.request.url.auth;\r", - "delete pm.request.url.port;\r", - "delete pm.request.url.hash;\r", - "if (pm.request.url.protocol) {\r", - " pm.request.url.protocol = pm.request.url.protocol.replace(/\\:$/, '');\r", - "} else {\r", - " pm.request.url.protocol = 'https';\r", - "}\r", - "pm.request.method = schemaTest.method;\r", - "pm.request.name = schemaTest.name;\r", - "\r", - "pm.variables.set('requestName', schemaTest.name);\r", - "pm.variables.set('body', JSON.stringify(schemaTest.body));\r", - "\r", - "// Add top level parameters from the path\r", - "const roleHeaderName = pm.environment.get('env-roleHeaderName');\r", - "\r", - "if (schemaTest.parameters) {\r", - " for (let i = 0; i < schemaTest.parameters.length; i++) {\r", - " let param = schemaTest.parameters[i];\r", - "\r", - " if (param.$ref) {\r", - " let pieces = param.$ref.split('/');\r", - " const name = pieces[pieces.length - 1];\r", - " const schemaParam = schema.components.parameters[name];\r", - " const paramType = schemaParam.in.toLowerCase();\r", - " const paramValue = loadParameterValue(schemaParam);\r", - " if (paramType == 'header' && schemaParam.required == true) {\r", - " if (roleHeaderName && schemaParam.name.toLowerCase() == roleHeaderName.toLowerCase()) {\r", - " pm.request.headers.upsert({ key: schemaParam.name, value: schemaTest.allowedRole });\r", - " }\r", - " else {\r", - " pm.request.headers.upsert({ key: schemaParam.name, value: paramValue });\r", - " }\r", - " } else if (paramType == 'query' && schemaParam.required == true) {\r", - " pm.request.url.query.upsert({ key: schemaParam.name, value: paramValue });\r", - " }\r", - " } else {\r", - " const paramType = param.in.toLowerCase();\r", - " const paramValue = loadParameterValue(param);\r", - " if (paramType == 'header') {\r", - " pm.request.headers.upsert({ key: param.name, value: paramValue });\r", - " } else if (paramType == 'query' && param.required == true) {\r", - " pm.request.url.query.upsert({ key: param.name, value: paramValue });\r", - " }\r", - " }\r", - " }\r", - "}\r", - "\r", - "function loadParameterValue(parameter) {\r", - " let parameterValue;\r", - " if (parameter['x-postman-variables']) {\r", - " let variable = parameter['x-postman-variables'].find(v => v.type.toLowerCase() === 'load');\r", - " if (variable && pm.collectionVariables.has(variable.name)) {\r", - " parameterValue = pm.collectionVariables.get(variable.name);\r", - " }\r", - " else {\r", - " parameterValue = resolveParameterExample(parameter);\r", - " }\r", - " }\r", - " else {\r", - " parameterValue = resolveParameterExample(parameter);\r", - " }\r", - "\r", - " return parameterValue;\r", - "}\r", - "\r", - "function resolveParameterExample(parameter) {\r", - " let paramValue = (parameter.schema.example != undefined) ? parameter.schema.example : parameter.example;\r", - " let value = paramValue;\r", - " if (typeof paramValue !== 'number' && typeof paramValue !== 'boolean') {\r", - " let pathVariableRegex = /^{{\\$.*}}$/;\r", - " let matches = paramValue.match(pathVariableRegex);\r", - "\r", - " if (matches && matches.length) {\r", - " value = pm.variables.replaceIn(paramValue);\r", - " }\r", - " }\r", - "\r", - " return encodeURIComponent(value);\r", - "}\r", - "\r", - "function replacePathParameters(schema, pathName, parameters) {\r", - " let replacedPathName = pathName;\r", - " let pathVariableRegex = /{([^}]*)}/g;\r", - " let matches = pathName.match(pathVariableRegex);\r", - " _.forEach(matches, function (match) {\r", - " let paramName = match.substring(1, match.length - 1);\r", - " _.forEach(parameters, function (param) {\r", - " if (param.$ref) {\r", - " let parameter = getSchemaReference(schema, param.$ref);\r", - " if (parameter.in && parameter.in.toLowerCase() == 'path' && parameter.name && parameter.name == paramName) {\r", - " let parameterValue = loadParameterValue(parameter);\r", - " replacedPathName = replacedPathName.replace(match, parameterValue);\r", - " return false;\r", - " }\r", - " } else {\r", - " if (param.in && param.in.toLowerCase() == 'path' && param.name && param.name == paramName) {\r", - " let parameterValue = loadParameterValue(param);\r", - " replacedPathName = replacedPathName.replace(match, parameterValue);\r", - " return false;\r", - " }\r", - " }\r", - " });\r", - " });\r", - "\r", - " return url.parse(replacedPathName);\r", - "}\r", - "\r", - "function getSchemaReference(schema, referenceName) {\r", - " const refPieces = referenceName.split('/');\r", - " let reference = schema;\r", - " for (let i = 1; i < refPieces.length; i++) {\r", - " reference = reference[refPieces[i]];\r", - " }\r", - "\r", - " return reference;\r", - "}" - ], - "type": "text/javascript" - } - }, - { - "listen": "test", - "script": { - "id": "2524e1aa-f349-412f-91eb-b7857f29d495", - "exec": [ - "const schemaTests = JSON.parse(pm.collectionVariables.get('coll-schemaTests'));\r", - "if(schemaTests.length > 0){\r", - " postman.setNextRequest('Test Request');\r", - "}\r", - "\r", - "const schemaTest = JSON.parse(pm.variables.get('currentSchemaTest'));\r", - "console.log(schemaTest.name);\r", - "\r", - "pm.test(`${schemaTest.name} - Has expected status code`, function () {\r", - " // const errorOn500 = pm.environment.get('env-errorOn500');\r", - " // if(errorOn500){\r", - " // pm.response.to.not.have.status(500);\r", - " // }\r", - "\r", - " if(schemaTest.success){\r", - " try{\r", - " if(pm.response.code >= 400) {\r", - " const jsonData = pm.response.json();\r", - " if(pm.response.code == 401) {\r", - " pm.expect(pm.request.headers.get('Role')).to.equal('role');\r", - " }\r", - " pm.expect('').to.equal(jsonData.message); \r", - " }\r", - " \r", - " pm.expect(pm.response.code).to.not.equal(400);\r", - " }\r", - " catch(err) {\r", - " console.log(err);\r", - " pm.expect(pm.response.code).to.not.equal(400);\r", - " } \r", - " }\r", - " else {\r", - " const statusCode = pm.response.code\r", - " pm.expect(statusCode === 400 || statusCode === 422).to.be.true;\r", - " } \r", - "});\r", - "\r", - "const expectedResponse = schemaTest.responses.find(r => r.statusCode == pm.response.code);\r", - "pm.test(`${schemaTest.name} - Status code (${pm.response.code}) is allowed`, function(){\r", - " pm.expect(expectedResponse).to.exist;\r", - "});\r", - "\r", - "if(expectedResponse){\r", - " pm.test(`${schemaTest.name} - Has expected response body schema`, function(){\r", - " const Ajv = require('ajv');\r", - " const ajv = new Ajv({allErrors: true,format: false,nullable: true});\r", - " \r", - " if(pm.response.code == 204 || shouldResponseBeEmpty(expectedResponse)){\r", - " checkForEmptyResponse();\r", - " }\r", - " else if(expectedResponse.$ref){ \r", - " const jsonData = pm.response.json();\r", - " const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", - " ajv.addSchema(schema, 'OAS');\r", - " const valid = ajv.validate({$ref: `OAS${expectedResponse.$ref}`}, jsonData);\r", - " const errors = ajv.errorsText(valid.errors);\r", - " pm.expect(errors).to.equal('No errors');\r", - " if(errors !== 'No errors'){\r", - " console.log(errors);\r", - " }\r", - " }\r", - " else if(expectedResponse.schema){\r", - " const jsonData = pm.response.json();\r", - " const validate = ajv.compile(expectedResponse.schema);\r", - " const valid = validate(jsonData);\r", - " const errors = ajv.errorsText(valid.errors);\r", - " pm.expect(errors).to.equal('No errors');\r", - " if(errors !== 'No errors'){\r", - " console.log(errors);\r", - " }\r", - " }\r", - " else {\r", - " checkForEmptyResponse();\r", - " }\r", - "\r", - " if(expectedResponse.variables){\r", - " const jsonData = pm.response.json();\r", - " _.forEach(expectedResponse.variables, function(variable){\r", - " let pathPieces = variable.path.split('.').filter(piece => piece);\r", - " let data = jsonData;\r", - " let found = true;\r", - " _.forEach(pathPieces, function(piece){\r", - " if(data[piece]){\r", - " data = data[piece];\r", - " }\r", - " else {\r", - " found = false;\r", - " }\r", - " });\r", - "\r", - " if(found){\r", - " pm.collectionVariables.set(variable.name, data);\r", - " }\r", - " else {\r", - " pm.test(`Unable to save dynamic variable ${variable.name} at the provided path.`, function() {\r", - " pm.expect(true).to.equal(variable.path);\r", - " });\r", - " }\r", - " });\r", - " }\r", - " });\r", - "}\r", - "\r", - "function checkForEmptyResponse() {\r", - " let emptyBody = true;\r", - " if(pm.response.text()){\r", - " emptyBody = false; \r", - " }\r", - "\r", - " pm.expect(emptyBody).to.be.true;\r", - "}\r", - "\r", - "function shouldResponseBeEmpty(expectedResponse){\r", - " let responseSchema = expectedResponse.schema;\r", - " if(expectedResponse.$ref){\r", - " let schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", - " responseSchema = getSchemaReference(schema, expectedResponse.$ref);\r", - " if(expectedResponse.$ref.startsWith('#/components/responses')){\r", - " return (!responseSchema || !responseSchema.content || !responseSchema.content['application/json'] \r", - " || !responseSchema.content['application/json'].schema || Object.keys(responseSchema.content['application/json'].schema).length == 0);\r", - " } else {\r", - " return false;\r", - " }\r", - " }\r", - " else {\r", - " return (Object.keys(responseSchema).length == 0);\r", - " }\r", - "}\r", - "\r", - "function getSchemaReference(schema, referenceName){\r", - " const refPieces = referenceName.split('/');\r", - " let reference = schema;\r", - " for(let i = 1; i < refPieces.length; i++){\r", - " reference = reference[refPieces[i]];\r", - " }\r", - "\r", - " return reference;\r", - "}" - ], - "type": "text/javascript" - } - } - ], - "id": "416eff4d-749b-4b49-9c3c-7e6cfbfb7c2c", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "{{body}}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "https://postman-echo.com/get" - }, - "response": [] - } - ], - "id": "5710c277-a099-4c7d-a8ab-9bb911918ef9" - }, - { - "name": "Finalize", - "item": [ - { - "name": "More APIs to Process?", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "let apis = pm.collectionVariables.get('coll-apiIds');\r", - "if(apis){\r", - " try{\r", - " apis = JSON.parse(apis);\r", - " if(apis.length > 0){\r", - " postman.setNextRequest('Get Current API Version');\r", - " }\r", - " }\r", - " catch(err){} \r", - "}" - ], - "type": "text/javascript", - "id": "c236b1d1-9f04-4502-b754-ee4d6430a3ed" - } - } - ], - "id": "eafd2509-05d8-42ee-ab2d-dd7f45453f5c", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [], - "url": "https://postman-echo.com/delay/0" - }, - "response": [] - }, - { - "name": "Remove Test Variables", - "event": [ - { - "listen": "prerequest", - "script": { - "exec": [ - "// See https://blog.postman.com/2019/05/28/pro-tip-dynamically-unset-postman-environment-variables/\r", - "// for more details on what we're doing here. \r", - "\r", - "cleanupCollectionVariables();\r", - "\r", - "function cleanupCollectionVariables() {\r", - " const clean = _.keys(pm.collectionVariables.toObject());\r", - "\r", - " _.each(clean, (arrItem) => {\r", - " pm.collectionVariables.unset(arrItem);\r", - " });\r", - "}" - ], - "type": "text/javascript", - "id": "168455e5-e394-48df-902d-dbab8352acab" - } - }, - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript", - "id": "ac89d252-3423-481b-9ba5-d0b3f55ca383" - } - } - ], - "id": "6e4c0ea7-b3c4-4e33-bc13-ac7ab563f57b", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "url": "https://postman-echo.com/delay/0" - }, - "response": [] - } - ], - "id": "92035d83-c9a1-425a-8a69-65bc677fbc13" - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ], - "id": "38efdb22-19c6-42a8-aa8b-ef1271ce04ef" - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ], - "id": "c48549ed-e6df-407a-8452-ec1db5cc81eb" - } - } - ], - "variable": [ - { - "key": "coll-schema", - "value": "" - }, - { - "key": "coll-baseUrl", - "value": "" - }, - { - "key": "coll-schemaTests", - "value": "" - } - ] -} \ No newline at end of file + "info": { + "_postman_id": "03c5c4b6-9071-4ab4-b5a1-4bda97a3b3aa", + "name": "Contract Test Generator", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" + }, + "item": [ + { + "name": "API Validation", + "item": [ + { + "name": "Cleanup Previous Run", + "event": [ + { + "listen": "prerequest", + "script": { + "id": "6e0312e1-1276-47b2-92be-b481545de5fb", + "exec": [ + "// See https://blog.postman.com/2019/05/28/pro-tip-dynamically-unset-postman-environment-variables/\r", + "// for more details on what we're doing here. \r", + "\r", + "cleanupCollectionVariables();\r", + "\r", + "function cleanupCollectionVariables() {\r", + " const clean = _.keys(pm.collectionVariables.toObject());\r", + "\r", + " _.each(clean, (arrItem) => {\r", + " pm.collectionVariables.unset(arrItem);\r", + " });\r", + "}" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "id": "acaabbd4-94fc-4444-8718-b9ca2c087721", + "exec": [""], + "type": "text/javascript" + } + } + ], + "id": "30368860-aef9-46d6-ad44-8fac61b8f842", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "url": "https://postman-echo.com/delay/0" + }, + "response": [] + }, + { + "name": "Initialize", + "event": [ + { + "listen": "test", + "script": { + "id": "d15a1e94-1145-4006-b514-b5300671da90", + "exec": [ + "var envSchema = null\r", + "if (pm.environment.get(\"env-openapi-json-url\")){\r", + " envSchema = JSON.stringify(pm.response.json());\r", + "}\r", + "\r", + "const providedSchema = pm.environment.get('env-schema') || envSchema;\r", + "if(providedSchema){\r", + " let success = true;\r", + " try{\r", + " const yaml = pm.environment.get('env-jsonToYaml');\r", + " (new Function(yaml))();\r", + "\r", + " const schema = jsyaml.load(providedSchema);\r", + " pm.collectionVariables.set('coll-schema', JSON.stringify(schema));\r", + " postman.setNextRequest('Get API Base Url');\r", + " }\r", + " catch(err){\r", + " console.log(err);\r", + " success = false;\r", + " postman.setNextRequest(null);\r", + " }\r", + "\r", + " pm.test('Successfully converted provided schema', function(){\r", + " pm.expect(success).to.be.true;\r", + " }); \r", + "}" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "9e1c1f12-58f5-4cea-b6c0-58be6133c033", + "exec": [ + "if (pm.environment.get(\"env-openapi-json-url\")){", + " pm.request.url = pm.environment.get(\"env-openapi-json-url\");", + "}" + ], + "type": "text/javascript" + } + } + ], + "id": "367b4112-337b-4bc0-821d-4687694559ad", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "url": "https://postman-echo.com/delay/0" + }, + "response": [] + }, + { + "name": "Validate API In Workspace", + "event": [ + { + "listen": "test", + "script": { + "id": "c7c74561-6423-4fbe-ab30-bd66746c6cdf", + "exec": [ + "const minApiCount = Number(pm.environment.get('env-minApiCount'));\r", + "const maxApiCount = Number(pm.environment.get('env-maxApiCount'));\r", + "const jsonData = pm.response.json();\r", + "\r", + "pm.test(`Workspace API count is between ${minApiCount} and ${maxApiCount}. (Count: ${jsonData.apis.length})`, function () { \r", + " pm.expect(jsonData.apis.length).to.be.at.least(minApiCount); \r", + " pm.expect(jsonData.apis.length).to.be.at.most(maxApiCount);\r", + "});\r", + "\r", + "let apiIds = [];\r", + "_.forEach(jsonData.apis, function(api){\r", + " apiIds.push(api.id);\r", + "});\r", + "\r", + "pm.collectionVariables.set('coll-apiIds', JSON.stringify(apiIds));" + ], + "type": "text/javascript" + } + } + ], + "id": "1baa5ceb-08b6-47c7-9112-8f54c039805d", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "X-Api-Key", + "value": "{{env-apiKey}}", + "type": "text" + } + ], + "url": { + "raw": "https://api.getpostman.com/apis?workspace={{env-workspaceId}}", + "protocol": "https", + "host": ["api", "getpostman", "com"], + "path": ["apis"], + "query": [ + { + "key": "workspace", + "value": "{{env-workspaceId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Get Current API Version", + "event": [ + { + "listen": "test", + "script": { + "id": "683d7e4f-8336-41a1-b14c-5893b3e49fba", + "exec": [ + "const jsonData = pm.response.json();\r", + "\r", + "pm.test('API has one or more versions', function(){\r", + " pm.expect(jsonData).to.have.property('versions').and.to.be.an('array');\r", + " pm.expect(jsonData.versions.length).to.be.above(0);\r", + "});\r", + "\r", + "const version = jsonData.versions[0];\r", + "pm.collectionVariables.set('coll-versionId', version.id);" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "ae6bfbce-aad4-4f31-9b82-ba6f666658a5", + "exec": [ + "let apiIds = pm.collectionVariables.get('coll-apiIds');\r", + "if(apiIds){\r", + " apiIds = JSON.parse(apiIds);\r", + " const apiId = apiIds.pop();\r", + "\r", + " pm.collectionVariables.set('coll-apiId', apiId);\r", + " pm.collectionVariables.set('coll-apiIds', JSON.stringify(apiIds));\r", + "}\r", + "else {\r", + " pm.request.url = 'https://postman-echo.com/delay/0'\r", + " pm.request.name = 'No APIs found in the workspace. Skipping execution';\r", + " postman.setNextRequest(null);\r", + "}" + ], + "type": "text/javascript" + } + } + ], + "id": "37810e31-7b06-4ca4-a422-d0012834d1e4", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "X-Api-Key", + "value": "{{env-apiKey}}", + "type": "text" + } + ], + "url": { + "raw": "https://api.getpostman.com/apis/:apiId/versions", + "protocol": "https", + "host": ["api", "getpostman", "com"], + "path": ["apis", ":apiId", "versions"], + "query": [ + { + "key": null, + "value": "", + "disabled": true + } + ], + "variable": [ + { + "id": "66de574b-c196-407e-9be7-ff53c0dac927", + "key": "apiId", + "value": "{{coll-apiId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Get Current API Schema", + "event": [ + { + "listen": "test", + "script": { + "id": "38beef82-f213-44cc-9a11-c326fb5b003d", + "exec": [ + "const jsonData = pm.response.json();\r", + "\r", + "pm.test('Has schema for current version', function(){\r", + " pm.expect(jsonData).to.have.property('version');\r", + " pm.expect(jsonData.version).to.have.property('schema').and.to.be.an('array');\r", + " pm.expect(jsonData.version.schema.length).to.be.above(0);\r", + "\r", + " pm.collectionVariables.set('coll-schemaId', jsonData.version.schema[0]);\r", + "});" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "00af0146-94da-4674-96cc-9feff4ba493f", + "exec": [""], + "type": "text/javascript" + } + } + ], + "id": "4ecaeaa0-5908-497b-ae55-045465cb47e4", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "X-Api-Key", + "type": "text", + "value": "{{env-apiKey}}" + } + ], + "url": { + "raw": "https://api.getpostman.com/apis/:apiId/versions/:versionId", + "protocol": "https", + "host": ["api", "getpostman", "com"], + "path": ["apis", ":apiId", "versions", ":versionId"], + "query": [ + { + "key": null, + "value": "", + "disabled": true + } + ], + "variable": [ + { + "id": "ab65af1f-47bf-463c-b68b-b2a2f1d70081", + "key": "apiId", + "value": "{{coll-apiId}}" + }, + { + "id": "82912cc7-9ae0-4090-9758-42c81f4b6924", + "key": "versionId", + "value": "{{coll-versionId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Get API Schema", + "event": [ + { + "listen": "test", + "script": { + "id": "6f74f9f2-f92e-4993-b0be-f4181d16e01e", + "exec": [ + "try {\r", + " const jsonData = pm.response.json();\r", + " if(jsonData.schema.language.toLowerCase() == 'json'){\r", + " pm.test('Schema is JSON', function(){\r", + " pm.expect(1).to.equal(1);\r", + " pm.collectionVariables.set('coll-schema', jsonData.schema.schema);\r", + " });\r", + " } else {\r", + " pm.test('Schema translates to JSON', function(){\r", + " try{\r", + " const yaml = pm.environment.get('env-jsonToYaml');\r", + " (new Function(yaml))();\r", + "\r", + " const schema = jsyaml.load(jsonData.schema.schema);\r", + " pm.collectionVariables.set('coll-schema', JSON.stringify(schema));\r", + " pm.expect(1).to.equal(1);\r", + " }\r", + " catch(err){\r", + " pm.expect(`${err.name} - ${err.message}`).to.equal(undefined);\r", + " } \r", + " });\r", + " }\r", + "}\r", + "catch(err) {\r", + " console.log(err);\r", + " pm.test('Unable to load schema', function(){\r", + " pm.expect(0).to.equal(1);\r", + " postman.setNextRequest(null);\r", + " })\r", + "}" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "7fb06c94-c343-47f8-8308-59170e3c56b8", + "exec": [ + "if (pm.environment.get(\"env-openapi-json-url\")){", + " pm.request.url = pm.environment.get(\"env-openapi-json-url\");", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "id": "1b67ec27-0a68-4755-b118-43190677c99d", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "X-Api-Key", + "value": "{{env-apiKey}}", + "type": "text" + } + ], + "url": { + "raw": "https://api.getpostman.com/apis/:apiId/versions/:apiVersionId/schemas/:schemaId", + "protocol": "https", + "host": ["api", "getpostman", "com"], + "path": [ + "apis", + ":apiId", + "versions", + ":apiVersionId", + "schemas", + ":schemaId" + ], + "variable": [ + { + "id": "ebe1d781-f0eb-4e96-847e-0f42e2ac15ac", + "key": "apiId", + "value": "{{coll-apiId}}" + }, + { + "id": "3bd638bb-503b-4e2b-8da2-ebd9f5d8a4d4", + "key": "apiVersionId", + "value": "{{coll-versionId}}" + }, + { + "id": "521e1c1e-e161-478c-89ba-1b079435201b", + "key": "schemaId", + "value": "{{coll-schemaId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Get API Base Url", + "event": [ + { + "listen": "test", + "script": { + "id": "5876b636-91d2-405b-a0e6-8bd4e2132969", + "exec": [ + "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", + "const server = pm.environment.get('env-server');\r", + "\r", + "pm.test('Environment has test server defined', function () {\r", + " pm.expect(server).to.not.be.undefined;\r", + "});\r", + "\r", + "pm.test('Schema has server/baseUrl defined', function () {\r", + " const servers = schema.servers;\r", + " pm.expect(servers).to.not.be.undefined;\r", + " const serverToTest = servers.find(s => s.description.toLowerCase() == server.toLowerCase());\r", + " pm.expect(serverToTest).to.not.be.undefined;\r", + "\r", + " pm.expect(serverToTest).to.have.property('url');\r", + " pm.collectionVariables.set('coll-baseUrl', serverToTest.url);\r", + "});\r", + "\r", + "const runComponentTests = pm.environment.get('env-runComponentTests') == 'true';\r", + "if(!runComponentTests){ \r", + " const runContractTests = pm.environment.get('env-runContractTests') == 'true';\r", + " if(runContractTests){\r", + " postman.setNextRequest('Build Schema Tests');\r", + " } else {\r", + " postman.setNextRequest('More APIs to Process?');\r", + " } \r", + "}" + ], + "type": "text/javascript" + } + } + ], + "id": "c8711439-c69a-4420-b034-cb58dc21e110", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": "https://postman-echo.com/delay/0" + }, + "response": [] + } + ], + "id": "2778dde1-f785-4aba-84cc-57e1102bff23" + }, + { + "name": "Components", + "item": [ + { + "name": "Verify Component Adherence", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", + "\r", + "const requireParamDescription = Boolean(pm.environment.get('env-requireParamDescription'));\r", + "const requireParamExample = Boolean(pm.environment.get('env-requireParamExample'));\r", + "\r", + "let paramDescriptionMinLength = pm.environment.get('env-paramDescriptionMinLength');\r", + "if (paramDescriptionMinLength) {\r", + " paramDescriptionMinLength = Number(paramDescriptionMinLength);\r", + "}\r", + "\r", + "let paramDescriptionMaxLength = pm.environment.get('env-paramDesciptionMaxLength');\r", + "if (paramDescriptionMaxLength) {\r", + " paramDescriptionMaxLength = Number(paramDescriptionMaxLength);\r", + "}\r", + "\r", + "var testedSchemaRefs = [];\r", + "\r", + "if (schema.components.parameters) {\r", + " for (let prop in schema.components.parameters) {\r", + " let parameter = schema.components.parameters[prop];\r", + "\r", + " pm.test(`Parameter '${prop}' starts with a lowercase letter`, function () {\r", + " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toLowerCase());\r", + " });\r", + "\r", + " if (requireParamDescription) {\r", + " pm.test(`Parameter '${prop}' has a description between ${paramDescriptionMinLength} and ${paramDescriptionMaxLength} characters`, function () {\r", + " pm.expect(parameter).to.have.property('description').and.to.be.a('string');\r", + " pm.expect(parameter.description.length).to.be.at.least(paramDescriptionMinLength);\r", + " pm.expect(parameter.description.length).to.be.at.most(paramDescriptionMaxLength);\r", + " });\r", + " }\r", + "\r", + " if (requireParamExample) {\r", + " pm.test(`Parameter '${prop}' has an example`, function () {\r", + " pm.expect(parameter).to.have.property('schema');\r", + " pm.expect(parameter.schema).to.have.property('example');\r", + " });\r", + " }\r", + " }\r", + "}\r", + "\r", + "if (schema.components.schemas) {\r", + " for (let prop in schema.components.schemas) {\r", + " pm.test(`Schema '${prop}' begins with an uppercase letter`, function () {\r", + " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toUpperCase());\r", + " });\r", + "\r", + " const testedSchema = testedSchemaRefs.find(tsr => tsr == prop);\r", + " if (!testedSchema) {\r", + " const schemaObject = schema.components.schemas[prop];\r", + " testSchemaObject(schema, schemaObject, prop);\r", + " testedSchemaRefs.push(prop);\r", + " }\r", + " }\r", + "}\r", + "\r", + "if (schema.components.responses) {\r", + " for (let prop in schema.components.responses) {\r", + " pm.test(`Response '${prop}' begins with an uppercase letter`, function () {\r", + " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toUpperCase());\r", + " });\r", + "\r", + " if (requireParamDescription) {\r", + " const response = schema.components.responses[prop];\r", + " pm.test(`Response '${prop}' has a description between ${paramDescriptionMinLength} and ${paramDescriptionMaxLength} characters`, function () {\r", + " pm.expect(response).to.have.property('description').and.to.be.a('string');\r", + " pm.expect(response.description.length).to.be.at.least(paramDescriptionMinLength);\r", + " pm.expect(response.description.length).to.be.at.most(paramDescriptionMaxLength);\r", + " });\r", + " }\r", + " }\r", + "}\r", + "\r", + "const runContractTests = pm.environment.get('env-runContractTests') == 'true';\r", + "if (runContractTests) {\r", + " postman.setNextRequest('Build Schema Tests');\r", + "} else {\r", + " postman.setNextRequest('More APIs to Process?');\r", + "}\r", + "\r", + "\r", + "function testSchemaObject(schema, object, objectName) {\r", + " if (object.type && object.type.toLowerCase() == 'object') {\r", + " if (object.required) {\r", + " for (let i = 0; i < object.required.length; i++) {\r", + " const requiredProp = object.required[i];\r", + " pm.test(`Schema '${objectName}' has required property '${requiredProp}' defined`, function () {\r", + " pm.expect(object.properties).to.have.property(requiredProp);\r", + " });\r", + " }\r", + " }\r", + "\r", + " let schemaPropertyExceptions = [];\r", + " if (pm.environment.has('env-schemaPropertyExceptions')) {\r", + " schemaPropertyExceptions = JSON.parse(pm.environment.get('env-schemaPropertyExceptions'));\r", + " }\r", + "\r", + " for (let prop in object.properties) {\r", + " const property = object.properties[prop];\r", + "\r", + " if (!schemaPropertyExceptions.some(pe => pe === prop)) {\r", + " pm.test(`Schema property '${objectName}.${prop}' is lowercase`, function () {\r", + " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toLowerCase());\r", + " });\r", + " }\r", + "\r", + " if (property.type && property.type.toLowerCase() == 'object') {\r", + " testSchemaObject(schema, property, `${objectName}.${prop}`);\r", + " }\r", + " else if (property.type && property.type.toLowerCase() == 'array') {\r", + " testSchemaObject(schema, property, `${objectName}.${prop}(list)`);\r", + " }\r", + " else if (property.oneOf) {\r", + " _.forEach(property.oneOf, (oneOf, i) => {\r", + " testSchemaObject(schema, oneOf, `${objectName}.${prop}(oneOf).${i}`)\r", + " });\r", + " }\r", + " else if (property.allOf) {\r", + " _.forEach(property.allOf, (allOf, i) => {\r", + " testSchemaObject(schema, allOf, `${objectName}.${prop}(allOf).${i}`)\r", + " });\r", + " }\r", + " else if (property.anyOf) {\r", + " _.forEach(property.anyOf, (anyOf, i) => {\r", + " testSchemaObject(schema, anyOf, `${objectName}.${prop}(anyOf).${i}`)\r", + " });\r", + " }\r", + " else {\r", + " if (requireParamDescription && !property.$ref) {\r", + " pm.test(`Schema property '${objectName}.${prop}' has a description between ${paramDescriptionMinLength} and ${paramDescriptionMaxLength} characters`, function () {\r", + " pm.expect(property).to.have.property('description').and.to.be.a('string');\r", + " pm.expect(property.description.length).to.be.at.least(paramDescriptionMinLength);\r", + " pm.expect(property.description.length).to.be.at.most(paramDescriptionMaxLength);\r", + " });\r", + "\r", + " if (property.description) {\r", + " pm.test(`Schema property '${objectName}.${prop}' description is not just the name`, function () {\r", + " pm.expect(prop.toLowerCase()).to.not.equal(property.description.toLowerCase());\r", + " });\r", + " }\r", + " }\r", + "\r", + " if (requireParamExample && !property.$ref) {\r", + " pm.test(`Schema property '${objectName}.${prop}' has an example`, function () {\r", + " pm.expect(property).to.have.property('example');\r", + " });\r", + " }\r", + " }\r", + " }\r", + " }\r", + " else if (object.type && object.type.toLowerCase() == 'array') {\r", + " pm.test(`Schema '${objectName}' has items defined`, function () {\r", + " pm.expect(object).to.have.property('items');\r", + " });\r", + "\r", + " testSchemaObject(schema, object.items, `${objectName}.list`);\r", + " }\r", + " else if (object.oneOf) {\r", + " handleSchemaArray(schema, object, objectName, 'oneOf');\r", + " } else if (object.allOf) {\r", + " handleSchemaArray(schema, object, objectName, 'allOf');\r", + " }\r", + " else if (object.anyOf) {\r", + " handleSchemaArray(schema, object, objectName, 'anyOf');\r", + " }\r", + " else if (object.$ref) {\r", + " const name = getName(object.$ref);\r", + " const testedRef = testedSchemaRefs.find(tsr => tsr == name);\r", + " if (!testedRef) {\r", + " testSchemaObject(schema, schema.components.schemas[name], objectName);\r", + " testedSchemaRefs.push(name);\r", + " }\r", + " }\r", + " else {\r", + " pm.test(`Schema '${objectName}' has a declared type`, function () {\r", + " pm.expect(object).to.have.property('type');\r", + " });\r", + " }\r", + "}\r", + "\r", + "function handleSchemaArray(schema, object, objectName, arrayType) {\r", + " for (let i = 0; i < object[arrayType].length; i++) {\r", + " const arraySchema = object[arrayType][i];\r", + " if (arraySchema.$ref) {\r", + " const name = getName(arraySchema.$ref);\r", + " const testedRef = testedSchemaRefs.find(tsr => tsr == name);\r", + " if (!testedRef) {\r", + " testSchemaObject(schema, schema.components.schemas[name], `${objectName}[${i}](ref ${name})`);\r", + " testedSchemaRefs.push(name);\r", + " }\r", + " }\r", + " else {\r", + " testSchemaObject(schema, arraySchema, `${objectName}[${i}]`);\r", + " }\r", + " }\r", + "}\r", + "\r", + "function getName(ref) {\r", + " let pieces = ref.split('/');\r", + " return pieces[pieces.length - 1];\r", + "}\r", + "" + ], + "type": "text/javascript", + "id": "968b2ac8-c423-42d7-ab68-809e0292ab31" + } + } + ], + "id": "46526b39-701b-4f22-a0a3-03ec53319450", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": "https://postman-echo.com/delay/0" + }, + "response": [] + } + ], + "id": "18e4b0a6-d641-4661-9a29-918247d63f5f" + }, + { + "name": "Contract Tests", + "item": [ + { + "name": "Build Schema Tests", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", + "\r", + "let schemaTests = [];\r", + "for (let prop in schema.paths) {\r", + " const pathName = prop;\r", + " let path = {\r", + " path: `${pm.collectionVariables.get('coll-baseUrl')}${pathName}`,\r", + " parameters: schema.paths[prop].parameters,\r", + " };\r", + "\r", + " for (let method in schema.paths[prop]) {\r", + " if (method.toLowerCase() == 'parameters' || isMockEndpoint(schema.paths[prop][method])) {\r", + " continue;\r", + " }\r", + "\r", + " let currentPath = _.cloneDeep(path);\r", + " currentPath.method = method.toUpperCase();\r", + " let pathMethod = schema.paths[prop][method];\r", + " currentPath.parameters = combineParameters(currentPath.parameters, pathMethod.parameters);\r", + " let securityExtension = pm.environment.get('env-securityExtensionName');\r", + " if (securityExtension && pathMethod[securityExtension] && pathMethod[securityExtension].length > 0) {\r", + " currentPath.allowedRole = pathMethod[securityExtension][0];\r", + " }\r", + "\r", + " const expectedResponses = getExpectedResponses(pathMethod);\r", + " currentPath.responses = expectedResponses;\r", + "\r", + " if (pathMethod.requestBody) {\r", + " let bodyModel;\r", + " if (pathMethod.requestBody.content['application/json']?.schema?.$ref) {\r", + " bodyModel = getSchemaReference(schema, pathMethod.requestBody.content['application/json'].schema.$ref);\r", + " }\r", + " else if (pathMethod.requestBody.content['application/json']?.schema) {\r", + " bodyModel = pathMethod.requestBody.content['application/json'].schema;\r", + " }\r", + " else {\r", + " continue;\r", + " }\r", + "\r", + " const models = buildModels(schema, bodyModel);\r", + " const mutations = buildModelMutations(models);\r", + "\r", + " mutations.forEach((mutation) => {\r", + " let schemaTest = _.cloneDeep(currentPath);\r", + " Object.assign(schemaTest, mutation);\r", + " schemaTest.name = `${schemaTest.method} - ${pathName} - ${schemaTest.description} - SUCCESS: ${schemaTest.success}`;\r", + " schemaTests.push(schemaTest);\r", + " });\r", + " }\r", + " else {\r", + " currentPath.name = `${currentPath.method} - ${pathName} - No Request Body - SUCCESS: true`;\r", + " currentPath.success = true;\r", + " schemaTests.push(currentPath);\r", + " }\r", + " }\r", + "}\r", + "schemaTests = moveDeleteEndpointsToEnd(schemaTests);\r", + "pm.collectionVariables.set('coll-schemaTests', JSON.stringify(schemaTests));\r", + "\r", + "// \r", + "// Move delete endpoints to the end for cleanup\r", + "//\r", + "function moveDeleteEndpointsToEnd(schemaTests) {\r", + " let sortedTests = [...schemaTests];\r", + " try {\r", + " let successfulDeletes = sortedTests.filter(schemaTest => schemaTest.method == 'DELETE' && schemaTest.success);\r", + "\r", + " if (successfulDeletes) {\r", + " // order deletes from the deepest entity to highest level entity based on path\r", + " successfulDeletes.sort((a, b) => b.path.split('/').length - a.path.split('/').length);\r", + " sortedTests = sortedTests.filter(schemaTest => !successfulDeletes.find(sd => sd == schemaTest));\r", + " sortedTests = sortedTests.concat(successfulDeletes);\r", + " }\r", + " }\r", + " catch (err) {\r", + " console.log('An error occurred when sorting delete tests', err);\r", + " }\r", + "\r", + " return sortedTests;\r", + "}\r", + "\r", + "//\r", + "// Supporting Methods Below\r", + "//\r", + "function buildModels(schema, object) {\r", + " let models = [];\r", + "\r", + " if (object['$ref']) {\r", + " object = getSchemaReference(schema, object['$ref']);\r", + " }\r", + "\r", + " if (object.type && object.type.toLowerCase() == 'object') {\r", + " if (object.required && object.required.length > 0) {\r", + " models.push({});\r", + " _.forEach(object.required, function (param) {\r", + " const property = object.properties[param];\r", + "\r", + " if (property.type && ['string', 'number', 'integer', 'boolean'].includes(property.type.toLowerCase())) {\r", + " for (let modelIndex = 0; modelIndex < models.length; modelIndex++) {\r", + " let model = models[modelIndex];\r", + " model[param] = property.example;\r", + " }\r", + " }\r", + " else {\r", + " const nestedObjects = buildModels(schema, property);\r", + " models = addToModels(models, nestedObjects, param);\r", + " }\r", + " });\r", + " }\r", + "\r", + " if (object.minProperties) {\r", + " _.forEach(models, function (model) {\r", + " if (Object.keys(model).length < object.minProperties) {\r", + " for (let i = Object.keys(model).length; i < object.minProperties; i++) {\r", + " for (const [key, value] of Object.entries(object.properties)) {\r", + " if (['string', 'number', 'integer', 'boolean'].includes(value.type.toLowerCase()) && model[key] == undefined) {\r", + " model[key] = value.example;\r", + " break;\r", + " }\r", + " }\r", + " }\r", + " }\r", + " })\r", + " }\r", + " }\r", + " else if (object.type && object.type.toLowerCase() == 'array') {\r", + " let items = buildModels(schema, object.items);\r", + " if (Array.isArray(items)) {\r", + " for (let i = 0; i < items.length; i++) {\r", + " models.push([items[i]]);\r", + " }\r", + " }\r", + " else {\r", + " models.push([items]);\r", + " }\r", + " }\r", + " else if (object.oneOf) {\r", + " _.forEach(object.oneOf, function (component) {\r", + " let items = buildModels(schema, component);\r", + " models = models.concat(items);\r", + " });\r", + " }\r", + " else if (object.allOf) {\r", + " let pieces = [{}];\r", + " _.forEach(object.allOf, function (component) {\r", + " let componentModels = buildModels(schema, component);\r", + " pieces = addToModels(pieces, componentModels);\r", + " });\r", + "\r", + " models = pieces;\r", + " }\r", + " else if (object.anyOf) {\r", + " let pieces = [];\r", + " let combinedPieces = [{}];\r", + " _.forEach(object.anyOf, function (component) {\r", + " let componentModels = buildModels(schema, component);\r", + " combinedPieces = addToModels(combinedPieces, componentModels);\r", + " pieces = pieces.concat(componentModels);\r", + " });\r", + "\r", + " models = pieces.concat(combinedPieces);\r", + " }\r", + " else {\r", + " // All other options are primitive values\r", + " return object.example;\r", + " }\r", + " return models;\r", + "}\r", + "\r", + "function getSchemaReference(schema, referenceName) {\r", + " const refPieces = referenceName.split('/');\r", + " let reference = schema;\r", + " for (let i = 1; i < refPieces.length; i++) {\r", + " reference = reference[refPieces[i]];\r", + " }\r", + "\r", + " return reference;\r", + "}\r", + "\r", + "function addToModels(models, newPieces, name) {\r", + " let newModels = [];\r", + " _.forEach(models, function (model) {\r", + " _.forEach(newPieces, function (newPiece) {\r", + " let newModel = _.cloneDeep(model);\r", + " if (name) {\r", + " newModel[name] = newPiece;\r", + " }\r", + " else {\r", + " Object.assign(newModel, newPiece);\r", + " }\r", + " newModels.push(newModel);\r", + " });\r", + " });\r", + "\r", + " return newModels;\r", + "}\r", + "\r", + "function buildModelMutations(models) {\r", + " let modelMutations = [];\r", + " _.forEach(models, function (model) {\r", + " addMutation(true, 'Has all required fields', model, modelMutations);\r", + " let mutations = buildMutation(model);\r", + " modelMutations = modelMutations.concat(mutations);\r", + " });\r", + "\r", + " return modelMutations;\r", + "}\r", + "\r", + "function buildMutation(model) {\r", + " let mutations = [];\r", + "\r", + " for (const [key, value] of Object.entries(model)) {\r", + " if (typeof value == 'object') {\r", + " let nestedMutations = buildMutation(value);\r", + " nestedMutations.forEach((nestedMutation) => {\r", + " let mutation = _.cloneDeep(model);\r", + " mutation[key] = nestedMutation.body;\r", + " addMutation(false, `${nestedMutation.description} in ${key} object`, mutation, mutations);\r", + " });\r", + "\r", + " let mutation = _.cloneDeep(model);\r", + " delete mutation[key];\r", + " addMutation(false, `Missing ${key} object`, mutation, mutations);\r", + "\r", + " let emptyMutation = _.cloneDeep(model);\r", + " emptyMutation[key] = {};\r", + " addMutation(false, `Empty ${key} object`, emptyMutation, mutations);\r", + " }\r", + " else {\r", + " if (Array.isArray(value)) {\r", + " console.log('probably an error');\r", + " }\r", + " let mutation = _.cloneDeep(model);\r", + " delete mutation[key];\r", + " addMutation(false, `Missing ${key} property`, mutation, mutations);\r", + "\r", + " let blankMutation = _.cloneDeep(model);\r", + " blankMutation[key] = '';\r", + " addMutation(false, `Blank ${key} property`, blankMutation, mutations);\r", + " }\r", + " }\r", + "\r", + " return mutations;\r", + "}\r", + "\r", + "function addMutation(isSuccess, description, mutation, mutations) {\r", + " mutations.push({\r", + " success: isSuccess,\r", + " description: description,\r", + " body: mutation\r", + " });\r", + "}\r", + "\r", + "function getExpectedResponses(pathMethod) {\r", + " const responses = [];\r", + " for (const [statusCode, value] of Object.entries(pathMethod.responses)) {\r", + " let response = {\r", + " statusCode: Number(statusCode)\r", + " };\r", + "\r", + " if (value['x-postman-variables'] && Array.isArray(value['x-postman-variables'])) {\r", + " response.variables = value['x-postman-variables'].filter(variable => variable.type.toLowerCase() === 'save');\r", + " }\r", + "\r", + " if (value.$ref) {\r", + " response.$ref = value.$ref;\r", + " }\r", + " else {\r", + " if (value.content?.['application/json']?.schema) {\r", + " if (value.content['application/json'].schema.$ref) {\r", + " response.$ref = value.content['application/json'].schema.$ref;\r", + " }\r", + " else {\r", + " response.schema = value.content['application/json'].schema;\r", + " }\r", + " }\r", + " }\r", + "\r", + " responses.push(response);\r", + " }\r", + " return responses;\r", + "}\r", + "\r", + "function isMockEndpoint(pathMethod) {\r", + " let isMock = false;\r", + " if (pathMethod && pathMethod['x-amazon-apigateway-integration'] && pathMethod['x-amazon-apigateway-integration'].type\r", + " && pathMethod['x-amazon-apigateway-integration'].type.toLowerCase() == 'mock') {\r", + " isMock = true;\r", + " }\r", + "\r", + " return isMock;\r", + "}\r", + "\r", + "function combineParameters(endpointParameters, methodParameters) {\r", + " if (!endpointParameters && !methodParameters) {\r", + " return;\r", + " }\r", + " let parameters = [];\r", + " if (endpointParameters && endpointParameters.length) {\r", + " parameters = [...endpointParameters];\r", + " }\r", + "\r", + " if (methodParameters && methodParameters.length) {\r", + " parameters = [...parameters, ...methodParameters];\r", + " }\r", + "\r", + " return parameters;\r", + "}" + ], + "type": "text/javascript", + "id": "3017117f-1119-4cda-8de9-f181be051dff" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "let schemaTests = pm.collectionVariables.get('coll-schemaTests');\r", + "if(schemaTests){\r", + " schemaTests = JSON.parse(schemaTests);\r", + " if(!schemaTests || !schemaTests.length){\r", + " postman.setNextRequest('More APIs to Process?');\r", + " }\r", + "}" + ], + "type": "text/javascript", + "id": "161ed627-1e43-4d10-923c-308e9b039bd9" + } + } + ], + "id": "bd6e30f9-999f-4ace-91ca-270b0bd1f1a5", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": "https://postman-echo.com/delay/0" + }, + "response": [] + }, + { + "name": "Test Request", + "event": [ + { + "listen": "prerequest", + "script": { + "id": "49ce35ea-de86-4a14-b90e-3d396e2feb2d", + "exec": [ + "const url = require('url');\r", + "\r", + "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", + "let schemaTests = JSON.parse(pm.collectionVariables.get('coll-schemaTests'));\r", + "\r", + "const schemaTest = schemaTests.shift();\r", + "pm.collectionVariables.set('coll-schemaTests', JSON.stringify(schemaTests));\r", + "pm.variables.set('currentSchemaTest', JSON.stringify(schemaTest));\r", + "\r", + "const path = replacePathParameters(schema, schemaTest.path, schemaTest.parameters);\r", + "pm.request.url.update(path);\r", + "delete pm.request.url.auth;\r", + "delete pm.request.url.port;\r", + "delete pm.request.url.hash;\r", + "if (pm.request.url.protocol) {\r", + " pm.request.url.protocol = pm.request.url.protocol.replace(/\\:$/, '');\r", + "} else {\r", + " pm.request.url.protocol = 'https';\r", + "}\r", + "pm.request.method = schemaTest.method;\r", + "pm.request.name = schemaTest.name;\r", + "\r", + "pm.variables.set('requestName', schemaTest.name);\r", + "pm.variables.set('body', JSON.stringify(schemaTest.body));\r", + "\r", + "// Add top level parameters from the path\r", + "const roleHeaderName = pm.environment.get('env-roleHeaderName');\r", + "\r", + "if (schemaTest.parameters) {\r", + " for (let i = 0; i < schemaTest.parameters.length; i++) {\r", + " let param = schemaTest.parameters[i];\r", + "\r", + " if (param.$ref) {\r", + " let pieces = param.$ref.split('/');\r", + " const name = pieces[pieces.length - 1];\r", + " const schemaParam = schema.components.parameters[name];\r", + " const paramType = schemaParam.in.toLowerCase();\r", + " const paramValue = loadParameterValue(schemaParam);\r", + " if (paramType == 'header' && schemaParam.required == true) {\r", + " if (roleHeaderName && schemaParam.name.toLowerCase() == roleHeaderName.toLowerCase()) {\r", + " pm.request.headers.upsert({ key: schemaParam.name, value: schemaTest.allowedRole });\r", + " }\r", + " else {\r", + " pm.request.headers.upsert({ key: schemaParam.name, value: paramValue });\r", + " }\r", + " } else if (paramType == 'query' && schemaParam.required == true) {\r", + " pm.request.url.query.upsert({ key: schemaParam.name, value: paramValue });\r", + " }\r", + " } else {\r", + " const paramType = param.in.toLowerCase();\r", + " const paramValue = loadParameterValue(param);\r", + " if (paramType == 'header') {\r", + " pm.request.headers.upsert({ key: param.name, value: paramValue });\r", + " } else if (paramType == 'query' && param.required == true) {\r", + " pm.request.url.query.upsert({ key: param.name, value: paramValue });\r", + " }\r", + " }\r", + " }\r", + "}\r", + "\r", + "function loadParameterValue(parameter) {\r", + " let parameterValue;\r", + " if (parameter['x-postman-variables']) {\r", + " let variable = parameter['x-postman-variables'].find(v => v.type.toLowerCase() === 'load');\r", + " if (variable && pm.collectionVariables.has(variable.name)) {\r", + " parameterValue = pm.collectionVariables.get(variable.name);\r", + " }\r", + " else {\r", + " parameterValue = resolveParameterExample(parameter);\r", + " }\r", + " }\r", + " else {\r", + " parameterValue = resolveParameterExample(parameter);\r", + " }\r", + "\r", + " return parameterValue;\r", + "}\r", + "\r", + "function resolveParameterExample(parameter) {\r", + " let paramValue = (parameter.schema.example != undefined) ? parameter.schema.example : parameter.example;\r", + " let value = paramValue;\r", + " if (typeof paramValue !== 'number' && typeof paramValue !== 'boolean') {\r", + " let pathVariableRegex = /^{{\\$.*}}$/;\r", + " let matches = paramValue.match(pathVariableRegex);\r", + "\r", + " if (matches && matches.length) {\r", + " value = pm.variables.replaceIn(paramValue);\r", + " }\r", + " }\r", + "\r", + " return encodeURIComponent(value);\r", + "}\r", + "\r", + "function replacePathParameters(schema, pathName, parameters) {\r", + " let replacedPathName = pathName;\r", + " let pathVariableRegex = /{([^}]*)}/g;\r", + " let matches = pathName.match(pathVariableRegex);\r", + " _.forEach(matches, function (match) {\r", + " let paramName = match.substring(1, match.length - 1);\r", + " _.forEach(parameters, function (param) {\r", + " if (param.$ref) {\r", + " let parameter = getSchemaReference(schema, param.$ref);\r", + " if (parameter.in && parameter.in.toLowerCase() == 'path' && parameter.name && parameter.name == paramName) {\r", + " let parameterValue = loadParameterValue(parameter);\r", + " replacedPathName = replacedPathName.replace(match, parameterValue);\r", + " return false;\r", + " }\r", + " } else {\r", + " if (param.in && param.in.toLowerCase() == 'path' && param.name && param.name == paramName) {\r", + " let parameterValue = loadParameterValue(param);\r", + " replacedPathName = replacedPathName.replace(match, parameterValue);\r", + " return false;\r", + " }\r", + " }\r", + " });\r", + " });\r", + "\r", + " return url.parse(replacedPathName);\r", + "}\r", + "\r", + "function getSchemaReference(schema, referenceName) {\r", + " const refPieces = referenceName.split('/');\r", + " let reference = schema;\r", + " for (let i = 1; i < refPieces.length; i++) {\r", + " reference = reference[refPieces[i]];\r", + " }\r", + "\r", + " return reference;\r", + "}" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "id": "2524e1aa-f349-412f-91eb-b7857f29d495", + "exec": [ + "const schemaTests = JSON.parse(pm.collectionVariables.get('coll-schemaTests'));\r", + "if(schemaTests.length > 0){\r", + " postman.setNextRequest('Test Request');\r", + "}\r", + "\r", + "const schemaTest = JSON.parse(pm.variables.get('currentSchemaTest'));\r", + "console.log(schemaTest.name);\r", + "\r", + "pm.test(`${schemaTest.name} - Has expected status code`, function () {\r", + " // const errorOn500 = pm.environment.get('env-errorOn500');\r", + " // if(errorOn500){\r", + " // pm.response.to.not.have.status(500);\r", + " // }\r", + "\r", + " if(schemaTest.success){\r", + " try{\r", + " if(pm.response.code >= 400) {\r", + " const jsonData = pm.response.json();\r", + " if(pm.response.code == 401) {\r", + " pm.expect(pm.request.headers.get('Role')).to.equal('role');\r", + " }\r", + " pm.expect('').to.equal(jsonData.message); \r", + " }\r", + " \r", + " pm.expect(pm.response.code).to.not.equal(400);\r", + " }\r", + " catch(err) {\r", + " console.log(err);\r", + " pm.expect(pm.response.code).to.not.equal(400);\r", + " } \r", + " }\r", + " else {\r", + " const statusCode = pm.response.code\r", + " pm.expect(statusCode === 400 || statusCode === 422).to.be.true;\r", + " } \r", + "});\r", + "\r", + "const expectedResponse = schemaTest.responses.find(r => r.statusCode == pm.response.code);\r", + "pm.test(`${schemaTest.name} - Status code (${pm.response.code}) is allowed`, function(){\r", + " pm.expect(expectedResponse).to.exist;\r", + "});\r", + "\r", + "if(expectedResponse){\r", + " pm.test(`${schemaTest.name} - Has expected response body schema`, function(){\r", + " const Ajv = require('ajv');\r", + " const ajv = new Ajv({allErrors: true,format: false,nullable: true});\r", + " \r", + " if(pm.response.code == 204 || shouldResponseBeEmpty(expectedResponse)){\r", + " checkForEmptyResponse();\r", + " }\r", + " else if(expectedResponse.$ref){ \r", + " const jsonData = pm.response.json();\r", + " const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", + " ajv.addSchema(schema, 'OAS');\r", + " const valid = ajv.validate({$ref: `OAS${expectedResponse.$ref}`}, jsonData);\r", + " const errors = ajv.errorsText(valid.errors);\r", + " pm.expect(errors).to.equal('No errors');\r", + " if(errors !== 'No errors'){\r", + " console.log(errors);\r", + " }\r", + " }\r", + " else if(expectedResponse.schema){\r", + " const jsonData = pm.response.json();\r", + " const validate = ajv.compile(expectedResponse.schema);\r", + " const valid = validate(jsonData);\r", + " const errors = ajv.errorsText(valid.errors);\r", + " pm.expect(errors).to.equal('No errors');\r", + " if(errors !== 'No errors'){\r", + " console.log(errors);\r", + " }\r", + " }\r", + " else {\r", + " checkForEmptyResponse();\r", + " }\r", + "\r", + " if(expectedResponse.variables){\r", + " const jsonData = pm.response.json();\r", + " _.forEach(expectedResponse.variables, function(variable){\r", + " let pathPieces = variable.path.split('.').filter(piece => piece);\r", + " let data = jsonData;\r", + " let found = true;\r", + " _.forEach(pathPieces, function(piece){\r", + " if(data[piece]){\r", + " data = data[piece];\r", + " }\r", + " else {\r", + " found = false;\r", + " }\r", + " });\r", + "\r", + " if(found){\r", + " pm.collectionVariables.set(variable.name, data);\r", + " }\r", + " else {\r", + " pm.test(`Unable to save dynamic variable ${variable.name} at the provided path.`, function() {\r", + " pm.expect(true).to.equal(variable.path);\r", + " });\r", + " }\r", + " });\r", + " }\r", + " });\r", + "}\r", + "\r", + "function checkForEmptyResponse() {\r", + " let emptyBody = true;\r", + " if(pm.response.text()){\r", + " emptyBody = false; \r", + " }\r", + "\r", + " pm.expect(emptyBody).to.be.true;\r", + "}\r", + "\r", + "function shouldResponseBeEmpty(expectedResponse){\r", + " let responseSchema = expectedResponse.schema;\r", + " if(expectedResponse.$ref){\r", + " let schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", + " responseSchema = getSchemaReference(schema, expectedResponse.$ref);\r", + " if(expectedResponse.$ref.startsWith('#/components/responses')){\r", + " return (!responseSchema || !responseSchema.content || !responseSchema.content['application/json'] \r", + " || !responseSchema.content['application/json'].schema || Object.keys(responseSchema.content['application/json'].schema).length == 0);\r", + " } else {\r", + " return false;\r", + " }\r", + " }\r", + " else {\r", + " return (Object.keys(responseSchema).length == 0);\r", + " }\r", + "}\r", + "\r", + "function getSchemaReference(schema, referenceName){\r", + " const refPieces = referenceName.split('/');\r", + " let reference = schema;\r", + " for(let i = 1; i < refPieces.length; i++){\r", + " reference = reference[refPieces[i]];\r", + " }\r", + "\r", + " return reference;\r", + "}" + ], + "type": "text/javascript" + } + } + ], + "id": "416eff4d-749b-4b49-9c3c-7e6cfbfb7c2c", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{{body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "https://postman-echo.com/get" + }, + "response": [] + } + ], + "id": "5710c277-a099-4c7d-a8ab-9bb911918ef9" + }, + { + "name": "Finalize", + "item": [ + { + "name": "More APIs to Process?", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "let apis = pm.collectionVariables.get('coll-apiIds');\r", + "if(apis){\r", + " try{\r", + " apis = JSON.parse(apis);\r", + " if(apis.length > 0){\r", + " postman.setNextRequest('Get Current API Version');\r", + " }\r", + " }\r", + " catch(err){} \r", + "}" + ], + "type": "text/javascript", + "id": "c236b1d1-9f04-4502-b754-ee4d6430a3ed" + } + } + ], + "id": "eafd2509-05d8-42ee-ab2d-dd7f45453f5c", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": "https://postman-echo.com/delay/0" + }, + "response": [] + }, + { + "name": "Remove Test Variables", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "// See https://blog.postman.com/2019/05/28/pro-tip-dynamically-unset-postman-environment-variables/\r", + "// for more details on what we're doing here. \r", + "\r", + "cleanupCollectionVariables();\r", + "\r", + "function cleanupCollectionVariables() {\r", + " const clean = _.keys(pm.collectionVariables.toObject());\r", + "\r", + " _.each(clean, (arrItem) => {\r", + " pm.collectionVariables.unset(arrItem);\r", + " });\r", + "}" + ], + "type": "text/javascript", + "id": "168455e5-e394-48df-902d-dbab8352acab" + } + }, + { + "listen": "test", + "script": { + "exec": [""], + "type": "text/javascript", + "id": "ac89d252-3423-481b-9ba5-d0b3f55ca383" + } + } + ], + "id": "6e4c0ea7-b3c4-4e33-bc13-ac7ab563f57b", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "url": "https://postman-echo.com/delay/0" + }, + "response": [] + } + ], + "id": "92035d83-c9a1-425a-8a69-65bc677fbc13" + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [""], + "id": "38efdb22-19c6-42a8-aa8b-ef1271ce04ef" + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [""], + "id": "c48549ed-e6df-407a-8452-ec1db5cc81eb" + } + } + ], + "variable": [ + { + "key": "coll-schema", + "value": "" + }, + { + "key": "coll-baseUrl", + "value": "" + }, + { + "key": "coll-schemaTests", + "value": "" + } + ] +} From 4e1259864d070ebe475d77d291b4108d51e421cf Mon Sep 17 00:00:00 2001 From: Kaspar Peterson Date: Mon, 27 Nov 2023 15:17:31 +0200 Subject: [PATCH 2/8] Adds auth option to schemas. --- schemas/openapi.json | 240 +++++++++++++++++-------------------------- schemas/openapi.yml | 33 +++++- 2 files changed, 124 insertions(+), 149 deletions(-) diff --git a/schemas/openapi.json b/schemas/openapi.json index 7acfe839..2afc0701 100644 --- a/schemas/openapi.json +++ b/schemas/openapi.json @@ -66,10 +66,7 @@ { "type": "object", "description": "Definition of an agent task.", - "required": [ - "task_id", - "artifacts" - ], + "required": ["task_id", "artifacts"], "properties": { "task_id": { "description": "The ID of the task.", @@ -146,8 +143,12 @@ "description": "Internal Server Error" } }, - "tags": [ - "agent" + "tags": ["agent"], + "security": [ + { + "HTTPBearer": [] + }, + {} ] }, "get": { @@ -213,10 +214,7 @@ { "type": "object", "description": "Definition of an agent task.", - "required": [ - "task_id", - "artifacts" - ], + "required": ["task_id", "artifacts"], "properties": { "task_id": { "description": "The ID of the task.", @@ -301,10 +299,7 @@ ] } }, - "required": [ - "tasks", - "pagination" - ] + "required": ["tasks", "pagination"] } } } @@ -313,8 +308,12 @@ "description": "Internal Server Error" } }, - "tags": [ - "agent" + "tags": ["agent"], + "security": [ + { + "HTTPBearer": [] + }, + {} ] } }, @@ -367,10 +366,7 @@ { "type": "object", "description": "Definition of an agent task.", - "required": [ - "task_id", - "artifacts" - ], + "required": ["task_id", "artifacts"], "properties": { "task_id": { "description": "The ID of the task.", @@ -438,9 +434,7 @@ "example": "Unable to find entity with the provided id" } }, - "required": [ - "message" - ] + "required": ["message"] } } } @@ -449,8 +443,12 @@ "description": "Internal Server Error" } }, - "tags": [ - "agent" + "tags": ["agent"], + "security": [ + { + "HTTPBearer": [] + }, + {} ] } }, @@ -561,11 +559,7 @@ "description": "The status of the task step.", "type": "string", "example": "created", - "enum": [ - "created", - "running", - "completed" - ] + "enum": ["created", "running", "completed"] }, "output": { "description": "Output of the task step.", @@ -659,10 +653,7 @@ ] } }, - "required": [ - "steps", - "pagination" - ] + "required": ["steps", "pagination"] } } } @@ -680,9 +671,7 @@ "example": "Unable to find entity with the provided id" } }, - "required": [ - "message" - ] + "required": ["message"] } } } @@ -691,8 +680,12 @@ "description": "Internal Server Error" } }, - "tags": [ - "agent" + "tags": ["agent"], + "security": [ + { + "HTTPBearer": [] + }, + {} ] }, "post": { @@ -793,11 +786,7 @@ "description": "The status of the task step.", "type": "string", "example": "created", - "enum": [ - "created", - "running", - "completed" - ] + "enum": ["created", "running", "completed"] }, "output": { "description": "Output of the task step.", @@ -881,9 +870,7 @@ "example": "Unable to find entity with the provided id" } }, - "required": [ - "message" - ] + "required": ["message"] } } } @@ -903,8 +890,12 @@ "description": "Internal Server Error" } }, - "tags": [ - "agent" + "tags": ["agent"], + "security": [ + { + "HTTPBearer": [] + }, + {} ] } }, @@ -1004,11 +995,7 @@ "description": "The status of the task step.", "type": "string", "example": "created", - "enum": [ - "created", - "running", - "completed" - ] + "enum": ["created", "running", "completed"] }, "output": { "description": "Output of the task step.", @@ -1085,9 +1072,7 @@ "example": "Unable to find entity with the provided id" } }, - "required": [ - "message" - ] + "required": ["message"] } } } @@ -1096,8 +1081,12 @@ "description": "Internal Server Error" } }, - "tags": [ - "agent" + "tags": ["agent"], + "security": [ + { + "HTTPBearer": [] + }, + {} ] } }, @@ -1224,10 +1213,7 @@ ] } }, - "required": [ - "artifacts", - "pagination" - ] + "required": ["artifacts", "pagination"] } } } @@ -1245,9 +1231,7 @@ "example": "Unable to find entity with the provided id" } }, - "required": [ - "message" - ] + "required": ["message"] } } } @@ -1256,8 +1240,12 @@ "description": "Internal Server Error" } }, - "tags": [ - "agent" + "tags": ["agent"], + "security": [ + { + "HTTPBearer": [] + }, + {} ] }, "post": { @@ -1300,9 +1288,7 @@ "example": "python/code" } }, - "required": [ - "file" - ] + "required": ["file"] } } } @@ -1338,11 +1324,7 @@ "nullable": true } }, - "required": [ - "artifact_id", - "agent_created", - "file_name" - ] + "required": ["artifact_id", "agent_created", "file_name"] } } } @@ -1360,9 +1342,7 @@ "example": "Unable to find entity with the provided id" } }, - "required": [ - "message" - ] + "required": ["message"] } } } @@ -1371,8 +1351,12 @@ "description": "Internal Server Error" } }, - "tags": [ - "agent" + "tags": ["agent"], + "security": [ + { + "HTTPBearer": [] + }, + {} ] } }, @@ -1433,9 +1417,7 @@ "example": "Unable to find entity with the provided id" } }, - "required": [ - "message" - ] + "required": ["message"] } } } @@ -1444,8 +1426,12 @@ "description": "Internal Server Error" } }, - "tags": [ - "agent" + "tags": ["agent"], + "security": [ + { + "HTTPBearer": [] + }, + {} ] } } @@ -1476,12 +1462,7 @@ "example": 25 } }, - "required": [ - "total_items", - "total_pages", - "current_page", - "page_size" - ] + "required": ["total_items", "total_pages", "current_page", "page_size"] }, "TaskListResponse": { "type": "object", @@ -1510,10 +1491,7 @@ { "type": "object", "description": "Definition of an agent task.", - "required": [ - "task_id", - "artifacts" - ], + "required": ["task_id", "artifacts"], "properties": { "task_id": { "description": "The ID of the task.", @@ -1598,10 +1576,7 @@ ] } }, - "required": [ - "tasks", - "pagination" - ] + "required": ["tasks", "pagination"] }, "TaskStepsListResponse": { "type": "object", @@ -1657,11 +1632,7 @@ "description": "The status of the task step.", "type": "string", "example": "created", - "enum": [ - "created", - "running", - "completed" - ] + "enum": ["created", "running", "completed"] }, "output": { "description": "Output of the task step.", @@ -1755,10 +1726,7 @@ ] } }, - "required": [ - "steps", - "pagination" - ] + "required": ["steps", "pagination"] }, "TaskArtifactsListResponse": { "type": "object", @@ -1791,11 +1759,7 @@ "nullable": true } }, - "required": [ - "artifact_id", - "agent_created", - "file_name" - ] + "required": ["artifact_id", "agent_created", "file_name"] } }, "pagination": { @@ -1830,10 +1794,7 @@ ] } }, - "required": [ - "artifacts", - "pagination" - ] + "required": ["artifacts", "pagination"] }, "TaskInput": { "description": "Input parameters for the task. Any value is allowed.", @@ -1866,11 +1827,7 @@ "nullable": true } }, - "required": [ - "artifact_id", - "agent_created", - "file_name" - ] + "required": ["artifact_id", "agent_created", "file_name"] }, "ArtifactUpload": { "description": "Artifact to upload to the agent.", @@ -1888,9 +1845,7 @@ "example": "python/code" } }, - "required": [ - "file" - ] + "required": ["file"] }, "StepInput": { "description": "Input parameters for the task step. Any value is allowed.", @@ -1942,10 +1897,7 @@ { "type": "object", "description": "Definition of an agent task.", - "required": [ - "task_id", - "artifacts" - ], + "required": ["task_id", "artifacts"], "properties": { "task_id": { "description": "The ID of the task.", @@ -1981,11 +1933,7 @@ "nullable": true } }, - "required": [ - "artifact_id", - "agent_created", - "file_name" - ] + "required": ["artifact_id", "agent_created", "file_name"] }, "example": [ "7a49f31c-f9c6-4346-a22c-e32bc5af4d8e", @@ -2063,11 +2011,7 @@ "description": "The status of the task step.", "type": "string", "example": "created", - "enum": [ - "created", - "running", - "completed" - ] + "enum": ["created", "running", "completed"] }, "output": { "description": "Output of the task step.", @@ -2110,11 +2054,7 @@ "nullable": true } }, - "required": [ - "artifact_id", - "agent_created", - "file_name" - ] + "required": ["artifact_id", "agent_created", "file_name"] }, "default": [] }, @@ -2154,13 +2094,17 @@ "example": "Unable to find entity with the provided id" } }, - "required": [ - "message" - ] + "required": ["message"] } } } } + }, + "securitySchemes": { + "HTTPBearer": { + "type": "http", + "scheme": "bearer" + } } } } diff --git a/schemas/openapi.yml b/schemas/openapi.yml index 0a17f5b1..bf0f902f 100644 --- a/schemas/openapi.yml +++ b/schemas/openapi.yml @@ -33,6 +33,9 @@ paths: description: Internal Server Error tags: - agent + security: + - HTTPBearer: [] + - {} get: operationId: listAgentTasks summary: List all tasks that have been created for the agent. @@ -68,6 +71,9 @@ paths: description: Internal Server Error tags: - agent + security: + - HTTPBearer: [] + - {} '/ap/v1/agent/tasks/{task_id}': get: operationId: getAgentTask @@ -96,6 +102,9 @@ paths: description: Internal Server Error tags: - agent + security: + - HTTPBearer: [] + - {} '/ap/v1/agent/tasks/{task_id}/steps': get: operationId: listAgentTaskSteps @@ -144,6 +153,9 @@ paths: description: Internal Server Error tags: - agent + security: + - HTTPBearer: [] + - {} post: operationId: executeAgentTaskStep summary: Execute a step in the specified agent task. @@ -182,6 +194,9 @@ paths: description: Internal Server Error tags: - agent + security: + - HTTPBearer: [] + - {} '/ap/v1/agent/tasks/{task_id}/steps/{step_id}': get: operationId: getAgentTaskStep @@ -222,6 +237,9 @@ paths: description: Internal Server Error tags: - agent + security: + - HTTPBearer: [] + - {} '/ap/v1/agent/tasks/{task_id}/artifacts': get: operationId: listAgentTaskArtifacts @@ -270,6 +288,9 @@ paths: description: Internal Server Error tags: - agent + security: + - HTTPBearer: [] + - {} post: operationId: uploadAgentTaskArtifacts summary: Upload an artifact for the specified task. @@ -302,6 +323,9 @@ paths: description: Internal Server Error tags: - agent + security: + - HTTPBearer: [] + - {} '/ap/v1/agent/tasks/{task_id}/artifacts/{artifact_id}': get: operationId: downloadAgentTaskArtifact @@ -338,6 +362,9 @@ paths: description: Internal Server Error tags: - agent + security: + - HTTPBearer: [] + - {} components: schemas: Pagination: @@ -543,7 +570,7 @@ components: output: description: Output of the task step. type: string - example: 'I am going to use the write_to_file command and write Washington to a file called output.txt Date: Sun, 11 Feb 2024 04:14:29 +1100 Subject: [PATCH 3/8] reverted styling changes --- .github/workflows/js-sdk-publish.yaml | 4 +- .github/workflows/python-client-publish.yaml | 2 +- .github/workflows/python-sdk-publish.yaml | 2 +- .github/workflows/update-docs.yaml | 4 +- .prettierignore | 1 + .prettierrc | 10 ++- CODE_OF_CONDUCT.md | 21 +++--- .../agent_protocol_client/docs/AgentApi.md | 4 +- packages/sdk/js/README.md | 1 - packages/sdk/js/src/agent.ts | 72 +++++++++---------- packages/sdk/js/src/api.ts | 57 ++++++++------- packages/sdk/js/src/index.ts | 2 +- packages/sdk/js/src/models.ts | 14 ++-- rfcs/2023-08-28-Pagination-RFC.md | 13 ++-- rfcs/2023-08-28-agent-created-RFC-.md | 11 ++- rfcs/2023-08-28-list-entities-RFC.md | 12 ++-- rfcs/2023-09-15-endpoint-schema.md | 12 ++-- 17 files changed, 122 insertions(+), 120 deletions(-) create mode 100644 .prettierignore diff --git a/.github/workflows/js-sdk-publish.yaml b/.github/workflows/js-sdk-publish.yaml index 2a2c4ef6..a0f991f1 100644 --- a/.github/workflows/js-sdk-publish.yaml +++ b/.github/workflows/js-sdk-publish.yaml @@ -20,8 +20,8 @@ jobs: - name: Setup Node uses: actions/setup-node@v3 with: - node-version: '16.x' - registry-url: 'https://registry.npmjs.org' + node-version: "16.x" + registry-url: "https://registry.npmjs.org" cache: npm cache-dependency-path: package-lock.json diff --git a/.github/workflows/python-client-publish.yaml b/.github/workflows/python-client-publish.yaml index 6c6a67ef..0b2e8221 100644 --- a/.github/workflows/python-client-publish.yaml +++ b/.github/workflows/python-client-publish.yaml @@ -21,7 +21,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: "3.10" - name: Install and configure Poetry uses: snok/install-poetry@v1 diff --git a/.github/workflows/python-sdk-publish.yaml b/.github/workflows/python-sdk-publish.yaml index 60ea0d5f..921105c6 100644 --- a/.github/workflows/python-sdk-publish.yaml +++ b/.github/workflows/python-sdk-publish.yaml @@ -21,7 +21,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: "3.10" - name: Install and configure Poetry uses: snok/install-poetry@v1 diff --git a/.github/workflows/update-docs.yaml b/.github/workflows/update-docs.yaml index a9202e0a..d508fd41 100644 --- a/.github/workflows/update-docs.yaml +++ b/.github/workflows/update-docs.yaml @@ -3,7 +3,7 @@ name: Regenerate Endpoints docs on change on: push: paths: - - 'schemas/openapi.yml' + - "schemas/openapi.yml" permissions: contents: write @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@v3 - uses: actions/setup-node@v1 with: - node-version: '16.x' + node-version: "16.x" - run: npm i - run: npm run generateEndpoints diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..dd449725 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +*.md diff --git a/.prettierrc b/.prettierrc index fa51da29..d3a05ceb 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,5 +2,13 @@ "trailingComma": "es5", "tabWidth": 2, "semi": false, - "singleQuote": true + "singleQuote": true, + "overrides": [ + { + "files": "*.yaml", + "options": { + "singleQuote": false + } + } + ] } diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 14dd214a..0ec871c8 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -17,23 +17,24 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, +* Demonstrating empathy and kindness toward other people + +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -- Focusing on what is best not just for us as individuals, but for the overall +* Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: -- The use of sexualized language or imagery, and sexual attention or advances of +* The use of sexualized language or imagery, and sexual attention or advances of any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email address, +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a +* Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities diff --git a/packages/client/python/agent_protocol_client/docs/AgentApi.md b/packages/client/python/agent_protocol_client/docs/AgentApi.md index ba3a9c70..da18a12c 100644 --- a/packages/client/python/agent_protocol_client/docs/AgentApi.md +++ b/packages/client/python/agent_protocol_client/docs/AgentApi.md @@ -2,8 +2,8 @@ All URIs are relative to _http://localhost_ -| Method | HTTP request | Description | -| ---------------------------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------- | +| Method | HTTP request | Description | +| ---------------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------- | | [**create_agent_task**](AgentApi.md#create_agent_task) | **POST** /ap/v1/agent/tasks | Creates a task for the agent. | | [**download_agent_task_artifact**](AgentApi.md#download_agent_task_artifact) | **GET** /ap/v1/agent/tasks/{task_id}/artifacts/{artifact_id} | Download a specified artifact. | | [**execute_agent_task_step**](AgentApi.md#execute_agent_task_step) | **POST** /ap/v1/agent/tasks/{task_id}/steps | Execute a step in the specified agent task. | diff --git a/packages/sdk/js/README.md b/packages/sdk/js/README.md index b5368fb0..cf8e8feb 100644 --- a/packages/sdk/js/README.md +++ b/packages/sdk/js/README.md @@ -43,7 +43,6 @@ Agent.handleTask(taskHandler).start() You can find more info and examples in the [docs](https://agentprotocol.ai/sdks/js). ## Contributing - ```bash git clone https://github.com/AI-Engineers-Foundation/agent-protocol cd agent-protocol/sdk/js diff --git a/packages/sdk/js/src/agent.ts b/packages/sdk/js/src/agent.ts index 91828822..80c12100 100644 --- a/packages/sdk/js/src/agent.ts +++ b/packages/sdk/js/src/agent.ts @@ -17,11 +17,11 @@ import { import { createApi, ApiApp, - ApiConfig, - RouteRegisterFn, - RouteContext, + type ApiConfig, + type RouteRegisterFn, + type RouteContext, } from './api' -import { Router } from 'express' +import { type Router } from 'express' /** * A function that handles a step in a task. @@ -33,7 +33,7 @@ export type StepHandler = (input: StepInput | null) => Promise * Returns a step handler. */ export type TaskHandler = ( - taskId: String, + taskId: string, input: TaskInput | null ) => Promise @@ -189,7 +189,7 @@ export const executeAgentTaskStep = async ( } // If there are artifacts in the step, append them to the task's artifacts array (or initialize it if necessary) - if (step.artifacts && step.artifacts.length > 0) { + if (step.artifacts != null && step.artifacts.length > 0) { task[0].artifacts = task[0].artifacts ?? [] task[0].artifacts.push(...step.artifacts) } @@ -251,7 +251,7 @@ export const getArtifacts = async ( taskId: string ): Promise => { const task = await getAgentTask(taskId) - return task.artifacts + return task.artifacts; } const registerGetArtifacts: RouteRegisterFn = (router: Router) => { router.get('/agent/tasks/:task_id/artifacts', (req, res) => { @@ -259,10 +259,10 @@ const registerGetArtifacts: RouteRegisterFn = (router: Router) => { const taskId = req.params.task_id try { const artifacts = await getArtifacts(taskId) - const current_page = Number(req.query['current_page']) || 1 - const page_size = Number(req.query['page_size']) || 10 + const current_page = Number(req.query.current_page) || 1 + const page_size = Number(req.query.page_size) || 10 - if (!artifacts) { + if (artifacts == null) { res.status(200).send({ artifacts: [], pagination: { @@ -309,9 +309,9 @@ export const getArtifactPath = ( workspace: string, artifact: Artifact ): string => { - const rootDir = path.isAbsolute(workspace) - ? workspace - : path.join(process.cwd(), workspace) + const rootDir = path.isAbsolute(workspace) ? + workspace : + path.join(process.cwd(), workspace) return path.join( rootDir, @@ -341,20 +341,21 @@ export const createArtifact = async ( relative_path: relativePath || null, created_at: Date.now().toString(), } - task.artifacts = task.artifacts || [] + task.artifacts = task.artifacts != null || [] task.artifacts.push(artifact) - const artifactFolderPath = getArtifactPath(task.task_id, workspace, artifact) + const artifactFolderPath = getArtifactPath( + task.task_id, + workspace, + artifact + ) // Save file to server's file system fs.mkdirSync(path.join(artifactFolderPath, '..'), { recursive: true }) fs.writeFileSync(artifactFolderPath, file.buffer) return artifact } -const registerCreateArtifact: RouteRegisterFn = ( - router: Router, - context: RouteContext -) => { +const registerCreateArtifact: RouteRegisterFn = (router: Router, context: RouteContext) => { router.post('/agent/tasks/:task_id/artifacts', (req, res) => { void (async () => { try { @@ -362,14 +363,14 @@ const registerCreateArtifact: RouteRegisterFn = ( const relativePath = req.body.relative_path const task = tasks.find(([{ task_id }]) => task_id == taskId) - if (!task) { + if (task == null) { res .status(404) .json({ message: 'Unable to find task with the provided id' }) } - const files = req.files as Array - let file = files.find(({ fieldname }) => fieldname == 'file') + const files = req.files as Express.Multer.File[] + const file = files.find(({ fieldname }) => fieldname == 'file') const artifact = await createArtifact( context.workspace, task[0], @@ -397,28 +398,21 @@ export const getTaskArtifact = async ( ): Promise => { const task = await getAgentTask(taskId) const artifact = task.artifacts?.find((a) => a.artifact_id === artifactId) - if (!artifact) { + if (artifact == null) { throw new Error( `Artifact with id ${artifactId} in task with id ${taskId} was not found` ) } return artifact } -const registerGetTaskArtifact: RouteRegisterFn = ( - router: Router, - context: RouteContext -) => { +const registerGetTaskArtifact: RouteRegisterFn = (router: Router, context: RouteContext) => { router.get('/agent/tasks/:task_id/artifacts/:artifact_id', (req, res) => { void (async () => { const taskId = req.params.task_id const artifactId = req.params.artifact_id try { const artifact = await getTaskArtifact(taskId, artifactId) - const artifactPath = getArtifactPath( - taskId, - context.workspace, - artifact - ) + const artifactPath = getArtifactPath(taskId, context.workspace, artifact) res.status(200).sendFile(artifactPath) } catch (err: Error | any) { console.error(err) @@ -429,20 +423,20 @@ const registerGetTaskArtifact: RouteRegisterFn = ( } export interface AgentConfig { - port: number - workspace: string + port: number; + workspace: string; } export const defaultAgentConfig: AgentConfig = { port: 8000, - workspace: './workspace', + workspace: "./workspace", } export class Agent { constructor( public taskHandler: TaskHandler, public config: AgentConfig - ) {} + ) { } static handleTask( _taskHandler: TaskHandler, @@ -452,7 +446,7 @@ export class Agent { return new Agent(_taskHandler, { workspace: config.workspace || defaultAgentConfig.workspace, port: config.port || defaultAgentConfig.port, - }) + }); } start(port?: number): void { @@ -467,13 +461,13 @@ export class Agent { registerGetAgentTaskStep, registerGetArtifacts, registerCreateArtifact, - registerGetTaskArtifact, + registerGetTaskArtifact ], callback: () => { console.log(`Agent listening at http://localhost:${this.config.port}`) }, context: { - workspace: this.config.workspace, + workspace: this.config.workspace }, } diff --git a/packages/sdk/js/src/api.ts b/packages/sdk/js/src/api.ts index a9346845..636c5918 100644 --- a/packages/sdk/js/src/api.ts +++ b/packages/sdk/js/src/api.ts @@ -1,33 +1,36 @@ -import * as OpenApiValidator from 'express-openapi-validator' -import yaml from 'js-yaml' -import express, { Router } from 'express' // <-- Import Router -import * as core from 'express-serve-static-core' +import * as OpenApiValidator from "express-openapi-validator"; +import yaml from "js-yaml"; +import express, { Router } from "express"; // <-- Import Router +import * as core from "express-serve-static-core"; -import spec from '../agent-protocol/schemas/openapi.yml' +import spec from "../agent-protocol/schemas/openapi.yml"; -export type ApiApp = core.Express +export type ApiApp = core.Express; export interface RouteContext { - workspace: string + workspace: string; } -export type RouteRegisterFn = (app: Router, context: RouteContext) => void +export type RouteRegisterFn = ( + app: Router, + context: RouteContext +) => void; export interface ApiConfig { - context: RouteContext - port: number - callback?: () => void - routes: RouteRegisterFn[] + context: RouteContext; + port: number; + callback?: () => void; + routes: RouteRegisterFn[]; } export const createApi = (config: ApiConfig) => { - const app = express() + const app = express(); - app.use(express.json()) - app.use(express.text()) - app.use(express.urlencoded({ extended: false })) + app.use(express.json()); + app.use(express.text()); + app.use(express.urlencoded({ extended: false })); - const parsedSpec = yaml.load(spec) + const parsedSpec = yaml.load(spec); app.use( OpenApiValidator.middleware({ @@ -35,18 +38,18 @@ export const createApi = (config: ApiConfig) => { validateRequests: true, // (default) validateResponses: true, // false by default }) - ) + ); - app.get('/openapi.yaml', (_, res) => { - res.setHeader('Content-Type', 'text/yaml').status(200).send(spec) - }) + app.get("/openapi.yaml", (_, res) => { + res.setHeader("Content-Type", "text/yaml").status(200).send(spec); + }); - const router = Router() + const router = Router(); config.routes.map((route) => { - route(router, config.context) - }) + route(router, config.context); + }); - app.use('/ap/v1', router) - app.listen(config.port, config.callback) -} + app.use("/ap/v1", router); + app.listen(config.port, config.callback); +}; diff --git a/packages/sdk/js/src/index.ts b/packages/sdk/js/src/index.ts index d52b14fd..c0b39a84 100644 --- a/packages/sdk/js/src/index.ts +++ b/packages/sdk/js/src/index.ts @@ -41,6 +41,6 @@ export { getAgentTaskStep, } -export { v4 } from 'uuid' +export { v4 } from "uuid" export default Agent diff --git a/packages/sdk/js/src/models.ts b/packages/sdk/js/src/models.ts index 194ddd2e..02198a7d 100644 --- a/packages/sdk/js/src/models.ts +++ b/packages/sdk/js/src/models.ts @@ -7,10 +7,10 @@ export type TaskInput = any * Artifact that the task has produced. Any value is allowed. */ export type Artifact = { - artifact_id: string - agent_created: boolean - file_name: string - relative_path: string | null + artifact_id: string, + agent_created: boolean, + file_name: string, + relative_path: string | null, created_at: string } @@ -25,9 +25,9 @@ export type StepInput = any export type StepOutput = any export enum StepStatus { - CREATED = 'created', - RUNNING = 'running', - COMPLETED = 'completed', + CREATED = "created", + RUNNING = "running", + COMPLETED = "completed", } export interface Step { diff --git a/rfcs/2023-08-28-Pagination-RFC.md b/rfcs/2023-08-28-Pagination-RFC.md index bdeea4f3..583787f4 100644 --- a/rfcs/2023-08-28-Pagination-RFC.md +++ b/rfcs/2023-08-28-Pagination-RFC.md @@ -1,11 +1,11 @@ # List tasks, artifacts and steps in a paginated way. -| Feature name | Support Pagination | -| :------------ | :---------------------------------------------------------------------------- | -| **Author(s)** | Merwane Hamadi (merwanehamadi@gmail.com) Craig Swift (craigswift13@gmail.com) | -| **RFC PR:** | [PR 53](https://github.com/e2b-dev/agent-protocol/pull/53) | -| **Updated** | 2023-08-28 | -| **Obsoletes** | | +| Feature name | Support Pagination | +| :------------ |:-----------------------------------------| +| **Author(s)** | Merwane Hamadi (merwanehamadi@gmail.com) Craig Swift (craigswift13@gmail.com)| +| **RFC PR:** | [PR 53](https://github.com/e2b-dev/agent-protocol/pull/53) | +| **Updated** | 2023-08-28 | +| **Obsoletes** | ## Summary @@ -24,7 +24,6 @@ Every app needs this. It's not really farfetched Query parameters for now. ### Alternatives Considered - - query parameter is the simplest, leanest design. We can add more later (body, headers, etc) => let's start lean. - for now, we won't add the pages in the response of the requests, this is another RFC. diff --git a/rfcs/2023-08-28-agent-created-RFC-.md b/rfcs/2023-08-28-agent-created-RFC-.md index e9f0e4a0..cea9b583 100644 --- a/rfcs/2023-08-28-agent-created-RFC-.md +++ b/rfcs/2023-08-28-agent-created-RFC-.md @@ -1,18 +1,17 @@ # Tell the user whether the artifact is agent generated or not -| Feature name | Artifact Created At | -| :------------ | :---------------------------------------------------------------------------- | +| Feature name | Artifact Created At | +|:--------------|:-----------------------------------------| | **Author(s)** | Merwane Hamadi (merwanehamadi@gmail.com) Craig Swift (craigswift13@gmail.com) | -| **RFC PR:** | | -| **Created** | 2023-08-28 | -| **Obsoletes** | | +| **RFC PR:** | | +| **Created** | 2023-08-28 | +| **Obsoletes** | | ## Summary Add agent_created to the artifact response body. ## Motivation - If we don't know whether an artifact is generated by the agent or not, it's hard to know what the agent did or did not do. ## Agent Builders Benefit diff --git a/rfcs/2023-08-28-list-entities-RFC.md b/rfcs/2023-08-28-list-entities-RFC.md index b5e6dbca..43cb4c4d 100644 --- a/rfcs/2023-08-28-list-entities-RFC.md +++ b/rfcs/2023-08-28-list-entities-RFC.md @@ -1,11 +1,11 @@ # List tasks, artifacts and steps in a paginated way. -| Feature name | Support Pagination | -| :------------ | :---------------------------------------------------------------------------- | -| **Author(s)** | Merwane Hamadi (merwanehamadi@gmail.com) Craig Swift (craigswift13@gmail.com) | -| **RFC PR:** | | -| **Updated** | 2023-08-28 | -| **Obsoletes** | | +| Feature name | Support Pagination | +| :------------ |:-----------------------------------------| +| **Author(s)** | Merwane Hamadi (merwanehamadi@gmail.com) Craig Swift (craigswift13@gmail.com)| +| **RFC PR:** | | +| **Updated** | 2023-08-28 | +| **Obsoletes** | | ## Summary diff --git a/rfcs/2023-09-15-endpoint-schema.md b/rfcs/2023-09-15-endpoint-schema.md index 0f7ae97c..ddbb413f 100644 --- a/rfcs/2023-09-15-endpoint-schema.md +++ b/rfcs/2023-09-15-endpoint-schema.md @@ -1,10 +1,10 @@ # Standardized Endpoint Schema -| Feature name | Example name | -| :------------ | :------------------------------------------------------------------------ | -| **Author(s)** | J. Zane Cook (jzanecook@z90.studio) | -| **RFC PR:** | [PR 66](https://github.com/AI-Engineer-Foundation/agent-protocol/pull/66) | -| **Updated** | 2023-09-18 | +| Feature name | Example name | +| :------------ | :------------------------------------------ | +| **Author(s)** | J. Zane Cook (jzanecook@z90.studio) | +| **RFC PR:** | [PR 66](https://github.com/AI-Engineer-Foundation/agent-protocol/pull/66) | +| **Updated** | 2023-09-18 | ## Summary @@ -23,7 +23,6 @@ The motivation for the changes is to simplify the protocol for users while makin ## Design Proposal #### Endpoint Schema Update - Change the current `/agent/` endpoint schema to `/ap/v1/agent/`. This brings clarity in versioning and separates the agent-specific endpoints under a versioned umbrella. Additionally, the `ap` solidifies the `agent-protocol` URL for clear identification of its usage. ### Alternatives Considered @@ -32,7 +31,6 @@ Change the current `/agent/` endpoint schema to `/ap/v1/agent/`. This brings cla - Considered not enforcing the full path, but enforcing the full path not only looks better but also creates a better standard for future improvements. ### Compatibility - These changes are not backwards compatible for the following reasons: - The change in the endpoint schema will break existing client implementations tied to the old URL structure. From 272394492ca8924c91f87cc82a81038eb214b64f Mon Sep 17 00:00:00 2001 From: nalbion Date: Sun, 11 Feb 2024 04:29:24 +1100 Subject: [PATCH 4/8] more reverting prettier changes to reduce noise in PR --- .husky/pre-commit | 2 +- .prettierignore | 1 + package.json | 1 + schemas/openapi.json | 132 +- testing_suite/agent_protocol_v1.json | 2993 +++++++++++++------------- 5 files changed, 1619 insertions(+), 1510 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 5f89fe32..1917bfa9 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npm run ci +# npm run ci diff --git a/.prettierignore b/.prettierignore index dd449725..ebcc9db4 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ *.md +*.json diff --git a/package.json b/package.json index a9606037..dba0039d 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "scripts": { "ci": "prettier . -c && eslint . --ext .ts,.tsx", "prettier": "prettier . -w", + "prettier:fix": "prettier --write .", "lint": "eslint . --ext .ts,.tsx --fix", "prepare": "husky install" }, diff --git a/schemas/openapi.json b/schemas/openapi.json index 2afc0701..38bcb415 100644 --- a/schemas/openapi.json +++ b/schemas/openapi.json @@ -66,7 +66,10 @@ { "type": "object", "description": "Definition of an agent task.", - "required": ["task_id", "artifacts"], + "required": [ + "task_id", + "artifacts" + ], "properties": { "task_id": { "description": "The ID of the task.", @@ -143,7 +146,9 @@ "description": "Internal Server Error" } }, - "tags": ["agent"], + "tags": [ + "agent" + ], "security": [ { "HTTPBearer": [] @@ -214,7 +219,10 @@ { "type": "object", "description": "Definition of an agent task.", - "required": ["task_id", "artifacts"], + "required": [ + "task_id", + "artifacts" + ], "properties": { "task_id": { "description": "The ID of the task.", @@ -299,7 +307,10 @@ ] } }, - "required": ["tasks", "pagination"] + "required": [ + "tasks", + "pagination" + ] } } } @@ -308,7 +319,9 @@ "description": "Internal Server Error" } }, - "tags": ["agent"], + "tags": [ + "agent" + ], "security": [ { "HTTPBearer": [] @@ -559,7 +572,11 @@ "description": "The status of the task step.", "type": "string", "example": "created", - "enum": ["created", "running", "completed"] + "enum": [ + "created", + "running", + "completed" + ] }, "output": { "description": "Output of the task step.", @@ -653,7 +670,10 @@ ] } }, - "required": ["steps", "pagination"] + "required": [ + "steps", + "pagination" + ] } } } @@ -671,7 +691,9 @@ "example": "Unable to find entity with the provided id" } }, - "required": ["message"] + "required": [ + "message" + ] } } } @@ -786,7 +808,11 @@ "description": "The status of the task step.", "type": "string", "example": "created", - "enum": ["created", "running", "completed"] + "enum": [ + "created", + "running", + "completed" + ] }, "output": { "description": "Output of the task step.", @@ -870,7 +896,9 @@ "example": "Unable to find entity with the provided id" } }, - "required": ["message"] + "required": [ + "message" + ] } } } @@ -995,7 +1023,11 @@ "description": "The status of the task step.", "type": "string", "example": "created", - "enum": ["created", "running", "completed"] + "enum": [ + "created", + "running", + "completed" + ] }, "output": { "description": "Output of the task step.", @@ -1072,7 +1104,9 @@ "example": "Unable to find entity with the provided id" } }, - "required": ["message"] + "required": [ + "message" + ] } } } @@ -1081,7 +1115,9 @@ "description": "Internal Server Error" } }, - "tags": ["agent"], + "tags": [ + "agent" + ], "security": [ { "HTTPBearer": [] @@ -1213,7 +1249,10 @@ ] } }, - "required": ["artifacts", "pagination"] + "required": [ + "artifacts", + "pagination" + ] } } } @@ -1231,7 +1270,9 @@ "example": "Unable to find entity with the provided id" } }, - "required": ["message"] + "required": [ + "message" + ] } } } @@ -1240,7 +1281,9 @@ "description": "Internal Server Error" } }, - "tags": ["agent"], + "tags": [ + "agent" + ], "security": [ { "HTTPBearer": [] @@ -1324,7 +1367,11 @@ "nullable": true } }, - "required": ["artifact_id", "agent_created", "file_name"] + "required": [ + "artifact_id", + "agent_created", + "file_name" + ] } } } @@ -1342,7 +1389,9 @@ "example": "Unable to find entity with the provided id" } }, - "required": ["message"] + "required": [ + "message" + ] } } } @@ -1351,7 +1400,9 @@ "description": "Internal Server Error" } }, - "tags": ["agent"], + "tags": [ + "agent" + ], "security": [ { "HTTPBearer": [] @@ -1417,7 +1468,9 @@ "example": "Unable to find entity with the provided id" } }, - "required": ["message"] + "required": [ + "message" + ] } } } @@ -1462,7 +1515,12 @@ "example": 25 } }, - "required": ["total_items", "total_pages", "current_page", "page_size"] + "required": [ + "total_items", + "total_pages", + "current_page", + "page_size" + ] }, "TaskListResponse": { "type": "object", @@ -1491,7 +1549,10 @@ { "type": "object", "description": "Definition of an agent task.", - "required": ["task_id", "artifacts"], + "required": [ + "task_id", + "artifacts" + ], "properties": { "task_id": { "description": "The ID of the task.", @@ -1576,7 +1637,10 @@ ] } }, - "required": ["tasks", "pagination"] + "required": [ + "tasks", + "pagination" + ] }, "TaskStepsListResponse": { "type": "object", @@ -1726,7 +1790,10 @@ ] } }, - "required": ["steps", "pagination"] + "required": [ + "steps", + "pagination" + ] }, "TaskArtifactsListResponse": { "type": "object", @@ -1759,7 +1826,11 @@ "nullable": true } }, - "required": ["artifact_id", "agent_created", "file_name"] + "required": [ + "artifact_id", + "agent_created", + "file_name" + ] } }, "pagination": { @@ -1794,7 +1865,10 @@ ] } }, - "required": ["artifacts", "pagination"] + "required": [ + "artifacts", + "pagination" + ] }, "TaskInput": { "description": "Input parameters for the task. Any value is allowed.", @@ -1827,7 +1901,11 @@ "nullable": true } }, - "required": ["artifact_id", "agent_created", "file_name"] + "required": [ + "artifact_id", + "agent_created", + "file_name" + ] }, "ArtifactUpload": { "description": "Artifact to upload to the agent.", diff --git a/testing_suite/agent_protocol_v1.json b/testing_suite/agent_protocol_v1.json index 8c9561ed..e4a94332 100644 --- a/testing_suite/agent_protocol_v1.json +++ b/testing_suite/agent_protocol_v1.json @@ -1,1484 +1,1513 @@ { - "info": { - "_postman_id": "84d6bd70-4a9f-4470-be02-f2f9089ec69b", - "name": "Agent Protocol - REST v1", - "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" - }, - "item": [ - { - "name": "Basic User Experience", - "item": [ - { - "name": "Cleanup Previous Run Copy", - "event": [ - { - "listen": "prerequest", - "script": { - "id": "6e0312e1-1276-47b2-92be-b481545de5fb", - "exec": [ - "// See https://blog.postman.com/2019/05/28/pro-tip-dynamically-unset-postman-environment-variables/\r", - "// for more details on what we're doing here. \r", - "\r", - "cleanupCollectionVariables();\r", - "\r", - "function cleanupCollectionVariables() {\r", - " const clean = _.keys(pm.collectionVariables.toObject());\r", - "\r", - " _.each(clean, (arrItem) => {\r", - " pm.collectionVariables.unset(arrItem);\r", - " });\r", - "}" - ], - "type": "text/javascript" - } - }, - { - "listen": "test", - "script": { - "id": "acaabbd4-94fc-4444-8718-b9ca2c087721", - "exec": [""], - "type": "text/javascript" - } - } - ], - "id": "5da26248-515e-4feb-b7b5-c7de25a03857", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "url": "https://postman-echo.com/delay/0" - }, - "response": [] - }, - { - "name": "Get all the tasks", - "event": [ - { - "listen": "test", - "script": { - "id": "32b75035-2164-4ec1-b61c-a86515847037", - "exec": [""], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "id": "98c48159-7553-4f9b-a1e7-dd66b961ec11", - "exec": [""], - "type": "text/javascript" - } - } - ], - "id": "52fdb36d-66c1-41e9-b81a-1084ae813a2d", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [ - { - "key": "mock-match", - "value": "19", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks" - }, - "response": [ - { - "id": "98f96c46-c680-4377-a681-27b93d8425cf", - "name": "mock response", - "originalRequest": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - }, - { - "key": "mock-match", - "value": "19", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks" - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Date", - "value": "Thu, 17 Aug 2023 18:03:12 GMT", - "enabled": true - }, - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - }, - { - "key": "Content-Length", - "value": "150", - "enabled": true - }, - { - "key": "Connection", - "value": "keep-alive", - "enabled": true - }, - { - "key": "x-srv-trace", - "value": "v=1;t=1c95cd08c248d38f", - "enabled": true - }, - { - "key": "x-srv-span", - "value": "v=1;s=2527ca982b2b7c75", - "enabled": true - }, - { - "key": "Access-Control-Allow-Origin", - "value": "*", - "enabled": true - }, - { - "key": "X-RateLimit-Limit", - "value": "120", - "enabled": true - }, - { - "key": "X-RateLimit-Remaining", - "value": "117", - "enabled": true - }, - { - "key": "X-RateLimit-Reset", - "value": "1692295416", - "enabled": true - }, - { - "key": "ETag", - "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", - "enabled": true - }, - { - "key": "Vary", - "value": "Accept-Encoding", - "enabled": true - } - ], - "cookie": [], - "responseTime": null, - "body": "[\n {\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"step_id\": \"1a379290-3abc-11ee-be56-0242ac120002\",\n \"status\": \"completed\",\n \"output\": \"I am going to use the write_to_file method to write the word 'Washington' to a .txt file\",\n \"artifacts\": [],\n \"is_last\": false\n }\n]" - } - ] - }, - { - "name": "Create a new task", - "event": [ - { - "listen": "prerequest", - "script": { - "id": "c5e90382-a818-4e98-9f9b-4a2877e0f129", - "exec": [""], - "type": "text/javascript" - } - }, - { - "listen": "test", - "script": { - "id": "683fd614-1610-4dab-bf88-6ae830186dac", - "exec": [ - "var jsonData = pm.response.json();", - "pm.globals.set(\"task_id\", jsonData.task_id);", - "", - "pm.globals.set(", - " \"step_body\",", - " JSON.stringify(", - " {", - " \"input\": JSON.parse(pm.request.body.raw).input", - " } ", - " )", - ");" - ], - "type": "text/javascript" - } - } - ], - "id": "090b65b7-80e6-4a20-81d7-2a6f72644ad5", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "POST", - "header": [ - { - "key": "mock-match", - "value": "34", - "type": "text" - }, - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\",\n \"additional_input\": {\"test_run_id\": \"123\"}\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks" - }, - "response": [ - { - "id": "5aa13fc7-8099-43ac-81d4-87af8cdbb6fe", - "name": "mock response", - "originalRequest": { - "method": "POST", - "header": [ - { - "key": "mock-match", - "value": "34" - }, - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks" - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Date", - "value": "Sun, 13 Aug 2023 23:23:05 GMT", - "enabled": true - }, - { - "key": "Content-Type", - "value": "application/json; charset=utf-8", - "enabled": true - }, - { - "key": "Content-Length", - "value": "28", - "enabled": true - }, - { - "key": "Connection", - "value": "keep-alive", - "enabled": true - }, - { - "key": "x-srv-trace", - "value": "v=1;t=daef761e243c402b", - "enabled": true - }, - { - "key": "x-srv-span", - "value": "v=1;s=2c0bd88502372360", - "enabled": true - }, - { - "key": "Access-Control-Allow-Origin", - "value": "*", - "enabled": true - }, - { - "key": "X-RateLimit-Limit", - "value": "120", - "enabled": true - }, - { - "key": "X-RateLimit-Remaining", - "value": "115", - "enabled": true - }, - { - "key": "X-RateLimit-Reset", - "value": "1691968334", - "enabled": true - }, - { - "key": "ETag", - "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", - "enabled": true - }, - { - "key": "Vary", - "value": "Accept-Encoding", - "enabled": true - } - ], - "cookie": [], - "responseTime": null, - "body": "{\n \"input\": \"Write the word 'Washington' to a .txt file\",\n \"additional_input\": {},\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"artifacts\": []\n}" - } - ] - }, - { - "name": "Execute a step", - "event": [ - { - "listen": "test", - "script": { - "id": "26426e07-115e-4841-9887-24fd9dccca2a", - "exec": [ - "if (pm.request.url.path[0] == \"agent\" && pm.response.headers.has(\"Content-Type\",`application/json`)) {", - " var artifacts = pm.response.json().artifacts;", - "", - " if (artifacts && artifacts.length > 0) {", - " pm.globals.set(\"artifactId\", artifacts[0].artifact_id);", - " }", - "", - " var artifacts = pm.response.json().artifacts;", - " var existingArtifactId = pm.globals.get(\"artifactId\");", - "/* Commented out artifact checking code and max step code because the SDK doesn't implement simple agents", - " if (artifacts && artifacts.length > 0) {", - " if (artifacts.length > 1) {", - " pm.test(\"This task should only create 1 artifact\", function () {", - " pm.expect.fail(\"More than one artifact was created.\");", - " });", - " } else {", - " pm.globals.set(\"artifactId\", artifacts[0].artifact_id);", - " }", - " }*/", - " stepNumber = pm.collectionVariables.get(\"step-number\") ?? 1", - " const maxSteps = 10", - " if(!pm.response.json().is_last) {", - " /*if (stepNumber > maxSteps) {", - " console.log(`Max steps reached (${maxSteps})`);", - " pm.test(`This task should be completed after ${maxSteps} steps`, function () {", - " pm.expect.fail(`is_last should be true before max steps reached (${maxSteps})`);", - " });", - " } else {", - " console.log(`Steps reached (${stepNumber})`);", - "", - " pm.collectionVariables.set('step-number', stepNumber + 1);", - " pm.globals.set(", - " \"step_body\",", - " JSON.stringify(", - " {", - " \"input\": \"y\"", - " }", - " )", - " );", - "", - " pm.collectionVariables.set('previous-step', pm.response.json().step_id)", - " postman.setNextRequest('Execute the steps until completion');", - " }*/", - " ", - " }", - "}" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "id": "3bdd2e35-1745-439e-9ef1-ea2e003ddc42", - "exec": [ - "stepNumber = pm.collectionVariables.get(\"step-number\") ?? 1", - "console.log(\"Step number:\" + stepNumber)", - "console.log(\"Task Input:\" + pm.globals.get(\"taskInput\"))", - "if (stepNumber) {", - " pm.request.headers.upsert({ key: 'mock-match', value: stepNumber.toString() });", - " console.log(pm.request)", - "}" - ], - "type": "text/javascript" - } - } - ], - "id": "c001b340-752c-45ea-aaa2-22cb52e47297", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{{step_body}}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" - }, - "response": [ - { - "id": "1def41e7-04a8-41d6-bcef-b0149882510d", - "name": "mock response", - "originalRequest": { - "method": "POST", - "header": [ - { - "key": "mock-match", - "value": "1", - "type": "text" - }, - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Date", - "value": "Mon, 14 Aug 2023 16:28:55 GMT", - "enabled": true - }, - { - "key": "Content-Type", - "value": "application/json; charset=utf-8", - "enabled": true - }, - { - "key": "Content-Length", - "value": "275", - "enabled": true - }, - { - "key": "Connection", - "value": "keep-alive", - "enabled": true - }, - { - "key": "x-srv-trace", - "value": "v=1;t=daef761e243c402b", - "enabled": true - }, - { - "key": "x-srv-span", - "value": "v=1;s=2c0bd88502372360", - "enabled": true - }, - { - "key": "Access-Control-Allow-Origin", - "value": "*", - "enabled": true - }, - { - "key": "X-RateLimit-Limit", - "value": "120", - "enabled": true - }, - { - "key": "X-RateLimit-Remaining", - "value": "115", - "enabled": true - }, - { - "key": "X-RateLimit-Reset", - "value": "1691968334", - "enabled": true - }, - { - "key": "ETag", - "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", - "enabled": true - }, - { - "key": "Vary", - "value": "Accept-Encoding", - "enabled": true - } - ], - "cookie": [], - "responseTime": null, - "body": "{\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"step_id\": \"1a379290-3abc-11ee-be56-0242ac120002\",\n \"input\": \"y\",\n \"status\": \"completed\",\n \"output\": \"I am going to use the write_to_file method to write the word 'Washington' to a .txt file\",\n \"artifacts\": [\n ],\n \"is_last\": false\n}" - }, - { - "id": "0bca46fd-7401-48fa-b413-6357e53ae19d", - "name": "mock response", - "originalRequest": { - "method": "POST", - "header": [ - { - "key": "mock-match", - "value": "2", - "type": "text" - }, - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"y\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Date", - "value": "Mon, 14 Aug 2023 16:28:55 GMT", - "enabled": true - }, - { - "key": "Content-Type", - "value": "application/json; charset=utf-8", - "enabled": true - }, - { - "key": "Content-Length", - "value": "275", - "enabled": true - }, - { - "key": "Connection", - "value": "keep-alive", - "enabled": true - }, - { - "key": "x-srv-trace", - "value": "v=1;t=daef761e243c402b", - "enabled": true - }, - { - "key": "x-srv-span", - "value": "v=1;s=2c0bd88502372360", - "enabled": true - }, - { - "key": "Access-Control-Allow-Origin", - "value": "*", - "enabled": true - }, - { - "key": "X-RateLimit-Limit", - "value": "120", - "enabled": true - }, - { - "key": "X-RateLimit-Remaining", - "value": "115", - "enabled": true - }, - { - "key": "X-RateLimit-Reset", - "value": "1691968334", - "enabled": true - }, - { - "key": "ETag", - "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", - "enabled": true - }, - { - "key": "Vary", - "value": "Accept-Encoding", - "enabled": true - } - ], - "cookie": [], - "responseTime": null, - "body": "{\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"step_id\": \"2a479290-3abc-11ee-be56-0242ac1209c1\",\n \"input\": \"y\",\n \"status\": \"completed\",\n \"output\": \"I used the write_to_file method to write the file test_output.txt\",\n \"artifacts\": [\n {\n \"artifact_id\": \"2ba79290-3abc-11ee-be56-0242ac1209d3\",\n \"agent_created\": true,\n \"uri\": \"file://test_output.txt\"\n }\n ],\n \"is_last\": true\n}" - } - ] - }, - { - "name": "Execute step after completion", - "event": [ - { - "listen": "test", - "script": { - "id": "26426e07-115e-4841-9887-24fd9dccca2a", - "exec": [""], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "id": "3bdd2e35-1745-439e-9ef1-ea2e003ddc42", - "exec": [""], - "type": "text/javascript" - } - } - ], - "id": "2db71ce6-f370-4e1e-83de-1990be2026ee", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - }, - { - "key": "mock-match", - "value": "34", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"y\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" - }, - "response": [ - { - "id": "768252c5-a6aa-4ae5-91a1-253e653a287d", - "name": "mock response", - "originalRequest": { - "method": "POST", - "header": [ - { - "key": "mock-match", - "value": "34", - "type": "text" - }, - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"y\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Date", - "value": "Mon, 14 Aug 2023 16:28:55 GMT", - "enabled": true - }, - { - "key": "Content-Type", - "value": "application/json; charset=utf-8", - "enabled": true - }, - { - "key": "Content-Length", - "value": "275", - "enabled": true - }, - { - "key": "Connection", - "value": "keep-alive", - "enabled": true - }, - { - "key": "x-srv-trace", - "value": "v=1;t=daef761e243c402b", - "enabled": true - }, - { - "key": "x-srv-span", - "value": "v=1;s=2c0bd88502372360", - "enabled": true - }, - { - "key": "Access-Control-Allow-Origin", - "value": "*", - "enabled": true - }, - { - "key": "X-RateLimit-Limit", - "value": "120", - "enabled": true - }, - { - "key": "X-RateLimit-Remaining", - "value": "115", - "enabled": true - }, - { - "key": "X-RateLimit-Reset", - "value": "1691968334", - "enabled": true - }, - { - "key": "ETag", - "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", - "enabled": true - }, - { - "key": "Vary", - "value": "Accept-Encoding", - "enabled": true - } - ], - "cookie": [], - "responseTime": null, - "body": "{\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"step_id\": \"2d479290-3abc-11ee-be56-0242ac120b95\",\n \"status\": \"completed\",\n \"output\": \"I am already done with my work.\",\n \"artifacts\": [\n ],\n \"is_last\": true\n}" - } - ] - } - ], - "id": "5ba23d82-afe2-41a2-b782-f41c903d45d6", - "description": "We ask the agent to write a file in his workspace.", - "event": [ - { - "listen": "prerequest", - "script": { - "id": "1f74f7f2-ed1c-43d4-88ee-e9688e6caf45", - "type": "text/javascript", - "exec": [""] - } - }, - { - "listen": "test", - "script": { - "id": "fbbe4b1f-0708-452d-99b7-fe8ad882a7ba", - "type": "text/javascript", - "exec": [ - "pm.test(\"Response status code is 200\", function () {", - " pm.response.to.have.status(200);", - "});" - ] - } - } - ] - }, - { - "name": "Tasks", - "item": [ - { - "name": "Create a new task", - "event": [ - { - "listen": "test", - "script": { - "id": "46ca258a-c625-4c17-b413-0c86023e705e", - "exec": [ - "var jsonData = pm.response.json();", - "pm.globals.set(\"taskId\", jsonData.task_id);", - "pm.globals.set(\"taskInput\", JSON.parse(pm.request.body.raw).input);" - ], - "type": "text/javascript" - } - } - ], - "id": "eb9d0c96-18a1-4e95-8948-9c1894d56409", - "protocolProfileBehavior": { - "disableBodyPruning": true, - "disabledSystemHeaders": {} - }, - "request": { - "method": "POST", - "header": [ - { - "key": "mock-match", - "value": "34" - }, - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks" - }, - "response": [] - }, - { - "name": "Get the task", - "event": [ - { - "listen": "test", - "script": { - "id": "dae8e511-e80d-478b-81ab-2d66dd6fd5b3", - "exec": [ - "pm.test(\"Response time is less than 500ms\", function () {\r", - " pm.expect(pm.response.responseTime).to.be.below(500);\r", - "});\r", - "\r", - "pm.test(\"Content-Type is present\", function () {\r", - " pm.response.to.have.header(\"Content-Type\");\r", - "});\r", - "\r", - "pm.test(\"Content-Type is application/json\", function () {\r", - " var contentType = pm.response.headers.get('Content-Type');\r", - " pm.expect(contentType).to.include('application/json');\r", - "});\r", - "\r", - "pm.test(\"Response has all required properties\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('task_id');\r", - " pm.expect(jsonData).to.have.property('input');\r", - " pm.expect(jsonData).to.have.property('additional_input');\r", - " pm.expect(jsonData).to.have.property('artifacts');\r", - "});\r", - "\r", - "" - ], - "type": "text/javascript" - } - } - ], - "id": "f84ed7b1-c97a-4968-a14c-88c231b187fa", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}" - }, - "response": [ - { - "id": "ea9d8d69-ed12-4d2c-85d9-f56028bbc628", - "name": "mock response", - "originalRequest": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{taskId}}" - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Date", - "value": "Sun, 13 Aug 2023 23:23:05 GMT", - "enabled": true - }, - { - "key": "Content-Type", - "value": "application/json; charset=utf-8", - "enabled": true - }, - { - "key": "Content-Length", - "value": "28", - "enabled": true - }, - { - "key": "Connection", - "value": "keep-alive", - "enabled": true - }, - { - "key": "x-srv-trace", - "value": "v=1;t=daef761e243c402b", - "enabled": true - }, - { - "key": "x-srv-span", - "value": "v=1;s=2c0bd88502372360", - "enabled": true - }, - { - "key": "Access-Control-Allow-Origin", - "value": "*", - "enabled": true - }, - { - "key": "X-RateLimit-Limit", - "value": "120", - "enabled": true - }, - { - "key": "X-RateLimit-Remaining", - "value": "115", - "enabled": true - }, - { - "key": "X-RateLimit-Reset", - "value": "1691968334", - "enabled": true - }, - { - "key": "ETag", - "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", - "enabled": true - }, - { - "key": "Vary", - "value": "Accept-Encoding", - "enabled": true - } - ], - "cookie": [], - "responseTime": null, - "body": "{\n \"input\": \"Write the word 'Washington' to a .txt file\",\n \"additional_input\": null,\n \"task_id\": \"121\",\n \"artifacts\": []\n}" - } - ] - }, - { - "name": "Get all the tasks", - "event": [ - { - "listen": "test", - "script": { - "id": "2e4ee6d8-e5d8-4112-be5c-ec8404f38ef0", - "exec": [ - "pm.test(\"Response time is less than 500ms\", function () {", - " pm.expect(pm.response.responseTime).to.be.below(500);", - "});", - "", - "pm.test(\"Content-Type is present\", function () {", - " pm.response.to.have.header(\"Content-Type\");", - "});", - "", - "pm.test(\"Content-Type is application/json\", function () {", - " var contentType = pm.response.headers.get('Content-Type');", - " pm.expect(contentType).to.include('application/json');", - "});" - ], - "type": "text/javascript" - } - } - ], - "id": "ca583fa3-bec4-4c73-b989-47f3414a8d51", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks" - }, - "response": [] - }, - { - "name": "Create a second task", - "event": [ - { - "listen": "test", - "script": { - "id": "2c8c047b-eec5-4015-86a8-0640f3315ce3", - "exec": [ - "var jsonData = pm.response.json();", - "pm.globals.set(\"lastTaskId\", jsonData.task_id);" - ], - "type": "text/javascript" - } - } - ], - "id": "0de5f275-995e-4eb5-b63c-0f2e8996fab1", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "POST", - "header": [ - { - "key": "mock-match", - "value": "34" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks" - }, - "response": [] - }, - { - "name": "Get all the tasks with Pagination", - "event": [ - { - "listen": "test", - "script": { - "id": "697349ba-26c9-431c-a68d-a1c0efeeaeae", - "exec": [ - "var jsonData = pm.response.json();", - "", - "pm.test(\"Response time is less than 500ms\", function () {", - " pm.expect(pm.response.responseTime).to.be.below(500);", - "});", - "", - "pm.test(\"Content-Type is present\", function () {", - " pm.response.to.have.header(\"Content-Type\");", - "});", - "", - "pm.test(\"Content-Type is application/json\", function () {", - " var contentType = pm.response.headers.get('Content-Type');", - " pm.expect(contentType).to.include('application/json');", - "});", - "", - "pm.test(\"Pagination is set\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.pagination).to.be.an('object');", - "});", - "", - "pm.test(\"Page size is 1\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.pagination.page_size).to.eql(1);", - "});", - "", - "pm.test(\"Items is an array with one item\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.tasks).to.be.an('array').that.has.lengthOf(1);", - "});", - "", - "", - "pm.test(\"Response length respects page_size\", function() {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.tasks.length).to.be.at.most(1);", - "});", - "", - "if (jsonData.items && jsonData.items.length > 0) {", - " if (pm.variables.has(\"lastTaskId\") && pm.variables.get(\"page\") > 1) {", - " pm.test(\"First task of page \" + pm.variables.get(\"page\") + \" is not the last task of page \" + (pm.variables.get(\"page\") - 1), function() {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.items[0].task_id).to.not.equal(pm.variables.get(\"lastTaskId\"));", - " });", - " }", - " if (jsonData.items.length > 0) {", - " pm.variables.set(\"lastTaskId\", jsonData.items[jsonData.items.length - 1].task_id);", - " }", - "}" - ], - "type": "text/javascript" - } - } - ], - "id": "f41e04c1-b266-4e90-909e-ebb0657958d9", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{url}}/ap/v1/agent/tasks?page_size=1¤t_page=1", - "host": ["{{url}}/ap/v1"], - "path": ["agent", "tasks"], - "query": [ - { - "key": "page_size", - "value": "1" - }, - { - "key": "current_page", - "value": "1" - } - ] - } - }, - "response": [] - } - ], - "id": "4b743f46-e582-4565-ac7d-c6446a710ee1", - "description": "Create tasks and consumes them.", - "event": [ - { - "listen": "prerequest", - "script": { - "id": "7f174c78-2068-4351-a64c-bd4c115470e8", - "type": "text/javascript", - "exec": [""] - } - }, - { - "listen": "test", - "script": { - "id": "ef296737-8c06-47ed-90c3-bfe882c1aa40", - "type": "text/javascript", - "exec": [ - "pm.test(\"Response status code is 200\", function () {", - " pm.response.to.have.status(200);", - "});" - ] - } - } - ] - }, - { - "name": "Artifacts", - "item": [ - { - "name": "Create a new task", - "event": [ - { - "listen": "test", - "script": { - "id": "65ad57a3-b776-4d7c-86f9-be6fbf17cac6", - "exec": [ - "var jsonData = pm.response.json();", - "pm.globals.set(\"task_id\", jsonData.task_id);" - ], - "type": "text/javascript" - } - } - ], - "id": "1ee1eee8-cc2d-43a2-a6e1-7168b0559c45", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks" - }, - "response": [] - }, - { - "name": "Upload Artifact", - "event": [ - { - "listen": "test", - "script": { - "id": "6cf7a2c4-1b8a-4c11-8c1a-6b56b2eb80e6", - "exec": [ - "var jsonData = pm.response.json();", - "pm.globals.set(\"artifact_id\", jsonData.artifact_id);" - ], - "type": "text/javascript" - } - } - ], - "id": "e71fee13-11cb-4b08-a4bb-c9c0f971efed", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "multipart/form-data" - } - ], - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "file", - "type": "file", - "src": "test_output.txt" - } - ] - }, - "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/artifacts" - }, - "response": [] - }, - { - "name": "Download Artifact", - "event": [ - { - "listen": "test", - "script": { - "id": "82829e0a-f8c7-4925-b28c-0513b018e83e", - "exec": [""], - "type": "text/javascript" - } - } - ], - "id": "b002580c-0d48-42c9-83e0-5fb5eaed38ce", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [ - { - "key": "mock-match", - "value": "11", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/artifacts/{{artifact_id}}" - }, - "response": [ - { - "id": "ade2dd37-0f84-45f0-881e-2224636a4956", - "name": "mock response", - "originalRequest": { - "method": "GET", - "header": [ - { - "key": "mock-match", - "value": "11", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "{{url}}/ap/v1/agent/tasks/{{taskId}}/artifacts/{{artifactId}}" - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "text", - "header": [ - { - "key": "Date", - "value": "Mon, 14 Aug 2023 16:28:55 GMT", - "enabled": true - }, - { - "key": "Content-Type", - "value": "application/json; charset=utf-8", - "enabled": true - }, - { - "key": "Content-Length", - "value": "275", - "enabled": true - }, - { - "key": "Connection", - "value": "keep-alive", - "enabled": true - }, - { - "key": "x-srv-trace", - "value": "v=1;t=daef761e243c402b", - "enabled": true - }, - { - "key": "x-srv-span", - "value": "v=1;s=2c0bd88502372360", - "enabled": true - }, - { - "key": "Access-Control-Allow-Origin", - "value": "*", - "enabled": true - }, - { - "key": "X-RateLimit-Limit", - "value": "120", - "enabled": true - }, - { - "key": "X-RateLimit-Remaining", - "value": "115", - "enabled": true - }, - { - "key": "X-RateLimit-Reset", - "value": "1691968334", - "enabled": true - }, - { - "key": "ETag", - "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", - "enabled": true - }, - { - "key": "Vary", - "value": "Accept-Encoding", - "enabled": true - } - ], - "cookie": [], - "responseTime": null, - "body": "Washington" - } - ] - } - ], - "id": "48e93b55-d463-452f-9d56-f59ee5e95060", - "description": "Create Artifacts and consumes them.", - "event": [ - { - "listen": "prerequest", - "script": { - "id": "61be92ef-c1cd-4f2a-a77c-78e5913ea299", - "type": "text/javascript", - "exec": [""] - } - }, - { - "listen": "test", - "script": { - "id": "7dcedb1e-4f42-48b1-ac9b-fd3b842b428b", - "type": "text/javascript", - "exec": [ - "pm.test(\"Response status code is 200\", function () {", - " pm.response.to.have.status(200);", - "});" - ] - } - } - ] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "id": "7dd8bbee-357a-44fd-b891-46bcc3a8a41c", - "type": "text/javascript", - "exec": [""] - } - }, - { - "listen": "test", - "script": { - "id": "b5f22749-138a-45fe-b6c7-79fe5cf06017", - "type": "text/javascript", - "exec": [""] - } - } - ] + "info": { + "_postman_id": "84d6bd70-4a9f-4470-be02-f2f9089ec69b", + "name": "Agent Protocol - REST v1", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" + }, + "item": [ + { + "name": "Basic User Experience", + "item": [ + { + "name": "Cleanup Previous Run Copy", + "event": [ + { + "listen": "prerequest", + "script": { + "id": "6e0312e1-1276-47b2-92be-b481545de5fb", + "exec": [ + "// See https://blog.postman.com/2019/05/28/pro-tip-dynamically-unset-postman-environment-variables/\r", + "// for more details on what we're doing here. \r", + "\r", + "cleanupCollectionVariables();\r", + "\r", + "function cleanupCollectionVariables() {\r", + " const clean = _.keys(pm.collectionVariables.toObject());\r", + "\r", + " _.each(clean, (arrItem) => {\r", + " pm.collectionVariables.unset(arrItem);\r", + " });\r", + "}" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "id": "acaabbd4-94fc-4444-8718-b9ca2c087721", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "id": "5da26248-515e-4feb-b7b5-c7de25a03857", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "url": "https://postman-echo.com/delay/0" + }, + "response": [] + }, + { + "name": "Get all the tasks", + "event": [ + { + "listen": "test", + "script": { + "id": "32b75035-2164-4ec1-b61c-a86515847037", + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "98c48159-7553-4f9b-a1e7-dd66b961ec11", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "id": "52fdb36d-66c1-41e9-b81a-1084ae813a2d", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "mock-match", + "value": "19", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks" + }, + "response": [ + { + "id": "98f96c46-c680-4377-a681-27b93d8425cf", + "name": "mock response", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "mock-match", + "value": "19", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks" + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Thu, 17 Aug 2023 18:03:12 GMT", + "enabled": true + }, + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + }, + { + "key": "Content-Length", + "value": "150", + "enabled": true + }, + { + "key": "Connection", + "value": "keep-alive", + "enabled": true + }, + { + "key": "x-srv-trace", + "value": "v=1;t=1c95cd08c248d38f", + "enabled": true + }, + { + "key": "x-srv-span", + "value": "v=1;s=2527ca982b2b7c75", + "enabled": true + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*", + "enabled": true + }, + { + "key": "X-RateLimit-Limit", + "value": "120", + "enabled": true + }, + { + "key": "X-RateLimit-Remaining", + "value": "117", + "enabled": true + }, + { + "key": "X-RateLimit-Reset", + "value": "1692295416", + "enabled": true + }, + { + "key": "ETag", + "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", + "enabled": true + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "enabled": true + } + ], + "cookie": [], + "responseTime": null, + "body": "[\n {\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"step_id\": \"1a379290-3abc-11ee-be56-0242ac120002\",\n \"status\": \"completed\",\n \"output\": \"I am going to use the write_to_file method to write the word 'Washington' to a .txt file\",\n \"artifacts\": [],\n \"is_last\": false\n }\n]" + } + ] + }, + { + "name": "Create a new task", + "event": [ + { + "listen": "prerequest", + "script": { + "id": "c5e90382-a818-4e98-9f9b-4a2877e0f129", + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "id": "683fd614-1610-4dab-bf88-6ae830186dac", + "exec": [ + "var jsonData = pm.response.json();", + "pm.globals.set(\"task_id\", jsonData.task_id);", + "", + "pm.globals.set(", + " \"step_body\",", + " JSON.stringify(", + " {", + " \"input\": JSON.parse(pm.request.body.raw).input", + " } ", + " )", + ");" + ], + "type": "text/javascript" + } + } + ], + "id": "090b65b7-80e6-4a20-81d7-2a6f72644ad5", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "POST", + "header": [ + { + "key": "mock-match", + "value": "34", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\",\n \"additional_input\": {\"test_run_id\": \"123\"}\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks" + }, + "response": [ + { + "id": "5aa13fc7-8099-43ac-81d4-87af8cdbb6fe", + "name": "mock response", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "mock-match", + "value": "34" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks" + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Sun, 13 Aug 2023 23:23:05 GMT", + "enabled": true + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "enabled": true + }, + { + "key": "Content-Length", + "value": "28", + "enabled": true + }, + { + "key": "Connection", + "value": "keep-alive", + "enabled": true + }, + { + "key": "x-srv-trace", + "value": "v=1;t=daef761e243c402b", + "enabled": true + }, + { + "key": "x-srv-span", + "value": "v=1;s=2c0bd88502372360", + "enabled": true + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*", + "enabled": true + }, + { + "key": "X-RateLimit-Limit", + "value": "120", + "enabled": true + }, + { + "key": "X-RateLimit-Remaining", + "value": "115", + "enabled": true + }, + { + "key": "X-RateLimit-Reset", + "value": "1691968334", + "enabled": true + }, + { + "key": "ETag", + "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", + "enabled": true + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "enabled": true + } + ], + "cookie": [], + "responseTime": null, + "body": "{\n \"input\": \"Write the word 'Washington' to a .txt file\",\n \"additional_input\": {},\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"artifacts\": []\n}" + } + ] + }, + { + "name": "Execute a step", + "event": [ + { + "listen": "test", + "script": { + "id": "26426e07-115e-4841-9887-24fd9dccca2a", + "exec": [ + "if (pm.request.url.path[0] == \"agent\" && pm.response.headers.has(\"Content-Type\",`application/json`)) {", + " var artifacts = pm.response.json().artifacts;", + "", + " if (artifacts && artifacts.length > 0) {", + " pm.globals.set(\"artifactId\", artifacts[0].artifact_id);", + " }", + "", + " var artifacts = pm.response.json().artifacts;", + " var existingArtifactId = pm.globals.get(\"artifactId\");", + "/* Commented out artifact checking code and max step code because the SDK doesn't implement simple agents", + " if (artifacts && artifacts.length > 0) {", + " if (artifacts.length > 1) {", + " pm.test(\"This task should only create 1 artifact\", function () {", + " pm.expect.fail(\"More than one artifact was created.\");", + " });", + " } else {", + " pm.globals.set(\"artifactId\", artifacts[0].artifact_id);", + " }", + " }*/", + " stepNumber = pm.collectionVariables.get(\"step-number\") ?? 1", + " const maxSteps = 10", + " if(!pm.response.json().is_last) {", + " /*if (stepNumber > maxSteps) {", + " console.log(`Max steps reached (${maxSteps})`);", + " pm.test(`This task should be completed after ${maxSteps} steps`, function () {", + " pm.expect.fail(`is_last should be true before max steps reached (${maxSteps})`);", + " });", + " } else {", + " console.log(`Steps reached (${stepNumber})`);", + "", + " pm.collectionVariables.set('step-number', stepNumber + 1);", + " pm.globals.set(", + " \"step_body\",", + " JSON.stringify(", + " {", + " \"input\": \"y\"", + " }", + " )", + " );", + "", + " pm.collectionVariables.set('previous-step', pm.response.json().step_id)", + " postman.setNextRequest('Execute the steps until completion');", + " }*/", + " ", + " }", + "}" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "3bdd2e35-1745-439e-9ef1-ea2e003ddc42", + "exec": [ + "stepNumber = pm.collectionVariables.get(\"step-number\") ?? 1", + "console.log(\"Step number:\" + stepNumber)", + "console.log(\"Task Input:\" + pm.globals.get(\"taskInput\"))", + "if (stepNumber) {", + " pm.request.headers.upsert({ key: 'mock-match', value: stepNumber.toString() });", + " console.log(pm.request)", + "}" + ], + "type": "text/javascript" + } + } + ], + "id": "c001b340-752c-45ea-aaa2-22cb52e47297", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{{step_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" + }, + "response": [ + { + "id": "1def41e7-04a8-41d6-bcef-b0149882510d", + "name": "mock response", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "mock-match", + "value": "1", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Mon, 14 Aug 2023 16:28:55 GMT", + "enabled": true + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "enabled": true + }, + { + "key": "Content-Length", + "value": "275", + "enabled": true + }, + { + "key": "Connection", + "value": "keep-alive", + "enabled": true + }, + { + "key": "x-srv-trace", + "value": "v=1;t=daef761e243c402b", + "enabled": true + }, + { + "key": "x-srv-span", + "value": "v=1;s=2c0bd88502372360", + "enabled": true + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*", + "enabled": true + }, + { + "key": "X-RateLimit-Limit", + "value": "120", + "enabled": true + }, + { + "key": "X-RateLimit-Remaining", + "value": "115", + "enabled": true + }, + { + "key": "X-RateLimit-Reset", + "value": "1691968334", + "enabled": true + }, + { + "key": "ETag", + "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", + "enabled": true + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "enabled": true + } + ], + "cookie": [], + "responseTime": null, + "body": "{\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"step_id\": \"1a379290-3abc-11ee-be56-0242ac120002\",\n \"input\": \"y\",\n \"status\": \"completed\",\n \"output\": \"I am going to use the write_to_file method to write the word 'Washington' to a .txt file\",\n \"artifacts\": [\n ],\n \"is_last\": false\n}" + }, + { + "id": "0bca46fd-7401-48fa-b413-6357e53ae19d", + "name": "mock response", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "mock-match", + "value": "2", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"y\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Mon, 14 Aug 2023 16:28:55 GMT", + "enabled": true + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "enabled": true + }, + { + "key": "Content-Length", + "value": "275", + "enabled": true + }, + { + "key": "Connection", + "value": "keep-alive", + "enabled": true + }, + { + "key": "x-srv-trace", + "value": "v=1;t=daef761e243c402b", + "enabled": true + }, + { + "key": "x-srv-span", + "value": "v=1;s=2c0bd88502372360", + "enabled": true + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*", + "enabled": true + }, + { + "key": "X-RateLimit-Limit", + "value": "120", + "enabled": true + }, + { + "key": "X-RateLimit-Remaining", + "value": "115", + "enabled": true + }, + { + "key": "X-RateLimit-Reset", + "value": "1691968334", + "enabled": true + }, + { + "key": "ETag", + "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", + "enabled": true + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "enabled": true + } + ], + "cookie": [], + "responseTime": null, + "body": "{\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"step_id\": \"2a479290-3abc-11ee-be56-0242ac1209c1\",\n \"input\": \"y\",\n \"status\": \"completed\",\n \"output\": \"I used the write_to_file method to write the file test_output.txt\",\n \"artifacts\": [\n {\n \"artifact_id\": \"2ba79290-3abc-11ee-be56-0242ac1209d3\",\n \"agent_created\": true,\n \"uri\": \"file://test_output.txt\"\n }\n ],\n \"is_last\": true\n}" + } + ] + }, + { + "name": "Execute step after completion", + "event": [ + { + "listen": "test", + "script": { + "id": "26426e07-115e-4841-9887-24fd9dccca2a", + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "3bdd2e35-1745-439e-9ef1-ea2e003ddc42", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "id": "2db71ce6-f370-4e1e-83de-1990be2026ee", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "mock-match", + "value": "34", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"y\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" + }, + "response": [ + { + "id": "768252c5-a6aa-4ae5-91a1-253e653a287d", + "name": "mock response", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "mock-match", + "value": "34", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"y\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/steps" + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Mon, 14 Aug 2023 16:28:55 GMT", + "enabled": true + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "enabled": true + }, + { + "key": "Content-Length", + "value": "275", + "enabled": true + }, + { + "key": "Connection", + "value": "keep-alive", + "enabled": true + }, + { + "key": "x-srv-trace", + "value": "v=1;t=daef761e243c402b", + "enabled": true + }, + { + "key": "x-srv-span", + "value": "v=1;s=2c0bd88502372360", + "enabled": true + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*", + "enabled": true + }, + { + "key": "X-RateLimit-Limit", + "value": "120", + "enabled": true + }, + { + "key": "X-RateLimit-Remaining", + "value": "115", + "enabled": true + }, + { + "key": "X-RateLimit-Reset", + "value": "1691968334", + "enabled": true + }, + { + "key": "ETag", + "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", + "enabled": true + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "enabled": true + } + ], + "cookie": [], + "responseTime": null, + "body": "{\n \"task_id\": \"fde559f8-3ab8-11ee-be56-0242ac120002\",\n \"step_id\": \"2d479290-3abc-11ee-be56-0242ac120b95\",\n \"status\": \"completed\",\n \"output\": \"I am already done with my work.\",\n \"artifacts\": [\n ],\n \"is_last\": true\n}" + } + ] + } + ], + "id": "5ba23d82-afe2-41a2-b782-f41c903d45d6", + "description": "We ask the agent to write a file in his workspace.", + "event": [ + { + "listen": "prerequest", + "script": { + "id": "1f74f7f2-ed1c-43d4-88ee-e9688e6caf45", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "fbbe4b1f-0708-452d-99b7-fe8ad882a7ba", + "type": "text/javascript", + "exec": [ + "pm.test(\"Response status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] + }, + { + "name": "Tasks", + "item": [ + { + "name": "Create a new task", + "event": [ + { + "listen": "test", + "script": { + "id": "46ca258a-c625-4c17-b413-0c86023e705e", + "exec": [ + "var jsonData = pm.response.json();", + "pm.globals.set(\"taskId\", jsonData.task_id);", + "pm.globals.set(\"taskInput\", JSON.parse(pm.request.body.raw).input);" + ], + "type": "text/javascript" + } + } + ], + "id": "eb9d0c96-18a1-4e95-8948-9c1894d56409", + "protocolProfileBehavior": { + "disableBodyPruning": true, + "disabledSystemHeaders": {} + }, + "request": { + "method": "POST", + "header": [ + { + "key": "mock-match", + "value": "34" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks" + }, + "response": [] + }, + { + "name": "Get the task", + "event": [ + { + "listen": "test", + "script": { + "id": "dae8e511-e80d-478b-81ab-2d66dd6fd5b3", + "exec": [ + "pm.test(\"Response time is less than 500ms\", function () {\r", + " pm.expect(pm.response.responseTime).to.be.below(500);\r", + "});\r", + "\r", + "pm.test(\"Content-Type is present\", function () {\r", + " pm.response.to.have.header(\"Content-Type\");\r", + "});\r", + "\r", + "pm.test(\"Content-Type is application/json\", function () {\r", + " var contentType = pm.response.headers.get('Content-Type');\r", + " pm.expect(contentType).to.include('application/json');\r", + "});\r", + "\r", + "pm.test(\"Response has all required properties\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('task_id');\r", + " pm.expect(jsonData).to.have.property('input');\r", + " pm.expect(jsonData).to.have.property('additional_input');\r", + " pm.expect(jsonData).to.have.property('artifacts');\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript" + } + } + ], + "id": "f84ed7b1-c97a-4968-a14c-88c231b187fa", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}" + }, + "response": [ + { + "id": "ea9d8d69-ed12-4d2c-85d9-f56028bbc628", + "name": "mock response", + "originalRequest": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{taskId}}" + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Sun, 13 Aug 2023 23:23:05 GMT", + "enabled": true + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "enabled": true + }, + { + "key": "Content-Length", + "value": "28", + "enabled": true + }, + { + "key": "Connection", + "value": "keep-alive", + "enabled": true + }, + { + "key": "x-srv-trace", + "value": "v=1;t=daef761e243c402b", + "enabled": true + }, + { + "key": "x-srv-span", + "value": "v=1;s=2c0bd88502372360", + "enabled": true + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*", + "enabled": true + }, + { + "key": "X-RateLimit-Limit", + "value": "120", + "enabled": true + }, + { + "key": "X-RateLimit-Remaining", + "value": "115", + "enabled": true + }, + { + "key": "X-RateLimit-Reset", + "value": "1691968334", + "enabled": true + }, + { + "key": "ETag", + "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", + "enabled": true + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "enabled": true + } + ], + "cookie": [], + "responseTime": null, + "body": "{\n \"input\": \"Write the word 'Washington' to a .txt file\",\n \"additional_input\": null,\n \"task_id\": \"121\",\n \"artifacts\": []\n}" + } + ] + }, + { + "name": "Get all the tasks", + "event": [ + { + "listen": "test", + "script": { + "id": "2e4ee6d8-e5d8-4112-be5c-ec8404f38ef0", + "exec": [ + "pm.test(\"Response time is less than 500ms\", function () {", + " pm.expect(pm.response.responseTime).to.be.below(500);", + "});", + "", + "pm.test(\"Content-Type is present\", function () {", + " pm.response.to.have.header(\"Content-Type\");", + "});", + "", + "pm.test(\"Content-Type is application/json\", function () {", + " var contentType = pm.response.headers.get('Content-Type');", + " pm.expect(contentType).to.include('application/json');", + "});" + ], + "type": "text/javascript" + } + } + ], + "id": "ca583fa3-bec4-4c73-b989-47f3414a8d51", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks" + }, + "response": [] + }, + { + "name": "Create a second task", + "event": [ + { + "listen": "test", + "script": { + "id": "2c8c047b-eec5-4015-86a8-0640f3315ce3", + "exec": [ + "var jsonData = pm.response.json();", + "pm.globals.set(\"lastTaskId\", jsonData.task_id);" + ], + "type": "text/javascript" + } + } + ], + "id": "0de5f275-995e-4eb5-b63c-0f2e8996fab1", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "POST", + "header": [ + { + "key": "mock-match", + "value": "34" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks" + }, + "response": [] + }, + { + "name": "Get all the tasks with Pagination", + "event": [ + { + "listen": "test", + "script": { + "id": "697349ba-26c9-431c-a68d-a1c0efeeaeae", + "exec": [ + "var jsonData = pm.response.json();", + "", + "pm.test(\"Response time is less than 500ms\", function () {", + " pm.expect(pm.response.responseTime).to.be.below(500);", + "});", + "", + "pm.test(\"Content-Type is present\", function () {", + " pm.response.to.have.header(\"Content-Type\");", + "});", + "", + "pm.test(\"Content-Type is application/json\", function () {", + " var contentType = pm.response.headers.get('Content-Type');", + " pm.expect(contentType).to.include('application/json');", + "});", + "", + "pm.test(\"Pagination is set\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.pagination).to.be.an('object');", + "});", + "", + "pm.test(\"Page size is 1\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.pagination.page_size).to.eql(1);", + "});", + "", + "pm.test(\"Items is an array with one item\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.tasks).to.be.an('array').that.has.lengthOf(1);", + "});", + "", + "", + "pm.test(\"Response length respects page_size\", function() {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.tasks.length).to.be.at.most(1);", + "});", + "", + "if (jsonData.items && jsonData.items.length > 0) {", + " if (pm.variables.has(\"lastTaskId\") && pm.variables.get(\"page\") > 1) {", + " pm.test(\"First task of page \" + pm.variables.get(\"page\") + \" is not the last task of page \" + (pm.variables.get(\"page\") - 1), function() {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.items[0].task_id).to.not.equal(pm.variables.get(\"lastTaskId\"));", + " });", + " }", + " if (jsonData.items.length > 0) {", + " pm.variables.set(\"lastTaskId\", jsonData.items[jsonData.items.length - 1].task_id);", + " }", + "}" + ], + "type": "text/javascript" + } + } + ], + "id": "f41e04c1-b266-4e90-909e-ebb0657958d9", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{url}}/ap/v1/agent/tasks?page_size=1¤t_page=1", + "host": [ + "{{url}}/ap/v1" + ], + "path": [ + "agent", + "tasks" + ], + "query": [ + { + "key": "page_size", + "value": "1" + }, + { + "key": "current_page", + "value": "1" + } + ] + } + }, + "response": [] + } + ], + "id": "4b743f46-e582-4565-ac7d-c6446a710ee1", + "description": "Create tasks and consumes them.", + "event": [ + { + "listen": "prerequest", + "script": { + "id": "7f174c78-2068-4351-a64c-bd4c115470e8", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "ef296737-8c06-47ed-90c3-bfe882c1aa40", + "type": "text/javascript", + "exec": [ + "pm.test(\"Response status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] + }, + { + "name": "Artifacts", + "item": [ + { + "name": "Create a new task", + "event": [ + { + "listen": "test", + "script": { + "id": "65ad57a3-b776-4d7c-86f9-be6fbf17cac6", + "exec": [ + "var jsonData = pm.response.json();", + "pm.globals.set(\"task_id\", jsonData.task_id);" + ], + "type": "text/javascript" + } + } + ], + "id": "1ee1eee8-cc2d-43a2-a6e1-7168b0559c45", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"input\": \"Write the word 'Washington' to a .txt file\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks" + }, + "response": [] + }, + { + "name": "Upload Artifact", + "event": [ + { + "listen": "test", + "script": { + "id": "6cf7a2c4-1b8a-4c11-8c1a-6b56b2eb80e6", + "exec": [ + "var jsonData = pm.response.json();", + "pm.globals.set(\"artifact_id\", jsonData.artifact_id);" + ], + "type": "text/javascript" + } + } + ], + "id": "e71fee13-11cb-4b08-a4bb-c9c0f971efed", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "multipart/form-data" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "test_output.txt" + } + ] + }, + "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/artifacts" + }, + "response": [] + }, + { + "name": "Download Artifact", + "event": [ + { + "listen": "test", + "script": { + "id": "82829e0a-f8c7-4925-b28c-0513b018e83e", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "id": "b002580c-0d48-42c9-83e0-5fb5eaed38ce", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "mock-match", + "value": "11", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{task_id}}/artifacts/{{artifact_id}}" + }, + "response": [ + { + "id": "ade2dd37-0f84-45f0-881e-2224636a4956", + "name": "mock response", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "mock-match", + "value": "11", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{url}}/ap/v1/agent/tasks/{{taskId}}/artifacts/{{artifactId}}" + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "text", + "header": [ + { + "key": "Date", + "value": "Mon, 14 Aug 2023 16:28:55 GMT", + "enabled": true + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "enabled": true + }, + { + "key": "Content-Length", + "value": "275", + "enabled": true + }, + { + "key": "Connection", + "value": "keep-alive", + "enabled": true + }, + { + "key": "x-srv-trace", + "value": "v=1;t=daef761e243c402b", + "enabled": true + }, + { + "key": "x-srv-span", + "value": "v=1;s=2c0bd88502372360", + "enabled": true + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*", + "enabled": true + }, + { + "key": "X-RateLimit-Limit", + "value": "120", + "enabled": true + }, + { + "key": "X-RateLimit-Remaining", + "value": "115", + "enabled": true + }, + { + "key": "X-RateLimit-Reset", + "value": "1691968334", + "enabled": true + }, + { + "key": "ETag", + "value": "W/\"96-S/5iQ2y1qqIInh5BwoPc+chvDJU\"", + "enabled": true + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "enabled": true + } + ], + "cookie": [], + "responseTime": null, + "body": "Washington" + } + ] + } + ], + "id": "48e93b55-d463-452f-9d56-f59ee5e95060", + "description": "Create Artifacts and consumes them.", + "event": [ + { + "listen": "prerequest", + "script": { + "id": "61be92ef-c1cd-4f2a-a77c-78e5913ea299", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "7dcedb1e-4f42-48b1-ac9b-fd3b842b428b", + "type": "text/javascript", + "exec": [ + "pm.test(\"Response status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "7dd8bbee-357a-44fd-b891-46bcc3a8a41c", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "b5f22749-138a-45fe-b6c7-79fe5cf06017", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] } From b73bf1a4acec95fa72a327804660fe870af0cb0a Mon Sep 17 00:00:00 2001 From: nalbion Date: Sun, 11 Feb 2024 04:33:13 +1100 Subject: [PATCH 5/8] more reverting prettier changes to reduce noise in PR --- testing_suite/contract_tests.json | 3065 +++++++++++++++-------------- 1 file changed, 1551 insertions(+), 1514 deletions(-) diff --git a/testing_suite/contract_tests.json b/testing_suite/contract_tests.json index b2dcab21..0bbbc074 100644 --- a/testing_suite/contract_tests.json +++ b/testing_suite/contract_tests.json @@ -1,1516 +1,1553 @@ { - "info": { - "_postman_id": "03c5c4b6-9071-4ab4-b5a1-4bda97a3b3aa", - "name": "Contract Test Generator", - "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" - }, - "item": [ - { - "name": "API Validation", - "item": [ - { - "name": "Cleanup Previous Run", - "event": [ - { - "listen": "prerequest", - "script": { - "id": "6e0312e1-1276-47b2-92be-b481545de5fb", - "exec": [ - "// See https://blog.postman.com/2019/05/28/pro-tip-dynamically-unset-postman-environment-variables/\r", - "// for more details on what we're doing here. \r", - "\r", - "cleanupCollectionVariables();\r", - "\r", - "function cleanupCollectionVariables() {\r", - " const clean = _.keys(pm.collectionVariables.toObject());\r", - "\r", - " _.each(clean, (arrItem) => {\r", - " pm.collectionVariables.unset(arrItem);\r", - " });\r", - "}" - ], - "type": "text/javascript" - } - }, - { - "listen": "test", - "script": { - "id": "acaabbd4-94fc-4444-8718-b9ca2c087721", - "exec": [""], - "type": "text/javascript" - } - } - ], - "id": "30368860-aef9-46d6-ad44-8fac61b8f842", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "url": "https://postman-echo.com/delay/0" - }, - "response": [] - }, - { - "name": "Initialize", - "event": [ - { - "listen": "test", - "script": { - "id": "d15a1e94-1145-4006-b514-b5300671da90", - "exec": [ - "var envSchema = null\r", - "if (pm.environment.get(\"env-openapi-json-url\")){\r", - " envSchema = JSON.stringify(pm.response.json());\r", - "}\r", - "\r", - "const providedSchema = pm.environment.get('env-schema') || envSchema;\r", - "if(providedSchema){\r", - " let success = true;\r", - " try{\r", - " const yaml = pm.environment.get('env-jsonToYaml');\r", - " (new Function(yaml))();\r", - "\r", - " const schema = jsyaml.load(providedSchema);\r", - " pm.collectionVariables.set('coll-schema', JSON.stringify(schema));\r", - " postman.setNextRequest('Get API Base Url');\r", - " }\r", - " catch(err){\r", - " console.log(err);\r", - " success = false;\r", - " postman.setNextRequest(null);\r", - " }\r", - "\r", - " pm.test('Successfully converted provided schema', function(){\r", - " pm.expect(success).to.be.true;\r", - " }); \r", - "}" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "id": "9e1c1f12-58f5-4cea-b6c0-58be6133c033", - "exec": [ - "if (pm.environment.get(\"env-openapi-json-url\")){", - " pm.request.url = pm.environment.get(\"env-openapi-json-url\");", - "}" - ], - "type": "text/javascript" - } - } - ], - "id": "367b4112-337b-4bc0-821d-4687694559ad", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "url": "https://postman-echo.com/delay/0" - }, - "response": [] - }, - { - "name": "Validate API In Workspace", - "event": [ - { - "listen": "test", - "script": { - "id": "c7c74561-6423-4fbe-ab30-bd66746c6cdf", - "exec": [ - "const minApiCount = Number(pm.environment.get('env-minApiCount'));\r", - "const maxApiCount = Number(pm.environment.get('env-maxApiCount'));\r", - "const jsonData = pm.response.json();\r", - "\r", - "pm.test(`Workspace API count is between ${minApiCount} and ${maxApiCount}. (Count: ${jsonData.apis.length})`, function () { \r", - " pm.expect(jsonData.apis.length).to.be.at.least(minApiCount); \r", - " pm.expect(jsonData.apis.length).to.be.at.most(maxApiCount);\r", - "});\r", - "\r", - "let apiIds = [];\r", - "_.forEach(jsonData.apis, function(api){\r", - " apiIds.push(api.id);\r", - "});\r", - "\r", - "pm.collectionVariables.set('coll-apiIds', JSON.stringify(apiIds));" - ], - "type": "text/javascript" - } - } - ], - "id": "1baa5ceb-08b6-47c7-9112-8f54c039805d", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "X-Api-Key", - "value": "{{env-apiKey}}", - "type": "text" - } - ], - "url": { - "raw": "https://api.getpostman.com/apis?workspace={{env-workspaceId}}", - "protocol": "https", - "host": ["api", "getpostman", "com"], - "path": ["apis"], - "query": [ - { - "key": "workspace", - "value": "{{env-workspaceId}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get Current API Version", - "event": [ - { - "listen": "test", - "script": { - "id": "683d7e4f-8336-41a1-b14c-5893b3e49fba", - "exec": [ - "const jsonData = pm.response.json();\r", - "\r", - "pm.test('API has one or more versions', function(){\r", - " pm.expect(jsonData).to.have.property('versions').and.to.be.an('array');\r", - " pm.expect(jsonData.versions.length).to.be.above(0);\r", - "});\r", - "\r", - "const version = jsonData.versions[0];\r", - "pm.collectionVariables.set('coll-versionId', version.id);" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "id": "ae6bfbce-aad4-4f31-9b82-ba6f666658a5", - "exec": [ - "let apiIds = pm.collectionVariables.get('coll-apiIds');\r", - "if(apiIds){\r", - " apiIds = JSON.parse(apiIds);\r", - " const apiId = apiIds.pop();\r", - "\r", - " pm.collectionVariables.set('coll-apiId', apiId);\r", - " pm.collectionVariables.set('coll-apiIds', JSON.stringify(apiIds));\r", - "}\r", - "else {\r", - " pm.request.url = 'https://postman-echo.com/delay/0'\r", - " pm.request.name = 'No APIs found in the workspace. Skipping execution';\r", - " postman.setNextRequest(null);\r", - "}" - ], - "type": "text/javascript" - } - } - ], - "id": "37810e31-7b06-4ca4-a422-d0012834d1e4", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "X-Api-Key", - "value": "{{env-apiKey}}", - "type": "text" - } - ], - "url": { - "raw": "https://api.getpostman.com/apis/:apiId/versions", - "protocol": "https", - "host": ["api", "getpostman", "com"], - "path": ["apis", ":apiId", "versions"], - "query": [ - { - "key": null, - "value": "", - "disabled": true - } - ], - "variable": [ - { - "id": "66de574b-c196-407e-9be7-ff53c0dac927", - "key": "apiId", - "value": "{{coll-apiId}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get Current API Schema", - "event": [ - { - "listen": "test", - "script": { - "id": "38beef82-f213-44cc-9a11-c326fb5b003d", - "exec": [ - "const jsonData = pm.response.json();\r", - "\r", - "pm.test('Has schema for current version', function(){\r", - " pm.expect(jsonData).to.have.property('version');\r", - " pm.expect(jsonData.version).to.have.property('schema').and.to.be.an('array');\r", - " pm.expect(jsonData.version.schema.length).to.be.above(0);\r", - "\r", - " pm.collectionVariables.set('coll-schemaId', jsonData.version.schema[0]);\r", - "});" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "id": "00af0146-94da-4674-96cc-9feff4ba493f", - "exec": [""], - "type": "text/javascript" - } - } - ], - "id": "4ecaeaa0-5908-497b-ae55-045465cb47e4", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "X-Api-Key", - "type": "text", - "value": "{{env-apiKey}}" - } - ], - "url": { - "raw": "https://api.getpostman.com/apis/:apiId/versions/:versionId", - "protocol": "https", - "host": ["api", "getpostman", "com"], - "path": ["apis", ":apiId", "versions", ":versionId"], - "query": [ - { - "key": null, - "value": "", - "disabled": true - } - ], - "variable": [ - { - "id": "ab65af1f-47bf-463c-b68b-b2a2f1d70081", - "key": "apiId", - "value": "{{coll-apiId}}" - }, - { - "id": "82912cc7-9ae0-4090-9758-42c81f4b6924", - "key": "versionId", - "value": "{{coll-versionId}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get API Schema", - "event": [ - { - "listen": "test", - "script": { - "id": "6f74f9f2-f92e-4993-b0be-f4181d16e01e", - "exec": [ - "try {\r", - " const jsonData = pm.response.json();\r", - " if(jsonData.schema.language.toLowerCase() == 'json'){\r", - " pm.test('Schema is JSON', function(){\r", - " pm.expect(1).to.equal(1);\r", - " pm.collectionVariables.set('coll-schema', jsonData.schema.schema);\r", - " });\r", - " } else {\r", - " pm.test('Schema translates to JSON', function(){\r", - " try{\r", - " const yaml = pm.environment.get('env-jsonToYaml');\r", - " (new Function(yaml))();\r", - "\r", - " const schema = jsyaml.load(jsonData.schema.schema);\r", - " pm.collectionVariables.set('coll-schema', JSON.stringify(schema));\r", - " pm.expect(1).to.equal(1);\r", - " }\r", - " catch(err){\r", - " pm.expect(`${err.name} - ${err.message}`).to.equal(undefined);\r", - " } \r", - " });\r", - " }\r", - "}\r", - "catch(err) {\r", - " console.log(err);\r", - " pm.test('Unable to load schema', function(){\r", - " pm.expect(0).to.equal(1);\r", - " postman.setNextRequest(null);\r", - " })\r", - "}" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "id": "7fb06c94-c343-47f8-8308-59170e3c56b8", - "exec": [ - "if (pm.environment.get(\"env-openapi-json-url\")){", - " pm.request.url = pm.environment.get(\"env-openapi-json-url\");", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "id": "1b67ec27-0a68-4755-b118-43190677c99d", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "X-Api-Key", - "value": "{{env-apiKey}}", - "type": "text" - } - ], - "url": { - "raw": "https://api.getpostman.com/apis/:apiId/versions/:apiVersionId/schemas/:schemaId", - "protocol": "https", - "host": ["api", "getpostman", "com"], - "path": [ - "apis", - ":apiId", - "versions", - ":apiVersionId", - "schemas", - ":schemaId" - ], - "variable": [ - { - "id": "ebe1d781-f0eb-4e96-847e-0f42e2ac15ac", - "key": "apiId", - "value": "{{coll-apiId}}" - }, - { - "id": "3bd638bb-503b-4e2b-8da2-ebd9f5d8a4d4", - "key": "apiVersionId", - "value": "{{coll-versionId}}" - }, - { - "id": "521e1c1e-e161-478c-89ba-1b079435201b", - "key": "schemaId", - "value": "{{coll-schemaId}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get API Base Url", - "event": [ - { - "listen": "test", - "script": { - "id": "5876b636-91d2-405b-a0e6-8bd4e2132969", - "exec": [ - "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", - "const server = pm.environment.get('env-server');\r", - "\r", - "pm.test('Environment has test server defined', function () {\r", - " pm.expect(server).to.not.be.undefined;\r", - "});\r", - "\r", - "pm.test('Schema has server/baseUrl defined', function () {\r", - " const servers = schema.servers;\r", - " pm.expect(servers).to.not.be.undefined;\r", - " const serverToTest = servers.find(s => s.description.toLowerCase() == server.toLowerCase());\r", - " pm.expect(serverToTest).to.not.be.undefined;\r", - "\r", - " pm.expect(serverToTest).to.have.property('url');\r", - " pm.collectionVariables.set('coll-baseUrl', serverToTest.url);\r", - "});\r", - "\r", - "const runComponentTests = pm.environment.get('env-runComponentTests') == 'true';\r", - "if(!runComponentTests){ \r", - " const runContractTests = pm.environment.get('env-runContractTests') == 'true';\r", - " if(runContractTests){\r", - " postman.setNextRequest('Build Schema Tests');\r", - " } else {\r", - " postman.setNextRequest('More APIs to Process?');\r", - " } \r", - "}" - ], - "type": "text/javascript" - } - } - ], - "id": "c8711439-c69a-4420-b034-cb58dc21e110", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [], - "url": "https://postman-echo.com/delay/0" - }, - "response": [] - } - ], - "id": "2778dde1-f785-4aba-84cc-57e1102bff23" - }, - { - "name": "Components", - "item": [ - { - "name": "Verify Component Adherence", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", - "\r", - "const requireParamDescription = Boolean(pm.environment.get('env-requireParamDescription'));\r", - "const requireParamExample = Boolean(pm.environment.get('env-requireParamExample'));\r", - "\r", - "let paramDescriptionMinLength = pm.environment.get('env-paramDescriptionMinLength');\r", - "if (paramDescriptionMinLength) {\r", - " paramDescriptionMinLength = Number(paramDescriptionMinLength);\r", - "}\r", - "\r", - "let paramDescriptionMaxLength = pm.environment.get('env-paramDesciptionMaxLength');\r", - "if (paramDescriptionMaxLength) {\r", - " paramDescriptionMaxLength = Number(paramDescriptionMaxLength);\r", - "}\r", - "\r", - "var testedSchemaRefs = [];\r", - "\r", - "if (schema.components.parameters) {\r", - " for (let prop in schema.components.parameters) {\r", - " let parameter = schema.components.parameters[prop];\r", - "\r", - " pm.test(`Parameter '${prop}' starts with a lowercase letter`, function () {\r", - " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toLowerCase());\r", - " });\r", - "\r", - " if (requireParamDescription) {\r", - " pm.test(`Parameter '${prop}' has a description between ${paramDescriptionMinLength} and ${paramDescriptionMaxLength} characters`, function () {\r", - " pm.expect(parameter).to.have.property('description').and.to.be.a('string');\r", - " pm.expect(parameter.description.length).to.be.at.least(paramDescriptionMinLength);\r", - " pm.expect(parameter.description.length).to.be.at.most(paramDescriptionMaxLength);\r", - " });\r", - " }\r", - "\r", - " if (requireParamExample) {\r", - " pm.test(`Parameter '${prop}' has an example`, function () {\r", - " pm.expect(parameter).to.have.property('schema');\r", - " pm.expect(parameter.schema).to.have.property('example');\r", - " });\r", - " }\r", - " }\r", - "}\r", - "\r", - "if (schema.components.schemas) {\r", - " for (let prop in schema.components.schemas) {\r", - " pm.test(`Schema '${prop}' begins with an uppercase letter`, function () {\r", - " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toUpperCase());\r", - " });\r", - "\r", - " const testedSchema = testedSchemaRefs.find(tsr => tsr == prop);\r", - " if (!testedSchema) {\r", - " const schemaObject = schema.components.schemas[prop];\r", - " testSchemaObject(schema, schemaObject, prop);\r", - " testedSchemaRefs.push(prop);\r", - " }\r", - " }\r", - "}\r", - "\r", - "if (schema.components.responses) {\r", - " for (let prop in schema.components.responses) {\r", - " pm.test(`Response '${prop}' begins with an uppercase letter`, function () {\r", - " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toUpperCase());\r", - " });\r", - "\r", - " if (requireParamDescription) {\r", - " const response = schema.components.responses[prop];\r", - " pm.test(`Response '${prop}' has a description between ${paramDescriptionMinLength} and ${paramDescriptionMaxLength} characters`, function () {\r", - " pm.expect(response).to.have.property('description').and.to.be.a('string');\r", - " pm.expect(response.description.length).to.be.at.least(paramDescriptionMinLength);\r", - " pm.expect(response.description.length).to.be.at.most(paramDescriptionMaxLength);\r", - " });\r", - " }\r", - " }\r", - "}\r", - "\r", - "const runContractTests = pm.environment.get('env-runContractTests') == 'true';\r", - "if (runContractTests) {\r", - " postman.setNextRequest('Build Schema Tests');\r", - "} else {\r", - " postman.setNextRequest('More APIs to Process?');\r", - "}\r", - "\r", - "\r", - "function testSchemaObject(schema, object, objectName) {\r", - " if (object.type && object.type.toLowerCase() == 'object') {\r", - " if (object.required) {\r", - " for (let i = 0; i < object.required.length; i++) {\r", - " const requiredProp = object.required[i];\r", - " pm.test(`Schema '${objectName}' has required property '${requiredProp}' defined`, function () {\r", - " pm.expect(object.properties).to.have.property(requiredProp);\r", - " });\r", - " }\r", - " }\r", - "\r", - " let schemaPropertyExceptions = [];\r", - " if (pm.environment.has('env-schemaPropertyExceptions')) {\r", - " schemaPropertyExceptions = JSON.parse(pm.environment.get('env-schemaPropertyExceptions'));\r", - " }\r", - "\r", - " for (let prop in object.properties) {\r", - " const property = object.properties[prop];\r", - "\r", - " if (!schemaPropertyExceptions.some(pe => pe === prop)) {\r", - " pm.test(`Schema property '${objectName}.${prop}' is lowercase`, function () {\r", - " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toLowerCase());\r", - " });\r", - " }\r", - "\r", - " if (property.type && property.type.toLowerCase() == 'object') {\r", - " testSchemaObject(schema, property, `${objectName}.${prop}`);\r", - " }\r", - " else if (property.type && property.type.toLowerCase() == 'array') {\r", - " testSchemaObject(schema, property, `${objectName}.${prop}(list)`);\r", - " }\r", - " else if (property.oneOf) {\r", - " _.forEach(property.oneOf, (oneOf, i) => {\r", - " testSchemaObject(schema, oneOf, `${objectName}.${prop}(oneOf).${i}`)\r", - " });\r", - " }\r", - " else if (property.allOf) {\r", - " _.forEach(property.allOf, (allOf, i) => {\r", - " testSchemaObject(schema, allOf, `${objectName}.${prop}(allOf).${i}`)\r", - " });\r", - " }\r", - " else if (property.anyOf) {\r", - " _.forEach(property.anyOf, (anyOf, i) => {\r", - " testSchemaObject(schema, anyOf, `${objectName}.${prop}(anyOf).${i}`)\r", - " });\r", - " }\r", - " else {\r", - " if (requireParamDescription && !property.$ref) {\r", - " pm.test(`Schema property '${objectName}.${prop}' has a description between ${paramDescriptionMinLength} and ${paramDescriptionMaxLength} characters`, function () {\r", - " pm.expect(property).to.have.property('description').and.to.be.a('string');\r", - " pm.expect(property.description.length).to.be.at.least(paramDescriptionMinLength);\r", - " pm.expect(property.description.length).to.be.at.most(paramDescriptionMaxLength);\r", - " });\r", - "\r", - " if (property.description) {\r", - " pm.test(`Schema property '${objectName}.${prop}' description is not just the name`, function () {\r", - " pm.expect(prop.toLowerCase()).to.not.equal(property.description.toLowerCase());\r", - " });\r", - " }\r", - " }\r", - "\r", - " if (requireParamExample && !property.$ref) {\r", - " pm.test(`Schema property '${objectName}.${prop}' has an example`, function () {\r", - " pm.expect(property).to.have.property('example');\r", - " });\r", - " }\r", - " }\r", - " }\r", - " }\r", - " else if (object.type && object.type.toLowerCase() == 'array') {\r", - " pm.test(`Schema '${objectName}' has items defined`, function () {\r", - " pm.expect(object).to.have.property('items');\r", - " });\r", - "\r", - " testSchemaObject(schema, object.items, `${objectName}.list`);\r", - " }\r", - " else if (object.oneOf) {\r", - " handleSchemaArray(schema, object, objectName, 'oneOf');\r", - " } else if (object.allOf) {\r", - " handleSchemaArray(schema, object, objectName, 'allOf');\r", - " }\r", - " else if (object.anyOf) {\r", - " handleSchemaArray(schema, object, objectName, 'anyOf');\r", - " }\r", - " else if (object.$ref) {\r", - " const name = getName(object.$ref);\r", - " const testedRef = testedSchemaRefs.find(tsr => tsr == name);\r", - " if (!testedRef) {\r", - " testSchemaObject(schema, schema.components.schemas[name], objectName);\r", - " testedSchemaRefs.push(name);\r", - " }\r", - " }\r", - " else {\r", - " pm.test(`Schema '${objectName}' has a declared type`, function () {\r", - " pm.expect(object).to.have.property('type');\r", - " });\r", - " }\r", - "}\r", - "\r", - "function handleSchemaArray(schema, object, objectName, arrayType) {\r", - " for (let i = 0; i < object[arrayType].length; i++) {\r", - " const arraySchema = object[arrayType][i];\r", - " if (arraySchema.$ref) {\r", - " const name = getName(arraySchema.$ref);\r", - " const testedRef = testedSchemaRefs.find(tsr => tsr == name);\r", - " if (!testedRef) {\r", - " testSchemaObject(schema, schema.components.schemas[name], `${objectName}[${i}](ref ${name})`);\r", - " testedSchemaRefs.push(name);\r", - " }\r", - " }\r", - " else {\r", - " testSchemaObject(schema, arraySchema, `${objectName}[${i}]`);\r", - " }\r", - " }\r", - "}\r", - "\r", - "function getName(ref) {\r", - " let pieces = ref.split('/');\r", - " return pieces[pieces.length - 1];\r", - "}\r", - "" - ], - "type": "text/javascript", - "id": "968b2ac8-c423-42d7-ab68-809e0292ab31" - } - } - ], - "id": "46526b39-701b-4f22-a0a3-03ec53319450", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [], - "url": "https://postman-echo.com/delay/0" - }, - "response": [] - } - ], - "id": "18e4b0a6-d641-4661-9a29-918247d63f5f" - }, - { - "name": "Contract Tests", - "item": [ - { - "name": "Build Schema Tests", - "event": [ - { - "listen": "prerequest", - "script": { - "exec": [ - "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", - "\r", - "let schemaTests = [];\r", - "for (let prop in schema.paths) {\r", - " const pathName = prop;\r", - " let path = {\r", - " path: `${pm.collectionVariables.get('coll-baseUrl')}${pathName}`,\r", - " parameters: schema.paths[prop].parameters,\r", - " };\r", - "\r", - " for (let method in schema.paths[prop]) {\r", - " if (method.toLowerCase() == 'parameters' || isMockEndpoint(schema.paths[prop][method])) {\r", - " continue;\r", - " }\r", - "\r", - " let currentPath = _.cloneDeep(path);\r", - " currentPath.method = method.toUpperCase();\r", - " let pathMethod = schema.paths[prop][method];\r", - " currentPath.parameters = combineParameters(currentPath.parameters, pathMethod.parameters);\r", - " let securityExtension = pm.environment.get('env-securityExtensionName');\r", - " if (securityExtension && pathMethod[securityExtension] && pathMethod[securityExtension].length > 0) {\r", - " currentPath.allowedRole = pathMethod[securityExtension][0];\r", - " }\r", - "\r", - " const expectedResponses = getExpectedResponses(pathMethod);\r", - " currentPath.responses = expectedResponses;\r", - "\r", - " if (pathMethod.requestBody) {\r", - " let bodyModel;\r", - " if (pathMethod.requestBody.content['application/json']?.schema?.$ref) {\r", - " bodyModel = getSchemaReference(schema, pathMethod.requestBody.content['application/json'].schema.$ref);\r", - " }\r", - " else if (pathMethod.requestBody.content['application/json']?.schema) {\r", - " bodyModel = pathMethod.requestBody.content['application/json'].schema;\r", - " }\r", - " else {\r", - " continue;\r", - " }\r", - "\r", - " const models = buildModels(schema, bodyModel);\r", - " const mutations = buildModelMutations(models);\r", - "\r", - " mutations.forEach((mutation) => {\r", - " let schemaTest = _.cloneDeep(currentPath);\r", - " Object.assign(schemaTest, mutation);\r", - " schemaTest.name = `${schemaTest.method} - ${pathName} - ${schemaTest.description} - SUCCESS: ${schemaTest.success}`;\r", - " schemaTests.push(schemaTest);\r", - " });\r", - " }\r", - " else {\r", - " currentPath.name = `${currentPath.method} - ${pathName} - No Request Body - SUCCESS: true`;\r", - " currentPath.success = true;\r", - " schemaTests.push(currentPath);\r", - " }\r", - " }\r", - "}\r", - "schemaTests = moveDeleteEndpointsToEnd(schemaTests);\r", - "pm.collectionVariables.set('coll-schemaTests', JSON.stringify(schemaTests));\r", - "\r", - "// \r", - "// Move delete endpoints to the end for cleanup\r", - "//\r", - "function moveDeleteEndpointsToEnd(schemaTests) {\r", - " let sortedTests = [...schemaTests];\r", - " try {\r", - " let successfulDeletes = sortedTests.filter(schemaTest => schemaTest.method == 'DELETE' && schemaTest.success);\r", - "\r", - " if (successfulDeletes) {\r", - " // order deletes from the deepest entity to highest level entity based on path\r", - " successfulDeletes.sort((a, b) => b.path.split('/').length - a.path.split('/').length);\r", - " sortedTests = sortedTests.filter(schemaTest => !successfulDeletes.find(sd => sd == schemaTest));\r", - " sortedTests = sortedTests.concat(successfulDeletes);\r", - " }\r", - " }\r", - " catch (err) {\r", - " console.log('An error occurred when sorting delete tests', err);\r", - " }\r", - "\r", - " return sortedTests;\r", - "}\r", - "\r", - "//\r", - "// Supporting Methods Below\r", - "//\r", - "function buildModels(schema, object) {\r", - " let models = [];\r", - "\r", - " if (object['$ref']) {\r", - " object = getSchemaReference(schema, object['$ref']);\r", - " }\r", - "\r", - " if (object.type && object.type.toLowerCase() == 'object') {\r", - " if (object.required && object.required.length > 0) {\r", - " models.push({});\r", - " _.forEach(object.required, function (param) {\r", - " const property = object.properties[param];\r", - "\r", - " if (property.type && ['string', 'number', 'integer', 'boolean'].includes(property.type.toLowerCase())) {\r", - " for (let modelIndex = 0; modelIndex < models.length; modelIndex++) {\r", - " let model = models[modelIndex];\r", - " model[param] = property.example;\r", - " }\r", - " }\r", - " else {\r", - " const nestedObjects = buildModels(schema, property);\r", - " models = addToModels(models, nestedObjects, param);\r", - " }\r", - " });\r", - " }\r", - "\r", - " if (object.minProperties) {\r", - " _.forEach(models, function (model) {\r", - " if (Object.keys(model).length < object.minProperties) {\r", - " for (let i = Object.keys(model).length; i < object.minProperties; i++) {\r", - " for (const [key, value] of Object.entries(object.properties)) {\r", - " if (['string', 'number', 'integer', 'boolean'].includes(value.type.toLowerCase()) && model[key] == undefined) {\r", - " model[key] = value.example;\r", - " break;\r", - " }\r", - " }\r", - " }\r", - " }\r", - " })\r", - " }\r", - " }\r", - " else if (object.type && object.type.toLowerCase() == 'array') {\r", - " let items = buildModels(schema, object.items);\r", - " if (Array.isArray(items)) {\r", - " for (let i = 0; i < items.length; i++) {\r", - " models.push([items[i]]);\r", - " }\r", - " }\r", - " else {\r", - " models.push([items]);\r", - " }\r", - " }\r", - " else if (object.oneOf) {\r", - " _.forEach(object.oneOf, function (component) {\r", - " let items = buildModels(schema, component);\r", - " models = models.concat(items);\r", - " });\r", - " }\r", - " else if (object.allOf) {\r", - " let pieces = [{}];\r", - " _.forEach(object.allOf, function (component) {\r", - " let componentModels = buildModels(schema, component);\r", - " pieces = addToModels(pieces, componentModels);\r", - " });\r", - "\r", - " models = pieces;\r", - " }\r", - " else if (object.anyOf) {\r", - " let pieces = [];\r", - " let combinedPieces = [{}];\r", - " _.forEach(object.anyOf, function (component) {\r", - " let componentModels = buildModels(schema, component);\r", - " combinedPieces = addToModels(combinedPieces, componentModels);\r", - " pieces = pieces.concat(componentModels);\r", - " });\r", - "\r", - " models = pieces.concat(combinedPieces);\r", - " }\r", - " else {\r", - " // All other options are primitive values\r", - " return object.example;\r", - " }\r", - " return models;\r", - "}\r", - "\r", - "function getSchemaReference(schema, referenceName) {\r", - " const refPieces = referenceName.split('/');\r", - " let reference = schema;\r", - " for (let i = 1; i < refPieces.length; i++) {\r", - " reference = reference[refPieces[i]];\r", - " }\r", - "\r", - " return reference;\r", - "}\r", - "\r", - "function addToModels(models, newPieces, name) {\r", - " let newModels = [];\r", - " _.forEach(models, function (model) {\r", - " _.forEach(newPieces, function (newPiece) {\r", - " let newModel = _.cloneDeep(model);\r", - " if (name) {\r", - " newModel[name] = newPiece;\r", - " }\r", - " else {\r", - " Object.assign(newModel, newPiece);\r", - " }\r", - " newModels.push(newModel);\r", - " });\r", - " });\r", - "\r", - " return newModels;\r", - "}\r", - "\r", - "function buildModelMutations(models) {\r", - " let modelMutations = [];\r", - " _.forEach(models, function (model) {\r", - " addMutation(true, 'Has all required fields', model, modelMutations);\r", - " let mutations = buildMutation(model);\r", - " modelMutations = modelMutations.concat(mutations);\r", - " });\r", - "\r", - " return modelMutations;\r", - "}\r", - "\r", - "function buildMutation(model) {\r", - " let mutations = [];\r", - "\r", - " for (const [key, value] of Object.entries(model)) {\r", - " if (typeof value == 'object') {\r", - " let nestedMutations = buildMutation(value);\r", - " nestedMutations.forEach((nestedMutation) => {\r", - " let mutation = _.cloneDeep(model);\r", - " mutation[key] = nestedMutation.body;\r", - " addMutation(false, `${nestedMutation.description} in ${key} object`, mutation, mutations);\r", - " });\r", - "\r", - " let mutation = _.cloneDeep(model);\r", - " delete mutation[key];\r", - " addMutation(false, `Missing ${key} object`, mutation, mutations);\r", - "\r", - " let emptyMutation = _.cloneDeep(model);\r", - " emptyMutation[key] = {};\r", - " addMutation(false, `Empty ${key} object`, emptyMutation, mutations);\r", - " }\r", - " else {\r", - " if (Array.isArray(value)) {\r", - " console.log('probably an error');\r", - " }\r", - " let mutation = _.cloneDeep(model);\r", - " delete mutation[key];\r", - " addMutation(false, `Missing ${key} property`, mutation, mutations);\r", - "\r", - " let blankMutation = _.cloneDeep(model);\r", - " blankMutation[key] = '';\r", - " addMutation(false, `Blank ${key} property`, blankMutation, mutations);\r", - " }\r", - " }\r", - "\r", - " return mutations;\r", - "}\r", - "\r", - "function addMutation(isSuccess, description, mutation, mutations) {\r", - " mutations.push({\r", - " success: isSuccess,\r", - " description: description,\r", - " body: mutation\r", - " });\r", - "}\r", - "\r", - "function getExpectedResponses(pathMethod) {\r", - " const responses = [];\r", - " for (const [statusCode, value] of Object.entries(pathMethod.responses)) {\r", - " let response = {\r", - " statusCode: Number(statusCode)\r", - " };\r", - "\r", - " if (value['x-postman-variables'] && Array.isArray(value['x-postman-variables'])) {\r", - " response.variables = value['x-postman-variables'].filter(variable => variable.type.toLowerCase() === 'save');\r", - " }\r", - "\r", - " if (value.$ref) {\r", - " response.$ref = value.$ref;\r", - " }\r", - " else {\r", - " if (value.content?.['application/json']?.schema) {\r", - " if (value.content['application/json'].schema.$ref) {\r", - " response.$ref = value.content['application/json'].schema.$ref;\r", - " }\r", - " else {\r", - " response.schema = value.content['application/json'].schema;\r", - " }\r", - " }\r", - " }\r", - "\r", - " responses.push(response);\r", - " }\r", - " return responses;\r", - "}\r", - "\r", - "function isMockEndpoint(pathMethod) {\r", - " let isMock = false;\r", - " if (pathMethod && pathMethod['x-amazon-apigateway-integration'] && pathMethod['x-amazon-apigateway-integration'].type\r", - " && pathMethod['x-amazon-apigateway-integration'].type.toLowerCase() == 'mock') {\r", - " isMock = true;\r", - " }\r", - "\r", - " return isMock;\r", - "}\r", - "\r", - "function combineParameters(endpointParameters, methodParameters) {\r", - " if (!endpointParameters && !methodParameters) {\r", - " return;\r", - " }\r", - " let parameters = [];\r", - " if (endpointParameters && endpointParameters.length) {\r", - " parameters = [...endpointParameters];\r", - " }\r", - "\r", - " if (methodParameters && methodParameters.length) {\r", - " parameters = [...parameters, ...methodParameters];\r", - " }\r", - "\r", - " return parameters;\r", - "}" - ], - "type": "text/javascript", - "id": "3017117f-1119-4cda-8de9-f181be051dff" - } - }, - { - "listen": "test", - "script": { - "exec": [ - "let schemaTests = pm.collectionVariables.get('coll-schemaTests');\r", - "if(schemaTests){\r", - " schemaTests = JSON.parse(schemaTests);\r", - " if(!schemaTests || !schemaTests.length){\r", - " postman.setNextRequest('More APIs to Process?');\r", - " }\r", - "}" - ], - "type": "text/javascript", - "id": "161ed627-1e43-4d10-923c-308e9b039bd9" - } - } - ], - "id": "bd6e30f9-999f-4ace-91ca-270b0bd1f1a5", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [], - "url": "https://postman-echo.com/delay/0" - }, - "response": [] - }, - { - "name": "Test Request", - "event": [ - { - "listen": "prerequest", - "script": { - "id": "49ce35ea-de86-4a14-b90e-3d396e2feb2d", - "exec": [ - "const url = require('url');\r", - "\r", - "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", - "let schemaTests = JSON.parse(pm.collectionVariables.get('coll-schemaTests'));\r", - "\r", - "const schemaTest = schemaTests.shift();\r", - "pm.collectionVariables.set('coll-schemaTests', JSON.stringify(schemaTests));\r", - "pm.variables.set('currentSchemaTest', JSON.stringify(schemaTest));\r", - "\r", - "const path = replacePathParameters(schema, schemaTest.path, schemaTest.parameters);\r", - "pm.request.url.update(path);\r", - "delete pm.request.url.auth;\r", - "delete pm.request.url.port;\r", - "delete pm.request.url.hash;\r", - "if (pm.request.url.protocol) {\r", - " pm.request.url.protocol = pm.request.url.protocol.replace(/\\:$/, '');\r", - "} else {\r", - " pm.request.url.protocol = 'https';\r", - "}\r", - "pm.request.method = schemaTest.method;\r", - "pm.request.name = schemaTest.name;\r", - "\r", - "pm.variables.set('requestName', schemaTest.name);\r", - "pm.variables.set('body', JSON.stringify(schemaTest.body));\r", - "\r", - "// Add top level parameters from the path\r", - "const roleHeaderName = pm.environment.get('env-roleHeaderName');\r", - "\r", - "if (schemaTest.parameters) {\r", - " for (let i = 0; i < schemaTest.parameters.length; i++) {\r", - " let param = schemaTest.parameters[i];\r", - "\r", - " if (param.$ref) {\r", - " let pieces = param.$ref.split('/');\r", - " const name = pieces[pieces.length - 1];\r", - " const schemaParam = schema.components.parameters[name];\r", - " const paramType = schemaParam.in.toLowerCase();\r", - " const paramValue = loadParameterValue(schemaParam);\r", - " if (paramType == 'header' && schemaParam.required == true) {\r", - " if (roleHeaderName && schemaParam.name.toLowerCase() == roleHeaderName.toLowerCase()) {\r", - " pm.request.headers.upsert({ key: schemaParam.name, value: schemaTest.allowedRole });\r", - " }\r", - " else {\r", - " pm.request.headers.upsert({ key: schemaParam.name, value: paramValue });\r", - " }\r", - " } else if (paramType == 'query' && schemaParam.required == true) {\r", - " pm.request.url.query.upsert({ key: schemaParam.name, value: paramValue });\r", - " }\r", - " } else {\r", - " const paramType = param.in.toLowerCase();\r", - " const paramValue = loadParameterValue(param);\r", - " if (paramType == 'header') {\r", - " pm.request.headers.upsert({ key: param.name, value: paramValue });\r", - " } else if (paramType == 'query' && param.required == true) {\r", - " pm.request.url.query.upsert({ key: param.name, value: paramValue });\r", - " }\r", - " }\r", - " }\r", - "}\r", - "\r", - "function loadParameterValue(parameter) {\r", - " let parameterValue;\r", - " if (parameter['x-postman-variables']) {\r", - " let variable = parameter['x-postman-variables'].find(v => v.type.toLowerCase() === 'load');\r", - " if (variable && pm.collectionVariables.has(variable.name)) {\r", - " parameterValue = pm.collectionVariables.get(variable.name);\r", - " }\r", - " else {\r", - " parameterValue = resolveParameterExample(parameter);\r", - " }\r", - " }\r", - " else {\r", - " parameterValue = resolveParameterExample(parameter);\r", - " }\r", - "\r", - " return parameterValue;\r", - "}\r", - "\r", - "function resolveParameterExample(parameter) {\r", - " let paramValue = (parameter.schema.example != undefined) ? parameter.schema.example : parameter.example;\r", - " let value = paramValue;\r", - " if (typeof paramValue !== 'number' && typeof paramValue !== 'boolean') {\r", - " let pathVariableRegex = /^{{\\$.*}}$/;\r", - " let matches = paramValue.match(pathVariableRegex);\r", - "\r", - " if (matches && matches.length) {\r", - " value = pm.variables.replaceIn(paramValue);\r", - " }\r", - " }\r", - "\r", - " return encodeURIComponent(value);\r", - "}\r", - "\r", - "function replacePathParameters(schema, pathName, parameters) {\r", - " let replacedPathName = pathName;\r", - " let pathVariableRegex = /{([^}]*)}/g;\r", - " let matches = pathName.match(pathVariableRegex);\r", - " _.forEach(matches, function (match) {\r", - " let paramName = match.substring(1, match.length - 1);\r", - " _.forEach(parameters, function (param) {\r", - " if (param.$ref) {\r", - " let parameter = getSchemaReference(schema, param.$ref);\r", - " if (parameter.in && parameter.in.toLowerCase() == 'path' && parameter.name && parameter.name == paramName) {\r", - " let parameterValue = loadParameterValue(parameter);\r", - " replacedPathName = replacedPathName.replace(match, parameterValue);\r", - " return false;\r", - " }\r", - " } else {\r", - " if (param.in && param.in.toLowerCase() == 'path' && param.name && param.name == paramName) {\r", - " let parameterValue = loadParameterValue(param);\r", - " replacedPathName = replacedPathName.replace(match, parameterValue);\r", - " return false;\r", - " }\r", - " }\r", - " });\r", - " });\r", - "\r", - " return url.parse(replacedPathName);\r", - "}\r", - "\r", - "function getSchemaReference(schema, referenceName) {\r", - " const refPieces = referenceName.split('/');\r", - " let reference = schema;\r", - " for (let i = 1; i < refPieces.length; i++) {\r", - " reference = reference[refPieces[i]];\r", - " }\r", - "\r", - " return reference;\r", - "}" - ], - "type": "text/javascript" - } - }, - { - "listen": "test", - "script": { - "id": "2524e1aa-f349-412f-91eb-b7857f29d495", - "exec": [ - "const schemaTests = JSON.parse(pm.collectionVariables.get('coll-schemaTests'));\r", - "if(schemaTests.length > 0){\r", - " postman.setNextRequest('Test Request');\r", - "}\r", - "\r", - "const schemaTest = JSON.parse(pm.variables.get('currentSchemaTest'));\r", - "console.log(schemaTest.name);\r", - "\r", - "pm.test(`${schemaTest.name} - Has expected status code`, function () {\r", - " // const errorOn500 = pm.environment.get('env-errorOn500');\r", - " // if(errorOn500){\r", - " // pm.response.to.not.have.status(500);\r", - " // }\r", - "\r", - " if(schemaTest.success){\r", - " try{\r", - " if(pm.response.code >= 400) {\r", - " const jsonData = pm.response.json();\r", - " if(pm.response.code == 401) {\r", - " pm.expect(pm.request.headers.get('Role')).to.equal('role');\r", - " }\r", - " pm.expect('').to.equal(jsonData.message); \r", - " }\r", - " \r", - " pm.expect(pm.response.code).to.not.equal(400);\r", - " }\r", - " catch(err) {\r", - " console.log(err);\r", - " pm.expect(pm.response.code).to.not.equal(400);\r", - " } \r", - " }\r", - " else {\r", - " const statusCode = pm.response.code\r", - " pm.expect(statusCode === 400 || statusCode === 422).to.be.true;\r", - " } \r", - "});\r", - "\r", - "const expectedResponse = schemaTest.responses.find(r => r.statusCode == pm.response.code);\r", - "pm.test(`${schemaTest.name} - Status code (${pm.response.code}) is allowed`, function(){\r", - " pm.expect(expectedResponse).to.exist;\r", - "});\r", - "\r", - "if(expectedResponse){\r", - " pm.test(`${schemaTest.name} - Has expected response body schema`, function(){\r", - " const Ajv = require('ajv');\r", - " const ajv = new Ajv({allErrors: true,format: false,nullable: true});\r", - " \r", - " if(pm.response.code == 204 || shouldResponseBeEmpty(expectedResponse)){\r", - " checkForEmptyResponse();\r", - " }\r", - " else if(expectedResponse.$ref){ \r", - " const jsonData = pm.response.json();\r", - " const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", - " ajv.addSchema(schema, 'OAS');\r", - " const valid = ajv.validate({$ref: `OAS${expectedResponse.$ref}`}, jsonData);\r", - " const errors = ajv.errorsText(valid.errors);\r", - " pm.expect(errors).to.equal('No errors');\r", - " if(errors !== 'No errors'){\r", - " console.log(errors);\r", - " }\r", - " }\r", - " else if(expectedResponse.schema){\r", - " const jsonData = pm.response.json();\r", - " const validate = ajv.compile(expectedResponse.schema);\r", - " const valid = validate(jsonData);\r", - " const errors = ajv.errorsText(valid.errors);\r", - " pm.expect(errors).to.equal('No errors');\r", - " if(errors !== 'No errors'){\r", - " console.log(errors);\r", - " }\r", - " }\r", - " else {\r", - " checkForEmptyResponse();\r", - " }\r", - "\r", - " if(expectedResponse.variables){\r", - " const jsonData = pm.response.json();\r", - " _.forEach(expectedResponse.variables, function(variable){\r", - " let pathPieces = variable.path.split('.').filter(piece => piece);\r", - " let data = jsonData;\r", - " let found = true;\r", - " _.forEach(pathPieces, function(piece){\r", - " if(data[piece]){\r", - " data = data[piece];\r", - " }\r", - " else {\r", - " found = false;\r", - " }\r", - " });\r", - "\r", - " if(found){\r", - " pm.collectionVariables.set(variable.name, data);\r", - " }\r", - " else {\r", - " pm.test(`Unable to save dynamic variable ${variable.name} at the provided path.`, function() {\r", - " pm.expect(true).to.equal(variable.path);\r", - " });\r", - " }\r", - " });\r", - " }\r", - " });\r", - "}\r", - "\r", - "function checkForEmptyResponse() {\r", - " let emptyBody = true;\r", - " if(pm.response.text()){\r", - " emptyBody = false; \r", - " }\r", - "\r", - " pm.expect(emptyBody).to.be.true;\r", - "}\r", - "\r", - "function shouldResponseBeEmpty(expectedResponse){\r", - " let responseSchema = expectedResponse.schema;\r", - " if(expectedResponse.$ref){\r", - " let schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", - " responseSchema = getSchemaReference(schema, expectedResponse.$ref);\r", - " if(expectedResponse.$ref.startsWith('#/components/responses')){\r", - " return (!responseSchema || !responseSchema.content || !responseSchema.content['application/json'] \r", - " || !responseSchema.content['application/json'].schema || Object.keys(responseSchema.content['application/json'].schema).length == 0);\r", - " } else {\r", - " return false;\r", - " }\r", - " }\r", - " else {\r", - " return (Object.keys(responseSchema).length == 0);\r", - " }\r", - "}\r", - "\r", - "function getSchemaReference(schema, referenceName){\r", - " const refPieces = referenceName.split('/');\r", - " let reference = schema;\r", - " for(let i = 1; i < refPieces.length; i++){\r", - " reference = reference[refPieces[i]];\r", - " }\r", - "\r", - " return reference;\r", - "}" - ], - "type": "text/javascript" - } - } - ], - "id": "416eff4d-749b-4b49-9c3c-7e6cfbfb7c2c", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "{{body}}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": "https://postman-echo.com/get" - }, - "response": [] - } - ], - "id": "5710c277-a099-4c7d-a8ab-9bb911918ef9" - }, - { - "name": "Finalize", - "item": [ - { - "name": "More APIs to Process?", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "let apis = pm.collectionVariables.get('coll-apiIds');\r", - "if(apis){\r", - " try{\r", - " apis = JSON.parse(apis);\r", - " if(apis.length > 0){\r", - " postman.setNextRequest('Get Current API Version');\r", - " }\r", - " }\r", - " catch(err){} \r", - "}" - ], - "type": "text/javascript", - "id": "c236b1d1-9f04-4502-b754-ee4d6430a3ed" - } - } - ], - "id": "eafd2509-05d8-42ee-ab2d-dd7f45453f5c", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [], - "url": "https://postman-echo.com/delay/0" - }, - "response": [] - }, - { - "name": "Remove Test Variables", - "event": [ - { - "listen": "prerequest", - "script": { - "exec": [ - "// See https://blog.postman.com/2019/05/28/pro-tip-dynamically-unset-postman-environment-variables/\r", - "// for more details on what we're doing here. \r", - "\r", - "cleanupCollectionVariables();\r", - "\r", - "function cleanupCollectionVariables() {\r", - " const clean = _.keys(pm.collectionVariables.toObject());\r", - "\r", - " _.each(clean, (arrItem) => {\r", - " pm.collectionVariables.unset(arrItem);\r", - " });\r", - "}" - ], - "type": "text/javascript", - "id": "168455e5-e394-48df-902d-dbab8352acab" - } - }, - { - "listen": "test", - "script": { - "exec": [""], - "type": "text/javascript", - "id": "ac89d252-3423-481b-9ba5-d0b3f55ca383" - } - } - ], - "id": "6e4c0ea7-b3c4-4e33-bc13-ac7ab563f57b", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "url": "https://postman-echo.com/delay/0" - }, - "response": [] - } - ], - "id": "92035d83-c9a1-425a-8a69-65bc677fbc13" - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [""], - "id": "38efdb22-19c6-42a8-aa8b-ef1271ce04ef" - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [""], - "id": "c48549ed-e6df-407a-8452-ec1db5cc81eb" - } - } - ], - "variable": [ - { - "key": "coll-schema", - "value": "" - }, - { - "key": "coll-baseUrl", - "value": "" - }, - { - "key": "coll-schemaTests", - "value": "" - } - ] + "info": { + "_postman_id": "03c5c4b6-9071-4ab4-b5a1-4bda97a3b3aa", + "name": "Contract Test Generator", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" + }, + "item": [ + { + "name": "API Validation", + "item": [ + { + "name": "Cleanup Previous Run", + "event": [ + { + "listen": "prerequest", + "script": { + "id": "6e0312e1-1276-47b2-92be-b481545de5fb", + "exec": [ + "// See https://blog.postman.com/2019/05/28/pro-tip-dynamically-unset-postman-environment-variables/\r", + "// for more details on what we're doing here. \r", + "\r", + "cleanupCollectionVariables();\r", + "\r", + "function cleanupCollectionVariables() {\r", + " const clean = _.keys(pm.collectionVariables.toObject());\r", + "\r", + " _.each(clean, (arrItem) => {\r", + " pm.collectionVariables.unset(arrItem);\r", + " });\r", + "}" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "id": "acaabbd4-94fc-4444-8718-b9ca2c087721", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "id": "30368860-aef9-46d6-ad44-8fac61b8f842", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "url": "https://postman-echo.com/delay/0" + }, + "response": [] + }, + { + "name": "Initialize", + "event": [ + { + "listen": "test", + "script": { + "id": "d15a1e94-1145-4006-b514-b5300671da90", + "exec": [ + "var envSchema = null\r", + "if (pm.environment.get(\"env-openapi-json-url\")){\r", + " envSchema = JSON.stringify(pm.response.json());\r", + "}\r", + "\r", + "const providedSchema = pm.environment.get('env-schema') || envSchema;\r", + "if(providedSchema){\r", + " let success = true;\r", + " try{\r", + " const yaml = pm.environment.get('env-jsonToYaml');\r", + " (new Function(yaml))();\r", + "\r", + " const schema = jsyaml.load(providedSchema);\r", + " pm.collectionVariables.set('coll-schema', JSON.stringify(schema));\r", + " postman.setNextRequest('Get API Base Url');\r", + " }\r", + " catch(err){\r", + " console.log(err);\r", + " success = false;\r", + " postman.setNextRequest(null);\r", + " }\r", + "\r", + " pm.test('Successfully converted provided schema', function(){\r", + " pm.expect(success).to.be.true;\r", + " }); \r", + "}" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "9e1c1f12-58f5-4cea-b6c0-58be6133c033", + "exec": [ + "if (pm.environment.get(\"env-openapi-json-url\")){", + " pm.request.url = pm.environment.get(\"env-openapi-json-url\");", + "}" + ], + "type": "text/javascript" + } + } + ], + "id": "367b4112-337b-4bc0-821d-4687694559ad", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "url": "https://postman-echo.com/delay/0" + }, + "response": [] + }, + { + "name": "Validate API In Workspace", + "event": [ + { + "listen": "test", + "script": { + "id": "c7c74561-6423-4fbe-ab30-bd66746c6cdf", + "exec": [ + "const minApiCount = Number(pm.environment.get('env-minApiCount'));\r", + "const maxApiCount = Number(pm.environment.get('env-maxApiCount'));\r", + "const jsonData = pm.response.json();\r", + "\r", + "pm.test(`Workspace API count is between ${minApiCount} and ${maxApiCount}. (Count: ${jsonData.apis.length})`, function () { \r", + " pm.expect(jsonData.apis.length).to.be.at.least(minApiCount); \r", + " pm.expect(jsonData.apis.length).to.be.at.most(maxApiCount);\r", + "});\r", + "\r", + "let apiIds = [];\r", + "_.forEach(jsonData.apis, function(api){\r", + " apiIds.push(api.id);\r", + "});\r", + "\r", + "pm.collectionVariables.set('coll-apiIds', JSON.stringify(apiIds));" + ], + "type": "text/javascript" + } + } + ], + "id": "1baa5ceb-08b6-47c7-9112-8f54c039805d", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "X-Api-Key", + "value": "{{env-apiKey}}", + "type": "text" + } + ], + "url": { + "raw": "https://api.getpostman.com/apis?workspace={{env-workspaceId}}", + "protocol": "https", + "host": [ + "api", + "getpostman", + "com" + ], + "path": [ + "apis" + ], + "query": [ + { + "key": "workspace", + "value": "{{env-workspaceId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Get Current API Version", + "event": [ + { + "listen": "test", + "script": { + "id": "683d7e4f-8336-41a1-b14c-5893b3e49fba", + "exec": [ + "const jsonData = pm.response.json();\r", + "\r", + "pm.test('API has one or more versions', function(){\r", + " pm.expect(jsonData).to.have.property('versions').and.to.be.an('array');\r", + " pm.expect(jsonData.versions.length).to.be.above(0);\r", + "});\r", + "\r", + "const version = jsonData.versions[0];\r", + "pm.collectionVariables.set('coll-versionId', version.id);" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "ae6bfbce-aad4-4f31-9b82-ba6f666658a5", + "exec": [ + "let apiIds = pm.collectionVariables.get('coll-apiIds');\r", + "if(apiIds){\r", + " apiIds = JSON.parse(apiIds);\r", + " const apiId = apiIds.pop();\r", + "\r", + " pm.collectionVariables.set('coll-apiId', apiId);\r", + " pm.collectionVariables.set('coll-apiIds', JSON.stringify(apiIds));\r", + "}\r", + "else {\r", + " pm.request.url = 'https://postman-echo.com/delay/0'\r", + " pm.request.name = 'No APIs found in the workspace. Skipping execution';\r", + " postman.setNextRequest(null);\r", + "}" + ], + "type": "text/javascript" + } + } + ], + "id": "37810e31-7b06-4ca4-a422-d0012834d1e4", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "X-Api-Key", + "value": "{{env-apiKey}}", + "type": "text" + } + ], + "url": { + "raw": "https://api.getpostman.com/apis/:apiId/versions", + "protocol": "https", + "host": [ + "api", + "getpostman", + "com" + ], + "path": [ + "apis", + ":apiId", + "versions" + ], + "query": [ + { + "key": null, + "value": "", + "disabled": true + } + ], + "variable": [ + { + "id": "66de574b-c196-407e-9be7-ff53c0dac927", + "key": "apiId", + "value": "{{coll-apiId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Get Current API Schema", + "event": [ + { + "listen": "test", + "script": { + "id": "38beef82-f213-44cc-9a11-c326fb5b003d", + "exec": [ + "const jsonData = pm.response.json();\r", + "\r", + "pm.test('Has schema for current version', function(){\r", + " pm.expect(jsonData).to.have.property('version');\r", + " pm.expect(jsonData.version).to.have.property('schema').and.to.be.an('array');\r", + " pm.expect(jsonData.version.schema.length).to.be.above(0);\r", + "\r", + " pm.collectionVariables.set('coll-schemaId', jsonData.version.schema[0]);\r", + "});" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "00af0146-94da-4674-96cc-9feff4ba493f", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "id": "4ecaeaa0-5908-497b-ae55-045465cb47e4", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "X-Api-Key", + "type": "text", + "value": "{{env-apiKey}}" + } + ], + "url": { + "raw": "https://api.getpostman.com/apis/:apiId/versions/:versionId", + "protocol": "https", + "host": [ + "api", + "getpostman", + "com" + ], + "path": [ + "apis", + ":apiId", + "versions", + ":versionId" + ], + "query": [ + { + "key": null, + "value": "", + "disabled": true + } + ], + "variable": [ + { + "id": "ab65af1f-47bf-463c-b68b-b2a2f1d70081", + "key": "apiId", + "value": "{{coll-apiId}}" + }, + { + "id": "82912cc7-9ae0-4090-9758-42c81f4b6924", + "key": "versionId", + "value": "{{coll-versionId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Get API Schema", + "event": [ + { + "listen": "test", + "script": { + "id": "6f74f9f2-f92e-4993-b0be-f4181d16e01e", + "exec": [ + "try {\r", + " const jsonData = pm.response.json();\r", + " if(jsonData.schema.language.toLowerCase() == 'json'){\r", + " pm.test('Schema is JSON', function(){\r", + " pm.expect(1).to.equal(1);\r", + " pm.collectionVariables.set('coll-schema', jsonData.schema.schema);\r", + " });\r", + " } else {\r", + " pm.test('Schema translates to JSON', function(){\r", + " try{\r", + " const yaml = pm.environment.get('env-jsonToYaml');\r", + " (new Function(yaml))();\r", + "\r", + " const schema = jsyaml.load(jsonData.schema.schema);\r", + " pm.collectionVariables.set('coll-schema', JSON.stringify(schema));\r", + " pm.expect(1).to.equal(1);\r", + " }\r", + " catch(err){\r", + " pm.expect(`${err.name} - ${err.message}`).to.equal(undefined);\r", + " } \r", + " });\r", + " }\r", + "}\r", + "catch(err) {\r", + " console.log(err);\r", + " pm.test('Unable to load schema', function(){\r", + " pm.expect(0).to.equal(1);\r", + " postman.setNextRequest(null);\r", + " })\r", + "}" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "7fb06c94-c343-47f8-8308-59170e3c56b8", + "exec": [ + "if (pm.environment.get(\"env-openapi-json-url\")){", + " pm.request.url = pm.environment.get(\"env-openapi-json-url\");", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "id": "1b67ec27-0a68-4755-b118-43190677c99d", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "X-Api-Key", + "value": "{{env-apiKey}}", + "type": "text" + } + ], + "url": { + "raw": "https://api.getpostman.com/apis/:apiId/versions/:apiVersionId/schemas/:schemaId", + "protocol": "https", + "host": [ + "api", + "getpostman", + "com" + ], + "path": [ + "apis", + ":apiId", + "versions", + ":apiVersionId", + "schemas", + ":schemaId" + ], + "variable": [ + { + "id": "ebe1d781-f0eb-4e96-847e-0f42e2ac15ac", + "key": "apiId", + "value": "{{coll-apiId}}" + }, + { + "id": "3bd638bb-503b-4e2b-8da2-ebd9f5d8a4d4", + "key": "apiVersionId", + "value": "{{coll-versionId}}" + }, + { + "id": "521e1c1e-e161-478c-89ba-1b079435201b", + "key": "schemaId", + "value": "{{coll-schemaId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Get API Base Url", + "event": [ + { + "listen": "test", + "script": { + "id": "5876b636-91d2-405b-a0e6-8bd4e2132969", + "exec": [ + "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", + "const server = pm.environment.get('env-server');\r", + "\r", + "pm.test('Environment has test server defined', function () {\r", + " pm.expect(server).to.not.be.undefined;\r", + "});\r", + "\r", + "pm.test('Schema has server/baseUrl defined', function () {\r", + " const servers = schema.servers;\r", + " pm.expect(servers).to.not.be.undefined;\r", + " const serverToTest = servers.find(s => s.description.toLowerCase() == server.toLowerCase());\r", + " pm.expect(serverToTest).to.not.be.undefined;\r", + "\r", + " pm.expect(serverToTest).to.have.property('url');\r", + " pm.collectionVariables.set('coll-baseUrl', serverToTest.url);\r", + "});\r", + "\r", + "const runComponentTests = pm.environment.get('env-runComponentTests') == 'true';\r", + "if(!runComponentTests){ \r", + " const runContractTests = pm.environment.get('env-runContractTests') == 'true';\r", + " if(runContractTests){\r", + " postman.setNextRequest('Build Schema Tests');\r", + " } else {\r", + " postman.setNextRequest('More APIs to Process?');\r", + " } \r", + "}" + ], + "type": "text/javascript" + } + } + ], + "id": "c8711439-c69a-4420-b034-cb58dc21e110", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": "https://postman-echo.com/delay/0" + }, + "response": [] + } + ], + "id": "2778dde1-f785-4aba-84cc-57e1102bff23" + }, + { + "name": "Components", + "item": [ + { + "name": "Verify Component Adherence", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", + "\r", + "const requireParamDescription = Boolean(pm.environment.get('env-requireParamDescription'));\r", + "const requireParamExample = Boolean(pm.environment.get('env-requireParamExample'));\r", + "\r", + "let paramDescriptionMinLength = pm.environment.get('env-paramDescriptionMinLength');\r", + "if (paramDescriptionMinLength) {\r", + " paramDescriptionMinLength = Number(paramDescriptionMinLength);\r", + "}\r", + "\r", + "let paramDescriptionMaxLength = pm.environment.get('env-paramDesciptionMaxLength');\r", + "if (paramDescriptionMaxLength) {\r", + " paramDescriptionMaxLength = Number(paramDescriptionMaxLength);\r", + "}\r", + "\r", + "var testedSchemaRefs = [];\r", + "\r", + "if (schema.components.parameters) {\r", + " for (let prop in schema.components.parameters) {\r", + " let parameter = schema.components.parameters[prop];\r", + "\r", + " pm.test(`Parameter '${prop}' starts with a lowercase letter`, function () {\r", + " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toLowerCase());\r", + " });\r", + "\r", + " if (requireParamDescription) {\r", + " pm.test(`Parameter '${prop}' has a description between ${paramDescriptionMinLength} and ${paramDescriptionMaxLength} characters`, function () {\r", + " pm.expect(parameter).to.have.property('description').and.to.be.a('string');\r", + " pm.expect(parameter.description.length).to.be.at.least(paramDescriptionMinLength);\r", + " pm.expect(parameter.description.length).to.be.at.most(paramDescriptionMaxLength);\r", + " });\r", + " }\r", + "\r", + " if (requireParamExample) {\r", + " pm.test(`Parameter '${prop}' has an example`, function () {\r", + " pm.expect(parameter).to.have.property('schema');\r", + " pm.expect(parameter.schema).to.have.property('example');\r", + " });\r", + " }\r", + " }\r", + "}\r", + "\r", + "if (schema.components.schemas) {\r", + " for (let prop in schema.components.schemas) {\r", + " pm.test(`Schema '${prop}' begins with an uppercase letter`, function () {\r", + " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toUpperCase());\r", + " });\r", + "\r", + " const testedSchema = testedSchemaRefs.find(tsr => tsr == prop);\r", + " if (!testedSchema) {\r", + " const schemaObject = schema.components.schemas[prop];\r", + " testSchemaObject(schema, schemaObject, prop);\r", + " testedSchemaRefs.push(prop);\r", + " }\r", + " }\r", + "}\r", + "\r", + "if (schema.components.responses) {\r", + " for (let prop in schema.components.responses) {\r", + " pm.test(`Response '${prop}' begins with an uppercase letter`, function () {\r", + " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toUpperCase());\r", + " });\r", + "\r", + " if (requireParamDescription) {\r", + " const response = schema.components.responses[prop];\r", + " pm.test(`Response '${prop}' has a description between ${paramDescriptionMinLength} and ${paramDescriptionMaxLength} characters`, function () {\r", + " pm.expect(response).to.have.property('description').and.to.be.a('string');\r", + " pm.expect(response.description.length).to.be.at.least(paramDescriptionMinLength);\r", + " pm.expect(response.description.length).to.be.at.most(paramDescriptionMaxLength);\r", + " });\r", + " }\r", + " }\r", + "}\r", + "\r", + "const runContractTests = pm.environment.get('env-runContractTests') == 'true';\r", + "if (runContractTests) {\r", + " postman.setNextRequest('Build Schema Tests');\r", + "} else {\r", + " postman.setNextRequest('More APIs to Process?');\r", + "}\r", + "\r", + "\r", + "function testSchemaObject(schema, object, objectName) {\r", + " if (object.type && object.type.toLowerCase() == 'object') {\r", + " if (object.required) {\r", + " for (let i = 0; i < object.required.length; i++) {\r", + " const requiredProp = object.required[i];\r", + " pm.test(`Schema '${objectName}' has required property '${requiredProp}' defined`, function () {\r", + " pm.expect(object.properties).to.have.property(requiredProp);\r", + " });\r", + " }\r", + " }\r", + "\r", + " let schemaPropertyExceptions = [];\r", + " if (pm.environment.has('env-schemaPropertyExceptions')) {\r", + " schemaPropertyExceptions = JSON.parse(pm.environment.get('env-schemaPropertyExceptions'));\r", + " }\r", + "\r", + " for (let prop in object.properties) {\r", + " const property = object.properties[prop];\r", + "\r", + " if (!schemaPropertyExceptions.some(pe => pe === prop)) {\r", + " pm.test(`Schema property '${objectName}.${prop}' is lowercase`, function () {\r", + " pm.expect(prop.charAt(0)).to.equal(prop.charAt(0).toLowerCase());\r", + " });\r", + " }\r", + "\r", + " if (property.type && property.type.toLowerCase() == 'object') {\r", + " testSchemaObject(schema, property, `${objectName}.${prop}`);\r", + " }\r", + " else if (property.type && property.type.toLowerCase() == 'array') {\r", + " testSchemaObject(schema, property, `${objectName}.${prop}(list)`);\r", + " }\r", + " else if (property.oneOf) {\r", + " _.forEach(property.oneOf, (oneOf, i) => {\r", + " testSchemaObject(schema, oneOf, `${objectName}.${prop}(oneOf).${i}`)\r", + " });\r", + " }\r", + " else if (property.allOf) {\r", + " _.forEach(property.allOf, (allOf, i) => {\r", + " testSchemaObject(schema, allOf, `${objectName}.${prop}(allOf).${i}`)\r", + " });\r", + " }\r", + " else if (property.anyOf) {\r", + " _.forEach(property.anyOf, (anyOf, i) => {\r", + " testSchemaObject(schema, anyOf, `${objectName}.${prop}(anyOf).${i}`)\r", + " });\r", + " }\r", + " else {\r", + " if (requireParamDescription && !property.$ref) {\r", + " pm.test(`Schema property '${objectName}.${prop}' has a description between ${paramDescriptionMinLength} and ${paramDescriptionMaxLength} characters`, function () {\r", + " pm.expect(property).to.have.property('description').and.to.be.a('string');\r", + " pm.expect(property.description.length).to.be.at.least(paramDescriptionMinLength);\r", + " pm.expect(property.description.length).to.be.at.most(paramDescriptionMaxLength);\r", + " });\r", + "\r", + " if (property.description) {\r", + " pm.test(`Schema property '${objectName}.${prop}' description is not just the name`, function () {\r", + " pm.expect(prop.toLowerCase()).to.not.equal(property.description.toLowerCase());\r", + " });\r", + " }\r", + " }\r", + "\r", + " if (requireParamExample && !property.$ref) {\r", + " pm.test(`Schema property '${objectName}.${prop}' has an example`, function () {\r", + " pm.expect(property).to.have.property('example');\r", + " });\r", + " }\r", + " }\r", + " }\r", + " }\r", + " else if (object.type && object.type.toLowerCase() == 'array') {\r", + " pm.test(`Schema '${objectName}' has items defined`, function () {\r", + " pm.expect(object).to.have.property('items');\r", + " });\r", + "\r", + " testSchemaObject(schema, object.items, `${objectName}.list`);\r", + " }\r", + " else if (object.oneOf) {\r", + " handleSchemaArray(schema, object, objectName, 'oneOf');\r", + " } else if (object.allOf) {\r", + " handleSchemaArray(schema, object, objectName, 'allOf');\r", + " }\r", + " else if (object.anyOf) {\r", + " handleSchemaArray(schema, object, objectName, 'anyOf');\r", + " }\r", + " else if (object.$ref) {\r", + " const name = getName(object.$ref);\r", + " const testedRef = testedSchemaRefs.find(tsr => tsr == name);\r", + " if (!testedRef) {\r", + " testSchemaObject(schema, schema.components.schemas[name], objectName);\r", + " testedSchemaRefs.push(name);\r", + " }\r", + " }\r", + " else {\r", + " pm.test(`Schema '${objectName}' has a declared type`, function () {\r", + " pm.expect(object).to.have.property('type');\r", + " });\r", + " }\r", + "}\r", + "\r", + "function handleSchemaArray(schema, object, objectName, arrayType) {\r", + " for (let i = 0; i < object[arrayType].length; i++) {\r", + " const arraySchema = object[arrayType][i];\r", + " if (arraySchema.$ref) {\r", + " const name = getName(arraySchema.$ref);\r", + " const testedRef = testedSchemaRefs.find(tsr => tsr == name);\r", + " if (!testedRef) {\r", + " testSchemaObject(schema, schema.components.schemas[name], `${objectName}[${i}](ref ${name})`);\r", + " testedSchemaRefs.push(name);\r", + " }\r", + " }\r", + " else {\r", + " testSchemaObject(schema, arraySchema, `${objectName}[${i}]`);\r", + " }\r", + " }\r", + "}\r", + "\r", + "function getName(ref) {\r", + " let pieces = ref.split('/');\r", + " return pieces[pieces.length - 1];\r", + "}\r", + "" + ], + "type": "text/javascript", + "id": "968b2ac8-c423-42d7-ab68-809e0292ab31" + } + } + ], + "id": "46526b39-701b-4f22-a0a3-03ec53319450", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": "https://postman-echo.com/delay/0" + }, + "response": [] + } + ], + "id": "18e4b0a6-d641-4661-9a29-918247d63f5f" + }, + { + "name": "Contract Tests", + "item": [ + { + "name": "Build Schema Tests", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", + "\r", + "let schemaTests = [];\r", + "for (let prop in schema.paths) {\r", + " const pathName = prop;\r", + " let path = {\r", + " path: `${pm.collectionVariables.get('coll-baseUrl')}${pathName}`,\r", + " parameters: schema.paths[prop].parameters,\r", + " };\r", + "\r", + " for (let method in schema.paths[prop]) {\r", + " if (method.toLowerCase() == 'parameters' || isMockEndpoint(schema.paths[prop][method])) {\r", + " continue;\r", + " }\r", + "\r", + " let currentPath = _.cloneDeep(path);\r", + " currentPath.method = method.toUpperCase();\r", + " let pathMethod = schema.paths[prop][method];\r", + " currentPath.parameters = combineParameters(currentPath.parameters, pathMethod.parameters);\r", + " let securityExtension = pm.environment.get('env-securityExtensionName');\r", + " if (securityExtension && pathMethod[securityExtension] && pathMethod[securityExtension].length > 0) {\r", + " currentPath.allowedRole = pathMethod[securityExtension][0];\r", + " }\r", + "\r", + " const expectedResponses = getExpectedResponses(pathMethod);\r", + " currentPath.responses = expectedResponses;\r", + "\r", + " if (pathMethod.requestBody) {\r", + " let bodyModel;\r", + " if (pathMethod.requestBody.content['application/json']?.schema?.$ref) {\r", + " bodyModel = getSchemaReference(schema, pathMethod.requestBody.content['application/json'].schema.$ref);\r", + " }\r", + " else if (pathMethod.requestBody.content['application/json']?.schema) {\r", + " bodyModel = pathMethod.requestBody.content['application/json'].schema;\r", + " }\r", + " else {\r", + " continue;\r", + " }\r", + "\r", + " const models = buildModels(schema, bodyModel);\r", + " const mutations = buildModelMutations(models);\r", + "\r", + " mutations.forEach((mutation) => {\r", + " let schemaTest = _.cloneDeep(currentPath);\r", + " Object.assign(schemaTest, mutation);\r", + " schemaTest.name = `${schemaTest.method} - ${pathName} - ${schemaTest.description} - SUCCESS: ${schemaTest.success}`;\r", + " schemaTests.push(schemaTest);\r", + " });\r", + " }\r", + " else {\r", + " currentPath.name = `${currentPath.method} - ${pathName} - No Request Body - SUCCESS: true`;\r", + " currentPath.success = true;\r", + " schemaTests.push(currentPath);\r", + " }\r", + " }\r", + "}\r", + "schemaTests = moveDeleteEndpointsToEnd(schemaTests);\r", + "pm.collectionVariables.set('coll-schemaTests', JSON.stringify(schemaTests));\r", + "\r", + "// \r", + "// Move delete endpoints to the end for cleanup\r", + "//\r", + "function moveDeleteEndpointsToEnd(schemaTests) {\r", + " let sortedTests = [...schemaTests];\r", + " try {\r", + " let successfulDeletes = sortedTests.filter(schemaTest => schemaTest.method == 'DELETE' && schemaTest.success);\r", + "\r", + " if (successfulDeletes) {\r", + " // order deletes from the deepest entity to highest level entity based on path\r", + " successfulDeletes.sort((a, b) => b.path.split('/').length - a.path.split('/').length);\r", + " sortedTests = sortedTests.filter(schemaTest => !successfulDeletes.find(sd => sd == schemaTest));\r", + " sortedTests = sortedTests.concat(successfulDeletes);\r", + " }\r", + " }\r", + " catch (err) {\r", + " console.log('An error occurred when sorting delete tests', err);\r", + " }\r", + "\r", + " return sortedTests;\r", + "}\r", + "\r", + "//\r", + "// Supporting Methods Below\r", + "//\r", + "function buildModels(schema, object) {\r", + " let models = [];\r", + "\r", + " if (object['$ref']) {\r", + " object = getSchemaReference(schema, object['$ref']);\r", + " }\r", + "\r", + " if (object.type && object.type.toLowerCase() == 'object') {\r", + " if (object.required && object.required.length > 0) {\r", + " models.push({});\r", + " _.forEach(object.required, function (param) {\r", + " const property = object.properties[param];\r", + "\r", + " if (property.type && ['string', 'number', 'integer', 'boolean'].includes(property.type.toLowerCase())) {\r", + " for (let modelIndex = 0; modelIndex < models.length; modelIndex++) {\r", + " let model = models[modelIndex];\r", + " model[param] = property.example;\r", + " }\r", + " }\r", + " else {\r", + " const nestedObjects = buildModels(schema, property);\r", + " models = addToModels(models, nestedObjects, param);\r", + " }\r", + " });\r", + " }\r", + "\r", + " if (object.minProperties) {\r", + " _.forEach(models, function (model) {\r", + " if (Object.keys(model).length < object.minProperties) {\r", + " for (let i = Object.keys(model).length; i < object.minProperties; i++) {\r", + " for (const [key, value] of Object.entries(object.properties)) {\r", + " if (['string', 'number', 'integer', 'boolean'].includes(value.type.toLowerCase()) && model[key] == undefined) {\r", + " model[key] = value.example;\r", + " break;\r", + " }\r", + " }\r", + " }\r", + " }\r", + " })\r", + " }\r", + " }\r", + " else if (object.type && object.type.toLowerCase() == 'array') {\r", + " let items = buildModels(schema, object.items);\r", + " if (Array.isArray(items)) {\r", + " for (let i = 0; i < items.length; i++) {\r", + " models.push([items[i]]);\r", + " }\r", + " }\r", + " else {\r", + " models.push([items]);\r", + " }\r", + " }\r", + " else if (object.oneOf) {\r", + " _.forEach(object.oneOf, function (component) {\r", + " let items = buildModels(schema, component);\r", + " models = models.concat(items);\r", + " });\r", + " }\r", + " else if (object.allOf) {\r", + " let pieces = [{}];\r", + " _.forEach(object.allOf, function (component) {\r", + " let componentModels = buildModels(schema, component);\r", + " pieces = addToModels(pieces, componentModels);\r", + " });\r", + "\r", + " models = pieces;\r", + " }\r", + " else if (object.anyOf) {\r", + " let pieces = [];\r", + " let combinedPieces = [{}];\r", + " _.forEach(object.anyOf, function (component) {\r", + " let componentModels = buildModels(schema, component);\r", + " combinedPieces = addToModels(combinedPieces, componentModels);\r", + " pieces = pieces.concat(componentModels);\r", + " });\r", + "\r", + " models = pieces.concat(combinedPieces);\r", + " }\r", + " else {\r", + " // All other options are primitive values\r", + " return object.example;\r", + " }\r", + " return models;\r", + "}\r", + "\r", + "function getSchemaReference(schema, referenceName) {\r", + " const refPieces = referenceName.split('/');\r", + " let reference = schema;\r", + " for (let i = 1; i < refPieces.length; i++) {\r", + " reference = reference[refPieces[i]];\r", + " }\r", + "\r", + " return reference;\r", + "}\r", + "\r", + "function addToModels(models, newPieces, name) {\r", + " let newModels = [];\r", + " _.forEach(models, function (model) {\r", + " _.forEach(newPieces, function (newPiece) {\r", + " let newModel = _.cloneDeep(model);\r", + " if (name) {\r", + " newModel[name] = newPiece;\r", + " }\r", + " else {\r", + " Object.assign(newModel, newPiece);\r", + " }\r", + " newModels.push(newModel);\r", + " });\r", + " });\r", + "\r", + " return newModels;\r", + "}\r", + "\r", + "function buildModelMutations(models) {\r", + " let modelMutations = [];\r", + " _.forEach(models, function (model) {\r", + " addMutation(true, 'Has all required fields', model, modelMutations);\r", + " let mutations = buildMutation(model);\r", + " modelMutations = modelMutations.concat(mutations);\r", + " });\r", + "\r", + " return modelMutations;\r", + "}\r", + "\r", + "function buildMutation(model) {\r", + " let mutations = [];\r", + "\r", + " for (const [key, value] of Object.entries(model)) {\r", + " if (typeof value == 'object') {\r", + " let nestedMutations = buildMutation(value);\r", + " nestedMutations.forEach((nestedMutation) => {\r", + " let mutation = _.cloneDeep(model);\r", + " mutation[key] = nestedMutation.body;\r", + " addMutation(false, `${nestedMutation.description} in ${key} object`, mutation, mutations);\r", + " });\r", + "\r", + " let mutation = _.cloneDeep(model);\r", + " delete mutation[key];\r", + " addMutation(false, `Missing ${key} object`, mutation, mutations);\r", + "\r", + " let emptyMutation = _.cloneDeep(model);\r", + " emptyMutation[key] = {};\r", + " addMutation(false, `Empty ${key} object`, emptyMutation, mutations);\r", + " }\r", + " else {\r", + " if (Array.isArray(value)) {\r", + " console.log('probably an error');\r", + " }\r", + " let mutation = _.cloneDeep(model);\r", + " delete mutation[key];\r", + " addMutation(false, `Missing ${key} property`, mutation, mutations);\r", + "\r", + " let blankMutation = _.cloneDeep(model);\r", + " blankMutation[key] = '';\r", + " addMutation(false, `Blank ${key} property`, blankMutation, mutations);\r", + " }\r", + " }\r", + "\r", + " return mutations;\r", + "}\r", + "\r", + "function addMutation(isSuccess, description, mutation, mutations) {\r", + " mutations.push({\r", + " success: isSuccess,\r", + " description: description,\r", + " body: mutation\r", + " });\r", + "}\r", + "\r", + "function getExpectedResponses(pathMethod) {\r", + " const responses = [];\r", + " for (const [statusCode, value] of Object.entries(pathMethod.responses)) {\r", + " let response = {\r", + " statusCode: Number(statusCode)\r", + " };\r", + "\r", + " if (value['x-postman-variables'] && Array.isArray(value['x-postman-variables'])) {\r", + " response.variables = value['x-postman-variables'].filter(variable => variable.type.toLowerCase() === 'save');\r", + " }\r", + "\r", + " if (value.$ref) {\r", + " response.$ref = value.$ref;\r", + " }\r", + " else {\r", + " if (value.content?.['application/json']?.schema) {\r", + " if (value.content['application/json'].schema.$ref) {\r", + " response.$ref = value.content['application/json'].schema.$ref;\r", + " }\r", + " else {\r", + " response.schema = value.content['application/json'].schema;\r", + " }\r", + " }\r", + " }\r", + "\r", + " responses.push(response);\r", + " }\r", + " return responses;\r", + "}\r", + "\r", + "function isMockEndpoint(pathMethod) {\r", + " let isMock = false;\r", + " if (pathMethod && pathMethod['x-amazon-apigateway-integration'] && pathMethod['x-amazon-apigateway-integration'].type\r", + " && pathMethod['x-amazon-apigateway-integration'].type.toLowerCase() == 'mock') {\r", + " isMock = true;\r", + " }\r", + "\r", + " return isMock;\r", + "}\r", + "\r", + "function combineParameters(endpointParameters, methodParameters) {\r", + " if (!endpointParameters && !methodParameters) {\r", + " return;\r", + " }\r", + " let parameters = [];\r", + " if (endpointParameters && endpointParameters.length) {\r", + " parameters = [...endpointParameters];\r", + " }\r", + "\r", + " if (methodParameters && methodParameters.length) {\r", + " parameters = [...parameters, ...methodParameters];\r", + " }\r", + "\r", + " return parameters;\r", + "}" + ], + "type": "text/javascript", + "id": "3017117f-1119-4cda-8de9-f181be051dff" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "let schemaTests = pm.collectionVariables.get('coll-schemaTests');\r", + "if(schemaTests){\r", + " schemaTests = JSON.parse(schemaTests);\r", + " if(!schemaTests || !schemaTests.length){\r", + " postman.setNextRequest('More APIs to Process?');\r", + " }\r", + "}" + ], + "type": "text/javascript", + "id": "161ed627-1e43-4d10-923c-308e9b039bd9" + } + } + ], + "id": "bd6e30f9-999f-4ace-91ca-270b0bd1f1a5", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": "https://postman-echo.com/delay/0" + }, + "response": [] + }, + { + "name": "Test Request", + "event": [ + { + "listen": "prerequest", + "script": { + "id": "49ce35ea-de86-4a14-b90e-3d396e2feb2d", + "exec": [ + "const url = require('url');\r", + "\r", + "const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", + "let schemaTests = JSON.parse(pm.collectionVariables.get('coll-schemaTests'));\r", + "\r", + "const schemaTest = schemaTests.shift();\r", + "pm.collectionVariables.set('coll-schemaTests', JSON.stringify(schemaTests));\r", + "pm.variables.set('currentSchemaTest', JSON.stringify(schemaTest));\r", + "\r", + "const path = replacePathParameters(schema, schemaTest.path, schemaTest.parameters);\r", + "pm.request.url.update(path);\r", + "delete pm.request.url.auth;\r", + "delete pm.request.url.port;\r", + "delete pm.request.url.hash;\r", + "if (pm.request.url.protocol) {\r", + " pm.request.url.protocol = pm.request.url.protocol.replace(/\\:$/, '');\r", + "} else {\r", + " pm.request.url.protocol = 'https';\r", + "}\r", + "pm.request.method = schemaTest.method;\r", + "pm.request.name = schemaTest.name;\r", + "\r", + "pm.variables.set('requestName', schemaTest.name);\r", + "pm.variables.set('body', JSON.stringify(schemaTest.body));\r", + "\r", + "// Add top level parameters from the path\r", + "const roleHeaderName = pm.environment.get('env-roleHeaderName');\r", + "\r", + "if (schemaTest.parameters) {\r", + " for (let i = 0; i < schemaTest.parameters.length; i++) {\r", + " let param = schemaTest.parameters[i];\r", + "\r", + " if (param.$ref) {\r", + " let pieces = param.$ref.split('/');\r", + " const name = pieces[pieces.length - 1];\r", + " const schemaParam = schema.components.parameters[name];\r", + " const paramType = schemaParam.in.toLowerCase();\r", + " const paramValue = loadParameterValue(schemaParam);\r", + " if (paramType == 'header' && schemaParam.required == true) {\r", + " if (roleHeaderName && schemaParam.name.toLowerCase() == roleHeaderName.toLowerCase()) {\r", + " pm.request.headers.upsert({ key: schemaParam.name, value: schemaTest.allowedRole });\r", + " }\r", + " else {\r", + " pm.request.headers.upsert({ key: schemaParam.name, value: paramValue });\r", + " }\r", + " } else if (paramType == 'query' && schemaParam.required == true) {\r", + " pm.request.url.query.upsert({ key: schemaParam.name, value: paramValue });\r", + " }\r", + " } else {\r", + " const paramType = param.in.toLowerCase();\r", + " const paramValue = loadParameterValue(param);\r", + " if (paramType == 'header') {\r", + " pm.request.headers.upsert({ key: param.name, value: paramValue });\r", + " } else if (paramType == 'query' && param.required == true) {\r", + " pm.request.url.query.upsert({ key: param.name, value: paramValue });\r", + " }\r", + " }\r", + " }\r", + "}\r", + "\r", + "function loadParameterValue(parameter) {\r", + " let parameterValue;\r", + " if (parameter['x-postman-variables']) {\r", + " let variable = parameter['x-postman-variables'].find(v => v.type.toLowerCase() === 'load');\r", + " if (variable && pm.collectionVariables.has(variable.name)) {\r", + " parameterValue = pm.collectionVariables.get(variable.name);\r", + " }\r", + " else {\r", + " parameterValue = resolveParameterExample(parameter);\r", + " }\r", + " }\r", + " else {\r", + " parameterValue = resolveParameterExample(parameter);\r", + " }\r", + "\r", + " return parameterValue;\r", + "}\r", + "\r", + "function resolveParameterExample(parameter) {\r", + " let paramValue = (parameter.schema.example != undefined) ? parameter.schema.example : parameter.example;\r", + " let value = paramValue;\r", + " if (typeof paramValue !== 'number' && typeof paramValue !== 'boolean') {\r", + " let pathVariableRegex = /^{{\\$.*}}$/;\r", + " let matches = paramValue.match(pathVariableRegex);\r", + "\r", + " if (matches && matches.length) {\r", + " value = pm.variables.replaceIn(paramValue);\r", + " }\r", + " }\r", + "\r", + " return encodeURIComponent(value);\r", + "}\r", + "\r", + "function replacePathParameters(schema, pathName, parameters) {\r", + " let replacedPathName = pathName;\r", + " let pathVariableRegex = /{([^}]*)}/g;\r", + " let matches = pathName.match(pathVariableRegex);\r", + " _.forEach(matches, function (match) {\r", + " let paramName = match.substring(1, match.length - 1);\r", + " _.forEach(parameters, function (param) {\r", + " if (param.$ref) {\r", + " let parameter = getSchemaReference(schema, param.$ref);\r", + " if (parameter.in && parameter.in.toLowerCase() == 'path' && parameter.name && parameter.name == paramName) {\r", + " let parameterValue = loadParameterValue(parameter);\r", + " replacedPathName = replacedPathName.replace(match, parameterValue);\r", + " return false;\r", + " }\r", + " } else {\r", + " if (param.in && param.in.toLowerCase() == 'path' && param.name && param.name == paramName) {\r", + " let parameterValue = loadParameterValue(param);\r", + " replacedPathName = replacedPathName.replace(match, parameterValue);\r", + " return false;\r", + " }\r", + " }\r", + " });\r", + " });\r", + "\r", + " return url.parse(replacedPathName);\r", + "}\r", + "\r", + "function getSchemaReference(schema, referenceName) {\r", + " const refPieces = referenceName.split('/');\r", + " let reference = schema;\r", + " for (let i = 1; i < refPieces.length; i++) {\r", + " reference = reference[refPieces[i]];\r", + " }\r", + "\r", + " return reference;\r", + "}" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "id": "2524e1aa-f349-412f-91eb-b7857f29d495", + "exec": [ + "const schemaTests = JSON.parse(pm.collectionVariables.get('coll-schemaTests'));\r", + "if(schemaTests.length > 0){\r", + " postman.setNextRequest('Test Request');\r", + "}\r", + "\r", + "const schemaTest = JSON.parse(pm.variables.get('currentSchemaTest'));\r", + "console.log(schemaTest.name);\r", + "\r", + "pm.test(`${schemaTest.name} - Has expected status code`, function () {\r", + " // const errorOn500 = pm.environment.get('env-errorOn500');\r", + " // if(errorOn500){\r", + " // pm.response.to.not.have.status(500);\r", + " // }\r", + "\r", + " if(schemaTest.success){\r", + " try{\r", + " if(pm.response.code >= 400) {\r", + " const jsonData = pm.response.json();\r", + " if(pm.response.code == 401) {\r", + " pm.expect(pm.request.headers.get('Role')).to.equal('role');\r", + " }\r", + " pm.expect('').to.equal(jsonData.message); \r", + " }\r", + " \r", + " pm.expect(pm.response.code).to.not.equal(400);\r", + " }\r", + " catch(err) {\r", + " console.log(err);\r", + " pm.expect(pm.response.code).to.not.equal(400);\r", + " } \r", + " }\r", + " else {\r", + " const statusCode = pm.response.code\r", + " pm.expect(statusCode === 400 || statusCode === 422).to.be.true;\r", + " } \r", + "});\r", + "\r", + "const expectedResponse = schemaTest.responses.find(r => r.statusCode == pm.response.code);\r", + "pm.test(`${schemaTest.name} - Status code (${pm.response.code}) is allowed`, function(){\r", + " pm.expect(expectedResponse).to.exist;\r", + "});\r", + "\r", + "if(expectedResponse){\r", + " pm.test(`${schemaTest.name} - Has expected response body schema`, function(){\r", + " const Ajv = require('ajv');\r", + " const ajv = new Ajv({allErrors: true,format: false,nullable: true});\r", + " \r", + " if(pm.response.code == 204 || shouldResponseBeEmpty(expectedResponse)){\r", + " checkForEmptyResponse();\r", + " }\r", + " else if(expectedResponse.$ref){ \r", + " const jsonData = pm.response.json();\r", + " const schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", + " ajv.addSchema(schema, 'OAS');\r", + " const valid = ajv.validate({$ref: `OAS${expectedResponse.$ref}`}, jsonData);\r", + " const errors = ajv.errorsText(valid.errors);\r", + " pm.expect(errors).to.equal('No errors');\r", + " if(errors !== 'No errors'){\r", + " console.log(errors);\r", + " }\r", + " }\r", + " else if(expectedResponse.schema){\r", + " const jsonData = pm.response.json();\r", + " const validate = ajv.compile(expectedResponse.schema);\r", + " const valid = validate(jsonData);\r", + " const errors = ajv.errorsText(valid.errors);\r", + " pm.expect(errors).to.equal('No errors');\r", + " if(errors !== 'No errors'){\r", + " console.log(errors);\r", + " }\r", + " }\r", + " else {\r", + " checkForEmptyResponse();\r", + " }\r", + "\r", + " if(expectedResponse.variables){\r", + " const jsonData = pm.response.json();\r", + " _.forEach(expectedResponse.variables, function(variable){\r", + " let pathPieces = variable.path.split('.').filter(piece => piece);\r", + " let data = jsonData;\r", + " let found = true;\r", + " _.forEach(pathPieces, function(piece){\r", + " if(data[piece]){\r", + " data = data[piece];\r", + " }\r", + " else {\r", + " found = false;\r", + " }\r", + " });\r", + "\r", + " if(found){\r", + " pm.collectionVariables.set(variable.name, data);\r", + " }\r", + " else {\r", + " pm.test(`Unable to save dynamic variable ${variable.name} at the provided path.`, function() {\r", + " pm.expect(true).to.equal(variable.path);\r", + " });\r", + " }\r", + " });\r", + " }\r", + " });\r", + "}\r", + "\r", + "function checkForEmptyResponse() {\r", + " let emptyBody = true;\r", + " if(pm.response.text()){\r", + " emptyBody = false; \r", + " }\r", + "\r", + " pm.expect(emptyBody).to.be.true;\r", + "}\r", + "\r", + "function shouldResponseBeEmpty(expectedResponse){\r", + " let responseSchema = expectedResponse.schema;\r", + " if(expectedResponse.$ref){\r", + " let schema = JSON.parse(pm.collectionVariables.get('coll-schema'));\r", + " responseSchema = getSchemaReference(schema, expectedResponse.$ref);\r", + " if(expectedResponse.$ref.startsWith('#/components/responses')){\r", + " return (!responseSchema || !responseSchema.content || !responseSchema.content['application/json'] \r", + " || !responseSchema.content['application/json'].schema || Object.keys(responseSchema.content['application/json'].schema).length == 0);\r", + " } else {\r", + " return false;\r", + " }\r", + " }\r", + " else {\r", + " return (Object.keys(responseSchema).length == 0);\r", + " }\r", + "}\r", + "\r", + "function getSchemaReference(schema, referenceName){\r", + " const refPieces = referenceName.split('/');\r", + " let reference = schema;\r", + " for(let i = 1; i < refPieces.length; i++){\r", + " reference = reference[refPieces[i]];\r", + " }\r", + "\r", + " return reference;\r", + "}" + ], + "type": "text/javascript" + } + } + ], + "id": "416eff4d-749b-4b49-9c3c-7e6cfbfb7c2c", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{{body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "https://postman-echo.com/get" + }, + "response": [] + } + ], + "id": "5710c277-a099-4c7d-a8ab-9bb911918ef9" + }, + { + "name": "Finalize", + "item": [ + { + "name": "More APIs to Process?", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "let apis = pm.collectionVariables.get('coll-apiIds');\r", + "if(apis){\r", + " try{\r", + " apis = JSON.parse(apis);\r", + " if(apis.length > 0){\r", + " postman.setNextRequest('Get Current API Version');\r", + " }\r", + " }\r", + " catch(err){} \r", + "}" + ], + "type": "text/javascript", + "id": "c236b1d1-9f04-4502-b754-ee4d6430a3ed" + } + } + ], + "id": "eafd2509-05d8-42ee-ab2d-dd7f45453f5c", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": "https://postman-echo.com/delay/0" + }, + "response": [] + }, + { + "name": "Remove Test Variables", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "// See https://blog.postman.com/2019/05/28/pro-tip-dynamically-unset-postman-environment-variables/\r", + "// for more details on what we're doing here. \r", + "\r", + "cleanupCollectionVariables();\r", + "\r", + "function cleanupCollectionVariables() {\r", + " const clean = _.keys(pm.collectionVariables.toObject());\r", + "\r", + " _.each(clean, (arrItem) => {\r", + " pm.collectionVariables.unset(arrItem);\r", + " });\r", + "}" + ], + "type": "text/javascript", + "id": "168455e5-e394-48df-902d-dbab8352acab" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "id": "ac89d252-3423-481b-9ba5-d0b3f55ca383" + } + } + ], + "id": "6e4c0ea7-b3c4-4e33-bc13-ac7ab563f57b", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "url": "https://postman-echo.com/delay/0" + }, + "response": [] + } + ], + "id": "92035d83-c9a1-425a-8a69-65bc677fbc13" + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ], + "id": "38efdb22-19c6-42a8-aa8b-ef1271ce04ef" + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ], + "id": "c48549ed-e6df-407a-8452-ec1db5cc81eb" + } + } + ], + "variable": [ + { + "key": "coll-schema", + "value": "" + }, + { + "key": "coll-baseUrl", + "value": "" + }, + { + "key": "coll-schemaTests", + "value": "" + } + ] } From 0dd9d72cdb81cc46bef87e2ecfed335cf2760d8e Mon Sep 17 00:00:00 2001 From: nalbion Date: Sun, 11 Feb 2024 04:55:06 +1100 Subject: [PATCH 6/8] more reverting prettier changes to reduce noise in PR --- .github/workflows/update-docs.yaml | 6 +-- .husky/pre-commit | 2 +- .prettierrc | 10 +--- CODE_OF_CONDUCT.md | 1 - package.json | 1 - packages/sdk/js/src/agent.ts | 38 +++++++-------- packages/sdk/js/src/api.ts | 2 +- packages/sdk/js/src/models.ts | 2 +- rfcs/2023-08-28-Pagination-RFC.md | 2 +- rfcs/2023-08-28-agent-created-RFC-.md | 3 +- rfcs/2023-08-28-list-entities-RFC.md | 4 +- rfcs/2023-09-15-endpoint-schema.md | 1 - schemas/openapi.json | 66 +++++++++++++++++++++------ 13 files changed, 83 insertions(+), 55 deletions(-) diff --git a/.github/workflows/update-docs.yaml b/.github/workflows/update-docs.yaml index d508fd41..3289121e 100644 --- a/.github/workflows/update-docs.yaml +++ b/.github/workflows/update-docs.yaml @@ -3,7 +3,7 @@ name: Regenerate Endpoints docs on change on: push: paths: - - "schemas/openapi.yml" + - 'schemas/openapi.yml' permissions: contents: write @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@v3 - uses: actions/setup-node@v1 with: - node-version: "16.x" + node-version: '16.x' - run: npm i - run: npm run generateEndpoints @@ -31,7 +31,7 @@ jobs: git commit -am "Update endpoint docs" || echo "No changes to commit" git push env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Install swagger-cli run: | diff --git a/.husky/pre-commit b/.husky/pre-commit index 1917bfa9..5f89fe32 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -# npm run ci +npm run ci diff --git a/.prettierrc b/.prettierrc index d3a05ceb..fa51da29 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,13 +2,5 @@ "trailingComma": "es5", "tabWidth": 2, "semi": false, - "singleQuote": true, - "overrides": [ - { - "files": "*.yaml", - "options": { - "singleQuote": false - } - } - ] + "singleQuote": true } diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 0ec871c8..401fdbb8 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -18,7 +18,6 @@ Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people - * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, diff --git a/package.json b/package.json index dba0039d..a9606037 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,6 @@ "scripts": { "ci": "prettier . -c && eslint . --ext .ts,.tsx", "prettier": "prettier . -w", - "prettier:fix": "prettier --write .", "lint": "eslint . --ext .ts,.tsx --fix", "prepare": "husky install" }, diff --git a/packages/sdk/js/src/agent.ts b/packages/sdk/js/src/agent.ts index 80c12100..f7a048fa 100644 --- a/packages/sdk/js/src/agent.ts +++ b/packages/sdk/js/src/agent.ts @@ -17,11 +17,11 @@ import { import { createApi, ApiApp, - type ApiConfig, - type RouteRegisterFn, - type RouteContext, -} from './api' -import { type Router } from 'express' + ApiConfig, + RouteRegisterFn, + RouteContext, +} from "./api"; +import { type Router } from 'express'; /** * A function that handles a step in a task. @@ -83,7 +83,7 @@ const registerCreateAgentTask: RouteRegisterFn = (router: Router) => { res.status(500).json({ error: err.message }) } })() - }) + }); } /** @@ -189,7 +189,7 @@ export const executeAgentTaskStep = async ( } // If there are artifacts in the step, append them to the task's artifacts array (or initialize it if necessary) - if (step.artifacts != null && step.artifacts.length > 0) { + if (step.artifacts && step.artifacts.length > 0) { task[0].artifacts = task[0].artifacts ?? [] task[0].artifacts.push(...step.artifacts) } @@ -250,7 +250,7 @@ const registerGetAgentTaskStep: RouteRegisterFn = (router: Router) => { export const getArtifacts = async ( taskId: string ): Promise => { - const task = await getAgentTask(taskId) + const task = await getAgentTask(taskId); return task.artifacts; } const registerGetArtifacts: RouteRegisterFn = (router: Router) => { @@ -259,10 +259,10 @@ const registerGetArtifacts: RouteRegisterFn = (router: Router) => { const taskId = req.params.task_id try { const artifacts = await getArtifacts(taskId) - const current_page = Number(req.query.current_page) || 1 - const page_size = Number(req.query.page_size) || 10 + const current_page = Number(req.query['current_page']) || 1 + const page_size = Number(req.query['page_size']) || 10 - if (artifacts == null) { + if (!artifacts) { res.status(200).send({ artifacts: [], pagination: { @@ -311,7 +311,7 @@ export const getArtifactPath = ( ): string => { const rootDir = path.isAbsolute(workspace) ? workspace : - path.join(process.cwd(), workspace) + path.join(process.cwd(), workspace); return path.join( rootDir, @@ -341,7 +341,7 @@ export const createArtifact = async ( relative_path: relativePath || null, created_at: Date.now().toString(), } - task.artifacts = task.artifacts != null || [] + task.artifacts = task.artifacts || [] task.artifacts.push(artifact) const artifactFolderPath = getArtifactPath( @@ -363,14 +363,14 @@ const registerCreateArtifact: RouteRegisterFn = (router: Router, context: RouteC const relativePath = req.body.relative_path const task = tasks.find(([{ task_id }]) => task_id == taskId) - if (task == null) { + if (!task) { res .status(404) .json({ message: 'Unable to find task with the provided id' }) } - const files = req.files as Express.Multer.File[] - const file = files.find(({ fieldname }) => fieldname == 'file') + const files = req.files as Array + let file = files.find(({ fieldname }) => fieldname == 'file') const artifact = await createArtifact( context.workspace, task[0], @@ -398,7 +398,7 @@ export const getTaskArtifact = async ( ): Promise => { const task = await getAgentTask(taskId) const artifact = task.artifacts?.find((a) => a.artifact_id === artifactId) - if (artifact == null) { + if (!artifact) { throw new Error( `Artifact with id ${artifactId} in task with id ${taskId} was not found` ) @@ -430,7 +430,7 @@ export interface AgentConfig { export const defaultAgentConfig: AgentConfig = { port: 8000, workspace: "./workspace", -} +}; export class Agent { constructor( @@ -445,7 +445,7 @@ export class Agent { taskHandler = _taskHandler return new Agent(_taskHandler, { workspace: config.workspace || defaultAgentConfig.workspace, - port: config.port || defaultAgentConfig.port, + port: config.port || defaultAgentConfig.port }); } diff --git a/packages/sdk/js/src/api.ts b/packages/sdk/js/src/api.ts index 636c5918..dcde6458 100644 --- a/packages/sdk/js/src/api.ts +++ b/packages/sdk/js/src/api.ts @@ -1,6 +1,6 @@ import * as OpenApiValidator from "express-openapi-validator"; import yaml from "js-yaml"; -import express, { Router } from "express"; // <-- Import Router +import express, { Router } from "express"; // <-- Import Router import * as core from "express-serve-static-core"; import spec from "../agent-protocol/schemas/openapi.yml"; diff --git a/packages/sdk/js/src/models.ts b/packages/sdk/js/src/models.ts index 02198a7d..1030ec96 100644 --- a/packages/sdk/js/src/models.ts +++ b/packages/sdk/js/src/models.ts @@ -27,7 +27,7 @@ export type StepOutput = any export enum StepStatus { CREATED = "created", RUNNING = "running", - COMPLETED = "completed", + COMPLETED = "completed" } export interface Step { diff --git a/rfcs/2023-08-28-Pagination-RFC.md b/rfcs/2023-08-28-Pagination-RFC.md index 583787f4..0df023c4 100644 --- a/rfcs/2023-08-28-Pagination-RFC.md +++ b/rfcs/2023-08-28-Pagination-RFC.md @@ -5,7 +5,7 @@ | **Author(s)** | Merwane Hamadi (merwanehamadi@gmail.com) Craig Swift (craigswift13@gmail.com)| | **RFC PR:** | [PR 53](https://github.com/e2b-dev/agent-protocol/pull/53) | | **Updated** | 2023-08-28 | -| **Obsoletes** | +| **Obsoletes** | | ## Summary diff --git a/rfcs/2023-08-28-agent-created-RFC-.md b/rfcs/2023-08-28-agent-created-RFC-.md index cea9b583..0a2362d5 100644 --- a/rfcs/2023-08-28-agent-created-RFC-.md +++ b/rfcs/2023-08-28-agent-created-RFC-.md @@ -5,7 +5,7 @@ | **Author(s)** | Merwane Hamadi (merwanehamadi@gmail.com) Craig Swift (craigswift13@gmail.com) | | **RFC PR:** | | | **Created** | 2023-08-28 | -| **Obsoletes** | | +| **Obsoletes** | | ## Summary @@ -14,6 +14,7 @@ Add agent_created to the artifact response body. ## Motivation If we don't know whether an artifact is generated by the agent or not, it's hard to know what the agent did or did not do. + ## Agent Builders Benefit - They can tell their users what their agent did. diff --git a/rfcs/2023-08-28-list-entities-RFC.md b/rfcs/2023-08-28-list-entities-RFC.md index 43cb4c4d..013149d9 100644 --- a/rfcs/2023-08-28-list-entities-RFC.md +++ b/rfcs/2023-08-28-list-entities-RFC.md @@ -5,7 +5,7 @@ | **Author(s)** | Merwane Hamadi (merwanehamadi@gmail.com) Craig Swift (craigswift13@gmail.com)| | **RFC PR:** | | | **Updated** | 2023-08-28 | -| **Obsoletes** | | +| **Obsoletes** | | ## Summary @@ -18,6 +18,7 @@ It's like a table. Currently to build that you need to get the list of task ids. And if you want to display 20 tasks. You will make 20 GET calls to show them. + ## Agent Builders Benefit - They can allow their users to list things: Currently they get a list of ids, that's not useful. @@ -27,7 +28,6 @@ Currently to build that you need to get the list of task ids. And if you want to Just do what everyone does: return an array of objects that represent the resource ### Alternatives Considered - - Just make 26 calls when you need to display a table of 25 tasks (1 call to get an id and then 25 calls to get the information of each task) ### Compatibility diff --git a/rfcs/2023-09-15-endpoint-schema.md b/rfcs/2023-09-15-endpoint-schema.md index ddbb413f..766d271b 100644 --- a/rfcs/2023-09-15-endpoint-schema.md +++ b/rfcs/2023-09-15-endpoint-schema.md @@ -32,7 +32,6 @@ Change the current `/agent/` endpoint schema to `/ap/v1/agent/`. This brings cla ### Compatibility These changes are not backwards compatible for the following reasons: - - The change in the endpoint schema will break existing client implementations tied to the old URL structure. Clients will need to update their integrations to accomodate these changes, necessitating a major version bump. diff --git a/schemas/openapi.json b/schemas/openapi.json index 38bcb415..d0250e7f 100644 --- a/schemas/openapi.json +++ b/schemas/openapi.json @@ -379,7 +379,10 @@ { "type": "object", "description": "Definition of an agent task.", - "required": ["task_id", "artifacts"], + "required": [ + "task_id", + "artifacts" + ], "properties": { "task_id": { "description": "The ID of the task.", @@ -447,7 +450,9 @@ "example": "Unable to find entity with the provided id" } }, - "required": ["message"] + "required": [ + "message" + ] } } } @@ -456,7 +461,9 @@ "description": "Internal Server Error" } }, - "tags": ["agent"], + "tags": [ + "agent" + ], "security": [ { "HTTPBearer": [] @@ -702,7 +709,9 @@ "description": "Internal Server Error" } }, - "tags": ["agent"], + "tags": [ + "agent" + ], "security": [ { "HTTPBearer": [] @@ -918,7 +927,9 @@ "description": "Internal Server Error" } }, - "tags": ["agent"], + "tags": [ + "agent" + ], "security": [ { "HTTPBearer": [] @@ -1331,7 +1342,9 @@ "example": "python/code" } }, - "required": ["file"] + "required": [ + "file" + ] } } } @@ -1479,7 +1492,9 @@ "description": "Internal Server Error" } }, - "tags": ["agent"], + "tags": [ + "agent" + ], "security": [ { "HTTPBearer": [] @@ -1696,7 +1711,11 @@ "description": "The status of the task step.", "type": "string", "example": "created", - "enum": ["created", "running", "completed"] + "enum": [ + "created", + "running", + "completed" + ] }, "output": { "description": "Output of the task step.", @@ -1923,7 +1942,9 @@ "example": "python/code" } }, - "required": ["file"] + "required": [ + "file" + ] }, "StepInput": { "description": "Input parameters for the task step. Any value is allowed.", @@ -1975,7 +1996,10 @@ { "type": "object", "description": "Definition of an agent task.", - "required": ["task_id", "artifacts"], + "required": [ + "task_id", + "artifacts" + ], "properties": { "task_id": { "description": "The ID of the task.", @@ -2011,7 +2035,11 @@ "nullable": true } }, - "required": ["artifact_id", "agent_created", "file_name"] + "required": [ + "artifact_id", + "agent_created", + "file_name" + ] }, "example": [ "7a49f31c-f9c6-4346-a22c-e32bc5af4d8e", @@ -2089,7 +2117,11 @@ "description": "The status of the task step.", "type": "string", "example": "created", - "enum": ["created", "running", "completed"] + "enum": [ + "created", + "running", + "completed" + ] }, "output": { "description": "Output of the task step.", @@ -2132,7 +2164,11 @@ "nullable": true } }, - "required": ["artifact_id", "agent_created", "file_name"] + "required": [ + "artifact_id", + "agent_created", + "file_name" + ] }, "default": [] }, @@ -2172,7 +2208,9 @@ "example": "Unable to find entity with the provided id" } }, - "required": ["message"] + "required": [ + "message" + ] } } } From cbea5189ce3d73e68711c6e1d81d3085705dfb90 Mon Sep 17 00:00:00 2001 From: nalbion Date: Sun, 11 Feb 2024 04:56:50 +1100 Subject: [PATCH 7/8] more reverting prettier changes to reduce noise in PR --- packages/sdk/js/src/agent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/js/src/agent.ts b/packages/sdk/js/src/agent.ts index f7a048fa..f0e01468 100644 --- a/packages/sdk/js/src/agent.ts +++ b/packages/sdk/js/src/agent.ts @@ -21,7 +21,7 @@ import { RouteRegisterFn, RouteContext, } from "./api"; -import { type Router } from 'express'; +import { Router } from 'express'; /** * A function that handles a step in a task. From 6434caeef829fabdfe8960addd5a7386f182bd98 Mon Sep 17 00:00:00 2001 From: nalbion Date: Sun, 11 Feb 2024 05:41:36 +1100 Subject: [PATCH 8/8] auth against apiKeysor jwtSecret --- package-lock.json | 1930 ++++++++--------------------- package.json | 5 + packages/sdk/js/authMiddleware.ts | 65 + packages/sdk/js/src/agent.ts | 8 +- packages/sdk/js/src/api.ts | 17 +- schemas/openapi.json | 32 + schemas/openapi.yml | 13 + 7 files changed, 625 insertions(+), 1445 deletions(-) create mode 100644 packages/sdk/js/authMiddleware.ts diff --git a/package-lock.json b/package-lock.json index 28412b84..9cbd4680 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,13 +6,18 @@ "": { "name": "agent-protocol", "devDependencies": { + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.5", "@typescript-eslint/eslint-plugin": "^5.62.0", "eslint": "^8.45.0", "eslint-config-prettier": "^8.8.0", "eslint-config-standard-with-typescript": "^36.1.0", "husky": "^7.0.0", - "lint-staged": "^13.2.3", - "prettier": "^3.0.0" + "prettier": "^3.0.0", + "typescript": "^5.1.6" + }, + "optionalDependencies": { + "jsonwebtoken": "^9.0.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -148,6 +153,55 @@ "node": ">= 8" } }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", @@ -161,12 +215,69 @@ "dev": true, "peer": true }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz", + "integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.11.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz", + "integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/qs": { + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", + "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, "node_modules/@types/semver": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", "dev": true }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", @@ -398,19 +509,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -427,45 +525,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -594,15 +653,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -644,6 +694,12 @@ "node": ">=8" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "optional": true + }, "node_modules/builtins": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", @@ -677,55 +733,6 @@ "node": ">=6" } }, - "node_modules/chalk": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", - "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", - "dev": true, - "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -744,21 +751,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "engines": { - "node": ">=14" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -843,17 +835,14 @@ "node": ">=6.0.0" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } }, "node_modules/es-abstract": { "version": "1.22.1", @@ -1391,29 +1380,6 @@ "node": ">=0.10.0" } }, - "node_modules/execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1596,18 +1562,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -1826,15 +1780,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", - "dev": true, - "engines": { - "node": ">=14.18.0" - } - }, "node_modules/husky": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz", @@ -1884,15 +1829,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2020,18 +1956,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2121,18 +2045,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", @@ -2244,6 +2156,49 @@ "json5": "lib/cli.js" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "optional": true, + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "optional": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "optional": true, + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2257,352 +2212,101 @@ "node": ">= 0.8.0" } }, - "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/lint-staged": { - "version": "13.2.3", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.2.3.tgz", - "integrity": "sha512-zVVEXLuQIhr1Y7R7YAWx4TZLdvuzk7DnmrsTNL0fax6Z3jrpFcas+vKbzxhhvp6TA55m1SQuWkpzI1qbfDZbAg==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { - "chalk": "5.2.0", - "cli-truncate": "^3.1.0", - "commander": "^10.0.0", - "debug": "^4.3.4", - "execa": "^7.0.0", - "lilconfig": "2.1.0", - "listr2": "^5.0.7", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-inspect": "^1.12.3", - "pidtree": "^0.6.0", - "string-argv": "^0.3.1", - "yaml": "^2.2.2" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" + "p-locate": "^5.0.0" }, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/lint-staged" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/listr2": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.8.tgz", - "integrity": "sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==", - "dev": true, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "optional": true + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "optional": true + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "optional": true + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "optional": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "optional": true + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "optional": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "optional": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "devOptional": true, "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.19", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.8.0", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" + "yallist": "^4.0.0" }, "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } + "node": ">=10" } }, - "node_modules/listr2/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, "engines": { - "node": ">=8" + "node": ">= 8" } }, - "node_modules/listr2/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "braces": "^3.0.2", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/listr2/node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/listr2/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/listr2/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/listr2/node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/listr2/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/listr2/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-update/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-update/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8.6" } }, "node_modules/minimatch": { @@ -2631,7 +2335,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true }, "node_modules/natural-compare": { "version": "1.4.0", @@ -2645,47 +2349,12 @@ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", "dev": true, + "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2777,21 +2446,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -2839,21 +2493,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -2921,18 +2560,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", - "dev": true, - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3031,43 +2658,6 @@ "node": ">=4" } }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/restore-cursor/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/restore-cursor/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -3078,12 +2668,6 @@ "node": ">=0.10.0" } }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -3122,15 +2706,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/safe-array-concat": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", @@ -3150,6 +2725,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, "node_modules/safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", @@ -3169,7 +2764,7 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, + "devOptional": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -3216,12 +2811,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -3231,48 +2820,6 @@ "node": ">=8" } }, - "node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/string-argv": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", - "dev": true, - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/string.prototype.trim": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", @@ -3321,21 +2868,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -3346,18 +2878,6 @@ "node": ">=4" } }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3401,12 +2921,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3432,12 +2946,6 @@ "strip-bom": "^3.0.0" } }, - "node_modules/tslib": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", - "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", - "dev": true - }, "node_modules/tsutils": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", @@ -3471,18 +2979,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", @@ -3557,7 +3053,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3582,6 +3077,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3643,88 +3144,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3735,16 +3154,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", - "dev": true, - "engines": { - "node": ">= 14" - } + "devOptional": true }, "node_modules/yocto-queue": { "version": "0.1.0", @@ -3853,6 +3263,55 @@ "fastq": "^1.6.0" } }, + "@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, "@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", @@ -3866,12 +3325,69 @@ "dev": true, "peer": true }, + "@types/jsonwebtoken": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz", + "integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "@types/node": { + "version": "20.11.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz", + "integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + }, + "@types/qs": { + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", + "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, "@types/semver": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", "dev": true }, + "@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "dev": true, + "requires": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, "@typescript-eslint/eslint-plugin": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", @@ -4002,16 +3518,6 @@ "dev": true, "requires": {} }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -4024,27 +3530,6 @@ "uri-js": "^4.2.2" } }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true - }, - "ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true - }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -4137,12 +3622,6 @@ "is-shared-array-buffer": "^1.0.2" } }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, "available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -4175,6 +3654,12 @@ "fill-range": "^7.0.1" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "optional": true + }, "builtins": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", @@ -4202,37 +3687,6 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, - "chalk": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", - "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", - "dev": true - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", - "dev": true, - "requires": { - "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" - } - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4248,18 +3702,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4321,17 +3763,14 @@ "esutils": "^2.0.2" } }, - "eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "optional": true, + "requires": { + "safe-buffer": "^5.0.1" + } }, "es-abstract": { "version": "1.22.1", @@ -4721,23 +4160,6 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, - "execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - } - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4889,12 +4311,6 @@ "has-symbols": "^1.0.3" } }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, "get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -5043,12 +4459,6 @@ "has-symbols": "^1.0.2" } }, - "human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", - "dev": true - }, "husky": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz", @@ -5074,13 +4484,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, "inflight": { @@ -5177,12 +4581,6 @@ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, - "is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "dev": true - }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -5242,12 +4640,6 @@ "call-bind": "^1.0.2" } }, - "is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true - }, "is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", @@ -5332,127 +4724,53 @@ "minimist": "^1.2.0" } }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, + "jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "optional": true, "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" } }, - "lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "optional": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } }, - "lint-staged": { - "version": "13.2.3", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.2.3.tgz", - "integrity": "sha512-zVVEXLuQIhr1Y7R7YAWx4TZLdvuzk7DnmrsTNL0fax6Z3jrpFcas+vKbzxhhvp6TA55m1SQuWkpzI1qbfDZbAg==", - "dev": true, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "optional": true, "requires": { - "chalk": "5.2.0", - "cli-truncate": "^3.1.0", - "commander": "^10.0.0", - "debug": "^4.3.4", - "execa": "^7.0.0", - "lilconfig": "2.1.0", - "listr2": "^5.0.7", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-inspect": "^1.12.3", - "pidtree": "^0.6.0", - "string-argv": "^0.3.1", - "yaml": "^2.2.2" + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" } }, - "listr2": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.8.tgz", - "integrity": "sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==", + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "requires": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.19", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.8.0", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "requires": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" } }, "locate-path": { @@ -5464,110 +4782,63 @@ "p-locate": "^5.0.0" } }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "optional": true + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "optional": true + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "optional": true + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "optional": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "optional": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "optional": true + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - } - } + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "optional": true }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "requires": { "yallist": "^4.0.0" } }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -5584,12 +4855,6 @@ "picomatch": "^2.3.1" } }, - "mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true - }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -5610,7 +4875,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true }, "natural-compare": { "version": "1.4.0", @@ -5624,34 +4889,12 @@ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", - "dev": true, - "requires": { - "path-key": "^4.0.0" - }, - "dependencies": { - "path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true - } - } - }, "object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true + "dev": true, + "peer": true }, "object-keys": { "version": "1.1.1", @@ -5719,15 +4962,6 @@ "wrappy": "1" } }, - "onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "requires": { - "mimic-fn": "^4.0.0" - } - }, "optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -5760,15 +4994,6 @@ "p-limit": "^3.0.2" } }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5815,12 +5040,6 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, - "pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", - "dev": true - }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5875,45 +5094,12 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "dependencies": { - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - } - } - }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, - "rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -5932,15 +5118,6 @@ "queue-microtask": "^1.2.2" } }, - "rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - }, "safe-array-concat": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", @@ -5954,6 +5131,12 @@ "isarray": "^2.0.5" } }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "optional": true + }, "safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", @@ -5970,7 +5153,7 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, + "devOptional": true, "requires": { "lru-cache": "^6.0.0" } @@ -6002,45 +5185,12 @@ "object-inspect": "^1.9.0" } }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, - "slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dev": true, - "requires": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - } - }, - "string-argv": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", - "dev": true - }, - "string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "requires": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - } - }, "string.prototype.trim": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", @@ -6077,15 +5227,6 @@ "es-abstract": "^1.20.4" } }, - "strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "requires": { - "ansi-regex": "^6.0.1" - } - }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -6093,12 +5234,6 @@ "dev": true, "peer": true }, - "strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -6127,12 +5262,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6155,12 +5284,6 @@ "strip-bom": "^3.0.0" } }, - "tslib": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", - "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", - "dev": true - }, "tsutils": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", @@ -6187,12 +5310,6 @@ "prelude-ls": "^1.2.1" } }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - }, "typed-array-buffer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", @@ -6248,8 +5365,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", - "dev": true, - "peer": true + "dev": true }, "unbox-primitive": { "version": "1.0.2", @@ -6264,6 +5380,12 @@ "which-boxed-primitive": "^1.0.2" } }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -6310,66 +5432,6 @@ "has-tostringtag": "^1.0.0" } }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -6380,13 +5442,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", - "dev": true + "devOptional": true }, "yocto-queue": { "version": "0.1.0", diff --git a/package.json b/package.json index a9606037..79c97c2c 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ } }, "devDependencies": { + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.5", "@typescript-eslint/eslint-plugin": "^5.62.0", "eslint": "^8.45.0", "eslint-config-prettier": "^8.8.0", @@ -19,5 +21,8 @@ "husky": "^7.0.0", "prettier": "^3.0.0", "typescript": "^5.1.6" + }, + "optionalDependencies": { + "jsonwebtoken": "^9.0.2" } } diff --git a/packages/sdk/js/authMiddleware.ts b/packages/sdk/js/authMiddleware.ts new file mode 100644 index 00000000..ce99e975 --- /dev/null +++ b/packages/sdk/js/authMiddleware.ts @@ -0,0 +1,65 @@ +import { type Request, type Response, type NextFunction } from 'express' +import { type VerifyErrors } from 'jsonwebtoken' +import { type ApiConfig } from './src/api' + +const authMiddleware = (config: ApiConfig) => { + return (req: Request, res: Response, next: NextFunction) => { + if (config.jwtSecret !== undefined) { + jwtAuthorisation(config.jwtSecret, req, res, next) + } else if (config.apiKeys !== undefined && config.apiKeys.length !== 0) { + apiKeyAuthorisation(config.apiKeys, req, res, next) + } else { + // No authorisation required + next() + } + } +} + +const apiKeyAuthorisation = ( + apiKeys: string[], + req: Request, + res: Response, + next: NextFunction +): void => { + const apiKey = req.headers['X-API-KEY'] as string | undefined + + if (apiKey === undefined) { + res.sendStatus(401) + } else if (apiKeys.includes(apiKey)) { + next() + } else { + res.sendStatus(403) + } +} + +const jwtAuthorisation = ( + jwtSecret: string, + req: Request, + res: Response, + next: NextFunction +): void => { + import('jsonwebtoken') + .then(({ verify }) => { + const authHeader = req.headers.authorization + if (authHeader === undefined) { + return res.sendStatus(401) + } + + const token = authHeader.split(' ')[1] + + verify(token, jwtSecret, (err: VerifyErrors | null, user) => { + if (err !== null) { + return res.sendStatus(403) + } + + // req.user = user + next() + }) + }) + .catch((err) => { + console.error(err) + res.sendStatus(500) + }) +} + +export default authMiddleware diff --git a/packages/sdk/js/src/agent.ts b/packages/sdk/js/src/agent.ts index f0e01468..c0a93879 100644 --- a/packages/sdk/js/src/agent.ts +++ b/packages/sdk/js/src/agent.ts @@ -423,8 +423,10 @@ const registerGetTaskArtifact: RouteRegisterFn = (router: Router, context: Route } export interface AgentConfig { - port: number; - workspace: string; + port: number + workspace: string + apiKeys?: string[] + jwtSecret?: string } export const defaultAgentConfig: AgentConfig = { @@ -469,6 +471,8 @@ export class Agent { context: { workspace: this.config.workspace }, + apiKeys: this.config.apiKeys, + jwtSecret: this.config.jwtSecret, } createApi(config) diff --git a/packages/sdk/js/src/api.ts b/packages/sdk/js/src/api.ts index dcde6458..dc6e9a23 100644 --- a/packages/sdk/js/src/api.ts +++ b/packages/sdk/js/src/api.ts @@ -3,7 +3,8 @@ import yaml from "js-yaml"; import express, { Router } from "express"; // <-- Import Router import * as core from "express-serve-static-core"; -import spec from "../agent-protocol/schemas/openapi.yml"; +import spec from '../agent-protocol/schemas/openapi.yml' +import authMiddleware from '../authMiddleware' export type ApiApp = core.Express; @@ -17,10 +18,12 @@ export type RouteRegisterFn = ( ) => void; export interface ApiConfig { - context: RouteContext; - port: number; - callback?: () => void; - routes: RouteRegisterFn[]; + context: RouteContext + port: number + apiKeys?: string[] + jwtSecret?: string + callback?: () => void + routes: RouteRegisterFn[] } export const createApi = (config: ApiConfig) => { @@ -44,7 +47,9 @@ export const createApi = (config: ApiConfig) => { res.setHeader("Content-Type", "text/yaml").status(200).send(spec); }); - const router = Router(); + const router = Router() + + router.use(authMiddleware(config)) config.routes.map((route) => { route(router, config.context); diff --git a/schemas/openapi.json b/schemas/openapi.json index d0250e7f..5652e33b 100644 --- a/schemas/openapi.json +++ b/schemas/openapi.json @@ -153,6 +153,9 @@ { "HTTPBearer": [] }, + { + "ApiKeyAuth": [] + }, {} ] }, @@ -326,6 +329,9 @@ { "HTTPBearer": [] }, + { + "ApiKeyAuth": [] + }, {} ] } @@ -468,6 +474,9 @@ { "HTTPBearer": [] }, + { + "ApiKeyAuth": [] + }, {} ] } @@ -716,6 +725,9 @@ { "HTTPBearer": [] }, + { + "ApiKeyAuth": [] + }, {} ] }, @@ -934,6 +946,9 @@ { "HTTPBearer": [] }, + { + "ApiKeyAuth": [] + }, {} ] } @@ -1133,6 +1148,9 @@ { "HTTPBearer": [] }, + { + "ApiKeyAuth": [] + }, {} ] } @@ -1299,6 +1317,9 @@ { "HTTPBearer": [] }, + { + "ApiKeyAuth": [] + }, {} ] }, @@ -1420,6 +1441,9 @@ { "HTTPBearer": [] }, + { + "ApiKeyAuth": [] + }, {} ] } @@ -1499,6 +1523,9 @@ { "HTTPBearer": [] }, + { + "ApiKeyAuth": [] + }, {} ] } @@ -2220,6 +2247,11 @@ "HTTPBearer": { "type": "http", "scheme": "bearer" + }, + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "X-API-KEY" } } } diff --git a/schemas/openapi.yml b/schemas/openapi.yml index bf0f902f..22d06aa7 100644 --- a/schemas/openapi.yml +++ b/schemas/openapi.yml @@ -35,6 +35,7 @@ paths: - agent security: - HTTPBearer: [] + - ApiKeyAuth: [] - {} get: operationId: listAgentTasks @@ -73,6 +74,7 @@ paths: - agent security: - HTTPBearer: [] + - ApiKeyAuth: [] - {} '/ap/v1/agent/tasks/{task_id}': get: @@ -104,6 +106,7 @@ paths: - agent security: - HTTPBearer: [] + - ApiKeyAuth: [] - {} '/ap/v1/agent/tasks/{task_id}/steps': get: @@ -155,6 +158,7 @@ paths: - agent security: - HTTPBearer: [] + - ApiKeyAuth: [] - {} post: operationId: executeAgentTaskStep @@ -196,6 +200,7 @@ paths: - agent security: - HTTPBearer: [] + - ApiKeyAuth: [] - {} '/ap/v1/agent/tasks/{task_id}/steps/{step_id}': get: @@ -239,6 +244,7 @@ paths: - agent security: - HTTPBearer: [] + - ApiKeyAuth: [] - {} '/ap/v1/agent/tasks/{task_id}/artifacts': get: @@ -290,6 +296,7 @@ paths: - agent security: - HTTPBearer: [] + - ApiKeyAuth: [] - {} post: operationId: uploadAgentTaskArtifacts @@ -325,6 +332,7 @@ paths: - agent security: - HTTPBearer: [] + - ApiKeyAuth: [] - {} '/ap/v1/agent/tasks/{task_id}/artifacts/{artifact_id}': get: @@ -364,6 +372,7 @@ paths: - agent security: - HTTPBearer: [] + - ApiKeyAuth: [] - {} components: schemas: @@ -610,3 +619,7 @@ components: HTTPBearer: type: http scheme: bearer + ApiKeyAuth: + type: apiKey + in: header + name: X-API-KEY