{
  "info": {
    "name": "TestingAPIs: Postman tutorial",
    "description": "The requests and tests from the Postman tutorial at https://testingapis.com/learn/postman. Every request goes to the free test API at https://api.testingapis.com. If an endpoint asks for an API key, put yours in the apiKey environment variable.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.testingapis.com"
    },
    {
      "key": "productId",
      "value": "1"
    }
  ],
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// Runs before every request in the collection. Endpoints outside the free tier read an API key from",
          "// this header; it's only added when the apiKey environment variable has a value.",
          "const apiKey = pm.environment.get(\"apiKey\");",
          "if (apiKey) {",
          "  pm.request.headers.upsert({ key: \"X-TestingApis-Key\", value: apiKey });",
          "}"
        ]
      }
    }
  ],
  "item": [
    {
      "name": "01 First requests",
      "description": "Lesson 1: sending requests and reading responses.",
      "item": [
        {
          "name": "Get one product",
          "event": [],
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/api/Products/1"
          }
        },
        {
          "name": "List electronics, two per page",
          "event": [],
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/api/Products?category=electronics&limit=2&page=1"
          }
        },
        {
          "name": "Send JSON with POST",
          "event": [],
          "request": {
            "method": "POST",
            "header": [],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Asha\",\n  \"role\": \"tester\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/api/methods/post"
          }
        }
      ]
    },
    {
      "name": "02 Variables",
      "description": "Lesson 2: collection and environment variables.",
      "item": [
        {
          "name": "Product by ID from a variable",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"the product matches the productId variable\", () => {",
                  "  pm.expect(pm.response.json().id).to.eql(Number(pm.variables.get(\"productId\")));",
                  "});",
                  "console.log(\"baseUrl resolved to\", pm.variables.get(\"baseUrl\"));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/api/Products/{{productId}}"
          }
        }
      ]
    },
    {
      "name": "03 Auth and chaining",
      "description": "Lesson 3: the Authorization tab, and passing a token from one request to the next.",
      "item": [
        {
          "name": "Basic auth",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Basic credentials accepted\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "basic",
              "basic": [
                {
                  "key": "username",
                  "value": "apibee"
                },
                {
                  "key": "password",
                  "value": "password123"
                }
              ]
            },
            "url": "{{baseUrl}}/api/auth/basic"
          }
        },
        {
          "name": "API key in a header",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"API key accepted\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "X-API-Key"
                },
                {
                  "key": "value",
                  "value": "apibee-key-123"
                },
                {
                  "key": "in",
                  "value": "header"
                }
              ]
            },
            "url": "{{baseUrl}}/api/auth/api-key/header"
          }
        },
        {
          "name": "JWT login (saves the tokens)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"login succeeds\", () => pm.response.to.have.status(200));",
                  "",
                  "const body = pm.response.json();",
                  "pm.collectionVariables.set(\"accessToken\", body.accessToken);",
                  "pm.collectionVariables.set(\"refreshToken\", body.refreshToken);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"username\": \"user\",\n  \"password\": \"user123\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/api/auth/jwt/login"
          }
        },
        {
          "name": "JWT: who am I",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"the token from the login works\", () => pm.response.to.have.status(200));",
                  "pm.test(\"it belongs to user\", () => pm.expect(pm.response.json().username).to.eql(\"user\"));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}"
                }
              ]
            },
            "url": "{{baseUrl}}/api/auth/jwt/me"
          }
        },
        {
          "name": "JWT: admin endpoint refuses a user token",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"a valid token without the admin role gets 403\", () => pm.response.to.have.status(403));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}"
                }
              ]
            },
            "url": "{{baseUrl}}/api/auth/jwt/admin"
          }
        },
        {
          "name": "OAuth 2.0: client credentials token",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"token issued\", () => pm.response.to.have.status(200));",
                  "pm.test(\"token responses are never cached\", () => pm.response.to.have.header(\"Cache-Control\", \"no-store\"));",
                  "pm.collectionVariables.set(\"oauthToken\", pm.response.json().access_token);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "auth": {
              "type": "basic",
              "basic": [
                {
                  "key": "username",
                  "value": "apibee-client"
                },
                {
                  "key": "password",
                  "value": "apibee-secret"
                }
              ]
            },
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "client_credentials"
                },
                {
                  "key": "scope",
                  "value": "read"
                }
              ]
            },
            "url": "{{baseUrl}}/api/auth/oauth/token"
          }
        },
        {
          "name": "OAuth 2.0: protected resource",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"the OAuth token is accepted\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{oauthToken}}"
                }
              ]
            },
            "url": "{{baseUrl}}/api/auth/oauth/protected"
          }
        }
      ]
    },
    {
      "name": "04 Tests",
      "description": "Lesson 4: assertions on status, headers, body, timing and schema.",
      "item": [
        {
          "name": "Product 1, fully checked",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => {",
                  "  pm.response.to.have.status(200);",
                  "});",
                  "",
                  "pm.test(\"the body is JSON\", () => {",
                  "  pm.response.to.have.header(\"Content-Type\");",
                  "  pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");",
                  "});",
                  "",
                  "const product = pm.response.json();",
                  "",
                  "pm.test(\"it is product 1, with a positive price\", () => {",
                  "  pm.expect(product.id).to.eql(1);",
                  "  pm.expect(product.price).to.be.a(\"number\").and.above(0);",
                  "});",
                  "",
                  "pm.test(\"the rating is between 0 and 5\", () => {",
                  "  pm.expect(product.rating.rate).to.be.within(0, 5);",
                  "});",
                  "",
                  "pm.test(\"it answers in under 2 seconds\", () => {",
                  "  pm.expect(pm.response.responseTime).to.be.below(2000);",
                  "});",
                  "",
                  "const schema = {",
                  "  type: \"object\",",
                  "  required: [\"id\", \"title\", \"price\", \"category\", \"rating\", \"inStock\"],",
                  "  properties: {",
                  "    id: { type: \"integer\" },",
                  "    title: { type: \"string\", minLength: 1 },",
                  "    price: { type: \"number\" },",
                  "    category: { type: \"string\" },",
                  "    rating: {",
                  "      type: \"object\",",
                  "      required: [\"rate\", \"count\"],",
                  "      properties: { rate: { type: \"number\" }, count: { type: \"integer\" } },",
                  "    },",
                  "    inStock: { type: \"boolean\" },",
                  "  },",
                  "};",
                  "",
                  "pm.test(\"the body matches the product schema\", () => {",
                  "  pm.response.to.have.jsonSchema(schema);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/api/Products/1"
          }
        },
        {
          "name": "A missing product returns 404",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 404\", () => pm.response.to.have.status(404));",
                  "pm.test(\"the error names the missing ID\", () => {",
                  "  pm.expect(pm.response.json().message).to.include(\"9999\");",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/api/Products/9999"
          }
        },
        {
          "name": "A filtered page of products",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const products = pm.response.json();",
                  "",
                  "pm.test(\"no more items than the limit\", () => {",
                  "  pm.expect(products.length).to.be.at.most(2);",
                  "});",
                  "",
                  "pm.test(\"every item is in the requested category\", () => {",
                  "  products.forEach((p) => pm.expect(p.category).to.eql(\"electronics\"));",
                  "});",
                  "",
                  "pm.test(\"X-Total-Count is a number at least as large as the page\", () => {",
                  "  const total = Number(pm.response.headers.get(\"X-Total-Count\"));",
                  "  pm.expect(total).to.be.at.least(products.length);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/api/Products?category=electronics&limit=2&page=1"
          }
        }
      ]
    },
    {
      "name": "05 Scripts and flows",
      "description": "Lesson 5: pre-request scripts, dynamic data, a create-read-delete chain, a signed request and polling.",
      "item": [
        {
          "name": "Create a todo with a random title",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// A title nobody else will have, so the read-back check can't match someone else's todo",
                  "const title = \"Postman tutorial \" + pm.variables.replaceIn(\"{{$randomLoremWords}}\");",
                  "pm.collectionVariables.set(\"todoTitle\", title);"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"created\", () => pm.response.to.have.status(201));",
                  "const todo = pm.response.json().data;",
                  "pm.collectionVariables.set(\"todoId\", todo.id);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"title\": \"{{todoTitle}}\",\n  \"userId\": 1,\n  \"priority\": \"low\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/api/Todos"
          }
        },
        {
          "name": "Read the todo back",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"the stored title is the one we sent\", () => {",
                  "  pm.expect(pm.response.json().title).to.eql(pm.collectionVariables.get(\"todoTitle\"));",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/api/Todos/{{todoId}}"
          }
        },
        {
          "name": "Delete the todo",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"deleted\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [],
            "url": "{{baseUrl}}/api/Todos/{{todoId}}"
          }
        },
        {
          "name": "Check the todo is gone",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"a deleted todo returns 404\", () => pm.response.to.have.status(404));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/api/Todos/{{todoId}}"
          }
        },
        {
          "name": "HMAC-signed request",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Sign timestamp + \".\" + body with the shared secret, as the endpoint expects",
                  "const cryptoJs = require(\"crypto-js\");",
                  "const timestamp = Math.floor(Date.now() / 1000).toString();",
                  "const body = pm.request.body.raw;",
                  "const signature = cryptoJs.HmacSHA256(timestamp + \".\" + body, \"apibee-hmac-secret\").toString(cryptoJs.enc.Hex);",
                  "",
                  "pm.request.headers.upsert({ key: \"X-Timestamp\", value: timestamp });",
                  "pm.request.headers.upsert({ key: \"X-Signature\", value: signature });"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"the signature is accepted\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "body": {
              "mode": "raw",
              "raw": "{\"event\":\"ping\"}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/api/auth/hmac"
          }
        },
        {
          "name": "Start a job",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"accepted for processing\", () => pm.response.to.have.status(202));",
                  "pm.collectionVariables.set(\"jobId\", pm.response.json().data.id);",
                  "pm.collectionVariables.set(\"polls\", 0);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"type\": \"report\",\n  \"durationSeconds\": 3\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/api/jobs"
          }
        },
        {
          "name": "Poll the job until it finishes",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Unlike \"Start a job\", the status endpoint returns the job itself, not wrapped in \"data\"",
                  "const job = pm.response.json();",
                  "const polls = Number(pm.collectionVariables.get(\"polls\")) + 1;",
                  "pm.collectionVariables.set(\"polls\", polls);",
                  "",
                  "if ((job.status === \"queued\" || job.status === \"running\") && polls < 30) {",
                  "  // Not done: run this request again after the delay the server suggests",
                  "  const wait = Number(pm.response.headers.get(\"Retry-After\") || 1) * 1000;",
                  "  pm.execution.setNextRequest(pm.info.requestName);",
                  "  setTimeout(() => {}, wait);",
                  "} else {",
                  "  pm.test(\"the job completed\", () => pm.expect(job.status).to.eql(\"completed\"));",
                  "  pm.collectionVariables.set(\"jobResultUrl\", job.resultUrl);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/api/jobs/{{jobId}}"
          }
        },
        {
          "name": "Get the job result",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"the result is available\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}{{jobResultUrl}}"
          }
        }
      ]
    },
    {
      "name": "06 Data-driven",
      "description": "Lesson 6: one request, many rows. Run this folder with products.csv as the data file.",
      "item": [
        {
          "name": "Product by ID, from the data file",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Without a data file there's no id to test, so skip instead of failing the whole run",
                  "if (!pm.iterationData.has(\"id\")) {",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const expectedStatus = Number(pm.iterationData.get(\"expectedStatus\"));",
                  "",
                  "pm.test(`product ${pm.iterationData.get(\"id\")} returns ${expectedStatus}`, () => {",
                  "  pm.response.to.have.status(expectedStatus);",
                  "});",
                  "",
                  "if (expectedStatus === 200) {",
                  "  pm.test(`product ${pm.iterationData.get(\"id\")} is in ${pm.iterationData.get(\"category\")}`, () => {",
                  "    pm.expect(pm.response.json().category).to.eql(pm.iterationData.get(\"category\"));",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/api/Products/{{id}}"
          }
        }
      ]
    }
  ]
}
