{
  "info": {
    "name": "PracticeQAAutomation Practice API",
    "description": "Run the practice server first: node practiceqa-api.mjs (add --buggy for the 8 hidden bugs).\nGuide: https://www.practiceqaautomation.com/api-practice\n\nRun the whole collection in order. Requests tagged [B1]\u2026[B8] fail when a bug is switched on.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "http://localhost:4000"
    },
    {
      "key": "token",
      "value": ""
    },
    {
      "key": "adminToken",
      "value": ""
    },
    {
      "key": "orderId",
      "value": ""
    }
  ],
  "item": [
    {
      "name": "0 \u00b7 Setup",
      "item": [
        {
          "name": "Reset data",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/reset",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "reset"
              ]
            },
            "auth": {
              "type": "noauth"
            }
          }
        }
      ]
    },
    {
      "name": "1 \u00b7 Auth",
      "item": [
        {
          "name": "Log in as ravi (saves token)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test(\"returns a Bearer token\", () => { pm.expect(body.token).to.be.a(\"string\").and.not.empty; pm.expect(body.tokenType).to.eql(\"Bearer\"); });",
                  "pm.collectionVariables.set(\"token\", body.token);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/auth/login",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "auth",
                "login"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"username\": \"ravi\",\n  \"password\": \"Ravi@123\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "Log in as admin (saves adminToken)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.response.to.have.status(200));",
                  "pm.collectionVariables.set(\"adminToken\", pm.response.json().token);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/auth/login",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "auth",
                "login"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"username\": \"admin\",\n  \"password\": \"Admin@123\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "Who am I",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"is ravi\", () => pm.expect(pm.response.json().username).to.eql(\"ravi\"));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/auth/me",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "auth",
                "me"
              ]
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{token}}",
                  "type": "string"
                }
              ]
            }
          }
        },
        {
          "name": "No token \u2192 401",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 401\", () => pm.response.to.have.status(401));",
                  "pm.test(\"asks for a Bearer token\", () => pm.expect(pm.response.headers.get(\"WWW-Authenticate\")).to.include(\"Bearer\"));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/auth/me",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "auth",
                "me"
              ]
            },
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "Wrong password \u2192 401",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 401\", () => pm.response.to.have.status(401));",
                  "pm.test(\"error code\", () => pm.expect(pm.response.json().error.code).to.eql(\"INVALID_CREDENTIALS\"));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/auth/login",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "auth",
                "login"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"username\": \"ravi\",\n  \"password\": \"wrong\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          }
        }
      ]
    },
    {
      "name": "2 \u00b7 Products",
      "item": [
        {
          "name": "List products",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.response.to.have.status(200));",
                  "const b = pm.response.json();",
                  "pm.test(\"page 1 of 24 products\", () => { pm.expect(b.page).to.eql(1); pm.expect(b.total).to.eql(24); pm.expect(b.data).to.have.length(10); });",
                  "pm.test(\"X-Total-Count matches\", () => pm.expect(pm.response.headers.get(\"X-Total-Count\")).to.eql(String(b.total)));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/products",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "products"
              ]
            },
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "Search, any case",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"finds the mouse\", () => pm.expect(pm.response.json().data.map(p => p.name)).to.eql([\"Wireless Mouse\"]));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/products?search=MOUSE",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "products"
              ],
              "query": [
                {
                  "key": "search",
                  "value": "MOUSE"
                }
              ]
            },
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "[B8] Sort by price, highest first",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.response.to.have.status(200));",
                  "const prices = pm.response.json().data.map(p => p.price);",
                  "pm.test(\"prices are descending\", () => pm.expect(prices).to.eql([...prices].sort((a, b) => b - a)));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/products?sort=-price&limit=100",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "products"
              ],
              "query": [
                {
                  "key": "sort",
                  "value": "-price"
                },
                {
                  "key": "limit",
                  "value": "100"
                }
              ]
            },
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "[B3] Page 2 starts after page 1",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"page 2 starts with the 6th product by name (Cotton T-shirt)\", () => pm.expect(pm.response.json().data[0].name).to.eql(\"Cotton T-shirt\"));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/products?page=2&limit=5",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "products"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "2"
                },
                {
                  "key": "limit",
                  "value": "5"
                }
              ]
            },
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "Get product 1 (ETag)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"has an ETag\", () => pm.expect(pm.response.headers.get(\"ETag\")).to.eql('\"v1\"'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/products/1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "products",
                "1"
              ]
            },
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "Unknown product \u2192 404",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 404\", () => pm.response.to.have.status(404));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/products/999",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "products",
                "999"
              ]
            },
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "[B1] Create product (admin) \u2192 201",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 201\", () => pm.response.to.have.status(201));",
                  "pm.test(\"Location points to the new product\", () => pm.expect(pm.response.headers.get(\"Location\")).to.match(/^\\/api\\/products\\/\\d+$/));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/products",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "products"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"sku\": \"EL-150\",\n  \"name\": \"Smart Watch\",\n  \"category\": \"electronics\",\n  \"price\": 4999,\n  \"stock\": 5\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{adminToken}}",
                  "type": "string"
                }
              ]
            }
          }
        },
        {
          "name": "[B2] Zero price \u2192 422",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 422\", () => pm.response.to.have.status(422));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/products",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "products"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"sku\": \"EL-151\",\n  \"name\": \"Freebie\",\n  \"category\": \"electronics\",\n  \"price\": 0\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{adminToken}}",
                  "type": "string"
                }
              ]
            }
          }
        },
        {
          "name": "Bad fields \u2192 422 with details",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 422\", () => pm.response.to.have.status(422));",
                  "pm.test(\"lists every bad field\", () => pm.expect(pm.response.json().error.details.map(d => d.field).sort()).to.eql([\"category\", \"name\", \"price\", \"sku\"]));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/products",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "products"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"sku\": \"bad\",\n  \"name\": \"x\",\n  \"category\": \"toys\",\n  \"price\": \"free\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{adminToken}}",
                  "type": "string"
                }
              ]
            }
          }
        },
        {
          "name": "Customer cannot create \u2192 403",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 403\", () => pm.response.to.have.status(403));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/products",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "products"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"sku\": \"EL-152\",\n  \"name\": \"Nope\",\n  \"category\": \"electronics\",\n  \"price\": 10\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{token}}",
                  "type": "string"
                }
              ]
            }
          }
        }
      ]
    },
    {
      "name": "3 \u00b7 Orders",
      "item": [
        {
          "name": "[B6] Place an order (ravi)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 201\", () => pm.response.to.have.status(201));",
                  "const o = pm.response.json();",
                  "pm.test(\"total = 3 \u00d7 799\", () => pm.expect(o.total).to.eql(2397));",
                  "pm.collectionVariables.set(\"orderId\", o.id);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"items\": [\n    {\n      \"productId\": 1,\n      \"quantity\": 3\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{token}}",
                  "type": "string"
                }
              ]
            }
          }
        },
        {
          "name": "[B5] Another customer's order \u2192 403",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 403\", () => pm.response.to.have.status(403));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/orders/2",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                "2"
              ]
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{token}}",
                  "type": "string"
                }
              ]
            }
          }
        },
        {
          "name": "Cancel my order",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"is cancelled\", () => pm.expect(pm.response.json().status).to.eql(\"CANCELLED\"));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/orders/{{orderId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                "{{orderId}}",
                "cancel"
              ]
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{token}}",
                  "type": "string"
                }
              ]
            }
          }
        },
        {
          "name": "Out of stock \u2192 409",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 409\", () => pm.response.to.have.status(409));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"items\": [\n    {\n      \"productId\": 2,\n      \"quantity\": 1\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{token}}",
                  "type": "string"
                }
              ]
            }
          }
        }
      ]
    },
    {
      "name": "4 \u00b7 Users",
      "item": [
        {
          "name": "[B4] List users (admin) \u2014 no passwords",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"status is 200\", () => pm.response.to.have.status(200));",
                  "pm.test(\"no user has a password field\", () => pm.response.json().data.forEach(u => pm.expect(u).to.not.have.property(\"password\")));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/users",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "users"
              ]
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{adminToken}}",
                  "type": "string"
                }
              ]
            }
          }
        }
      ]
    }
  ]
}
