diff --git a/extensions/matrix/src/matrix/monitor/reply-context.test.ts b/extensions/matrix/src/matrix/monitor/reply-context.test.ts index a2e3fc1a833..9fb98f7d809 100644 --- a/extensions/matrix/src/matrix/monitor/reply-context.test.ts +++ b/extensions/matrix/src/matrix/monitor/reply-context.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it, vi } from "vitest"; import { createMatrixReplyContextResolver, summarizeMatrixReplyEvent } from "./reply-context.js"; +import { createPollStartEvent } from "./test-events.js"; import type { MatrixRawEvent } from "./types.js"; describe("matrix reply context", () => { @@ -51,25 +52,9 @@ describe("matrix reply context", () => { }); it("summarizes poll start events from poll content", () => { - expect( - summarizeMatrixReplyEvent({ - event_id: "$poll", - sender: "@alice:example.org", - type: "m.poll.start", - origin_server_ts: Date.now(), - content: { - "m.poll.start": { - question: { "m.text": "Lunch?" }, - kind: "m.poll.disclosed", - max_selections: 1, - answers: [ - { id: "a1", "m.text": "Pizza" }, - { id: "a2", "m.text": "Sushi" }, - ], - }, - }, - } as MatrixRawEvent), - ).toBe("[Poll]\nLunch?\n\n1. Pizza\n2. Sushi"); + expect(summarizeMatrixReplyEvent(createPollStartEvent("$poll"))).toBe( + "[Poll]\nLunch?\n\n1. Pizza\n2. Sushi", + ); }); it("resolves and caches reply context", async () => { diff --git a/extensions/matrix/src/matrix/monitor/test-events.ts b/extensions/matrix/src/matrix/monitor/test-events.ts new file mode 100644 index 00000000000..ef78a472584 --- /dev/null +++ b/extensions/matrix/src/matrix/monitor/test-events.ts @@ -0,0 +1,21 @@ +import type { MatrixRawEvent } from "./types.js"; + +export function createPollStartEvent(eventId: string): MatrixRawEvent { + return { + event_id: eventId, + sender: "@alice:example.org", + type: "m.poll.start", + origin_server_ts: Date.now(), + content: { + "m.poll.start": { + question: { "m.text": "Lunch?" }, + kind: "m.poll.disclosed", + max_selections: 1, + answers: [ + { id: "a1", "m.text": "Pizza" }, + { id: "a2", "m.text": "Sushi" }, + ], + }, + }, + }; +} diff --git a/extensions/matrix/src/matrix/monitor/thread-context.test.ts b/extensions/matrix/src/matrix/monitor/thread-context.test.ts index 54c94e494f8..6ec33b0046b 100644 --- a/extensions/matrix/src/matrix/monitor/thread-context.test.ts +++ b/extensions/matrix/src/matrix/monitor/thread-context.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it, vi } from "vitest"; +import { createPollStartEvent } from "./test-events.js"; import { createMatrixThreadContextResolver, summarizeMatrixThreadStarterEvent, @@ -126,24 +127,8 @@ describe("matrix thread context", () => { }); it("summarizes poll start thread roots from poll content", () => { - expect( - summarizeMatrixThreadStarterEvent({ - event_id: "$root", - sender: "@alice:example.org", - type: "m.poll.start", - origin_server_ts: Date.now(), - content: { - "m.poll.start": { - question: { "m.text": "Lunch?" }, - kind: "m.poll.disclosed", - max_selections: 1, - answers: [ - { id: "a1", "m.text": "Pizza" }, - { id: "a2", "m.text": "Sushi" }, - ], - }, - }, - } as MatrixRawEvent), - ).toBe("[Poll]\nLunch?\n\n1. Pizza\n2. Sushi"); + expect(summarizeMatrixThreadStarterEvent(createPollStartEvent("$root"))).toBe( + "[Poll]\nLunch?\n\n1. Pizza\n2. Sushi", + ); }); }); diff --git a/extensions/matrix/src/matrix/subagent-hooks.test.ts b/extensions/matrix/src/matrix/subagent-hooks.test.ts index 2e19ebfdf4e..863130f3c38 100644 --- a/extensions/matrix/src/matrix/subagent-hooks.test.ts +++ b/extensions/matrix/src/matrix/subagent-hooks.test.ts @@ -645,6 +645,19 @@ describe("handleMatrixSubagentDeliveryTarget", () => { }); describe("concurrent spawns across accounts", () => { + function spawnForAccount(accountId: "ops" | "forge") { + return handleMatrixSubagentSpawning(fakeApi, { + threadRequested: true, + requester: { + channel: "matrix", + accountId, + to: `room:!room-${accountId}:example.org`, + }, + childSessionKey: `agent:${accountId}:subagent:child-${accountId}`, + agentId: `worker-${accountId}`, + }); + } + beforeEach(() => { bindMock.mockReset(); getManagerMock.mockReset(); @@ -664,18 +677,8 @@ describe("concurrent spawns across accounts", () => { .mockResolvedValueOnce({ conversation: { accountId: "forge", conversationId: "$t-forge" } }); const [opsResult, forgeResult] = await Promise.all([ - handleMatrixSubagentSpawning(fakeApi, { - threadRequested: true, - requester: { channel: "matrix", accountId: "ops", to: "room:!room-ops:example.org" }, - childSessionKey: "agent:ops:subagent:child-ops", - agentId: "worker-ops", - }), - handleMatrixSubagentSpawning(fakeApi, { - threadRequested: true, - requester: { channel: "matrix", accountId: "forge", to: "room:!room-forge:example.org" }, - childSessionKey: "agent:forge:subagent:child-forge", - agentId: "worker-forge", - }), + spawnForAccount("ops"), + spawnForAccount("forge"), ]); expect(opsResult).toMatchObject({ status: "ok", threadBindingReady: true }); @@ -709,18 +712,8 @@ describe("concurrent spawns across accounts", () => { .mockResolvedValueOnce({ conversation: { accountId: "forge", conversationId: "$t-forge" } }); const [opsResult, forgeResult] = await Promise.all([ - handleMatrixSubagentSpawning(fakeApi, { - threadRequested: true, - requester: { channel: "matrix", accountId: "ops", to: "room:!room-ops:example.org" }, - childSessionKey: "agent:ops:subagent:child-ops", - agentId: "worker-ops", - }), - handleMatrixSubagentSpawning(fakeApi, { - threadRequested: true, - requester: { channel: "matrix", accountId: "forge", to: "room:!room-forge:example.org" }, - childSessionKey: "agent:forge:subagent:child-forge", - agentId: "worker-forge", - }), + spawnForAccount("ops"), + spawnForAccount("forge"), ]); expect(opsResult).toEqual( diff --git a/extensions/matrix/src/startup-maintenance.test.ts b/extensions/matrix/src/startup-maintenance.test.ts index 491245738d4..7c8538515a7 100644 --- a/extensions/matrix/src/startup-maintenance.test.ts +++ b/extensions/matrix/src/startup-maintenance.test.ts @@ -67,6 +67,31 @@ function createSuccessfulMatrixMigrationDeps() { }; } +function createWarningOnlyMaintenanceHarness() { + return { + deps: { + maybeCreateMatrixMigrationSnapshot: vi.fn(), + autoMigrateLegacyMatrixState: vi.fn(), + autoPrepareLegacyMatrixCrypto: vi.fn(), + }, + log: { + info: vi.fn(), + warn: vi.fn(), + }, + }; +} + +function expectWarningOnlyMaintenanceSkipped( + harness: ReturnType, +) { + expect(harness.deps.maybeCreateMatrixMigrationSnapshot).not.toHaveBeenCalled(); + expect(harness.deps.autoMigrateLegacyMatrixState).not.toHaveBeenCalled(); + expect(harness.deps.autoPrepareLegacyMatrixCrypto).not.toHaveBeenCalled(); + expect(harness.log.info).toHaveBeenCalledWith( + "matrix: migration remains in a warning-only state; no pre-migration snapshot was needed yet", + ); +} + describe("runMatrixStartupMaintenance", () => { beforeEach(() => { legacyCryptoInspectorAvailability.available = true; @@ -104,30 +129,19 @@ describe("runMatrixStartupMaintenance", () => { it("skips snapshot creation when startup only has warning-only migration state", async () => { await withTempHome(async (home) => { await seedLegacyMatrixState(home); - const maybeCreateMatrixMigrationSnapshotMock = vi.fn(); - const autoMigrateLegacyMatrixStateMock = vi.fn(); - const autoPrepareLegacyMatrixCryptoMock = vi.fn(); - const info = vi.fn(); - const warn = vi.fn(); + const harness = createWarningOnlyMaintenanceHarness(); await runMatrixStartupMaintenance({ cfg: makeMatrixStartupConfig(false), env: process.env, - deps: { - maybeCreateMatrixMigrationSnapshot: maybeCreateMatrixMigrationSnapshotMock as never, - autoMigrateLegacyMatrixState: autoMigrateLegacyMatrixStateMock as never, - autoPrepareLegacyMatrixCrypto: autoPrepareLegacyMatrixCryptoMock as never, - }, - log: { info, warn }, + deps: harness.deps as never, + log: harness.log, }); - expect(maybeCreateMatrixMigrationSnapshotMock).not.toHaveBeenCalled(); - expect(autoMigrateLegacyMatrixStateMock).not.toHaveBeenCalled(); - expect(autoPrepareLegacyMatrixCryptoMock).not.toHaveBeenCalled(); - expect(info).toHaveBeenCalledWith( - "matrix: migration remains in a warning-only state; no pre-migration snapshot was needed yet", + expectWarningOnlyMaintenanceSkipped(harness); + expect(harness.log.warn).toHaveBeenCalledWith( + expect.stringContaining("could not be resolved yet"), ); - expect(warn).toHaveBeenCalledWith(expect.stringContaining("could not be resolved yet")); }); }); @@ -136,30 +150,17 @@ describe("runMatrixStartupMaintenance", () => { await withTempHome(async (home) => { await seedLegacyMatrixCrypto(home); - const maybeCreateMatrixMigrationSnapshotMock = vi.fn(); - const autoMigrateLegacyMatrixStateMock = vi.fn(); - const autoPrepareLegacyMatrixCryptoMock = vi.fn(); - const info = vi.fn(); - const warn = vi.fn(); + const harness = createWarningOnlyMaintenanceHarness(); await runMatrixStartupMaintenance({ cfg: makeMatrixStartupConfig(), env: process.env, - deps: { - maybeCreateMatrixMigrationSnapshot: maybeCreateMatrixMigrationSnapshotMock as never, - autoMigrateLegacyMatrixState: autoMigrateLegacyMatrixStateMock as never, - autoPrepareLegacyMatrixCrypto: autoPrepareLegacyMatrixCryptoMock as never, - }, - log: { info, warn }, + deps: harness.deps as never, + log: harness.log, }); - expect(maybeCreateMatrixMigrationSnapshotMock).not.toHaveBeenCalled(); - expect(autoMigrateLegacyMatrixStateMock).not.toHaveBeenCalled(); - expect(autoPrepareLegacyMatrixCryptoMock).not.toHaveBeenCalled(); - expect(info).toHaveBeenCalledWith( - "matrix: migration remains in a warning-only state; no pre-migration snapshot was needed yet", - ); - expect(warn).toHaveBeenCalledWith( + expectWarningOnlyMaintenanceSkipped(harness); + expect(harness.log.warn).toHaveBeenCalledWith( "matrix: legacy encrypted-state warnings:\n- Legacy Matrix encrypted state was detected, but the Matrix crypto inspector is unavailable.", ); });