1 Commits

Author SHA1 Message Date
clawbot
3c1525d59e test: add rollback error condition tests
Add tests for Rollback method error paths:
- No previous image available
- Empty previous image string
- App deployment lock held
- App lock already acquired

Relates to #71
2026-02-16 00:27:46 -08:00
6 changed files with 142 additions and 65 deletions

21
TODO.md
View File

@@ -53,8 +53,8 @@
- [x] Deployment logs storage - [x] Deployment logs storage
- [x] View deployment history per app - [x] View deployment history per app
- [x] Container logs viewing - [x] Container logs viewing
- [x] Deployment rollback to previous image - [ ] Deployment rollback to previous image
- [x] Deployment cancellation - [ ] Deployment cancellation
### Manual Container Controls ### Manual Container Controls
- [x] Restart container - [x] Restart container
@@ -116,9 +116,10 @@
- [x] Deployment history page - [x] Deployment history page
- [x] Login page - [x] Login page
- [x] Setup page - [x] Setup page
- [x] Container logs page - [ ] Container logs page
- [ ] Webhook event history page - [ ] Webhook event history page
- [ ] Settings page (webhook secret, SSH public key) - [ ] Settings page (webhook secret, SSH public key)
- [ ] Real-time deployment log streaming (WebSocket/SSE)
### Future Considerations ### Future Considerations
- [ ] Multi-user support with roles - [ ] Multi-user support with roles
@@ -200,18 +201,18 @@ Protected Routes (require auth):
- Validate paths before saving - Validate paths before saving
### 3.2 Deployment Rollback ### 3.2 Deployment Rollback
- [x] Add `previous_image_id` column to apps table - [ ] Add `previous_image_id` column to apps table
- Store last successful image ID before new deploy - Store last successful image ID before new deploy
- [x] Add `POST /apps/:id/rollback` endpoint - [ ] Add `POST /apps/:id/rollback` endpoint
- Stop current container - Stop current container
- Start container with previous image - Start container with previous image
- Create deployment record for rollback - Create deployment record for rollback
- [x] Update deploy service to save previous image before building new one - [ ] Update deploy service to save previous image before building new one
### 3.3 Deployment Cancellation ### 3.3 Deployment Cancellation
- [x] Add cancellation context to deploy service - [ ] Add cancellation context to deploy service
- [x] Add `POST /apps/:id/deployments/:id/cancel` endpoint - [ ] Add `POST /apps/:id/deployments/:id/cancel` endpoint
- [x] Handle cleanup of partial builds/containers - [ ] Handle cleanup of partial builds/containers
## Phase 4: Lower Priority (Nice to Have) ## Phase 4: Lower Priority (Nice to Have)
@@ -239,6 +240,8 @@ Protected Routes (require auth):
- [ ] Add settings page - [ ] Add settings page
- View/regenerate webhook secret - View/regenerate webhook secret
- View SSH public key - View SSH public key
- [ ] Add real-time deployment log streaming
- WebSocket or SSE for live build output
### 4.4 Observability ### 4.4 Observability
- [ ] Add structured logging for all operations - [ ] Add structured logging for all operations

View File

@@ -0,0 +1,74 @@
package deploy_test
import (
"context"
"database/sql"
"log/slog"
"testing"
"github.com/stretchr/testify/assert"
"git.eeqj.de/sneak/upaas/internal/models"
"git.eeqj.de/sneak/upaas/internal/service/deploy"
)
func TestRollback_NoPreviousImage(t *testing.T) {
t.Parallel()
svc := deploy.NewTestService(slog.Default())
app := &models.App{
ID: "app-rollback-1",
PreviousImageID: sql.NullString{},
}
err := svc.Rollback(context.Background(), app)
assert.ErrorIs(t, err, deploy.ErrNoPreviousImage)
}
func TestRollback_EmptyPreviousImage(t *testing.T) {
t.Parallel()
svc := deploy.NewTestService(slog.Default())
app := &models.App{
ID: "app-rollback-2",
PreviousImageID: sql.NullString{String: "", Valid: true},
}
err := svc.Rollback(context.Background(), app)
assert.ErrorIs(t, err, deploy.ErrNoPreviousImage)
}
func TestRollback_DeploymentLocked(t *testing.T) {
t.Parallel()
svc := deploy.NewTestService(slog.Default())
// Simulate a deploy holding the lock
assert.True(t, svc.TryLockApp("app-rollback-3"))
defer svc.UnlockApp("app-rollback-3")
app := &models.App{
ID: "app-rollback-3",
PreviousImageID: sql.NullString{String: "sha256:abc123", Valid: true},
}
err := svc.Rollback(context.Background(), app)
assert.ErrorIs(t, err, deploy.ErrDeploymentInProgress)
}
func TestRollback_LockedApp(t *testing.T) {
t.Parallel()
svc := deploy.NewTestService(slog.Default())
assert.True(t, svc.TryLockApp("app-rollback-4"))
defer svc.UnlockApp("app-rollback-4")
app := &models.App{
ID: "app-rollback-4",
PreviousImageID: sql.NullString{String: "sha256:abc123", Valid: true},
}
err := svc.Rollback(context.Background(), app)
assert.ErrorIs(t, err, deploy.ErrDeploymentInProgress)
}