Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions internal/drivers/api_gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,3 +295,16 @@ func TestAPIGatewayDriver_HealthCheck_NoDeployment(t *testing.T) {
t.Errorf("message should contain 'no deployment', got: %q", result.Message)
}
}

func TestAPIGatewayDriver_HealthCheck_InProgress_UnknownPhase(t *testing.T) {
d := drivers.NewAPIGatewayDriverWithClient(&mockAPIGatewayClient{
app: gwAppWithPhases(nil, gwPhasePtr(godo.DeploymentPhase_Unknown), nil),
}, "nyc3")
result, _ := d.HealthCheck(context.Background(), interfaces.ResourceRef{Name: "phased-gw", ProviderID: "app-gw-999"})
if result.Healthy {
t.Error("expected Healthy=false for unknown phase")
}
if !strings.Contains(result.Message, "unknown phase") {
t.Errorf("message should contain 'unknown phase', got: %q", result.Message)
}
}
16 changes: 13 additions & 3 deletions internal/drivers/app_platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,11 @@ func (d *AppPlatformDriver) Update(ctx context.Context, ref interfaces.ResourceR
return nil, fmt.Errorf("app platform update %q: %w", ref.Name, WrapGodoError(err))
}
// Trigger a new deployment — Update only changes the spec; DO does not auto-deploy.
if _, _, err := d.client.CreateDeployment(ctx, ref.ProviderID, &godo.DeploymentCreateRequest{ForceBuild: true}); err != nil {
dep, _, err := d.client.CreateDeployment(ctx, ref.ProviderID, &godo.DeploymentCreateRequest{ForceBuild: true})
if err != nil {
return nil, fmt.Errorf("app platform create deployment %q: %w", ref.Name, WrapGodoError(err))
}
fmt.Printf(" app platform deploy %q: triggered deployment %s\n", spec.Name, dep.ID)
return appOutput(app), nil
}

Expand Down Expand Up @@ -203,12 +205,20 @@ func appHealthResult(app *godo.App) *interfaces.HealthResult {
Healthy: false,
Message: fmt.Sprintf("deployment in progress: %s", dep.Phase),
}
default:
// ERROR, CANCELED, SUPERSEDED, etc.
case godo.DeploymentPhase_Error,
godo.DeploymentPhase_Canceled,
godo.DeploymentPhase_Superseded:
return &interfaces.HealthResult{
Healthy: false,
Message: fmt.Sprintf("deployment failed: %s", dep.Phase),
}
default:
// Forward-compat: a future godo release may add new phases.
// Report "unknown" rather than "failed" to avoid mislabeling.
return &interfaces.HealthResult{
Healthy: false,
Message: fmt.Sprintf("unknown phase: %s", dep.Phase),
}
}
}

Expand Down
16 changes: 16 additions & 0 deletions internal/drivers/app_platform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ func TestAppPlatformDriver_Update_Error(t *testing.T) {
if err == nil {
t.Fatal("expected error, got nil")
}
if mock.createDeploymentCalled {
t.Error("CreateDeployment should not be called on Update failure")
}
}

func TestAppPlatformDriver_Update_TriggersCreateDeployment(t *testing.T) {
Expand Down Expand Up @@ -514,6 +517,19 @@ func TestAppPlatformDriver_HealthCheck_NoDeployment(t *testing.T) {
}
}

func TestAppPlatformDriver_HealthCheck_InProgress_UnknownPhase(t *testing.T) {
d := drivers.NewAppPlatformDriverWithClient(&mockAppClient{
app: appWithPhases(nil, phasePtr(godo.DeploymentPhase_Unknown), nil),
}, "nyc3")
result, _ := d.HealthCheck(context.Background(), interfaces.ResourceRef{Name: "phased-app", ProviderID: "app-999"})
if result.Healthy {
t.Error("expected Healthy=false for unknown phase")
}
if !strings.Contains(result.Message, "unknown phase") {
t.Errorf("message should contain 'unknown phase', got: %q", result.Message)
}
}

// ── ParseImageRef unit tests ──────────────────────────────────────────────────

func TestParseImageRef_DOCR(t *testing.T) {
Expand Down
9 changes: 8 additions & 1 deletion internal/drivers/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ import (
// sentinel so callers can use errors.Is for classification while still having
// access to the original DO API message via err.Error().
//
// If err is nil or not a *godo.ErrorResponse, it is returned unchanged.
// Two passthrough branches return err unchanged:
// - err is nil → return nil (no-op; safe to call unconditionally)
// - err is not a *godo.ErrorResponse, or its Response field is nil → return err
// (e.g. network errors, context cancellation, or SDK bugs; not DO API errors)
//
// HTTP codes with no sentinel mapping (e.g. 301, 400 handled elsewhere) also
// pass through unchanged via sentinelForStatus returning nil.
func WrapGodoError(err error) error {
if err == nil {
return nil
}
gErr, ok := err.(*godo.ErrorResponse)
if !ok || gErr.Response == nil {
// Not a DO API error — pass through so the original error is preserved.
return err
}
sentinel := sentinelForStatus(gErr.Response.StatusCode)
Expand Down
Loading