From 625f2fe7f27d48a6de06e90a2e6fa567f2b514fe Mon Sep 17 00:00:00 2001 From: Anudeep Date: Wed, 6 Jul 2022 20:38:43 +0530 Subject: [PATCH 1/2] feat: support file in interaction --- package-lock.json | 2 +- package.json | 2 +- src/exports/mock.d.ts | 1 + src/models/Interaction.model.js | 3 ++ src/models/server.js | 36 +++++++++++++++---- test/component/interactions.file.spec.js | 44 ++++++++++++++++++++++++ test/component/interactions.mock.spec.js | 27 +++++++++++++++ test/component/interactions.spec.js | 1 - 8 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 test/component/interactions.file.spec.js diff --git a/package-lock.json b/package-lock.json index cca1ff2..926dbc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "pactum", - "version": "3.1.10", + "version": "3.1.11", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index df33bab..0e87202 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pactum", - "version": "3.1.10", + "version": "3.1.11", "description": "REST API Testing Tool for all levels in a Test Pyramid", "main": "./src/index.js", "types": "./src/index.d.ts", diff --git a/src/exports/mock.d.ts b/src/exports/mock.d.ts index 52a55d4..1d609b7 100644 --- a/src/exports/mock.d.ts +++ b/src/exports/mock.d.ts @@ -20,6 +20,7 @@ export interface InteractionResponse { status: number; headers?: object; body?: object; + file?: string; fixedDelay?: number; randomDelay?: RandomDelay; } diff --git a/src/models/Interaction.model.js b/src/models/Interaction.model.js index 6c6c06f..5c578c0 100644 --- a/src/models/Interaction.model.js +++ b/src/models/Interaction.model.js @@ -166,6 +166,9 @@ class InteractionResponse { } else if (response.randomDelay) { this.delay = new InteractionResponseDelay('RANDOM', response.randomDelay); } + if (response.file) { + this.file = getValue(response.file); + } } } diff --git a/src/models/server.js b/src/models/server.js index fbbff08..a80e63e 100644 --- a/src/models/server.js +++ b/src/models/server.js @@ -1,4 +1,7 @@ const polka = require('polka'); +const fs = require('fs'); +const mime = require('mime-lite') +const fs_path = require('path'); const Interaction = require('./Interaction.model'); const helper = require('../helpers/helper'); @@ -138,9 +141,9 @@ function sendInteractionFoundResponse(req, res, interaction) { res.status(_response.status); const delay = getDelay(_response); if (delay > 0) { - setTimeout(() => sendResponseBody(res, _response.body), delay); + setTimeout(() => sendResponse(res, _response), delay); } else { - sendResponseBody(res, _response.body); + sendResponse(res, _response); } } interaction.callCount += 1; @@ -164,11 +167,13 @@ function updateCalls(req, interaction) { /** * sends response body * @param {ExpressResponse} res - HTTP response - * @param {object} body - response body to be sent + * @param {object} interaction_response - response body to be sent */ -function sendResponseBody(res, body) { - if (body) { - res.send(body); +function sendResponse(res, interaction_response) { + if (interaction_response.file) { + res.download(interaction_response); + } else if (interaction_response.body) { + res.send(interaction_response.body); } else { res.send(); } @@ -392,7 +397,10 @@ class ExpressResponse { send(data) { if (data) { if (typeof data === 'object') { - this.res.setHeader('Content-Type', 'application/json'); + const header_keys = this.res.getHeaderNames(); + if (!header_keys.includes('content-type')) { + this.res.setHeader('Content-Type', 'application/json'); + } this.res.end(JSON.stringify(data)); } else { this.res.end(data); @@ -401,6 +409,20 @@ class ExpressResponse { this.res.end(); } } + + download(interaction_response) { + if (interaction_response.headers) { + const header_keys = Object.keys(interaction_response.headers).map(_ => _.toLowerCase()); + if (!header_keys.includes('content-type')) { + const file_name = fs_path.basename(interaction_response.file) + this.res.setHeader('content-type', mime.getType(file_name)); + } + } else { + const file_name = fs_path.basename(interaction_response.file) + this.res.setHeader('content-type', mime.getType(file_name)); + } + fs.createReadStream(interaction_response.file).pipe(this.res); + } } module.exports = Server; diff --git a/test/component/interactions.file.spec.js b/test/component/interactions.file.spec.js new file mode 100644 index 0000000..a8961d0 --- /dev/null +++ b/test/component/interactions.file.spec.js @@ -0,0 +1,44 @@ +const pactum = require('../../src/index'); + +describe('Interactions - File', () => { + + it('download file with default headers', async () => { + await pactum.spec() + .useInteraction({ + request: { + method: 'GET', + path: '/download' + }, + response: { + status: 200, + file: 'assets/logo.png' + } + }) + .get('http://localhost:9393/download') + .expectStatus(200) + .expectBodyContains('PNG') + .expectHeader('content-type', 'image/png'); + }); + + it('download file with custom headers', async () => { + await pactum.spec() + .useInteraction({ + request: { + method: 'GET', + path: '/download' + }, + response: { + status: 200, + headers: { + 'content-type': 'application/json' + }, + file: 'assets/logo.png' + } + }) + .get('http://localhost:9393/download') + .expectStatus(200) + .expectBodyContains('PNG') + .expectHeader('content-type', 'application/json'); + }); + +}); \ No newline at end of file diff --git a/test/component/interactions.mock.spec.js b/test/component/interactions.mock.spec.js index acacd40..62e0e31 100644 --- a/test/component/interactions.mock.spec.js +++ b/test/component/interactions.mock.spec.js @@ -623,4 +623,31 @@ describe('Interactions - Not Strict - Follow Redirects', () => { pactum.request.setDefaultFollowRedirects(false); }); +}); + +describe('Interactions - Response - Headers', () => { + + it('with custom content-type headers', async () => { + await pactum.spec() + .useInteraction({ + request: { + method: 'GET', + path: '/custom/header' + }, + response: { + status: 200, + headers: { + 'content-type': 'any' + }, + body: { + message: 'hello' + } + } + }) + .get('http://localhost:9393/custom/header') + .expectStatus(200) + .expectHeader('content-type', 'any') + .expectJson('message', 'hello'); + }); + }); \ No newline at end of file diff --git a/test/component/interactions.spec.js b/test/component/interactions.spec.js index f688703..09085a8 100644 --- a/test/component/interactions.spec.js +++ b/test/component/interactions.spec.js @@ -825,7 +825,6 @@ describe('Pact - VALID', () => { }); - describe('Interactions - expects skip check', () => { it('skip expects - defaults', async () => { From f8d7b21cb35b15fcf86c05ee9cd0a9c9c421eb3f Mon Sep 17 00:00:00 2001 From: Anudeep Date: Wed, 6 Jul 2022 20:51:04 +0530 Subject: [PATCH 2/2] fix: custom headers when multipart form data --- src/helpers/requestProcessor.js | 4 +++- test/component/files.spec.js | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/helpers/requestProcessor.js b/src/helpers/requestProcessor.js index 5144182..0ab6262 100644 --- a/src/helpers/requestProcessor.js +++ b/src/helpers/requestProcessor.js @@ -90,7 +90,9 @@ function setMultiPartFormData(request) { request.headers = multiPartHeaders; } else { for (const prop in multiPartHeaders) { - request.headers[prop] = multiPartHeaders[prop]; + if (request.headers[prop] === undefined) { + request.headers[prop] = multiPartHeaders[prop]; + } } } delete request._multiPartFormData; diff --git a/test/component/files.spec.js b/test/component/files.spec.js index ca95401..d9492ac 100644 --- a/test/component/files.spec.js +++ b/test/component/files.spec.js @@ -57,4 +57,13 @@ describe('Files', () => { .expectStatus(200); }); + it('with file - custom content-type header', async () => { + await pactum.spec() + .useInteraction('post file') + .post('http://localhost:9393/api/file') + .withFile('./package.json') + .withHeaders('content-type', 'any') + .expectStatus(200); + }); + }); \ No newline at end of file