feat: import Chinese-localized Buzz source snapshot
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
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>
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { hexToBytes } from "@noble/hashes/utils.js";
|
||||
import { finalizeEvent } from "nostr-tools/pure";
|
||||
|
||||
import { installMockBridge } from "../helpers/bridge";
|
||||
|
||||
// Tyler's test identity — from TEST_IDENTITIES.tyler in tests/helpers/bridge.ts
|
||||
const TYLER_PRIVATE_KEY = hexToBytes(
|
||||
"3dbaebadb5dfd777ff25149ee230d907a15a9e1294b40b830661e65bb42f6c03",
|
||||
);
|
||||
const TYLER_PUBKEY =
|
||||
"e5ebc6cdb579be112e336cc319b5989b4bb6af11786ea90dbe52b5f08d741b34";
|
||||
|
||||
const D_TAG = "sync-test-persona";
|
||||
const KIND_PERSONA = 30175;
|
||||
const KIND_DELETION = 5;
|
||||
// The command scopes an inbound event to the community it arrived on. Under the
|
||||
// mock bridge the app subscribes on e2eBridge's DEFAULT_RELAY_WS_URL, so that is
|
||||
// the arrival relay these direct invocations stand in for.
|
||||
const ARRIVAL_RELAY_URL = "ws://localhost:3000";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await installMockBridge(page);
|
||||
});
|
||||
|
||||
async function gotoApp(page: import("@playwright/test").Page) {
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await waitForInvokeBridge(page);
|
||||
await expect(page.getByTestId("open-agents-view")).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForInvokeBridge(page: import("@playwright/test").Page) {
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
const w = window as Window & {
|
||||
__BUZZ_E2E_INVOKE_MOCK_COMMAND__?: unknown;
|
||||
__TAURI_INTERNALS__?: { invoke?: unknown };
|
||||
};
|
||||
return (
|
||||
typeof w.__BUZZ_E2E_INVOKE_MOCK_COMMAND__ === "function" ||
|
||||
typeof w.__TAURI_INTERNALS__?.invoke === "function"
|
||||
);
|
||||
},
|
||||
null,
|
||||
{ timeout: 5_000 },
|
||||
);
|
||||
}
|
||||
|
||||
async function invokeTauri<T>(
|
||||
page: import("@playwright/test").Page,
|
||||
command: string,
|
||||
payload?: Record<string, unknown>,
|
||||
): Promise<T> {
|
||||
await waitForInvokeBridge(page);
|
||||
return page.evaluate(
|
||||
async ({ command: c, payload: p }) => {
|
||||
const w = window as Window & {
|
||||
__BUZZ_E2E_INVOKE_MOCK_COMMAND__?: (
|
||||
c: string,
|
||||
p?: Record<string, unknown>,
|
||||
) => Promise<unknown>;
|
||||
__TAURI_INTERNALS__?: {
|
||||
invoke?: (c: string, p?: Record<string, unknown>) => Promise<unknown>;
|
||||
};
|
||||
};
|
||||
const invoke =
|
||||
w.__BUZZ_E2E_INVOKE_MOCK_COMMAND__ ?? w.__TAURI_INTERNALS__?.invoke;
|
||||
if (!invoke) throw new Error("Mock invoke bridge is unavailable.");
|
||||
return (await invoke(c, p)) as T;
|
||||
},
|
||||
{ command, payload },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a one-shot `agents-data-changed` listener BEFORE the reconcile call
|
||||
* and wait up to 500 ms for it to fire (covers the 200 ms debounce coalesce).
|
||||
* Returns true if the event fired, false on timeout.
|
||||
*
|
||||
* Must be called before the invokeTauri that triggers the emit so the listener
|
||||
* is registered before the event fires — no race.
|
||||
*/
|
||||
async function listenForAgentsDataChanged(
|
||||
page: import("@playwright/test").Page,
|
||||
): Promise<() => Promise<boolean>> {
|
||||
// Inject a Promise into the page that resolves when the event fires.
|
||||
await page.evaluate(() => {
|
||||
(
|
||||
window as Window & { __agentsDataChangedFired?: Promise<boolean> }
|
||||
).__agentsDataChangedFired = new Promise<boolean>((resolve) => {
|
||||
const internals = (
|
||||
window as Window & {
|
||||
__TAURI_INTERNALS__?: {
|
||||
listen?: (event: string, cb: () => void) => Promise<() => void>;
|
||||
};
|
||||
}
|
||||
).__TAURI_INTERNALS__;
|
||||
if (!internals?.listen) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
void internals.listen("agents-data-changed", () => resolve(true));
|
||||
// Timeout guard: 500 ms covers the 200 ms debounce coalesce with margin.
|
||||
setTimeout(() => resolve(false), 500);
|
||||
});
|
||||
});
|
||||
|
||||
// Return a thunk the caller invokes after the reconcile to await the result.
|
||||
return () =>
|
||||
page.evaluate(
|
||||
() =>
|
||||
(window as Window & { __agentsDataChangedFired?: Promise<boolean> })
|
||||
.__agentsDataChangedFired ?? Promise.resolve(false),
|
||||
);
|
||||
}
|
||||
|
||||
test("upsert round-trip: reconcile_inbound_persona_event writes record and emits agents-data-changed", async ({
|
||||
page,
|
||||
}) => {
|
||||
await gotoApp(page);
|
||||
|
||||
const createdAt = Math.floor(Date.now() / 1000);
|
||||
|
||||
// Build + sign the kind:30175 persona event using nostr-tools.
|
||||
const personaEvent = finalizeEvent(
|
||||
{
|
||||
kind: KIND_PERSONA,
|
||||
content: JSON.stringify({
|
||||
display_name: "Sync Test Persona",
|
||||
system_prompt: "You are a sync test.",
|
||||
}),
|
||||
tags: [["d", D_TAG]],
|
||||
created_at: createdAt,
|
||||
},
|
||||
TYLER_PRIVATE_KEY,
|
||||
);
|
||||
|
||||
// Register the listener BEFORE the reconcile call — no race.
|
||||
const awaitFired = await listenForAgentsDataChanged(page);
|
||||
|
||||
// Drive the inbound reconcile path.
|
||||
await invokeTauri(page, "reconcile_inbound_persona_event", {
|
||||
eventJson: JSON.stringify(personaEvent),
|
||||
arrivalRelayUrl: ARRIVAL_RELAY_URL,
|
||||
});
|
||||
|
||||
// Assert the record landed on disk.
|
||||
const personas = await invokeTauri<
|
||||
Array<{ id: string; display_name: string }>
|
||||
>(page, "list_personas");
|
||||
const record = personas.find((p) => p.id === D_TAG);
|
||||
expect(record?.display_name).toBe("Sync Test Persona");
|
||||
|
||||
// Assert the emit fired (debounce settles within 500 ms timeout guard).
|
||||
const fired = await awaitFired();
|
||||
expect(fired).toBe(true);
|
||||
});
|
||||
|
||||
test("tombstone round-trip: reconcile_inbound_persona_event removes record and emits agents-data-changed", async ({
|
||||
page,
|
||||
}) => {
|
||||
await gotoApp(page);
|
||||
|
||||
const upsertCreatedAt = Math.floor(Date.now() / 1000);
|
||||
|
||||
// Step 1: upsert the persona first.
|
||||
const personaEvent = finalizeEvent(
|
||||
{
|
||||
kind: KIND_PERSONA,
|
||||
content: JSON.stringify({
|
||||
display_name: "Sync Test Persona",
|
||||
system_prompt: "You are a sync test.",
|
||||
}),
|
||||
tags: [["d", D_TAG]],
|
||||
created_at: upsertCreatedAt,
|
||||
},
|
||||
TYLER_PRIVATE_KEY,
|
||||
);
|
||||
|
||||
await invokeTauri(page, "reconcile_inbound_persona_event", {
|
||||
eventJson: JSON.stringify(personaEvent),
|
||||
arrivalRelayUrl: ARRIVAL_RELAY_URL,
|
||||
});
|
||||
|
||||
// Step 2: confirm it landed.
|
||||
const afterUpsert = await invokeTauri<Array<{ id: string }>>(
|
||||
page,
|
||||
"list_personas",
|
||||
);
|
||||
expect(afterUpsert.some((p) => p.id === D_TAG)).toBe(true);
|
||||
|
||||
// Step 3: build + sign a kind:5 deletion event. created_at must be strictly
|
||||
// after the upsert so the retention db does not skip it.
|
||||
const tombstoneEvent = finalizeEvent(
|
||||
{
|
||||
kind: KIND_DELETION,
|
||||
content: "",
|
||||
tags: [["a", `${KIND_PERSONA}:${TYLER_PUBKEY}:${D_TAG}`]],
|
||||
created_at: upsertCreatedAt + 1,
|
||||
},
|
||||
TYLER_PRIVATE_KEY,
|
||||
);
|
||||
|
||||
// Register the listener BEFORE the tombstone reconcile call — no race.
|
||||
const awaitFired = await listenForAgentsDataChanged(page);
|
||||
|
||||
await invokeTauri(page, "reconcile_inbound_persona_event", {
|
||||
eventJson: JSON.stringify(tombstoneEvent),
|
||||
arrivalRelayUrl: ARRIVAL_RELAY_URL,
|
||||
});
|
||||
|
||||
// Step 4: assert the record is gone.
|
||||
const afterTombstone = await invokeTauri<Array<{ id: string }>>(
|
||||
page,
|
||||
"list_personas",
|
||||
);
|
||||
expect(afterTombstone.some((p) => p.id === D_TAG)).toBe(false);
|
||||
|
||||
// Step 5: assert the emit fired for the tombstone path too.
|
||||
const fired = await awaitFired();
|
||||
expect(fired).toBe(true);
|
||||
});
|
||||
Reference in New Issue
Block a user