package sdk import ( "testing" "github.com/lukaszraczylo/claude-mnemonic/pkg/models" "github.com/stretchr/testify/assert" ) func TestParseObservations_SingleObservation(t *testing.T) { text := `Some text before bugfix Fixed null pointer error In user service The service was crashing when user ID was nil Added nil check Added unit test error-handling debugging user_service.go user_service.go user_service_test.go Some text after` observations := ParseObservations(text, "test-correlation-id") assert.Len(t, observations, 1) obs := observations[0] assert.Equal(t, models.ObservationType("bugfix"), obs.Type) assert.Equal(t, "Fixed null pointer error", obs.Title) assert.Equal(t, "In user service", obs.Subtitle) assert.Equal(t, "The service was crashing when user ID was nil", obs.Narrative) assert.Equal(t, []string{"Added nil check", "Added unit test"}, obs.Facts) assert.Equal(t, []string{"error-handling", "debugging"}, obs.Concepts) assert.Equal(t, []string{"user_service.go"}, obs.FilesRead) assert.Equal(t, []string{"user_service.go", "user_service_test.go"}, obs.FilesModified) } func TestParseObservations_MultipleObservations(t *testing.T) { text := ` feature Added caching Implemented Redis caching Added cache layer caching refactor Cleaned up code Removed dead code Removed unused functions refactoring ` observations := ParseObservations(text, "test-id") assert.Len(t, observations, 2) assert.Equal(t, models.ObservationType("feature"), observations[0].Type) assert.Equal(t, "Added caching", observations[0].Title) assert.Equal(t, models.ObservationType("refactor"), observations[1].Type) assert.Equal(t, "Cleaned up code", observations[1].Title) } func TestParseObservations_TableDriven(t *testing.T) { tests := []struct { name string input string expectedType models.ObservationType expectedTitle string checkConcepts []string expectedCount int }{ { name: "valid_bugfix_observation", input: ` bugfix Fixed bug Details `, expectedCount: 1, expectedType: models.ObsTypeBugfix, expectedTitle: "Fixed bug", }, { name: "valid_feature_observation", input: ` feature New feature Added new stuff `, expectedCount: 1, expectedType: models.ObsTypeFeature, expectedTitle: "New feature", }, { name: "valid_refactor_observation", input: ` refactor Code cleanup Refactored module `, expectedCount: 1, expectedType: models.ObsTypeRefactor, expectedTitle: "Code cleanup", }, { name: "valid_change_observation", input: ` change Config update Changed settings `, expectedCount: 1, expectedType: models.ObsTypeChange, expectedTitle: "Config update", }, { name: "valid_discovery_observation", input: ` discovery Found pattern Discovered new pattern `, expectedCount: 1, expectedType: models.ObsTypeDiscovery, expectedTitle: "Found pattern", }, { name: "valid_decision_observation", input: ` decision Architecture decision Chose microservices `, expectedCount: 1, expectedType: models.ObsTypeDecision, expectedTitle: "Architecture decision", }, { name: "invalid_type_defaults_to_change", input: ` invalid_type Some title Details `, expectedCount: 1, expectedType: models.ObsTypeChange, expectedTitle: "Some title", }, { name: "missing_type_defaults_to_change", input: ` No type specified Details `, expectedCount: 1, expectedType: models.ObsTypeChange, expectedTitle: "No type specified", }, { name: "empty_input", input: "", expectedCount: 0, }, { name: "no_observation_tags", input: "Just regular text without any observation", expectedCount: 0, }, { name: "valid_concepts_filtered", input: ` bugfix Test Test best-practice invalid-concept security `, expectedCount: 1, expectedType: models.ObsTypeBugfix, checkConcepts: []string{"best-practice", "security"}, }, { name: "type_in_concepts_filtered_out", input: ` bugfix Test Test bugfix security `, expectedCount: 1, expectedType: models.ObsTypeBugfix, checkConcepts: []string{"security"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { observations := ParseObservations(tt.input, "test-correlation-id") assert.Len(t, observations, tt.expectedCount) if tt.expectedCount > 0 { obs := observations[0] assert.Equal(t, tt.expectedType, obs.Type) if tt.expectedTitle != "" { assert.Equal(t, tt.expectedTitle, obs.Title) } if tt.checkConcepts != nil { assert.Equal(t, tt.checkConcepts, obs.Concepts) } } }) } } func TestParseObservations_AllValidConcepts(t *testing.T) { // Test all valid concepts are accepted validConcepts := []string{ "how-it-works", "why-it-exists", "what-changed", "problem-solution", "gotcha", "pattern", "trade-off", "best-practice", "anti-pattern", "architecture", "security", "performance", "testing", "debugging", "workflow", "tooling", "refactoring", "api", "database", "configuration", "error-handling", "caching", "logging", "auth", "validation", } for _, concept := range validConcepts { t.Run("concept_"+concept, func(t *testing.T) { input := ` discovery Test Test ` + concept + ` ` observations := ParseObservations(input, "test-id") assert.Len(t, observations, 1) assert.Contains(t, observations[0].Concepts, concept) }) } } func TestParseObservations_ConceptCaseInsensitive(t *testing.T) { input := ` discovery Test Test SECURITY Best-Practice caching ` observations := ParseObservations(input, "test-id") assert.Len(t, observations, 1) assert.Equal(t, []string{"security", "best-practice", "caching"}, observations[0].Concepts) } func TestParseSummary_ValidSummary(t *testing.T) { text := `Some text before User asked to fix the bug Looked at error logs and stack traces The issue was a race condition Fixed the race condition with mutex Add more tests for concurrent access May need to review similar code elsewhere Some text after` summary := ParseSummary(text, 123) assert.NotNil(t, summary) assert.Equal(t, "User asked to fix the bug", summary.Request) assert.Equal(t, "Looked at error logs and stack traces", summary.Investigated) assert.Equal(t, "The issue was a race condition", summary.Learned) assert.Equal(t, "Fixed the race condition with mutex", summary.Completed) assert.Equal(t, "Add more tests for concurrent access", summary.NextSteps) assert.Equal(t, "May need to review similar code elsewhere", summary.Notes) } func TestParseSummary_TableDriven(t *testing.T) { tests := []struct { name string input string expectedRequest string sessionID int64 expectNil bool }{ { name: "empty_input", input: "", sessionID: 1, expectNil: true, }, { name: "no_summary_tag", input: "Just some text without summary", sessionID: 1, expectNil: true, }, { name: "skip_summary_tag", input: ``, sessionID: 1, expectNil: true, }, { name: "skip_summary_with_different_reason", input: ``, sessionID: 2, expectNil: true, }, { name: "valid_summary_minimal", input: ` Test request `, sessionID: 3, expectNil: false, expectedRequest: "Test request", }, { name: "valid_summary_all_fields", input: ` Full request Full investigated Full learned Full completed Full next steps Full notes `, sessionID: 4, expectNil: false, expectedRequest: "Full request", }, { name: "summary_with_empty_fields", input: ` `, sessionID: 5, expectNil: false, expectedRequest: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { summary := ParseSummary(tt.input, tt.sessionID) if tt.expectNil { assert.Nil(t, summary) } else { assert.NotNil(t, summary) assert.Equal(t, tt.expectedRequest, summary.Request) } }) } } func TestParseSummary_SkipSummaryPriority(t *testing.T) { // skip_summary should take priority over summary block text := ` This should be ignored ` summary := ParseSummary(text, 1) assert.Nil(t, summary) } func TestExtractField_TableDriven(t *testing.T) { tests := []struct { name string content string fieldName string expected string }{ { name: "simple_field", content: "Test Title", fieldName: "title", expected: "Test Title", }, { name: "field_with_whitespace", content: " Test Title ", fieldName: "title", expected: "Test Title", }, { name: "field_not_found", content: "Value", fieldName: "title", expected: "", }, { name: "empty_field", content: "", fieldName: "title", expected: "", }, { name: "nested_content", content: "Nested", fieldName: "title", expected: "Nested", }, { name: "field_among_others", content: "ATargetB", fieldName: "title", expected: "Target", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := extractField(tt.content, tt.fieldName) assert.Equal(t, tt.expected, result) }) } } func TestExtractArrayElements_TableDriven(t *testing.T) { tests := []struct { name string content string arrayName string elementName string expected []string }{ { name: "simple_array", content: "OneTwo", arrayName: "facts", elementName: "fact", expected: []string{"One", "Two"}, }, { name: "empty_array", content: "", arrayName: "facts", elementName: "fact", expected: nil, }, { name: "array_not_found", content: "Value", arrayName: "facts", elementName: "fact", expected: nil, }, { name: "single_element", content: "security", arrayName: "concepts", elementName: "concept", expected: []string{"security"}, }, { name: "multiline_array", content: ` file1.go file2.go file3.go `, arrayName: "files", elementName: "file", expected: []string{"file1.go", "file2.go", "file3.go"}, }, { name: "whitespace_trimmed", content: " trimmed ", arrayName: "items", elementName: "item", expected: []string{"trimmed"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := extractArrayElements(tt.content, tt.arrayName, tt.elementName) assert.Equal(t, tt.expected, result) }) } } func TestValidObsTypes(t *testing.T) { expected := map[string]bool{ "bugfix": true, "feature": true, "refactor": true, "change": true, "discovery": true, "decision": true, } assert.Equal(t, expected, validObsTypes) } func TestValidConcepts(t *testing.T) { // Verify expected concepts are valid expectedValid := []string{ "how-it-works", "why-it-exists", "what-changed", "problem-solution", "gotcha", "pattern", "trade-off", "best-practice", "anti-pattern", "architecture", "security", "performance", "testing", "debugging", "workflow", "tooling", "refactoring", "api", "database", "configuration", "error-handling", "caching", "logging", "auth", "validation", } for _, concept := range expectedValid { assert.True(t, validConcepts[concept], "Expected %s to be valid", concept) } // Verify invalid concepts invalidConcepts := []string{"random", "invalid", "not-a-concept", "foo", "bar"} for _, concept := range invalidConcepts { assert.False(t, validConcepts[concept], "Expected %s to be invalid", concept) } }