Skip to content

Commit a668c26

Browse files
authored
feat: support pending interactions, comments and test names (#851)
* feat: add pending and interaction metadata APIs
1 parent 56c95c5 commit a668c26

11 files changed

Lines changed: 545 additions & 14 deletions

File tree

native/addon.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
2323
exports.Set(Napi::String::New(env, "pactffiGiven"), Napi::Function::New(env, PactffiGiven));
2424
exports.Set(Napi::String::New(env, "pactffiGivenWithParam"), Napi::Function::New(env, PactffiGivenWithParam));
2525
exports.Set(Napi::String::New(env, "pactffiGivenWithParams"), Napi::Function::New(env, PactffiGivenWithParams));
26+
exports.Set(Napi::String::New(env, "pactffiSetPending"), Napi::Function::New(env, PactffiSetPending));
27+
exports.Set(Napi::String::New(env, "pactffiSetComment"), Napi::Function::New(env, PactffiSetComment));
28+
exports.Set(Napi::String::New(env, "pactffiAddTextComment"), Napi::Function::New(env, PactffiAddTextComment));
29+
exports.Set(Napi::String::New(env, "pactffiInteractionTestName"), Napi::Function::New(env, PactffiInteractionTestName));
2630
exports.Set(Napi::String::New(env, "pactffiWithRequest"), Napi::Function::New(env, PactffiWithRequest));
2731
exports.Set(Napi::String::New(env, "pactffiWithQueryParameter"), Napi::Function::New(env, PactffiWithQueryParameter));
2832
exports.Set(Napi::String::New(env, "pactffiWithSpecification"), Napi::Function::New(env, PactffiWithSpecification));

native/consumer.cc

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,131 @@ Napi::Value PactffiGivenWithParams(const Napi::CallbackInfo& info) {
679679
return Napi::Boolean::New(env, true);
680680
}
681681

682+
/**
683+
* Sets the pending status for an interaction.
684+
*
685+
* C interface:
686+
*
687+
* bool pactffi_set_pending(InteractionHandle interaction, bool pending);
688+
*/
689+
Napi::Value PactffiSetPending(const Napi::CallbackInfo& info) {
690+
Napi::Env env = info.Env();
691+
692+
if (info.Length() < 2) {
693+
throw Napi::Error::New(env, "PactffiSetPending received < 2 arguments");
694+
}
695+
696+
if (!info[0].IsNumber()) {
697+
throw Napi::Error::New(env, "PactffiSetPending(arg 0) expected a InteractionHandle (uint32_t)");
698+
}
699+
700+
if (!info[1].IsBoolean()) {
701+
throw Napi::Error::New(env, "PactffiSetPending(arg 1) expected a boolean");
702+
}
703+
704+
InteractionHandle interaction = info[0].As<Napi::Number>().Uint32Value();
705+
bool pending = info[1].As<Napi::Boolean>().Value();
706+
707+
bool res = pactffi_set_pending(interaction, pending);
708+
709+
return Napi::Boolean::New(env, res);
710+
}
711+
712+
/**
713+
* Sets metadata comment key/value pair for an interaction.
714+
*
715+
* C interface:
716+
*
717+
* bool pactffi_set_comment(InteractionHandle interaction, const char *key, const char *value);
718+
*/
719+
Napi::Value PactffiSetComment(const Napi::CallbackInfo& info) {
720+
Napi::Env env = info.Env();
721+
722+
if (info.Length() < 3) {
723+
throw Napi::Error::New(env, "PactffiSetComment received < 3 arguments");
724+
}
725+
726+
if (!info[0].IsNumber()) {
727+
throw Napi::Error::New(env, "PactffiSetComment(arg 0) expected a InteractionHandle (uint32_t)");
728+
}
729+
730+
if (!info[1].IsString()) {
731+
throw Napi::Error::New(env, "PactffiSetComment(arg 1) expected a string");
732+
}
733+
734+
if (!info[2].IsString()) {
735+
throw Napi::Error::New(env, "PactffiSetComment(arg 2) expected a string");
736+
}
737+
738+
InteractionHandle interaction = info[0].As<Napi::Number>().Uint32Value();
739+
std::string key = info[1].As<Napi::String>().Utf8Value();
740+
std::string value = info[2].As<Napi::String>().Utf8Value();
741+
742+
bool res = pactffi_set_comment(interaction, key.c_str(), value.c_str());
743+
744+
return Napi::Boolean::New(env, res);
745+
}
746+
747+
/**
748+
* Adds a plain text comment to interaction metadata.
749+
*
750+
* C interface:
751+
*
752+
* bool pactffi_add_text_comment(InteractionHandle interaction, const char *comment);
753+
*/
754+
Napi::Value PactffiAddTextComment(const Napi::CallbackInfo& info) {
755+
Napi::Env env = info.Env();
756+
757+
if (info.Length() < 2) {
758+
throw Napi::Error::New(env, "PactffiAddTextComment received < 2 arguments");
759+
}
760+
761+
if (!info[0].IsNumber()) {
762+
throw Napi::Error::New(env, "PactffiAddTextComment(arg 0) expected a InteractionHandle (uint32_t)");
763+
}
764+
765+
if (!info[1].IsString()) {
766+
throw Napi::Error::New(env, "PactffiAddTextComment(arg 1) expected a string");
767+
}
768+
769+
InteractionHandle interaction = info[0].As<Napi::Number>().Uint32Value();
770+
std::string comment = info[1].As<Napi::String>().Utf8Value();
771+
772+
bool res = pactffi_add_text_comment(interaction, comment.c_str());
773+
774+
return Napi::Boolean::New(env, res);
775+
}
776+
777+
/**
778+
* Sets test name metadata for an interaction.
779+
*
780+
* C interface:
781+
*
782+
* unsigned int pactffi_interaction_test_name(InteractionHandle interaction, const char *test_name);
783+
*/
784+
Napi::Value PactffiInteractionTestName(const Napi::CallbackInfo& info) {
785+
Napi::Env env = info.Env();
786+
787+
if (info.Length() < 2) {
788+
throw Napi::Error::New(env, "PactffiInteractionTestName received < 2 arguments");
789+
}
790+
791+
if (!info[0].IsNumber()) {
792+
throw Napi::Error::New(env, "PactffiInteractionTestName(arg 0) expected a InteractionHandle (uint32_t)");
793+
}
794+
795+
if (!info[1].IsString()) {
796+
throw Napi::Error::New(env, "PactffiInteractionTestName(arg 1) expected a string");
797+
}
798+
799+
InteractionHandle interaction = info[0].As<Napi::Number>().Uint32Value();
800+
std::string testName = info[1].As<Napi::String>().Utf8Value();
801+
802+
unsigned int res = pactffi_interaction_test_name(interaction, testName.c_str());
803+
804+
return Napi::Number::New(env, res);
805+
}
806+
682807
/**
683808
* Configures the request for the Interaction. Returns false if the interaction or Pact can't be
684809
* modified (i.e. the mock server for it has already started)

native/consumer.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ Napi::Value PactffiCreateMockServer(const Napi::CallbackInfo& info);
1818
Napi::Value PactffiGiven(const Napi::CallbackInfo& info);
1919
Napi::Value PactffiGivenWithParam(const Napi::CallbackInfo& info);
2020
Napi::Value PactffiGivenWithParams(const Napi::CallbackInfo& info);
21+
Napi::Value PactffiSetPending(const Napi::CallbackInfo& info);
22+
Napi::Value PactffiSetComment(const Napi::CallbackInfo& info);
23+
Napi::Value PactffiAddTextComment(const Napi::CallbackInfo& info);
24+
Napi::Value PactffiInteractionTestName(const Napi::CallbackInfo& info);
2125
Napi::Value PactffiMockServerLogs(const Napi::CallbackInfo& info);
2226
Napi::Value PactffiMockServerMatched(const Napi::CallbackInfo& info);
2327
Napi::Value PactffiMockServerMismatches(const Napi::CallbackInfo& info);
@@ -58,7 +62,6 @@ Napi::Value PactffiGenerateDatetimeString(const Napi::CallbackInfo& info);
5862
Napi::Value PactffiGenerateRegexValue(const Napi::CallbackInfo& info);
5963
Napi::Value PactffiGetErrorMessage(const Napi::CallbackInfo& info);
6064
Napi::Value PactffiGetTlsCaCertificate(const Napi::CallbackInfo& info);
61-
Napi::Value PactffiInteractionTestName(const Napi::CallbackInfo& info);
6265
Napi::Value PactffiMatchMessage(const Napi::CallbackInfo& info);
6366
Napi::Value PactffiMessageDelete(const Napi::CallbackInfo& info);
6467
Napi::Value PactffiMessageFindMetadata(const Napi::CallbackInfo& info);
@@ -128,4 +131,4 @@ Napi::Value PactffiSyncMessageGetResponseContents(const Napi::CallbackInfo& info
128131
Napi::Value PactffiSyncMessageGetResponseContentsBin(const Napi::CallbackInfo& info);
129132
Napi::Value PactffiSyncMessageGetResponseContentsLength(const Napi::CallbackInfo& info);
130133
Napi::Value PactffiSyncMessageSetDescription(const Napi::CallbackInfo& info);
131-
Napi::Value PactffiCreateMockServerForTransport(const Napi::CallbackInfo& info);
134+
Napi::Value PactffiCreateMockServerForTransport(const Napi::CallbackInfo& info);

src/consumer/checkErrors.spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { expect } from 'chai';
2+
import logger from '../logger';
3+
import { wrapWithCheck } from './checkErrors';
4+
5+
describe('checkErrors', function () {
6+
const originalPactCrash = logger.pactCrash;
7+
8+
afterEach(function () {
9+
logger.pactCrash = originalPactCrash;
10+
});
11+
12+
it('treats numeric status code 0 as success', function () {
13+
let called = false;
14+
logger.pactCrash = () => {
15+
called = true;
16+
};
17+
18+
const wrapped = wrapWithCheck(() => 0, 'num');
19+
20+
const result = wrapped();
21+
expect(result).to.equal(0);
22+
expect(called).to.equal(false);
23+
});
24+
25+
it('treats non-zero numeric status as failure', function () {
26+
let called = false;
27+
logger.pactCrash = () => {
28+
called = true;
29+
};
30+
31+
const wrapped = wrapWithCheck(() => 1, 'num');
32+
33+
const result = wrapped();
34+
expect(result).to.equal(1);
35+
expect(called).to.equal(true);
36+
});
37+
});

src/consumer/checkErrors.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,37 @@
11
import logger from '../logger';
22

33
export const wrapWithCheck =
4-
<F extends (...args: never[]) => boolean>(
5-
f: BooleanFunction<F>,
4+
<F extends (...args: never[]) => boolean | number>(
5+
f: CheckableFunction<F>,
66
contextMessage: string
77
) =>
8-
(...args: Parameters<F>): boolean => {
8+
(...args: Parameters<F>): ReturnType<F> => {
99
const result = f(...args);
10-
if (!result) {
10+
const failed = typeof result === 'number' ? result !== 0 : !result;
11+
if (failed) {
1112
logger.pactCrash(
1213
`The pact consumer core returned false at '${contextMessage}'. This\nshould only happen if the core methods were invoked out of order`
1314
);
1415
}
1516
return result;
1617
};
1718

18-
type BooleanFunction<T> = T extends (...args: infer A) => boolean
19-
? (...args: A) => boolean
19+
type CheckableFunction<T> = T extends (...args: infer A) => boolean | number
20+
? (...args: A) => ReturnType<T>
2021
: never;
2122

22-
type BooleanFunctions<T> = {
23-
[key in keyof T]: BooleanFunction<T[key]>;
23+
type CheckableFunctions<T> = {
24+
[key in keyof T]: CheckableFunction<T[key]>;
2425
};
2526

26-
export const wrapAllWithCheck = <T extends BooleanFunctions<T>>(
27+
export const wrapAllWithCheck = <T extends CheckableFunctions<T>>(
2728
o: T
28-
): BooleanFunctions<T> =>
29+
): CheckableFunctions<T> =>
2930
(Object.keys(o) as Array<keyof T>)
3031
.map((key: keyof T) => ({
31-
[key]: wrapWithCheck(o[key] as BooleanFunction<T[keyof T]>, String(key)),
32+
[key]: wrapWithCheck(
33+
o[key] as CheckableFunction<T[keyof T]>,
34+
String(key)
35+
),
3236
}))
3337
.reduce((acc, curr) => ({ ...acc, ...curr }), {}) as T;

src/consumer/index.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ const asyncMessage = (
5252
ffi.pactffiMessageGivenWithParam(interactionPtr, state, name, value),
5353
givenWithParams: (state: string, params: string) =>
5454
ffi.pactffiMessageGivenWithParams(interactionPtr, state, params),
55+
setPending: (pending: boolean) =>
56+
ffi.pactffiSetPending(interactionPtr, pending),
57+
setComment: (key: string, value: string) =>
58+
ffi.pactffiSetComment(interactionPtr, key, value),
59+
addTextComment: (comment: string) =>
60+
ffi.pactffiAddTextComment(interactionPtr, comment),
61+
setInteractionTestName: (name: string) =>
62+
ffi.pactffiInteractionTestName(interactionPtr, name),
5563
withContents: (body: string, contentType: string) =>
5664
ffi.pactffiMessageWithContents(interactionPtr, contentType, body),
5765
withBinaryContents: (body: Buffer, contentType: string) =>
@@ -211,6 +219,14 @@ export const makeConsumerPact = (
211219
ffi.pactffiGivenWithParam(interactionPtr, state, name, value),
212220
givenWithParams: (state: string, params: string) =>
213221
ffi.pactffiGivenWithParams(interactionPtr, state, params),
222+
setPending: (pending: boolean) =>
223+
ffi.pactffiSetPending(interactionPtr, pending),
224+
setComment: (key: string, value: string) =>
225+
ffi.pactffiSetComment(interactionPtr, key, value),
226+
addTextComment: (comment: string) =>
227+
ffi.pactffiAddTextComment(interactionPtr, comment),
228+
setInteractionTestName: (name: string) =>
229+
ffi.pactffiInteractionTestName(interactionPtr, name),
214230
withRequestContents: (body: string, contentType: string) =>
215231
ffi.pactffiWithBody(
216232
interactionPtr,
@@ -297,6 +313,14 @@ export const makeConsumerPact = (
297313
ffi.pactffiGivenWithParam(interactionPtr, state, name, value),
298314
givenWithParams: (state: string, params: string) =>
299315
ffi.pactffiGivenWithParams(interactionPtr, state, params),
316+
setPending: (pending: boolean) =>
317+
ffi.pactffiSetPending(interactionPtr, pending),
318+
setComment: (key: string, value: string) =>
319+
ffi.pactffiSetComment(interactionPtr, key, value),
320+
addTextComment: (comment: string) =>
321+
ffi.pactffiAddTextComment(interactionPtr, comment),
322+
setInteractionTestName: (name: string) =>
323+
ffi.pactffiInteractionTestName(interactionPtr, name),
300324
withRequest: (method: string, path: string) =>
301325
ffi.pactffiWithRequest(interactionPtr, method, path),
302326
withQuery: (name: string, index: number, value: string) =>
@@ -562,6 +586,14 @@ export const makeConsumerMessagePact = (
562586
ffi.pactffiGivenWithParam(interactionPtr, state, name, value),
563587
givenWithParams: (state: string, params: string) =>
564588
ffi.pactffiGivenWithParams(interactionPtr, state, params),
589+
setPending: (pending: boolean) =>
590+
ffi.pactffiSetPending(interactionPtr, pending),
591+
setComment: (key: string, value: string) =>
592+
ffi.pactffiSetComment(interactionPtr, key, value),
593+
addTextComment: (comment: string) =>
594+
ffi.pactffiAddTextComment(interactionPtr, comment),
595+
setInteractionTestName: (name: string) =>
596+
ffi.pactffiInteractionTestName(interactionPtr, name),
565597
withRequestContents: (body: string, contentType: string) =>
566598
ffi.pactffiWithBody(
567599
interactionPtr,

src/consumer/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ export type ConsumerInteraction = PluginInteraction & {
162162
given: (state: string) => boolean;
163163
givenWithParam: (state: string, name: string, value: string) => boolean;
164164
givenWithParams: (state: string, params: string) => boolean;
165+
setPending: (pending: boolean) => boolean;
166+
setComment: (key: string, value: string) => boolean;
167+
addTextComment: (comment: string) => boolean;
168+
setInteractionTestName: (name: string) => number;
165169
withRequest: (method: string, path: string) => boolean;
166170
withQuery: (name: string, index: number, value: string) => boolean;
167171
withStatus: (status: number) => boolean;
@@ -239,6 +243,10 @@ export type AsynchronousMessage = RequestPluginInteraction & {
239243
given: (state: string) => void;
240244
givenWithParam: (state: string, name: string, value: string) => void;
241245
givenWithParams: (state: string, params: string) => void;
246+
setPending: (pending: boolean) => void;
247+
setComment: (key: string, value: string) => boolean;
248+
addTextComment: (comment: string) => boolean;
249+
setInteractionTestName: (name: string) => number;
242250
expectsToReceive: (description: string) => void;
243251
withMetadata: (name: string, value: string) => void;
244252
withContents: (body: string, contentType: string) => void;
@@ -254,6 +262,10 @@ export type SynchronousMessage = PluginInteraction & {
254262
given: (state: string) => void;
255263
givenWithParam: (state: string, name: string, value: string) => void;
256264
givenWithParams: (state: string, params: string) => void;
265+
setPending: (pending: boolean) => void;
266+
setComment: (key: string, value: string) => boolean;
267+
addTextComment: (comment: string) => boolean;
268+
setInteractionTestName: (name: string) => number;
257269
withMetadata: (name: string, value: string) => void;
258270
withRequestContents: (body: string, contentType: string) => void;
259271
withResponseContents: (body: string, contentType: string) => void;

src/ffi/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,11 @@ const loadPathMessage = (bindingsPath: string) =>
9797
const bindingsResolver = (bindingsPath: string | undefined) =>
9898
bindings(bindingsPath);
9999

100+
const localBindingPath =
101+
process.env['PACT_PREBUILD_LOCATION']?.toString() ?? path.resolve();
100102
const bindingPaths = [
103+
localBindingPath,
101104
path.resolve(getPlatformArchSpecificPackage()),
102-
process.env['PACT_PREBUILD_LOCATION']?.toString() ?? path.resolve(),
103105
];
104106
let ffiLib: Ffi;
105107

src/ffi/types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,17 @@ export type FfiConsumerFunctions = {
162162
description: string,
163163
params: string
164164
): boolean;
165+
pactffiSetPending(handle: FfiInteractionHandle, pending: boolean): boolean;
166+
pactffiSetComment(
167+
handle: FfiInteractionHandle,
168+
key: string,
169+
value: string
170+
): boolean;
171+
pactffiAddTextComment(handle: FfiInteractionHandle, comment: string): boolean;
172+
pactffiInteractionTestName(
173+
handle: FfiInteractionHandle,
174+
name: string
175+
): number;
165176
pactffiWithRequest(
166177
handle: FfiInteractionHandle,
167178
method: string,

0 commit comments

Comments
 (0)