mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
test: cover the inbox edit verbs, completion reports and write inspection
This commit is contained in:
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user