mirror of
https://github.com/garrytan/gstack.git
synced 2026-06-17 15:20:11 +02:00
c87e57e150
#1576's crash in sourceLocalPath was already fixed in v1.42.0.0 (dual-shape handling). But the readers disagreed: sourceLocalPath accepted both the wrapped {sources:[...]} object (v0.20+) and a bare array, while probeSource and sourcePageCount accepted only the wrapped shape. Extract one parseSourcesList() normalizer and route all three through it, so the shape assumption lives in a single place. This is also the base the #1734 remote_url audit builds on. parseSourcesList returns [] for null/garbage rather than throwing; callers treat 'no rows' as absent. New test/gbrain-sources-parse.test.ts pins both shapes plus the garbage paths and confirms config.remote_url survives for the audit. #1576 is closeable as already-fixed in v1.42.0.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
50 lines
1.9 KiB
TypeScript
50 lines
1.9 KiB
TypeScript
import { describe, test, expect } from "bun:test";
|
|
import { parseSourcesList } from "../lib/gbrain-sources";
|
|
|
|
// #1576 hardening: `gbrain sources list --json` has shipped two shapes — a
|
|
// wrapped `{ sources: [...] }` object (v0.20+) and a bare top-level array.
|
|
// parseSourcesList is the single place that normalizes both, so every reader
|
|
// (probeSource, sourcePageCount, sourceLocalPath, the #1734 remote_url audit)
|
|
// agrees on the shape. These tests pin both shapes plus the garbage paths.
|
|
describe("parseSourcesList", () => {
|
|
const rows = [
|
|
{ id: "a", local_path: "/x", page_count: 3 },
|
|
{ id: "b", local_path: "/y", config: { remote_url: "https://example.com/r.git" } },
|
|
];
|
|
|
|
test("wrapped { sources: [...] } shape", () => {
|
|
expect(parseSourcesList({ sources: rows })).toEqual(rows);
|
|
});
|
|
|
|
test("bare top-level array shape", () => {
|
|
expect(parseSourcesList(rows)).toEqual(rows);
|
|
});
|
|
|
|
test("both shapes yield identical rows (shape-independent)", () => {
|
|
expect(parseSourcesList({ sources: rows })).toEqual(parseSourcesList(rows));
|
|
});
|
|
|
|
test("null / undefined → empty array (no throw)", () => {
|
|
expect(parseSourcesList(null)).toEqual([]);
|
|
expect(parseSourcesList(undefined)).toEqual([]);
|
|
});
|
|
|
|
test("object without sources key → empty array", () => {
|
|
expect(parseSourcesList({ pages: [] })).toEqual([]);
|
|
});
|
|
|
|
test("sources key present but not an array → empty array", () => {
|
|
expect(parseSourcesList({ sources: "oops" })).toEqual([]);
|
|
});
|
|
|
|
test("scalar garbage → empty array", () => {
|
|
expect(parseSourcesList("nope")).toEqual([]);
|
|
expect(parseSourcesList(42)).toEqual([]);
|
|
});
|
|
|
|
test("preserves config.remote_url for the #1734 audit", () => {
|
|
const parsed = parseSourcesList({ sources: rows });
|
|
expect(parsed.find((r) => r.id === "b")?.config?.remote_url).toBe("https://example.com/r.git");
|
|
});
|
|
});
|