Real, tested implementation of docs/11-lakehouse-write-plan.md's items 2-8 (the "future implementation session" that doc named): 0pt is now the canonical writer of Iceberg tables through ST0RE's real, shipped /v1/st0re/lakehouse/* table registry (sax3l/st0re commit 9ea0abb), never persisting lakehouse table/catalog/snapshot state itself. - src/types + src/services for 0pt.dataset_iceberg_mapping.schema.v1 (dataset -> ST0RE tableId reference) and 0pt.source_rate_policy.schema.v1 (the sourceRatePolicy ownership repair, moved out of N0D per ADR-0030). - src/clients/st0re-lakehouse-client.ts: new client matching ST0RE's actual camelCase route contract, with the same mock/HTTP boundary convention as this repo's existing cl0ud/n0d/aud0/st0re clients. Honest caveat in its module doc comment: not verified against a live ST0RE instance in this pass (no reachable ST0RE_URL); every test exercises the deterministic mock. - src/services/iceberg-write-service.ts: registers tables/snapshots through ST0RE, requests N0D jobs only for heavy writes (never executes them in-process), emits all four AUD0 event names (dataset_iceberg_table_mapped / iceberg_write_requested / iceberg_snapshot_created / source_rate_policy_created). - sql/004, packages/schemas/*.json, app/api/0pt/lakehouse + sources/rate-policies routes, tests/p0/*.test.ts (154 passed + 1 honest it.todo for the live-ST0RE case, up from 142 baseline, 0 failures). - Reconciled a real collision: an earlier untracked draft schema/test pair (tests/p0/enterprise-data-os-expansion.test.ts) asserted a different, now-superseded source_rate_policy shape; updated that one test block only. - docs/ICEBERG_WRITE_BOUNDARY.md states the ownership boundary plainly and is explicit throughout about what's real-and-tested vs. contract-only. docs/11-lakehouse-write-plan.md gets a status-update section, not a rewrite. - Data quality rules (quality-check-service.ts, quality-report-service.ts) were not touched -- confirmed still 0pt-owned. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
85 lines
3.5 KiB
TypeScript
85 lines
3.5 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { SourceService } from "@/src/services/source-service";
|
|
import { SourceRatePolicyService } from "@/src/services/source-rate-policy-service";
|
|
|
|
describe("SourceRatePolicyService", () => {
|
|
it("createSourceRatePolicy persists rate limit + crawl budget + compliance notes", async () => {
|
|
const sourceService = new SourceService();
|
|
const service = new SourceRatePolicyService({ sourceService });
|
|
await sourceService.createSource({
|
|
source_id: "src_rate_pol_1",
|
|
tenant_id: "t1",
|
|
project_id: "p1",
|
|
source_type: "web",
|
|
name: "allabolag.se",
|
|
});
|
|
|
|
const policy = await service.createSourceRatePolicy({
|
|
source_rate_policy_id: "srp_test_001",
|
|
tenant_id: "t1",
|
|
project_id: "p1",
|
|
source_id: "src_rate_pol_1",
|
|
rate_limit_per_minute: 60,
|
|
crawl_budget: 5000,
|
|
compliance_notes: "robots.txt allows /bolag/*; authorized_web tier per docs/06-data-engine.md",
|
|
domain: "allabolag.se",
|
|
robots_ok: true,
|
|
access: "authorized_web",
|
|
});
|
|
|
|
expect(policy.source_rate_policy_id).toBe("srp_test_001");
|
|
expect(policy.rate_limit_per_minute).toBe(60);
|
|
expect(policy.crawl_budget).toBe(5000);
|
|
expect(policy.compliance_notes).toContain("robots.txt");
|
|
expect(policy.status).toBe("active");
|
|
});
|
|
|
|
it("createSourceRatePolicy rejects an unknown source_id", async () => {
|
|
const service = new SourceRatePolicyService();
|
|
await expect(
|
|
service.createSourceRatePolicy({
|
|
source_rate_policy_id: "srp_test_bad",
|
|
tenant_id: "t1",
|
|
project_id: "p1",
|
|
source_id: "src_does_not_exist",
|
|
rate_limit_per_minute: 10,
|
|
crawl_budget: 100,
|
|
})
|
|
).rejects.toThrow("not found");
|
|
});
|
|
|
|
it("createSourceRatePolicy rejects a non-positive rate_limit_per_minute", async () => {
|
|
const sourceService = new SourceService();
|
|
const service = new SourceRatePolicyService({ sourceService });
|
|
await sourceService.createSource({
|
|
source_id: "src_rate_pol_2",
|
|
tenant_id: "t1",
|
|
project_id: "p1",
|
|
source_type: "api",
|
|
name: "ratsit.se",
|
|
});
|
|
await expect(
|
|
service.createSourceRatePolicy({
|
|
source_rate_policy_id: "srp_test_zero",
|
|
tenant_id: "t1",
|
|
project_id: "p1",
|
|
source_id: "src_rate_pol_2",
|
|
rate_limit_per_minute: 0,
|
|
crawl_budget: 100,
|
|
})
|
|
).rejects.toThrow("rate_limit_per_minute must be > 0");
|
|
});
|
|
|
|
it("listSourceRatePolicies filters by source_id", async () => {
|
|
const sourceService = new SourceService();
|
|
const service = new SourceRatePolicyService({ sourceService });
|
|
await sourceService.createSource({ source_id: "src_rate_pol_3", tenant_id: "t1", project_id: "p1", source_type: "web", name: "eniro.se" });
|
|
await sourceService.createSource({ source_id: "src_rate_pol_4", tenant_id: "t1", project_id: "p1", source_type: "web", name: "merinfo.se" });
|
|
await service.createSourceRatePolicy({ source_rate_policy_id: "srp_list_1", tenant_id: "t1", project_id: "p1", source_id: "src_rate_pol_3", rate_limit_per_minute: 30, crawl_budget: 1000 });
|
|
await service.createSourceRatePolicy({ source_rate_policy_id: "srp_list_2", tenant_id: "t1", project_id: "p1", source_id: "src_rate_pol_4", rate_limit_per_minute: 30, crawl_budget: 1000 });
|
|
const results = await service.listSourceRatePolicies({ source_id: "src_rate_pol_3" });
|
|
expect(results).toHaveLength(1);
|
|
expect(results[0].source_rate_policy_id).toBe("srp_list_1");
|
|
});
|
|
});
|