package deploy_test import ( "context" "log/slog" "net/http" "net/http/httptest" "os" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/fx/fxtest" "sneak.berlin/go/upaas/internal/config" "sneak.berlin/go/upaas/internal/database" "sneak.berlin/go/upaas/internal/docker" "sneak.berlin/go/upaas/internal/logger" "sneak.berlin/go/upaas/internal/models" "sneak.berlin/go/upaas/internal/service/deploy" ) // TestBuildImageLogsBuildErrorBeforeDeployError runs a build that fails // against a fake Docker API and checks that the deploy fails with the // build's own error, which the deployment log shows before the deploy's. func TestBuildImageLogsBuildErrorBeforeDeployError(t *testing.T) { t.Parallel() srv := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") switch { case strings.HasSuffix(r.URL.Path, "/containers/create"): _, _ = w.Write([]byte(`{"Id":"gitcontainer"}`)) case strings.HasSuffix(r.URL.Path, "/version"): _, _ = w.Write([]byte(`{"Version":"27.3.1","ApiVersion":"1.47"}`)) case strings.HasSuffix(r.URL.Path, "/build"): _, _ = w.Write([]byte(`{"stream":"Step 1/1 : RUN false\n"}` + "\n" + `{"errorDetail":{"message":"exit code: 1"},"error":"exit code: 1"}`)) default: // The steps of the git clone, which succeeds. _, _ = w.Write([]byte(`{}`)) } }, )) t.Cleanup(srv.Close) log := slog.New(slog.NewTextHandler(os.Stderr, nil)) lifecycle := fxtest.NewLifecycle(t) dockerClient, err := docker.New(lifecycle, docker.Params{ Logger: logger.NewForTest(log), Config: &config.Config{DockerHost: "tcp://" + srv.Listener.Addr().String()}, }) require.NoError(t, err) lifecycle.RequireStart() t.Cleanup(lifecycle.RequireStop) db := database.NewTestDatabase(t) ctx := context.Background() app := models.NewApp(db) app.ID = "buildapp-id" app.Name = "buildapp" app.Branch = "main" require.NoError(t, app.Save(ctx)) deployment := models.NewDeployment(db) deployment.AppID = app.ID require.NoError(t, deployment.Save(ctx)) dataDir := t.TempDir() cfg := &config.Config{DataDir: dataDir, HostDataDir: dataDir} // The service has no notify service: the app has no ntfy topic and no // Slack webhook, so the build failure notification sends nothing. svc := deploy.NewTestServiceWithConfig(log, cfg, dockerClient) _, err = svc.BuildImage(ctx, app, deployment) require.EqualError(t, err, "failed to build image: exit code: 1") logs := deployment.Logs.String buildError := strings.Index(logs, "ERROR: exit code: 1") deployError := strings.Index(logs, "ERROR: failed to build image: exit code: 1") require.NotEqual(t, -1, buildError, logs) assert.Less(t, buildError, deployError, logs) }