// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later package codex import ( "strings" "testing" "git.stormux.org/storm/fenrir/internal/command" "git.stormux.org/storm/fenrir/internal/screen" ) func accept_event(t *testing.T, a *Adapter, event Event) { t.Helper() event.Version, event.Session, event.Epoch = 1, "fixture-session", 1 if err := a.Accept(event); err != nil { t.Fatal(err) } } func TestCompleteChunksDeduplicationHistoryAndStatus(t *testing.T) { a := &Adapter{} accept_event(t, a, Event{Kind: "status", Status: "working"}) accept_event(t, a, Event{Kind: "message", ID: "first", Text: "Whole ", Parts: 2}) if frame := a.Observe(screen.Snapshot{}); len(frame.Updates) != 0 { t.Fatal("partial message spoken") } accept_event(t, a, Event{Kind: "message", ID: "first", Text: "reply", Part: 1, Parts: 2}) frame := a.Observe(screen.Snapshot{}) if !frame.OwnsPresentation || len(frame.Updates) != 1 || frame.Updates[0].Text != "Whole reply" { t.Fatal(frame) } accept_event(t, a, Event{Kind: "message", ID: "first", Text: "Whole reply", Parts: 1}) if len(a.Observe(screen.Snapshot{}).Updates) != 0 { t.Fatal("duplicate spoken") } accept_event(t, a, Event{Kind: "message", ID: "second", Text: "Old reply", Parts: 1, Baseline: true}) if len(a.Observe(screen.Snapshot{}).Updates) != 0 { t.Fatal("baseline spoken") } if got, _ := a.Command(command.ReadLiveRegion1); got.Text != "Old reply" { t.Fatal(got) } if got, _ := a.Command(command.ReadLiveRegion2); got.Text != "Whole reply" { t.Fatal(got) } if got, _ := a.Command(command.ReadLiveRegion10); got.Text != "Codex working" { t.Fatal(got) } a.Reset() if got, _ := a.Command(command.ReadLiveRegion2); got.Text != "Whole reply" { t.Fatal("suspension lost history") } accept_event(t, a, Event{Kind: "status", Status: "closed"}) if a.Observe(screen.Snapshot{}).Recognized { t.Fatal("exit retained presentation ownership") } } func TestRejectInterleavedSessionOutOfOrderAndOversize(t *testing.T) { a := &Adapter{} accept_event(t, a, Event{Kind: "status", Status: "idle"}) if err := a.Accept(Event{Version: 1, Epoch: 1, Session: "another-session", Kind: "status", Status: "working"}); err == nil { t.Fatal("another session attached") } if err := a.Accept(Event{Version: 1, Epoch: 1, Session: "fixture-session", Kind: "message", ID: "test", Part: 1, Parts: 2, Text: "tail"}); err == nil { t.Fatal("out of order accepted") } if err := a.Accept(Event{Version: 1, Epoch: 1, Session: "fixture-session", Kind: "message", ID: "test", Parts: 1, Text: strings.Repeat("x", 6001)}); err == nil { t.Fatal("oversize chunk accepted") } } func TestConversationSwitchRejectsPreviousFollower(t *testing.T) { a := &Adapter{} accept_event(t, a, Event{Kind: "status", Status: "working"}) accept_event(t, a, Event{Kind: "message", ID: "old", Text: "Previous conversation", Parts: 1}) if err := a.Accept(Event{Version: 1, Epoch: 2, Session: "new-session", Kind: "status", Status: "idle"}); err != nil { t.Fatal(err) } if len(a.history) != 0 || len(a.pending) != 0 { t.Fatal("conversation history leaked") } if err := a.Accept(Event{Version: 1, Epoch: 1, Session: "fixture-session", Kind: "status", Status: "working"}); err == nil { t.Fatal("old follower replaced new conversation") } } func TestInputLimitedToComposer(t *testing.T) { a := &Adapter{} s := screen.Snapshot{Active: true, Rows: 8, Cols: 40, X: 5, Y: 6, Cells: []rune(strings.Repeat(" ", 320))} copy(s.Line(6), []rune("› hello")) frame := a.Observe(s) if frame.Input == nil || frame.Input.Top != 6 || frame.Input.Left != 2 { t.Fatal(frame.Input) } s.Y = 2 if a.Observe(s).Input != nil { t.Fatal("transcript treated as composer") } }