feat(FN-2995): export research settings resolver for dashboard alias builds
Exports the research settings resolver from `@fusion/core` types to fix dashboard alias builds, ensuring the resolver is available when imported through the dashboard's module alias configuration. Fusion-Task-Id: FN-2995
This commit is contained in:
@@ -333,6 +333,45 @@ describe("GlobalSettingsStore", () => {
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.ntfyTopic).toMatch(/^topic-\d$/);
|
||||
});
|
||||
|
||||
it("persists and clears nested researchGlobalDefaults with null-as-delete semantics", async () => {
|
||||
await store.init();
|
||||
await store.updateSettings({
|
||||
researchGlobalDefaults: {
|
||||
searchProvider: "tavily",
|
||||
enabledSources: {
|
||||
webSearch: true,
|
||||
pageFetch: true,
|
||||
github: false,
|
||||
localDocs: true,
|
||||
llmSynthesis: true,
|
||||
},
|
||||
maxSourcesPerRun: 15,
|
||||
defaultExportFormat: "json",
|
||||
},
|
||||
});
|
||||
|
||||
let settings = await store.getSettings();
|
||||
expect(settings.researchGlobalDefaults?.searchProvider).toBe("tavily");
|
||||
|
||||
// @ts-expect-error null is intentionally used to clear field
|
||||
await store.updateSettings({ researchGlobalDefaults: null });
|
||||
settings = await store.getSettings();
|
||||
expect(settings.researchGlobalDefaults).toMatchObject({
|
||||
searchProvider: undefined,
|
||||
synthesisProvider: undefined,
|
||||
synthesisModelId: undefined,
|
||||
maxSourcesPerRun: 20,
|
||||
defaultExportFormat: "markdown",
|
||||
});
|
||||
expect(settings.researchGlobalDefaults?.enabledSources).toEqual({
|
||||
webSearch: true,
|
||||
pageFetch: true,
|
||||
github: false,
|
||||
localDocs: true,
|
||||
llmSynthesis: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("schema protection", () => {
|
||||
|
||||
116
packages/core/src/__tests__/research-settings.test.ts
Normal file
116
packages/core/src/__tests__/research-settings.test.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveResearchSettings } from "../research-settings.js";
|
||||
import type { Settings } from "../types.js";
|
||||
|
||||
describe("resolveResearchSettings", () => {
|
||||
it("resolves global-only defaults", () => {
|
||||
const resolved = resolveResearchSettings({
|
||||
researchGlobalDefaults: {
|
||||
searchProvider: "tavily",
|
||||
synthesisProvider: "anthropic",
|
||||
synthesisModelId: "claude-sonnet-4-5",
|
||||
enabledSources: {
|
||||
webSearch: true,
|
||||
pageFetch: true,
|
||||
github: false,
|
||||
localDocs: true,
|
||||
llmSynthesis: true,
|
||||
},
|
||||
maxSourcesPerRun: 12,
|
||||
defaultExportFormat: "json",
|
||||
},
|
||||
researchGlobalEnabled: true,
|
||||
});
|
||||
|
||||
expect(resolved.searchProvider).toBe("tavily");
|
||||
expect(resolved.synthesisProvider).toBe("anthropic");
|
||||
expect(resolved.synthesisModelId).toBe("claude-sonnet-4-5");
|
||||
expect(resolved.enabled).toBe(true);
|
||||
expect(resolved.enabledSources.github).toBe(false);
|
||||
expect(resolved.limits.maxSourcesPerRun).toBe(12);
|
||||
expect(resolved.defaultExportFormat).toBe("json");
|
||||
});
|
||||
|
||||
it("project values override global values", () => {
|
||||
const resolved = resolveResearchSettings({
|
||||
researchGlobalDefaults: { searchProvider: "brave", maxSourcesPerRun: 10 },
|
||||
researchSettings: {
|
||||
enabled: false,
|
||||
searchProvider: "tavily",
|
||||
limits: { maxSourcesPerRun: 3, maxConcurrentRuns: 1, maxDurationMs: 1000, requestTimeoutMs: 500 },
|
||||
},
|
||||
});
|
||||
|
||||
expect(resolved.enabled).toBe(false);
|
||||
expect(resolved.searchProvider).toBe("tavily");
|
||||
expect(resolved.limits.maxSourcesPerRun).toBe(3);
|
||||
expect(resolved.limits.maxConcurrentRuns).toBe(1);
|
||||
});
|
||||
|
||||
it("partial nested project overrides preserve remaining global defaults", () => {
|
||||
const resolved = resolveResearchSettings({
|
||||
researchGlobalDefaults: {
|
||||
enabledSources: {
|
||||
webSearch: true,
|
||||
pageFetch: true,
|
||||
github: true,
|
||||
localDocs: true,
|
||||
llmSynthesis: true,
|
||||
},
|
||||
},
|
||||
researchSettings: {
|
||||
enabledSources: { github: false },
|
||||
},
|
||||
});
|
||||
|
||||
expect(resolved.enabledSources.github).toBe(false);
|
||||
expect(resolved.enabledSources.webSearch).toBe(true);
|
||||
expect(resolved.enabledSources.pageFetch).toBe(true);
|
||||
});
|
||||
|
||||
it("clearing project override falls back to global/default", () => {
|
||||
const resolved = resolveResearchSettings({
|
||||
researchGlobalDefaults: {
|
||||
searchProvider: "brave",
|
||||
},
|
||||
researchSettings: {
|
||||
searchProvider: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
expect(resolved.searchProvider).toBe("brave");
|
||||
});
|
||||
|
||||
it("supports null-as-delete semantics for researchSettings object", () => {
|
||||
const resolved = resolveResearchSettings({
|
||||
researchGlobalDefaults: {
|
||||
searchProvider: "tavily",
|
||||
},
|
||||
researchSettings: null as unknown as Settings["researchSettings"],
|
||||
});
|
||||
|
||||
expect(resolved.searchProvider).toBe("tavily");
|
||||
expect(resolved.enabled).toBe(true);
|
||||
});
|
||||
|
||||
it("falls back through legacy and hardcoded limit chains", () => {
|
||||
const fromLegacyProjectTimeout = resolveResearchSettings({
|
||||
researchDefaultTimeout: 111_000,
|
||||
researchFetchTimeoutMs: 8_000,
|
||||
});
|
||||
expect(fromLegacyProjectTimeout.limits.maxDurationMs).toBe(111_000);
|
||||
expect(fromLegacyProjectTimeout.limits.requestTimeoutMs).toBe(8_000);
|
||||
|
||||
const fromLegacyGlobalTimeout = resolveResearchSettings({
|
||||
researchGlobalDefaultTimeout: 222_000,
|
||||
researchGlobalMaxConcurrentRuns: 9,
|
||||
});
|
||||
expect(fromLegacyGlobalTimeout.limits.maxDurationMs).toBe(222_000);
|
||||
expect(fromLegacyGlobalTimeout.limits.maxConcurrentRuns).toBe(9);
|
||||
|
||||
const hardcodedFallback = resolveResearchSettings({});
|
||||
expect(hardcodedFallback.limits.maxDurationMs).toBe(300000);
|
||||
expect(hardcodedFallback.limits.requestTimeoutMs).toBe(30000);
|
||||
expect(hardcodedFallback.defaultExportFormat).toBe("markdown");
|
||||
});
|
||||
});
|
||||
@@ -51,8 +51,11 @@ describe("settings key parity", () => {
|
||||
expect(isProjectSettingsKey("maxConcurrent")).toBe(true);
|
||||
expect(isProjectSettingsKey("heartbeatMultiplier")).toBe(true);
|
||||
expect(isProjectSettingsKey("remoteAccess")).toBe(true);
|
||||
expect(isProjectSettingsKey("researchSettings")).toBe(true);
|
||||
expect(isGlobalSettingsKey("researchGlobalDefaults")).toBe(true);
|
||||
expect(isProjectSettingsKey("themeMode")).toBe(false);
|
||||
expect(isGlobalSettingsKey("remoteAccess")).toBe(false);
|
||||
expect(isGlobalSettingsKey("researchSettings")).toBe(false);
|
||||
});
|
||||
|
||||
it("includes heartbeatMultiplier in project defaults", () => {
|
||||
|
||||
Reference in New Issue
Block a user