Merge branch 'master' of github.com:akatreyt/MyCribAPI_GO
Some checks failed
Some checks failed
This commit is contained in:
61
cmd/backfill-completion-columns/main_test.go
Normal file
61
cmd/backfill-completion-columns/main_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestClassifyCompletion_CompletedAfterDue_ReturnsOverdue(t *testing.T) {
|
||||
due := time.Date(2025, 6, 1, 0, 0, 0, 0, time.UTC)
|
||||
completed := time.Date(2025, 6, 5, 14, 0, 0, 0, time.UTC)
|
||||
got := classifyCompletion(completed, due, 7)
|
||||
if got != "overdue_tasks" {
|
||||
t.Errorf("got %q, want overdue_tasks", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifyCompletion_CompletedOnDueDate_ReturnsDueSoon(t *testing.T) {
|
||||
due := time.Date(2025, 6, 1, 0, 0, 0, 0, time.UTC)
|
||||
completed := time.Date(2025, 6, 1, 10, 0, 0, 0, time.UTC)
|
||||
got := classifyCompletion(completed, due, 7)
|
||||
if got != "due_soon_tasks" {
|
||||
t.Errorf("got %q, want due_soon_tasks", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifyCompletion_CompletedWithinThreshold_ReturnsDueSoon(t *testing.T) {
|
||||
due := time.Date(2025, 6, 10, 0, 0, 0, 0, time.UTC)
|
||||
completed := time.Date(2025, 6, 5, 0, 0, 0, 0, time.UTC) // 5 days before due, threshold 7
|
||||
got := classifyCompletion(completed, due, 7)
|
||||
if got != "due_soon_tasks" {
|
||||
t.Errorf("got %q, want due_soon_tasks", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifyCompletion_CompletedAtExactThreshold_ReturnsDueSoon(t *testing.T) {
|
||||
due := time.Date(2025, 6, 10, 0, 0, 0, 0, time.UTC)
|
||||
completed := time.Date(2025, 6, 3, 0, 0, 0, 0, time.UTC) // exactly 7 days before due
|
||||
got := classifyCompletion(completed, due, 7)
|
||||
if got != "due_soon_tasks" {
|
||||
t.Errorf("got %q, want due_soon_tasks", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifyCompletion_CompletedBeyondThreshold_ReturnsUpcoming(t *testing.T) {
|
||||
due := time.Date(2025, 6, 30, 0, 0, 0, 0, time.UTC)
|
||||
completed := time.Date(2025, 6, 1, 0, 0, 0, 0, time.UTC) // 29 days before due, threshold 7
|
||||
got := classifyCompletion(completed, due, 7)
|
||||
if got != "upcoming_tasks" {
|
||||
t.Errorf("got %q, want upcoming_tasks", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifyCompletion_TimeNormalization_SameDayDifferentTimes(t *testing.T) {
|
||||
due := time.Date(2025, 6, 1, 23, 59, 59, 0, time.UTC)
|
||||
completed := time.Date(2025, 6, 1, 0, 0, 1, 0, time.UTC) // same day, different times
|
||||
got := classifyCompletion(completed, due, 7)
|
||||
// Same day → daysBefore == 0 → within threshold → due_soon
|
||||
if got != "due_soon_tasks" {
|
||||
t.Errorf("got %q, want due_soon_tasks", got)
|
||||
}
|
||||
}
|
||||
50
cmd/migrate-encrypt/helpers.go
Normal file
50
cmd/migrate-encrypt/helpers.go
Normal 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)
|
||||
}
|
||||
96
cmd/migrate-encrypt/helpers_test.go
Normal file
96
cmd/migrate-encrypt/helpers_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
24
cmd/worker/startup.go
Normal file
24
cmd/worker/startup.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import "github.com/treytartt/honeydue-api/internal/worker/jobs"
|
||||
|
||||
// queuePriorities returns the Asynq queue priority map.
|
||||
func queuePriorities() map[string]int {
|
||||
return map[string]int{
|
||||
"critical": 6,
|
||||
"default": 3,
|
||||
"low": 1,
|
||||
}
|
||||
}
|
||||
|
||||
// allJobTypes returns all registered job type strings.
|
||||
func allJobTypes() []string {
|
||||
return []string{
|
||||
jobs.TypeSmartReminder,
|
||||
jobs.TypeDailyDigest,
|
||||
jobs.TypeSendEmail,
|
||||
jobs.TypeSendPush,
|
||||
jobs.TypeOnboardingEmails,
|
||||
jobs.TypeReminderLogCleanup,
|
||||
}
|
||||
}
|
||||
45
cmd/worker/startup_test.go
Normal file
45
cmd/worker/startup_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestQueuePriorities_CriticalHighest(t *testing.T) {
|
||||
p := queuePriorities()
|
||||
if p["critical"] <= p["default"] || p["critical"] <= p["low"] {
|
||||
t.Errorf("critical (%d) should be highest", p["critical"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueuePriorities_ThreeQueues(t *testing.T) {
|
||||
p := queuePriorities()
|
||||
if len(p) != 3 {
|
||||
t.Errorf("len = %d, want 3", len(p))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllJobTypes_Count(t *testing.T) {
|
||||
types := allJobTypes()
|
||||
if len(types) != 6 {
|
||||
t.Errorf("len = %d, want 6", len(types))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllJobTypes_NoDuplicates(t *testing.T) {
|
||||
types := allJobTypes()
|
||||
seen := make(map[string]bool)
|
||||
for _, typ := range types {
|
||||
if seen[typ] {
|
||||
t.Errorf("duplicate job type: %q", typ)
|
||||
}
|
||||
seen[typ] = true
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllJobTypes_AllNonEmpty(t *testing.T) {
|
||||
for _, typ := range allJobTypes() {
|
||||
if typ == "" {
|
||||
t.Error("found empty job type")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user