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:
committed by
gsxdsm
parent
4416de159c
commit
a2bd876eb4
@@ -573,6 +573,7 @@ vi.mock("@fusion/engine", async (importOriginal) => {
|
|||||||
PeerExchangeService: vi.fn().mockImplementation(() => ({
|
PeerExchangeService: vi.fn().mockImplementation(() => ({
|
||||||
start: vi.fn(),
|
start: vi.fn(),
|
||||||
stop: vi.fn().mockResolvedValue(undefined),
|
stop: vi.fn().mockResolvedValue(undefined),
|
||||||
|
updateGlobalSettings: vi.fn(),
|
||||||
})),
|
})),
|
||||||
TriageProcessor: mocks.triageCtor,
|
TriageProcessor: mocks.triageCtor,
|
||||||
TaskExecutor: mocks.executorCtor,
|
TaskExecutor: mocks.executorCtor,
|
||||||
|
|||||||
@@ -715,6 +715,7 @@ vi.mock("@fusion/engine", async (importOriginal) => {
|
|||||||
PeerExchangeService: vi.fn().mockImplementation(() => ({
|
PeerExchangeService: vi.fn().mockImplementation(() => ({
|
||||||
start: vi.fn(),
|
start: vi.fn(),
|
||||||
stop: vi.fn().mockResolvedValue(undefined),
|
stop: vi.fn().mockResolvedValue(undefined),
|
||||||
|
updateGlobalSettings: vi.fn(),
|
||||||
})),
|
})),
|
||||||
shouldUseHybridExecutor: mockShouldUseHybridExecutor,
|
shouldUseHybridExecutor: mockShouldUseHybridExecutor,
|
||||||
HybridExecutor: mockHybridExecutorCtor,
|
HybridExecutor: mockHybridExecutorCtor,
|
||||||
@@ -2626,6 +2627,7 @@ describe("runDashboard — mesh lifecycle ownership", () => {
|
|||||||
expect(peerExchangeCtor.mock.calls.length).toBeGreaterThan(baselineCalls);
|
expect(peerExchangeCtor.mock.calls.length).toBeGreaterThan(baselineCalls);
|
||||||
const peerExchangeInstance = peerExchangeCtor.mock.results.at(-1)?.value;
|
const peerExchangeInstance = peerExchangeCtor.mock.results.at(-1)?.value;
|
||||||
expect(peerExchangeInstance.start).toHaveBeenCalledTimes(1);
|
expect(peerExchangeInstance.start).toHaveBeenCalledTimes(1);
|
||||||
|
expect(peerExchangeInstance.updateGlobalSettings).toHaveBeenCalledTimes(1);
|
||||||
expect(startDiscovery).toHaveBeenCalledWith(expect.objectContaining({
|
expect(startDiscovery).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
broadcast: true,
|
broadcast: true,
|
||||||
listen: true,
|
listen: true,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ const mockApplyRemoteSettings = vi.fn();
|
|||||||
const mockGetSettingsForSync = vi.fn();
|
const mockGetSettingsForSync = vi.fn();
|
||||||
const mockGetAuthMaterialSnapshot = vi.fn();
|
const mockGetAuthMaterialSnapshot = vi.fn();
|
||||||
const mockApplyAuthMaterialSnapshot = vi.fn();
|
const mockApplyAuthMaterialSnapshot = vi.fn();
|
||||||
|
const mockStoreUpdateGlobalSettings = vi.fn().mockResolvedValue({});
|
||||||
const mockChatStoreInit = vi.fn().mockResolvedValue(undefined);
|
const mockChatStoreInit = vi.fn().mockResolvedValue(undefined);
|
||||||
const mockAgentStoreInit = vi.fn().mockResolvedValue(undefined);
|
const mockAgentStoreInit = vi.fn().mockResolvedValue(undefined);
|
||||||
const mockAgentStoreGetAgent = vi.fn().mockResolvedValue(null);
|
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 ──────────────────────────────────────────────────────
|
// ── Test helpers ──────────────────────────────────────────────────────
|
||||||
@@ -177,6 +182,7 @@ describe("Node settings sync routes", () => {
|
|||||||
mockGetSettingsSyncState.mockResolvedValue(null);
|
mockGetSettingsSyncState.mockResolvedValue(null);
|
||||||
mockUpdateSettingsSyncState.mockResolvedValue({});
|
mockUpdateSettingsSyncState.mockResolvedValue({});
|
||||||
mockApplyRemoteSettings.mockResolvedValue({ success: true, globalCount: 1, projectCount: 1, authCount: 0 });
|
mockApplyRemoteSettings.mockResolvedValue({ success: true, globalCount: 1, projectCount: 1, authCount: 0 });
|
||||||
|
mockStoreUpdateGlobalSettings.mockReset();
|
||||||
mockGetSettingsForSync.mockResolvedValue({});
|
mockGetSettingsForSync.mockResolvedValue({});
|
||||||
mockGetAuthMaterialSnapshot.mockReturnValue({
|
mockGetAuthMaterialSnapshot.mockReturnValue({
|
||||||
version: 1,
|
version: 1,
|
||||||
@@ -1026,6 +1032,34 @@ describe("Node settings sync routes", () => {
|
|||||||
expect(mockApplyRemoteSettings).toHaveBeenCalled();
|
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 () => {
|
it("returns 401 when auth header is missing", async () => {
|
||||||
const res = await request(
|
const res = await request(
|
||||||
app,
|
app,
|
||||||
|
|||||||
@@ -6,6 +6,17 @@ import type { TaskStore, GlobalSettings, CustomProvider } from "@fusion/core";
|
|||||||
import { createApiRoutes } from "../../routes.js";
|
import { createApiRoutes } from "../../routes.js";
|
||||||
import { request as performRequest } from "../../test-request.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) {
|
function createMockGlobalSettingsStore(settings: GlobalSettings) {
|
||||||
return {
|
return {
|
||||||
getSettings: vi.fn(async () => settings),
|
getSettings: vi.fn(async () => settings),
|
||||||
@@ -97,6 +108,7 @@ describe("custom provider routes", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
settings = {};
|
settings = {};
|
||||||
|
mockInvalidateAllGlobalSettingsCaches.mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("GET /custom-providers returns empty array when none configured", async () => {
|
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(res.body.apiKey).toBe("sk-•••••5678");
|
||||||
expect(updates).toHaveLength(1);
|
expect(updates).toHaveLength(1);
|
||||||
|
expect(mockInvalidateAllGlobalSettingsCaches).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
const persisted = updates[0].customProviders as CustomProvider[];
|
const persisted = updates[0].customProviders as CustomProvider[];
|
||||||
expect(persisted[0]?.apiKey).toBe("sk-my-secret-5678");
|
expect(persisted[0]?.apiKey).toBe("sk-my-secret-5678");
|
||||||
@@ -264,6 +277,7 @@ describe("custom provider routes", () => {
|
|||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
expect(res.body).toEqual({ success: true });
|
expect(res.body).toEqual({ success: true });
|
||||||
expect(settings.customProviders).toEqual([]);
|
expect(settings.customProviders).toEqual([]);
|
||||||
|
expect(mockInvalidateAllGlobalSettingsCaches).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("DELETE /custom-providers/:id returns 404 for non-existent id", async () => {
|
it("DELETE /custom-providers/:id returns 404 for non-existent id", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user