fix(FN-1966): use public skills search fallback for catalog fetches

- Route unauthenticated catalog requests to skills.sh /api/search with wildcard query support and normalized response mapping
- Keep authenticated v1 catalog fetch, but fall back to public search on 401/403 while preserving auth metadata
- Expand skills route tests for unauthenticated payload shape, query passthrough, and no-query wildcard behavior
- Stabilize onboarding reopen tests by awaiting Authentication section render before interaction
- Document dashboard dist build prerequisite for CLI test troubleshooting
This commit is contained in:
Fusion
2026-04-16 15:55:00 -07:00
committed by gsxdsm
parent c18c431c48
commit 84bd5e7c9d
4 changed files with 270 additions and 80 deletions

View File

@@ -377,7 +377,56 @@ describe("Skills routes", () => {
expect(res.body).toMatchObject({ error: "Skills adapter not configured", code: "adapter_not_configured" });
});
it("passes limit and query parameters to catalog fetch", async () => {
it("returns unauthenticated public-search style entries", async () => {
const mockAdapter = createMockSkillsAdapter({
fetchCatalog: vi.fn().mockResolvedValue({
entries: [
{
id: "vercel-labs/agent-skills/vercel-react-best-practices",
slug: "vercel-labs/agent-skills/vercel-react-best-practices",
name: "vercel-react-best-practices",
repo: "vercel-labs/agent-skills",
installs: 421,
installation: {
installed: false,
matchingSkillIds: [],
matchingPaths: [],
},
},
],
auth: { mode: "unauthenticated", tokenPresent: false, fallbackUsed: false },
}),
});
const store = new MockStore();
const app = createServer(store as any, { skillsAdapter: mockAdapter as SkillsAdapter });
const res = await request(app, "GET", "/api/skills/catalog");
expect(res.status).toBe(200);
expect(res.body).toEqual({
entries: [
{
id: "vercel-labs/agent-skills/vercel-react-best-practices",
slug: "vercel-labs/agent-skills/vercel-react-best-practices",
name: "vercel-react-best-practices",
repo: "vercel-labs/agent-skills",
installs: 421,
installation: {
installed: false,
matchingSkillIds: [],
matchingPaths: [],
},
},
],
auth: {
mode: "unauthenticated",
tokenPresent: false,
fallbackUsed: false,
},
});
});
it("passes search query through to catalog adapter", async () => {
const mockAdapter = createMockSkillsAdapter({
fetchCatalog: vi.fn().mockResolvedValue({
entries: [],
@@ -387,10 +436,38 @@ describe("Skills routes", () => {
const store = new MockStore();
const app = createServer(store as any, { skillsAdapter: mockAdapter as SkillsAdapter });
const res = await request(app, "GET", "/api/skills/catalog?limit=50&q=search-term");
const res = await request(app, "GET", "/api/skills/catalog?q=react");
expect(res.status).toBe(200);
expect(mockAdapter.fetchCatalog).toHaveBeenCalledWith({ limit: 50, query: "search-term" });
expect(mockAdapter.fetchCatalog).toHaveBeenCalledWith({ limit: 20, query: "react" });
});
it("returns results when query is omitted (adapter handles wildcard fallback)", async () => {
const mockAdapter = createMockSkillsAdapter({
fetchCatalog: vi.fn().mockResolvedValue({
entries: [
{
id: "default-skill",
slug: "default-skill",
name: "Default Skill",
installation: {
installed: false,
matchingSkillIds: [],
matchingPaths: [],
},
},
],
auth: { mode: "unauthenticated", tokenPresent: false, fallbackUsed: false },
}),
});
const store = new MockStore();
const app = createServer(store as any, { skillsAdapter: mockAdapter as SkillsAdapter });
const res = await request(app, "GET", "/api/skills/catalog");
expect(res.status).toBe(200);
expect(res.body.entries).toHaveLength(1);
expect(mockAdapter.fetchCatalog).toHaveBeenCalledWith({ limit: 20, query: undefined });
});
it("bounds limit parameter to max 100", async () => {