package deploy_test import ( "context" "log/slog" "net/http" "net/http/httptest" "net/url" "os" "slices" "strings" "testing" "github.com/distribution/reference" "github.com/docker/docker/daemon/names" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "sneak.berlin/go/upaas/internal/database" "sneak.berlin/go/upaas/internal/globals" "sneak.berlin/go/upaas/internal/handlers" "sneak.berlin/go/upaas/internal/logger" "sneak.berlin/go/upaas/internal/models" "sneak.berlin/go/upaas/internal/service/app" ) // TestDeployAppWithDotInName creates an app named sneak.berlin through the // new app form, as a user does, then builds and deploys it against a fake // Docker API and checks that Docker accepts the names of the image it // builds and of the container it runs, using Docker's own rules for each. func TestDeployAppWithDotInName(t *testing.T) { t.Parallel() api := &fakeImageAPI{ images: map[string][]string{}, shortSHA: "abc1234", nextID: "sha256:built", } svc, db := newImageTestService(t, api) ctx := context.Background() createdApp := createAppWithForm(t, db, "sneak.berlin") deployment := models.NewDeployment(db) deployment.AppID = createdApp.ID require.NoError(t, deployment.Save(ctx)) imageID, err := svc.BuildImage(ctx, createdApp, deployment) require.NoError(t, err) require.NoError(t, svc.DeployContainer(ctx, createdApp, deployment, imageID)) images, _ := api.state() api.mu.Lock() containers := slices.Clone(api.containers) api.mu.Unlock() require.Equal(t, map[string][]string{ "sha256:built": {"upaas-sneak.berlin:abc1234"}, }, images) _, err = reference.ParseNormalizedNamed("upaas-sneak.berlin:abc1234") require.NoError(t, err) require.Equal(t, []string{"upaas-sneak.berlin"}, containers) assert.Regexp(t, names.RestrictedNamePattern, containers[0]) assert.DirExists(t, svc.GetBuildDirExported(createdApp.Name)) } // createAppWithForm posts the new app form with the given name, which // checks the name as it does for a user, and returns the app it created. func createAppWithForm( t *testing.T, db *database.Database, name string, ) *models.App { t.Helper() log := logger.NewForTest(slog.New(slog.NewTextHandler(os.Stderr, nil))) appSvc, err := app.New(nil, app.ServiceParams{Logger: log, Database: db}) require.NoError(t, err) globalInstance, err := globals.New(nil) require.NoError(t, err) handlersInstance, err := handlers.New(nil, handlers.Params{ Logger: log, Globals: globalInstance, Database: db, App: appSvc, }) require.NoError(t, err) form := url.Values{ "name": {name}, "repo_url": {"git@example.com:sneak/" + name + ".git"}, } request := httptest.NewRequestWithContext( t.Context(), http.MethodPost, "/apps", strings.NewReader(form.Encode()), ) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") recorder := httptest.NewRecorder() handlersInstance.HandleAppCreate().ServeHTTP(recorder, request) // The form redirects to the new app's page; it shows the form again, // with the error, when it refuses the name. require.Equal(t, http.StatusSeeOther, recorder.Code, recorder.Body.String()) appID, found := strings.CutPrefix(recorder.Header().Get("Location"), "/apps/") require.True(t, found) createdApp, err := models.FindApp(t.Context(), db, appID) require.NoError(t, err) require.NotNil(t, createdApp) return createdApp }