← back to Homesonspec
collectors/legacy-al/src/selftest.test.ts
93 lines
import { readFileSync, readdirSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import { validatePayload } from "@homesonspec/schemas";
import { legacyAlAdapter, parseHome } from "./index";
const HERE = dirname(fileURLToPath(import.meta.url));
const FX = join(HERE, "..", "fixtures", "homes");
function urlMap(): Record<string, string> {
const out: Record<string, string> = {};
for (const line of readFileSync(join(FX, "_urls.tsv"), "utf8").split(/\r?\n/)) {
const [file, url] = line.split("\t");
if (file && url) out[file.trim()] = url.trim();
}
return out;
}
function mkPage(url: string, html: string) {
return { url, retrievedAt: new Date().toISOString(), contentType: "text/html", body: Buffer.from(html), contentHash: "test" };
}
describe("legacy-al adapter — real fixture E2E boundaries", () => {
const files = readdirSync(FX).filter((f) => f.endsWith(".html")).sort();
const urls = urlMap();
it("parses eight distinct Alabama inventory homes with honest nulls", () => {
const homes = files.map((file) => parseHome(readFileSync(join(FX, file), "utf8"), urls[file]!));
expect(homes).toHaveLength(8);
expect(homes.every(Boolean)).toBe(true);
const parsed = homes.filter((h) => h != null);
expect(new Set(parsed.map((h) => h.street)).size).toBe(8);
expect(new Set(parsed.map((h) => h.builderInventoryId)).size).toBe(8);
expect(parsed.filter((h) => h.stories != null).length).toBeGreaterThanOrEqual(1);
expect(parsed.filter((h) => h.planName != null).length).toBeGreaterThanOrEqual(2);
for (const h of parsed) {
expect(h.state).toBe("AL");
expect(h.zip).toMatch(/^3[5-6]\d{3}$/);
expect(h.street).toMatch(/^\d/);
expect(h.city).toBeTruthy();
expect(h.community).toBeTruthy();
if (h.planName != null) expect(h.planName).toMatch(/\S/);
expect(h.builderInventoryId).toBeTruthy();
expect(h.beds).toBeGreaterThan(0);
expect(h.bathsTotal).toBeGreaterThan(0);
expect(h.sqft).toBeGreaterThan(0);
if (h.stories != null) expect(h.stories).toBeGreaterThan(0);
expect(h.lat).toBeGreaterThan(30);
expect(h.lat).toBeLessThan(36);
expect(h.lon).toBeGreaterThan(-89);
expect(h.lon).toBeLessThan(-84);
if (h.price != null) expect(h.price).toBeGreaterThan(50_000);
expect(h.garages).toBeNull();
expect(h.lotNumber).toBeNull();
expect(h.status).toBeNull();
}
});
it("emits schema-valid community and inventory records with real address hints", () => {
let homeCount = 0;
for (const file of files) {
const html = readFileSync(join(FX, file), "utf8");
const url = urls[file]!;
const { records, errors } = legacyAlAdapter.extract(mkPage(url, html));
expect(errors).toEqual([]);
expect(records.map((r) => r.entityType)).toEqual(["community", "inventory_home"]);
for (const record of records) expect(validatePayload(record).ok).toBe(true);
const home = records[1]!;
homeCount++;
expect(home.canonicalHints.address).toBe(parseHome(html, url)!.street);
expect(home.canonicalHints.address).not.toMatch(/^lot\b|^https?:/i);
expect(home.fields.constructionStatus?.value).toBeNull();
expect(home.fields.garageSpaces?.value).toBeNull();
}
expect(homeCount).toBe(8);
});
it("rejects a page without per-home Product JSON-LD", () => {
const file = files[0]!;
const html = readFileSync(join(FX, file), "utf8");
const withoutJsonLd = html.replace(/<script[^>]*type="application\/ld\+json"[^>]*>[\s\S]*?<\/script>/gi, "");
expect(parseHome(withoutJsonLd, urls[file]!)).toBeNull();
});
it("does not invent a missing primary spec", () => {
const file = files[0]!;
const html = readFileSync(join(FX, file), "utf8");
const withoutBeds = html.replace(/<li\s+class="DetailOverview_listItem"[^>]*>[\s\S]*?Beds[\s\S]*?<\/li>/i, "");
expect(parseHome(withoutBeds, urls[file]!)?.beds).toBeNull();
});
});