test: cover the inbox edit verbs, completion reports and write inspection

This commit is contained in:
esengine committed 2026-10-16 07:28:32 -07:00
1 parent 3765035e3d
commit f33caaca90
3 files changed
+236

No files matched your search

@@ -0,0 +1,53 @@
package evidence
import (
"encoding/json"
"strings"
"testing"
)
func TestParseCompletionReportNormalisesAValidReport(t *testing.T) {
raw := json.RawMessage(`{"status":" Complete ","summary":" fixed the loop ","acceptance_criteria":[
{"id":" c1 ","status":"SATISFIED","evidence":[
{"kind":" Verification ","summary":"ran","command":"go test ./..."},
{"kind":"diff","summary":"d","paths":["a.go"]},
{"kind":"files","summary":"f","paths":["b.go"]},
{"kind":"review","summary":"r"},
{"kind":"manual","summary":"m"}]}],
"unresolved":["integration run"]}`)
got, err := ParseCompletionReport(raw)
if err != nil {
t.Fatal(err)
}
if got.Status != CompletionComplete || got.Summary != "fixed the loop" {
t.Fatalf("status/summary = %q / %q", got.Status, got.Summary)
}
c := got.Criteria[0]
if c.ID != "c1" || c.Status != CriterionSatisfied || c.Evidence[0].Kind != CompletionEvidenceVerification {
t.Fatalf("criterion = %+v", c)
}
if len(got.Unresolved) != 1 {
t.Fatalf("unresolved = %v", got.Unresolved)
}
}
func TestParseCompletionReportRefusesWhatIsMalformedAndSaysWhere(t *testing.T) {
for name, tc := range map[string]struct{ raw, want string }{
"not json": {`{`, "invalid complete_subtask JSON"},
"unknown status": {`{"status":"done","summary":"x"}`, "status must be"},
"missing status": {`{"summary":"x"}`, "status must be"},
"blank summary": {`{"status":"complete","summary":" "}`, "summary is required"},
"criterion without id": {`{"status":"partial","summary":"x","acceptance_criteria":[{"id":" ","status":"satisfied"}]}`, "acceptance_criteria[0].id"},
"criterion with odd status": {`{"status":"partial","summary":"x","acceptance_criteria":[{"id":"a","status":"maybe"}]}`, "acceptance_criteria[0].status"},
"verification with no cmd": {`{"status":"partial","summary":"x","acceptance_criteria":[{"id":"a","status":"satisfied","evidence":[{"kind":"verification","summary":"s"}]}]}`, "verification requires command"},
"diff with no paths": {`{"status":"partial","summary":"x","acceptance_criteria":[{"id":"a","status":"satisfied","evidence":[{"kind":"diff","summary":"s"}]}]}`, "diff requires paths"},
"files with no paths": {`{"status":"partial","summary":"x","acceptance_criteria":[{"id":"a","status":"satisfied","evidence":[{"kind":"files","summary":"s"}]}]}`, "files requires paths"},
"unknown evidence kind": {`{"status":"partial","summary":"x","acceptance_criteria":[{"id":"a","status":"satisfied","evidence":[{"kind":"vibes","summary":"s"}]}]}`, "not a known evidence kind"},
"second criterion is broken": {`{"status":"partial","summary":"x","acceptance_criteria":[{"id":"a","status":"satisfied"},{"id":"","status":"satisfied"}]}`, "acceptance_criteria[1].id"},
} {
_, err := ParseCompletionReport(json.RawMessage(tc.raw))
if err == nil || !strings.Contains(err.Error(), tc.want) {
t.Errorf("%s: err = %v, want it to mention %q", name, err, tc.want)
}
}
}
@@ -0,0 +1,95 @@
package evidence
import (
"encoding/json"
"slices"
"testing"
"reasonix/internal/contract/provider"
)
func recorded(receipts ...Receipt) *Ledger {
l := NewLedger()
for _, r := range receipts {
l.Record(r)
}
return l
}
func TestUninspectedWritePathsIsFreshnessPerPath(t *testing.T) {
write := func(p string) Receipt {
return Receipt{ToolName: "edit_file", Success: true, Write: true, Mutation: true, Paths: []string{p}}
}
read := func(p string) Receipt {
return Receipt{ToolName: "read_file", Success: true, Read: true, Paths: []string{p}}
}
l := recorded(write("a.go"), read("a.go"), write("b.go"), write("c.go"), read("c.go"), write("a.go"))
// a.go was read, then written again; b.go was never read; c.go was read after its last write.
got := l.UninspectedWritePaths([]string{"a.go", "b.go", "c.go", "never-written.go", ""})
if want := []string{"a.go", "b.go"}; !slices.Equal(got, want) {
t.Fatalf("UninspectedWritePaths = %v, want %v", got, want)
}
}
func TestUninspectedWritePathsCountsAFailedWriteForNothingAndAShownPathAsSeen(t *testing.T) {
failed := Receipt{ToolName: "edit_file", Success: false, Write: true, Paths: []string{"x.go"}}
shown := Receipt{ToolName: "bash", Success: true, Showed: []string{"y.go"}}
wrote := Receipt{ToolName: "edit_file", Success: true, Write: true, Paths: []string{"y.go"}}
l := recorded(failed, wrote, shown)
if got := l.UninspectedWritePaths([]string{"x.go", "y.go"}); len(got) != 0 {
t.Fatalf("UninspectedWritePaths = %v, want none (x.go never written, y.go shown)", got)
}
var nilLedger *Ledger
if nilLedger.UninspectedWritePaths([]string{"a"}) != nil || recorded().UninspectedWritePaths(nil) != nil {
t.Fatal("a nil ledger and an empty path list owe nothing")
}
}
func TestReceiptProgressSummaryCountsOnlySuccessfulWork(t *testing.T) {
l := recorded(
Receipt{ToolName: "edit_file", Success: true, Write: true},
Receipt{ToolName: "bash", Success: true, Mutation: true, Command: "make build"},
Receipt{ToolName: "bash", Success: false, Command: "make test"},
Receipt{ToolName: "todo_write", Success: true},
Receipt{ToolName: "todo_write", Success: false},
Receipt{ToolName: "complete_step", Success: true, StepProof: true},
Receipt{ToolName: "complete_step", Success: true},
)
got := l.ReceiptProgressSummary()
if got.Writes != 2 || got.Commands != 1 || got.Todos != 1 || got.Signoffs != 1 {
t.Fatalf("summary = %+v, want 2 writes, 1 command, 1 todo, 1 signoff", got)
}
var nilLedger *Ledger
if nilLedger.ReceiptProgressSummary() != (ReceiptProgressSummary{}) {
t.Fatal("a nil ledger has no progress")
}
}
func TestPathsProvenInSessionNeedsEveryPathAndIgnoresFailedCalls(t *testing.T) {
call := func(id, name, path string) provider.ToolCall {
args, _ := json.Marshal(map[string]string{"path": path})
return provider.ToolCall{ID: id, Name: name, Arguments: string(args)}
}
facts := func(name string) ToolFacts {
return ToolFacts{ReadOnly: name == "read_file", WritesNamedPaths: name == "edit_file"}
}
msgs := []provider.Message{
{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{call("1", "edit_file", "a.go"), call("2", "edit_file", "b.go"), call("3", "read_file", "c.go")}},
{Role: provider.RoleTool, ToolCallID: "2", Content: "error: permission denied"},
}
if !PathsProvenInSession(msgs, []string{"a.go"}, true, facts) {
t.Fatal("a.go was written by a call that did not fail")
}
if PathsProvenInSession(msgs, []string{"a.go", "b.go"}, true, facts) {
t.Fatal("b.go's write failed, so both paths are not proven")
}
if PathsProvenInSession(msgs, []string{"c.go"}, true, facts) {
t.Fatal("a read does not prove a write")
}
if !PathsProvenInSession(msgs, []string{"c.go"}, false, facts) {
t.Fatal("a read proves the path was looked at when writes are not required")
}
if PathsProvenInSession(msgs, nil, false, facts) {
t.Fatal("no paths proves nothing")
}
}
@@ -0,0 +1,88 @@
package control
import (
"errors"
"testing"
"reasonix/internal/state/sessioninbox"
)
func pausedInboxWith(t *testing.T, submit string, extra map[string]string) (*Controller, string) {
t.Helper()
c, _, _ := newInboxDispatchController(t)
if err := c.SetInboxPaused(true); err != nil {
t.Fatal(err)
}
rec, err := c.EnqueueInbox(InboxRequest{Submit: submit, Extra: extra})
if err != nil {
t.Fatal(err)
}
return c, rec.ItemID
}
func TestAppendInboxItemMergesTextAndBindsTheMessageIDAsAnAlias(t *testing.T) {
c, id := pausedInboxWith(t, "first line", nil)
meta, err := c.AppendInboxItem(id, " second line ", "platform-msg-7", nil)
if err != nil {
t.Fatal(err)
}
if meta.ID != id {
t.Fatalf("append returned item %q, want the same durable item %q", meta.ID, id)
}
_, env, err := c.ReadInboxItem(id)
if err != nil {
t.Fatal(err)
}
if env.SubmitText != "first line\nsecond line" || env.DisplayText != env.SubmitText || env.RawText != env.SubmitText {
t.Fatalf("merged text = submit %q display %q raw %q", env.SubmitText, env.DisplayText, env.RawText)
}
again, err := c.EnqueueInbox(InboxRequest{Submit: "second line", Idempotency: "platform-msg-7"})
if err != nil {
t.Fatal(err)
}
if again.ItemID != id {
t.Fatalf("the redelivered platform message created %q instead of resolving to %q", again.ItemID, id)
}
}
func TestAppendInboxItemRefusesNothingToAddAndWhatIsNotThere(t *testing.T) {
c, id := pausedInboxWith(t, "first line", nil)
if _, err := c.AppendInboxItem(id, " ", "k", nil); !errors.Is(err, sessioninbox.ErrEmpty) {
t.Fatalf("blank append = %v, want ErrEmpty", err)
}
if _, err := c.AppendInboxItem("no-such-item", "text", "k", nil); !errors.Is(err, sessioninbox.ErrNotFound) {
t.Fatalf("unknown id = %v, want ErrNotFound", err)
}
_, env, err := c.ReadInboxItem(id)
if err != nil || env.SubmitText != "first line" {
t.Fatalf("a refused append changed the item: %q (%v)", env.SubmitText, err)
}
}
func TestUpdateInboxItemReplacesTheWordsAndFallsBackAcrossFields(t *testing.T) {
c, id := pausedInboxWith(t, "original", nil)
meta, err := c.UpdateInboxItem(id, "shown", "raw words", " sent words ")
if err != nil {
t.Fatal(err)
}
if meta.ID != id {
t.Fatalf("update returned %q, want %q", meta.ID, id)
}
_, env, err := c.ReadInboxItem(id)
if err != nil {
t.Fatal(err)
}
if env.SubmitText != "sent words" || env.DisplayText != "shown" || env.RawText != "raw words" {
t.Fatalf("after update: submit %q display %q raw %q", env.SubmitText, env.DisplayText, env.RawText)
}
if _, err := c.UpdateInboxItem(id, "", "", "only submit"); err != nil {
t.Fatal(err)
}
_, env, _ = c.ReadInboxItem(id)
if env.DisplayText != "only submit" || env.RawText != "only submit" {
t.Fatalf("display and raw must follow submit when blank: %q / %q", env.DisplayText, env.RawText)
}
if _, err := c.UpdateInboxItem("no-such-item", "a", "a", "a"); !errors.Is(err, sessioninbox.ErrNotFound) {
t.Fatalf("unknown id = %v, want ErrNotFound", err)
}
}