package indexing import ( "encoding/json" "os" "testing" "time" "github.com/stretchr/testify/assert" ) func createTestLessonsFile(t *testing.T, count int) string { file, err := os.CreateTemp("", "lessons-*.jsonl") assert.NoError(t, err) defer file.Close() for i := 0; i < count; i++ { lesson := Lesson{ ID: "lesson-" + string(rune(48+i%10)) + "-" + string(rune(48+i/10)), TaskType: []string{"add_feature", "fix_bug", "refactor"}[i%3], ActivityType: []string{"implementer", "judge", "planner"}[i%3], FailureType: []string{"syntax_error", "logic_error", "timeout"}[i%3], FailureMsg: "Error message " + string(rune(48+i%100)), Resolution: "Fix strategy", Pattern: "pattern-" + string(rune(48+i%5)), TimesSeen: i % 10, LastSeen: time.Now().Add(-time.Duration(i) * time.Hour), FirstSeen: time.Now().Add(-time.Duration(i*24) * time.Hour), Metadata: map[string]interface{}{ "index": i, }, } data, _ := json.Marshal(lesson) file.WriteString(string(data) + "\n") } return file.Name() } func TestNewLessonIndex(t *testing.T) { index := NewLessonIndex() assert.NotNil(t, index) assert.Equal(t, 0, index.Count()) } func TestBuildFromFile(t *testing.T) { file := createTestLessonsFile(t, 50) defer os.Remove(file) index := NewLessonIndex() err := index.BuildFromFile(file) assert.NoError(t, err) assert.Greater(t, index.Count(), 0) } func TestAddLesson(t *testing.T) { index := NewLessonIndex() lesson := &Lesson{ ID: "test-1", TaskType: "add_feature", ActivityType: "implementer", FailureType: "syntax_error", FailureMsg: "Missing semicolon", Resolution: "Add semicolon", Pattern: "syntax-missing-semi", TimesSeen: 1, LastSeen: time.Now(), FirstSeen: time.Now(), } index.AddLesson(lesson) assert.Equal(t, 1, index.Count()) retrieved, exists := index.GetLesson("test-1") assert.True(t, exists) assert.Equal(t, "test-1", retrieved.ID) } func TestFindByTaskType(t *testing.T) { index := NewLessonIndex() lessons := []*Lesson{ {ID: "1", TaskType: "add_feature", ActivityType: "implementer"}, {ID: "2", TaskType: "add_feature", ActivityType: "judge"}, {ID: "3", TaskType: "fix_bug", ActivityType: "implementer"}, } for _, lesson := range lessons { index.AddLesson(lesson) } results := index.FindByTaskType("add_feature") assert.Equal(t, 2, len(results)) } func TestFindByActivityType(t *testing.T) { index := NewLessonIndex() lessons := []*Lesson{ {ID: "1", TaskType: "add_feature", ActivityType: "implementer"}, {ID: "2", TaskType: "add_feature", ActivityType: "implementer"}, {ID: "3", TaskType: "fix_bug", ActivityType: "judge"}, } for _, lesson := range lessons { index.AddLesson(lesson) } results := index.FindByActivityType("implementer") assert.Equal(t, 2, len(results)) } func TestFindByFailureType(t *testing.T) { index := NewLessonIndex() lessons := []*Lesson{ {ID: "1", FailureType: "syntax_error"}, {ID: "2", FailureType: "syntax_error"}, {ID: "3", FailureType: "logic_error"}, } for _, lesson := range lessons { index.AddLesson(lesson) } results := index.FindByFailureType("syntax_error") assert.Equal(t, 2, len(results)) } func TestFindByPattern(t *testing.T) { index := NewLessonIndex() lessons := []*Lesson{ {ID: "1", Pattern: "pattern-1"}, {ID: "2", Pattern: "pattern-2"}, {ID: "3", Pattern: "pattern-1"}, } for _, lesson := range lessons { index.AddLesson(lesson) } results := index.FindByPattern("pattern-1") assert.Equal(t, 2, len(results)) } func TestFindSimilar(t *testing.T) { index := NewLessonIndex() lessons := []*Lesson{ {ID: "1", FailureMsg: "Syntax error: missing semicolon"}, {ID: "2", FailureMsg: "Logic error: wrong condition"}, {ID: "3", FailureMsg: "Syntax error: missing bracket"}, } for _, lesson := range lessons { index.AddLesson(lesson) } results := index.FindSimilar("syntax") assert.Equal(t, 2, len(results)) } func TestQueryMultiple(t *testing.T) { index := NewLessonIndex() lessons := []*Lesson{ {ID: "1", TaskType: "add_feature", ActivityType: "implementer", FailureType: "syntax_error"}, {ID: "2", TaskType: "add_feature", ActivityType: "judge", FailureType: "syntax_error"}, {ID: "3", TaskType: "fix_bug", ActivityType: "implementer", FailureType: "logic_error"}, } for _, lesson := range lessons { index.AddLesson(lesson) } results := index.QueryMultiple("add_feature", "implementer", "syntax_error") assert.Equal(t, 1, len(results)) assert.Equal(t, "1", results[0].ID) } func TestGetByTimeRange(t *testing.T) { index := NewLessonIndex() now := time.Now() lessons := []*Lesson{ {ID: "1", LastSeen: now.Add(-2 * time.Hour)}, {ID: "2", LastSeen: now.Add(-1 * time.Hour)}, {ID: "3", LastSeen: now.Add(-24 * time.Hour)}, } for _, lesson := range lessons { index.AddLesson(lesson) } // Range before any lessons should find 0 results := index.GetByTimeRange(now.Add(-48*time.Hour), now.Add(-25*time.Hour)) assert.Equal(t, 0, len(results)) // Range that includes all lessons results = index.GetByTimeRange(now.Add(-25*time.Hour), now) assert.Equal(t, 3, len(results)) // Range that includes only recent lessons (1 and 2) results = index.GetByTimeRange(now.Add(-3*time.Hour), now) assert.Equal(t, 2, len(results)) } func TestGetStats(t *testing.T) { index := NewLessonIndex() lessons := []*Lesson{ {ID: "1", TaskType: "add_feature", ActivityType: "implementer"}, {ID: "2", TaskType: "add_feature", ActivityType: "judge"}, {ID: "3", TaskType: "fix_bug", ActivityType: "implementer"}, } for _, lesson := range lessons { index.AddLesson(lesson) } stats := index.GetStats() assert.Equal(t, 3, stats["total_lessons"]) assert.Equal(t, 2, stats["unique_task_types"]) assert.Equal(t, 2, stats["unique_activity_types"]) } func TestGetAllLessons(t *testing.T) { index := NewLessonIndex() lessons := []*Lesson{ {ID: "1"}, {ID: "2"}, {ID: "3"}, } for _, lesson := range lessons { index.AddLesson(lesson) } all := index.GetAllLessons() assert.Equal(t, 3, len(all)) } func TestClear(t *testing.T) { index := NewLessonIndex() index.AddLesson(&Lesson{ID: "1"}) index.AddLesson(&Lesson{ID: "2"}) assert.Equal(t, 2, index.Count()) index.Clear() assert.Equal(t, 0, index.Count()) } func TestGetMostFrequentFailures(t *testing.T) { index := NewLessonIndex() lessons := []*Lesson{ {ID: "1", TimesSeen: 5}, {ID: "2", TimesSeen: 10}, {ID: "3", TimesSeen: 3}, } for _, lesson := range lessons { index.AddLesson(lesson) } top := index.GetMostFrequentFailures(2) assert.Equal(t, 2, len(top)) assert.Equal(t, 10, top[0].TimesSeen) assert.Equal(t, 5, top[1].TimesSeen) } func TestLookupLatency(t *testing.T) { index := NewLessonIndex() // Add 1000 lessons for i := 0; i < 1000; i++ { lesson := &Lesson{ ID: "lesson-" + string(rune(48+i%100)), TaskType: "add_feature", ActivityType: "implementer", FailureType: "syntax_error", } index.AddLesson(lesson) } // Measure lookup time start := time.Now() results := index.FindByTaskType("add_feature") elapsed := time.Since(start) assert.Greater(t, len(results), 0) // Should be < 10ms assert.Less(t, elapsed, 10*time.Millisecond) } func TestLookupLatencyLarge(t *testing.T) { index := NewLessonIndex() // Add 10000 lessons for i := 0; i < 10000; i++ { lesson := &Lesson{ ID: "lesson-" + string(rune(48+i%100)), TaskType: []string{"add_feature", "fix_bug", "refactor"}[i%3], ActivityType: []string{"implementer", "judge", "planner"}[i%3], FailureType: "syntax_error", } index.AddLesson(lesson) } // Measure lookup time start := time.Now() results := index.FindByActivityType("implementer") elapsed := time.Since(start) assert.Greater(t, len(results), 0) // Should be < 10ms even with 10k entries assert.Less(t, elapsed, 10*time.Millisecond) } func TestConcurrentQueries(t *testing.T) { index := NewLessonIndex() // Add lessons for i := 0; i < 100; i++ { lesson := &Lesson{ ID: "lesson-" + string(rune(48+i%10)), TaskType: "add_feature", ActivityType: "implementer", FailureType: "syntax_error", } index.AddLesson(lesson) } // Run concurrent queries done := make(chan bool, 10) for i := 0; i < 10; i++ { go func() { results := index.FindByTaskType("add_feature") assert.Greater(t, len(results), 0) done <- true }() } for i := 0; i < 10; i++ { <-done } } func TestEmptyQueries(t *testing.T) { index := NewLessonIndex() results := index.FindByTaskType("nonexistent") assert.Equal(t, 0, len(results)) results = index.FindByActivityType("nonexistent") assert.Equal(t, 0, len(results)) results = index.FindByFailureType("nonexistent") assert.Equal(t, 0, len(results)) } func TestGetLesson(t *testing.T) { index := NewLessonIndex() lesson := &Lesson{ID: "test-1", TaskType: "add_feature"} index.AddLesson(lesson) retrieved, exists := index.GetLesson("test-1") assert.True(t, exists) assert.Equal(t, "test-1", retrieved.ID) _, exists = index.GetLesson("nonexistent") assert.False(t, exists) } func TestMultipleIndexes(t *testing.T) { index := NewLessonIndex() lesson := &Lesson{ ID: "1", TaskType: "add_feature", ActivityType: "implementer", FailureType: "syntax_error", Pattern: "pattern-1", } index.AddLesson(lesson) // Should be findable by all indexes assert.Equal(t, 1, len(index.FindByTaskType("add_feature"))) assert.Equal(t, 1, len(index.FindByActivityType("implementer"))) assert.Equal(t, 1, len(index.FindByFailureType("syntax_error"))) assert.Equal(t, 1, len(index.FindByPattern("pattern-1"))) } func TestRebuild(t *testing.T) { file := createTestLessonsFile(t, 50) defer os.Remove(file) index := NewLessonIndex() _ = index.BuildFromFile(file) count1 := index.Count() _ = index.Rebuild() count2 := index.Count() assert.Equal(t, count1, count2) } func BenchmarkAddLesson(b *testing.B) { index := NewLessonIndex() for i := 0; i < b.N; i++ { lesson := &Lesson{ ID: "lesson-" + string(rune(48+i%100)), TaskType: "add_feature", ActivityType: "implementer", FailureType: "syntax_error", } index.AddLesson(lesson) } } func BenchmarkFindByTaskType(b *testing.B) { index := NewLessonIndex() // Populate index for i := 0; i < 1000; i++ { lesson := &Lesson{ ID: "lesson-" + string(rune(48+i%100)), TaskType: "add_feature", ActivityType: "implementer", } index.AddLesson(lesson) } b.ResetTimer() for i := 0; i < b.N; i++ { index.FindByTaskType("add_feature") } } func BenchmarkFindByActivityType(b *testing.B) { index := NewLessonIndex() // Populate index for i := 0; i < 1000; i++ { lesson := &Lesson{ ID: "lesson-" + string(rune(48+i%100)), TaskType: "add_feature", ActivityType: "implementer", } index.AddLesson(lesson) } b.ResetTimer() for i := 0; i < b.N; i++ { index.FindByActivityType("implementer") } }