package server import ( "archive/zip" "bytes" "encoding/json" "net/http" "net/http/httptest" "os" "testing" "time" "github.com/gin-gonic/gin" "warpbox/lib/boxstore" "warpbox/lib/config" "warpbox/lib/models" ) const oneTimeTestBoxID = "0123456789abcdef0123456789abcdef" func TestOneTimeDownloadNotReadyDoesNotConsume(t *testing.T) { app := setupOneTimeDownloadTest(t, false) writeOneTimeManifest(t, models.FileStatusWork, false) response := performOneTimeDownload(app) if response.Code != http.StatusConflict { t.Fatalf("expected not-ready download to return 409, got %d", response.Code) } manifest, err := boxstore.ReadManifest(oneTimeTestBoxID) if err != nil { t.Fatalf("ReadManifest returned error: %v", err) } if manifest.Consumed { t.Fatal("expected not-ready box to remain unconsumed") } } func TestOneTimeDownloadReadyConsumesAndDeletes(t *testing.T) { app := setupOneTimeDownloadTest(t, false) writeOneTimeManifest(t, models.FileStatusReady, true) response := performOneTimeDownload(app) if response.Code != http.StatusOK { t.Fatalf("expected ready download to return 200, got %d", response.Code) } if _, err := zip.NewReader(bytes.NewReader(response.Body.Bytes()), int64(response.Body.Len())); err != nil { t.Fatalf("expected valid zip body: %v", err) } if _, err := os.Stat(boxstore.BoxPath(oneTimeTestBoxID)); !os.IsNotExist(err) { t.Fatalf("expected consumed box to be deleted, stat err=%v", err) } } func TestOneTimeDownloadWriterFailureConsumesByDefault(t *testing.T) { app := setupOneTimeDownloadTest(t, false) writeOneTimeManifest(t, models.FileStatusReady, false) response := performOneTimeDownload(app) if response.Code != http.StatusInternalServerError { t.Fatalf("expected failed ZIP to return 500, got %d", response.Code) } if _, err := os.Stat(boxstore.BoxPath(oneTimeTestBoxID)); !os.IsNotExist(err) { t.Fatalf("expected failed ZIP to delete box by default, stat err=%v", err) } } func TestOneTimeDownloadWriterFailureCanRemainRetryable(t *testing.T) { app := setupOneTimeDownloadTest(t, true) writeOneTimeManifest(t, models.FileStatusReady, false) response := performOneTimeDownload(app) if response.Code != http.StatusInternalServerError { t.Fatalf("expected failed ZIP to return 500, got %d", response.Code) } manifest, err := boxstore.ReadManifest(oneTimeTestBoxID) if err != nil { t.Fatalf("ReadManifest returned error: %v", err) } if manifest.Consumed { t.Fatal("expected failed retryable ZIP to remain unconsumed") } } func TestOneTimeDownloadSecondAccessAfterConsumeIsGone(t *testing.T) { app := setupOneTimeDownloadTest(t, false) writeOneTimeManifest(t, models.FileStatusReady, true) manifest, err := boxstore.ReadManifest(oneTimeTestBoxID) if err != nil { t.Fatalf("ReadManifest returned error: %v", err) } manifest.Consumed = true if err := boxstore.WriteManifest(oneTimeTestBoxID, manifest); err != nil { t.Fatalf("WriteManifest returned error: %v", err) } response := performOneTimeDownload(app) if response.Code != http.StatusGone { t.Fatalf("expected consumed download to return 410, got %d", response.Code) } } func TestOneTimeStatusStripsThumbnailPath(t *testing.T) { app := setupOneTimeDownloadTest(t, false) app.config.APIEnabled = true writeOneTimeManifest(t, models.FileStatusReady, true) manifest, err := boxstore.ReadManifest(oneTimeTestBoxID) if err != nil { t.Fatalf("ReadManifest returned error: %v", err) } thumbnailPath := "/box/" + oneTimeTestBoxID + "/thumbnails/0123456789abcdef" manifest.Files[0].ThumbnailPath = &thumbnailPath manifest.Files[0].ThumbnailStatus = models.ThumbnailStatusReady if err := boxstore.WriteManifest(oneTimeTestBoxID, manifest); err != nil { t.Fatalf("WriteManifest returned error: %v", err) } response := performOneTimeStatus(app) if response.Code != http.StatusOK { t.Fatalf("expected status to return 200, got %d", response.Code) } var payload struct { Files []models.BoxFile `json:"files"` } if err := json.Unmarshal(response.Body.Bytes(), &payload); err != nil { t.Fatalf("json.Unmarshal returned error: %v", err) } if len(payload.Files) != 1 { t.Fatalf("expected one file, got %#v", payload.Files) } if payload.Files[0].ThumbnailPath != nil { t.Fatalf("expected one-time status to strip thumbnail path, got %q", *payload.Files[0].ThumbnailPath) } } func TestRuntimeConfigAppliesDBOneTimeExpiryOverride(t *testing.T) { restoreExpiry := boxstore.OneTimeDownloadExpiry() defer boxstore.SetOneTimeDownloadExpiry(restoreExpiry) cfg, err := config.Load() if err != nil { t.Fatalf("Load returned error: %v", err) } if err := cfg.ApplyOverrides(map[string]string{config.SettingOneTimeDownloadExpirySecs: "42"}); err != nil { t.Fatalf("ApplyOverrides returned error: %v", err) } applyBoxstoreRuntimeConfig(cfg) if got := boxstore.OneTimeDownloadExpiry(); got != 42 { t.Fatalf("expected runtime one-time expiry to be updated from config, got %d", got) } } func setupOneTimeDownloadTest(t *testing.T, retryOnFailure bool) *App { t.Helper() gin.SetMode(gin.TestMode) restoreUploadRoot := boxstore.UploadRoot() t.Cleanup(func() { boxstore.SetUploadRoot(restoreUploadRoot) }) boxstore.SetUploadRoot(t.TempDir()) return &App{config: &config.Config{ ZipDownloadsEnabled: true, OneTimeDownloadRetryOnFailure: retryOnFailure, }} } func writeOneTimeManifest(t *testing.T, status string, createFile bool) { t.Helper() if err := os.MkdirAll(boxstore.BoxPath(oneTimeTestBoxID), 0755); err != nil { t.Fatalf("MkdirAll returned error: %v", err) } if createFile { path, ok := boxstore.SafeBoxFilePath(oneTimeTestBoxID, "file.txt") if !ok { t.Fatal("SafeBoxFilePath rejected test file") } if err := os.WriteFile(path, []byte("hello"), 0644); err != nil { t.Fatalf("WriteFile returned error: %v", err) } } manifest := models.BoxManifest{ Files: []models.BoxFile{{ ID: "0123456789abcdef", Name: "file.txt", Size: 5, MimeType: "text/plain", Status: status, }}, CreatedAt: time.Now().UTC(), OneTimeDownload: true, } if err := boxstore.WriteManifest(oneTimeTestBoxID, manifest); err != nil { t.Fatalf("WriteManifest returned error: %v", err) } } func performOneTimeDownload(app *App) *httptest.ResponseRecorder { router := gin.New() router.GET("/box/:id/download", app.handleDownloadBox) request := httptest.NewRequest(http.MethodGet, "/box/"+oneTimeTestBoxID+"/download", nil) response := httptest.NewRecorder() router.ServeHTTP(response, request) return response } func performOneTimeStatus(app *App) *httptest.ResponseRecorder { router := gin.New() router.GET("/box/:id/status", app.handleBoxStatus) request := httptest.NewRequest(http.MethodGet, "/box/"+oneTimeTestBoxID+"/status", nil) response := httptest.NewRecorder() router.ServeHTTP(response, request) return response }