Closed IngridCampos0502 closed 2 years ago
Thanks for the report!
I believe the issue is that you need a way to clear out specific data after a pact run has happened.
There are a few questions I have:
I could see how 2+3 or 2+4 could be used together to address your use case.
Let me know.
Hi Matt :sunglasses:, thanks for the recommendations, below are my comments:
Why do you need to be able to tear down the data at all? Pact is usually expected to run in an ephemeral environment - i.e. the provider and all of its state is usually run in a unit test like context. So needing to be able to clear out the environment afterwards doesn't make sense. This leads me to believe that you're running against a real environment, which I would strongly recommend against
:point_up: you are absolutely right, we are working on implementing afterwards environments, but at the moment, due to some limitations that we have, it is not possible. For this reason, we are working on the real environment
The request filter feature has access to the full request and response. You could capture any information you needed here and take whatever necessary action is required
:point_up: I also tried this, but the res
variable does not return the response of my API call, then I send you the information returned by the res
variable
ServerResponse {
_events: [Object: null prototype] { finish: [Function: bound resOnFinish] },
_eventsCount: 1,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: false,
chunkedEncoding: false,
shouldKeepAlive: true,
maxRequestsOnConnectionReached: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: true,
sendDate: true,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
_contentLength: null,
_hasBody: true,
_trailer: '',
finished: false,
_headerSent: false,
_closed: false,
socket: <ref *1> Socket {
connecting: false,
_hadError: false,
_parent: null,
_host: null,
_readableState: ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: BufferList { head: null, tail: null, length: 0 },
length: 0,
pipes: [],
flowing: true,
ended: false,
endEmitted: false,
reading: true,
constructed: true,
sync: false,
needReadable: true,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
errorEmitted: false,
emitClose: false,
autoDestroy: true,
destroyed: false,
errored: null,
closed: false,
closeEmitted: false,
defaultEncoding: 'utf8',
awaitDrainWriters: null,
multiAwaitDrain: false,
readingMore: false,
dataEmitted: false,
decoder: null,
encoding: null,
[Symbol(kPaused)]: false
},
_events: [Object: null prototype] {
end: [Array],
timeout: [Function: socketOnTimeout],
data: [Function: bound socketOnData],
error: [Function: socketOnError],
close: [Array],
drain: [Function: bound socketOnDrain],
resume: [Function: onSocketResume],
pause: [Function: onSocketPause]
},
_eventsCount: 8,
_maxListeners: undefined,
_writableState: WritableState {
objectMode: false,
highWaterMark: 16384,
finalCalled: false,
needDrain: false,
ending: false,
ended: false,
finished: false,
destroyed: false,
decodeStrings: false,
defaultEncoding: 'utf8',
length: 0,
writing: false,
corked: 0,
sync: false,
bufferProcessing: false,
onwrite: [Function: bound onwrite],
writecb: null,
writelen: 0,
afterWriteTickInfo: null,
buffered: [],
bufferedIndex: 0,
allBuffers: true,
allNoop: true,
pendingcb: 0,
constructed: true,
prefinished: false,
errorEmitted: false,
emitClose: false,
autoDestroy: true,
errored: null,
closed: false,
closeEmitted: false,
[Symbol(kOnFinished)]: []
},
allowHalfOpen: true,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: Server {
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
_events: [Object: null prototype],
_eventsCount: 4,
_maxListeners: undefined,
_connections: 1,
_handle: [TCP],
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
httpAllowHalfOpen: false,
timeout: 0,
keepAliveTimeout: 5000,
maxHeadersCount: null,
maxRequestsPerSocket: 0,
headersTimeout: 60000,
requestTimeout: 0,
_connectionKey: '6::::0',
[Symbol(IncomingMessage)]: [Function: IncomingMessage],
[Symbol(ServerResponse)]: [Function: ServerResponse],
[Symbol(kCapture)]: false,
[Symbol(async_id_symbol)]: 234
},
_server: Server {
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
_events: [Object: null prototype],
_eventsCount: 4,
_maxListeners: undefined,
_connections: 1,
_handle: [TCP],
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
httpAllowHalfOpen: false,
timeout: 0,
keepAliveTimeout: 5000,
maxHeadersCount: null,
maxRequestsPerSocket: 0,
headersTimeout: 60000,
requestTimeout: 0,
_connectionKey: '6::::0',
[Symbol(IncomingMessage)]: [Function: IncomingMessage],
[Symbol(ServerResponse)]: [Function: ServerResponse],
[Symbol(kCapture)]: false,
[Symbol(async_id_symbol)]: 234
},
parser: HTTPParser {
'0': [Function: bound setRequestTimeout],
'1': [Function: parserOnHeaders],
'2': [Function: parserOnHeadersComplete],
'3': [Function: parserOnBody],
'4': [Function: parserOnMessageComplete],
'5': [Function: bound onParserExecute],
'6': [Function: bound onParserTimeout],
_headers: [],
_url: '',
socket: [Circular *1],
incoming: [IncomingMessage],
outgoing: null,
maxHeaderPairs: 2000,
_consumed: true,
onIncoming: [Function: bound parserOnIncoming],
[Symbol(owner_symbol)]: [HTTPServerAsyncResource]
},
on: [Function: socketListenerWrap],
addListener: [Function: socketListenerWrap],
prependListener: [Function: socketListenerWrap],
setEncoding: [Function: socketSetEncoding],
_paused: false,
_httpMessage: [Circular *2],
timeout: 0,
[Symbol(async_id_symbol)]: 452,
[Symbol(kHandle)]: TCP {
reading: true,
onconnection: null,
_consumed: true,
[Symbol(owner_symbol)]: [Circular *1]
},
[Symbol(kSetNoDelay)]: false,
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: Timeout {
_idleTimeout: -1,
_idlePrev: null,
_idleNext: null,
_idleStart: 6828,
_onTimeout: null,
_timerArgs: undefined,
_repeat: null,
_destroyed: true,
[Symbol(refed)]: false,
[Symbol(kHasPrimitive)]: false,
[Symbol(asyncId)]: 481,
[Symbol(triggerId)]: 479
},
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0,
[Symbol(RequestTimeout)]: undefined
},
_header: null,
_keepAliveTimeout: 5000,
_onPendingData: [Function: bound updateOutgoingData],
req: IncomingMessage {
_readableState: ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: BufferList { head: null, tail: null, length: 0 },
length: 0,
pipes: [],
flowing: null,
ended: false,
endEmitted: false,
reading: false,
constructed: true,
sync: true,
needReadable: false,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
errorEmitted: false,
emitClose: true,
autoDestroy: true,
destroyed: false,
errored: null,
closed: false,
closeEmitted: false,
defaultEncoding: 'utf8',
awaitDrainWriters: null,
multiAwaitDrain: false,
readingMore: true,
dataEmitted: false,
decoder: null,
encoding: null,
[Symbol(kPaused)]: null
},
_events: [Object: null prototype] { end: [Function: clearRequestTimeout] },
_eventsCount: 1,
_maxListeners: undefined,
socket: <ref *1> Socket {
connecting: false,
_hadError: false,
_parent: null,
_host: null,
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 8,
_maxListeners: undefined,
_writableState: [WritableState],
allowHalfOpen: true,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: [Server],
_server: [Server],
parser: [HTTPParser],
on: [Function: socketListenerWrap],
addListener: [Function: socketListenerWrap],
prependListener: [Function: socketListenerWrap],
setEncoding: [Function: socketSetEncoding],
_paused: false,
_httpMessage: [Circular *2],
timeout: 0,
[Symbol(async_id_symbol)]: 452,
[Symbol(kHandle)]: [TCP],
[Symbol(kSetNoDelay)]: false,
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: Timeout {
_idleTimeout: -1,
_idlePrev: null,
_idleNext: null,
_idleStart: 6828,
_onTimeout: null,
_timerArgs: undefined,
_repeat: null,
_destroyed: true,
[Symbol(refed)]: false,
[Symbol(kHasPrimitive)]: false,
[Symbol(asyncId)]: 481,
[Symbol(triggerId)]: 479
},
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0,
[Symbol(RequestTimeout)]: undefined
},
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: false,
rawHeaders: [
'authorization',
'Bearer UQiQPSG-MtD3mNHG0JZT2mqfBh1',
'content-type',
'application/json',
'accept',
'*/*',
'accept-encoding',
'gzip, deflate',
'host',
'localhost:50258'
],
rawTrailers: [],
aborted: false,
upgrade: false,
url: '/transfers/alias-directory-service/clients/8e0e6d94-a0bf-4c67-a525-c7627424f4f2/identities/7d7de180-0624-40d4-8407-929cd89550d2',
method: 'GET',
statusCode: null,
statusMessage: null,
client: <ref *1> Socket {
connecting: false,
_hadError: false,
_parent: null,
_host: null,
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 8,
_maxListeners: undefined,
_writableState: [WritableState],
allowHalfOpen: true,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: [Server],
_server: [Server],
parser: [HTTPParser],
on: [Function: socketListenerWrap],
addListener: [Function: socketListenerWrap],
prependListener: [Function: socketListenerWrap],
setEncoding: [Function: socketSetEncoding],
_paused: false,
_httpMessage: [Circular *2],
timeout: 0,
[Symbol(async_id_symbol)]: 452,
[Symbol(kHandle)]: [TCP],
[Symbol(kSetNoDelay)]: false,
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: Timeout {
_idleTimeout: -1,
_idlePrev: null,
_idleNext: null,
_idleStart: 6828,
_onTimeout: null,
_timerArgs: undefined,
_repeat: null,
_destroyed: true,
[Symbol(refed)]: false,
[Symbol(kHasPrimitive)]: false,
[Symbol(asyncId)]: 481,
[Symbol(triggerId)]: 479
},
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0,
[Symbol(RequestTimeout)]: undefined
},
_consuming: false,
_dumped: false,
next: [Function: next],
baseUrl: '',
originalUrl: '/transfers/alias-directory-service/clients/8e0e6d94-a0bf-4c67-a525-c7627424f4f2/identities/7d7de180-0624-40d4-8407-929cd89550d2',
_parsedUrl: Url {
protocol: null,
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
search: null,
query: null,
pathname: '/transfers/alias-directory-service/clients/8e0e6d94-a0bf-4c67-a525-c7627424f4f2/identities/7d7de180-0624-40d4-8407-929cd89550d2',
path: '/transfers/alias-directory-service/clients/8e0e6d94-a0bf-4c67-a525-c7627424f4f2/identities/7d7de180-0624-40d4-8407-929cd89550d2',
href: '/transfers/alias-directory-service/clients/8e0e6d94-a0bf-4c67-a525-c7627424f4f2/identities/7d7de180-0624-40d4-8407-929cd89550d2',
_raw: '/transfers/alias-directory-service/clients/8e0e6d94-a0bf-4c67-a525-c7627424f4f2/identities/7d7de180-0624-40d4-8407-929cd89550d2'
},
params: {},
query: {},
res: [Circular *2],
[Symbol(kCapture)]: false,
[Symbol(kHeaders)]: {
authorization: 'Bearer UQiQPSG-MtD3mNHG0JZT2mqfBh1',
'content-type': 'application/json',
accept: '*/*',
'accept-encoding': 'gzip, deflate',
host: 'localhost:50258'
},
[Symbol(kHeadersCount)]: 10,
[Symbol(kTrailers)]: null,
[Symbol(kTrailersCount)]: 0,
[Symbol(RequestTimeout)]: undefined
},
_sent100: false,
_expect_continue: false,
locals: [Object: null prototype] {},
[Symbol(kCapture)]: false,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype] {
'x-powered-by': [ 'X-Powered-By', 'Express' ]
}
}
this is the response that I am waiting
compare_maps: Comparing maps at $: {"alias": String("51788704902"), "aliasType": String("PHONE"), "consentDate": String("2019-05-17T08:02:02.000Z"), "countryCode": String("PER"), "dob": String("2019-05-17T08:02:02.000Z"), "email": String("ssuarez@gmail.com"), "firstName": String("Johann Sebastian"), "homeAddress": Object({"city": String("Lima"), "isoCountry3Char": String("PER"), "line1": String("calle 143 a # 128 - 81"), "line2": String("Dg. 61c #26-36, Bogotá"), "line3": String("Ak. 7 ##40b-53, Bogotá"), "postalCode": String("1111121")}), "identificationNumber": String("1022989871"), "identificationType": String("PASSPORT"), "identityId": String("7d7de180-0624-40d4-8407-929cd89550d2"), "lastName": String("Suarez"), "permanentAlias": String("1234567890"), "permanentAliasType": String("PHONE"), "qr": Null} -> {"alias": String("51788704902"), "aliasType": String("PHONE"), "consentDate": String("2019-05-17T08:02:02.000Z"), "countryCode": String("PER"), "dob": String("2019-05-17T08:02:02.000Z"), "email": String("ssuarez@gmail.com"), "firstName": String("Johann Sebastian"), "homeAddress": Object({"city": String("Lima"), "isoCountry3Char": String("PER"), "line1": String("calle 143 a # 128 - 81"), "line2": String("Dg. 61c #26-36, Bogotá"), "line3": String("Ak. 7 ##40b-53, Bogotá"), "postalCode": String("1111121")}), "identificationNumber": String("1022989871"), "identificationType": String("PASSPORT"), "identityId": String("7d7de180-0624-40d4-8407-929cd89550d2"), "lastName": String("Suarez"), "permanentAlias": String("1234567890"), "permanentAliasType": String("PHONE"), "qr": Null}
this is my code of the provider side
describe("Validate pact of identity", () => {
it("Validate pact of identity creation", () => {
let opts = {
afterEach: () => {
console.log('------> I would want to get the complete response here <---------')
},
providerBaseUrl: baseUrl.BASE_URL,
changeOrigin: true,
provider: "Create identity",
logLevel: "TRACE",
pactUrls: [
path.resolve(
process.cwd(),
`./__tests__/contract/pacts/${identity_data.nameConsumerPactFile}-${identity_data.nameProviderPactFile}.json`
),
],
requestFilter: async (req, res, next) => {
console.log("the response is ::::: ", res)
req.headers["authorization"] = `Bearer ${await postRequestTokenNonCDE(credentials.bbva_client)}`,
next()
},
consumerVersionTags: ["QA"],
providerVersionTags: ["QA"],
publishVerificationResult: false,
providerVersion: "1.0.0"
}
return new Verifier(opts).verifyProvider()
.then((res) => {
console.log(' ------> Pact Verification Complete!: OR I would want to get the complete response here <---------', res);
}).catch((res) => {
console.log('Pact Verification FAIL!: Get identity ', res);
});
})
})
For additional information, I attach the complete log
We already have hooks before/after tests runs: https://github.com/pact-foundation/pact-js/#before-and-after-hooks
:point_up: yes, I plan to use these hooks when I get the response from the API
In the new V3 interface, there are also per provider state setup and teardown hooks (see docs).
:point_up: Oh cool :smile:, that might work too, but I need the API response :sleepy:
The response object here is an instance of the standard NodeJS ServerResponse
. So the body isn't just available as an object, you need to read it from the event stream.
Here is an example of how to extract a JSON body from it (note you should consider how to use this for your use cases, especially if you're expecting bigger payloads/non text formats):
const extractResponse = (res) => {
return new Promise((resolve, reject) => {
const [oldWrite, oldEnd] = [res.write, res.end];
const chunks = [];
res.write = (chunk) => {
chunks.push(Buffer.from(chunk));
return oldWrite.apply(res, [chunk]);
};
res.end = (chunk) => {
if (chunk) {
chunks.push(Buffer.from(chunk));
}
const body = Buffer.concat(chunks).toString('utf8');
oldEnd.apply(res, [chunk]);
resolve(body);
};
});
};
Here is an example of how you can use it in a requestFilter
:
...
requestFilter: async (req, res, next) => {
// Read the body on requests you're interested in...
const bodyPromise = extractResponse(res, next);
// Invoke the next middleware, to ensure it passes to the
// actual provider
next();
// Wait for the data to be read in from the event stream
const lastBody = await bodyPromise
// do something with body
console.log(lastBody)
}
I'm reluctant to add this sort of functionality to the framework itself for the following reasons:
mefellows Thank you very much, this works for me :sunglasses: :ok_hand:
Great! I'll be honest.. this post did lead to me pushing up a new branch that might improve this situation. But I won't make any promises just yet.
Glad to hear you're back on track!
Checklist
This checklist is optional, but studies show that people who have followed it checklist are really excellent people and we like them
Before making a feature request, I have:
Feature description
Thank you very much for opening these spaces to improve :smiley:
At this moment, it is not possible to obtain the response of the calls to the APIs on the provider side; it is only possible to get a pass or fail result thrown by the following method:
Use case
What is the use case that motivates this feature request?
At this moment I have several tests that call an API that returns a guid; I need to obtain that guid to be able to eliminate the data created by this API; I must do this elimination when PACT finishes doing all the validations, but PACT is not storing that answer anywhere.
Please describe why you would like Pact-js to have this feature.
If PACT JS implements this solution, it could do the work normally done after executing the tests much more naturally, such as the elimination of information or the dechaining of tasks after completing the tests.
My code on the provider side is following.
Additional information:
I made the consultation in the slack channel; I attached the conversation for more context: