Skip to content

Commit 404c361

Browse files
authored
Merge pull request #3518 from Skords-01/claude/youthful-gates-cktfvg
2 parents 6a416b4 + 69d12f1 commit 404c361

14 files changed

Lines changed: 591 additions & 545 deletions

packages/api-client/src/__tests__/contracts/_pact.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,22 @@ export const PACT_DIR = path.resolve(
4141
* gymnastics, and we don't need V4-only features (sync messages,
4242
* GraphQL) for HTTP REST contracts.
4343
*/
44+
/**
45+
* Vitest suite options for every pact contract `describe`.
46+
*
47+
* `retry: 2` absorbs a known pact-core FFI race: under loaded CI runners
48+
* the mock server occasionally reports `missing-request` ("The following
49+
* request was expected but not received") even though the client received
50+
* the fully matched response and every assertion passed (the error is the
51+
* `Promise.reject` branch of `executeTest`, which is only reachable when
52+
* the test callback resolved). The race lives in the Rust mock-server
53+
* match bookkeeping, not in our request shape, and surfaces in files with
54+
* several sequential interactions (the PactV4 builder is torn down and
55+
* re-created between `executeTest` calls). A real contract mismatch is
56+
* deterministic and still fails all three attempts.
57+
*/
58+
export const CONTRACT_SUITE_OPTIONS = { retry: 2 } as const;
59+
4460
export function createPact(): PactV4 {
4561
return new PactV4({
4662
consumer: CONSUMER,

packages/api-client/src/__tests__/contracts/barcode.contract.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ import type { PactV4 } from "@pact-foundation/pact";
1919

2020
import { createHttpClient } from "../../httpClient";
2121
import { createBarcodeEndpoints } from "../../endpoints/barcode";
22-
import { createPact } from "./_pact";
22+
import { CONTRACT_SUITE_OPTIONS, createPact } from "./_pact";
2323

24-
describe("contract @ GET /api/v1/barcode", () => {
24+
describe("contract @ GET /api/v1/barcode", CONTRACT_SUITE_OPTIONS, () => {
2525
let pact: PactV4;
2626
beforeAll(() => {
2727
pact = createPact();

packages/api-client/src/__tests__/contracts/chat.contract.test.ts

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -17,52 +17,56 @@ import type { PactV4 } from "@pact-foundation/pact";
1717

1818
import { createHttpClient } from "../../httpClient";
1919
import { createChatEndpoints } from "../../endpoints/chat";
20-
import { createPact } from "./_pact";
20+
import { CONTRACT_SUITE_OPTIONS, createPact } from "./_pact";
2121

22-
describe("contract @ POST /api/v1/chat (non-streaming)", () => {
23-
let pact: PactV4;
24-
beforeAll(() => {
25-
pact = createPact();
26-
});
27-
afterAll(() => {});
22+
describe(
23+
"contract @ POST /api/v1/chat (non-streaming)",
24+
CONTRACT_SUITE_OPTIONS,
25+
() => {
26+
let pact: PactV4;
27+
beforeAll(() => {
28+
pact = createPact();
29+
});
30+
afterAll(() => {});
2831

29-
it("returns assistant text for a simple user prompt (hub persona)", async () => {
30-
await pact
31-
.addInteraction()
32-
.given(
33-
"authenticated user-pact-001 with a clean conversation; Anthropic stub returns fixed assistant text",
34-
)
35-
.uponReceiving(
36-
"a POST /api/v1/chat request without tool-calls (non-streaming)",
37-
)
38-
.withRequest("POST", "/api/v1/chat", (req) => {
39-
req.headers({
40-
accept: "application/json",
41-
"content-type": "application/json",
32+
it("returns assistant text for a simple user prompt (hub persona)", async () => {
33+
await pact
34+
.addInteraction()
35+
.given(
36+
"authenticated user-pact-001 with a clean conversation; Anthropic stub returns fixed assistant text",
37+
)
38+
.uponReceiving(
39+
"a POST /api/v1/chat request without tool-calls (non-streaming)",
40+
)
41+
.withRequest("POST", "/api/v1/chat", (req) => {
42+
req.headers({
43+
accept: "application/json",
44+
"content-type": "application/json",
45+
});
46+
req.jsonBody({
47+
context: "hub",
48+
messages: [{ role: "user", content: "Привіт, як справи?" }],
49+
stream: false,
50+
});
51+
})
52+
.willRespondWith(200, (res) => {
53+
res.headers({ "content-type": "application/json" });
54+
res.jsonBody({
55+
text: "Все ок, що треба зробити?",
56+
});
57+
})
58+
.executeTest(async (mockServer) => {
59+
const http = createHttpClient({ baseUrl: mockServer.url });
60+
const chat = createChatEndpoints(http);
61+
const out = await chat.send({
62+
context: "hub",
63+
messages: [{ role: "user", content: "Привіт, як справи?" }],
64+
stream: false,
65+
});
66+
expect(out.text).toBe("Все ок, що треба зробити?");
67+
expect(out.tool_calls).toBeUndefined();
68+
expect(out.error).toBeUndefined();
4269
});
43-
req.jsonBody({
44-
context: "hub",
45-
messages: [{ role: "user", content: "Привіт, як справи?" }],
46-
stream: false,
47-
});
48-
})
49-
.willRespondWith(200, (res) => {
50-
res.headers({ "content-type": "application/json" });
51-
res.jsonBody({
52-
text: "Все ок, що треба зробити?",
53-
});
54-
})
55-
.executeTest(async (mockServer) => {
56-
const http = createHttpClient({ baseUrl: mockServer.url });
57-
const chat = createChatEndpoints(http);
58-
const out = await chat.send({
59-
context: "hub",
60-
messages: [{ role: "user", content: "Привіт, як справи?" }],
61-
stream: false,
62-
});
63-
expect(out.text).toBe("Все ок, що треба зробити?");
64-
expect(out.tool_calls).toBeUndefined();
65-
expect(out.error).toBeUndefined();
66-
});
67-
});
68-
});
70+
});
71+
},
72+
);

packages/api-client/src/__tests__/contracts/coach-memory.contract.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ import type { PactV4 } from "@pact-foundation/pact";
1717

1818
import { createHttpClient } from "../../httpClient";
1919
import { createCoachEndpoints } from "../../endpoints/coach";
20-
import { createPact } from "./_pact";
20+
import { CONTRACT_SUITE_OPTIONS, createPact } from "./_pact";
2121

22-
describe("contract @ GET /api/v1/coach/memory", () => {
22+
describe("contract @ GET /api/v1/coach/memory", CONTRACT_SUITE_OPTIONS, () => {
2323
let pact: PactV4;
2424
beforeAll(() => {
2525
pact = createPact();

packages/api-client/src/__tests__/contracts/food-search.contract.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ import type { PactV4 } from "@pact-foundation/pact";
2020

2121
import { createHttpClient } from "../../httpClient";
2222
import { createFoodSearchEndpoints } from "../../endpoints/foodSearch";
23-
import { createPact } from "./_pact";
23+
import { CONTRACT_SUITE_OPTIONS, createPact } from "./_pact";
2424

25-
describe("contract @ GET /api/food-search", () => {
25+
describe("contract @ GET /api/food-search", CONTRACT_SUITE_OPTIONS, () => {
2626
let pact: PactV4;
2727
beforeAll(() => {
2828
pact = createPact();

packages/api-client/src/__tests__/contracts/me.contract.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import type { PactV4 } from "@pact-foundation/pact";
1414

1515
import { createHttpClient } from "../../httpClient";
1616
import { createMeEndpoints } from "../../endpoints/me";
17-
import { createPact } from "./_pact";
17+
import { CONTRACT_SUITE_OPTIONS, createPact } from "./_pact";
1818

19-
describe("contract @ GET /api/v1/me", () => {
19+
describe("contract @ GET /api/v1/me", CONTRACT_SUITE_OPTIONS, () => {
2020
let pact: PactV4;
2121
beforeAll(() => {
2222
pact = createPact();

packages/api-client/src/__tests__/contracts/mono-syncState.contract.test.ts

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -15,44 +15,48 @@ import type { PactV4 } from "@pact-foundation/pact";
1515

1616
import { createHttpClient } from "../../httpClient";
1717
import { createMonoWebhookEndpoints } from "../../endpoints/mono";
18-
import { createPact } from "./_pact";
18+
import { CONTRACT_SUITE_OPTIONS, createPact } from "./_pact";
1919

20-
describe("contract @ GET /api/v1/mono/sync-state", () => {
21-
let pact: PactV4;
22-
beforeAll(() => {
23-
pact = createPact();
24-
});
25-
afterAll(() => {});
20+
describe(
21+
"contract @ GET /api/v1/mono/sync-state",
22+
CONTRACT_SUITE_OPTIONS,
23+
() => {
24+
let pact: PactV4;
25+
beforeAll(() => {
26+
pact = createPact();
27+
});
28+
afterAll(() => {});
2629

27-
it("returns MonoSyncState for a connected, active webhook (finyk persona)", async () => {
28-
await pact
29-
.addInteraction()
30-
.given(
31-
"an authenticated session for user-pact-001 with an active Monobank webhook (2 accounts)",
32-
)
33-
.uponReceiving("a GET /api/v1/mono/sync-state request")
34-
.withRequest("GET", "/api/v1/mono/sync-state", (req) => {
35-
req.headers({ accept: "application/json" });
36-
})
37-
.willRespondWith(200, (res) => {
38-
res.headers({ "content-type": "application/json" });
39-
res.jsonBody({
40-
status: "active",
41-
webhookActive: true,
42-
lastEventAt: "2026-05-13T08:30:00.000Z",
43-
lastBackfillAt: "2026-05-12T22:00:00.000Z",
44-
accountsCount: 2,
30+
it("returns MonoSyncState for a connected, active webhook (finyk persona)", async () => {
31+
await pact
32+
.addInteraction()
33+
.given(
34+
"an authenticated session for user-pact-001 with an active Monobank webhook (2 accounts)",
35+
)
36+
.uponReceiving("a GET /api/v1/mono/sync-state request")
37+
.withRequest("GET", "/api/v1/mono/sync-state", (req) => {
38+
req.headers({ accept: "application/json" });
39+
})
40+
.willRespondWith(200, (res) => {
41+
res.headers({ "content-type": "application/json" });
42+
res.jsonBody({
43+
status: "active",
44+
webhookActive: true,
45+
lastEventAt: "2026-05-13T08:30:00.000Z",
46+
lastBackfillAt: "2026-05-12T22:00:00.000Z",
47+
accountsCount: 2,
48+
});
49+
})
50+
.executeTest(async (mockServer) => {
51+
const http = createHttpClient({ baseUrl: mockServer.url });
52+
const mono = createMonoWebhookEndpoints(http);
53+
const state = await mono.syncState();
54+
expect(state.status).toBe("active");
55+
expect(state.webhookActive).toBe(true);
56+
expect(state.lastEventAt).toBe("2026-05-13T08:30:00.000Z");
57+
expect(state.lastBackfillAt).toBe("2026-05-12T22:00:00.000Z");
58+
expect(state.accountsCount).toBe(2);
4559
});
46-
})
47-
.executeTest(async (mockServer) => {
48-
const http = createHttpClient({ baseUrl: mockServer.url });
49-
const mono = createMonoWebhookEndpoints(http);
50-
const state = await mono.syncState();
51-
expect(state.status).toBe("active");
52-
expect(state.webhookActive).toBe(true);
53-
expect(state.lastEventAt).toBe("2026-05-13T08:30:00.000Z");
54-
expect(state.lastBackfillAt).toBe("2026-05-12T22:00:00.000Z");
55-
expect(state.accountsCount).toBe(2);
56-
});
57-
});
58-
});
60+
});
61+
},
62+
);

0 commit comments

Comments
 (0)