98 lines
3.1 KiB
Go
98 lines
3.1 KiB
Go
|
|
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)
|
||
|
|
}
|