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/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 {\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": "" + } + ] +}