169 lines
4.5 KiB
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)
|
|
}
|
|
}
|
|
}
|