diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 6739da0..e973f86 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -4,6 +4,15 @@ import { defineConfig } from 'vite'; export default defineConfig({ plugins: [react()], + // Keep Vitest's dependency cache separate from the browser test server. + cacheDir: process.env.VITE_E2E === '1' ? 'node_modules/.vite-e2e' : undefined, + optimizeDeps: process.env.VITE_E2E === '1' ? { + // Lazy routes and workers must be scanned before browsers cache React chunks. + // Late discovery otherwise mixes cached and newly optimized React instances + // across full document navigations when HMR is disabled. + entries: ['index.html', 'src/**/*.{ts,tsx}', '!src/**/*.test.{ts,tsx}', '!src/**/*.d.ts', '!src/test/**'], + include: ['react', 'react-dom', 'react-dom/client', 'react/jsx-runtime', 'react/jsx-dev-runtime'], + } : undefined, build: { target: 'es2022', }, diff --git a/services/api/cmd/api/e2e_test.go b/services/api/cmd/api/e2e_test.go index 744292e..a4b5932 100644 --- a/services/api/cmd/api/e2e_test.go +++ b/services/api/cmd/api/e2e_test.go @@ -26,6 +26,7 @@ import ( "time" "github.com/google/uuid" + "github.com/jackc/pgx/v5" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/require" @@ -120,6 +121,7 @@ func TestUserJourney(t *testing.T) { if dbURL == "" { t.Skip("TEST_DATABASE_URL is not set") } + dbURL = userJourneyDatabase(t, dbURL) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -133,6 +135,7 @@ func TestUserJourney(t *testing.T) { t.Setenv("DATABASE_URL", dbURL) t.Setenv("REDIS_URL", os.Getenv("TEST_REDIS_URL")) t.Setenv("BOOTSTRAP_ADMIN_PASSWORD", "admin") + t.Setenv("BOOTSTRAP_ADMIN_USERNAME", "admin") t.Setenv("MUSICBRAINZ_ENABLED", "false") // public provider availability is covered by explicit live tests cfg, err := config.Load[config.API]() require.NoError(t, err) @@ -172,6 +175,7 @@ func TestUserJourney(t *testing.T) { BootstrapRequired bool `json:"bootstrap_required"` } c.ok("GET", "/api/v1/server-info", nil, &info) + require.True(t, info.BootstrapRequired, "user journey requires a fresh database before the first admin login") var tokens struct { AccessToken string `json:"access_token"` User struct { @@ -376,3 +380,29 @@ func TestUserJourney(t *testing.T) { }) fmt.Println("user journey ok") } + +// Other integration packages deliberately create users and catalog fixtures. +// The bootstrap journey needs a new database, without modifying those fixtures. +// TEST_DATABASE_URL must use a role with CREATEDB (the CI Postgres owner does). +func userJourneyDatabase(t *testing.T, address string) string { + t.Helper() + ctx := context.Background() + admin, err := db.Connect(ctx, address) + require.NoError(t, err) + t.Cleanup(admin.Close) + name := "pipu_journey_" + strings.ReplaceAll(uuid.NewString(), "-", "") + identifier := pgx.Identifier{name}.Sanitize() + _, err = admin.Exec(ctx, "CREATE DATABASE "+identifier) + require.NoError(t, err) + t.Cleanup(func() { + cleanup, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + _, err := admin.Exec(cleanup, "DROP DATABASE "+identifier+" WITH (FORCE)") + require.NoError(t, err) + }) + dsn, err := url.Parse(address) + require.NoError(t, err) + require.Contains(t, []string{"postgres", "postgresql"}, dsn.Scheme) + dsn.Path = "/" + name + return dsn.String() +} diff --git a/services/api/internal/uploads/artist_images_integration_test.go b/services/api/internal/uploads/artist_images_integration_test.go index 9042460..44e00f3 100644 --- a/services/api/internal/uploads/artist_images_integration_test.go +++ b/services/api/internal/uploads/artist_images_integration_test.go @@ -25,16 +25,18 @@ func TestUploadedArtistPortraitStagingLifetime(t *testing.T) { ctx := context.Background() pool, err := db.Connect(ctx, address) require.NoError(t, err) - defer pool.Close() + t.Cleanup(pool.Close) require.NoError(t, db.Migrate(ctx, pool)) store := NewStore(pool) author := uuid.New() _, err = pool.Exec(ctx, `INSERT INTO users(id,username,password_hash,role) VALUES($1,$2,'x','admin')`, author, "portrait-"+author.String()) require.NoError(t, err) t.Cleanup(func() { - _, _ = pool.Exec(ctx, `DELETE FROM upload_batches WHERE author_id=$1`, author) - _, _ = pool.Exec(ctx, `DELETE FROM users WHERE id=$1`, author) - _ = store.ReconcileQuota(ctx) + _, err := pool.Exec(ctx, `DELETE FROM upload_batches WHERE author_id=$1`, author) + require.NoError(t, err) + _, err = pool.Exec(ctx, `DELETE FROM users WHERE id=$1`, author) + require.NoError(t, err) + require.NoError(t, store.ReconcileQuota(ctx)) }) svc := NewService(store, nil, nil, nil, nil, nil, library.FPCalc{}, Config{StagingDir: t.TempDir()}, slog.New(slog.DiscardHandler)) owner := auth.WithPrincipal(ctx, auth.Principal{UserID: author, Role: auth.RoleAdmin})