fix: use strings.HasPrefix and delegate export to real cleanup method

- Replace manual string prefix check with strings.HasPrefix (idiomatic Go)
- Refactor CleanupCancelledDeploy export to call the real cleanupCancelledDeploy
  method instead of re-implementing the logic, so tests catch regressions
- Add nil-db guard in Deployment.Save and App.Save to prevent panics when
  models are used without a database connection (e.g. in test exports)
- Fix wsl_v5 lint issue (missing blank line before assignment)
This commit is contained in:
clawbot 2026-02-19 20:17:06 -08:00
parent 729425132b
commit 25cd02e1d7
4 changed files with 17 additions and 19 deletions

View File

@ -62,6 +62,10 @@ func NewApp(db *database.Database) *App {
// Save inserts or updates the app in the database. // Save inserts or updates the app in the database.
func (a *App) Save(ctx context.Context) error { func (a *App) Save(ctx context.Context) error {
if a.db == nil {
return fmt.Errorf("no database connection")
}
if a.exists(ctx) { if a.exists(ctx) {
return a.update(ctx) return a.update(ctx)
} }

View File

@ -57,6 +57,10 @@ func NewDeployment(db *database.Database) *Deployment {
// Save inserts or updates the deployment in the database. // Save inserts or updates the deployment in the database.
func (d *Deployment) Save(ctx context.Context) error { func (d *Deployment) Save(ctx context.Context) error {
if d.db == nil {
return fmt.Errorf("no database connection")
}
if d.ID == 0 { if d.ID == 0 {
return d.insert(ctx) return d.insert(ctx)
} }

View File

@ -11,6 +11,7 @@ import (
"log/slog" "log/slog"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"sync" "sync"
"time" "time"
@ -715,7 +716,7 @@ func (svc *Service) cleanupCancelledDeploy(
prefix := fmt.Sprintf("%d-", deployment.ID) prefix := fmt.Sprintf("%d-", deployment.ID)
for _, entry := range entries { for _, entry := range entries {
if entry.IsDir() && len(entry.Name()) > len(prefix) && entry.Name()[:len(prefix)] == prefix { if entry.IsDir() && strings.HasPrefix(entry.Name(), prefix) {
dirPath := filepath.Join(buildDir, entry.Name()) dirPath := filepath.Join(buildDir, entry.Name())
removeErr := os.RemoveAll(dirPath) removeErr := os.RemoveAll(dirPath)
@ -725,6 +726,7 @@ func (svc *Service) cleanupCancelledDeploy(
} else { } else {
svc.log.Info("cleaned up build dir from cancelled deploy", svc.log.Info("cleaned up build dir from cancelled deploy",
"app", app.Name, "path", dirPath) "app", app.Name, "path", dirPath)
_ = deployment.AppendLog(ctx, "Cleaned up build directory") _ = deployment.AppendLog(ctx, "Cleaned up build directory")
} }
} }

View File

@ -2,13 +2,11 @@ package deploy
import ( import (
"context" "context"
"fmt"
"log/slog" "log/slog"
"os"
"path/filepath"
"git.eeqj.de/sneak/upaas/internal/config" "git.eeqj.de/sneak/upaas/internal/config"
"git.eeqj.de/sneak/upaas/internal/docker" "git.eeqj.de/sneak/upaas/internal/docker"
"git.eeqj.de/sneak/upaas/internal/models"
) )
// NewTestService creates a Service with minimal dependencies for testing. // NewTestService creates a Service with minimal dependencies for testing.
@ -54,23 +52,13 @@ func (svc *Service) CleanupCancelledDeploy(
deploymentID int64, deploymentID int64,
imageID string, imageID string,
) { ) {
// We can't create real models.App/Deployment in tests easily, app := models.NewApp(nil)
// so we test the build dir cleanup portion directly. app.Name = appName
buildDir := svc.GetBuildDir(appName)
entries, err := os.ReadDir(buildDir) deployment := models.NewDeployment(nil)
if err != nil { deployment.ID = deploymentID
return
}
prefix := fmt.Sprintf("%d-", deploymentID) svc.cleanupCancelledDeploy(ctx, app, deployment, imageID)
for _, entry := range entries {
if entry.IsDir() && len(entry.Name()) > len(prefix) && entry.Name()[:len(prefix)] == prefix {
dirPath := filepath.Join(buildDir, entry.Name())
_ = os.RemoveAll(dirPath)
}
}
} }
// GetBuildDirExported exposes GetBuildDir for testing. // GetBuildDirExported exposes GetBuildDir for testing.