test(FN-2733): expand memory dreams regression coverage

- Add ProjectEngine memory dreams wiring tests for startup ordering, settings-change resync, and unrelated-setting no-op behavior
- Cover degraded-mode startup and settings-update failure paths to ensure sync errors are logged without stopping the engine
- Extend MemoryView Dream Now tests for success, failure toast handling, and disabled loading-state behavior
This commit is contained in:
Fusion
2026-04-28 06:58:49 -07:00
committed by gsxdsm
parent f19ecac8f5
commit 6d5ca2cc91
2 changed files with 184 additions and 1 deletions

View File

@@ -202,7 +202,7 @@ describe("MemoryView", () => {
expect(screen.getByRole("button", { name: "Dream Now" })).toBeInTheDocument(); expect(screen.getByRole("button", { name: "Dream Now" })).toBeInTheDocument();
}); });
it("triggers dream processing and refreshes memory files", async () => { it("clicking Dream Now triggers processing API once and refreshes memory files", async () => {
const triggerDreamNow = vi.fn().mockResolvedValue({}); const triggerDreamNow = vi.fn().mockResolvedValue({});
const reloadMemoryFiles = vi.fn().mockResolvedValue(undefined); const reloadMemoryFiles = vi.fn().mockResolvedValue(undefined);
const addToast = vi.fn(); const addToast = vi.fn();
@@ -232,6 +232,52 @@ describe("MemoryView", () => {
expect(addToast).toHaveBeenCalledWith("Dream processing completed", "success"); expect(addToast).toHaveBeenCalledWith("Dream processing completed", "success");
}); });
it("shows error toast when Dream Now processing fails", async () => {
const triggerDreamNow = vi.fn().mockRejectedValue(new Error("dream failed"));
const addToast = vi.fn();
mockUseMemoryData.mockReturnValue(
createMemoryData({
triggerDreamNow,
memorySettings: {
memoryEnabled: true,
memoryAutoSummarizeEnabled: false,
memoryAutoSummarizeThresholdChars: 50000,
memoryAutoSummarizeSchedule: "0 3 * * *",
memoryDreamsEnabled: true,
memoryDreamsSchedule: "0 4 * * *",
},
}),
);
render(<MemoryView addToast={addToast} />);
await userEvent.click(screen.getByRole("button", { name: "Dream Now" }));
await waitFor(() => {
expect(addToast).toHaveBeenCalledWith("dream failed", "error");
});
});
it("shows Dreaming loading state while processing is running", () => {
mockUseMemoryData.mockReturnValue(
createMemoryData({
dreamRunning: true,
memorySettings: {
memoryEnabled: true,
memoryAutoSummarizeEnabled: false,
memoryAutoSummarizeThresholdChars: 50000,
memoryAutoSummarizeSchedule: "0 3 * * *",
memoryDreamsEnabled: true,
memoryDreamsSchedule: "0 4 * * *",
},
}),
);
render(<MemoryView addToast={vi.fn()} />);
const button = screen.getByRole("button", { name: /Dreaming…/i });
expect(button).toBeDisabled();
});
it("hides Dream Now button when dreams are disabled", () => { it("hides Dream Now button when dreams are disabled", () => {
render(<MemoryView addToast={vi.fn()} />); render(<MemoryView addToast={vi.fn()} />);

View File

@@ -316,6 +316,143 @@ describe("ProjectEngine auto-summarize wiring", () => {
}); });
}); });
describe("ProjectEngine memory dreams wiring", () => {
beforeEach(() => {
vi.clearAllMocks();
const mockStore = createMockStore(baseSettings);
mocks.currentStore = mockStore.store;
});
it("starts cron after memory dreams startup sync", async () => {
const engine = createEngine();
await engine.start();
const cronRunnerStartOrder = mocks.cronRunnerStart.mock.invocationCallOrder[0];
expect(mocks.syncMemoryDreamsAutomation).toHaveBeenCalledTimes(1);
expect(mocks.syncMemoryDreamsAutomation.mock.invocationCallOrder[0]).toBeLessThan(
cronRunnerStartOrder,
);
await engine.stop();
});
it("re-syncs memory dreams automation when memoryDreamsEnabled changes", async () => {
const mockStore = createMockStore(baseSettings);
mocks.currentStore = mockStore.store;
const engine = createEngine();
await engine.start();
mocks.syncMemoryDreamsAutomation.mockClear();
const previous = { ...baseSettings };
const next = {
...previous,
memoryDreamsEnabled: true,
};
await mockStore.emitSettingsUpdated(next, previous);
expect(mocks.syncMemoryDreamsAutomation).toHaveBeenCalledTimes(1);
expect(mocks.syncMemoryDreamsAutomation).toHaveBeenCalledWith(expect.anything(), next);
await engine.stop();
});
it("re-syncs memory dreams automation when memoryDreamsSchedule changes", async () => {
const mockStore = createMockStore(baseSettings);
mocks.currentStore = mockStore.store;
const engine = createEngine();
await engine.start();
mocks.syncMemoryDreamsAutomation.mockClear();
const previous = { ...baseSettings };
const next = {
...previous,
memoryDreamsSchedule: "0 */8 * * *",
};
await mockStore.emitSettingsUpdated(next, previous);
expect(mocks.syncMemoryDreamsAutomation).toHaveBeenCalledTimes(1);
await engine.stop();
});
it("does not re-sync memory dreams automation on unrelated settings changes", async () => {
const mockStore = createMockStore(baseSettings);
mocks.currentStore = mockStore.store;
const engine = createEngine();
await engine.start();
mocks.syncMemoryDreamsAutomation.mockClear();
const previous = { ...baseSettings };
const next = {
...previous,
pollIntervalMs: 30_000,
};
await mockStore.emitSettingsUpdated(next, previous);
expect(mocks.syncMemoryDreamsAutomation).not.toHaveBeenCalled();
await engine.stop();
});
it("logs warning and continues startup when memory dreams startup sync fails", async () => {
const warnSpy = vi.spyOn(runtimeLog, "warn").mockImplementation(() => {});
mocks.syncMemoryDreamsAutomation.mockRejectedValueOnce(new Error("dream startup sync failed"));
const engine = createEngine();
await expect(engine.start()).resolves.toBeUndefined();
expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining("Memory dreams automation startup sync failed"),
);
expect(engine.getAutomationSubsystemHealth()).toMatchObject({
status: "degraded",
});
expect(engine.getCronRunner()).toBeDefined();
await engine.stop();
warnSpy.mockRestore();
});
it("catches and logs memory dreams sync failures on settings changes", async () => {
const warnSpy = vi.spyOn(runtimeLog, "warn").mockImplementation(() => {});
const mockStore = createMockStore(baseSettings);
mocks.currentStore = mockStore.store;
const engine = createEngine();
await engine.start();
warnSpy.mockClear();
mocks.syncMemoryDreamsAutomation.mockRejectedValueOnce(new Error("dream settings sync failed"));
await expect(
mockStore.emitSettingsUpdated(
{
...baseSettings,
memoryDreamsEnabled: true,
},
{
...baseSettings,
memoryDreamsEnabled: false,
},
),
).resolves.toBeUndefined();
expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining("Failed to sync memory maintenance automation"),
);
await engine.stop();
warnSpy.mockRestore();
});
});
describe("ProjectEngine remote tunnel manager wiring", () => { describe("ProjectEngine remote tunnel manager wiring", () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();