- Add video upload endpoint POST /courses/:id/lessons/:lesson_id/video - Add VideoTranscodeService for multi-bitrate HLS (720p/480p/360p) - Add VideoTranscodeWorker for async lesson video processing - Add SetLessonVideoPath and UpdateLessonTranscoding to education service - Add uploadLessonVideo to frontend educationService with progress - Add comprehensive handler tests (video upload, auth, validation) - Add service-level tests (models, slugs, clamping, errors, UUIDs) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
342 lines
9.6 KiB
Go
342 lines
9.6 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"go.uber.org/zap"
|
|
|
|
"veza-backend-api/internal/core/education"
|
|
)
|
|
|
|
func TestNewEducationHandler(t *testing.T) {
|
|
logger := zap.NewNop()
|
|
svc := education.NewService(nil, logger)
|
|
handler := NewEducationHandler(svc, logger)
|
|
|
|
if handler == nil {
|
|
t.Fatal("expected non-nil handler")
|
|
}
|
|
if handler.service == nil {
|
|
t.Error("expected non-nil service")
|
|
}
|
|
}
|
|
|
|
func TestEducationHandler_CreateCourse_NoAuth(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
logger := zap.NewNop()
|
|
svc := education.NewService(nil, logger)
|
|
handler := NewEducationHandler(svc, logger)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/courses", strings.NewReader(`{}`))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
handler.CreateCourse(c)
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected 401, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestEducationHandler_CreateCourse_InvalidBody(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
logger := zap.NewNop()
|
|
svc := education.NewService(nil, logger)
|
|
handler := NewEducationHandler(svc, logger)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("user_id", uuid.New())
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/courses", strings.NewReader(`{}`))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
handler.CreateCourse(c)
|
|
|
|
var resp map[string]interface{}
|
|
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
if resp["success"] != false {
|
|
t.Error("expected success=false for invalid request")
|
|
}
|
|
}
|
|
|
|
func TestEducationHandler_GetCourse_InvalidID(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
logger := zap.NewNop()
|
|
svc := education.NewService(nil, logger)
|
|
handler := NewEducationHandler(svc, logger)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Params = gin.Params{{Key: "id", Value: "not-a-uuid"}}
|
|
c.Request = httptest.NewRequest(http.MethodGet, "/courses/not-a-uuid", nil)
|
|
|
|
handler.GetCourse(c)
|
|
|
|
var resp map[string]interface{}
|
|
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
if resp["success"] != false {
|
|
t.Error("expected success=false for invalid ID")
|
|
}
|
|
}
|
|
|
|
func TestEducationHandler_GetCourseBySlug_EmptySlug(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
logger := zap.NewNop()
|
|
svc := education.NewService(nil, logger)
|
|
handler := NewEducationHandler(svc, logger)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Params = gin.Params{{Key: "slug", Value: ""}}
|
|
c.Request = httptest.NewRequest(http.MethodGet, "/courses/slug/", nil)
|
|
|
|
handler.GetCourseBySlug(c)
|
|
|
|
var resp map[string]interface{}
|
|
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
if resp["success"] != false {
|
|
t.Error("expected success=false for empty slug")
|
|
}
|
|
}
|
|
|
|
func TestEducationHandler_Enroll_NoAuth(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
logger := zap.NewNop()
|
|
svc := education.NewService(nil, logger)
|
|
handler := NewEducationHandler(svc, logger)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/courses/123/enroll", strings.NewReader(`{}`))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
handler.Enroll(c)
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected 401, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestEducationHandler_UpdateProgress_NoAuth(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
logger := zap.NewNop()
|
|
svc := education.NewService(nil, logger)
|
|
handler := NewEducationHandler(svc, logger)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/lessons/123/progress", strings.NewReader(`{}`))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
handler.UpdateProgress(c)
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected 401, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestEducationHandler_IssueCertificate_NoAuth(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
logger := zap.NewNop()
|
|
svc := education.NewService(nil, logger)
|
|
handler := NewEducationHandler(svc, logger)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/courses/123/certificate", nil)
|
|
|
|
handler.IssueCertificate(c)
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected 401, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestEducationHandler_CreateReview_NoAuth(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
logger := zap.NewNop()
|
|
svc := education.NewService(nil, logger)
|
|
handler := NewEducationHandler(svc, logger)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/courses/123/reviews", strings.NewReader(`{}`))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
handler.CreateReview(c)
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected 401, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestEducationHandler_GetMyEnrollments_NoAuth(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
logger := zap.NewNop()
|
|
svc := education.NewService(nil, logger)
|
|
handler := NewEducationHandler(svc, logger)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = httptest.NewRequest(http.MethodGet, "/enrollments", nil)
|
|
|
|
handler.GetMyEnrollments(c)
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected 401, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestEducationHandler_UploadLessonVideo_NoAuth(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
logger := zap.NewNop()
|
|
svc := education.NewService(nil, logger)
|
|
handler := NewEducationHandler(svc, logger)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/courses/123/lessons/456/video", nil)
|
|
c.Request.Header.Set("Content-Type", "multipart/form-data")
|
|
|
|
handler.UploadLessonVideo(c)
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected 401, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestEducationHandler_UploadLessonVideo_InvalidCourseID(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
logger := zap.NewNop()
|
|
svc := education.NewService(nil, logger)
|
|
handler := NewEducationHandler(svc, logger)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("user_id", uuid.New())
|
|
c.Params = gin.Params{{Key: "id", Value: "not-a-uuid"}}
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/courses/not-a-uuid/lessons/456/video", nil)
|
|
|
|
handler.UploadLessonVideo(c)
|
|
|
|
var resp map[string]interface{}
|
|
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
if resp["success"] != false {
|
|
t.Error("expected success=false for invalid course ID")
|
|
}
|
|
}
|
|
|
|
func TestEducationHandler_UploadLessonVideo_InvalidLessonID(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
logger := zap.NewNop()
|
|
svc := education.NewService(nil, logger)
|
|
handler := NewEducationHandler(svc, logger)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Set("user_id", uuid.New())
|
|
c.Params = gin.Params{
|
|
{Key: "id", Value: uuid.New().String()},
|
|
{Key: "lesson_id", Value: "not-a-uuid"},
|
|
}
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/courses/123/lessons/not-a-uuid/video", nil)
|
|
|
|
handler.UploadLessonVideo(c)
|
|
|
|
var resp map[string]interface{}
|
|
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
if resp["success"] != false {
|
|
t.Error("expected success=false for invalid lesson ID")
|
|
}
|
|
}
|
|
|
|
func TestEducationHandler_DeleteLesson_NoAuth(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
logger := zap.NewNop()
|
|
svc := education.NewService(nil, logger)
|
|
handler := NewEducationHandler(svc, logger)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = httptest.NewRequest(http.MethodDelete, "/courses/123/lessons/456", nil)
|
|
|
|
handler.DeleteLesson(c)
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected 401, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestEducationHandler_ReorderLessons_NoAuth(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
logger := zap.NewNop()
|
|
svc := education.NewService(nil, logger)
|
|
handler := NewEducationHandler(svc, logger)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/courses/123/lessons/reorder", strings.NewReader(`{}`))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
handler.ReorderLessons(c)
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected 401, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestEducationHandler_GetCourseProgress_NoAuth(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
logger := zap.NewNop()
|
|
svc := education.NewService(nil, logger)
|
|
handler := NewEducationHandler(svc, logger)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = httptest.NewRequest(http.MethodGet, "/courses/123/progress", nil)
|
|
|
|
handler.GetCourseProgress(c)
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected 401, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestEducationHandler_VerifyCertificate_EmptyCode(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
logger := zap.NewNop()
|
|
svc := education.NewService(nil, logger)
|
|
handler := NewEducationHandler(svc, logger)
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Params = gin.Params{{Key: "code", Value: ""}}
|
|
c.Request = httptest.NewRequest(http.MethodGet, "/certificates/", nil)
|
|
|
|
handler.VerifyCertificate(c)
|
|
|
|
var resp map[string]interface{}
|
|
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
if resp["success"] != false {
|
|
t.Error("expected success=false for empty code")
|
|
}
|
|
}
|