fix: satisfy Buzz CI boundaries (#113419)

This commit is contained in:
Shakker
2026-07-26 14:06:41 +01:00
committed by Shakker
parent 04cafe601b
commit 8f524a23b2
6 changed files with 58 additions and 43 deletions
+20 -6
View File
@@ -1,6 +1,8 @@
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { finalizeEvent, getPublicKey, type Event, type Filter } from "nostr-tools";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useAutoCleanupTempDirTracker } from "../../../test/helpers/temp-dir.js";
const relayMocks = vi.hoisted(() => ({
connect: vi.fn<() => Promise<void>>(),
@@ -68,14 +70,16 @@ const ACCOUNT_ID = "default";
const CHANNEL_ID = "7c4a6d2a-2ed9-4b4e-a5e2-4d705ee9b34c";
const BOT_PUBLIC_KEY = getPublicKey(Uint8Array.from(Buffer.from(PRIVATE_KEY, "hex")));
const SENDER_PUBLIC_KEY = getPublicKey(Uint8Array.from(Buffer.from(SENDER_PRIVATE_KEY, "hex")));
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
const tempDirs = new Set<string>();
let previousStateDir: string | undefined;
let stateDir: string;
describe("Buzz bus lifecycle", () => {
beforeEach(() => {
previousStateDir = process.env.OPENCLAW_STATE_DIR;
stateDir = tempDirs.make("openclaw-buzz-dedupe-");
// openclaw-temp-dir: allow extension tests cannot import root test helpers.
stateDir = mkdtempSync(path.join(tmpdir(), "openclaw-buzz-dedupe-"));
tempDirs.add(stateDir);
process.env.OPENCLAW_STATE_DIR = stateDir;
vi.clearAllMocks();
relayMocks.subscriptions.length = 0;
@@ -106,6 +110,10 @@ describe("Buzz bus lifecycle", () => {
} else {
process.env.OPENCLAW_STATE_DIR = previousStateDir;
}
for (const tempDir of tempDirs) {
rmSync(tempDir, { recursive: true, force: true });
}
tempDirs.clear();
});
it("closes a connected relay when authentication fails", async () => {
@@ -239,9 +247,15 @@ describe("Buzz bus lifecycle", () => {
?.handlers.onevent(event);
await vi.waitFor(() => expect(onMessageError).toHaveBeenCalledWith(expect.any(Error)));
await new Promise<void>((resolve) => setTimeout(resolve, 0));
expect(relayMocks.publish.mock.calls.some(([event]) => event.kind === 0)).toBe(false);
expect(relayMocks.publish.mock.calls.some(([event]) => event.kind === 10_100)).toBe(true);
await new Promise<void>((resolve) => {
setTimeout(resolve, 0);
});
expect(
relayMocks.publish.mock.calls.some(([publishedEvent]) => publishedEvent.kind === 0),
).toBe(false);
expect(
relayMocks.publish.mock.calls.some(([publishedEvent]) => publishedEvent.kind === 10_100),
).toBe(true);
expect(onProfilePublished).toHaveBeenCalledOnce();
expect(onFatalError).not.toHaveBeenCalled();
await bus.close();
+18 -20
View File
@@ -139,25 +139,26 @@ async function sleepWithSignal(delayMs: number, signal?: AbortSignal): Promise<v
signal?.throwIfAborted();
await new Promise<void>((resolve, reject) => {
let settled = false;
let timer: ReturnType<typeof setTimeout> | undefined;
const finish = (error?: unknown) => {
if (settled) {
return;
}
settled = true;
if (timer) {
clearTimeout(timer);
}
clearTimeout(timer);
signal?.removeEventListener("abort", onAbort);
if (error === undefined) {
resolve();
} else {
reject(error);
reject(
error instanceof Error
? error
: new Error("Buzz room membership refresh failed", { cause: error }),
);
}
};
const onAbort = () =>
finish(signal?.reason ?? new Error("Buzz room membership refresh aborted"));
timer = setTimeout(() => finish(), delayMs);
const timer = setTimeout(() => finish(), delayMs);
signal?.addEventListener("abort", onAbort, { once: true });
if (signal?.aborted) {
onAbort();
@@ -175,18 +176,15 @@ async function queryBuzzRoomMemberships(params: {
const memberships = new Map<string, BuzzRoomMembership>();
return await new Promise<Map<string, BuzzRoomMembership>>((resolve, reject) => {
let settled = false;
let subscription: ReturnType<Relay["subscribe"]> | undefined;
let timeout: ReturnType<typeof setTimeout> | undefined;
const subscriptionRef: { current?: ReturnType<Relay["subscribe"]> } = {};
const finish = (error?: unknown) => {
if (settled) {
return;
}
settled = true;
if (timeout) {
clearTimeout(timeout);
}
clearTimeout(timeout);
params.signal?.removeEventListener("abort", onAbort);
subscription?.close("membership snapshot loaded");
subscriptionRef.current?.close("membership snapshot loaded");
if (error === undefined) {
resolve(memberships);
} else {
@@ -199,12 +197,12 @@ async function queryBuzzRoomMemberships(params: {
};
const onAbort = () =>
finish(params.signal?.reason ?? new Error("Buzz room membership query aborted"));
timeout = setTimeout(
const timeout = setTimeout(
() => finish(new Error("Timed out loading Buzz room membership")),
params.timeoutMs ?? MEMBERSHIP_READY_TIMEOUT_MS,
);
params.signal?.addEventListener("abort", onAbort, { once: true });
subscription = params.relay.subscribe(
subscriptionRef.current = params.relay.subscribe(
[
{
kinds: [BUZZ_ROOM_MEMBERSHIP_KIND],
@@ -233,7 +231,7 @@ async function queryBuzzRoomMemberships(params: {
},
);
if (settled) {
subscription.close("membership snapshot loaded");
subscriptionRef.current.close("membership snapshot loaded");
}
if (params.signal?.aborted) {
onAbort();
@@ -384,22 +382,22 @@ async function createBuzzRoomMembershipTracker(params: {
const handleSystemEvent = (event: Event): Promise<void> | undefined => {
if (!markSystemEventSeen(event.id)) {
return;
return undefined;
}
const channelId = event.tags
.find((tag) => tag[0] === "h")?.[1]
?.trim()
.toLowerCase();
if (!channelId) {
return;
return undefined;
}
const membership = memberships.get(channelId);
if (!membership) {
return;
return undefined;
}
const change = parseBuzzRoomMembershipChangeEvent(event, membership);
if (!change) {
return;
return undefined;
}
// System events invalidate membership; the relay-signed roster decides the
// final state. Removals deny immediately, while joins wait for confirmation.
@@ -580,7 +578,7 @@ export async function startBuzzBus(options: {
authTag,
signal,
});
let subscriptions: Array<ReturnType<Relay["subscribe"]>> = [];
const subscriptions: Array<ReturnType<Relay["subscribe"]>> = [];
let stopPresenceHeartbeat = () => {};
const bus: BuzzBus = {
publicKey,
+7 -6
View File
@@ -6,9 +6,7 @@ const PROFILE_QUERY_TIMEOUT_MS = 5_000;
const DEFAULT_CHANNEL_ADD_POLICY = "anyone";
const CHANNEL_ADD_POLICIES = new Set(["anyone", "owner_only", "nobody"]);
export type BuzzProfileSyncResult =
| { status: "unchanged" }
| { status: "published"; eventId: string };
type BuzzProfileSyncResult = { status: "unchanged" } | { status: "published"; eventId: string };
function parseProfileContent(event: Event | undefined): Record<string, unknown> {
if (!event) {
@@ -27,9 +25,12 @@ function parseProfileContent(event: Event | undefined): Record<string, unknown>
function resolveProfileTags(event: Event | undefined, authTag: string[] | undefined): string[][] {
const existingTags = event?.tags ?? [];
if (!authTag) {
return existingTags.map((tag) => [...tag]);
return existingTags.map((tag) => tag.slice());
}
return [...existingTags.filter((tag) => tag[0] !== "auth").map((tag) => [...tag]), [...authTag]];
return [
...existingTags.filter((tag) => tag[0] !== "auth").map((tag) => tag.slice()),
[...authTag],
];
}
function hasConfiguredAuthTag(event: Event | undefined, authTag: string[] | undefined): boolean {
@@ -192,7 +193,7 @@ export async function syncBuzzProfile(params: {
kind: AGENT_PROFILE_KIND,
content: agentContent,
current: currentAgentProfile,
tags: currentAgentProfile?.tags.map((tag) => [...tag]) ?? [],
tags: currentAgentProfile?.tags.map((tag) => tag.slice()) ?? [],
secretKey: params.secretKey,
}),
);
+9 -5
View File
@@ -29,7 +29,11 @@ async function sleepWithSignal(delayMs: number, signal: AbortSignal): Promise<vo
clearTimeout(timer);
signal.removeEventListener("abort", onAbort);
if (error !== undefined) {
reject(error);
reject(
error instanceof Error
? error
: new Error("Buzz room access wait failed", { cause: error }),
);
} else {
resolve();
}
@@ -71,7 +75,7 @@ export async function waitForBuzzRoomAccess(params: {
let settled = false;
let checking = false;
let queuedRetry = false;
let subscription: ReturnType<Relay["subscribe"]> | undefined;
const subscriptionRef: { current?: ReturnType<Relay["subscribe"]> } = {};
let pollTimer: ReturnType<typeof setInterval> | undefined;
const seenEvents = new Set<string>();
@@ -84,7 +88,7 @@ export async function waitForBuzzRoomAccess(params: {
if (pollTimer) {
clearInterval(pollTimer);
}
subscription?.close("room access found");
subscriptionRef.current?.close("room access found");
if (error !== undefined) {
reject(
error instanceof Error
@@ -141,7 +145,7 @@ export async function waitForBuzzRoomAccess(params: {
};
signal.addEventListener("abort", onAbort, { once: true });
subscription = relay.subscribe(
subscriptionRef.current = relay.subscribe(
[
{
kinds: [MEMBER_ADDED_KIND],
@@ -179,7 +183,7 @@ export async function waitForBuzzRoomAccess(params: {
},
);
if (settled) {
subscription.close("room access found");
subscriptionRef.current.close("room access found");
}
});
} finally {
+1 -1
View File
@@ -7,7 +7,7 @@ export const BUZZ_ROOM_SYSTEM_KIND = 40099;
const HEX_PUBLIC_KEY_PATTERN = /^[0-9a-f]{64}$/u;
const MEMBERSHIP_CHANGE_TYPES = new Set(["member_joined", "member_left", "member_removed"]);
export type BuzzRoomMembershipChange = {
type BuzzRoomMembershipChange = {
type: "member_joined" | "member_left" | "member_removed";
targetPublicKey: string;
};
+3 -5
View File
@@ -300,11 +300,10 @@ export function createBuzzSetupWizard(
},
configure: async ({ cfg, prompter, options }) => {
const existingBuzzConfig = cfg.channels?.buzz;
const hasExistingAccessConfig = Boolean(
const hasExistingAccessConfig =
existingBuzzConfig?.groupPolicy !== undefined ||
existingBuzzConfig?.groupAllowFrom !== undefined ||
existingBuzzConfig?.groups !== undefined,
);
existingBuzzConfig?.groups !== undefined;
const useFreshAccessDefaults = !isBuzzSetupConfigured(cfg) && !hasExistingAccessConfig;
const configuredRelayUrl =
existingBuzzConfig?.relayUrl?.trim() || process.env.BUZZ_RELAY_URL?.trim();
@@ -434,8 +433,7 @@ export function createBuzzSetupWizard(
roomId,
{
enabled: configuredGroups[roomId]?.enabled ?? true,
requireMention:
configuredGroups[roomId]?.requireMention ?? (useFreshAccessDefaults ? false : true),
requireMention: configuredGroups[roomId]?.requireMention ?? !useFreshAccessDefaults,
},
]),
),