Yimaru-BackEnd/internal/services/lmsprogress/service_test.go
Yared Yemane 08a2886654 feat: optional dynamic question_text and OPEN_LEARNER completed access
Derive question_text from QUESTION_TEXT, INSTRUCTION, or TEXT_PASSAGE stimulus for DYNAMIC questions so the top-level field is no longer required on create.

OPEN_LEARNER access responses now set is_accessible and is_completed to true on all LMS and exam-prep content, with full progress when totals exist.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 09:48:33 -07:00

139 lines
4.3 KiB
Go

package lmsprogress
import (
"testing"
"Yimaru-Backend/internal/domain"
)
func TestLMSProgressCounts(t *testing.T) {
tests := []struct {
name string
completed int32
total int32
isCompleted bool
wantCompleted int
wantTotal int
wantPercent int
wantPercentFloat float64
}{
{
name: "fractional progress rounds to two decimals",
completed: 1,
total: 3,
wantCompleted: 1,
wantTotal: 3,
wantPercent: 33,
wantPercentFloat: 33.33,
},
{
name: "larger fraction rounds precisely",
completed: 2,
total: 3,
wantCompleted: 2,
wantTotal: 3,
wantPercent: 66,
wantPercentFloat: 66.67,
},
{
name: "completed forces full progress",
completed: 2,
total: 3,
isCompleted: true,
wantCompleted: 3,
wantTotal: 3,
wantPercent: 100,
wantPercentFloat: 100,
},
{
name: "empty scope stays zeroed",
wantCompleted: 0,
wantTotal: 0,
wantPercent: 0,
wantPercentFloat: 0,
},
{
name: "negative counts are sanitized",
completed: -2,
total: -5,
wantCompleted: 0,
wantTotal: 0,
wantPercent: 0,
wantPercentFloat: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotCompleted, gotTotal, gotPercent, gotPercentFloat := lmsProgressCounts(tt.completed, tt.total, tt.isCompleted)
if gotCompleted != tt.wantCompleted || gotTotal != tt.wantTotal {
t.Fatalf("counts=(%d,%d), want (%d,%d)", gotCompleted, gotTotal, tt.wantCompleted, tt.wantTotal)
}
if gotPercent != tt.wantPercent {
t.Fatalf("progress_percent=%d, want %d", gotPercent, tt.wantPercent)
}
if gotPercentFloat != tt.wantPercentFloat {
t.Fatalf("progress_percent_precise=%v, want %v", gotPercentFloat, tt.wantPercentFloat)
}
})
}
}
func TestPracticeScopeFraction(t *testing.T) {
fraction, done, completed, total := practiceScopeFraction(1, 5)
if fraction != 0.2 || done || completed != 1 || total != 5 {
t.Fatalf("practiceScopeFraction(1,5)=(%v,%v,%d,%d), want (0.2,false,1,5)", fraction, done, completed, total)
}
fraction, done, completed, total = practiceScopeFraction(5, 5)
if fraction != 1 || !done || completed != 5 || total != 5 {
t.Fatalf("practiceScopeFraction(5,5)=(%v,%v,%d,%d), want (1,true,5,5)", fraction, done, completed, total)
}
fraction, done, completed, total = practiceScopeFraction(0, 0)
if fraction != 0 || done || completed != 0 || total != 0 {
t.Fatalf("practiceScopeFraction(0,0)=(%v,%v,%d,%d), want (0,false,0,0)", fraction, done, completed, total)
}
}
func TestFinalizeOpenLearnerAccess(t *testing.T) {
incomplete := buildLMSEntityAccessFromFraction(false, "locked", false, 1, 5, 0.2)
got := finalizeOpenLearnerAccess(domain.RoleOpenLearner, incomplete)
if got == nil || !got.IsAccessible || !got.IsCompleted {
t.Fatalf("expected accessible and completed, got %+v", got)
}
if got.CompletedCount != 5 || got.ProgressPercent != 100 {
t.Fatalf("expected full progress, got %+v", got)
}
studentAccess := buildLMSEntityAccessFromFraction(false, "locked", false, 1, 5, 0.2)
unchanged := finalizeOpenLearnerAccess(domain.RoleStudent, studentAccess)
if unchanged != studentAccess || unchanged.IsCompleted || unchanged.IsAccessible {
t.Fatalf("STUDENT access should be unchanged, got %+v", unchanged)
}
emptyScope := finalizeOpenLearnerAccess(domain.RoleOpenLearner, buildLMSEntityAccessFromFraction(true, "", false, 0, 0, 0))
if emptyScope == nil || !emptyScope.IsCompleted {
t.Fatalf("expected completed even with zero total, got %+v", emptyScope)
}
}
func TestLMSProgressComplete(t *testing.T) {
tests := []struct {
name string
completed int32
total int32
want bool
}{
{name: "complete when all practices done", completed: 3, total: 3, want: true},
{name: "incomplete when practices remain", completed: 2, total: 3, want: false},
{name: "zero total is not completed", completed: 0, total: 0, want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := lmsProgressComplete(tt.completed, tt.total); got != tt.want {
t.Fatalf("lmsProgressComplete(%d, %d)=%v, want %v", tt.completed, tt.total, got, tt.want)
}
})
}
}