From a77bf8f60a240c4ffdb9eb1c8197be087b653547 Mon Sep 17 00:00:00 2001 From: Gbadebo Bello Date: Fri, 13 Mar 2026 14:37:05 +0100 Subject: [PATCH 1/3] Added publisher route and added some tests to the collection --- .postman/resources.yaml | 9 ++++ .../Book API/.resources/definition.yaml | 7 +++ .../Book API/Books/.resources/definition.yaml | 4 ++ .../Book API/Books/Create Book.request.yaml | 44 +++++++++++++++++++ .../Book API/Books/Delete Book.request.yaml | 24 ++++++++++ .../Book API/Books/Get Book.request.yaml | 27 ++++++++++++ .../Book API/Books/List Books.request.yaml | 28 ++++++++++++ .../Book API/Books/Update Book.request.yaml | 36 +++++++++++++++ .../Health/.resources/definition.yaml | 4 ++ .../Book API/Health/Health Check.request.yaml | 24 ++++++++++ .../Book API/Health/Root.request.yaml | 18 ++++++++ postman/globals/workspace.globals.yaml | 2 + src/database/db.js | 7 +-- src/models/Book.js | 11 +++-- src/routes/books.js | 2 +- 15 files changed, 240 insertions(+), 7 deletions(-) create mode 100644 .postman/resources.yaml create mode 100644 postman/collections/Book API/.resources/definition.yaml create mode 100644 postman/collections/Book API/Books/.resources/definition.yaml create mode 100644 postman/collections/Book API/Books/Create Book.request.yaml create mode 100644 postman/collections/Book API/Books/Delete Book.request.yaml create mode 100644 postman/collections/Book API/Books/Get Book.request.yaml create mode 100644 postman/collections/Book API/Books/List Books.request.yaml create mode 100644 postman/collections/Book API/Books/Update Book.request.yaml create mode 100644 postman/collections/Book API/Health/.resources/definition.yaml create mode 100644 postman/collections/Book API/Health/Health Check.request.yaml create mode 100644 postman/collections/Book API/Health/Root.request.yaml create mode 100644 postman/globals/workspace.globals.yaml diff --git a/.postman/resources.yaml b/.postman/resources.yaml new file mode 100644 index 0000000..61a5fd9 --- /dev/null +++ b/.postman/resources.yaml @@ -0,0 +1,9 @@ +# Use this workspace to collaborate +workspace: + id: bc3391dd-f878-43c3-977f-da01f0b63f07 + +# All resources in the `postman/` folder are automatically registered in Local View. +# Point to additional files outside the `postman/` folder to register them individually. Example: +#localResources: +# collections: +# - ../tests/E2E Test Collection/ diff --git a/postman/collections/Book API/.resources/definition.yaml b/postman/collections/Book API/.resources/definition.yaml new file mode 100644 index 0000000..74b5540 --- /dev/null +++ b/postman/collections/Book API/.resources/definition.yaml @@ -0,0 +1,7 @@ +$kind: collection +name: Book API +description: REST API for managing a book collection. Provides endpoints for + CRUD operations on books. +variables: + baseUrl: http://localhost:3000 + bookId: "" diff --git a/postman/collections/Book API/Books/.resources/definition.yaml b/postman/collections/Book API/Books/.resources/definition.yaml new file mode 100644 index 0000000..c043b44 --- /dev/null +++ b/postman/collections/Book API/Books/.resources/definition.yaml @@ -0,0 +1,4 @@ +$kind: collection +name: Books +description: CRUD operations for managing books in the collection +order: 2000 diff --git a/postman/collections/Book API/Books/Create Book.request.yaml b/postman/collections/Book API/Books/Create Book.request.yaml new file mode 100644 index 0000000..17af41b --- /dev/null +++ b/postman/collections/Book API/Books/Create Book.request.yaml @@ -0,0 +1,44 @@ +$kind: http-request +name: Create Book +description: Creates a new book in the collection. Returns 201 with the created book. +method: POST +url: '{{baseUrl}}/api/v1/books' +order: 3000 +headers: + - key: Content-Type + value: application/json +body: + type: json + content: |- + { + "title": "The Great Gatsby", + "author": "F. Scott Fitzgerald", + "year": 1925, + "publisher": "Scribner" + } +scripts: + - type: afterResponse + language: text/javascript + code: |- + pm.test("Status code is 201", function () { + pm.response.to.have.status(201); + }); + + pm.test("Response has book object", function () { + const jsonData = pm.response.json(); + pm.expect(jsonData).to.have.property("book"); + }); + + pm.test("Book has required properties", function () { + const jsonData = pm.response.json(); + pm.expect(jsonData.book).to.have.property("id"); + pm.expect(jsonData.book).to.have.property("title"); + pm.expect(jsonData.book).to.have.property("author"); + }); + + pm.test("Save book id to collection variable", function () { + const jsonData = pm.response.json(); + if (jsonData.book && jsonData.book.id) { + pm.collectionVariables.set("bookId", jsonData.book.id); + } + }); \ No newline at end of file diff --git a/postman/collections/Book API/Books/Delete Book.request.yaml b/postman/collections/Book API/Books/Delete Book.request.yaml new file mode 100644 index 0000000..e7ad08c --- /dev/null +++ b/postman/collections/Book API/Books/Delete Book.request.yaml @@ -0,0 +1,24 @@ +$kind: http-request +name: Delete Book +description: Deletes a book by ID. Returns success message or 404 if not found. +method: DELETE +url: '{{baseUrl}}/api/v1/books/:id' +order: 5000 +pathVariables: + - key: id + value: '' + description: The UUID of the book to delete +scripts: + - type: afterResponse + language: text/javascript + code: |- + pm.test("Status code is 200 or 404", function () { + pm.expect(pm.response.code).to.be.oneOf([200, 404]); + }); + + pm.test("If 200, response has message property", function () { + if (pm.response.code === 200) { + const jsonData = pm.response.json(); + pm.expect(jsonData).to.have.property("message"); + } + }); \ No newline at end of file diff --git a/postman/collections/Book API/Books/Get Book.request.yaml b/postman/collections/Book API/Books/Get Book.request.yaml new file mode 100644 index 0000000..4b52de5 --- /dev/null +++ b/postman/collections/Book API/Books/Get Book.request.yaml @@ -0,0 +1,27 @@ +$kind: http-request +name: Get Book +description: Returns a single book by ID. Returns 404 if book not found. +method: GET +url: '{{baseUrl}}/api/v1/books/:id' +order: 2000 +pathVariables: + - key: id + value: '' + description: The UUID of the book to retrieve +scripts: + - type: afterResponse + language: text/javascript + code: |- + pm.test("Status code is 200 or 404", function () { + pm.expect(pm.response.code).to.be.oneOf([200, 404]); + }); + + pm.test("If 200, response has book object with required properties", function () { + if (pm.response.code === 200) { + const jsonData = pm.response.json(); + pm.expect(jsonData).to.have.property("book"); + pm.expect(jsonData.book).to.have.property("id"); + pm.expect(jsonData.book).to.have.property("title"); + pm.expect(jsonData.book).to.have.property("author"); + } + }); \ No newline at end of file diff --git a/postman/collections/Book API/Books/List Books.request.yaml b/postman/collections/Book API/Books/List Books.request.yaml new file mode 100644 index 0000000..9770ee2 --- /dev/null +++ b/postman/collections/Book API/Books/List Books.request.yaml @@ -0,0 +1,28 @@ +$kind: http-request +name: List Books +description: Returns an array of all books in the collection +method: GET +url: '{{baseUrl}}/api/v1/books' +order: 1000 +scripts: + - type: afterResponse + language: text/javascript + code: |- + pm.test("Status code is 200", function () { + pm.response.to.have.status(200); + }); + + pm.test("Response has books array", function () { + const jsonData = pm.response.json(); + pm.expect(jsonData).to.have.property("books"); + pm.expect(jsonData.books).to.be.an("array"); + }); + + pm.test("Each book has required properties", function () { + const jsonData = pm.response.json(); + jsonData.books.forEach(function(book) { + pm.expect(book).to.have.property("id"); + pm.expect(book).to.have.property("title"); + pm.expect(book).to.have.property("author"); + }); + }); \ No newline at end of file diff --git a/postman/collections/Book API/Books/Update Book.request.yaml b/postman/collections/Book API/Books/Update Book.request.yaml new file mode 100644 index 0000000..73d7fe4 --- /dev/null +++ b/postman/collections/Book API/Books/Update Book.request.yaml @@ -0,0 +1,36 @@ +$kind: http-request +name: Update Book +description: Updates an existing book by ID. Returns the updated book or 404 if not found. +method: PUT +url: '{{baseUrl}}/api/v1/books/:id' +order: 4000 +headers: + - key: Content-Type + value: application/json +pathVariables: + - key: id + value: '' + description: The UUID of the book to update +body: + type: json + content: |- + { + "title": "The Great Gatsby", + "author": "F. Scott Fitzgerald", + "year": 1925, + "publisher": "Scribner" + } +scripts: + - type: afterResponse + language: text/javascript + code: |- + pm.test("Status code is 200 or 404", function () { + pm.expect(pm.response.code).to.be.oneOf([200, 404]); + }); + + pm.test("If 200, response has book object", function () { + if (pm.response.code === 200) { + const jsonData = pm.response.json(); + pm.expect(jsonData).to.have.property("book"); + } + }); \ No newline at end of file diff --git a/postman/collections/Book API/Health/.resources/definition.yaml b/postman/collections/Book API/Health/.resources/definition.yaml new file mode 100644 index 0000000..e3b3db5 --- /dev/null +++ b/postman/collections/Book API/Health/.resources/definition.yaml @@ -0,0 +1,4 @@ +$kind: collection +name: Health +description: Health and status endpoints for the API +order: 1000 diff --git a/postman/collections/Book API/Health/Health Check.request.yaml b/postman/collections/Book API/Health/Health Check.request.yaml new file mode 100644 index 0000000..8bbc238 --- /dev/null +++ b/postman/collections/Book API/Health/Health Check.request.yaml @@ -0,0 +1,24 @@ +$kind: http-request +name: Health Check +description: Returns the health status of the API with current timestamp +method: GET +url: '{{baseUrl}}/health' +order: 2000 +scripts: + - type: afterResponse + language: text/javascript + code: |- + pm.test("Status code is 200", function () { + pm.response.to.have.status(200); + }); + + pm.test("Response has status property equal to ok", function () { + const jsonData = pm.response.json(); + pm.expect(jsonData).to.have.property("status"); + pm.expect(jsonData.status).to.eql("ok"); + }); + + pm.test("Response has timestamp property", function () { + const jsonData = pm.response.json(); + pm.expect(jsonData).to.have.property("timestamp"); + }); \ No newline at end of file diff --git a/postman/collections/Book API/Health/Root.request.yaml b/postman/collections/Book API/Health/Root.request.yaml new file mode 100644 index 0000000..f5175cf --- /dev/null +++ b/postman/collections/Book API/Health/Root.request.yaml @@ -0,0 +1,18 @@ +$kind: http-request +name: Root +description: Returns API info message with welcome information +method: GET +url: '{{baseUrl}}/' +order: 1000 +scripts: + - type: afterResponse + language: text/javascript + code: |- + pm.test("Status code is 200", function () { + pm.response.to.have.status(200); + }); + + pm.test("Response has message property", function () { + const jsonData = pm.response.json(); + pm.expect(jsonData).to.have.property("message"); + }); diff --git a/postman/globals/workspace.globals.yaml b/postman/globals/workspace.globals.yaml new file mode 100644 index 0000000..e96c6d6 --- /dev/null +++ b/postman/globals/workspace.globals.yaml @@ -0,0 +1,2 @@ +name: Globals +values: [] diff --git a/src/database/db.js b/src/database/db.js index 2b72908..dccee84 100644 --- a/src/database/db.js +++ b/src/database/db.js @@ -13,8 +13,8 @@ class Database { _seed() { const sample = [ - new Book('1', 'The Great Gatsby', 'F. Scott Fitzgerald', 1925), - new Book('2', '1984', 'George Orwell', 1949) + new Book('1', 'The Great Gatsby', 'F. Scott Fitzgerald', 1925, 'Scribner'), + new Book('2', '1984', 'George Orwell', 1949, 'Secker & Warburg') ]; sample.forEach(b => this.books.set(b.id, b)); } @@ -29,7 +29,7 @@ class Database { createBook(data) { const id = uuidv4().slice(0, 8); - const book = new Book(id, data.title.trim(), data.author.trim(), data.year ?? null); + const book = new Book(id, data.title.trim(), data.author.trim(), data.year ?? null, data.publisher ? data.publisher.trim() : null); this.books.set(id, book); return book; } @@ -40,6 +40,7 @@ class Database { if (data.title !== undefined) book.title = data.title.trim(); if (data.author !== undefined) book.author = data.author.trim(); if (data.year !== undefined) book.year = data.year; + if (data.publisher !== undefined) book.publisher = data.publisher ? data.publisher.trim() : null; return book; } diff --git a/src/models/Book.js b/src/models/Book.js index 8711403..0f43fdd 100644 --- a/src/models/Book.js +++ b/src/models/Book.js @@ -1,13 +1,14 @@ /** - * Book model – title, author, optional year + * Book model – title, author, optional year, optional publisher */ class Book { - constructor(id, title, author, year = null) { + constructor(id, title, author, year = null, publisher = null) { this.id = id; this.title = title; this.author = author; this.year = year; + this.publisher = publisher; } static validate(data) { @@ -20,6 +21,9 @@ class Book { if (data.year != null && (typeof data.year !== 'number' || data.year < 0 || !Number.isInteger(data.year))) { return { isValid: false, error: 'Year must be a non-negative integer' }; } + if (data.publisher != null && (typeof data.publisher !== 'string' || !data.publisher.trim())) { + return { isValid: false, error: 'Publisher must be a non-empty string if provided' }; + } return { isValid: true }; } @@ -28,7 +32,8 @@ class Book { id: this.id, title: this.title, author: this.author, - year: this.year + year: this.year, + publisher: this.publisher }; } } diff --git a/src/routes/books.js b/src/routes/books.js index 102744e..16fb234 100644 --- a/src/routes/books.js +++ b/src/routes/books.js @@ -39,7 +39,7 @@ router.put('/:id', (req, res) => { return res.status(404).json({ error: { name: 'notFound', message: 'Book not found' } }); } const updates = req.body; - const merged = { title: updates.title ?? book.title, author: updates.author ?? book.author, year: updates.year !== undefined ? updates.year : book.year }; + const merged = { title: updates.title ?? book.title, author: updates.author ?? book.author, year: updates.year !== undefined ? updates.year : book.year, publisher: updates.publisher !== undefined ? updates.publisher : book.publisher }; const validation = Book.validate(merged); if (!validation.isValid) { return res.status(400).json({ error: { name: 'validationError', message: validation.error } }); From 239de453398e6f70ed6fb9e40b4918bc2b7b23a0 Mon Sep 17 00:00:00 2001 From: Gbadebo Bello Date: Fri, 13 Mar 2026 16:20:41 +0100 Subject: [PATCH 2/3] commit --- .../Book API/Books/Create Book.request.yaml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/postman/collections/Book API/Books/Create Book.request.yaml b/postman/collections/Book API/Books/Create Book.request.yaml index 17af41b..5328a20 100644 --- a/postman/collections/Book API/Books/Create Book.request.yaml +++ b/postman/collections/Book API/Books/Create Book.request.yaml @@ -2,11 +2,10 @@ $kind: http-request name: Create Book description: Creates a new book in the collection. Returns 201 with the created book. method: POST -url: '{{baseUrl}}/api/v1/books' +url: "{{baseUrl}}/api/v1/books" order: 3000 headers: - - key: Content-Type - value: application/json + Content-Type: application/json body: type: json content: |- @@ -18,7 +17,6 @@ body: } scripts: - type: afterResponse - language: text/javascript code: |- pm.test("Status code is 201", function () { pm.response.to.have.status(201); @@ -36,9 +34,15 @@ scripts: pm.expect(jsonData.book).to.have.property("author"); }); + pm.test("Book has publisher property", function () { + const jsonData = pm.response.json(); + pm.expect(jsonData.book).to.have.property("publisher"); + }); + pm.test("Save book id to collection variable", function () { const jsonData = pm.response.json(); if (jsonData.book && jsonData.book.id) { pm.collectionVariables.set("bookId", jsonData.book.id); } - }); \ No newline at end of file + }); + language: text/javascript From 474fd4b7a4dae941d3921cdeb581e815cefb5893 Mon Sep 17 00:00:00 2001 From: Gbadebo Bello Date: Fri, 13 Mar 2026 16:46:39 +0100 Subject: [PATCH 3/3] fixed the collection name --- .github/workflows/postman.yaml | 2 +- .postman/resources.yaml | 10 +++------- .../{Book API => Book-API}/.resources/definition.yaml | 0 .../Books/.resources/definition.yaml | 0 .../Books/Create Book.request.yaml | 0 .../Books/Delete Book.request.yaml | 0 .../{Book API => Book-API}/Books/Get Book.request.yaml | 0 .../Books/List Books.request.yaml | 0 .../Books/Update Book.request.yaml | 0 .../Health/.resources/definition.yaml | 0 .../Health/Health Check.request.yaml | 0 .../{Book API => Book-API}/Health/Root.request.yaml | 0 12 files changed, 4 insertions(+), 8 deletions(-) rename postman/collections/{Book API => Book-API}/.resources/definition.yaml (100%) rename postman/collections/{Book API => Book-API}/Books/.resources/definition.yaml (100%) rename postman/collections/{Book API => Book-API}/Books/Create Book.request.yaml (100%) rename postman/collections/{Book API => Book-API}/Books/Delete Book.request.yaml (100%) rename postman/collections/{Book API => Book-API}/Books/Get Book.request.yaml (100%) rename postman/collections/{Book API => Book-API}/Books/List Books.request.yaml (100%) rename postman/collections/{Book API => Book-API}/Books/Update Book.request.yaml (100%) rename postman/collections/{Book API => Book-API}/Health/.resources/definition.yaml (100%) rename postman/collections/{Book API => Book-API}/Health/Health Check.request.yaml (100%) rename postman/collections/{Book API => Book-API}/Health/Root.request.yaml (100%) diff --git a/.github/workflows/postman.yaml b/.github/workflows/postman.yaml index 697f032..b29b2d6 100644 --- a/.github/workflows/postman.yaml +++ b/.github/workflows/postman.yaml @@ -43,7 +43,7 @@ jobs: POSTMAN_API_KEY: ${{ secrets.POSTMAN_API_KEY }} run: | postman login --with-api-key "$POSTMAN_API_KEY" - postman collection run ./postman/collections/Book-API + postman collection run postman/collections/Book-API - name: Push to Postman Cloud if: github.event_name == 'push' diff --git a/.postman/resources.yaml b/.postman/resources.yaml index 61a5fd9..57543fd 100644 --- a/.postman/resources.yaml +++ b/.postman/resources.yaml @@ -1,9 +1,5 @@ -# Use this workspace to collaborate workspace: id: bc3391dd-f878-43c3-977f-da01f0b63f07 - -# All resources in the `postman/` folder are automatically registered in Local View. -# Point to additional files outside the `postman/` folder to register them individually. Example: -#localResources: -# collections: -# - ../tests/E2E Test Collection/ +cloudResources: + collections: + ../postman/collections/Book API: 21505573-9b973608-fff1-41d9-a176-67845f911fd0 diff --git a/postman/collections/Book API/.resources/definition.yaml b/postman/collections/Book-API/.resources/definition.yaml similarity index 100% rename from postman/collections/Book API/.resources/definition.yaml rename to postman/collections/Book-API/.resources/definition.yaml diff --git a/postman/collections/Book API/Books/.resources/definition.yaml b/postman/collections/Book-API/Books/.resources/definition.yaml similarity index 100% rename from postman/collections/Book API/Books/.resources/definition.yaml rename to postman/collections/Book-API/Books/.resources/definition.yaml diff --git a/postman/collections/Book API/Books/Create Book.request.yaml b/postman/collections/Book-API/Books/Create Book.request.yaml similarity index 100% rename from postman/collections/Book API/Books/Create Book.request.yaml rename to postman/collections/Book-API/Books/Create Book.request.yaml diff --git a/postman/collections/Book API/Books/Delete Book.request.yaml b/postman/collections/Book-API/Books/Delete Book.request.yaml similarity index 100% rename from postman/collections/Book API/Books/Delete Book.request.yaml rename to postman/collections/Book-API/Books/Delete Book.request.yaml diff --git a/postman/collections/Book API/Books/Get Book.request.yaml b/postman/collections/Book-API/Books/Get Book.request.yaml similarity index 100% rename from postman/collections/Book API/Books/Get Book.request.yaml rename to postman/collections/Book-API/Books/Get Book.request.yaml diff --git a/postman/collections/Book API/Books/List Books.request.yaml b/postman/collections/Book-API/Books/List Books.request.yaml similarity index 100% rename from postman/collections/Book API/Books/List Books.request.yaml rename to postman/collections/Book-API/Books/List Books.request.yaml diff --git a/postman/collections/Book API/Books/Update Book.request.yaml b/postman/collections/Book-API/Books/Update Book.request.yaml similarity index 100% rename from postman/collections/Book API/Books/Update Book.request.yaml rename to postman/collections/Book-API/Books/Update Book.request.yaml diff --git a/postman/collections/Book API/Health/.resources/definition.yaml b/postman/collections/Book-API/Health/.resources/definition.yaml similarity index 100% rename from postman/collections/Book API/Health/.resources/definition.yaml rename to postman/collections/Book-API/Health/.resources/definition.yaml diff --git a/postman/collections/Book API/Health/Health Check.request.yaml b/postman/collections/Book-API/Health/Health Check.request.yaml similarity index 100% rename from postman/collections/Book API/Health/Health Check.request.yaml rename to postman/collections/Book-API/Health/Health Check.request.yaml diff --git a/postman/collections/Book API/Health/Root.request.yaml b/postman/collections/Book-API/Health/Root.request.yaml similarity index 100% rename from postman/collections/Book API/Health/Root.request.yaml rename to postman/collections/Book-API/Health/Root.request.yaml