Files
0pt/tests/p0/source-rate-policy.test.ts
T
adminandClaude Sonnet 5 35b6ec63bf Add 0pt Iceberg write-path ownership: dataset<->table mapping + source rate policy
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>
2026-07-12 19:46:47 +02:00

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");
});
});