Skip to content

Commit 73c642e

Browse files
sij411claude
andcommitted
Address review feedback on smoke test suite
- Extract parseRecipient helper in backdoor.ts to reduce duplication - Extract pollHarnessInbox helper in orchestrator.ts to reduce duplication and centralize inbox polling - Make poll() catch transient errors and retry instead of aborting, include last error message on timeout - Fix workflow name reference in mastodon.env comment - Pin Deno Docker image to 2.7.1 to match mise.toml Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 59f4dc0 commit 73c642e

4 files changed

Lines changed: 43 additions & 41 deletions

File tree

test/smoke/harness/backdoor.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ function json(data: unknown, status = 200): Response {
99
});
1010
}
1111

12+
// Build recipient manually — Mastodon's WebFinger requires HTTPS but our
13+
// harness only has HTTP. Parse the handle (user@domain) to construct the
14+
// actor URI and inbox URL directly.
15+
function parseRecipient(
16+
handle: string,
17+
): { id: URL; inboxId: URL; actorId: URL } {
18+
const [user, domain] = handle.split("@");
19+
const inboxId = new URL(`http://${domain}/users/${user}/inbox`);
20+
// Mastodon generates https:// actor URIs; use that as the canonical id
21+
const actorId = new URL(`https://${domain}/users/${user}`);
22+
return { id: actorId, inboxId, actorId };
23+
}
24+
1225
export async function handleBackdoor(
1326
request: Request,
1427
federation: Federation<void>,
@@ -43,14 +56,7 @@ export async function handleBackdoor(
4356
undefined as void,
4457
);
4558

46-
// Build the recipient manually — Mastodon's WebFinger requires HTTPS
47-
// but we only have HTTP. Parse the handle (user@domain) to construct
48-
// the actor URI and inbox URL directly.
49-
const [user, domain] = to.split("@");
50-
const inboxUrl = new URL(`http://${domain}/users/${user}/inbox`);
51-
// Mastodon generates https:// actor URIs; use that as the canonical id
52-
const actorId = new URL(`https://${domain}/users/${user}`);
53-
const recipient = { id: actorId, inboxId: inboxUrl };
59+
const { actorId, ...recipient } = parseRecipient(to);
5460

5561
const noteId = crypto.randomUUID();
5662
const note = new Note({
@@ -92,10 +98,7 @@ export async function handleBackdoor(
9298
undefined as void,
9399
);
94100

95-
const [user, domain] = target.split("@");
96-
const inboxUrl = new URL(`http://${domain}/users/${user}/inbox`);
97-
const actorId = new URL(`https://${domain}/users/${user}`);
98-
const recipient = { id: actorId, inboxId: inboxUrl };
101+
const { actorId, ...recipient } = parseRecipient(target);
99102

100103
const follow = new Follow({
101104
id: new URL(
@@ -128,10 +131,7 @@ export async function handleBackdoor(
128131
undefined as void,
129132
);
130133

131-
const [user, domain] = target.split("@");
132-
const inboxUrl = new URL(`http://${domain}/users/${user}/inbox`);
133-
const actorId = new URL(`https://${domain}/users/${user}`);
134-
const recipient = { id: actorId, inboxId: inboxUrl };
134+
const { actorId, ...recipient } = parseRecipient(target);
135135

136136
const undo = new Undo({
137137
id: new URL(

test/smoke/mastodon/docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ services:
2929
# Fedify test harness — runs inside the Docker network so Mastodon's
3030
# Resolv::DNS can resolve "fedify-harness" natively via Docker DNS.
3131
fedify-harness:
32-
image: denoland/deno:latest
32+
image: denoland/deno:2.7.1
3333
working_dir: /workspace
3434
volumes:
3535
- ../../../:/workspace

test/smoke/mastodon/mastodon.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Mastodon configuration for smoke tests.
22
# SECRET_KEY_BASE, OTP_SECRET, VAPID_*, and ACTIVE_RECORD_ENCRYPTION_*
3-
# are appended by CI (see .github/workflows/smoke.yml).
3+
# are appended by CI (see .github/workflows/smoke-mastodon.yml).
44

55
LOCAL_DOMAIN=mastodon:3000
66
ALTERNATE_DOMAINS=localhost:3000

test/smoke/orchestrator.ts

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,30 @@ async function poll<T>(
3636
fn: () => Promise<T | null>,
3737
): Promise<T> {
3838
const deadline = Date.now() + POLL_TIMEOUT_MS;
39+
let lastError: unknown = null;
3940
while (Date.now() < deadline) {
40-
const result = await fn();
41-
if (result !== null) return result;
41+
try {
42+
const result = await fn();
43+
if (result !== null) return result;
44+
} catch (err) {
45+
lastError = err;
46+
}
4247
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
4348
}
44-
throw new Error(`Timed out waiting for: ${label}`);
49+
const suffix = lastError instanceof Error
50+
? ` (last error: ${lastError.message})`
51+
: "";
52+
throw new Error(`Timed out waiting for: ${label}${suffix}`);
53+
}
54+
55+
function pollHarnessInbox(
56+
activityType: string,
57+
): Promise<{ type: string; id: string }> {
58+
return poll(`${activityType} in harness inbox`, async () => {
59+
const res = await fetch(`${HARNESS_URL}/_test/inbox`);
60+
const items = await res.json() as { type: string; id: string }[];
61+
return items.find((a) => a.type === activityType) ?? null;
62+
});
4563
}
4664

4765
async function serverGet(path: string): Promise<unknown> {
@@ -179,11 +197,7 @@ async function testFollowMastodonToFedify(): Promise<void> {
179197
await assertNotFollowing(accountId, "following");
180198
await serverPost(`/api/v1/accounts/${accountId}/follow`);
181199

182-
await poll("Follow in harness inbox", async () => {
183-
const res = await fetch(`${HARNESS_URL}/_test/inbox`);
184-
const items = await res.json() as { type: string; id: string }[];
185-
return items.find((a) => a.type === "Follow") ?? null;
186-
});
200+
await pollHarnessInbox("Follow");
187201

188202
await poll("follow accepted", async () => {
189203
const rels = await serverGet(
@@ -216,11 +230,7 @@ async function testFollowFedifyToMastodon(): Promise<void> {
216230
return rel?.followed_by ? rel : null;
217231
});
218232

219-
await poll("Accept in harness inbox", async () => {
220-
const res = await fetch(`${HARNESS_URL}/_test/inbox`);
221-
const items = await res.json() as { type: string; id: string }[];
222-
return items.find((a) => a.type === "Accept") ?? null;
223-
});
233+
await pollHarnessInbox("Accept");
224234
}
225235

226236
// ---------------------------------------------------------------------------
@@ -260,11 +270,7 @@ async function testReply(): Promise<void> {
260270
status: replyContent,
261271
});
262272

263-
await poll("Create in harness inbox", async () => {
264-
const res = await fetch(`${HARNESS_URL}/_test/inbox`);
265-
const items = await res.json() as { type: string; id: string }[];
266-
return items.find((a) => a.type === "Create") ?? null;
267-
});
273+
await pollHarnessInbox("Create");
268274
}
269275

270276
// ---------------------------------------------------------------------------
@@ -277,11 +283,7 @@ async function testUnfollowMastodonFromFedify(): Promise<void> {
277283
const accountId = await lookupFedifyAccount();
278284
await serverPost(`/api/v1/accounts/${accountId}/unfollow`);
279285

280-
await poll("Undo in harness inbox", async () => {
281-
const res = await fetch(`${HARNESS_URL}/_test/inbox`);
282-
const items = await res.json() as { type: string; id: string }[];
283-
return items.find((a) => a.type === "Undo") ?? null;
284-
});
286+
await pollHarnessInbox("Undo");
285287

286288
await poll("unfollow confirmed", async () => {
287289
const rels = await serverGet(

0 commit comments

Comments
 (0)