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)
}
}