Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions desktop/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default defineConfig({
name: "smoke",
testMatch: [
"**/smoke.spec.ts",
"**/mock-subscription-readiness.spec.ts",
"**/owned-agent-discovery.spec.ts",
"**/thread-head-stale-edit.spec.ts",
"**/sidebar-offcanvas-rail.spec.ts",
Expand Down
87 changes: 38 additions & 49 deletions desktop/src/testing/e2eBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,13 @@ import {
subscribeControlResults,
} from "@/features/agents/observerRelayStore";
import { switchManagedAgentModel } from "@/shared/api/agentControl";
import { getAudioMediaLoadSchedulerSnapshot } from "@/features/messages/lib/audioMediaLoadScheduler";
import { mockSearchHitMatches } from "./e2eBridgeSearch.ts";
import { selectMockHistory } from "./e2eBridgeHistory.ts";
import {
createMockSubscription,
hasMockSubscription,
} from "./e2eBridgeSubscriptions.ts";
export { mockSearchHitMatches };
import type { ConnectionState } from "@/shared/api/relayClientShared";
import type {
Expand Down Expand Up @@ -1085,14 +1090,7 @@ type MockManagedAgentRuntimeRow = {
type WsHandler = (message: unknown) => void;
const GLOBAL_MOCK_SUBSCRIPTION = "*";

type MockSubscription = {
channelIds: string[];
kinds: number[] | null;
/** `#p` values from the REQ filters, if any — lets specs assert an
* owner-scoped live subscription (e.g. the observer-archive `24200`
* reconciliation gate) independently of channel-scoped ones. */
ownerPubkeys: string[];
};
type MockSubscription = ReturnType<typeof createMockSubscription>;

type MockFilter = {
"#a"?: string[];
Expand Down Expand Up @@ -1275,6 +1273,7 @@ declare global {
__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?: (input: {
channelName: string;
kind?: number;
exactChannel?: boolean;
}) => boolean;
__BUZZ_E2E_HAS_MOCK_OWNER_KIND_SUBSCRIPTION__?: (input: {
ownerPubkey: string;
Expand Down Expand Up @@ -1609,6 +1608,8 @@ declare global {
__BUZZ_E2E_LINK_PREVIEW_UPLOAD_STARTS__?: number;
/** Hold renderer-owned media fetches until their cancellation command. */
__BUZZ_E2E_HOLD_MEDIA_FETCHES__?: boolean;
/** Real scheduler ownership, including work not yet admitted to native fetch. */
__BUZZ_E2E_AUDIO_LOAD_STATE__?: typeof getAudioMediaLoadSchedulerSnapshot;
/** Exact active/peak native media-fetch ownership for scheduler tests. */
__BUZZ_E2E_MEDIA_FETCH_STATE__?: { active: number; peak: number };
/** Object-URL lifecycle counters installed by the audio E2E regression. */
Expand Down Expand Up @@ -5040,22 +5041,19 @@ function emitMockGlobalEvent(event: RelayEvent) {
}
}

function hasMockLiveSubscription(channelId: string, kind?: number) {
for (const socket of mockSockets.values()) {
for (const subscription of socket.subscriptions.values()) {
if (
(subscription.channelIds.includes(channelId) ||
subscription.channelIds.includes(GLOBAL_MOCK_SUBSCRIPTION)) &&
(kind === undefined ||
!subscription.kinds ||
subscription.kinds.includes(kind))
) {
return true;
}
}
}

return false;
function hasMockLiveSubscription(
channelId: string,
kind?: number,
exactChannel = false,
) {
return [...mockSockets.values()].some((socket) =>
hasMockSubscription(
socket.subscriptions.values(),
channelId,
kind,
exactChannel,
),
);
}

/**
Expand Down Expand Up @@ -10862,43 +10860,29 @@ function sendToMockSocket(args: {
}

if (subId.startsWith("live-")) {
// Collect channel IDs from all filters in the REQ
const channelIds = new Set<string>();
const kinds = new Set<number>();
const ownerPubkeys = new Set<string>();
for (const f of filters) {
for (const channelId of f["#h"] ?? []) channelIds.add(channelId);
for (const kind of f.kinds ?? []) {
kinds.add(kind);
}
for (const p of f["#p"] ?? []) {
ownerPubkeys.add(p);
}
}
const subscription = createMockSubscription(filters);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Bind the correlation regression to this REQ/socket boundary. The new tests call createMockSubscription(filters) directly, so they verify the helper but not that parsed REQ filters actually reach it here. Mutation evidence shows replacing only this invocation with the prior flattened projection leaves all three new subscription tests green, including the correlation test. Add a regression that sends the two-filter counterexample through the actual mock socket/REQ path and observes exact readiness false, plus a same-filter positive control; reverting this call site to flattening must make that test fail.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 50b7da2. The new registered smoke spec mock-subscription-readiness.spec.ts invokes plugin:websocket|connect and plugin:websocket|send with serialized REQs, requires EOSE, then reads exposed readiness. Locked boot prevents app-owned subscriptions from satisfying the assertion. Both split-filter counterexamples are false; the same-filter positive control is true; CLOSE removes readiness.

Mutation proof: changed only createMockSubscription(filters) at the real sendToMockSocket call site to pass flattened channel/kind unions. The socket test fails because both splitGlobal and splitOtherChannel become true. The mutation was restored before committing.

Validation: rebuilt after each mutation; restored build and all 32 affected smoke tests passed with zero retries before formatting-only commit-hook changes. Exact committed head passed all 6,592 desktop unit tests and required pre-push checks. Hosted CI is separate and pending.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

const onlyChannelId =
channelIds.size === 1
? (channelIds.values().next().value as string)
subscription.channelIds.length === 1 &&
subscription.channelIds[0] !== GLOBAL_MOCK_SUBSCRIPTION
? subscription.channelIds[0]
: undefined;
if (
getConfig()?.mock?.closeChannelLiveSubscriptionOnce &&
!mockClosedChannelLiveSubscription &&
onlyChannelId &&
kinds.has(KIND_CHANNEL_THREAD_SUMMARY)
subscription.kinds?.includes(KIND_CHANNEL_THREAD_SUMMARY)
) {
mockClosedChannelLiveSubscription = true;
sendWsText(socket.handler, ["CLOSED", subId, "rate-limited"]);
return;
}
socket.subscriptions.set(subId, {
channelIds:
channelIds.size > 0 ? [...channelIds] : [GLOBAL_MOCK_SUBSCRIPTION],
kinds: kinds.size > 0 ? [...kinds] : null,
ownerPubkeys: [...ownerPubkeys],
});
socket.subscriptions.set(subId, subscription);
// Live requests still replay stored matches; pacing can admit them after
// a publish. Ephemeral/global fixtures are not channel history.
const history = new Map(
[...channelIds].map((id) => [id, getMockMessageStore(id)]),
subscription.channelIds
.filter((id) => id !== GLOBAL_MOCK_SUBSCRIPTION)
.map((id) => [id, getMockMessageStore(id)]),
);
for (const event of selectMockHistory(history, filters)) {
sendWsText(socket.handler, ["EVENT", subId, event]);
Expand Down Expand Up @@ -11362,6 +11346,7 @@ export function maybeInstallE2eTauriMocks() {
cancelledMediaFetchIds = new Set<string>();
mockMediaFetchControllers = new Map<string, AbortController>();
window.__BUZZ_E2E_LINK_PREVIEW_UPLOAD_STARTS__ = 0;
window.__BUZZ_E2E_AUDIO_LOAD_STATE__ = getAudioMediaLoadSchedulerSnapshot;
window.__BUZZ_E2E_MEDIA_FETCH_STATE__ = { active: 0, peak: 0 };
window.__BUZZ_E2E_RELEASE_LINK_PREVIEW_METADATA__ = () => {
const queued = deferredLinkPreviewMetadataQueue.splice(0);
Expand Down Expand Up @@ -11618,15 +11603,19 @@ export function maybeInstallE2eTauriMocks() {
createdAt,
);
};
window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__ = ({ channelName, kind }) => {
window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__ = ({
channelName,
kind,
exactChannel,
}) => {
const channel = mockChannels.find(
(candidate) => candidate.name === channelName,
);
if (!channel) {
throw new Error(`Mock channel ${channelName} not found.`);
}

return hasMockLiveSubscription(channel.id, kind);
return hasMockLiveSubscription(channel.id, kind, exactChannel);
};
window.__BUZZ_E2E_HAS_MOCK_OWNER_KIND_SUBSCRIPTION__ = ({
ownerPubkey,
Expand Down
68 changes: 68 additions & 0 deletions desktop/src/testing/e2eBridgeSubscriptions.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
createMockSubscription,
hasMockSubscription,
} from "./e2eBridgeSubscriptions.ts";

const ready = (filters, channel = "channel", kind = 9, exact = true) =>
hasMockSubscription([createMockSubscription(filters)], channel, kind, exact);

test("channel readiness rejects global-only, wrong-channel and wrong-kind REQs", () => {
for (const filter of [
{ kinds: [9] },
{ "#h": [], kinds: [9] },
{ "#h": ["other"], kinds: [9] },
{ "#h": ["channel"], kinds: [30078] },
])
assert.equal(ready([filter]), false);
assert.equal(hasMockSubscription([], "channel", 9, true), false);
for (const kinds of [[9], [7, 9], undefined]) {
assert.equal(ready([{ "#h": ["channel"], kinds }]), true);
}
const emptyKinds = [
createMockSubscription([{ "#h": ["channel"], kinds: [] }]),
];
assert.equal(hasMockSubscription(emptyKinds, "channel", 9, true), false);
assert.equal(
hasMockSubscription(emptyKinds, "channel", undefined, true),
false,
);
assert.equal(
ready([
{ "#h": ["channel"], kinds: [] },
{ "#h": ["channel"], kinds: [9] },
]),
true,
);
});

test("REQ storage preserves channel/kind correlation across OR filters", () => {
for (const unrelated of [{ kinds: [9] }, { "#h": ["other"], kinds: [9] }]) {
const filters = [{ "#h": ["channel"], kinds: [30078] }, unrelated];
const stored = createMockSubscription(filters);
assert.deepEqual(stored.filters, filters);
assert.equal(hasMockSubscription([stored], "channel", 9, true), false);
assert.equal(hasMockSubscription([stored], "channel", 30078, true), true);
assert.equal(ready([...filters, { "#h": ["channel"], kinds: [9] }]), true);
}
});

test("legacy readiness and explicit global queries retain their semantics", () => {
const global = [createMockSubscription([{ kinds: [30078] }])];
assert.equal(hasMockSubscription(global, "channel"), true);
assert.equal(hasMockSubscription(global, "channel", 9), false);
assert.equal(hasMockSubscription(global, "channel", 30078), true);
assert.equal(hasMockSubscription(global, "*", 30078), true);
assert.equal(hasMockSubscription(global, "*", 9), false);
assert.equal(hasMockSubscription(global, "channel", 30078, true), false);
assert.equal(
ready(
[{ "#h": ["channel"], kinds: [30078] }, { kinds: [9] }],
"channel",
9,
false,
),
true,
);
});
50 changes: 50 additions & 0 deletions desktop/src/testing/e2eBridgeSubscriptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
type SubscriptionFilter = {
"#h"?: readonly string[];
"#p"?: readonly string[];
kinds?: readonly number[];
};

/** Preserve raw REQ correlation alongside legacy mock delivery projections. */
export function createMockSubscription(filters: readonly SubscriptionFilter[]) {
const channelIds = [
...new Set(filters.flatMap((filter) => filter["#h"] ?? [])),
];
const kinds = [...new Set(filters.flatMap((filter) => filter.kinds ?? []))];
return {
filters,
channelIds: channelIds.length ? channelIds : ["*"],
kinds: kinds.length ? kinds : null,
ownerPubkeys: [...new Set(filters.flatMap((filter) => filter["#p"] ?? []))],
};
}

/** Test readiness must distinguish a channel consumer from unrelated global REQs. */
export function hasMockSubscription(
subscriptions: Iterable<ReturnType<typeof createMockSubscription>>,
channelId: string,
kind?: number,
exactChannel = false,
): boolean {
for (const subscription of subscriptions) {
if (exactChannel) {
if (
subscription.filters.some(
(filter) =>
filter["#h"]?.includes(channelId) &&
(filter.kinds === undefined ||
(filter.kinds.length > 0 &&
(kind === undefined || filter.kinds.includes(kind)))),
)
)
return true;
} else if (
(subscription.channelIds.includes(channelId) ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Require the channel and kind to match within one original REQ filter. The bridge currently unions every #h and kinds value across an OR-list of filters before calling this predicate. As a result, [{"#h":["channel"],"kinds":[30078]},{"kinds":[9]}] is flattened to channelIds=["channel"], kinds=[30078,9], and this exact-channel check returns true even though no filter requested channel-scoped kind 9. I reproduced that result at this exact head. Preserve each filter (or correlated channel/kind pairs) through the bridge and add this two-filter case at that boundary; tests that construct only the already-flattened shape cannot catch the information loss.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in dac0e8c. The live-REQ storage constructor now retains the original filters, and exact readiness requires channel and kind in the same filter. The regression passes both global-kind and other-channel counterexamples through that actual constructor. Legacy nonexact/wildcard readiness and event-delivery projections remain unchanged.

Validation: all 6,592 desktop unit tests, formatting/static checks and TypeScript passed via the commit/push hooks. A fresh E2E build and all 31 affected smoke tests passed with zero retries before the formatting-only hook changes. Please re-review the corrected boundary.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

subscription.channelIds.includes("*")) &&
(kind === undefined ||
!subscription.kinds ||
subscription.kinds.includes(kind))
)
return true;
}
return false;
}
5 changes: 5 additions & 0 deletions desktop/tests/e2e/channel-activity-popover.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,14 @@ async function waitForMockLiveSubscription(page: Page, channelName: string) {
window as Window & {
__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?: (input: {
channelName: string;
kind: number;
exactChannel: boolean;
}) => boolean;
}
).__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({
channelName: name,
kind: 9,
exactChannel: true,
}) ?? false,
channelName,
),
Expand Down Expand Up @@ -184,6 +188,7 @@ async function seedChannelActivity(
await page.getByTestId("channel-random").click();
await expect(page.getByTestId("chat-title")).toHaveText("random");

await waitForMockLiveSubscription(page, "general");
const unreadAt = Math.floor(Date.now() / 1000) + 60;
await emitMockMessage(
page,
Expand Down
10 changes: 8 additions & 2 deletions desktop/tests/e2e/channel-mute.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,15 @@ async function waitForMockLiveSubscription(
window as Window & {
__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?: (input: {
channelName: string;
kind: number;
exactChannel: boolean;
}) => boolean;
}
).__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({ channelName: ch }) ??
false,
).__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({
channelName: ch,
kind: 9,
exactChannel: true,
}) ?? false,
{ ch: channelName },
);
})
Expand Down Expand Up @@ -124,6 +129,7 @@ test.describe("channel muting", () => {

await page.getByTestId("channel-random").click();
await expect(page.getByTestId("chat-title")).toHaveText("random");
await waitForMockLiveSubscription(page, "engineering");

await page.evaluate(
({ pubkey, mockPubkey }) => {
Expand Down
10 changes: 10 additions & 0 deletions desktop/tests/e2e/inbox-reactions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,16 @@ test("inbox reaction on a thread-reply mention persists after refetch", async ({

const selectedMessage = page.getByTestId("home-inbox-selected-message");

// Detail renders from history before the paced background consumer is ready.
// Exercise live delivery only once this channel actually requests kind 7.
await page.waitForFunction(() =>
window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({
channelName: "general",
kind: 7,
exactChannel: true,
}),
);

// Deliver a live reaction with the real add_reaction wire shape: `e` target,
// no `h` channel tag. The Inbox must render it without waiting for another
// message or a context refetch.
Expand Down
Loading
Loading