[object Object]

← back to Rentv 2026

test(adslots): rate-card data contract + rate-card/slot endpoint coverage

adcaafca8ca2be6f4a71156f744e7272f822e792 · 2026-08-08 07:40:29 -0700 · Steve Abrams

Covers the half of src/ad-system.cjs cycle-1 left untested. Real-data contract on
data/ad-slots.json (unique ids, required id/name/type, numeric price, valid status
enum, created_at present) so a malformed hand-edit is caught in CI, not by an
advertiser. Plus fake-Express coverage of /api/ad/rate-card creative merge and
/api/ad/slot/:id known-id merge + unknown-id 404. No production code changed.
Suite 77->84 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit adcaafca8ca2be6f4a71156f744e7272f822e792
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Aug 8 07:40:29 2026 -0700

    test(adslots): rate-card data contract + rate-card/slot endpoint coverage
    
    Covers the half of src/ad-system.cjs cycle-1 left untested. Real-data contract on
    data/ad-slots.json (unique ids, required id/name/type, numeric price, valid status
    enum, created_at present) so a malformed hand-edit is caught in CI, not by an
    advertiser. Plus fake-Express coverage of /api/ad/rate-card creative merge and
    /api/ad/slot/:id known-id merge + unknown-id 404. No production code changed.
    Suite 77->84 green.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 test/adslots/ratecard.test.mjs | 116 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 116 insertions(+)

diff --git a/test/adslots/ratecard.test.mjs b/test/adslots/ratecard.test.mjs
new file mode 100644
index 00000000..ee09dc30
--- /dev/null
+++ b/test/adslots/ratecard.test.mjs
@@ -0,0 +1,116 @@
+// ─────────────────────────────────────────────────────────────────────────────
+// test/adslots/ratecard.test.mjs — the OTHER half of src/ad-system.cjs that
+// cycle-1 (inventory + reserve) didn't cover: the banner/newsletter RATE CARD.
+// yoloforever TK-10364 loop cycle 2.
+//
+// Two layers:
+//  A. REAL-DATA CONTRACT — asserts the shipped data/ad-slots.json (served live by
+//     /api/ad/rate-card + /api/ad/slot/:id) is well-formed, so a hand-edit that
+//     drops an id / breaks a price silently breaking the advertiser rate card is
+//     caught in CI, not by an advertiser.
+//  B. ENDPOINT BEHAVIOR — via the same zero-dep fake-Express harness as cycle 1:
+//     /api/ad/rate-card merges persisted creatives onto placements; /api/ad/slot/:id
+//     returns a merged slot for a known id and 404 for an unknown one.
+// No production code changes — pure test coverage.
+// ─────────────────────────────────────────────────────────────────────────────
+import { test } from 'node:test';
+import assert from 'node:assert/strict';
+import { createRequire } from 'node:module';
+import fs from 'node:fs';
+import os from 'node:os';
+import path from 'node:path';
+
+const require = createRequire(import.meta.url);
+const mountAdSystem = require('../../src/ad-system.cjs');
+const realCard = require('../../data/ad-slots.json');
+
+// ── minimal fake Express app (captures last handler per "METHOD path") ────────
+function mount(DATA) {
+  const routes = {};
+  const reg = m => (p, ...h) => { routes[`${m} ${p}`] = h[h.length - 1]; };
+  const app = { get: reg('GET'), post: reg('POST'), put: reg('PUT'), use() {} };
+  mountAdSystem(app, { DATA, PUB: path.join(DATA, '__nopub__'), adminOnly: (_q, _r, n) => n && n() });
+  return routes;
+}
+function hit(routes, key, { query = {}, body = {}, params = {} } = {}) {
+  const req = { query, body, params, headers: {}, ip: '127.0.0.1' };
+  let out = { status: 200, body: null };
+  const res = { set() { return this; }, status(c) { out.status = c; return this; }, json(o) { out.body = o; return this; } };
+  routes[key](req, res);
+  return out;
+}
+function tmpData(files) {
+  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'adcard-'));
+  for (const [f, o] of Object.entries(files)) fs.writeFileSync(path.join(dir, f), JSON.stringify(o));
+  return dir;
+}
+
+const VALID_STATUS = new Set(['available', 'reserved', 'sold']);
+
+// ── A. real-data contract on the SHIPPED rate card ───────────────────────────
+test('ad-slots.json: placements are a non-empty array', () => {
+  assert.ok(Array.isArray(realCard.placements) && realCard.placements.length > 0);
+});
+
+test('ad-slots.json: every placement has id/name/type + a numeric price + valid status', () => {
+  for (const p of realCard.placements) {
+    assert.equal(typeof p.id, 'string'); assert.ok(p.id.length, 'non-empty id');
+    assert.equal(typeof p.name, 'string'); assert.ok(p.name.length, 'non-empty name');
+    assert.equal(typeof p.type, 'string'); assert.ok(p.type.length, 'non-empty type');
+    assert.equal(typeof p.price_monthly, 'number');
+    assert.ok(p.price_monthly >= 0 && Number.isFinite(p.price_monthly), `${p.id} price sane`);
+    assert.ok(VALID_STATUS.has(p.status), `${p.id} status "${p.status}" is a known enum`);
+  }
+});
+
+test('ad-slots.json: placement ids are unique', () => {
+  const ids = realCard.placements.map(p => p.id);
+  assert.equal(new Set(ids).size, ids.length, 'no duplicate slot ids');
+});
+
+test('ad-slots.json: every placement carries a created_at (admin-card recency rule)', () => {
+  for (const p of realCard.placements) assert.equal(typeof p.created_at, 'string', `${p.id} has created_at`);
+});
+
+// ── B. /api/ad/rate-card merges persisted creatives onto placements ──────────
+test('rate-card: persisted creative/status merges onto the matching placement', () => {
+  const DATA = tmpData({
+    'ad-slots.json': { placements: [
+      { id: 'home-leaderboard', name: 'HL', type: 'banner', price_monthly: 3500, status: 'available' },
+      { id: 'nl-top', name: 'NL', type: 'newsletter', price_monthly: 1200, status: 'available' },
+    ] },
+    'ad-state.json': { videos: {}, creatives: {
+      'home-leaderboard': { creative_url: '/x.png', click_url: 'https://acme.example', status: 'sold', advertiser: 'ACME' },
+    } },
+  });
+  const { status, body } = hit(mount(DATA), 'GET /api/ad/rate-card');
+  assert.equal(status, 200);
+  const hl = body.placements.find(p => p.id === 'home-leaderboard');
+  const nl = body.placements.find(p => p.id === 'nl-top');
+  assert.equal(hl.creative_url, '/x.png');
+  assert.equal(hl.click_url, 'https://acme.example');
+  assert.equal(hl.status, 'sold');
+  assert.equal(hl.advertiser, 'ACME');
+  assert.equal(nl.status, 'available', 'untouched placement stays as-is');
+});
+
+// ── B. /api/ad/slot/:id — known id merged, unknown id 404 ────────────────────
+test('slot/:id: returns the merged slot for a known id', () => {
+  const DATA = tmpData({
+    'ad-slots.json': { placements: [{ id: 'home-leaderboard', name: 'HL', type: 'banner', price_monthly: 3500, status: 'available' }] },
+    'ad-state.json': { videos: {}, creatives: { 'home-leaderboard': { creative_url: '/x.png', status: 'sold' } } },
+  });
+  const { status, body } = hit(mount(DATA), 'GET /api/ad/slot/:id', { params: { id: 'home-leaderboard' } });
+  assert.equal(status, 200);
+  assert.equal(body.ok, true);
+  assert.equal(body.slot.id, 'home-leaderboard');
+  assert.equal(body.slot.creative_url, '/x.png');
+  assert.equal(body.slot.status, 'sold');
+});
+
+test('slot/:id: unknown id returns 404', () => {
+  const DATA = tmpData({ 'ad-slots.json': { placements: [{ id: 'home-leaderboard', name: 'HL', type: 'banner', price_monthly: 3500, status: 'available' }] } });
+  const { status, body } = hit(mount(DATA), 'GET /api/ad/slot/:id', { params: { id: 'does-not-exist' } });
+  assert.equal(status, 404);
+  assert.equal(body.ok, false);
+});

← e15f5398 test(adslots): cover ad-system + fix filtered inventory summ  ·  back to Rentv 2026  ·  chore: sync package-lock version to 0.24.0 (trailing the v0. 9af4fe93 →