Ctrl K
ComunidadGuías…

test/finalize-presenters.test.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { test } from "node:test";
import assert from "node:assert/strict";
import { mkdtemp, mkdir, writeFile, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import YAML from "yaml";
import { DIVIDER_TYPES } from "../src/ai/team.js";

// finalizeDeck reads DECKS from src/paths.js at import time, so this test
// imports the pipeline fresh after pointing the env at a scratch dir.
const scratch = await mkdtemp(path.join(tmpdir(), "forge-finalize-"));
process.env.FORGE_DECKS_DIR = scratch;
// Pinned for the same reason FORGE_DECKS_DIR is: finalize now records the
// deck's score, and an unpinned config dir writes that row into the real
// config/scores.jsonl — a test polluting the operator's history.
process.env.FORGE_CONFIG_DIR = process.env.FORGE_CONFIG_DIR ?? path.join(scratch, "config");

const { finalizeDeck } = await import("../src/ai/pipeline.js");

/** A fake model client: coherence review finds nothing, field-length rewrites
 *  nothing. A deck with short content never flags, so no model calls occur —
 *  the fake exists to make the pipeline not touch Ollama even if one does. */
const fakeChat = async () => ({ data: { findings: [] } });

async function makeDeck(slug) {
  const dir = path.join(scratch, slug);
  await mkdir(path.join(dir, "research"), { recursive: true });
  const meta = {
    slug,
    brief: "finalize presenter test",
    theme: "warm-humanist",
    slidesPerMember: 1,
    team: {
      label: "Group T",
      members: [
        { name: "A", roll: "1", presenting: true },
        { name: "B", roll: "2", presenting: true },
        { name: "C", roll: "3", presenting: true },
      ],
    },
  };
  await writeFile(path.join(dir, "meta.yaml"), YAML.stringify(meta));
  const plan = {
    title: "finalize presenter test",
    sections: ["Intro", "Body", "Close"],
    slides: [
      { type: "title", section: 0, purpose: "open" },
      { type: "section", section: 0, purpose: "intro part" },
      { type: "bullets", section: 0, purpose: "key points" },
      { type: "bullets", section: 1, purpose: "body points" },
      { type: "section", section: 1, purpose: "body part" },
      { type: "bullets", section: 2, purpose: "close points" },
      { type: "closing", section: 2, purpose: "close" },
    ],
  };
  await writeFile(path.join(dir, "plan.yaml"), YAML.stringify(plan));
  // The deck as the resume shortcut would leave it: content written, but with
  // NO presenters — the write half assigns them in memory only at the end, and
  // finalize re-reads deck.yaml from disk.
  const deck = {
    title: plan.title,
    sections: plan.sections,
    theme: "warm-humanist",
    slides: [
      { type: "title", headline: "finalize presenter test", subtitle: "s" },
      { type: "section", section: 0, headline: "Intro" },
      { type: "bullets", section: 0, headline: "Key points", bullets: ["a", "b", "c", "d"] },
      { type: "bullets", section: 1, headline: "Body points", bullets: ["a", "b", "c", "d"] },
      { type: "section", section: 1, headline: "Body" },
      { type: "bullets", section: 2, headline: "Close points", bullets: ["a", "b", "c", "d"] },
      { type: "closing", headline: "Thanks" },
    ],
  };
  await writeFile(path.join(dir, "deck.yaml"), YAML.stringify(deck));
  return dir;
}

test("finalizeDeck assigns presenters to a deck that reached finalize presenter-less", async () => {
  const dir = await makeDeck("finalize-presenters");
  try {
    const r = await finalizeDeck({ slug: "finalize-presenters", model: "mock", chat: fakeChat });
    const content = r.deck.slides.filter((s) => !DIVIDER_TYPES.has(s.type));
    assert.equal(content.length, 3, "three content slides");
    for (const s of content) assert.ok(s.presenter, `content slide gets a presenter (${s.type})`);
    // All three presenters are the team's — one per member, in order.
    assert.deepEqual(content.map((s) => s.presenter), ["A", "B", "C"]);
    // Dividers never carry a presenter.
    for (const s of r.deck.slides) {
      if (DIVIDER_TYPES.has(s.type)) assert.equal(s.presenter, undefined, `divider ${s.type} carries nothing`);
    }

    // The persisted deck.yaml carries the assignment too — finalize re-reads it
    // from disk, so the assignment must be on the file render reads.
    const persisted = YAML.parse(await readFile(path.join(dir, "deck.yaml"), "utf8"));
    const pContent = persisted.slides.filter((s) => !DIVIDER_TYPES.has(s.type));
    for (const s of pContent) assert.ok(s.presenter, `persisted content slide gets a presenter`);
  } finally {
    await rm(dir, { recursive: true, force: true });
  }
});