package intake import ( "database/sql" "testing" "time" _ "modernc.org/sqlite" ) // setupTestDB creates an in-memory SQLite DB with intake schema // Each call gets a fresh isolated database func setupTestDB(t *testing.T) *sql.DB { db, err := sql.Open("sqlite", "file::memory:") if err != nil { t.Fatalf("open in-memory db: %v", err) } // Create tables schema := ` CREATE TABLE IF NOT EXISTS intake_sessions ( id INTEGER PRIMARY KEY AUTOINCREMENT, current_phase INTEGER DEFAULT 0, is_complete BOOLEAN DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS intake_answers ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id INTEGER NOT NULL, phase INTEGER NOT NULL, fields_json TEXT, nested_items_json TEXT, submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (session_id) REFERENCES intake_sessions(id), UNIQUE(session_id, phase) ); CREATE INDEX IF NOT EXISTS idx_intake_answers_session_id ON intake_answers(session_id); ` if _, err := db.Exec(schema); err != nil { t.Fatalf("create schema: %v", err) } return db } func TestNewSessionCreatesFirstSession(t *testing.T) { db := setupTestDB(t) defer db.Close() sess := NewSession(db) session, err := sess.LoadSession() if err != nil { t.Fatalf("LoadSession: %v", err) } if session.CurrentPhase != PhaseIdentity { t.Errorf("expected current phase PhaseIdentity (0), got %d", session.CurrentPhase) } if session.IsComplete { t.Errorf("expected IsComplete=false, got true") } if session.ID == 0 { t.Errorf("expected non-zero ID, got %d", session.ID) } } func TestSaveAnswerPersistsAndLoads(t *testing.T) { db := setupTestDB(t) defer db.Close() sess := NewSession(db) session, err := sess.LoadSession() if err != nil { t.Fatalf("LoadSession: %v", err) } // Save an answer for Phase 0 answer := FormAnswer{ Phase: PhaseIdentity, Fields: map[string]string{ "name": "John Doe", "email": "john@example.com", "location": "San Francisco", "timezone": "PST", }, SubmittedAt: time.Now(), } if err := sess.SaveAnswer(session, answer); err != nil { t.Fatalf("SaveAnswer: %v", err) } // Load session again session2, err := sess.LoadSession() if err != nil { t.Fatalf("LoadSession (2nd): %v", err) } if len(session2.Answers) != 1 { t.Errorf("expected 1 answer, got %d", len(session2.Answers)) } loaded := session2.Answers[0] if loaded.Phase != PhaseIdentity { t.Errorf("expected phase PhaseIdentity, got %d", loaded.Phase) } if loaded.Fields["name"] != "John Doe" { t.Errorf("expected name 'John Doe', got %q", loaded.Fields["name"]) } if loaded.Fields["email"] != "john@example.com" { t.Errorf("expected email 'john@example.com', got %q", loaded.Fields["email"]) } } func TestNextPhaseAdvancesCorrectly(t *testing.T) { db := setupTestDB(t) defer db.Close() sess := NewSession(db) session, _ := sess.LoadSession() if session.CurrentPhase != PhaseIdentity { t.Fatalf("expected current phase 0, got %d", session.CurrentPhase) } // Advance to Phase 1 next := sess.NextPhase(session) if next != PhaseEducation { t.Errorf("expected PhaseEducation (1), got %d", next) } // Set current phase to Voice (7) session.CurrentPhase = PhaseVoice next = sess.NextPhase(session) if next != PhaseComplete { t.Errorf("expected PhaseComplete (8), got %d", next) } } func TestCanAdvanceIdentityPhase(t *testing.T) { db := setupTestDB(t) defer db.Close() sess := NewSession(db) session, _ := sess.LoadSession() // Initially cannot advance if sess.CanAdvance(session) { t.Errorf("expected CanAdvance=false without answers, got true") } // Add incomplete answer answer := FormAnswer{ Phase: PhaseIdentity, Fields: map[string]string{ "name": "John Doe", // Missing email, location, timezone }, SubmittedAt: time.Now(), } if err := sess.SaveAnswer(session, answer); err != nil { t.Fatalf("SaveAnswer failed: %v", err) } // Reload and check session, _ = sess.LoadSession() if sess.CanAdvance(session) { t.Errorf("expected CanAdvance=false with incomplete identity, got true") } // For testing complete identity, create a new session with complete answer // (can't update same phase due to UNIQUE constraint) db2 := setupTestDB(t) defer db2.Close() sess2 := NewSession(db2) session2, _ := sess2.LoadSession() completedAnswer := FormAnswer{ Phase: PhaseIdentity, Fields: map[string]string{ "name": "John Doe", "email": "john@example.com", "location": "SF", "timezone": "PST", }, SubmittedAt: time.Now(), } err := sess2.SaveAnswer(session2, completedAnswer) if err != nil { t.Fatalf("SaveAnswer failed: %v", err) } session2, err = sess2.LoadSession() if err != nil { t.Fatalf("LoadSession failed: %v", err) } if !sess2.CanAdvance(session2) { t.Errorf("expected CanAdvance=true with complete identity, got false") } } func TestCanAdvanceMultiItemPhases(t *testing.T) { // Test Education phase without items db1 := setupTestDB(t) defer db1.Close() sess1 := NewSession(db1) session1, _ := sess1.LoadSession() session1.CurrentPhase = PhaseEducation // Answer without nested items answer1 := FormAnswer{ Phase: PhaseEducation, Fields: map[string]string{"dummy": "value"}, NestedItems: []map[string]string{}, SubmittedAt: time.Now(), } sess1.SaveAnswer(session1, answer1) session1, _ = sess1.LoadSession() if sess1.CanAdvance(session1) { t.Errorf("expected CanAdvance=false for Education with no items, got true") } // Test Education phase with items (new session) db2 := setupTestDB(t) defer db2.Close() sess2 := NewSession(db2) session2, _ := sess2.LoadSession() session2.CurrentPhase = PhaseEducation answer2 := FormAnswer{ Phase: PhaseEducation, Fields: map[string]string{"dummy": "value"}, NestedItems: []map[string]string{ {"institution": "MIT", "degree": "BS"}, }, SubmittedAt: time.Now(), } sess2.SaveAnswer(session2, answer2) session2, _ = sess2.LoadSession() if !sess2.CanAdvance(session2) { t.Errorf("expected CanAdvance=true for Education with items, got false") } } func TestMarkComplete(t *testing.T) { db := setupTestDB(t) defer db.Close() sess := NewSession(db) session, _ := sess.LoadSession() if session.IsComplete { t.Fatalf("expected IsComplete=false initially") } if err := sess.MarkComplete(session); err != nil { t.Fatalf("MarkComplete: %v", err) } session, _ = sess.LoadSession() if !session.IsComplete { t.Errorf("expected IsComplete=true after MarkComplete, got false") } if session.CurrentPhase != PhaseComplete { t.Errorf("expected CurrentPhase=PhaseComplete, got %d", session.CurrentPhase) } }