package services import ( "bytes" "os" "path/filepath" "testing" "github.com/treytartt/honeydue-api/internal/config" ) func setupTestStorage(t *testing.T, encrypt bool) (*StorageService, string) { t.Helper() tmpDir := t.TempDir() cfg := &config.StorageConfig{ UploadDir: tmpDir, BaseURL: "/uploads", MaxFileSize: 10 * 1024 * 1024, AllowedTypes: "image/jpeg,image/png,application/pdf", } svc, err := NewStorageService(cfg) if err != nil { t.Fatalf("failed to create storage service: %v", err) } if encrypt { encSvc, err := NewEncryptionService(validTestKey()) if err != nil { t.Fatalf("failed to create encryption service: %v", err) } svc.SetEncryptionService(encSvc) } return svc, tmpDir } func TestReadFile_PlainFile(t *testing.T) { svc, tmpDir := setupTestStorage(t, false) // Write a plain file content := []byte("hello world") dir := filepath.Join(tmpDir, "images") if err := os.WriteFile(filepath.Join(dir, "test.jpg"), content, 0644); err != nil { t.Fatal(err) } data, mimeType, err := svc.ReadFile("/uploads/images/test.jpg") if err != nil { t.Fatalf("ReadFile failed: %v", err) } if !bytes.Equal(data, content) { t.Fatalf("data mismatch: got %q, want %q", data, content) } if mimeType == "" { t.Fatal("expected non-empty MIME type") } } func TestReadFile_EncryptedFile(t *testing.T) { svc, tmpDir := setupTestStorage(t, true) // Encrypt and write a file manually (simulating what Upload does) originalContent := []byte("sensitive document content here - must be long enough for detection") encSvc, _ := NewEncryptionService(validTestKey()) encrypted, err := encSvc.Encrypt(originalContent) if err != nil { t.Fatal(err) } dir := filepath.Join(tmpDir, "documents") if err := os.WriteFile(filepath.Join(dir, "test.pdf.enc"), encrypted, 0644); err != nil { t.Fatal(err) } // ReadFile should find the .enc file and decrypt it data, _, err := svc.ReadFile("/uploads/documents/test.pdf") if err != nil { t.Fatalf("ReadFile failed: %v", err) } if !bytes.Equal(data, originalContent) { t.Fatalf("decrypted data mismatch: got %q, want %q", data, originalContent) } } func TestReadFile_EncFilePreferredOverPlain(t *testing.T) { svc, tmpDir := setupTestStorage(t, true) encSvc, _ := NewEncryptionService(validTestKey()) plainContent := []byte("plain version") encContent := []byte("encrypted version - the correct one") encrypted, _ := encSvc.Encrypt(encContent) dir := filepath.Join(tmpDir, "images") os.WriteFile(filepath.Join(dir, "photo.jpg"), plainContent, 0644) os.WriteFile(filepath.Join(dir, "photo.jpg.enc"), encrypted, 0644) // Should prefer the .enc file data, _, err := svc.ReadFile("/uploads/images/photo.jpg") if err != nil { t.Fatalf("ReadFile failed: %v", err) } if !bytes.Equal(data, encContent) { t.Fatalf("expected encrypted version content, got %q", data) } } func TestReadFile_EmptyURL(t *testing.T) { svc, _ := setupTestStorage(t, false) _, _, err := svc.ReadFile("") if err == nil { t.Fatal("expected error for empty URL") } } func TestReadFile_MissingFile(t *testing.T) { svc, _ := setupTestStorage(t, false) _, _, err := svc.ReadFile("/uploads/images/nonexistent.jpg") if err == nil { t.Fatal("expected error for missing file") } } func TestDelete_HandlesEncAndPlain(t *testing.T) { svc, tmpDir := setupTestStorage(t, false) dir := filepath.Join(tmpDir, "images") // Create both plain and .enc files os.WriteFile(filepath.Join(dir, "photo.jpg"), []byte("plain"), 0644) os.WriteFile(filepath.Join(dir, "photo.jpg.enc"), []byte("encrypted"), 0644) err := svc.Delete("/uploads/images/photo.jpg") if err != nil { t.Fatalf("Delete failed: %v", err) } // Both should be gone if _, err := os.Stat(filepath.Join(dir, "photo.jpg")); !os.IsNotExist(err) { t.Fatal("plain file should be deleted") } if _, err := os.Stat(filepath.Join(dir, "photo.jpg.enc")); !os.IsNotExist(err) { t.Fatal("encrypted file should be deleted") } } func TestDelete_NonexistentFile(t *testing.T) { svc, _ := setupTestStorage(t, false) // Should not error for non-existent files err := svc.Delete("/uploads/images/nope.jpg") if err != nil { t.Fatalf("Delete should not error for non-existent file: %v", err) } }