277 lines
6.6 KiB
Go
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)
|
|
}
|
|
}
|