diff --git a/package-lock.json b/package-lock.json index f74d3fb1..7bd1e2aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "express": "5.1.0" }, "devDependencies": { - "@pact-foundation/pact": "16.0.0", + "@pact-foundation/pact": "^16.0.0", "dotenv": "17.2.3", "eslint": "9.37.0", "jest": "30.2.0" @@ -1204,6 +1204,7 @@ "resolved": "https://registry.npmjs.org/@pact-foundation/pact/-/pact-16.0.0.tgz", "integrity": "sha512-QPjvu40M0vX5L6boUIPZt8JGsA+eL6G48XTtDAUGKA9wlgIjShaVF4bdkDGZmex3WLb1aht4P5BG+DGguR7NZQ==", "dev": true, + "license": "MIT", "dependencies": { "@pact-foundation/pact-core": "^17.0.0", "axios": "^1.12.2", @@ -1234,6 +1235,7 @@ "arm64" ], "dev": true, + "license": "MIT", "os": [ "darwin", "linux", @@ -1268,6 +1270,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1281,6 +1284,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1294,6 +1298,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1307,6 +1312,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1320,6 +1326,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1333,6 +1340,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1346,6 +1354,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -1900,6 +1909,7 @@ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.0.0" } @@ -2212,7 +2222,8 @@ "version": "11.2.3", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", "integrity": "sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ci-info": { "version": "4.3.1", @@ -2345,7 +2356,8 @@ "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -2437,6 +2449,7 @@ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -2508,6 +2521,7 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=8" } @@ -2603,6 +2617,7 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "dev": true, + "license": "MIT", "dependencies": { "once": "^1.4.0" } @@ -2958,7 +2973,8 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -2982,7 +2998,8 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fb-watchman": { "version": "2.0.2", @@ -3418,7 +3435,8 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/html-escaper": { "version": "2.0.2", @@ -4325,6 +4343,7 @@ "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } @@ -4601,6 +4620,7 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4653,6 +4673,7 @@ "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "dev": true, + "license": "MIT", "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -4716,6 +4737,7 @@ "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -4935,6 +4957,7 @@ "resolved": "https://registry.npmjs.org/pino/-/pino-9.13.1.tgz", "integrity": "sha512-Szuj+ViDTjKPQYiKumGmEn3frdl+ZPSdosHyt9SnUevFosOkMY2b7ipxlEctNKPmMD/VibeBI+ZcZCJK+4DPuw==", "dev": true, + "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", @@ -4957,6 +4980,7 @@ "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", "dev": true, + "license": "MIT", "dependencies": { "split2": "^4.0.0" } @@ -4966,6 +4990,7 @@ "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.2.tgz", "integrity": "sha512-3cN0tCakkT4f3zo9RXDIhy6GTvtYD6bK4CRBLN9j3E/ePqN1tugAXD5rGVfoChW6s0hiek+eyYlLNqc/BG7vBQ==", "dev": true, + "license": "MIT", "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", @@ -4990,6 +5015,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -5001,7 +5027,8 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pirates": { "version": "4.0.7", @@ -5125,7 +5152,8 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" } - ] + ], + "license": "MIT" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -5150,6 +5178,7 @@ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", "dev": true, + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -5198,13 +5227,15 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ramda": { "version": "0.31.3", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.31.3.tgz", "integrity": "sha512-xKADKRNnqmDdX59PPKLm3gGmk1ZgNnj3k7DryqWwkamp4TJ6B36DdpyKEQ0EoEYmH2R62bV4Q+S0ym2z8N2f3Q==", "dev": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/ramda" @@ -5271,6 +5302,7 @@ "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 12.13.0" } @@ -5368,6 +5400,7 @@ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } @@ -5391,7 +5424,8 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/semver": { "version": "6.3.1", @@ -5556,13 +5590,15 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/slow-redact/-/slow-redact-0.3.2.tgz", "integrity": "sha512-MseHyi2+E/hBRqdOi5COy6wZ7j7DxXRz9NkseavNYSvvWC06D8a5cidVZX3tcG5eCW3NIyVU4zT63hw0Q486jw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/sonic-boom": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", "dev": true, + "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0" } @@ -5591,6 +5627,7 @@ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "dev": true, + "license": "ISC", "engines": { "node": ">= 10.x" } @@ -5857,6 +5894,7 @@ "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", "dev": true, + "license": "MIT", "dependencies": { "real-require": "^0.2.0" } @@ -5943,7 +5981,8 @@ "version": "1.13.7", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/undici-types": { "version": "7.14.0", diff --git a/package.json b/package.json index 31b1f39d..b09d7d86 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "express": "5.1.0" }, "devDependencies": { - "@pact-foundation/pact": "16.0.0", + "@pact-foundation/pact": "^16.0.0", "dotenv": "17.2.3", "eslint": "9.37.0", "jest": "30.2.0" diff --git a/pactflow-example-consumer-pactflow-example-provider.json b/pactflow-example-consumer-pactflow-example-provider.json new file mode 100644 index 00000000..5405e740 --- /dev/null +++ b/pactflow-example-consumer-pactflow-example-provider.json @@ -0,0 +1,260 @@ +{ + "consumer": { + "name": "pactflow-example-consumer" + }, + "interactions": [ + { + "description": "a request to create a product", + "pending": false, + "providerStates": [ + { + "name": "a product with ID 11 does not exist" + } + ], + "request": { + "body": { + "content": { + "id": "100", + "name": "Latitude Go", + "type": "CREDIT_CARD", + "version": "v1" + }, + "contentType": "application/json", + "encoded": false + }, + "headers": { + "Authorization": [ + "Bearer 2019-01-14T11:34:18.045Z" + ], + "Content-Type": [ + "application/json" + ] + }, + "matchingRules": { + "body": { + "$": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + }, + "header": { + "Authorization": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + } + }, + "method": "POST", + "path": "/product" + }, + "response": { + "body": { + "content": { + "id": "100", + "name": "Latitude Go", + "type": "CREDIT_CARD", + "version": "v1" + }, + "contentType": "application/json", + "encoded": false + }, + "headers": { + "Content-Type": [ + "application/json; charset=utf-8" + ] + }, + "matchingRules": { + "body": { + "$": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + } + }, + "status": 200 + }, + "type": "Synchronous/HTTP" + }, + { + "description": "a request to get a product", + "pending": false, + "providerStates": [ + { + "name": "a product with ID 10 exists" + } + ], + "request": { + "headers": { + "Authorization": [ + "Bearer 2019-01-14T11:34:18.045Z" + ] + }, + "matchingRules": { + "header": { + "Authorization": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + } + }, + "method": "GET", + "path": "/product/10" + }, + "response": { + "body": { + "content": { + "id": "10", + "name": "28 Degrees", + "type": "CREDIT_CARD" + }, + "contentType": "application/json", + "encoded": false + }, + "headers": { + "Content-Type": [ + "application/json; charset=utf-8" + ] + }, + "matchingRules": { + "body": { + "$": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + } + }, + "status": 200 + }, + "type": "Synchronous/HTTP" + }, + { + "description": "a request to get a product", + "pending": false, + "providerStates": [ + { + "name": "a product with ID 11 does not exist" + } + ], + "request": { + "headers": { + "Authorization": [ + "Bearer 2019-01-14T11:34:18.045Z" + ] + }, + "matchingRules": { + "header": { + "Authorization": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + } + }, + "method": "GET", + "path": "/product/11" + }, + "response": { + "status": 404 + }, + "type": "Synchronous/HTTP" + }, + { + "description": "a request to get all products", + "pending": false, + "providerStates": [ + { + "name": "products exist" + } + ], + "request": { + "headers": { + "Authorization": [ + "Bearer 2019-01-14T11:34:18.045Z" + ] + }, + "matchingRules": { + "header": { + "Authorization": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + } + }, + "method": "GET", + "path": "/products" + }, + "response": { + "body": { + "content": [ + { + "id": "10", + "name": "28 Degrees", + "type": "CREDIT_CARD" + } + ], + "contentType": "application/json", + "encoded": false + }, + "headers": { + "Content-Type": [ + "application/json; charset=utf-8" + ] + }, + "matchingRules": { + "body": { + "$": { + "combine": "AND", + "matchers": [ + { + "match": "type", + "min": 1 + } + ] + } + } + }, + "status": 200 + }, + "type": "Synchronous/HTTP" + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.28", + "models": "1.3.5" + }, + "pactSpecification": { + "version": "4.0" + } + }, + "provider": { + "name": "pactflow-example-provider" + } +} \ No newline at end of file diff --git a/server.js b/server.js index 6f501f9a..e2738025 100644 --- a/server.js +++ b/server.js @@ -1,4 +1,5 @@ -const app = require('express')(); +const express = require('express'); +const app = express(); const cors = require('cors'); const routes = require('./src/product/product.routes'); const authMiddleware = require('./src/middleware/auth.middleware'); @@ -6,6 +7,7 @@ const authMiddleware = require('./src/middleware/auth.middleware'); const port = 8080; const init = () => { + app.use(express.json()); app.use(cors()); app.use(routes); app.use(authMiddleware); diff --git a/src/product/pact.setup.js b/src/product/pact.setup.js index b4a4cd55..1452ea5e 100644 --- a/src/product/pact.setup.js +++ b/src/product/pact.setup.js @@ -2,7 +2,7 @@ const controller = require("./product.controller"); const Product = require("./product"); const baseOpts = { - logLevel: "INFO", + logLevel: "debug", providerBaseUrl: "http://localhost:8080", providerVersion: process.env.GIT_COMMIT, providerVersionBranch: process.env.GIT_BRANCH, // the recommended way of publishing verification results with the branch property @@ -12,8 +12,10 @@ const baseOpts = { // Setup provider server to verify const setupServer = () => { - const app = require("express")(); + const express = require('express'); + const app = express(); const authMiddleware = require("../middleware/auth.middleware"); + app.use(express.json()); app.use(authMiddleware); app.use(require("./product.routes")); const server = app.listen("8080"); @@ -47,6 +49,10 @@ const requestFilter = (req, res, next) => { return; } req.headers["authorization"] = `Bearer ${new Date().toISOString()}`; + if (req.method === "POST") { + req['body']["foo"] = "v342234" + req.headers['content-length'] = Buffer.byteLength(JSON.stringify(req.body)) + } next(); }; diff --git a/src/product/product.consumerChange.pact.test.js b/src/product/product.consumerChange.pact.test.js index cd65f6ef..d19e7b21 100644 --- a/src/product/product.consumerChange.pact.test.js +++ b/src/product/product.consumerChange.pact.test.js @@ -9,8 +9,10 @@ const { describe('Pact Verification', () => { let server; + let count; beforeAll(() => { server = setupServer(); + count = 1; }); afterAll(() => { if (server) { @@ -31,7 +33,11 @@ describe('Pact Verification', () => { ...baseOpts, pactUrls: [process.env.PACT_URL], stateHandlers: stateHandlers, - requestFilter: requestFilter + requestFilter: requestFilter, + beforeEach: () => { + console.log(`Verifying interaction: ${count}`); + count++; + }, }; return new Verifier(opts).verifyProvider().then(() => { diff --git a/src/product/product.controller.js b/src/product/product.controller.js index 5590da85..a02c74f8 100644 --- a/src/product/product.controller.js +++ b/src/product/product.controller.js @@ -1,3 +1,4 @@ +const Product = require("./product"); const ProductRepository = require("./product.repository"); const repository = new ProductRepository(); @@ -10,4 +11,11 @@ exports.getById = async (req, res) => { product ? res.send(product) : res.status(404).send({message: "Product not found"}) }; +exports.create = async (req, res) => { + try { + return res.send(new Product(req.body).id); + } catch { + res.status(400).send({ message: "invalid product" }); + } +}; exports.repository = repository; \ No newline at end of file diff --git a/src/product/product.repository.js b/src/product/product.repository.js index e1a37381..7306fbe2 100644 --- a/src/product/product.repository.js +++ b/src/product/product.repository.js @@ -17,6 +17,10 @@ class ProductRepository { async getById(id) { return this.products.get(id); } + + async create(product) { + return this.products.set(product.id, product); + } } module.exports = ProductRepository; diff --git a/src/product/product.routes.js b/src/product/product.routes.js index 6ba6dfe3..87fe5a18 100644 --- a/src/product/product.routes.js +++ b/src/product/product.routes.js @@ -3,5 +3,6 @@ const controller = require('./product.controller'); router.get("/product/:id", controller.getById); router.get("/products", controller.getAll); +router.post("/product", controller.create); module.exports = router; \ No newline at end of file