UNPKG

@pact-foundation/pact

Version:
311 lines 13.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const axios_1 = __importDefault(require("axios")); const fs = require("node:fs"); const net = require("node:net"); const path = require("node:path"); const v3_1 = require("./v3"); const v4_1 = require("./v4"); describe('V4 Pact', () => { let pact; const pactFilePath = path.resolve(process.cwd(), 'pacts', 'v4consumer-v4provider.json'); const interactionByDescription = (description) => { const pactJson = JSON.parse(fs.readFileSync(pactFilePath, 'utf8')); const matches = pactJson.interactions.filter((item) => item.description === description); expect(matches, `expected exactly one interaction for "${description}"`).toHaveLength(1); const [interaction] = matches; return interaction; }; const expectCommentsToContain = (interaction, reason, textComment, testName) => { const comments = interaction.comments; expect(comments.reason).toBe(reason); expect(comments.text).toContain(textComment); expect(comments.testname).toBe(testName); }; const expectReferenceToEqual = (interaction, group, name, value) => { const comments = interaction.comments; const references = comments.references; expect(references[group][name]).toBe(value); }; beforeEach(() => { pact = new v4_1.PactV4({ consumer: 'v4consumer', provider: 'v4provider', }); }); describe('HTTP req/res contract', () => { it('generates a pact', () => pact .addInteraction() .given('some state') .given('a second state') .uponReceiving('a standard HTTP req/res') .withRequest('POST', '/', (builder) => { builder .jsonBody({ foo: 'bar', }) .headers({ 'x-foo': 'x-bar', }); }) .willRespondWith(200, (builder) => { builder .jsonBody({ foo: 'bar', }) .headers({ 'x-foo': 'x-bar', }); }) .executeTest(async (server) => axios_1.default.post(server.url, { foo: 'bar', }, { headers: { 'x-foo': 'x-bar', }, }))); it('generates a pact with interaction metadata', async () => { const description = 'v4 metadata http req/res interaction'; await pact .addInteraction() .given('some state') .given('a second state') .uponReceiving(description) .pending() .comment({ key: 'reason', value: 'covered by HTTP metadata test' }) .comment('second note from HTTP metadata test') .testName('http metadata test name') .withRequest('POST', '/', (builder) => { builder .jsonBody({ foo: 'bar', }) .headers({ 'x-foo': 'x-bar', }); }) .willRespondWith(200, (builder) => { builder .jsonBody({ foo: 'bar', }) .headers({ 'x-foo': 'x-bar', }); }) .executeTest(async (server) => axios_1.default.post(server.url, { foo: 'bar', }, { headers: { 'x-foo': 'x-bar', }, })); const interaction = interactionByDescription(description); expect(interaction.pending).toBe(true); expectCommentsToContain(interaction, 'covered by HTTP metadata test', 'second note from HTTP metadata test', 'http metadata test name'); }); it('records interaction references in the pact file', async () => { const description = 'v4 http interaction with references'; await pact .addInteraction() .uponReceiving(description) .reference('Jira', 'TICKET-123', 'https://jira.example.com/TICKET-123') .withRequest('GET', '/') .willRespondWith(200) .executeTest(async (server) => axios_1.default.get(server.url)); const interaction = interactionByDescription(description); expectReferenceToEqual(interaction, 'Jira', 'TICKET-123', 'https://jira.example.com/TICKET-123'); }); it('supports regex matcher for response content-type with optional charset', () => pact .addInteraction() .uponReceiving('a response with regex content-type header matcher') .withRequest('GET', '/') .willRespondWith(200, (builder) => { builder .headers({ 'Content-Type': v3_1.MatchersV3.regex(/^application\/json(;\s?charset=[\w-]+)?$/i, 'application/json'), }) .jsonBody({ foo: 'bar', }); }) .executeTest(async (server) => { const response = await axios_1.default.get(server.url); expect(response.data).toEqual({ foo: 'bar' }); })); it('supports regex matcher for request content-type with optional charset', () => pact .addInteraction() .uponReceiving('a request with regex content-type header matcher') .withRequest('POST', '/', (builder) => { builder .headers({ 'Content-Type': v3_1.MatchersV3.regex(/^application\/json(;\s?charset=[\w-]+)?$/i, 'application/json'), }) .jsonBody({ foo: 'bar', }); }) .willRespondWith(200, (builder) => { builder.jsonBody({ ok: true, }); }) .executeTest((server) => axios_1.default.post(server.url, { foo: 'bar', }))); }); describe('Asynchronous message contract', () => { it('generates a pact with interaction metadata', async () => { const description = 'v4 metadata async interaction message'; await pact .addAsynchronousInteraction() .given('an async message state') .pending() .comment({ key: 'reason', value: 'covered by async metadata test' }) .comment('second note from async metadata test') .testName('async metadata test name') .expectsToReceive(description, (builder) => { builder.withJSONContent({ event: 'user.created', }); }) .executeTest(async () => Promise.resolve()); const interaction = interactionByDescription(description); expect(interaction.pending).toBe(true); expectCommentsToContain(interaction, 'covered by async metadata test', 'second note from async metadata test', 'async metadata test name'); }); it('records interaction references in the pact file', async () => { const description = 'v4 async interaction with references'; await pact .addAsynchronousInteraction() .reference('GitHub', 'PR-456', 'https://github.com/example/repo/pull/456') .expectsToReceive(description, (builder) => { builder.withJSONContent({ event: 'user.created' }); }) .executeTest(async () => Promise.resolve()); const interaction = interactionByDescription(description); expectReferenceToEqual(interaction, 'GitHub', 'PR-456', 'https://github.com/example/repo/pull/456'); }); }); describe('Synchronous message contract', () => { it('generates a pact with interaction metadata', async () => { const description = 'v4 metadata sync interaction message'; await pact .addSynchronousInteraction(description) .given('a synchronous message state') .pending() .comment({ key: 'reason', value: 'covered by sync metadata test' }) .comment('second note from sync metadata test') .testName('sync metadata test name') .withRequest((builder) => { builder.withJSONContent({ request: 'ping', }); }) .withResponse((builder) => { builder.withJSONContent({ response: 'pong', }); }) .executeTest(async () => Promise.resolve()); const interaction = interactionByDescription(description); expect(interaction.pending).toBe(true); expectCommentsToContain(interaction, 'covered by sync metadata test', 'second note from sync metadata test', 'sync metadata test name'); }); it('records interaction references in the pact file', async () => { const description = 'v4 sync interaction with references'; await pact .addSynchronousInteraction(description) .reference('Jira', 'TICKET-789', 'https://jira.example.com/TICKET-789') .withRequest((builder) => { builder.withJSONContent({ request: 'ping' }); }) .withResponse((builder) => { builder.withJSONContent({ response: 'pong' }); }) .executeTest(async () => Promise.resolve()); const interaction = interactionByDescription(description); expectReferenceToEqual(interaction, 'Jira', 'TICKET-789', 'https://jira.example.com/TICKET-789'); }); }); describe('Plugin test', () => { describe('Using the MATT plugin', () => { const parseMattMessage = (raw) => raw.replace(/(MATT)+/g, '').trim(); const generateMattMessage = (raw) => `MATT${raw}MATT`; describe('HTTP interface', () => { it('generates a pact', async () => { const mattRequest = `{"request": {"body": "hello"}}`; const mattResponse = `{"response":{"body":"world"}}`; await pact .addInteraction() .given('the Matt protocol exists') .uponReceiving('an HTTP request to /matt') .usingPlugin({ plugin: 'matt', version: '0.1.1', }) .withRequest('POST', '/matt', (builder) => { builder.pluginContents('application/matt', mattRequest); }) .willRespondWith(200, (builder) => { builder.pluginContents('application/matt', mattResponse); }) .executeTest((mockserver) => axios_1.default .request({ baseURL: mockserver.url, headers: { 'content-type': 'application/matt', Accept: 'application/matt', }, data: generateMattMessage('hello'), method: 'POST', url: '/matt', }) .then((res) => { expect(parseMattMessage(res.data)).toBe('world'); })); }); }); describe('Synchronous Message (TCP) ', () => { describe('with MATT protocol', () => { const HOST = '127.0.0.1'; const sendMattMessageTCP = (message, host, port) => { const socket = net.connect({ port, host, }); const res = socket.write(`${generateMattMessage(message)}\n`); if (!res) { throw Error('unable to connect to host'); } return new Promise((resolve) => { socket.on('data', (data) => { resolve(parseMattMessage(data.toString())); socket.destroy(); }); }); }; it('generates a pact', () => { const mattMessage = `{"request": {"body": "hellotcp"}, "response":{"body":"tcpworld"}}`; return pact .addSynchronousInteraction('a MATT message') .usingPlugin({ plugin: 'matt', version: '0.1.1', }) .withPluginContents(mattMessage, 'application/matt') .startTransport('matt', HOST) .executeTest(async (tc) => { const message = await sendMattMessageTCP('hellotcp', HOST, tc.port); expect(message).toBe('tcpworld'); }); }); }); }); }); }); }); //# sourceMappingURL=pact.integration.spec.js.map