backend-ci.yml's `test -z "$(gofmt -l .)"` strict gate (added in
13c21ac11) failed on a backlog of unformatted files. None of the
85 files in this commit had been edited since the gate was added
because no push touched veza-backend-api/** in between, so the
gate never fired until today's CI fixes triggered it.
The diff is exclusively whitespace alignment in struct literals
and trailing-space comments. `go build ./...` and the full test
suite (with VEZA_SKIP_INTEGRATION=1 -short) pass identically.
392 lines
9.3 KiB
Go
392 lines
9.3 KiB
Go
package education
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
func TestCourseTableName(t *testing.T) {
|
|
c := Course{}
|
|
if c.TableName() != "courses" {
|
|
t.Errorf("expected courses, got %s", c.TableName())
|
|
}
|
|
}
|
|
|
|
func TestLessonTableName(t *testing.T) {
|
|
l := Lesson{}
|
|
if l.TableName() != "lessons" {
|
|
t.Errorf("expected lessons, got %s", l.TableName())
|
|
}
|
|
}
|
|
|
|
func TestCourseEnrollmentTableName(t *testing.T) {
|
|
e := CourseEnrollment{}
|
|
if e.TableName() != "course_enrollments" {
|
|
t.Errorf("expected course_enrollments, got %s", e.TableName())
|
|
}
|
|
}
|
|
|
|
func TestLessonProgressTableName(t *testing.T) {
|
|
p := LessonProgress{}
|
|
if p.TableName() != "lesson_progress" {
|
|
t.Errorf("expected lesson_progress, got %s", p.TableName())
|
|
}
|
|
}
|
|
|
|
func TestCertificateTableName(t *testing.T) {
|
|
cert := Certificate{}
|
|
if cert.TableName() != "certificates" {
|
|
t.Errorf("expected certificates, got %s", cert.TableName())
|
|
}
|
|
}
|
|
|
|
func TestCourseReviewTableName(t *testing.T) {
|
|
r := CourseReview{}
|
|
if r.TableName() != "course_reviews" {
|
|
t.Errorf("expected course_reviews, got %s", r.TableName())
|
|
}
|
|
}
|
|
|
|
func TestCourseStatusConstants(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
status CourseStatus
|
|
expected string
|
|
}{
|
|
{"draft", CourseStatusDraft, "draft"},
|
|
{"published", CourseStatusPublished, "published"},
|
|
{"archived", CourseStatusArchived, "archived"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if string(tt.status) != tt.expected {
|
|
t.Errorf("expected %s, got %s", tt.expected, string(tt.status))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCourseLevelConstants(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
level CourseLevel
|
|
expected string
|
|
}{
|
|
{"beginner", CourseLevelBeginner, "beginner"},
|
|
{"intermediate", CourseLevelIntermediate, "intermediate"},
|
|
{"advanced", CourseLevelAdvanced, "advanced"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if string(tt.level) != tt.expected {
|
|
t.Errorf("expected %s, got %s", tt.expected, string(tt.level))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPricingModelConstants(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
model PricingModel
|
|
expected string
|
|
}{
|
|
{"fixed", PricingFixed, "fixed"},
|
|
{"pay_what_you_want", PricingPayWhatYou, "pay_what_you_want"},
|
|
{"free", PricingFree, "free"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if string(tt.model) != tt.expected {
|
|
t.Errorf("expected %s, got %s", tt.expected, string(tt.model))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTranscodingStatusConstants(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
status TranscodingStatus
|
|
expected string
|
|
}{
|
|
{"pending", TranscodingPending, "pending"},
|
|
{"processing", TranscodingProcessing, "processing"},
|
|
{"complete", TranscodingComplete, "complete"},
|
|
{"failed", TranscodingFailed, "failed"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if string(tt.status) != tt.expected {
|
|
t.Errorf("expected %s, got %s", tt.expected, string(tt.status))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestServiceErrors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
err error
|
|
msg string
|
|
}{
|
|
{"course not found", ErrCourseNotFound, "course not found"},
|
|
{"lesson not found", ErrLessonNotFound, "lesson not found"},
|
|
{"enrollment not found", ErrEnrollmentNotFound, "enrollment not found"},
|
|
{"already enrolled", ErrAlreadyEnrolled, "already enrolled in this course"},
|
|
{"not enrolled", ErrNotEnrolled, "not enrolled in this course"},
|
|
{"course not published", ErrCourseNotPublished, "course is not published"},
|
|
{"not course owner", ErrNotCourseOwner, "you are not the owner of this course"},
|
|
{"review exists", ErrReviewAlreadyExists, "you have already reviewed this course"},
|
|
{"invalid rating", ErrInvalidRating, "rating must be between 1 and 5"},
|
|
{"certificate not found", ErrCertificateNotFound, "certificate not found"},
|
|
{"course not completed", ErrCourseNotCompleted, "course is not fully completed"},
|
|
{"certificate exists", ErrCertificateExists, "certificate already issued for this course"},
|
|
{"cannot enroll own", ErrCannotEnrollOwnCourse, "cannot enroll in your own course"},
|
|
{"slug taken", ErrSlugAlreadyTaken, "slug is already taken"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.err.Error() != tt.msg {
|
|
t.Errorf("expected %q, got %q", tt.msg, tt.err.Error())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerateSlug(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expected string
|
|
}{
|
|
{"My First Course", "my-first-course"},
|
|
{"Introduction à la Musique", "introduction--la-musique"},
|
|
{"Hello World 123", "hello-world-123"},
|
|
{" spaces ", "spaces"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.input, func(t *testing.T) {
|
|
got := generateSlug(tt.input)
|
|
if got == "" {
|
|
t.Error("slug should not be empty")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewService(t *testing.T) {
|
|
svc := NewService(nil, nil)
|
|
if svc == nil {
|
|
t.Fatal("expected non-nil service")
|
|
}
|
|
}
|
|
|
|
func TestCourseFields(t *testing.T) {
|
|
course := Course{
|
|
ID: uuid.New(),
|
|
CreatorID: uuid.New(),
|
|
Title: "Test Course",
|
|
Slug: "test-course",
|
|
PriceCents: 1999,
|
|
Currency: "USD",
|
|
Status: CourseStatusDraft,
|
|
Level: CourseLevelBeginner,
|
|
Language: "en",
|
|
}
|
|
|
|
if course.Title != "Test Course" {
|
|
t.Errorf("expected Test Course, got %s", course.Title)
|
|
}
|
|
if course.PriceCents != 1999 {
|
|
t.Errorf("expected 1999, got %d", course.PriceCents)
|
|
}
|
|
if course.Status != CourseStatusDraft {
|
|
t.Errorf("expected draft, got %s", course.Status)
|
|
}
|
|
}
|
|
|
|
func TestLessonFields(t *testing.T) {
|
|
lesson := Lesson{
|
|
ID: uuid.New(),
|
|
CourseID: uuid.New(),
|
|
OrderIndex: 1,
|
|
Title: "Lesson 1",
|
|
DurationSeconds: 600,
|
|
IsPreviewFree: true,
|
|
TranscodingStatus: TranscodingPending,
|
|
}
|
|
|
|
if lesson.OrderIndex != 1 {
|
|
t.Errorf("expected 1, got %d", lesson.OrderIndex)
|
|
}
|
|
if !lesson.IsPreviewFree {
|
|
t.Error("expected is_preview_free to be true")
|
|
}
|
|
if lesson.DurationSeconds != 600 {
|
|
t.Errorf("expected 600, got %d", lesson.DurationSeconds)
|
|
}
|
|
}
|
|
|
|
func TestEnrollmentFields(t *testing.T) {
|
|
enrollment := CourseEnrollment{
|
|
ID: uuid.New(),
|
|
UserID: uuid.New(),
|
|
CourseID: uuid.New(),
|
|
PurchasedPriceCents: 1999,
|
|
Currency: "USD",
|
|
Status: EnrollmentActive,
|
|
}
|
|
|
|
if enrollment.PurchasedPriceCents != 1999 {
|
|
t.Errorf("expected 1999, got %d", enrollment.PurchasedPriceCents)
|
|
}
|
|
if enrollment.Status != EnrollmentActive {
|
|
t.Errorf("expected active, got %s", enrollment.Status)
|
|
}
|
|
}
|
|
|
|
func TestCertificateFields(t *testing.T) {
|
|
cert := Certificate{
|
|
ID: uuid.New(),
|
|
CertificateCode: "VEZA-CERT-abc12345-def67890",
|
|
Status: CertificateActive,
|
|
}
|
|
|
|
if cert.Status != CertificateActive {
|
|
t.Errorf("expected active, got %s", cert.Status)
|
|
}
|
|
if cert.CertificateCode == "" {
|
|
t.Error("expected non-empty certificate code")
|
|
}
|
|
}
|
|
|
|
func TestReviewFields(t *testing.T) {
|
|
review := CourseReview{
|
|
ID: uuid.New(),
|
|
Rating: 4,
|
|
Title: "Great course",
|
|
Content: "Loved it!",
|
|
Status: ReviewApproved,
|
|
}
|
|
|
|
if review.Rating != 4 {
|
|
t.Errorf("expected 4, got %d", review.Rating)
|
|
}
|
|
if review.Status != ReviewApproved {
|
|
t.Errorf("expected approved, got %s", review.Status)
|
|
}
|
|
}
|
|
|
|
func TestProgressPercentageClamping(t *testing.T) {
|
|
tests := []struct {
|
|
input int
|
|
expected int
|
|
}{
|
|
{-10, 0},
|
|
{0, 0},
|
|
{50, 50},
|
|
{100, 100},
|
|
{150, 100},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
clamped := tt.input
|
|
if clamped < 0 {
|
|
clamped = 0
|
|
}
|
|
if clamped > 100 {
|
|
clamped = 100
|
|
}
|
|
if clamped != tt.expected {
|
|
t.Errorf("clamp(%d) = %d, want %d", tt.input, clamped, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRatingValidation(t *testing.T) {
|
|
tests := []struct {
|
|
rating int
|
|
isValid bool
|
|
}{
|
|
{0, false},
|
|
{1, true},
|
|
{3, true},
|
|
{5, true},
|
|
{6, false},
|
|
{-1, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
valid := tt.rating >= 1 && tt.rating <= 5
|
|
if valid != tt.isValid {
|
|
t.Errorf("rating %d: expected valid=%v, got %v", tt.rating, tt.isValid, valid)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUploadLessonVideoResult(t *testing.T) {
|
|
result := UploadLessonVideoResult{
|
|
LessonID: uuid.New(),
|
|
VideoFilePath: "/tmp/test/video.mp4",
|
|
Status: string(TranscodingPending),
|
|
}
|
|
|
|
if result.LessonID == uuid.Nil {
|
|
t.Error("expected non-nil lesson ID")
|
|
}
|
|
if result.VideoFilePath == "" {
|
|
t.Error("expected non-empty video path")
|
|
}
|
|
if result.Status != "pending" {
|
|
t.Errorf("expected pending, got %s", result.Status)
|
|
}
|
|
}
|
|
|
|
func TestBeforeCreateUUIDs(t *testing.T) {
|
|
// All models should generate UUID on create if nil
|
|
models := []struct {
|
|
name string
|
|
fn func() uuid.UUID
|
|
}{
|
|
{"Course", func() uuid.UUID {
|
|
c := &Course{}
|
|
c.BeforeCreate(nil)
|
|
return c.ID
|
|
}},
|
|
{"Lesson", func() uuid.UUID {
|
|
l := &Lesson{}
|
|
l.BeforeCreate(nil)
|
|
return l.ID
|
|
}},
|
|
{"CourseEnrollment", func() uuid.UUID {
|
|
e := &CourseEnrollment{}
|
|
e.BeforeCreate(nil)
|
|
return e.ID
|
|
}},
|
|
{"LessonProgress", func() uuid.UUID {
|
|
p := &LessonProgress{}
|
|
p.BeforeCreate(nil)
|
|
return p.ID
|
|
}},
|
|
{"Certificate", func() uuid.UUID {
|
|
c := &Certificate{}
|
|
c.BeforeCreate(nil)
|
|
return c.ID
|
|
}},
|
|
{"CourseReview", func() uuid.UUID {
|
|
r := &CourseReview{}
|
|
r.BeforeCreate(nil)
|
|
return r.ID
|
|
}},
|
|
}
|
|
|
|
for _, m := range models {
|
|
t.Run(m.name, func(t *testing.T) {
|
|
id := m.fn()
|
|
if id == uuid.Nil {
|
|
t.Errorf("%s: expected non-nil UUID after BeforeCreate", m.name)
|
|
}
|
|
})
|
|
}
|
|
}
|