23e2ae9eb4
- ThreadIndexEntry gains status and completedAt fields - createThreadIndexEntry defaults to idle/null - normalizeThreadIndexEntry backward-compat defaults - updateThreadHead resets to idle (衔尾蛇 resume prep) - markThreadSuspended sets status=suspended - New markThreadCompleted(entry, status, now) function - serializeThreadIndexEntry includes new fields Part of #39, closes #40
170 lines
4.8 KiB
TypeScript
170 lines
4.8 KiB
TypeScript
import { describe, expect, test } from "vitest";
|
|
import {
|
|
createThreadIndexEntry,
|
|
markThreadCompleted,
|
|
markThreadSuspended,
|
|
normalizeThreadIndexEntry,
|
|
parseThreadsIndex,
|
|
serializeThreadIndexEntry,
|
|
serializeThreadsIndex,
|
|
updateThreadHead,
|
|
} from "../thread-index.js";
|
|
|
|
describe("thread-index", () => {
|
|
test("parse legacy string head hash", () => {
|
|
const entry = normalizeThreadIndexEntry("0123456789ABC");
|
|
expect(entry).toEqual({
|
|
head: "0123456789ABC",
|
|
suspendedRole: null,
|
|
suspendMessage: null,
|
|
status: "idle",
|
|
completedAt: null,
|
|
});
|
|
});
|
|
|
|
test("parse suspended object entry", () => {
|
|
const entry = normalizeThreadIndexEntry({
|
|
head: "0123456789ABC",
|
|
suspendedRole: "worker",
|
|
suspendMessage: "Please clarify: Which API?",
|
|
});
|
|
expect(entry).toEqual({
|
|
head: "0123456789ABC",
|
|
suspendedRole: "worker",
|
|
suspendMessage: "Please clarify: Which API?",
|
|
status: "idle",
|
|
completedAt: null,
|
|
});
|
|
});
|
|
|
|
test("normalizeThreadIndexEntry preserves status and completedAt from new data", () => {
|
|
const entry = normalizeThreadIndexEntry({
|
|
head: "0123456789ABC",
|
|
suspendedRole: null,
|
|
suspendMessage: null,
|
|
status: "completed",
|
|
completedAt: 1234567890,
|
|
});
|
|
expect(entry).toEqual({
|
|
head: "0123456789ABC",
|
|
suspendedRole: null,
|
|
suspendMessage: null,
|
|
status: "completed",
|
|
completedAt: 1234567890,
|
|
});
|
|
});
|
|
|
|
test("normalizeThreadIndexEntry defaults status=idle, completedAt=null for old data", () => {
|
|
const entry = normalizeThreadIndexEntry({
|
|
head: "0123456789ABC",
|
|
suspendedRole: null,
|
|
suspendMessage: null,
|
|
});
|
|
expect(entry).toEqual({
|
|
head: "0123456789ABC",
|
|
suspendedRole: null,
|
|
suspendMessage: null,
|
|
status: "idle",
|
|
completedAt: null,
|
|
});
|
|
});
|
|
|
|
test("serialize non-suspended entry as compact string", () => {
|
|
const entry = createThreadIndexEntry("0123456789ABC");
|
|
expect(serializeThreadIndexEntry(entry)).toBe("0123456789ABC");
|
|
});
|
|
|
|
test("serialize suspended entry as object", () => {
|
|
const entry = markThreadSuspended(
|
|
createThreadIndexEntry("0123456789ABC"),
|
|
"worker",
|
|
"Please clarify: Which API?",
|
|
);
|
|
expect(serializeThreadIndexEntry(entry)).toEqual({
|
|
head: "0123456789ABC",
|
|
suspendedRole: "worker",
|
|
suspendMessage: "Please clarify: Which API?",
|
|
status: "suspended",
|
|
});
|
|
});
|
|
|
|
test("serialize completed entry as object", () => {
|
|
const entry = markThreadCompleted(
|
|
createThreadIndexEntry("0123456789ABC"),
|
|
"completed",
|
|
1234567890,
|
|
);
|
|
expect(serializeThreadIndexEntry(entry)).toEqual({
|
|
head: "0123456789ABC",
|
|
status: "completed",
|
|
completedAt: 1234567890,
|
|
});
|
|
});
|
|
|
|
test("updateThreadHead clears suspend metadata and resets status to idle", () => {
|
|
const suspended = markThreadSuspended(
|
|
createThreadIndexEntry("OLDHEAD0123456"),
|
|
"worker",
|
|
"Waiting",
|
|
);
|
|
const resumed = updateThreadHead(suspended, "NEWHEAD01234567");
|
|
expect(resumed).toEqual({
|
|
head: "NEWHEAD01234567",
|
|
suspendedRole: null,
|
|
suspendMessage: null,
|
|
status: "idle",
|
|
completedAt: null,
|
|
});
|
|
});
|
|
|
|
test("markThreadSuspended sets status to suspended", () => {
|
|
const entry = createThreadIndexEntry("0123456789ABC");
|
|
const suspended = markThreadSuspended(entry, "worker", "Waiting for input");
|
|
expect(suspended).toEqual({
|
|
head: "0123456789ABC",
|
|
suspendedRole: "worker",
|
|
suspendMessage: "Waiting for input",
|
|
status: "suspended",
|
|
completedAt: null,
|
|
});
|
|
});
|
|
|
|
test("markThreadCompleted sets status and completedAt", () => {
|
|
const entry = createThreadIndexEntry("0123456789ABC");
|
|
const completed = markThreadCompleted(entry, "completed", 1234567890);
|
|
expect(completed).toEqual({
|
|
head: "0123456789ABC",
|
|
suspendedRole: null,
|
|
suspendMessage: null,
|
|
status: "completed",
|
|
completedAt: 1234567890,
|
|
});
|
|
});
|
|
|
|
test("markThreadCompleted with cancelled status", () => {
|
|
const entry = createThreadIndexEntry("0123456789ABC");
|
|
const cancelled = markThreadCompleted(entry, "cancelled", 9876543210);
|
|
expect(cancelled).toEqual({
|
|
head: "0123456789ABC",
|
|
suspendedRole: null,
|
|
suspendMessage: null,
|
|
status: "cancelled",
|
|
completedAt: 9876543210,
|
|
});
|
|
});
|
|
|
|
test("parseThreadsIndex round-trip", () => {
|
|
const raw = {
|
|
"01THREAD0000000000000001": "HEAD00000000001",
|
|
"01THREAD0000000000000002": {
|
|
head: "HEAD00000000002",
|
|
suspendedRole: "reviewer",
|
|
suspendMessage: "Need input",
|
|
status: "suspended",
|
|
},
|
|
};
|
|
const parsed = parseThreadsIndex(raw);
|
|
expect(serializeThreadsIndex(parsed)).toEqual(raw);
|
|
});
|
|
});
|