test(FN-4907): cover stale settings invalidation and sync refresh paths

Fusion-Task-Id: FN-4907
Fusion-Task-Lineage: e84d5357-0719-4cc7-ab3f-f665ac83b218
This commit is contained in:
Fusion (runfusion.ai)
2026-05-17 10:11:57 -07:00
committed by gsxdsm
parent 4416de159c
commit a2bd876eb4
4 changed files with 51 additions and 0 deletions

View File

@@ -573,6 +573,7 @@ vi.mock("@fusion/engine", async (importOriginal) => {
PeerExchangeService: vi.fn().mockImplementation(() => ({
start: vi.fn(),
stop: vi.fn().mockResolvedValue(undefined),
updateGlobalSettings: vi.fn(),
})),
TriageProcessor: mocks.triageCtor,
TaskExecutor: mocks.executorCtor,

View File

@@ -715,6 +715,7 @@ vi.mock("@fusion/engine", async (importOriginal) => {
PeerExchangeService: vi.fn().mockImplementation(() => ({
start: vi.fn(),
stop: vi.fn().mockResolvedValue(undefined),
updateGlobalSettings: vi.fn(),
})),
shouldUseHybridExecutor: mockShouldUseHybridExecutor,
HybridExecutor: mockHybridExecutorCtor,
@@ -2626,6 +2627,7 @@ describe("runDashboard — mesh lifecycle ownership", () => {
expect(peerExchangeCtor.mock.calls.length).toBeGreaterThan(baselineCalls);
const peerExchangeInstance = peerExchangeCtor.mock.results.at(-1)?.value;
expect(peerExchangeInstance.start).toHaveBeenCalledTimes(1);
expect(peerExchangeInstance.updateGlobalSettings).toHaveBeenCalledTimes(1);
expect(startDiscovery).toHaveBeenCalledWith(expect.objectContaining({
broadcast: true,
listen: true,

View File

@@ -34,6 +34,7 @@ const mockApplyRemoteSettings = vi.fn();
const mockGetSettingsForSync = vi.fn();
const mockGetAuthMaterialSnapshot = vi.fn();
const mockApplyAuthMaterialSnapshot = vi.fn();
const mockStoreUpdateGlobalSettings = vi.fn().mockResolvedValue({});
const mockChatStoreInit = vi.fn().mockResolvedValue(undefined);
const mockAgentStoreInit = vi.fn().mockResolvedValue(undefined);
const mockAgentStoreGetAgent = vi.fn().mockResolvedValue(null);
@@ -118,6 +119,10 @@ class MockStore extends EventEmitter {
},
};
}
async updateGlobalSettings(patch: Record<string, unknown>) {
return mockStoreUpdateGlobalSettings(patch);
}
}
// ── Test helpers ──────────────────────────────────────────────────────
@@ -177,6 +182,7 @@ describe("Node settings sync routes", () => {
mockGetSettingsSyncState.mockResolvedValue(null);
mockUpdateSettingsSyncState.mockResolvedValue({});
mockApplyRemoteSettings.mockResolvedValue({ success: true, globalCount: 1, projectCount: 1, authCount: 0 });
mockStoreUpdateGlobalSettings.mockReset();
mockGetSettingsForSync.mockResolvedValue({});
mockGetAuthMaterialSnapshot.mockReturnValue({
version: 1,
@@ -1026,6 +1032,34 @@ describe("Node settings sync routes", () => {
expect(mockApplyRemoteSettings).toHaveBeenCalled();
});
it("applies inbound global settings via store.updateGlobalSettings when local values are unset", async () => {
const localNode = createMockLocalNode();
mockListNodes.mockResolvedValue([localNode]);
mockApplyRemoteSettings.mockResolvedValue({
success: true,
globalCount: 2,
projectCount: 0,
authCount: 0,
});
const res = await request(
app,
"POST",
"/api/settings/sync-receive",
JSON.stringify({
sourceNodeId: "node-remote-001",
exportedAt: "2026-04-14T10:00:00.000Z",
checksum: "abc123",
version: 1,
global: { dashboardCurrentNodeId: "node-remote-001", defaultProvider: "openai" },
}),
{ "content-type": "application/json", "Authorization": `Bearer ${localNode.apiKey}` },
);
expect(res.status).toBe(200);
expect(mockStoreUpdateGlobalSettings).toHaveBeenCalledWith({ dashboardCurrentNodeId: "node-remote-001" });
});
it("returns 401 when auth header is missing", async () => {
const res = await request(
app,

View File

@@ -6,6 +6,17 @@ import type { TaskStore, GlobalSettings, CustomProvider } from "@fusion/core";
import { createApiRoutes } from "../../routes.js";
import { request as performRequest } from "../../test-request.js";
const { mockInvalidateAllGlobalSettingsCaches } = vi.hoisted(() => ({
mockInvalidateAllGlobalSettingsCaches: vi.fn(),
}));
vi.mock("../../project-store-resolver.js", async () => {
const actual = await vi.importActual<typeof import("../../project-store-resolver.js")>("../../project-store-resolver.js");
return {
...actual,
invalidateAllGlobalSettingsCaches: mockInvalidateAllGlobalSettingsCaches,
};
});
function createMockGlobalSettingsStore(settings: GlobalSettings) {
return {
getSettings: vi.fn(async () => settings),
@@ -97,6 +108,7 @@ describe("custom provider routes", () => {
beforeEach(() => {
settings = {};
mockInvalidateAllGlobalSettingsCaches.mockReset();
});
it("GET /custom-providers returns empty array when none configured", async () => {
@@ -165,6 +177,7 @@ describe("custom provider routes", () => {
);
expect(res.body.apiKey).toBe("sk-•••••5678");
expect(updates).toHaveLength(1);
expect(mockInvalidateAllGlobalSettingsCaches).toHaveBeenCalledTimes(1);
const persisted = updates[0].customProviders as CustomProvider[];
expect(persisted[0]?.apiKey).toBe("sk-my-secret-5678");
@@ -264,6 +277,7 @@ describe("custom provider routes", () => {
expect(res.status).toBe(200);
expect(res.body).toEqual({ success: true });
expect(settings.customProviders).toEqual([]);
expect(mockInvalidateAllGlobalSettingsCaches).toHaveBeenCalledTimes(1);
});
it("DELETE /custom-providers/:id returns 404 for non-existent id", async () => {