package boxstore import ( "archive/zip" "bytes" "os" "path/filepath" "testing" "time" "warpbox/lib/models" ) func TestStartRetentionWaitsForEveryFileToFinish(t *testing.T) { manifest := models.BoxManifest{ RetentionSecs: 10, Files: []models.BoxFile{ {ID: "one", Status: models.FileStatusReady}, {ID: "two", Status: models.FileStatusWork}, }, } startRetentionIfTerminalUnlocked(&manifest) if !manifest.ExpiresAt.IsZero() { t.Fatalf("expected retention to stay unset while a file is still uploading, got %s", manifest.ExpiresAt) } } func TestStartRetentionBeginsWhenEveryFileIsTerminal(t *testing.T) { manifest := models.BoxManifest{ RetentionSecs: 10, Files: []models.BoxFile{ {ID: "one", Status: models.FileStatusReady}, {ID: "two", Status: models.FileStatusFailed}, }, } before := time.Now().UTC() startRetentionIfTerminalUnlocked(&manifest) if manifest.ExpiresAt.IsZero() { t.Fatal("expected retention to start once every file is complete or failed") } if manifest.ExpiresAt.Before(before.Add(9 * time.Second)) { t.Fatalf("expected retention to start from completion time, got %s", manifest.ExpiresAt) } } func TestStartRetentionSkipsOneTimeDownload(t *testing.T) { manifest := models.BoxManifest{ RetentionSecs: 10, OneTimeDownload: true, Files: []models.BoxFile{ {ID: "one", Status: models.FileStatusReady}, {ID: "two", Status: models.FileStatusReady}, }, } startRetentionIfTerminalUnlocked(&manifest) if !manifest.ExpiresAt.IsZero() { t.Fatalf("expected one-time download box to avoid retention expiry, got %s", manifest.ExpiresAt) } } func TestSafeBoxFilePathRejectsTraversal(t *testing.T) { restoreUploadRoot := UploadRoot() defer SetUploadRoot(restoreUploadRoot) SetUploadRoot(t.TempDir()) boxID := "0123456789abcdef0123456789abcdef" if _, ok := SafeBoxFilePath(boxID, "../outside.txt"); ok { t.Fatal("expected traversal to be rejected") } if _, ok := SafeBoxFilePath("../bad", "file.txt"); ok { t.Fatal("expected invalid box id to be rejected") } } func TestAddFileToZipRejectsUnsafeManifestName(t *testing.T) { restoreUploadRoot := UploadRoot() defer SetUploadRoot(restoreUploadRoot) SetUploadRoot(t.TempDir()) var buffer bytes.Buffer zipWriter := zip.NewWriter(&buffer) if err := AddFileToZip(zipWriter, "0123456789abcdef0123456789abcdef", "../outside.txt"); err == nil { t.Fatal("expected unsafe zip filename to be rejected") } } func TestListFilesSkipsSymlinks(t *testing.T) { restoreUploadRoot := UploadRoot() defer SetUploadRoot(restoreUploadRoot) SetUploadRoot(t.TempDir()) boxID := "0123456789abcdef0123456789abcdef" if err := os.MkdirAll(BoxPath(boxID), 0755); err != nil { t.Fatalf("MkdirAll returned error: %v", err) } if err := os.WriteFile(filepath.Join(BoxPath(boxID), "safe.txt"), []byte("safe"), 0644); err != nil { t.Fatalf("WriteFile returned error: %v", err) } if err := os.Symlink(filepath.Join(BoxPath(boxID), "safe.txt"), filepath.Join(BoxPath(boxID), "link.txt")); err != nil { t.Skipf("symlink unavailable: %v", err) } files, err := ListFiles(boxID) if err != nil { t.Fatalf("ListFiles returned error: %v", err) } if len(files) != 1 || files[0].Name != "safe.txt" { t.Fatalf("expected only regular file, got %#v", files) } } func TestBoxPasswordUsesBcryptAndVerifiesLegacy(t *testing.T) { restoreUploadRoot := UploadRoot() defer SetUploadRoot(restoreUploadRoot) SetUploadRoot(t.TempDir()) boxID := "0123456789abcdef0123456789abcdef" if err := os.MkdirAll(BoxPath(boxID), 0755); err != nil { t.Fatalf("MkdirAll returned error: %v", err) } if _, err := CreateManifest(boxID, models.CreateBoxRequest{Password: "secret"}); err != nil { t.Fatalf("CreateManifest returned error: %v", err) } manifest, err := ReadManifest(boxID) if err != nil { t.Fatalf("ReadManifest returned error: %v", err) } if manifest.PasswordHashAlg != "bcrypt" { t.Fatalf("expected bcrypt password hash, got %q", manifest.PasswordHashAlg) } if !VerifyPassword(manifest, "secret") { t.Fatal("expected bcrypt password to verify") } legacy := models.BoxManifest{ PasswordSalt: "salt", PasswordHash: legacyPasswordHash("salt", "secret"), AuthToken: "token", } if !VerifyPassword(legacy, "secret") { t.Fatal("expected legacy password hash to verify") } }