package review import ( "fmt" "os" "path/filepath" "strings" "testing" "github.com/goccy/go-yaml/ast" ) func TestLoadBuiltinPersona(t *testing.T) { tests := []struct { name string personaName string wantErr bool wantDisplay string }{ { name: "security persona", personaName: "security", wantErr: false, wantDisplay: "Security Specialist", }, { name: "architect persona", personaName: "architect", wantErr: false, wantDisplay: "Software Architect", }, { name: "docs persona", personaName: "docs", wantErr: false, wantDisplay: "Documentation Reviewer", }, { name: "unknown persona", personaName: "nonexistent", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p, err := LoadBuiltinPersona(tt.personaName) if tt.wantErr { if err == nil { t.Error("expected error, got nil") } return } if err != nil { t.Fatalf("unexpected error: %v", err) } if p.Name != tt.personaName { t.Errorf("Name = %q, want %q", p.Name, tt.personaName) } if p.DisplayName != tt.wantDisplay { t.Errorf("DisplayName = %q, want %q", p.DisplayName, tt.wantDisplay) } if p.Identity == "" { t.Error("Identity should not be empty") } if len(p.Focus) == 0 { t.Error("Focus should not be empty") } }) } } func TestListBuiltinPersonas(t *testing.T) { names := ListBuiltinPersonas() if len(names) == 0 { t.Fatal("expected at least one built-in persona") } // Check for expected personas expected := map[string]bool{"security": false, "architect": false, "docs": false} for _, name := range names { if _, ok := expected[name]; ok { expected[name] = true } } for name, found := range expected { if !found { t.Errorf("expected built-in persona %q not found", name) } } } func TestLoadPersonaFromYAMLFile(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test.yaml") content := `# Test persona name: test display_name: Test Persona identity: | You are a test persona. Multi-line identity works. focus: - testing - validation ignore: - nothing severity: major: Big problems minor: Small problems nit: Tiny problems ` if err := os.WriteFile(path, []byte(content), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } p, err := LoadPersona(path) if err != nil { t.Fatalf("LoadPersona failed: %v", err) } if p.Name != "test" { t.Errorf("Name = %q, want %q", p.Name, "test") } if p.DisplayName != "Test Persona" { t.Errorf("DisplayName = %q, want %q", p.DisplayName, "Test Persona") } if len(p.Focus) != 2 { t.Errorf("Focus len = %d, want 2", len(p.Focus)) } if !strings.Contains(p.Identity, "Multi-line") { t.Error("Identity should contain multi-line content") } } func TestLoadPersonaFromYMLFile(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test.yml") content := `name: test display_name: Test YML identity: Test identity focus: - testing ignore: [] severity: major: Big minor: Small nit: Tiny ` if err := os.WriteFile(path, []byte(content), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } p, err := LoadPersona(path) if err != nil { t.Fatalf("LoadPersona failed: %v", err) } if p.Name != "test" { t.Errorf("Name = %q, want %q", p.Name, "test") } if p.DisplayName != "Test YML" { t.Errorf("DisplayName = %q, want %q", p.DisplayName, "Test YML") } } func TestLoadPersonaFromJSONFile(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test.json") content := `{ "name": "test", "display_name": "Test Persona", "identity": "You are a test persona.\nMulti-line identity works.", "focus": ["testing", "validation"], "ignore": ["nothing"], "severity": { "major": "Big problems", "minor": "Small problems", "nit": "Tiny problems" } }` if err := os.WriteFile(path, []byte(content), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } p, err := LoadPersona(path) if err != nil { t.Fatalf("LoadPersona failed: %v", err) } if p.Name != "test" { t.Errorf("Name = %q, want %q", p.Name, "test") } if p.DisplayName != "Test Persona" { t.Errorf("DisplayName = %q, want %q", p.DisplayName, "Test Persona") } if len(p.Focus) != 2 { t.Errorf("Focus len = %d, want 2", len(p.Focus)) } if !strings.Contains(p.Identity, "Multi-line") { t.Error("Identity should contain multi-line content") } } func TestLoadPersonaValidation(t *testing.T) { tests := []struct { name string content string ext string wantErr string }{ { name: "missing name yaml", content: "identity: test\n", ext: ".yaml", wantErr: "name is required", }, { name: "missing identity yaml", content: "name: test\n", ext: ".yaml", wantErr: "identity is required", }, { name: "missing name json", content: `{"identity": "test"}`, ext: ".json", wantErr: "name is required", }, { name: "missing identity json", content: `{"name": "test"}`, ext: ".json", wantErr: "identity is required", }, { name: "display_name defaults to name", content: "name: test\nidentity: test identity\n", ext: ".yaml", // No error expected - should succeed }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test"+tt.ext) if err := os.WriteFile(path, []byte(tt.content), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } p, err := LoadPersona(path) if tt.wantErr != "" { if err == nil { t.Errorf("expected error containing %q, got nil", tt.wantErr) return } if !strings.Contains(err.Error(), tt.wantErr) { t.Errorf("error = %q, want containing %q", err.Error(), tt.wantErr) } return } if err != nil { t.Fatalf("unexpected error: %v", err) } // Check display_name defaulting if p.DisplayName == "" { t.Error("DisplayName should default to Name") } if p.DisplayName != p.Name { t.Errorf("DisplayName should default to Name, got %q", p.DisplayName) } }) } } func TestLoadPersonaFileNotFound(t *testing.T) { _, err := LoadPersona("/nonexistent/path/persona.yaml") if err == nil { t.Error("expected error for nonexistent file") } } func TestLoadPersonaInvalidYAML(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "invalid.yaml") if err := os.WriteFile(path, []byte("not valid yaml:\n - [broken"), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } _, err := LoadPersona(path) if err == nil { t.Error("expected error for invalid YAML") } } func TestLoadPersonaInvalidJSON(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "invalid.json") if err := os.WriteFile(path, []byte("not valid json {"), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } _, err := LoadPersona(path) if err == nil { t.Error("expected error for invalid JSON") } } func TestLoadPersonaCaseInsensitiveExtension(t *testing.T) { tests := []struct { name string ext string }{ {"lowercase yaml", ".yaml"}, {"uppercase YAML", ".YAML"}, {"mixed case Yaml", ".Yaml"}, {"lowercase yml", ".yml"}, {"uppercase YML", ".YML"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test"+tt.ext) content := "name: test\nidentity: test identity\n" if err := os.WriteFile(path, []byte(content), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } p, err := LoadPersona(path) if err != nil { t.Fatalf("LoadPersona failed for extension %s: %v", tt.ext, err) } if p.Name != "test" { t.Errorf("Name = %q, want %q", p.Name, "test") } }) } } func TestCapitalizeFirst(t *testing.T) { tests := []struct { input string want string }{ {"hello", "Hello"}, {"Hello", "Hello"}, {"HELLO", "HELLO"}, {"a", "A"}, {"", ""}, {"日本語", "日本語"}, // Non-ASCII: Japanese doesn't have case {"über", "Über"}, // German umlaut {"élève", "Élève"}, // French accent } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { got := CapitalizeFirst(tt.input) if got != tt.want { t.Errorf("CapitalizeFirst(%q) = %q, want %q", tt.input, got, tt.want) } }) } } func TestListBuiltinPersonasReturnsEmptySlice(t *testing.T) { // ListBuiltinPersonas should return an empty slice (not nil) on error. // We can't easily test the error case, but we can verify the success case // returns a proper slice. names := ListBuiltinPersonas() if names == nil { t.Error("ListBuiltinPersonas should return empty slice, not nil") } } func TestYAMLMultilineStrings(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "multiline.yaml") // Test literal block scalar (|) which preserves newlines content := `name: multiline display_name: Multiline Test identity: | First line. Second line. Third line. focus: - item one ignore: [] severity: major: Major issue minor: Minor issue nit: Nit ` if err := os.WriteFile(path, []byte(content), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } p, err := LoadPersona(path) if err != nil { t.Fatalf("LoadPersona failed: %v", err) } // Literal block scalar preserves newlines if !strings.Contains(p.Identity, "\n") { t.Error("Identity should contain newlines from literal block scalar") } if !strings.Contains(p.Identity, "Second line") { t.Error("Identity should contain 'Second line'") } } func TestYAMLComments(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "comments.yaml") content := `# This is a comment name: commented # inline comment display_name: Commented Persona # Another comment identity: Test identity focus: - item # comment after item ignore: [] severity: major: Major minor: Minor nit: Nit ` if err := os.WriteFile(path, []byte(content), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } p, err := LoadPersona(path) if err != nil { t.Fatalf("LoadPersona failed: %v", err) } // Comments should be ignored if p.Name != "commented" { t.Errorf("Name = %q, want %q", p.Name, "commented") } if p.Focus[0] != "item" { t.Errorf("Focus[0] = %q, want %q", p.Focus[0], "item") } } func TestYAMLDeeplyNestedRejection(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "deeply-nested.yaml") // Build a deeply nested YAML structure that exceeds MaxYAMLDepth (20). // Depth accumulation trace for "nested: \n level0: \n level1: ...": // - Document root parsed at depth 0 // - Root MappingNode children (MappingValueNodes) visited at depth 1 // - "nested" MappingValueNode: key at depth 2, value at depth 2 // - Each levelN adds depth via MappingValueNode traversal (key + value) // - Exact depth per level depends on AST structure (MappingNode wrapping), // but 25 levels reliably exceeds MaxYAMLDepth (20) with comfortable margin. // The test uses 25 levels rather than exactly 21 to avoid brittleness. var sb strings.Builder sb.WriteString("name: test\nidentity: test\nnested:\n") indent := " " for i := 0; i < 25; i++ { sb.WriteString(strings.Repeat(indent, i+1)) sb.WriteString(fmt.Sprintf("level%d:\n", i)) } sb.WriteString(strings.Repeat(indent, 26)) sb.WriteString("value: too-deep\n") if err := os.WriteFile(path, []byte(sb.String()), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } _, err := LoadPersona(path) if err == nil { t.Error("expected error for deeply nested YAML, got nil") } if !strings.Contains(err.Error(), "nesting depth exceeds") { t.Errorf("error = %q, want containing 'nesting depth exceeds'", err.Error()) } } func TestYAMLEmptyFileRejection(t *testing.T) { tests := []struct { name string content string }{ {"completely_empty", ""}, {"whitespace_only", " \n\n "}, {"comment_only", "# just a comment\n"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, tc.name+".yaml") if err := os.WriteFile(path, []byte(tc.content), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } _, err := LoadPersona(path) if err == nil { t.Fatal("expected error for empty YAML input, got nil") } if !strings.Contains(err.Error(), "empty YAML document") { t.Errorf("expected error containing %q, got: %v", "empty YAML document", err) } }) } } func TestYAMLFileSizeLimit(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "huge.yaml") // Create a file larger than MaxPersonaFileSize (64 KB) content := "name: test\nidentity: " + strings.Repeat("x", MaxPersonaFileSize+1) + "\n" if err := os.WriteFile(path, []byte(content), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } _, err := LoadPersona(path) if err == nil { t.Error("expected error for oversized file, got nil") } if !strings.Contains(err.Error(), "exceeds maximum size") { t.Errorf("error = %q, want containing 'exceeds maximum size'", err.Error()) } } func TestYAMLAliasCycleDetection(t *testing.T) { // Test that our checkYAMLDepth function handles alias cycles gracefully // by using the visiting map to prevent infinite recursion. // Create a node structure where an alias points to a parent node, // simulating what could happen with crafted input. parent := &ast.MappingNode{ Values: []*ast.MappingValueNode{ { Key: &ast.StringNode{Value: "name"}, Value: &ast.StringNode{Value: "test"}, }, }, } // Create a child that aliases back to the parent (artificial cycle) aliasToParent := &ast.AliasNode{ Value: parent, } parent.Values = append(parent.Values, &ast.MappingValueNode{ Key: &ast.StringNode{Value: "nested"}, Value: aliasToParent, }) nodeCount := 0 validated := make(map[ast.Node]int) visiting := make(map[ast.Node]bool) // This should NOT hang or stack overflow - cycle detection prevents infinite recursion err := checkYAMLDepth(parent, 0, MaxYAMLDepth, MaxYAMLNodes, validated, visiting, &nodeCount) if err != nil { t.Errorf("unexpected error traversing cyclic structure: %v", err) } // Verify we tracked the parent in the validated map if _, ok := validated[parent]; !ok { t.Error("parent node not tracked in validated map") } } func TestYAMLMultiDocumentRejection(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "multi.yaml") // Multi-document YAML (documents separated by ---) content := `name: first identity: first document --- name: second identity: second document ` if err := os.WriteFile(path, []byte(content), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } _, err := LoadPersona(path) if err == nil { t.Error("expected error for multi-document YAML, got nil") } if !strings.Contains(err.Error(), "multi-document") { t.Errorf("error = %q, want containing 'multi-document'", err.Error()) } } func TestYAMLNodeCountLimit(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "wide.yaml") // Build a YAML structure that's shallow but wide - many keys at the same level // to test the node count limit (should exceed MaxYAMLNodes = 1000) var sb strings.Builder sb.WriteString("name: test\nidentity: test\n") for i := 0; i < 600; i++ { sb.WriteString(fmt.Sprintf("key%d: value%d\n", i, i)) } if err := os.WriteFile(path, []byte(sb.String()), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } _, err := LoadPersona(path) if err == nil { t.Error("expected error for wide YAML exceeding node count, got nil") } if !strings.Contains(err.Error(), "node count exceeds") { t.Errorf("error = %q, want containing 'node count exceeds'", err.Error()) } } func TestCheckYAMLDepthCycleDetectionDirect(t *testing.T) { // Direct test of cycle detection in checkYAMLDepth by creating // a node structure with an artificial cycle. node := &ast.MappingNode{ Values: []*ast.MappingValueNode{ { Key: &ast.StringNode{Value: "key"}, Value: &ast.StringNode{Value: "value"}, }, }, } // Create a cycle by making a child reference the parent cycleChild := &ast.AliasNode{ Value: node, // Points back to the parent } node.Values = append(node.Values, &ast.MappingValueNode{ Key: &ast.StringNode{Value: "cyclic"}, Value: cycleChild, }) nodeCount := 0 validated := make(map[ast.Node]int) visiting := make(map[ast.Node]bool) err := checkYAMLDepth(node, 0, MaxYAMLDepth, MaxYAMLNodes, validated, visiting, &nodeCount) // Should complete without infinite recursion due to cycle detection if err != nil { t.Errorf("unexpected error: %v", err) } // The validated map should contain multiple entries if len(validated) < 2 { t.Errorf("validated map has %d entries, expected at least 2", len(validated)) } } func TestYAMLAliasDepthBypass(t *testing.T) { // Test that an anchored subtree first validated at a shallow depth is // re-checked when referenced via alias at a deeper position. Without the // depth-aware validated map, the alias reference would skip re-checking // and allow the effective nesting to exceed MaxYAMLDepth. dir := t.TempDir() path := filepath.Join(dir, "alias-depth-bypass.yaml") // Build YAML with an anchor at shallow depth containing a subtree near the limit, // then reference it via alias deep enough that effective depth exceeds MaxYAMLDepth. var sb strings.Builder sb.WriteString("name: test\nidentity: test\n") // Create the anchored subtree at depth 1 (key level) that nests 15 levels deep. sb.WriteString("anchor_key: &deep_anchor\n") for i := 0; i < 15; i++ { sb.WriteString(strings.Repeat(" ", i+1)) sb.WriteString(fmt.Sprintf("level%d:\n", i)) } sb.WriteString(strings.Repeat(" ", 16)) sb.WriteString("leaf: value\n") // Create a wrapper that nests 6 levels deep, then references the anchor. // Effective depth at alias target = 6 (wrapper nesting) + 1 (alias) + 15 (subtree) = 22 > 20 sb.WriteString("wrapper:\n") for i := 0; i < 6; i++ { sb.WriteString(strings.Repeat(" ", i+1)) sb.WriteString(fmt.Sprintf("n%d:\n", i)) } sb.WriteString(strings.Repeat(" ", 7)) sb.WriteString("alias_ref: *deep_anchor\n") if err := os.WriteFile(path, []byte(sb.String()), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } _, err := LoadPersona(path) if err == nil { t.Fatal("expected error for alias depth bypass, got nil") } if !strings.Contains(err.Error(), "nesting depth exceeds") { t.Errorf("error = %q, want containing 'nesting depth exceeds'", err.Error()) } } func TestListBuiltinPersonasSortedOrder(t *testing.T) { names := ListBuiltinPersonas() if len(names) < 2 { t.Skip("need at least 2 personas to test ordering") } // Verify the list is sorted for i := 1; i < len(names); i++ { if names[i-1] > names[i] { t.Errorf("ListBuiltinPersonas not sorted: %q > %q", names[i-1], names[i]) } } } func TestYAMLUnknownFieldsRejected(t *testing.T) { tests := []struct { name string content string wantErr string }{ { name: "unknown top-level field", content: `name: test identity: test identity unknown_field: should fail `, wantErr: "unknown_field", }, { name: "typo in field name", content: `name: test identiy: typo should fail `, wantErr: "identiy", }, { name: "unknown field in severity", content: `name: test identity: test severity: major: Major minro: typo `, wantErr: "minro", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "unknown.yaml") if err := os.WriteFile(path, []byte(tt.content), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } _, err := LoadPersona(path) if err == nil { t.Errorf("expected error for unknown field %q, got nil", tt.wantErr) return } if !strings.Contains(err.Error(), tt.wantErr) { t.Errorf("error = %q, want containing %q", err.Error(), tt.wantErr) } }) } } func TestJSONUnknownFieldsRejected(t *testing.T) { tests := []struct { name string content string wantErr string }{ { name: "unknown top-level field", content: `{ "name": "test", "identity": "test identity", "unknown_field": "should fail" }`, wantErr: "unknown_field", }, { name: "typo in field name", content: `{ "name": "test", "identiy": "typo should fail" }`, wantErr: "identiy", }, { name: "unknown field in severity", content: `{ "name": "test", "identity": "test", "severity": { "major": "ok", "miner": "typo" } }`, wantErr: "miner", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test.json") if err := os.WriteFile(path, []byte(tt.content), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } _, err := LoadPersona(path) if err == nil { t.Fatal("expected error for unknown field, got nil") } if !strings.Contains(err.Error(), tt.wantErr) { t.Errorf("error = %q, want to contain %q", err.Error(), tt.wantErr) } }) } } func TestLoadPersonaSymlink(t *testing.T) { // Create a regular persona file dir := t.TempDir() realFile := filepath.Join(dir, "real.yaml") content := `name: test identity: test identity ` if err := os.WriteFile(realFile, []byte(content), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } // Create a symlink to it symlink := filepath.Join(dir, "link.yaml") if err := os.Symlink(realFile, symlink); err != nil { t.Fatalf("failed to create symlink: %v", err) } // LoadPersona should work via symlink p, err := LoadPersona(symlink) if err != nil { t.Fatalf("LoadPersona via symlink failed: %v", err) } if p.Name != "test" { t.Errorf("Name = %q, want %q", p.Name, "test") } } func TestJSONTrailingContentRejected(t *testing.T) { tests := []struct { name string content string }{ { name: "trailing garbage after object", content: `{"name":"test","identity":"test identity"}garbage`, }, { name: "two JSON objects", content: `{"name":"test","identity":"test identity"}{"name":"other"}`, }, { name: "trailing array", content: `{"name":"test","identity":"test identity"}[]`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test.json") if err := os.WriteFile(path, []byte(tt.content), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } _, err := LoadPersona(path) if err == nil { t.Fatal("expected error for trailing content, got nil") } if !strings.Contains(err.Error(), "trailing content") { t.Errorf("error = %q, want to contain 'trailing content'", err.Error()) } }) } } func TestParsePersonaBytesSizeLimit(t *testing.T) { // ParsePersonaBytes should reject input exceeding MaxPersonaFileSize oversized := make([]byte, MaxPersonaFileSize+1) for i := range oversized { oversized[i] = 'x' } _, err := ParsePersonaBytes(oversized, "oversized.yaml") if err == nil { t.Fatal("expected error for oversized input, got nil") } if !strings.Contains(err.Error(), "exceeds maximum size") { t.Errorf("error = %q, want to contain 'exceeds maximum size'", err.Error()) } // Just under the limit should not trigger size error (may fail parse, but not size) underLimit := []byte("name: test\nidentity: test persona\n") p, err := ParsePersonaBytes(underLimit, "valid.yaml") if err != nil { t.Fatalf("unexpected error for valid input: %v", err) } if p.Name != "test" { t.Errorf("Name = %q, want %q", p.Name, "test") } } func TestYAMLMergeKeyDepthCheck(t *testing.T) { // Verify that YAML merge keys (<<: *alias) are properly handled by the // depth checker. The merge key content is in the MappingValueNode.Value // (an AliasNode), not in the MergeKeyNode itself. p, err := ParsePersonaBytes([]byte("name: merge-test\nidentity: test\n"), "merge.yaml") if err != nil { t.Fatalf("basic parse failed: %v", err) } if p.Name != "merge-test" { t.Errorf("Name = %q, want %q", p.Name, "merge-test") } // Test that deeply nested merge keys still hit depth limit. // Build YAML with merge key content nested beyond MaxYAMLDepth. var sb strings.Builder sb.WriteString("name: deep-merge\nidentity: deep merge persona\n") sb.WriteString("anchor: &deep\n") indent := " " for i := 0; i < MaxYAMLDepth+5; i++ { sb.WriteString(indent) sb.WriteString(fmt.Sprintf("level%d:\n", i)) indent += " " } sb.WriteString(indent + "leaf: value\n") sb.WriteString("target:\n <<: *deep\n") _, err = ParsePersonaBytes([]byte(sb.String()), "deep-merge.yaml") if err == nil { t.Fatal("expected error for deeply nested merge key content, got nil") } if !strings.Contains(err.Error(), "depth") { t.Errorf("error = %q, want to contain 'depth'", err.Error()) } } func TestLoadPersona_NonexistentFile(t *testing.T) { _, err := LoadPersona("/tmp/nonexistent-persona-file-xyz.yaml") if err == nil { t.Fatal("expected error for nonexistent file, got nil") } } func TestLoadPersona_NotARegularFile(t *testing.T) { // Use a directory as the path — directories are not regular files. dir := t.TempDir() _, err := LoadPersona(dir) if err == nil { t.Fatal("expected error for directory path, got nil") } if !strings.Contains(err.Error(), "not a regular file") { t.Errorf("error = %q, want to contain 'not a regular file'", err.Error()) } } func TestLoadPersona_OversizedFile(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "big.yaml") // Write a file larger than MaxPersonaFileSize data := make([]byte, MaxPersonaFileSize+1) for i := range data { data[i] = 'x' } if err := os.WriteFile(path, data, 0644); err != nil { t.Fatalf("failed to create test file: %v", err) } _, err := LoadPersona(path) if err == nil { t.Fatal("expected error for oversized file, got nil") } if !strings.Contains(err.Error(), "exceeds maximum size") { t.Errorf("error = %q, want to contain 'exceeds maximum size'", err.Error()) } } func TestCapitalizeFirst_RuneError(t *testing.T) { // An invalid UTF-8 byte sequence should return the original string unchanged. invalid := string([]byte{0xFF, 0xFE}) got := CapitalizeFirst(invalid) if got != invalid { t.Errorf("CapitalizeFirst(%q) = %q, want original %q", invalid, got, invalid) } }