SPB Git forge
7commits 1branches 0releases
229.0 KBsize
maindefault branch
12 days agolast push
TypeScript 91.8% HTML 3.2% JavaScript 3% SQL 1.4% CSS 0.7%
4.2 KB · 70 lines typescript
Raw Blame History
1import { describe, expect, it } from "vitest";2import { profileJson, guessFieldSemantics, shapeSignature } from "./SchemaProfiler.ts";3import { mineEntities, classifyResponse, type CapturedResponse } from "./ResponseClassifier.ts";4import { youtubeAdapter } from "@src/connectors";56// Fixture shaped like an innertube search response (property names are *not* assumed by the profiler).7const youtubeLike = {8  contents: {9    sectionList: {10      items: [11        { videoRenderer: { videoId: "dQw4w9WgXcQ", title: { runs: [{ text: "IA au Québec — table ronde" }] }, ownerText: { runs: [{ text: "Chaîne Exemple" }] }, lengthText: { simpleText: "12:34" }, viewCountText: { simpleText: "1,2 M de vues" }, thumbnail: { thumbnails: [{ url: "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg", width: 480, height: 360 }] } } },12        { videoRenderer: { videoId: "abcdefghijk", title: { runs: [{ text: "Montréal AI meetup" }] }, ownerText: { runs: [{ text: "Autre Chaîne" }] }, lengthText: { simpleText: "1:02:03" }, viewCountText: { simpleText: "48 k vues" }, thumbnail: { thumbnails: [{ url: "https://i.ytimg.com/vi/abcdefghijk/hqdefault.jpg", width: 480, height: 360 }] } } },13        { channelRenderer: { channelId: "UC1234567890abcdefghij", title: { simpleText: "Québec IA" }, subscriberCountText: { simpleText: "12 k abonnés" } } },14      ],15    },16    continuation: { token: "EpMBEgVoZWxsbxqJAVNCU0NBUXFsY1ZvVFVIZG9kMlV5UmtsbGRuZG1kMkkyYjBOaGRIZG9UbGxSVUZGVmxWMFVrMWFTV2xhVjFwWGRGUlY" },17  },18};1920describe("SchemaProfiler", () => {21  it("infers field semantics from value shapes and key names", () => {22    expect(guessFieldSemantics("videoId", ["dQw4w9WgXcQ", "abcdefghijk"])[0]!.kind).toBe("identifier");23    expect(guessFieldSemantics("url", ["https://i.ytimg.com/vi/x/hq.jpg"])[0]!.kind).toBe("thumbnail_url");24    expect(guessFieldSemantics("simpleText", ["12:34", "1:02:03"])[0]!.kind).toBe("duration");25    expect(guessFieldSemantics("handle", ["@simon", "@bob"])[0]!.kind).toBe("username");26    expect(guessFieldSemantics("created_utc", [1700000000, 1700000500])[0]!.kind).toBe("timestamp");27  });2829  it("produces a stable shape hash independent of values", () => {30    const a = shapeSignature({ id: "1", name: "a", tags: ["x"] });31    const b = shapeSignature({ name: "zzz", id: "999", tags: ["y", "z"] });32    expect(a).toBe(b);33  });3435  it("finds repeated objects and candidate entity lists", () => {36    const p = profileJson(youtubeLike);37    expect(p.repeated_object_paths).toContain("contents.sectionList.items");38    expect(p.fields.some((f) => f.path.endsWith("videoId") && f.semantic[0]!.kind === "identifier")).toBe(true);39    expect(p.candidate_entity_types.length).toBeGreaterThan(0);40  });41});4243describe("mineEntities (generic JSON entity miner)", () => {44  it("extracts videos and channels with collapsed rich text and provenance", () => {45    const ents = mineEntities(youtubeLike, youtubeAdapter.classifierHints);46    const videos = ents.filter((e) => e.type === "video");47    const channels = ents.filter((e) => e.type === "channel");48    expect(videos).toHaveLength(2);49    expect(channels).toHaveLength(1);50    const v = videos.find((e) => e.platform_id === "dQw4w9WgXcQ")!;51    expect(v.name).toBe("IA au Québec — table ronde");52    expect(v.author).toBe("Chaîne Exemple");53    expect(v.media?.duration_s).toBe(12 * 60 + 34);54    expect(v.metrics?.views).toBe(1_200_000);55    expect(v.url).toBe("https://youtube.com/watch?v=dQw4w9WgXcQ");56    expect(v.provenance[0]!.surface).toBe("network");57    expect(Object.keys(v.fields)).toContain("platform_id");58  });5960  it("classifies a JSON response end-to-end", () => {61    const r: CapturedResponse = { request_id: "req_1", url: "https://www.youtube.com/youtubei/v1/search?prettyPrint=false", method: "POST", status: 200, content_type: "application/json; charset=UTF-8", resource_type: "xhr", body_size: 100, body: JSON.stringify(youtubeLike), captured_at: new Date().toISOString() };62    const c = classifyResponse(r, youtubeAdapter.classifierHints);63    expect(c.kind).toBe("json");64    expect(c.entities.length).toBe(3);65    expect(c.fingerprint.observed_entity_types).toContain("video");66    expect(c.fingerprint.path_pattern).toBe("/youtubei/v1/search");67    expect(c.confidence).toBeGreaterThan(0.8);68  });69});70