9dfa06ffee
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
226 lines
7.4 KiB
TypeScript
226 lines
7.4 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import { installMockBridge } from "../helpers/bridge";
|
|
|
|
async function openLocalArchiveSettings(page: import("@playwright/test").Page) {
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
await page.getByTestId("open-settings").click();
|
|
await page.getByTestId("profile-popover-settings").click();
|
|
await expect(page.getByTestId("settings-view")).toBeVisible();
|
|
await page.getByTestId("settings-nav-local-archive").click();
|
|
const card = page.getByTestId("settings-local-archive");
|
|
await expect(card).toBeVisible({ timeout: 10_000 });
|
|
return card;
|
|
}
|
|
|
|
test.describe("observer archive policy — Settings toggle", () => {
|
|
test("fresh identity: observer toggle is enabled and checked by default", async ({
|
|
page,
|
|
}) => {
|
|
// Archive is default-on for all builds. A fresh identity (no stored opt-out)
|
|
// should show the toggle enabled and checked after reconciliation seeds the
|
|
// kind-24200 subscription.
|
|
await installMockBridge(page, {
|
|
saveSubscriptions: [
|
|
{
|
|
scope_type: "owner_p",
|
|
scope_value: "deadbeef".repeat(8),
|
|
kinds: "[24200]",
|
|
},
|
|
],
|
|
});
|
|
|
|
const card = await openLocalArchiveSettings(page);
|
|
const toggle = card.getByTestId("local-archive-observer-toggle");
|
|
await expect(toggle).toBeVisible({ timeout: 5_000 });
|
|
await expect(toggle).toBeEnabled();
|
|
await expect(toggle).toBeChecked();
|
|
});
|
|
|
|
test("toggle click OFF disables, then ON re-enables", async ({ page }) => {
|
|
await installMockBridge(page, {
|
|
saveSubscriptions: [
|
|
{
|
|
scope_type: "owner_p",
|
|
scope_value: "deadbeef".repeat(8),
|
|
kinds: "[24200]",
|
|
},
|
|
],
|
|
});
|
|
|
|
const card = await openLocalArchiveSettings(page);
|
|
const toggle = card.getByTestId("local-archive-observer-toggle");
|
|
await expect(toggle).toBeVisible({ timeout: 5_000 });
|
|
await expect(toggle).toBeChecked();
|
|
|
|
// OFF: removes kind 24200.
|
|
await toggle.click();
|
|
await expect(toggle).not.toBeChecked();
|
|
|
|
// ON again: re-creates the row from empty.
|
|
await toggle.click();
|
|
await expect(toggle).toBeChecked();
|
|
});
|
|
|
|
test("explicit opt-out persists across reload: toggle stays OFF", async ({
|
|
page,
|
|
}) => {
|
|
// Simulate a user who previously clicked OFF: the identity-scoped opt-out
|
|
// is recorded in localStorage ("0") and the owner_p/24200 subscription row
|
|
// is absent. Reconciliation must honour the stored choice and leave the
|
|
// toggle unchecked (user can re-enable via the toggle).
|
|
const MOCK_PUBKEY = "deadbeef".repeat(8);
|
|
await page.addInitScript(
|
|
({ storageKey }) => {
|
|
window.localStorage.setItem(storageKey, "0");
|
|
},
|
|
{
|
|
storageKey: `buzz:observer-archive-default-seeded:${MOCK_PUBKEY}`,
|
|
},
|
|
);
|
|
|
|
await installMockBridge(page, {
|
|
saveSubscriptions: [],
|
|
});
|
|
|
|
const card = await openLocalArchiveSettings(page);
|
|
const toggle = card.getByTestId("local-archive-observer-toggle");
|
|
await expect(toggle).toBeVisible({ timeout: 5_000 });
|
|
await expect(toggle).toBeEnabled();
|
|
await expect(toggle).not.toBeChecked();
|
|
});
|
|
|
|
test("no subscriptions, no stored choice: defaults ON then OFF removes, ON re-creates", async ({
|
|
page,
|
|
}) => {
|
|
// A fresh identity with no stored choice and an empty subscription table
|
|
// must be seeded to ON by reconciliation. Thereafter the toggle must
|
|
// function: OFF removes kind 24200, ON re-creates it.
|
|
await installMockBridge(page, {
|
|
saveSubscriptions: [],
|
|
});
|
|
|
|
const card = await openLocalArchiveSettings(page);
|
|
const toggle = card.getByTestId("local-archive-observer-toggle");
|
|
await expect(toggle).toBeVisible({ timeout: 5_000 });
|
|
|
|
// Default-on: reconciliation seeds the row, toggle must be checked.
|
|
await expect(toggle).toBeChecked();
|
|
|
|
// OFF: removes kind 24200.
|
|
await toggle.click();
|
|
await expect(toggle).not.toBeChecked();
|
|
|
|
// ON again: re-creates the row from empty.
|
|
await toggle.click();
|
|
await expect(toggle).toBeChecked();
|
|
});
|
|
});
|
|
|
|
test.describe("observer archive policy — reconciliation gate", () => {
|
|
test("archive sync reaches subscription path after reconciliation", async ({
|
|
page,
|
|
}) => {
|
|
// The reconciliation gate (useObserverArchiveReconciliation) must resolve
|
|
// successfully for a fresh identity, allowing useArchiveSync to start the
|
|
// ArchiveSyncManager, which calls list_save_subscriptions.
|
|
await installMockBridge(page, {
|
|
saveSubscriptions: [
|
|
{
|
|
scope_type: "owner_p",
|
|
scope_value: "deadbeef".repeat(8),
|
|
kinds: "[24200]",
|
|
},
|
|
],
|
|
});
|
|
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
|
|
// Wait for the channel list (proves AppShell mounted fully).
|
|
await expect(page.getByTestId("channel-general")).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
|
|
// The IPC counter proves the subscription path was reached.
|
|
await page.waitForFunction(
|
|
() => {
|
|
const counters = (window as Record<string, unknown>)
|
|
.__BUZZ_E2E_IPC_COUNTERS__ as Record<string, number> | undefined;
|
|
return (counters?.list_save_subscriptions ?? 0) > 0;
|
|
},
|
|
null,
|
|
{ timeout: 10_000 },
|
|
);
|
|
|
|
const count = await page.evaluate(() => {
|
|
const counters = (window as Record<string, unknown>)
|
|
.__BUZZ_E2E_IPC_COUNTERS__ as Record<string, number> | undefined;
|
|
return counters?.list_save_subscriptions ?? 0;
|
|
});
|
|
expect(count).toBeGreaterThan(0);
|
|
|
|
// The reconciliation gate must also result in a real `#p` + kind-24200
|
|
// live REQ filter.
|
|
const hasOwnerKindSubscription = await page.evaluate(
|
|
(ownerPubkey) =>
|
|
(
|
|
window as Window & {
|
|
__BUZZ_E2E_HAS_MOCK_OWNER_KIND_SUBSCRIPTION__?: (input: {
|
|
ownerPubkey: string;
|
|
kind: number;
|
|
}) => boolean;
|
|
}
|
|
).__BUZZ_E2E_HAS_MOCK_OWNER_KIND_SUBSCRIPTION__?.({
|
|
ownerPubkey,
|
|
kind: 24200,
|
|
}) ?? false,
|
|
"deadbeef".repeat(8),
|
|
);
|
|
expect(hasOwnerKindSubscription).toBe(true);
|
|
});
|
|
|
|
test("fresh install with empty subscriptions: reconciliation seeds kind 24200", async ({
|
|
page,
|
|
}) => {
|
|
// A fresh install with no owner_p/24200 row must end up with one after
|
|
// startup reconciliation runs — the actual production repair path.
|
|
await installMockBridge(page, {
|
|
saveSubscriptions: [],
|
|
});
|
|
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
await expect(page.getByTestId("channel-general")).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
|
|
await expect
|
|
.poll(
|
|
() =>
|
|
page.evaluate(
|
|
(ownerPubkey) =>
|
|
(
|
|
window as Window & {
|
|
__BUZZ_E2E_HAS_MOCK_OWNER_KIND_SUBSCRIPTION__?: (input: {
|
|
ownerPubkey: string;
|
|
kind: number;
|
|
}) => boolean;
|
|
}
|
|
).__BUZZ_E2E_HAS_MOCK_OWNER_KIND_SUBSCRIPTION__?.({
|
|
ownerPubkey,
|
|
kind: 24200,
|
|
}) ?? false,
|
|
"deadbeef".repeat(8),
|
|
),
|
|
{ timeout: 10_000 },
|
|
)
|
|
.toBe(true);
|
|
|
|
const commands = await page.evaluate(
|
|
() =>
|
|
(window as Window & { __BUZZ_E2E_COMMANDS__?: string[] })
|
|
.__BUZZ_E2E_COMMANDS__ ?? [],
|
|
);
|
|
expect(commands).toContain("merge_save_subscription_kinds");
|
|
});
|
|
});
|