package progression import ( "testing" "tutor/internal/learnermemory" "tutor/internal/workflows" ) func TestReadinessMapUsesEvidenceBackedMemory(t *testing.T) { service := seededService(t, workflows.ReadinessImproving) readiness, err := service.ReadinessMap("user-1") if err != nil { t.Fatalf("ReadinessMap error: %v", err) } if readiness.ReadinessPercentage != 50 { t.Fatalf("readiness = %d, want 50", readiness.ReadinessPercentage) } if len(readiness.Concepts) != 1 { t.Fatalf("concepts = %d, want 1", len(readiness.Concepts)) } if readiness.Concepts[0].LadderLevel != workflows.LadderTradeoffs { t.Fatalf("ladder = %q", readiness.Concepts[0].LadderLevel) } if len(readiness.Rewards) != 1 { t.Fatalf("rewards = %d, want 1", len(readiness.Rewards)) } } func TestNextChallengeTargetsWeakestConcept(t *testing.T) { memory := learnermemory.NewService(learnermemory.NewMemoryStore()) if _, err := memory.EnsureProfile(learnermemory.ProfileInput{ UserID: "user-1", TargetRole: "backend developer", Stack: []string{"go"}, }); err != nil { t.Fatalf("EnsureProfile error: %v", err) } evidence := []workflows.EvidenceRef{{Kind: workflows.EvidenceAnswer, ID: "a-1", Confidence: 1}} if err := memory.ApplyCandidate(workflows.MemoryUpdateCandidate{ UserID: "user-1", Updates: []workflows.MemoryUpdate{ { Kind: workflows.MemoryConceptMastery, Concept: workflows.ConceptRef{ID: "cache", Label: "Cache invalidation", Track: "backend-developer"}, ProposedState: workflows.ReadinessInterviewReady, Evidence: evidence, }, { Kind: workflows.MemoryConceptMastery, Concept: workflows.ConceptRef{ID: "indexes", Label: "Database indexes", Track: "backend-developer"}, ProposedState: workflows.ReadinessFragile, Evidence: evidence, }, }, }); err != nil { t.Fatalf("ApplyCandidate error: %v", err) } challenge, err := NewService(memory).NextChallenge("user-1") if err != nil { t.Fatalf("NextChallenge error: %v", err) } if challenge.Concept.ID != "indexes" { t.Fatalf("challenge concept = %q", challenge.Concept.ID) } if challenge.DifficultyAction != workflows.DifficultyRecover { t.Fatalf("difficulty = %q", challenge.DifficultyAction) } } func seededService(t *testing.T, state workflows.ReadinessState) *Service { t.Helper() memory := learnermemory.NewService(learnermemory.NewMemoryStore()) if _, err := memory.EnsureProfile(learnermemory.ProfileInput{ UserID: "user-1", TargetRole: "backend developer", Stack: []string{"go"}, }); err != nil { t.Fatalf("EnsureProfile error: %v", err) } if err := memory.ApplyCandidate(workflows.MemoryUpdateCandidate{ UserID: "user-1", Updates: []workflows.MemoryUpdate{ { Kind: workflows.MemoryConceptMastery, Concept: workflows.ConceptRef{ID: "idempotency", Label: "HTTP idempotency", Track: "backend-developer"}, ProposedState: state, Evidence: []workflows.EvidenceRef{{Kind: workflows.EvidenceAnswer, ID: "a-1", Confidence: 1}}, }, }, }); err != nil { t.Fatalf("ApplyCandidate error: %v", err) } return NewService(memory) }