Skip to content

Commit c30de63

Browse files
authored
pingback test, see #339 and #338 (#340)
1 parent 725a874 commit c30de63

File tree

6 files changed

+170
-19
lines changed

6 files changed

+170
-19
lines changed

packages/foundation/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
This simulator gives some base level functionality which is likely to be used in every simulator. It is built with the expectation to be extended and meet your needs for any custom simulators as well. If you need assistance in building a simulator for your needs, please reach out to [Frontside for this or any other consulting services](https://frontside.com/).
44

5+
Use this base with an OpenAPI specification or other API contract enforcement mechanisms to quickly wire up test data that feels real. Implement APIs that feel real _including_ the handling features such as webhooks, WebSockets, GraphQL, and/or Authentication/Authorization. See the other simulators in these repos as additional examples on how you may implement a simulator.
6+
57
## Quick Start
68

79
Get started with some JSON file, and incrementally build up as needs arise. Start with:

packages/foundation/example/extensiveServer/store.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ const selectors = ({
6262
};
6363

6464
const tasks = ({ createWebhook }: ExtendSimulationTasks<ExtendedSchema>) => {
65-
const webhook = createWebhook("https://example.com/webhook");
65+
const webhook = createWebhook("https://example.com");
6666
const onTest = webhook.create<{ id: string; name: string }>(
67-
"webhook:test",
67+
"/webhook-endpoint",
6868
function* (ctx, next) {
6969
// the following would send off that request
7070
// but we don't want to post in tests

packages/foundation/example/pingback/server-one.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ export function simulation(): FoundationSimulator<any> {
1212
schema: ({ slice }: ExtendSimulationSchema) => {
1313
let slices = {
1414
boop: slice.num(),
15+
bapped: slice.num(),
1516
};
1617
return slices;
1718
},
1819
tasks: ({ createWebhook }) => {
19-
const webhook = createWebhook("http://localhost:3050");
20-
const boopTrigger = webhook.create<null>(
20+
const webhook = createWebhook("http://localhost:3051");
21+
// use webhook.create<TypeOfPayload> if a payload is expected
22+
const boopTrigger = webhook.create(
2123
"/event/boop",
2224
function* (ctx, next) {
2325
console.log("firing webhook from 3050 to 3051");
@@ -29,7 +31,7 @@ export function simulation(): FoundationSimulator<any> {
2931
yield* next();
3032

3133
// this allows you to inspect after the request
32-
console.dir({ ctx });
34+
console.log({ ctx });
3335
}
3436
);
3537
return { tasks: [webhook.task], actions: { webhook: { boopTrigger } } };
@@ -40,16 +42,24 @@ export function simulation(): FoundationSimulator<any> {
4042
console.log("received boop increment request on 3050");
4143
simulationStore.store.dispatch(
4244
simulationStore.actions.batchUpdater(
43-
// @ts-expect-error TODO check on this type rror
4445
simulationStore.schema.boop.increment()
4546
)
4647
);
4748
res.status(200).json({ status: "ok" });
4849
});
4950

51+
router.post("/event/bap", (_req, res) => {
52+
console.log("received bap increment request on 3050");
53+
simulationStore.store.dispatch(
54+
simulationStore.actions.batchUpdater(
55+
simulationStore.schema.bapped.increment()
56+
)
57+
);
58+
res.status(200).json({ status: "ok" });
59+
});
60+
5061
router.get("/external/boop", (_req, res) => {
5162
simulationStore.store.dispatch(
52-
// @ts-expect-error TODO check on this type rror
5363
simulationStore.actions.webhook.boopTrigger()
5464
);
5565
res.status(200).json({ status: "ok" });
@@ -61,6 +71,13 @@ export function simulation(): FoundationSimulator<any> {
6171
);
6272
res.status(200).json({ count });
6373
});
74+
75+
router.get("/get/bap", (_req, res) => {
76+
const count = simulationStore.schema.bapped.select(
77+
simulationStore.store.getState()
78+
);
79+
res.status(200).json({ count });
80+
});
6481
},
6582
})();
6683
}

packages/foundation/example/pingback/server-two.ts

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { put, take, takeEvery, type Action } from "starfx";
12
import { createFoundationSimulationServer } from "../../src/index.ts";
23
import type {
34
ExtendSimulationSchema,
@@ -17,7 +18,8 @@ export function simulation(): FoundationSimulator<any> {
1718
},
1819
tasks: ({ createWebhook }) => {
1920
const webhook = createWebhook("http://localhost:3050");
20-
const boopTrigger = webhook.create<null>(
21+
// use webhook.create<TypeOfPayload> if a payload is expected
22+
const boopTrigger = webhook.create(
2123
"/event/boop",
2224
function* (ctx, next) {
2325
console.log("firing webhook from 3051 to 3050");
@@ -28,18 +30,76 @@ export function simulation(): FoundationSimulator<any> {
2830
// this fires off the actual request
2931
yield* next();
3032

31-
console.dir({ ctx });
33+
// this allows you to inspect after the request
34+
// console.log({ ctx });
3235
}
3336
);
34-
return { tasks: [webhook.task], actions: { webhook: { boopTrigger } } };
37+
const bappedTrigger = webhook.create(
38+
"/event/bap",
39+
function* (ctx, next) {
40+
console.log("firing bapped webhook from 3051 to 3050");
41+
ctx.request = ctx.req({
42+
body: JSON.stringify(ctx.payload),
43+
});
44+
45+
// this fires off the actual request
46+
yield* next();
47+
48+
// this allows you to inspect after the request
49+
// console.dir({ ctx });
50+
}
51+
);
52+
const isBoopEvent = (action: Action) =>
53+
action.type === "store" &&
54+
!!action?.payload?.patches.find((p: any) => p.path.includes("boop"));
55+
56+
// using takeEvery is one method of doing of listening for events
57+
function* boopWatcherOptionOne() {
58+
yield* takeEvery("store", function* (action) {
59+
if (isBoopEvent(action)) {
60+
// console.dir(
61+
// {
62+
// w: "one",
63+
// m: "saw a boop, triggering bap on 3050",
64+
// action,
65+
// },
66+
// { depth: null }
67+
// );
68+
// to avoid the double call from the two watchers
69+
// skip this one
70+
// yield* put(bappedTrigger());
71+
}
72+
});
73+
}
74+
// using take and your own iteration loop is another method of doing of listening for events
75+
function* boopWatcherOptionTwo() {
76+
while (true) {
77+
const action = yield* take("*");
78+
if (isBoopEvent(action)) {
79+
// console.dir(
80+
// {
81+
// w: "two",
82+
// m: "saw a boop, triggering bap on 3050",
83+
// action,
84+
// },
85+
// { depth: null }
86+
// );
87+
yield* put(bappedTrigger());
88+
}
89+
}
90+
}
91+
92+
return {
93+
tasks: [webhook.task, boopWatcherOptionOne, boopWatcherOptionTwo],
94+
actions: { webhook: { boopTrigger, bappedTrigger } },
95+
};
3596
},
3697
},
3798
extendRouter(router, simulationStore) {
3899
router.post("/event/boop", (_req, res) => {
39100
console.log("received boop increment request on 3051");
40101
simulationStore.store.dispatch(
41102
simulationStore.actions.batchUpdater(
42-
// @ts-expect-error TODO check on this type rror
43103
simulationStore.schema.boop.increment()
44104
)
45105
);
@@ -48,7 +108,6 @@ export function simulation(): FoundationSimulator<any> {
48108

49109
router.get("/external/boop", (_req, res) => {
50110
simulationStore.store.dispatch(
51-
// @ts-expect-error TODO check on this type rror
52111
simulationStore.actions.webhook.boopTrigger()
53112
);
54113
res.status(200).json({ status: "ok" });

packages/foundation/src/store/index.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,13 @@ export function createSimulationStore<
172172
// where all the thunks get called in the middleware stack
173173
thunks.use(thunks.routes());
174174

175-
let batchUpdater = thunks.create<StoreUpdater<AnyState>[]>(
176-
"update",
177-
function* (ctx, next) {
178-
yield* updateStore(ctx.payload);
179-
yield* next();
180-
}
181-
);
175+
let batchUpdater = thunks.create<
176+
StoreUpdater<AnyState> | StoreUpdater<AnyState>[]
177+
>("update", function* (ctx, next) {
178+
yield* updateStore(ctx.payload);
179+
yield* next();
180+
});
181+
182182
let simulationLog = thunks.create<{
183183
method: string;
184184
url: string;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
2+
import { simulation as simOne } from "../example/pingback/server-one.ts";
3+
import { simulation as simTwo } from "../example/pingback/server-two.ts";
4+
import type { FoundationSimulatorListening } from "../src/index.ts";
5+
6+
const appPortOne = 3050;
7+
const appPortTwo = 3051;
8+
const host = "http://localhost";
9+
const urlOne = `${host}:${appPortOne}`;
10+
const urlTwo = `${host}:${appPortTwo}`;
11+
12+
const f = (url: string) => fetch(url).then((r) => r.json());
13+
const p = (url: string) => fetch(url, { method: "POST" }).then((r) => r.json());
14+
15+
console.log = (...args: any) => {
16+
// comment out in test to reduce noise
17+
};
18+
19+
describe("pingback", () => {
20+
let serverOne: FoundationSimulatorListening<any>;
21+
let serverTwo: FoundationSimulatorListening<any>;
22+
beforeAll(async () => {
23+
let appOne = simOne();
24+
serverOne = await appOne.listen(appPortOne);
25+
let appTwo = simTwo();
26+
serverTwo = await appTwo.listen(appPortTwo);
27+
});
28+
afterAll(async () => {
29+
await serverOne.ensureClose();
30+
await serverTwo.ensureClose();
31+
});
32+
33+
it("sim servers talk back and forth", async () => {
34+
// check initial state
35+
let r1 = await f(`${urlOne}/get/boop`);
36+
expect(r1).toEqual({ count: 0 });
37+
let r2 = await f(`${urlTwo}/get/boop`);
38+
expect(r2).toEqual({ count: 0 });
39+
40+
// trigger direct bump on each
41+
let r3 = await p(`${urlOne}/event/boop`);
42+
expect(r3).toEqual({ status: "ok" });
43+
let r4 = await p(`${urlTwo}/event/boop`);
44+
expect(r4).toEqual({ status: "ok" });
45+
46+
// check they both incremented
47+
let r5 = await f(`${urlOne}/get/boop`);
48+
expect(r5).toEqual({ count: 1 });
49+
let r6 = await f(`${urlTwo}/get/boop`);
50+
expect(r6).toEqual({ count: 1 });
51+
52+
// check the webhook listener is gets double incremented
53+
let r7 = await f(`${urlOne}/get/bap`);
54+
// note that we have two watchers
55+
// but only one sends the increment request
56+
expect(r7).toEqual({ count: 1 });
57+
58+
// trigger via external endpoint on one
59+
// which will webhook to the other
60+
let r8 = await f(`${urlOne}/external/boop`);
61+
expect(r8).toEqual({ status: "ok" });
62+
63+
// check the webhook pinged the other server
64+
let r9 = await f(`${urlOne}/get/boop`);
65+
expect(r9).toEqual({ count: 1 }); // unchanged
66+
let r10 = await f(`${urlTwo}/get/boop`);
67+
expect(r10).toEqual({ count: 2 });
68+
69+
// our watcher has updated the bapped count though
70+
let r11 = await f(`${urlOne}/get/bap`);
71+
expect(r11).toEqual({ count: 2 }); // ie see r9 vs r11
72+
});
73+
});

0 commit comments

Comments
 (0)