Files
linearvoid/backend/chat/src/handlers/handlers_test.go
T

947 lines
25 KiB
Go

package handlers
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"time"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
"chat/src/auth"
"chat/src/config"
"chat/src/db"
"chat/src/ws"
)
// mockRedis implements db.RedisClient for handler tests.
type mockRedis struct {
mu sync.Mutex
store map[string]string
}
func newMockRedis() *mockRedis {
return &mockRedis{store: make(map[string]string)}
}
func (m *mockRedis) Ping(ctx context.Context) error { return nil }
func (m *mockRedis) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
m.mu.Lock()
defer m.mu.Unlock()
m.store[key] = value.(string)
return nil
}
func (m *mockRedis) Get(ctx context.Context, key string) (string, error) {
m.mu.Lock()
defer m.mu.Unlock()
return m.store[key], nil
}
func (m *mockRedis) Del(ctx context.Context, keys ...string) error {
m.mu.Lock()
defer m.mu.Unlock()
for _, k := range keys {
delete(m.store, k)
}
return nil
}
func (m *mockRedis) Incr(ctx context.Context, key string) (int64, error) {
m.mu.Lock()
defer m.mu.Unlock()
m.store[key] = "1"
return 1, nil
}
func (m *mockRedis) Decr(ctx context.Context, key string) (int64, error) { return 0, nil }
func (m *mockRedis) Expire(ctx context.Context, key string, expiration time.Duration) error {
return nil
}
func (m *mockRedis) Exists(ctx context.Context, keys ...string) (int64, error) {
m.mu.Lock()
defer m.mu.Unlock()
for _, k := range keys {
if _, ok := m.store[k]; ok {
return 1, nil
}
}
return 0, nil
}
func (m *mockRedis) Publish(ctx context.Context, channel string, message interface{}) error {
return nil
}
func (m *mockRedis) PSubscribe(ctx context.Context, channels ...string) db.PubSub {
return nil
}
func (m *mockRedis) Scan(ctx context.Context, cursor uint64, match string, count int64) ([]string, uint64, error) {
m.mu.Lock()
defer m.mu.Unlock()
var keys []string
for k := range m.store {
if strings.Contains(k, strings.TrimSuffix(match, "*")) {
keys = append(keys, k)
}
}
return keys, 0, nil
}
func (m *mockRedis) Info(ctx context.Context, sections ...string) (string, error) {
return "connected_clients:1\nused_memory_human:1M\n", nil
}
func (m *mockRedis) Close() error { return nil }
func setupTestDeps(t *testing.T) (*Dependencies, *gorm.DB, *auth.SessionStore) {
t.Helper()
gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
if err != nil {
t.Fatalf("failed to open test DB: %v", err)
}
gormDB.AutoMigrate(&db.Message{}, &db.ModerationLog{}, &db.Media{}, &db.ScheduleOverride{})
mr := newMockRedis()
sessionStore := auth.NewSessionStore()
hub := ws.NewHub(gormDB, mr, "test-instance", 10, 10*time.Second, config.AdminUsername())
deps := &Dependencies{
DB: gormDB,
Redis: mr,
Hub: hub,
SessionStore: sessionStore,
DBDriver: "sqlite",
}
return deps, gormDB, sessionStore
}
// adminCookie creates a valid admin session and returns the cookie header.
func adminCookie(t *testing.T, store *auth.SessionStore) string {
t.Helper()
token, _ := auth.GenerateToken()
store.Add(token, time.Now().Add(1*time.Hour))
return config.AdminSessionCookieName + "=" + token
}
func TestHandleAdminLogin_Success(t *testing.T) {
deps, _, _ := setupTestDeps(t)
handler := HandleAdminLogin(deps)
// Ensure env vars are set for credential check
os.Setenv("ADMIN_USERNAME", "admin")
os.Setenv("ADMIN_PASSWORD", "admin")
defer os.Unsetenv("ADMIN_USERNAME")
defer os.Unsetenv("ADMIN_PASSWORD")
body := `{"username":"admin","password":"admin"}`
req := httptest.NewRequest("POST", "/api/admin/login", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
var resp AdminSessionResponse
json.NewDecoder(w.Body).Decode(&resp)
if !resp.Authenticated {
t.Error("Authenticated = false, want true")
}
// Check cookie set
cookies := w.Result().Cookies()
found := false
for _, c := range cookies {
if c.Name == config.AdminSessionCookieName {
found = true
break
}
}
if !found {
t.Error("session cookie not set")
}
}
func TestHandleAdminLogin_InvalidCreds(t *testing.T) {
deps, _, _ := setupTestDeps(t)
handler := HandleAdminLogin(deps)
body := `{"username":"admin","password":"wrong"}`
req := httptest.NewRequest("POST", "/api/admin/login", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusUnauthorized {
t.Errorf("status = %d, want 401", w.Code)
}
}
func TestHandleAdminLogin_InvalidJSON(t *testing.T) {
deps, _, _ := setupTestDeps(t)
handler := HandleAdminLogin(deps)
req := httptest.NewRequest("POST", "/api/admin/login", strings.NewReader("not json"))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("status = %d, want 400", w.Code)
}
}
func TestHandleAdminLogin_WrongMethod(t *testing.T) {
deps, _, _ := setupTestDeps(t)
handler := HandleAdminLogin(deps)
req := httptest.NewRequest("GET", "/api/admin/login", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusMethodNotAllowed {
t.Errorf("status = %d, want 405", w.Code)
}
}
func TestHandleAdminLogout(t *testing.T) {
deps, _, store := setupTestDeps(t)
handler := HandleAdminLogout(deps)
cookie := adminCookie(t, store)
req := httptest.NewRequest("POST", "/api/admin/logout", nil)
req.Header.Set("Cookie", cookie)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
// Cookie should be cleared
cookies := w.Result().Cookies()
found := false
for _, c := range cookies {
if c.Name == config.AdminSessionCookieName && c.MaxAge == -1 {
found = true
break
}
}
if !found {
t.Error("session cookie not cleared")
}
}
func TestHandleAdminSession_Authenticated(t *testing.T) {
deps, _, store := setupTestDeps(t)
handler := HandleAdminSession(deps)
cookie := adminCookie(t, store)
req := httptest.NewRequest("GET", "/api/admin/session", nil)
req.Header.Set("Cookie", cookie)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
var resp AdminSessionResponse
json.NewDecoder(w.Body).Decode(&resp)
if !resp.Authenticated {
t.Error("Authenticated = false, want true")
}
}
func TestHandleAdminSession_Unauthenticated(t *testing.T) {
deps, _, _ := setupTestDeps(t)
handler := HandleAdminSession(deps)
req := httptest.NewRequest("GET", "/api/admin/session", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusUnauthorized {
t.Errorf("status = %d, want 401", w.Code)
}
}
func TestHandleAdminOverview(t *testing.T) {
deps, gormDB, _ := setupTestDeps(t)
// Add some test data
gormDB.Create(&db.Message{Room: "main", Username: "u1", Text: "hi", Timestamp: time.Now(), Type: "public"})
gormDB.Create(&db.ModerationLog{Action: "mute", Target: "u2", Moderator: "admin", Reason: "spam", Timestamp: time.Now()})
handler := HandleAdminOverview(deps)
req := httptest.NewRequest("GET", "/api/admin/overview", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
var overview AdminOverview
json.NewDecoder(w.Body).Decode(&overview)
if overview.StoredMessages != 1 {
t.Errorf("StoredMessages = %d, want 1", overview.StoredMessages)
}
if overview.ModerationActions != 1 {
t.Errorf("ModerationActions = %d, want 1", overview.ModerationActions)
}
}
func TestHandleAdminPlaylist_Get(t *testing.T) {
deps, _, _ := setupTestDeps(t)
// Create temp playlist
tmpDir := t.TempDir()
playlistPath := filepath.Join(tmpDir, "playlist.txt")
os.WriteFile(playlistPath, []byte("file /videos/test.mp4\n"), 0644)
os.Setenv("PLAYLIST_PATH", playlistPath)
defer os.Unsetenv("PLAYLIST_PATH")
handler := HandleAdminPlaylist(deps)
req := httptest.NewRequest("GET", "/api/admin/playlist", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
var payload PlaylistPayload
json.NewDecoder(w.Body).Decode(&payload)
if !strings.Contains(payload.Content, "test.mp4") {
t.Errorf("Content = %q, want test.mp4", payload.Content)
}
}
func TestHandleAdminPlaylist_Put(t *testing.T) {
deps, _, _ := setupTestDeps(t)
tmpDir := t.TempDir()
playlistPath := filepath.Join(tmpDir, "playlist.txt")
os.Setenv("PLAYLIST_PATH", playlistPath)
defer os.Unsetenv("PLAYLIST_PATH")
handler := HandleAdminPlaylist(deps)
body := `{"content":"file /videos/new.mp4"}`
req := httptest.NewRequest("PUT", "/api/admin/playlist", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
// Verify file written
data, _ := os.ReadFile(playlistPath)
if !strings.Contains(string(data), "new.mp4") {
t.Errorf("file content = %q, want new.mp4", string(data))
}
}
func TestHandleAdminModerationLogs(t *testing.T) {
deps, gormDB, _ := setupTestDeps(t)
gormDB.Create(&db.ModerationLog{Action: "mute", Target: "u1", Moderator: "admin", Reason: "spam", Timestamp: time.Now()})
handler := HandleAdminModerationLogs(deps)
req := httptest.NewRequest("GET", "/api/admin/moderation/logs", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
var resp ModerationLogsResponse
json.NewDecoder(w.Body).Decode(&resp)
if len(resp.Logs) != 1 {
t.Errorf("len(Logs) = %d, want 1", len(resp.Logs))
}
}
func TestHandleAdminModerationAction(t *testing.T) {
deps, gormDB, _ := setupTestDeps(t)
handler := HandleAdminModerationAction(deps)
body := `{"action":"mute","target":"baduser","moderator":"admin","reason":"spam","room":"main"}`
req := httptest.NewRequest("POST", "/api/admin/moderation/actions", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
var count int64
gormDB.Model(&db.ModerationLog{}).Count(&count)
if count != 1 {
t.Errorf("moderation log count = %d, want 1", count)
}
}
func TestHandleAdminModerationAction_MissingFields(t *testing.T) {
deps, _, _ := setupTestDeps(t)
handler := HandleAdminModerationAction(deps)
body := `{"action":"mute"}`
req := httptest.NewRequest("POST", "/api/admin/moderation/actions", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("status = %d, want 400", w.Code)
}
}
func TestHandleAdminAnalytics(t *testing.T) {
deps, gormDB, _ := setupTestDeps(t)
gormDB.Create(&db.Message{Room: "main", Username: "u1", Text: "hi", Timestamp: time.Now(), Type: "public"})
gormDB.Create(&db.Message{Room: "main", Username: "u2", Text: "hey", Timestamp: time.Now(), Type: "public"})
handler := HandleAdminAnalytics(deps)
req := httptest.NewRequest("GET", "/api/admin/analytics", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
var analytics AdminAnalytics
json.NewDecoder(w.Body).Decode(&analytics)
if analytics.TotalMessages != 2 {
t.Errorf("TotalMessages = %d, want 2", analytics.TotalMessages)
}
}
func TestHandleAdminSystemSummary(t *testing.T) {
deps, _, _ := setupTestDeps(t)
handler := HandleAdminSystemSummary(deps)
req := httptest.NewRequest("GET", "/api/admin/system", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
var summary AdminSystemSummary
json.NewDecoder(w.Body).Decode(&summary)
if summary.DatabaseDriver != "sqlite" {
t.Errorf("DatabaseDriver = %q, want sqlite", summary.DatabaseDriver)
}
}
func TestHandleAdminConfig_Get(t *testing.T) {
deps, _, _ := setupTestDeps(t)
handler := HandleAdminConfig(deps)
req := httptest.NewRequest("GET", "/api/admin/config", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
}
func TestHandleAdminConfig_Put(t *testing.T) {
deps, _, _ := setupTestDeps(t)
handler := HandleAdminConfig(deps)
body := `{"rateLimitMax":20}`
req := httptest.NewRequest("PUT", "/api/admin/config", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
}
func TestHandleAdminRestart(t *testing.T) {
deps, _, _ := setupTestDeps(t)
handler := HandleAdminRestart(deps)
req := httptest.NewRequest("POST", "/api/admin/restart?service=chat", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
}
func TestHandleAdminUsers_Get(t *testing.T) {
deps, _, _ := setupTestDeps(t)
handler := HandleAdminUsers(deps)
req := httptest.NewRequest("GET", "/api/admin/users", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
}
func TestHandleAdminUsers_Post(t *testing.T) {
deps, _, _ := setupTestDeps(t)
handler := HandleAdminUsers(deps)
body := `{"action":"mute","username":"baduser","duration":10}`
req := httptest.NewRequest("POST", "/api/admin/users", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
}
func TestHandleAdminSchedule(t *testing.T) {
deps, gormDB, _ := setupTestDeps(t)
tmpDir := t.TempDir()
playlistPath := filepath.Join(tmpDir, "playlist.txt")
os.WriteFile(playlistPath, []byte("file /videos/test.mp4\n"), 0644)
os.Setenv("PLAYLIST_PATH", playlistPath)
defer os.Unsetenv("PLAYLIST_PATH")
gormDB.Create(&db.Media{
Filename: "test.mp4",
OriginalName: "test.mp4",
Path: "/videos/test.mp4",
SizeBytes: 1024,
DurationSeconds: 60,
Status: "ready",
Title: "Test Video",
})
handler := HandleAdminSchedule(deps)
req := httptest.NewRequest("GET", "/api/admin/schedule", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
var resp AdminScheduleResponse
json.NewDecoder(w.Body).Decode(&resp)
if resp.MatchedEntries != 1 {
t.Errorf("MatchedEntries = %d, want 1", resp.MatchedEntries)
}
}
func TestHandleAdminScheduleOverrides_CRUD(t *testing.T) {
deps, gormDB, _ := setupTestDeps(t)
// Create
handler := HandleAdminScheduleOverrides(deps)
now := time.Now()
body, _ := json.Marshal(db.ScheduleOverride{
MediaID: 1,
StartTime: now,
EndTime: now.Add(1 * time.Hour),
Reason: "test",
})
req := httptest.NewRequest("POST", "/api/admin/schedule/overrides", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusCreated {
t.Errorf("POST status = %d, want 201", w.Code)
}
// List
req2 := httptest.NewRequest("GET", "/api/admin/schedule/overrides", nil)
w2 := httptest.NewRecorder()
handler(w2, req2)
if w2.Code != http.StatusOK {
t.Errorf("GET status = %d, want 200", w2.Code)
}
// Delete
var overrides []db.ScheduleOverride
gormDB.Find(&overrides)
if len(overrides) > 0 {
req3 := httptest.NewRequest("DELETE", "/api/admin/schedule/overrides?id=1", nil)
w3 := httptest.NewRecorder()
handler(w3, req3)
if w3.Code != http.StatusNoContent {
t.Errorf("DELETE status = %d, want 204", w3.Code)
}
}
}
func TestHandleAdminChatLog(t *testing.T) {
deps, gormDB, _ := setupTestDeps(t)
gormDB.Create(&db.Message{Room: "main", Username: "u1", Text: "hello", Timestamp: time.Now(), Type: "public"})
handler := HandleAdminChatLog(deps)
req := httptest.NewRequest("GET", "/api/admin/chat-log", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
var resp ChatLogListResponse
json.NewDecoder(w.Body).Decode(&resp)
if resp.Total != 1 {
t.Errorf("Total = %d, want 1", resp.Total)
}
}
func TestHandleAdminChatLog_FilterByRoom(t *testing.T) {
deps, gormDB, _ := setupTestDeps(t)
gormDB.Create(&db.Message{Room: "main", Username: "u1", Text: "hello", Timestamp: time.Now(), Type: "public"})
gormDB.Create(&db.Message{Room: "lobby", Username: "u2", Text: "hey", Timestamp: time.Now(), Type: "public"})
handler := HandleAdminChatLog(deps)
req := httptest.NewRequest("GET", "/api/admin/chat-log?room=main", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
var resp ChatLogListResponse
json.NewDecoder(w.Body).Decode(&resp)
if resp.Total != 1 {
t.Errorf("Total = %d, want 1", resp.Total)
}
}
func TestHandleAdminChatLogByID_Get(t *testing.T) {
deps, gormDB, _ := setupTestDeps(t)
msg := db.Message{Room: "main", Username: "u1", Text: "hello", Timestamp: time.Now(), Type: "public"}
gormDB.Create(&msg)
handler := HandleAdminChatLogByID(deps)
req := httptest.NewRequest("GET", "/api/admin/chat-log/1", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
}
func TestHandleAdminChatLogByID_Delete(t *testing.T) {
deps, gormDB, _ := setupTestDeps(t)
msg := db.Message{Room: "main", Username: "u1", Text: "hello", Timestamp: time.Now(), Type: "public"}
gormDB.Create(&msg)
handler := HandleAdminChatLogByID(deps)
req := httptest.NewRequest("DELETE", "/api/admin/chat-log/1", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusNoContent {
t.Errorf("status = %d, want 204", w.Code)
}
var count int64
gormDB.Model(&db.Message{}).Count(&count)
if count != 0 {
t.Errorf("message count after delete = %d, want 0", count)
}
}
func TestHandleAdminChatLogBulk(t *testing.T) {
deps, gormDB, _ := setupTestDeps(t)
gormDB.Create(&db.Message{Room: "main", Username: "u1", Text: "m1", Timestamp: time.Now(), Type: "public"})
gormDB.Create(&db.Message{Room: "main", Username: "u2", Text: "m2", Timestamp: time.Now(), Type: "public"})
handler := HandleAdminChatLogBulk(deps)
body := `{"ids":[1,2]}`
req := httptest.NewRequest("DELETE", "/api/admin/chat-log/bulk", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
var count int64
gormDB.Model(&db.Message{}).Count(&count)
if count != 0 {
t.Errorf("message count after bulk delete = %d, want 0", count)
}
}
func TestHandleAdminChatLogExport(t *testing.T) {
deps, gormDB, _ := setupTestDeps(t)
gormDB.Create(&db.Message{Room: "main", Username: "u1", Text: "hello", Timestamp: time.Now(), Type: "public"})
handler := HandleAdminChatLogExport(deps)
req := httptest.NewRequest("GET", "/api/admin/chat-log/export", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
contentType := w.Header().Get("Content-Type")
if !strings.Contains(contentType, "text/csv") {
t.Errorf("Content-Type = %q, want text/csv", contentType)
}
body := w.Body.String()
if !strings.Contains(body, "hello") {
t.Errorf("CSV body missing 'hello': %s", body)
}
}
func TestHandlePublicSchedule(t *testing.T) {
deps, _, _ := setupTestDeps(t)
tmpDir := t.TempDir()
playlistPath := filepath.Join(tmpDir, "playlist.txt")
os.WriteFile(playlistPath, []byte("file /videos/test.mp4\n"), 0644)
os.Setenv("PLAYLIST_PATH", playlistPath)
defer os.Unsetenv("PLAYLIST_PATH")
handler := HandlePublicSchedule(deps)
req := httptest.NewRequest("GET", "/api/schedule", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
}
func TestHandleAdminMedia_Get(t *testing.T) {
deps, gormDB, _ := setupTestDeps(t)
gormDB.Create(&db.Media{
Filename: "test.mp4",
OriginalName: "test.mp4",
Path: "/media/test.mp4",
SizeBytes: 1024,
DurationSeconds: 60,
Status: "ready",
Title: "Test",
})
handler := HandleAdminMedia(deps)
req := httptest.NewRequest("GET", "/api/admin/media", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
var resp AdminMediaResponse
json.NewDecoder(w.Body).Decode(&resp)
if len(resp.Files) != 1 {
t.Errorf("len(Files) = %d, want 1", len(resp.Files))
}
}
func TestHandleAdminMediaByID_Get(t *testing.T) {
deps, gormDB, _ := setupTestDeps(t)
gormDB.Create(&db.Media{
Filename: "test.mp4",
OriginalName: "test.mp4",
Path: "/media/test.mp4",
SizeBytes: 1024,
DurationSeconds: 60,
Status: "ready",
Title: "Test",
})
handler := HandleAdminMediaByID(deps)
req := httptest.NewRequest("GET", "/api/admin/media/1", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
}
func TestHandleAdminMediaByID_NotFound(t *testing.T) {
deps, _, _ := setupTestDeps(t)
handler := HandleAdminMediaByID(deps)
req := httptest.NewRequest("GET", "/api/admin/media/999", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusNotFound {
t.Errorf("status = %d, want 404", w.Code)
}
}
func TestHandleAdminMediaByID_Put(t *testing.T) {
deps, gormDB, _ := setupTestDeps(t)
gormDB.Create(&db.Media{
Filename: "test.mp4",
OriginalName: "test.mp4",
Path: "/media/test.mp4",
SizeBytes: 1024,
DurationSeconds: 60,
Status: "ready",
Title: "Old Title",
})
handler := HandleAdminMediaByID(deps)
body := `{"title":"New Title","status":"needs_normalization","tags":"tag1"}`
req := httptest.NewRequest("PUT", "/api/admin/media/1", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
var updated db.Media
gormDB.First(&updated, 1)
if updated.Title != "New Title" {
t.Errorf("Title = %q, want New Title", updated.Title)
}
}
func TestHandleAdminMediaByID_Delete(t *testing.T) {
deps, gormDB, _ := setupTestDeps(t)
// Create a real temp file for the media record
tmpDir := t.TempDir()
os.Setenv("MEDIA_DIR", tmpDir)
defer os.Unsetenv("MEDIA_DIR")
mediaPath := filepath.Join(tmpDir, "test.mp4")
os.WriteFile(mediaPath, []byte("fake video"), 0644)
gormDB.Create(&db.Media{
Filename: "test.mp4",
OriginalName: "test.mp4",
Path: mediaPath,
SizeBytes: 10,
DurationSeconds: 1,
Status: "ready",
Title: "Test",
})
handler := HandleAdminMediaByID(deps)
req := httptest.NewRequest("DELETE", "/api/admin/media/1", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
var count int64
gormDB.Model(&db.Media{}).Count(&count)
if count != 0 {
t.Errorf("media count after delete = %d, want 0", count)
}
}
func TestHandleAdminMediaUploads_Init(t *testing.T) {
deps, _, _ := setupTestDeps(t)
handler := HandleAdminMediaUploads(deps)
body := `{"fileName":"video.mp4","fileSize":1024,"mimeType":"video/mp4","chunkSize":524288,"totalChunks":1}`
req := httptest.NewRequest("POST", "/api/admin/media/uploads", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
var status AdminMediaUploadStatusResponse
json.NewDecoder(w.Body).Decode(&status)
if status.FileName != "video.mp4" {
t.Errorf("FileName = %q, want video.mp4", status.FileName)
}
if status.UploadID == "" {
t.Error("UploadID is empty")
}
}
func TestHandleAdminMediaUploads_InvalidFile(t *testing.T) {
deps, _, _ := setupTestDeps(t)
handler := HandleAdminMediaUploads(deps)
body := `{"fileName":"bad.exe","fileSize":1024,"mimeType":"application/x-msdownload","chunkSize":524288,"totalChunks":1}`
req := httptest.NewRequest("POST", "/api/admin/media/uploads", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("status = %d, want 400", w.Code)
}
}