package api import ( "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "time" "jiggablend/internal/config" "jiggablend/internal/storage" ) func TestCheckWebSocketOrigin_DevelopmentAllowsOrigin(t *testing.T) { t.Setenv("PRODUCTION", "") s := &Manager{cfg: &config.Config{}} req := httptest.NewRequest("GET", "http://localhost/ws", nil) req.Host = "localhost:8080" req.Header.Set("Origin", "http://example.com") if !s.checkWebSocketOrigin(req) { t.Fatal("expected development mode to allow origin") } } func TestCheckWebSocketOrigin_ProductionSameHostAllowed(t *testing.T) { t.Setenv("PRODUCTION", "true") t.Setenv("ALLOWED_ORIGINS", "") s := &Manager{cfg: &config.Config{}} req := httptest.NewRequest("GET", "http://localhost/ws", nil) req.Host = "localhost:8080" req.Header.Set("Origin", "http://localhost:8080") if !s.checkWebSocketOrigin(req) { t.Fatal("expected same-host origin to be allowed") } } func TestRespondErrorWithCode_IncludesCodeField(t *testing.T) { s := &Manager{} rr := httptest.NewRecorder() s.respondErrorWithCode(rr, http.StatusBadRequest, "UPLOAD_SESSION_EXPIRED", "Upload session expired.") if rr.Code != http.StatusBadRequest { t.Fatalf("status = %d, want %d", rr.Code, http.StatusBadRequest) } var payload map[string]string if err := json.Unmarshal(rr.Body.Bytes(), &payload); err != nil { t.Fatalf("failed to decode response: %v", err) } if payload["code"] != "UPLOAD_SESSION_EXPIRED" { t.Fatalf("unexpected code: %q", payload["code"]) } if payload["error"] == "" { t.Fatal("expected non-empty error message") } } func TestSecurityHeadersMiddleware_SetsBaselineHeaders(t *testing.T) { h := securityHeadersMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })) rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/", nil) h.ServeHTTP(rr, req) if rr.Header().Get("X-Content-Type-Options") != "nosniff" { t.Fatalf("missing nosniff, got %q", rr.Header().Get("X-Content-Type-Options")) } if rr.Header().Get("X-Frame-Options") != "DENY" { t.Fatalf("missing frame deny") } if rr.Header().Get("Content-Security-Policy") == "" { t.Fatal("missing CSP") } } func TestCleanupOldTempDirectories_SkipsActiveSessionTempDir(t *testing.T) { base := t.TempDir() st, err := storage.NewStorage(base) if err != nil { t.Fatalf("NewStorage: %v", err) } tempPath := filepath.Join(base, "temp") activeDir, err := os.MkdirTemp(tempPath, "jiggablend-upload-*") if err != nil { t.Fatal(err) } oldTime := time.Now().Add(-2 * time.Hour) _ = os.Chtimes(activeDir, oldTime, oldTime) staleDir := filepath.Join(tempPath, "stale-old-dir") if err := os.MkdirAll(staleDir, 0755); err != nil { t.Fatal(err) } _ = os.Chtimes(staleDir, oldTime, oldTime) s := &Manager{ storage: st, uploadSessions: map[string]*UploadSession{ "active": {SessionID: "active", TempDir: activeDir}, }, } s.cleanupOldTempDirectoriesOnce() if _, err := os.Stat(activeDir); err != nil { t.Fatalf("active session temp dir was deleted: %v", err) } if _, err := os.Stat(staleDir); !os.IsNotExist(err) { t.Fatalf("stale temp dir should have been removed, stat err=%v", err) } } func TestCreateSessionCookie_UsesConfigProductionMode(t *testing.T) { t.Setenv("PRODUCTION", "true") s := &Manager{cfg: &config.Config{}, sessionCookieMaxAge: 3600} cookie := s.createSessionCookie("sid") if !cookie.Secure { t.Fatal("expected Secure cookie when production mode is on via env") } }