diff --git a/src/shared/host-skill-config.test.ts b/src/shared/host-skill-config.test.ts new file mode 100644 index 000000000..68564dc4a --- /dev/null +++ b/src/shared/host-skill-config.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, test } from "bun:test" + +import { adaptHostSkillConfig } from "./host-skill-config" + +describe("adaptHostSkillConfig", () => { + test("converts paths and urls into SkillsConfig sources", () => { + // given + const hostConfig = { + paths: ["/host/skills", "/other/skills"], + urls: ["https://example.com/skills/"], + } + + // when + const result = adaptHostSkillConfig(hostConfig) + + // then + expect(result).toEqual({ + sources: ["/host/skills", "/other/skills", "https://example.com/skills/"], + }) + }) + + test("filters blank and whitespace-only entries", () => { + // given + const hostConfig = { + paths: ["", " ", "/real/skills"], + urls: ["\n", "https://example.com/skills/"], + } + + // when + const result = adaptHostSkillConfig(hostConfig) + + // then + expect(result).toEqual({ + sources: ["/real/skills", "https://example.com/skills/"], + }) + }) + + test("returns undefined when no usable sources remain", () => { + // when + const result = adaptHostSkillConfig({ paths: ["", " "], urls: ["\t"] }) + + // then + expect(result).toBeUndefined() + }) + + test("returns undefined for null input", () => { + expect(adaptHostSkillConfig(null)).toBeUndefined() + }) + + test("returns undefined for undefined input", () => { + expect(adaptHostSkillConfig(undefined)).toBeUndefined() + }) + + test("returns undefined for non-object input", () => { + expect(adaptHostSkillConfig("string")).toBeUndefined() + }) + + test("handles missing paths or urls gracefully", () => { + // when - only paths + const pathsOnly = adaptHostSkillConfig({ paths: ["/skills"] }) + expect(pathsOnly).toEqual({ sources: ["/skills"] }) + + // when - only urls + const urlsOnly = adaptHostSkillConfig({ urls: ["https://example.com/skills/"] }) + expect(urlsOnly).toEqual({ sources: ["https://example.com/skills/"] }) + }) + + test("ignores non-string array elements", () => { + // given + const hostConfig = { + paths: ["/valid", 42, null, true, "/also-valid"], + } + + // when + const result = adaptHostSkillConfig(hostConfig) + + // then + expect(result).toEqual({ sources: ["/valid", "/also-valid"] }) + }) +}) diff --git a/src/shared/host-skill-config.ts b/src/shared/host-skill-config.ts new file mode 100644 index 000000000..9ab72ea3a --- /dev/null +++ b/src/shared/host-skill-config.ts @@ -0,0 +1,28 @@ +import type { SkillsConfig } from "../config/schema/skills" + +type HostSkillConfig = { + paths?: unknown + urls?: unknown +} + +function toStringArray(value: unknown): string[] { + if (!Array.isArray(value)) return [] + return value + .filter((item): item is string => typeof item === "string") + .map((item) => item.trim()) + .filter((item) => item.length > 0) +} + +export function adaptHostSkillConfig(value: unknown): SkillsConfig | undefined { + if (!value || typeof value !== "object") return undefined + + const hostSkillConfig = value as HostSkillConfig + const sources = [ + ...toStringArray(hostSkillConfig.paths), + ...toStringArray(hostSkillConfig.urls), + ] + + if (sources.length === 0) return undefined + + return { sources } as SkillsConfig +}