Files
claude-mnemonic/pkg/models/summary_test.go
T

268 lines
9.0 KiB
Go

// Package models contains domain models for claude-mnemonic.
package models
import (
"database/sql"
"encoding/json"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
// SummarySuite is a test suite for SessionSummary operations.
type SummarySuite struct {
suite.Suite
}
func TestSummarySuite(t *testing.T) {
suite.Run(t, new(SummarySuite))
}
// TestNewSessionSummary tests summary creation.
func (s *SummarySuite) TestNewSessionSummary() {
parsed := &ParsedSummary{
Request: "Fix the bug in handler.go",
Investigated: "Looked at error logs",
Learned: "The issue was a race condition",
Completed: "Fixed the race condition",
NextSteps: "Add more tests",
Notes: "Consider adding mutex",
}
summary := NewSessionSummary("sdk-123", "test-project", parsed, 5, 1000)
s.NotNil(summary)
s.Equal("sdk-123", summary.SDKSessionID)
s.Equal("test-project", summary.Project)
s.True(summary.Request.Valid)
s.Equal("Fix the bug in handler.go", summary.Request.String)
s.True(summary.Investigated.Valid)
s.True(summary.Learned.Valid)
s.True(summary.Completed.Valid)
s.True(summary.NextSteps.Valid)
s.True(summary.Notes.Valid)
s.True(summary.PromptNumber.Valid)
s.Equal(int64(5), summary.PromptNumber.Int64)
s.Equal(int64(1000), summary.DiscoveryTokens)
s.NotEmpty(summary.CreatedAt)
s.Greater(summary.CreatedAtEpoch, int64(0))
}
// TestNewSessionSummary_EmptyFields tests summary creation with empty fields.
func (s *SummarySuite) TestNewSessionSummary_EmptyFields() {
parsed := &ParsedSummary{
Request: "Test request",
// All other fields empty
}
summary := NewSessionSummary("sdk-123", "project", parsed, 0, 0)
s.True(summary.Request.Valid)
s.False(summary.Investigated.Valid)
s.False(summary.Learned.Valid)
s.False(summary.Completed.Valid)
s.False(summary.NextSteps.Valid)
s.False(summary.Notes.Valid)
s.False(summary.PromptNumber.Valid) // 0 is not valid
s.Equal(int64(0), summary.DiscoveryTokens)
}
// TestSessionSummary_MarshalJSON tests JSON marshaling.
func (s *SummarySuite) TestSessionSummary_MarshalJSON() {
summary := &SessionSummary{
ID: 1,
SDKSessionID: "sdk-123",
Project: "test-project",
Request: sql.NullString{String: "Test request", Valid: true},
Investigated: sql.NullString{String: "Test investigation", Valid: true},
Learned: sql.NullString{Valid: false}, // Invalid - should be omitted
Completed: sql.NullString{String: "Test completion", Valid: true},
NextSteps: sql.NullString{Valid: false},
Notes: sql.NullString{String: "Test notes", Valid: true},
PromptNumber: sql.NullInt64{Int64: 3, Valid: true},
DiscoveryTokens: 500,
CreatedAt: "2024-01-01T00:00:00Z",
CreatedAtEpoch: 1704067200000,
}
data, err := json.Marshal(summary)
s.NoError(err)
// Parse the JSON
var result map[string]interface{}
err = json.Unmarshal(data, &result)
s.NoError(err)
// Check fields
s.Equal(float64(1), result["id"])
s.Equal("sdk-123", result["sdk_session_id"])
s.Equal("test-project", result["project"])
s.Equal("Test request", result["request"])
s.Equal("Test investigation", result["investigated"])
s.Equal("Test completion", result["completed"])
s.Equal("Test notes", result["notes"])
s.Equal(float64(3), result["prompt_number"])
s.Equal(float64(500), result["discovery_tokens"])
// Empty fields should be omitted
_, hasLearned := result["learned"]
s.False(hasLearned, "Empty learned should be omitted")
_, hasNextSteps := result["next_steps"]
s.False(hasNextSteps, "Empty next_steps should be omitted")
}
// TestSessionSummary_MarshalJSON_AllEmpty tests JSON marshaling with all empty optional fields.
func (s *SummarySuite) TestSessionSummary_MarshalJSON_AllEmpty() {
summary := &SessionSummary{
ID: 1,
SDKSessionID: "sdk-123",
Project: "test-project",
Request: sql.NullString{Valid: false},
Investigated: sql.NullString{Valid: false},
Learned: sql.NullString{Valid: false},
Completed: sql.NullString{Valid: false},
NextSteps: sql.NullString{Valid: false},
Notes: sql.NullString{Valid: false},
PromptNumber: sql.NullInt64{Valid: false},
DiscoveryTokens: 0,
CreatedAt: "2024-01-01T00:00:00Z",
CreatedAtEpoch: 1704067200000,
}
data, err := json.Marshal(summary)
s.NoError(err)
var result map[string]interface{}
err = json.Unmarshal(data, &result)
s.NoError(err)
// Required fields should be present
s.Equal(float64(1), result["id"])
s.Equal("sdk-123", result["sdk_session_id"])
s.Equal("test-project", result["project"])
// Optional fields should be empty strings or omitted
request, hasRequest := result["request"]
if hasRequest {
s.Equal("", request)
}
}
// TestParsedSummary tests ParsedSummary structure.
func (s *SummarySuite) TestParsedSummary() {
parsed := &ParsedSummary{
Request: "Request text",
Investigated: "Investigation text",
Learned: "Learned text",
Completed: "Completed text",
NextSteps: "Next steps text",
Notes: "Notes text",
}
s.Equal("Request text", parsed.Request)
s.Equal("Investigation text", parsed.Investigated)
s.Equal("Learned text", parsed.Learned)
s.Equal("Completed text", parsed.Completed)
s.Equal("Next steps text", parsed.NextSteps)
s.Equal("Notes text", parsed.Notes)
}
// TestSessionSummaryJSON tests the JSON-friendly type.
func (s *SummarySuite) TestSessionSummaryJSON() {
j := SessionSummaryJSON{
ID: 1,
SDKSessionID: "sdk-123",
Project: "test-project",
Request: "Request",
Investigated: "Investigation",
Learned: "Learned",
Completed: "Completed",
NextSteps: "Next steps",
Notes: "Notes",
PromptNumber: 5,
DiscoveryTokens: 1000,
CreatedAt: "2024-01-01T00:00:00Z",
CreatedAtEpoch: 1704067200000,
}
s.Equal(int64(1), j.ID)
s.Equal("sdk-123", j.SDKSessionID)
s.Equal("test-project", j.Project)
s.Equal("Request", j.Request)
s.Equal("Investigation", j.Investigated)
s.Equal("Learned", j.Learned)
s.Equal("Completed", j.Completed)
s.Equal("Next steps", j.NextSteps)
s.Equal("Notes", j.Notes)
s.Equal(int64(5), j.PromptNumber)
s.Equal(int64(1000), j.DiscoveryTokens)
}
// TestSessionSummary_TimestampValidity tests that timestamps are set correctly.
func TestSessionSummary_TimestampValidity(t *testing.T) {
before := time.Now().Add(-time.Second) // Give 1 second buffer
parsed := &ParsedSummary{Request: "Test"}
summary := NewSessionSummary("sdk-123", "project", parsed, 1, 100)
after := time.Now().Add(time.Second) // Give 1 second buffer
// Parse the timestamp
createdAt, err := time.Parse(time.RFC3339, summary.CreatedAt)
require.NoError(t, err)
// Timestamp should be between before and after (with buffer)
assert.True(t, createdAt.After(before) || createdAt.Equal(before), "created_at should be >= before")
assert.True(t, createdAt.Before(after) || createdAt.Equal(after), "created_at should be <= after")
// Epoch should also be in range (with buffer)
beforeEpoch := before.UnixMilli()
afterEpoch := after.UnixMilli()
assert.GreaterOrEqual(t, summary.CreatedAtEpoch, beforeEpoch, "epoch should be >= before epoch")
assert.LessOrEqual(t, summary.CreatedAtEpoch, afterEpoch, "epoch should be <= after epoch")
}
// TestSessionSummary_JSONRoundTrip tests that summaries can be marshaled and unmarshaled.
func TestSessionSummary_JSONRoundTrip(t *testing.T) {
original := &SessionSummary{
ID: 1,
SDKSessionID: "sdk-123",
Project: "test-project",
Request: sql.NullString{String: "Test request", Valid: true},
Investigated: sql.NullString{String: "Test investigation", Valid: true},
Learned: sql.NullString{String: "Test learned", Valid: true},
Completed: sql.NullString{String: "Test completed", Valid: true},
NextSteps: sql.NullString{String: "Test next steps", Valid: true},
Notes: sql.NullString{String: "Test notes", Valid: true},
PromptNumber: sql.NullInt64{Int64: 5, Valid: true},
DiscoveryTokens: 1000,
CreatedAt: "2024-01-01T00:00:00Z",
CreatedAtEpoch: 1704067200000,
}
// Marshal
data, err := json.Marshal(original)
require.NoError(t, err)
// Unmarshal into JSON type
var result SessionSummaryJSON
err = json.Unmarshal(data, &result)
require.NoError(t, err)
// Verify
assert.Equal(t, original.ID, result.ID)
assert.Equal(t, original.SDKSessionID, result.SDKSessionID)
assert.Equal(t, original.Project, result.Project)
assert.Equal(t, original.Request.String, result.Request)
assert.Equal(t, original.Investigated.String, result.Investigated)
assert.Equal(t, original.Learned.String, result.Learned)
assert.Equal(t, original.Completed.String, result.Completed)
assert.Equal(t, original.NextSteps.String, result.NextSteps)
assert.Equal(t, original.Notes.String, result.Notes)
assert.Equal(t, original.PromptNumber.Int64, result.PromptNumber)
assert.Equal(t, original.DiscoveryTokens, result.DiscoveryTokens)
}