Skip to content

Commit d63f8ec

Browse files
committed
Extract the benchmark test fixture
Move the local benchmark-mode Fedify target used by fedify bench integration tests into test/bench. This gives the scenario tests a shared fixture app that exercises WebFinger, actor discovery, signature verification, and inbox handling through the same benchmark target. #744 #784 Assisted-by: Codex:gpt-5.5
1 parent eda267c commit d63f8ec

2 files changed

Lines changed: 95 additions & 67 deletions

File tree

packages/cli/src/bench/action.test.ts

Lines changed: 5 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,12 @@
1-
import {
2-
createFederation,
3-
generateCryptoKeyPair,
4-
MemoryKvStore,
5-
} from "@fedify/fedify";
6-
import { Create, Endpoints, Person } from "@fedify/vocab";
71
import assert from "node:assert/strict";
82
import { mkdtemp, writeFile } from "node:fs/promises";
93
import { tmpdir } from "node:os";
104
import { join } from "node:path";
115
import test from "node:test";
12-
import { serve } from "srvx";
6+
import { spawnBenchmarkTarget } from "../../../../test/bench/fixture.ts";
137
import runBench, { withUserAgent } from "./action.ts";
148
import type { BenchCommand } from "./command.ts";
159

16-
async function spawnTarget() {
17-
const federation = createFederation<void>({
18-
kv: new MemoryKvStore(),
19-
benchmarkMode: true,
20-
});
21-
let keyPairs: CryptoKeyPair[] | undefined;
22-
const requests: { method: string; path: string }[] = [];
23-
federation
24-
.setActorDispatcher("/users/{identifier}", async (ctx, identifier) => {
25-
if (identifier !== "alice") return null;
26-
const pairs = await ctx.getActorKeyPairs(identifier);
27-
return new Person({
28-
id: ctx.getActorUri(identifier),
29-
preferredUsername: identifier,
30-
inbox: ctx.getInboxUri(identifier),
31-
endpoints: new Endpoints({ sharedInbox: ctx.getInboxUri() }),
32-
publicKey: pairs[0]?.cryptographicKey,
33-
assertionMethods: pairs.map((p) => p.multikey),
34-
});
35-
})
36-
.mapHandle((_ctx, username) => (username === "alice" ? "alice" : null))
37-
.setKeyPairsDispatcher(async (_ctx, identifier) => {
38-
if (identifier !== "alice") return [];
39-
keyPairs ??= [
40-
await generateCryptoKeyPair("RSASSA-PKCS1-v1_5"),
41-
await generateCryptoKeyPair("Ed25519"),
42-
];
43-
return keyPairs;
44-
});
45-
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(
46-
Create,
47-
() => {},
48-
);
49-
let inboxUserAgent: string | null = null;
50-
const server = serve({
51-
port: 0,
52-
hostname: "127.0.0.1",
53-
silent: true,
54-
fetch: (request: Request) => {
55-
const url = new URL(request.url);
56-
requests.push({ method: request.method, path: url.pathname });
57-
if (request.method === "POST") {
58-
inboxUserAgent = request.headers.get("user-agent");
59-
}
60-
return federation.fetch(request, { contextData: undefined });
61-
},
62-
});
63-
await server.ready();
64-
return {
65-
url: new URL(server.url!),
66-
inboxUserAgent: () => inboxUserAgent,
67-
requests: () => requests.slice(),
68-
close: () => server.close(true),
69-
};
70-
}
71-
7210
function command(overrides: Partial<BenchCommand>): BenchCommand {
7311
return {
7412
command: "bench",
@@ -108,7 +46,7 @@ ${expectLine}
10846
}
10947

11048
test("runBench - passing gate exits 0 and writes a valid report", async () => {
111-
const target = await spawnTarget();
49+
const target = await spawnBenchmarkTarget();
11250
try {
11351
const file = await writeSuite(
11452
inboxSuite(target.url, ' successRate: ">= 99%"'),
@@ -177,7 +115,7 @@ test("withUserAgent - does not override an explicit User-Agent", async () => {
177115
});
178116

179117
test("runBench - failing gate exits 1", async () => {
180-
const target = await spawnTarget();
118+
const target = await spawnBenchmarkTarget();
181119
try {
182120
// An impossible latency threshold makes the gate fail.
183121
const file = await writeSuite(
@@ -198,7 +136,7 @@ test("runBench - failing gate exits 1", async () => {
198136
});
199137

200138
test("runBench - dry run prints a plan and sends nothing", async () => {
201-
const target = await spawnTarget();
139+
const target = await spawnBenchmarkTarget();
202140
try {
203141
const file = await writeSuite(
204142
inboxSuite(target.url, ' successRate: ">= 99%"'),
@@ -346,7 +284,7 @@ test("runBench - refuses an inbox destination off the gated target (exit 2)", as
346284
// A loopback target passes the gate, but an explicit public `inbox:` is the
347285
// actual load destination; it must be gated too, or production could be
348286
// benchmarked through the back door.
349-
const target = await spawnTarget();
287+
const target = await spawnBenchmarkTarget();
350288
try {
351289
const file = await writeSuite(`version: 1
352290
target: ${target.url.href}

test/bench/fixture.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import {
2+
createFederation,
3+
generateCryptoKeyPair,
4+
MemoryKvStore,
5+
} from "@fedify/fedify";
6+
import { Create, Endpoints, Person } from "@fedify/vocab";
7+
8+
/** A running local benchmark-mode target used by `fedify bench` tests. */
9+
export interface BenchmarkTargetFixture {
10+
/** The target base URL. */
11+
readonly url: URL;
12+
/** The last User-Agent observed on signed inbox load. */
13+
readonly inboxUserAgent: () => string | null;
14+
/** The HTTP requests the fixture target has received. */
15+
readonly requests: () => readonly { method: string; path: string }[];
16+
/** Stops the fixture server. */
17+
readonly close: () => Promise<void>;
18+
}
19+
20+
/**
21+
* Starts a local Fedify target in benchmark mode.
22+
*
23+
* The app exposes one actor, `alice`, with a personal and shared inbox, and an
24+
* inbox listener that accepts signed `Create` activities. It is intentionally
25+
* small but exercises the same WebFinger, actor discovery, signature
26+
* verification, and inbox paths that `fedify bench` drives.
27+
* @returns The running fixture.
28+
*/
29+
export function spawnBenchmarkTarget(): BenchmarkTargetFixture {
30+
const federation = createFederation<void>({
31+
kv: new MemoryKvStore(),
32+
benchmarkMode: true,
33+
});
34+
let keyPairs: CryptoKeyPair[] | undefined;
35+
const requests: { method: string; path: string }[] = [];
36+
federation
37+
.setActorDispatcher("/users/{identifier}", async (ctx, identifier) => {
38+
if (identifier !== "alice") return null;
39+
const pairs = await ctx.getActorKeyPairs(identifier);
40+
return new Person({
41+
id: ctx.getActorUri(identifier),
42+
preferredUsername: identifier,
43+
inbox: ctx.getInboxUri(identifier),
44+
endpoints: new Endpoints({ sharedInbox: ctx.getInboxUri() }),
45+
publicKey: pairs[0]?.cryptographicKey,
46+
assertionMethods: pairs.map((p) => p.multikey),
47+
});
48+
})
49+
.mapHandle((_ctx, username) => (username === "alice" ? "alice" : null))
50+
.setKeyPairsDispatcher(async (_ctx, identifier) => {
51+
if (identifier !== "alice") return [];
52+
keyPairs ??= [
53+
await generateCryptoKeyPair("RSASSA-PKCS1-v1_5"),
54+
await generateCryptoKeyPair("Ed25519"),
55+
];
56+
return keyPairs;
57+
});
58+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(
59+
Create,
60+
() => {},
61+
);
62+
let inboxUserAgent: string | null = null;
63+
const abort = new AbortController();
64+
const server = Deno.serve(
65+
{
66+
port: 0,
67+
hostname: "127.0.0.1",
68+
signal: abort.signal,
69+
onListen: () => {},
70+
},
71+
(request: Request) => {
72+
const url = new URL(request.url);
73+
requests.push({ method: request.method, path: url.pathname });
74+
if (request.method === "POST") {
75+
inboxUserAgent = request.headers.get("user-agent");
76+
}
77+
return federation.fetch(request, { contextData: undefined });
78+
},
79+
);
80+
const address = server.addr as Deno.NetAddr;
81+
return {
82+
url: new URL(`http://${address.hostname}:${address.port}/`),
83+
inboxUserAgent: () => inboxUserAgent,
84+
requests: () => requests.slice(),
85+
close: async () => {
86+
abort.abort();
87+
await server.finished.catch(() => {});
88+
},
89+
};
90+
}

0 commit comments

Comments
 (0)