ollie/fs/log_validator_test.go

169 lines
4.5 KiB
Go

package fs
import (
"strings"
"testing"
"ollie/format"
)
func TestValidate_validUserAssistant(t *testing.T) {
v := NewLogValidator()
data := []byte("[[[user]]]\nhello\n[[[end]]]\n[[[assistant]]]\nworld\n[[[end]]]\n")
errs := v.Validate(data)
if errs != nil {
t.Fatalf("expected no errors, got: %v", errs)
}
}
func TestValidate_nestedBlocks(t *testing.T) {
v := NewLogValidator()
data := []byte("[[[user]]]\nouter\n[[[assistant]]]\nnested — should fail\n")
errs := v.Validate(data)
if len(errs) == 0 {
t.Fatal("expected nesting violation, got none")
}
got := errs[0].Error()
if !strings.Contains(got, "nested block") {
t.Errorf("expected 'nested block' in error, got: %s", got)
}
}
func TestValidate_unexpectedEnd(t *testing.T) {
v := NewLogValidator()
data := []byte("[[[end]]]\n")
errs := v.Validate(data)
if len(errs) == 0 {
t.Fatal("expected unexpected end error, got none")
}
got := errs[0].Error()
if !strings.Contains(got, "unexpected") {
t.Errorf("expected 'unexpected' in error, got: %s", got)
}
}
func TestValidate_unclosedAtTeardown(t *testing.T) {
v := NewLogValidator()
data := []byte("[[[user]]]\nhello\n")
v.Validate(data)
unclosed := v.UnclosedBlocks()
if len(unclosed) != 1 {
t.Fatalf("expected 1 unclosed block, got %d", len(unclosed))
}
if unclosed[0].header.Role != "user" {
t.Errorf("expected role 'user', got '%s'", unclosed[0].header.Role)
}
}
func TestValidate_toolWithSubname(t *testing.T) {
v := NewLogValidator()
data := []byte("[[[tool:shell]]]\necho hi\n```\n[[[end]]]\n")
errs := v.Validate(data)
if errs != nil {
t.Fatalf("expected no errors, got: %v", errs)
}
}
func TestValidate_partialLineAcrossWrites(t *testing.T) {
v := NewLogValidator()
// "[[[user]]]" split across two writes
v.Validate([]byte("[["))
v.Validate([]byte("[user]]]\nbody\n"))
// Now validate the end marker separately
v.Validate([]byte("[[[end]]]\n"))
errs := v.Validate(nil) // flush remaining buffer
if errs != nil {
t.Fatalf("expected no errors, got: %v", errs)
}
}
func TestValidate_fatalRejectsFurtherData(t *testing.T) {
v := NewLogValidator()
// First write causes a fatal nesting error
v.Validate([]byte("[[[user]]]\n[[[assistant]]]\n"))
errs := v.Validate([]byte("[[[end]]]\n"))
if len(errs) == 0 {
t.Fatal("expected rejection after fatal, got none")
}
if !strings.Contains(errs[0].Error(), "already failed") {
t.Errorf("expected 'already failed' message, got: %s", errs[0].Error())
}
}
func TestValidate_hasViolations(t *testing.T) {
v := NewLogValidator()
if v.HasViolations() {
t.Fatal("fresh validator should have no violations")
}
v.Validate([]byte("[[[user]]]\n"))
if v.HasViolations() {
t.Fatal("valid open block should not be a violation yet")
}
v.Validate([]byte("[[[assistant]]]\n"))
if !v.HasViolations() {
t.Fatal("nested block should trigger HasViolations")
}
}
func TestParseBlockHeader(t *testing.T) {
tests := []struct {
input string
want *format.BlockHeader
}{
{"[[[user]]]", &format.BlockHeader{Role: "user"}},
{"[[[assistant]]]", &format.BlockHeader{Role: "assistant"}},
{"[[[tool:shell]]]", &format.BlockHeader{Role: "tool", Name: "shell"}},
{"[[[call:file_read]]]", &format.BlockHeader{Role: "call", Name: "file_read"}},
{"[[[retry]]]", &format.BlockHeader{Role: "retry"}},
{"not a header", nil},
{"[[[user]]] extra", nil},
{"", nil},
}
for _, tt := range tests {
got := format.ParseBlockHeader(tt.input)
if (got == nil) != (tt.want == nil) {
t.Errorf("ParseBlockHeader(%q) = %v, want %v", tt.input, got, tt.want)
continue
}
if got != nil && (got.Role != tt.want.Role || got.Name != tt.want.Name) {
t.Errorf("ParseBlockHeader(%q) = [%s:%s], want [%s:%s]",
tt.input, got.Role, got.Name, tt.want.Role, tt.want.Name)
}
}
}
func TestIsEndMarker(t *testing.T) {
tests := []struct {
input string
want bool
}{
{"[[[end]]]", true},
{"[[[end]]] ", false},
{" [[[end]]]", false},
{"[[[end]]]\n", false},
{"[[[user]]]", false},
{"", false},
}
for _, tt := range tests {
got := format.IsEndMarker(tt.input)
if got != tt.want {
t.Errorf("IsEndMarker(%q) = %v, want %v", tt.input, got, tt.want)
}
}
}
func TestValidate_lineNumbers(t *testing.T) {
v := NewLogValidator()
data := []byte("[[[user]]]\nline2\n[[[assistant]]]\n")
errs := v.Validate(data)
if len(errs) == 0 {
t.Fatal("expected nesting error at line 3, got none")
}
// The nested assistant header is on line 3
if ve, ok := errs[0].(*violation); ok {
if ve.line != 3 {
t.Errorf("expected violation at line 3, got line %d", ve.line)
}
}
}