Ctrl K
ComunidadGuías…

test/resume.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 { generateDeck } from "../src/ai/generate.js";

const PLAN = {
  title: "Resumable deck",
  sections: ["Intro"],
  slides: [
    { type: "title", section: 0, purpose: "Open the deck." },
    { type: "bullets", section: 0, purpose: "The key point." },
    { type: "cards", section: 0, purpose: "Three ideas." },
    { type: "closing", section: 0, purpose: "Close." },
  ],
};

const SLIDES = {
  title: { type: "title", headline: "Resumable deck", subtitle: "s" },
  bullets: { type: "bullets", headline: "Key point", bullets: ["a", "b", "c", "d"] },
  cards: { type: "cards", headline: "Ideas", cards: [{ title: "A", body: "b" }, { title: "B", body: "b" }, { title: "C", body: "b" }] },
  closing: { type: "closing", headline: "Thanks", quote: "q" },
};

/** A fake chat that appends the given slide per type, throwing the named error
 *  when the requested slide's type matches `throwOn`. */
function fakeChat({ throwOn } = {}) {
  return async ({ schema }) => {
    const type = schema.properties.ops.items.properties.slide.properties.type.enum[0];
    if (type === throwOn) {
      const err = new Error("stopped");
      err.name = "AbortError";
      throw err;
    }
    return { data: { ops: [{ op: "append_slide", slide: SLIDES[type] }] } };
  };
}

test("an aborted write leaves a checkpoint of the slides written so far", async () => {
  const checkpoints = [];
  await assert.rejects(
    generateDeck({
      brief: "b", plan: PLAN, model: "mock", chat: fakeChat({ throwOn: "cards" }),
      onSlide: (deck, written, total) => checkpoints.push({ deck, written, total }),
    }),
    (err) => err.name === "AbortError",
  );
  // The abort happened writing the third slide; two slides had been checkpointed.
  assert.equal(checkpoints.length, 2);
  assert.equal(checkpoints[1].written, 2);
  assert.equal(checkpoints[1].total, 4);
  assert.equal(checkpoints[1].deck.slides.length, 2);
  assert.equal(checkpoints[1].deck.slides[0].type, "title");
  assert.equal(checkpoints[1].deck.slides[1].type, "bullets");
});

test("a resume continues from the checkpoint and completes the deck", async () => {
  // First run writes slide 0 and aborts on slide 1.
  const checkpoints = [];
  await assert.rejects(
    generateDeck({
      brief: "b", plan: PLAN, model: "mock", chat: fakeChat({ throwOn: "bullets" }),
      onSlide: (deck, written) => checkpoints.push({ deck, written }),
    }),
    (err) => err.name === "AbortError",
  );
  const partial = checkpoints[0].deck;

  // Resume with the checkpoint as baseDeck, continuing from index 1.
  const res = await generateDeck({
    brief: "b", plan: PLAN, model: "mock", chat: fakeChat(),
    baseDeck: partial, fromIndex: 1,
    onSlide: () => {},
  });

  assert.equal(res.deck.slides.length, 4);
  assert.deepEqual(res.deck.slides.map((s) => s.type), ["title", "bullets", "cards", "closing"]);
  assert.deepEqual(res.deck.slides[0].headline, partial.slides[0].headline, "checkpointed slide preserved");
});

test("a resumed run re-runs the writer only for the remaining slides", async () => {
  const partial = {
    title: PLAN.title,
    sections: PLAN.sections,
    slides: [SLIDES.title],
  };
  const calls = [];
  const chat = async ({ schema }) => {
    const type = schema.properties.ops.items.properties.slide.properties.type.enum[0];
    calls.push(type);
    return { data: { ops: [{ op: "append_slide", slide: SLIDES[type] }] } };
  };
  await generateDeck({
    brief: "b", plan: PLAN, model: "mock", chat,
    baseDeck: partial, fromIndex: 1, onSlide: () => {},
  });
  assert.deepEqual(calls, ["bullets", "cards", "closing"], "only the unwritten slides hit the writer");
});