diff --git a/.github/workflows/postman_tests.yml b/.github/workflows/postman_tests.yml new file mode 100644 index 0000000..9a027b3 --- /dev/null +++ b/.github/workflows/postman_tests.yml @@ -0,0 +1,83 @@ +name: API Postman Tests + +on: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + newman: + runs-on: ubuntu-latest + services: + mongo: + image: mongo:7 + ports: + - 27017:27017 + options: >- + --health-cmd="mongosh --eval \"db.adminCommand({ ping: 1 })\"" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Start backend server + env: + S_PORT: 3100 + MONGODB_URI: mongodb://127.0.0.1:27017/foodstoragemanager_test + run: | + set -eo pipefail + npm --workspace=server run dev >/tmp/server.log 2>&1 & + echo $! > /tmp/server.pid + + started=0 + for i in {1..30}; do + if curl -sSf http://localhost:3100/health >/dev/null; then + echo "Server is up"; + started=1 + break + fi + + if ! kill -0 $(cat /tmp/server.pid) 2>/dev/null; then + echo "Server process died; dumping log"; + cat /tmp/server.log + exit 1 + fi + + sleep 2 + done + + if [ "$started" -ne 1 ]; then + echo "Server did not become healthy in time; dumping log" + cat /tmp/server.log + exit 1 + fi + + - name: Run Postman (Newman) collection + run: | + npx --yes newman run server/postman/backend.postman_collection.json \ + -e server/postman/local.postman_environment.json \ + --env-var baseUrl=http://localhost:3100 + + - name: Show server logs + if: always() + run: cat /tmp/server.log + + - name: Stop server + if: always() + run: | + if [ -f /tmp/server.pid ]; then + kill $(cat /tmp/server.pid) 2>/dev/null || true + fi diff --git a/server/postman/backend.postman_collection.json b/server/postman/backend.postman_collection.json new file mode 100644 index 0000000..f7d5b7c --- /dev/null +++ b/server/postman/backend.postman_collection.json @@ -0,0 +1,532 @@ +{ + "info": { + "_postman_id": "50ae6559-7993-4de2-9353-f0903e2e8670", + "name": "FoodStorageManager Backend", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "if (!pm.collectionVariables.get('uniqueSuffix')) {", + " const hex = '0123456789abcdef';", + " const randomObjectId = () => Array.from({ length: 24 }, () => hex[Math.floor(Math.random() * hex.length)]).join('');", + " const suffix = Date.now();", + " pm.collectionVariables.set('uniqueSuffix', suffix);", + " pm.collectionVariables.set('fakeLocationId', randomObjectId());", + " pm.collectionVariables.set('itemName', `Automated Item ${suffix}`);", + " pm.collectionVariables.set('updatedItemName', `Updated Item ${suffix}`);", + " pm.collectionVariables.set('userEmail', `api-user-${suffix}@example.com`);", + " pm.collectionVariables.set('userPassword', `P@ss-${suffix}`);", + " pm.collectionVariables.set('userName', `API User ${suffix}`);", + " pm.collectionVariables.set('updatedUserName', `API User ${suffix} Updated`);", + "}", + "" + ] + } + } + ], + "item": [ + { + "name": "Health check", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/health", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "health" + ] + } + }, + "response": [], + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('returns 200 OK', () => pm.response.to.have.status(200));", + "const body = pm.response.json();", + "pm.test('reports healthy', () => pm.expect(body).to.have.property('healthy', true));" + ] + } + } + ] + }, + { + "name": "Items", + "item": [ + { + "name": "Create item", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"item\": {\n \"name\": \"{{itemName}}\",\n \"locationId\": \"{{fakeLocationId}}\",\n \"unit\": \"ea\",\n \"note\": \"Created by Newman collection\"\n }\n}" + }, + "url": { + "raw": "{{baseUrl}}/items", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items" + ] + } + }, + "response": [], + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('created item returns 201', () => pm.response.to.have.status(201));", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('item');", + "pm.collectionVariables.set('itemId', json.item._id);", + "pm.test('item name echoed back', () => pm.expect(json.item.name).to.eql(pm.collectionVariables.get('itemName')));", + "pm.test('item has location id', () => pm.expect(json.item.locationId).to.be.a('string'));" + ] + } + } + ] + }, + { + "name": "Update item", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"item\": {\n \"name\": \"{{updatedItemName}}\",\n \"tags\": [\"automated\", \"postman\"],\n \"note\": \"Updated by Newman collection\"\n }\n}" + }, + "url": { + "raw": "{{baseUrl}}/items/{{itemId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items", + "{{itemId}}" + ] + } + }, + "response": [], + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('update returns 200', () => pm.response.to.have.status(200));", + "const json = pm.response.json();", + "pm.test('same id returned', () => pm.expect(json.item._id).to.eql(pm.collectionVariables.get('itemId')));", + "pm.test('name updated', () => pm.expect(json.item.name).to.eql(pm.collectionVariables.get('updatedItemName')));", + "pm.test('tags applied', () => pm.expect(json.item.tags).to.include('postman'));" + ] + } + } + ] + }, + { + "name": "List items includes created entry", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/items", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items" + ] + } + }, + "response": [], + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('list returns 200', () => pm.response.to.have.status(200));", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('items');", + "const found = json.items.find((item) => item._id === pm.collectionVariables.get('itemId'));", + "pm.test('created item present', () => pm.expect(found).to.exist);" + ] + } + } + ] + }, + { + "name": "Delete item", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{baseUrl}}/items/{{itemId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items", + "{{itemId}}" + ] + } + }, + "response": [], + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('delete returns 200', () => pm.response.to.have.status(200));", + "const json = pm.response.json();", + "pm.test('deleted item matches id', () => pm.expect(json.item._id).to.eql(pm.collectionVariables.get('itemId')));" + ] + } + } + ] + }, + { + "name": "Confirm item cleanup", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/items", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items" + ] + } + }, + "response": [], + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "const status = pm.response.code;", + "const id = pm.collectionVariables.get('itemId');", + "if (status === 204) {", + " pm.test('no items left after delete', () => pm.expect(status).to.eql(204));", + "} else {", + " pm.test('cleanup list returns 200', () => pm.expect(status).to.eql(200));", + " const json = pm.response.json();", + " const exists = Array.isArray(json.items) && json.items.some((item) => item._id === id);", + " pm.test('created item removed', () => pm.expect(exists).to.be.false);", + "}" + ] + } + } + ] + } + ] + }, + { + "name": "Users", + "item": [ + { + "name": "Create user", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"user\": {\n \"email\": \"{{userEmail}}\",\n \"name\": \"{{userName}}\",\n \"password\": \"{{userPassword}}\",\n \"role\": \"staff\",\n \"enabled\": true\n }\n}" + }, + "url": { + "raw": "{{baseUrl}}/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users" + ] + } + }, + "response": [], + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('created user returns 201', () => pm.response.to.have.status(201));", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('user');", + "pm.collectionVariables.set('userId', json.user._id);", + "pm.test('user email echoed back', () => pm.expect(json.user.email).to.eql(pm.collectionVariables.get('userEmail')));" + ] + } + } + ] + }, + { + "name": "List users includes created entry", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users" + ] + } + }, + "response": [], + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('list users returns 200', () => pm.response.to.have.status(200));", + "const json = pm.response.json();", + "const found = json.users.find((user) => user._id === pm.collectionVariables.get('userId'));", + "pm.test('created user present', () => pm.expect(found).to.exist);" + ] + } + } + ] + }, + { + "name": "Login succeeds", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"{{userEmail}}\",\n \"password\": \"{{userPassword}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/auth/login", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [], + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('login returns 200', () => pm.response.to.have.status(200));", + "const json = pm.response.json();", + "pm.test('user id matches created user', () => pm.expect(json.user._id).to.eql(pm.collectionVariables.get('userId')));", + "pm.test('user is enabled', () => pm.expect(json.user.enabled).to.be.true);" + ] + } + } + ] + }, + { + "name": "Update user", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"user\": {\n \"name\": \"{{updatedUserName}}\",\n \"role\": \"admin\",\n \"enabled\": true\n }\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}" + ] + } + }, + "response": [], + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('update user returns 200', () => pm.response.to.have.status(200));", + "const json = pm.response.json();", + "pm.test('user id unchanged', () => pm.expect(json.user._id).to.eql(pm.collectionVariables.get('userId')));", + "pm.test('name updated', () => pm.expect(json.user.name).to.eql(pm.collectionVariables.get('updatedUserName')));", + "pm.test('role is admin', () => pm.expect(json.user.role).to.include('admin'));" + ] + } + } + ] + }, + { + "name": "Delete user", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{baseUrl}}/users/{{userId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}" + ] + } + }, + "response": [], + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('delete user returns 200', () => pm.response.to.have.status(200));", + "const json = pm.response.json();", + "pm.test('deleted flag true', () => pm.expect(json.deleted).to.be.true);" + ] + } + } + ] + }, + { + "name": "Confirm user cleanup", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users" + ] + } + }, + "response": [], + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "const json = pm.response.json();", + "const id = pm.collectionVariables.get('userId');", + "const exists = Array.isArray(json.users) && json.users.some((user) => user._id === id);", + "pm.test('created user removed', () => pm.expect(exists).to.be.false);" + ] + } + } + ] + }, + { + "name": "Login fails after deletion", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"{{userEmail}}\",\n \"password\": \"{{userPassword}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/auth/login", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [], + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('login after delete is unauthorized', () => pm.response.to.have.status(401));", + "const json = pm.response.json();", + "pm.test('error message present', () => pm.expect(json.error.message).to.match(/invalid email or password/i));" + ] + } + } + ] + } + ] + } + ], + "variable": [ + { + "key": "baseUrl", + "value": "http://localhost:3000" + } + ] +} diff --git a/server/postman/items.postman_collection.json b/server/postman/items.postman_collection.json deleted file mode 100644 index 3d06a47..0000000 --- a/server/postman/items.postman_collection.json +++ /dev/null @@ -1,405 +0,0 @@ -{ - "info": { - "_postman_id": "98c142ab-8960-4b1b-9796-4b2d4e6d4c6f", - "name": "Inventory Items", - "description": "Regression tests for inventory item routes. Configure {{baseUrl}} to match the API origin. For create-item happy path tests, set {{locationId}} to an existing Mongo ObjectId tied to a Location document.", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "item": [ - { - "name": "GET /items", - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/items", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "items" - ] - } - }, - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test('responds with HTTP 200', function () {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.test('content-type is JSON', function () {", - " pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json');", - "});", - "", - "pm.test('body is valid JSON object', function () {", - " const body = pm.response.json();", - " pm.expect(body).to.be.an('object');", - "});", - "", - "const payload = pm.response.json();", - "", - "pm.test('payload contains data.items array', function () {", - " pm.expect(payload).to.have.property('data');", - " pm.expect(payload.data).to.have.property('items');", - " pm.expect(payload.data.items).to.be.an('array');", - "});", - "", - "pm.test('items have expected shape', function () {", - " const items = payload.data.items;", - " items.forEach((item, index) => {", - " pm.expect(item, `item at index ${index}`).to.be.an('object');", - " pm.expect(item).to.have.property('_id').that.is.a('string').and.not.empty;", - " pm.expect(item).to.have.property('name').that.is.a('string').and.not.empty;", - " pm.expect(item).to.have.property('locationId').that.is.a('string').and.not.empty;", - " if (Object.prototype.hasOwnProperty.call(item, 'expiresAt') && item.expiresAt !== undefined) {", - " pm.expect(item.expiresAt).to.satisfy((value) => {", - " if (value === null) {", - " return true;", - " }", - " const parsed = Date.parse(value);", - " return !Number.isNaN(parsed);", - " }, 'expiresAt must be an ISO date string or null');", - " }", - " });", - "});", - "", - "pm.test('server does not return HTML', function () {", - " pm.expect(pm.response.text()).to.not.match(/^/i);", - "});" - ], - "type": "text/javascript" - } - } - ] - }, - { - "name": "GET /items with invalid locationId", - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/items?locationId=not-a-valid-object-id", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "items" - ], - "query": [ - { - "key": "locationId", - "value": "not-a-valid-object-id" - } - ] - } - }, - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test('responds with HTTP 400', function () {", - " pm.response.to.have.status(400);", - "});", - "", - "pm.test('content-type is JSON', function () {", - " pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json');", - "});", - "", - "const payload = pm.response.json();", - "", - "pm.test('error payload matches schema', function () {", - " pm.expect(payload).to.have.property('error');", - " pm.expect(payload.error).to.have.property('message').that.is.a('string').and.not.empty;", - " pm.expect(payload.error).to.have.property('issues');", - "});", - "", - "pm.test('issues include locationId hint', function () {", - " pm.expect(payload.error.issues).to.have.property('locationId');", - "});", - "", - "pm.test('server does not return HTML', function () {", - " pm.expect(pm.response.text()).to.not.match(/^/i);", - "});" - ], - "type": "text/javascript" - } - } - ] - }, - { - "name": "POST /items - missing body", - "request": { - "method": "POST", - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{}" - }, - "url": { - "raw": "{{baseUrl}}/items", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "items" - ] - } - }, - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test('responds with HTTP 400 when body missing', function () {", - " pm.response.to.have.status(400);", - "});", - "", - "pm.test('content-type is JSON', function () {", - " pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json');", - "});", - "", - "const payload = pm.response.json();", - "", - "pm.test('error payload includes message', function () {", - " pm.expect(payload).to.have.property('error');", - " pm.expect(payload.error).to.have.property('message').that.is.a('string').and.not.empty;", - "});", - "", - "pm.test('issues object is present when available', function () {", - " if (Object.prototype.hasOwnProperty.call(payload.error, 'issues')) {", - " pm.expect(payload.error.issues).to.be.an('object');", - " }", - "});", - "", - "pm.test('server does not return HTML', function () {", - " pm.expect(pm.response.text()).to.not.match(/^/i);", - "});" - ], - "type": "text/javascript" - } - } - ] - }, - { - "name": "POST /items - invalid locationId", - "request": { - "method": "POST", - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"item\": {\n \"name\": \"Invalid Location Item\",\n \"locationId\": \"not-a-valid-object-id\"\n }\n}" - }, - "url": { - "raw": "{{baseUrl}}/items", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "items" - ] - } - }, - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test('responds with HTTP 400 when locationId invalid', function () {", - " pm.response.to.have.status(400);", - "});", - "", - "pm.test('content-type is JSON', function () {", - " pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json');", - "});", - "", - "const payload = pm.response.json();", - "", - "pm.test('error payload includes message and issues when present', function () {", - " pm.expect(payload).to.have.property('error');", - " pm.expect(payload.error).to.have.property('message').that.is.a('string').and.not.empty;", - " if (Object.prototype.hasOwnProperty.call(payload.error, 'issues')) {", - " pm.expect(payload.error.issues).to.be.an('object');", - " }", - "});", - "", - "pm.test('server does not return HTML', function () {", - " pm.expect(pm.response.text()).to.not.match(/^/i);", - "});" - ], - "type": "text/javascript" - } - } - ] - }, - { - "name": "POST /items - create item (requires locationId)", - "request": { - "method": "POST", - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"item\": {\n \"name\": \"Sample Inventory Item\",\n \"locationId\": \"{{locationId}}\",\n \"unit\": \"ea\",\n \"caseSize\": 6\n }\n}" - }, - "url": { - "raw": "{{baseUrl}}/items", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "items" - ] - } - }, - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "const locationId = pm.variables.get('locationId');", - "", - "if (!locationId || !locationId.trim()) {", - " console.warn('Skipping create-item assertions: set collection/environment variable \"locationId\" to a valid Location ObjectId.');", - " pm.test('skip create-item when locationId not configured', function () {", - " pm.expect(true).to.be.true;", - " });", - " return;", - "}", - "", - "pm.test('responds with HTTP 201 when item is created', function () {", - " pm.response.to.have.status(201);", - "});", - "", - "pm.test('content-type is JSON', function () {", - " pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json');", - "});", - "", - "const payload = pm.response.json();", - "", - "pm.test('response includes created item with expected fields', function () {", - " pm.expect(payload).to.have.property('data');", - " pm.expect(payload.data).to.have.property('item');", - " const item = payload.data.item;", - " pm.expect(item).to.have.property('_id').that.is.a('string').and.not.empty;", - " pm.expect(item).to.have.property('name', 'Sample Inventory Item');", - " pm.expect(item).to.have.property('locationId', locationId);", - "});", - "", - "pm.test('server does not return HTML', function () {", - " pm.expect(pm.response.text()).to.not.match(/^/i);", - "});" - ], - "type": "text/javascript" - } - } - ] - }, - { - "name": "POST /item alias - invalid payload", - "request": { - "method": "POST", - "header": [ - { - "key": "Accept", - "value": "application/json" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"item\": {\n \"name\": \"Alias Route\",\n \"locationId\": \"\"\n }\n}" - }, - "url": { - "raw": "{{baseUrl}}/item", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "item" - ] - } - }, - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test('alias route still validates input', function () {", - " pm.response.to.have.status(400);", - "});", - "", - "pm.test('content-type is JSON', function () {", - " pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json');", - "});", - "", - "const payload = pm.response.json();", - "", - "pm.test('error payload surfaced for alias route', function () {", - " pm.expect(payload).to.have.property('error');", - " pm.expect(payload.error).to.have.property('message').that.is.a('string').and.not.empty;", - "});", - "", - "pm.test('server does not return HTML', function () {", - " pm.expect(pm.response.text()).to.not.match(/^/i);", - "});" - ], - "type": "text/javascript" - } - } - ] - } - ], - "variable": [ - { - "key": "baseUrl", - "value": "http://localhost:3000" - }, - { - "key": "locationId", - "value": "" - } - ] -} diff --git a/server/postman/local.postman_environment.json b/server/postman/local.postman_environment.json new file mode 100644 index 0000000..e1a5c2c --- /dev/null +++ b/server/postman/local.postman_environment.json @@ -0,0 +1,15 @@ +{ + "id": "04e3f48a-d10d-4c7c-bcf2-4ae1da2d6a5e", + "name": "Local FoodStorageManager", + "values": [ + { + "key": "baseUrl", + "value": "http://localhost:3000", + "type": "default", + "enabled": true + } + ], + "_postman_variable_scope": "environment", + "_postman_exported_at": "2025-12-01T20:00:44-05:00", + "_postman_exported_using": "Postman" +}