Files
apex-public/internal/intake/intake_test.go
T
2026-07-06 11:05:50 -04:00

277 lines
6.6 KiB
Go

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