← 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();
  });
});