Coverage priorities 1-5: test pure functions, extract interfaces, mock-based handler tests

- Priority 1: Test NewSendEmailTask + NewSendPushTask (5 tests)
- Priority 2: Test customHTTPErrorHandler — all 15+ branches (21 tests)
- Priority 3: Extract Enqueuer interface + payload builders in worker pkg (5 tests)
- Priority 4: Extract ClassifyFile/ComputeRelPath in migrate-encrypt (6 tests)
- Priority 5: Define Handler interfaces, refactor to accept them, mock-based tests (14 tests)
- Fix .gitignore: /worker instead of worker to stop ignoring internal/worker/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-04-01 20:30:09 -05:00
parent 00fd674b56
commit bec880886b
83 changed files with 19569 additions and 730 deletions

View File

@@ -0,0 +1,50 @@
package main
import (
"path/filepath"
"strings"
)
// isEncrypted checks if a file path ends with .enc
func isEncrypted(path string) bool {
return strings.HasSuffix(path, ".enc")
}
// encryptedPath appends .enc to the file path.
func encryptedPath(path string) string {
return path + ".enc"
}
// shouldProcessFile returns true if the file should be encrypted.
func shouldProcessFile(isDir bool, path string) bool {
return !isDir && !isEncrypted(path)
}
// FileAction represents the decision about what to do with a file during encryption migration.
type FileAction int
const (
ActionSkipDir FileAction = iota // Directory, skip
ActionSkipEncrypted // Already encrypted, skip
ActionDryRun // Would encrypt (dry run mode)
ActionEncrypt // Should encrypt
)
// ClassifyFile determines what action to take for a file during the walk.
func ClassifyFile(isDir bool, path string, dryRun bool) FileAction {
if isDir {
return ActionSkipDir
}
if isEncrypted(path) {
return ActionSkipEncrypted
}
if dryRun {
return ActionDryRun
}
return ActionEncrypt
}
// ComputeRelPath computes the relative path from base to path.
func ComputeRelPath(base, path string) (string, error) {
return filepath.Rel(base, path)
}

View File

@@ -0,0 +1,96 @@
package main
import "testing"
func TestIsEncrypted_EncFile_True(t *testing.T) {
if !isEncrypted("photo.jpg.enc") {
t.Error("expected true for .enc file")
}
}
func TestIsEncrypted_PdfFile_False(t *testing.T) {
if isEncrypted("doc.pdf") {
t.Error("expected false for .pdf file")
}
}
func TestIsEncrypted_DotEncOnly_True(t *testing.T) {
if !isEncrypted(".enc") {
t.Error("expected true for '.enc'")
}
}
func TestEncryptedPath_AppendsDotEnc(t *testing.T) {
got := encryptedPath("uploads/photo.jpg")
want := "uploads/photo.jpg.enc"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
func TestShouldProcessFile_RegularFile_True(t *testing.T) {
if !shouldProcessFile(false, "photo.jpg") {
t.Error("expected true for regular file")
}
}
func TestShouldProcessFile_Directory_False(t *testing.T) {
if shouldProcessFile(true, "uploads") {
t.Error("expected false for directory")
}
}
func TestShouldProcessFile_AlreadyEncrypted_False(t *testing.T) {
if shouldProcessFile(false, "photo.jpg.enc") {
t.Error("expected false for already encrypted file")
}
}
// --- ClassifyFile ---
func TestClassifyFile_Directory_SkipDir(t *testing.T) {
if got := ClassifyFile(true, "uploads", false); got != ActionSkipDir {
t.Errorf("got %d, want ActionSkipDir", got)
}
}
func TestClassifyFile_EncryptedFile_SkipEncrypted(t *testing.T) {
if got := ClassifyFile(false, "photo.jpg.enc", false); got != ActionSkipEncrypted {
t.Errorf("got %d, want ActionSkipEncrypted", got)
}
}
func TestClassifyFile_DryRun_DryRun(t *testing.T) {
if got := ClassifyFile(false, "photo.jpg", true); got != ActionDryRun {
t.Errorf("got %d, want ActionDryRun", got)
}
}
func TestClassifyFile_Normal_Encrypt(t *testing.T) {
if got := ClassifyFile(false, "photo.jpg", false); got != ActionEncrypt {
t.Errorf("got %d, want ActionEncrypt", got)
}
}
// --- ComputeRelPath ---
func TestComputeRelPath_Valid(t *testing.T) {
got, err := ComputeRelPath("/uploads", "/uploads/photo.jpg")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != "photo.jpg" {
t.Errorf("got %q, want %q", got, "photo.jpg")
}
}
func TestComputeRelPath_NestedPath(t *testing.T) {
got, err := ComputeRelPath("/uploads", "/uploads/2024/01/photo.jpg")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
want := "2024/01/photo.jpg"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}

View File

@@ -13,7 +13,6 @@ import (
"flag"
"os"
"path/filepath"
"strings"
"time"
"github.com/rs/zerolog"
@@ -87,13 +86,11 @@ func main() {
return nil
}
// Skip directories
if info.IsDir() {
action := ClassifyFile(info.IsDir(), path, *dryRun)
switch action {
case ActionSkipDir:
return nil
}
// Skip files already encrypted
if strings.HasSuffix(path, ".enc") {
case ActionSkipEncrypted:
skipped++
return nil
}
@@ -101,14 +98,14 @@ func main() {
totalFiles++
// Compute the relative path from upload dir
relPath, err := filepath.Rel(absUploadDir, path)
relPath, err := ComputeRelPath(absUploadDir, path)
if err != nil {
log.Warn().Err(err).Str("path", path).Msg("Failed to compute relative path")
errCount++
return nil
}
if *dryRun {
if action == ActionDryRun {
log.Info().Str("file", relPath).Msg("[DRY RUN] Would encrypt")
return nil
}