103 lines
3.7 KiB
Go
103 lines
3.7 KiB
Go
// 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")
|
||
}
|
||
}
|