Files
kanban_cpp/backend/flows_source_test.go
agent 0b93a985d6 feat(backend): issues/flows sync layer (issue 0119)
Read dev/issues/*.md and dev/flows/*.md as kanban cards via new
/api/boards/{issues|flows}/cards endpoints. PATCH writes status back
to the frontmatter atomically (tmp + rename), POST .../launch proxies
to agent_runner_api.

- issues_source.go: scan + parse frontmatter (yaml.v3) into IssueCard.
  Skips README/INDEX/AGENT_GUIDE. Malformed YAML yields parse-error
  cards (no crash). Description = first 5 body lines (no full body).
- flows_source.go: same shape, distinct status->column mapping
  (pending/running/done/deferred -> Pending/Running/Done/Deferred).
- frontmatter_edit.go: PatchFrontmatterField — atomic, preserves the
  rest of the file byte-for-byte, inserts key if missing.
- handlers_boards.go: list + patch + launch endpoints, taxonomy 0103
  enforced. Cache 30s in memory, thread-safe (mutex), invalidated on
  PATCH. Launch returns 502 with suggestion when runner is down.
- main.go: SkipPaths += "/api/boards/" so the C++ frontend hits the
  read endpoints without a kanban_web session.

Smoke (FN_REGISTRY_ROOT pointed at the worktree, 87 issues + 9 flows
on disk):
  GET  /api/boards/issues/cards -> 200, 87 cards
  GET  /api/boards/flows/cards  -> 200, 9 cards
  PATCH /api/boards/issues/cards/0119 {status:en-curso} -> 200,
    file mtime changes, frontmatter rewritten, rest preserved
  POST /api/boards/issues/cards/0119/launch -> 502
    agent_runner_unreachable (expected, runner not yet implemented)

Tests: issues_source_test (3 cases incl. malformed + missing status),
flows_source_test (3 cases), frontmatter_edit_test (4 cases incl.
atomic rename + no tmp leftovers). Pre-existing tools_test failure
on TestExecuteTool_MoveCard_BetweenColumns_OpensHistory is unrelated
(CardHistoryResponse type assert, not touched here).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:56:22 +02:00

61 lines
1.7 KiB
Go

package main
import (
"testing"
)
func TestLoadFlowCards_MapsStatuses(t *testing.T) {
dir := t.TempDir()
writeFixture(t, dir, "INDEX.md", "skip")
writeFixture(t, dir, "0001-foo.md", "---\nid: 0001\nname: foo\nstatus: pending\n---\nbody\n")
writeFixture(t, dir, "0002-bar.md", "---\nid: 0002\nname: bar\nstatus: running\n---\nbody\n")
writeFixture(t, dir, "0003-baz.md", "---\nid: 0003\nname: baz\nstatus: done\n---\nbody\n")
writeFixture(t, dir, "0004-bop.md", "---\nid: 0004\nname: bop\nstatus: deferred\n---\nbody\n")
cards, err := loadFlowCards(dir)
if err != nil {
t.Fatalf("load: %v", err)
}
if len(cards) != 4 {
t.Fatalf("expected 4 cards, got %d", len(cards))
}
want := map[string]string{
"0001": "Pending",
"0002": "Running",
"0003": "Done",
"0004": "Deferred",
}
for _, c := range cards {
if want[c.ID] != c.ColumnID {
t.Fatalf("%s: expected column %s, got %s", c.ID, want[c.ID], c.ColumnID)
}
}
}
func TestLoadFlowCards_MissingStatusDefaultsPending(t *testing.T) {
dir := t.TempDir()
writeFixture(t, dir, "0010-nostatus.md", "---\nid: 0010\nname: empty\n---\nbody\n")
cards, err := loadFlowCards(dir)
if err != nil {
t.Fatalf("load: %v", err)
}
if len(cards) != 1 {
t.Fatalf("expected 1 card")
}
if cards[0].ColumnID != "Pending" {
t.Fatalf("expected Pending column, got %q", cards[0].ColumnID)
}
}
func TestLoadFlowCards_MalformedDoesNotCrash(t *testing.T) {
dir := t.TempDir()
writeFixture(t, dir, "0011-bad.md", "---\nid: 0011\nstatus: pending\n : malformed yaml\n---\nbody\n")
cards, err := loadFlowCards(dir)
if err != nil {
t.Fatalf("load: %v", err)
}
if len(cards) != 1 || cards[0].ParseError == "" {
t.Fatalf("expected 1 card with ParseError, got %#v", cards)
}
}