mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
237 lines
7.0 KiB
TypeScript
237 lines
7.0 KiB
TypeScript
import type { Page } from "@playwright/test";
|
|
|
|
export const TEST_IDENTITIES = {
|
|
tyler: {
|
|
privateKey:
|
|
"3dbaebadb5dfd777ff25149ee230d907a15a9e1294b40b830661e65bb42f6c03",
|
|
pubkey: "e5ebc6cdb579be112e336cc319b5989b4bb6af11786ea90dbe52b5f08d741b34",
|
|
username: "tyler",
|
|
},
|
|
alice: {
|
|
privateKey:
|
|
"3fa69cbac1dcb9b7b6ac83117c74bd23bb1e717fe8fc7cfda67b47bb4323383d",
|
|
pubkey: "953d3363262e86b770419834c53d2446409db6d918a57f8f339d495d54ab001f",
|
|
username: "alice",
|
|
},
|
|
bob: {
|
|
privateKey:
|
|
"7667ae87cbc50ac0b2251b115c9c51aca7e2da65301b28ecf82f4e4c5260a6bb",
|
|
pubkey: "bb22a5299220cad76ffd46190ccbeede8ab5dc260faa28b6e5a2cb31b9aff260",
|
|
username: "bob",
|
|
},
|
|
charlie: {
|
|
privateKey:
|
|
"813fc3bb90587a82b2bfee9b833503e7686c7480681850b3d789c6987e997fc8",
|
|
pubkey: "554cef57437abac34522ac2c9f0490d685b72c80478cf9f7ed6f9570ee8624ea",
|
|
username: "charlie",
|
|
},
|
|
outsider: {
|
|
privateKey:
|
|
"91bd673543195c0c78fc74a881545dcc8cd6ea6d0f9f8efb3225d58c4bc70dad",
|
|
pubkey: "df8e91b86fda13a9a67896df77232f7bdab2ba9c3e165378e1ba3d24c13a328e",
|
|
username: "outsider",
|
|
},
|
|
} as const;
|
|
|
|
type BridgeMode = "mock" | "relay";
|
|
|
|
type MockCommandAvailability = {
|
|
available?: boolean;
|
|
command?: string;
|
|
resolvedPath?: string | null;
|
|
};
|
|
|
|
type MockBridgeOptions = {
|
|
acpProvidersCatalog?: Record<string, unknown>[];
|
|
managedAgentPrereqs?: {
|
|
acp?: MockCommandAvailability;
|
|
mcp?: MockCommandAvailability;
|
|
};
|
|
profileReadDelayMs?: number;
|
|
profileReadError?: string;
|
|
profileUpdateError?: string;
|
|
stallWebsocketSends?: boolean;
|
|
// NIP-IA gate inputs — drive the archive-button gate matrix in
|
|
// tests/e2e/identity-archive.spec.ts.
|
|
/**
|
|
* Lowercase-hex pubkeys returned by `list_archived_identities`. Drives the
|
|
* "Archived on this relay" flair + Unarchive button.
|
|
*/
|
|
archivedIdentities?: string[];
|
|
/**
|
|
* Drives the `is_me` field of `resolve_oa_owner`. When true, the harness
|
|
* reports the active identity as the verified NIP-OA owner of the viewee
|
|
* (owner-path branch of the gate).
|
|
*/
|
|
oaOwnerIsMe?: boolean;
|
|
/**
|
|
* Active identity's role in the seeded `mockRelayMembers`. `null` removes
|
|
* the active identity from the membership list entirely (admin-path branch
|
|
* evaluates false).
|
|
*/
|
|
relayRole?: "owner" | "admin" | "member" | null;
|
|
};
|
|
|
|
type BridgeOptions = {
|
|
mode: BridgeMode;
|
|
mock?: MockBridgeOptions;
|
|
relayHttpUrl?: string;
|
|
relayWsUrl?: string;
|
|
skipOnboardingSeed?: boolean;
|
|
user?: keyof typeof TEST_IDENTITIES;
|
|
};
|
|
|
|
const ONBOARDING_COMPLETION_STORAGE_KEY_PREFIX =
|
|
"sprout-onboarding-complete.v1:";
|
|
const DEFAULT_MOCK_PUBKEY = "deadbeef".repeat(8);
|
|
const DEFAULT_RELAY_WS_URL = "ws://localhost:3000";
|
|
|
|
async function seedOnboardingCompletionForKnownIdentities(page: Page) {
|
|
const pubkeys = [
|
|
DEFAULT_MOCK_PUBKEY,
|
|
...Object.values(TEST_IDENTITIES).map(({ pubkey }) => pubkey),
|
|
];
|
|
await page.addInitScript(
|
|
({ prefix, pubkeys: pubkeysToSeed }) => {
|
|
for (const pubkey of pubkeysToSeed) {
|
|
window.localStorage.setItem(`${prefix}${pubkey}`, "true");
|
|
}
|
|
},
|
|
{ prefix: ONBOARDING_COMPLETION_STORAGE_KEY_PREFIX, pubkeys },
|
|
);
|
|
}
|
|
|
|
async function seedDefaultWorkspace(page: Page, relayWsUrl?: string) {
|
|
await page.addInitScript(
|
|
({ relayUrl }) => {
|
|
const workspaceId = "e2e-default-workspace";
|
|
const workspace = {
|
|
id: workspaceId,
|
|
name: "E2E Test",
|
|
relayUrl,
|
|
addedAt: new Date().toISOString(),
|
|
};
|
|
window.localStorage.setItem(
|
|
"sprout-workspaces",
|
|
JSON.stringify([workspace]),
|
|
);
|
|
window.localStorage.setItem("sprout-active-workspace-id", workspaceId);
|
|
},
|
|
{ relayUrl: relayWsUrl ?? DEFAULT_RELAY_WS_URL },
|
|
);
|
|
}
|
|
|
|
export async function installBridge(page: Page, options: BridgeOptions) {
|
|
const identity =
|
|
options.mode === "relay"
|
|
? TEST_IDENTITIES[options.user ?? "tyler"]
|
|
: undefined;
|
|
|
|
// Always seed a workspace so useWorkspaceInit doesn't show WelcomeSetup.
|
|
// skipOnboardingSeed only controls the onboarding-completion flag.
|
|
await seedDefaultWorkspace(page, options.relayWsUrl);
|
|
if (!options.skipOnboardingSeed) {
|
|
await seedOnboardingCompletionForKnownIdentities(page);
|
|
}
|
|
|
|
await page.addInitScript(
|
|
({ identity: bridgeIdentity, mock, mode, relayHttpUrl, relayWsUrl }) => {
|
|
const notificationLog: Array<{
|
|
body: string | null;
|
|
title: string;
|
|
}> = [];
|
|
const notificationInstances: MockNotification[] = [];
|
|
|
|
class MockNotification extends EventTarget {
|
|
static permission: NotificationPermission = "granted";
|
|
|
|
static async requestPermission(): Promise<NotificationPermission> {
|
|
return MockNotification.permission;
|
|
}
|
|
|
|
body: string | null;
|
|
onclick: ((event: Event) => void) | null = null;
|
|
title: string;
|
|
|
|
constructor(title: string, options?: NotificationOptions) {
|
|
super();
|
|
this.title = title;
|
|
this.body = options?.body ?? null;
|
|
notificationInstances.push(this);
|
|
notificationLog.push({
|
|
body: this.body,
|
|
title: this.title,
|
|
});
|
|
}
|
|
|
|
close() {}
|
|
}
|
|
|
|
Object.defineProperty(window, "Notification", {
|
|
configurable: true,
|
|
value: MockNotification,
|
|
writable: true,
|
|
});
|
|
|
|
const testWindow = window as Window & {
|
|
__SPROUT_E2E__?: Record<string, unknown>;
|
|
__SPROUT_E2E_APP_BADGE_COUNT__?: number;
|
|
__SPROUT_E2E_CLICK_NOTIFICATION__?: (index: number) => boolean;
|
|
__SPROUT_E2E_NOTIFICATIONS__?: Array<{
|
|
body: string | null;
|
|
title: string;
|
|
}>;
|
|
};
|
|
const currentConfig = testWindow.__SPROUT_E2E__ ?? {};
|
|
|
|
testWindow.__SPROUT_E2E__ = {
|
|
...currentConfig,
|
|
identity: bridgeIdentity ?? currentConfig.identity,
|
|
mock,
|
|
mode,
|
|
relayHttpUrl: relayHttpUrl ?? currentConfig.relayHttpUrl,
|
|
relayWsUrl: relayWsUrl ?? currentConfig.relayWsUrl,
|
|
};
|
|
testWindow.__SPROUT_E2E_APP_BADGE_COUNT__ = 0;
|
|
testWindow.__SPROUT_E2E_CLICK_NOTIFICATION__ = (index: number) => {
|
|
const notification = notificationInstances[index];
|
|
if (!notification) {
|
|
return false;
|
|
}
|
|
|
|
const event = new Event("click");
|
|
notification.dispatchEvent(event);
|
|
notification.onclick?.(event);
|
|
return true;
|
|
};
|
|
testWindow.__SPROUT_E2E_NOTIFICATIONS__ = notificationLog;
|
|
},
|
|
{
|
|
identity,
|
|
mock: options.mock,
|
|
mode: options.mode,
|
|
relayHttpUrl: options.relayHttpUrl,
|
|
relayWsUrl: options.relayWsUrl,
|
|
},
|
|
);
|
|
}
|
|
|
|
export async function installMockBridge(
|
|
page: Page,
|
|
mock?: MockBridgeOptions,
|
|
options?: { skipOnboardingSeed?: boolean },
|
|
) {
|
|
await installBridge(page, {
|
|
mode: "mock",
|
|
mock,
|
|
skipOnboardingSeed: options?.skipOnboardingSeed,
|
|
});
|
|
}
|
|
|
|
export async function installRelayBridge(
|
|
page: Page,
|
|
user: keyof typeof TEST_IDENTITIES = "tyler",
|
|
) {
|
|
await installBridge(page, { mode: "relay", user });
|
|
}
|