diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..817bce3f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,69 @@ +# QA-001: main/test/dev 合并门禁 — go vet、短测、迁移校验、前端测试 +name: CI + +on: + push: + branches: [dev, test, main] + pull_request: + branches: [dev, test, main] + +jobs: + go-vet-and-short-test: + runs-on: ubuntu-latest + services: + postgres: + image: pgvector/pgvector:pg16 + env: + POSTGRES_USER: app + POSTGRES_PASSWORD: ci_secret_db_pass + POSTGRES_DB: agent_system_ci + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U app -d agent_system_ci" + --health-interval 5s + --health-timeout 5s + --health-retries 20 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: "1.27.1" + cache-dependency-path: go.sum + + - name: go vet + run: go vet ./... + + - name: migration gate + env: + TEST_DATABASE_URL: postgres://app:ci_secret_db_pass@localhost:5432/agent_system_ci?sslmode=disable + run: go test -count=1 -run TestMigrationGate ./internal/db/... + + - name: go test (short) + env: + TEST_DATABASE_URL: postgres://app:ci_secret_db_pass@localhost:5432/agent_system_ci?sslmode=disable + run: go test -short -timeout 20m ./... + + web: + runs-on: ubuntu-latest + defaults: + run: + working-directory: web + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: npm + cache-dependency-path: web/package-lock.json + + - name: npm ci + run: npm ci + + - name: npm test + run: npm test + + - name: npm build + run: npm run build diff --git a/internal/db/migrate_test.go b/internal/db/migrate_test.go new file mode 100644 index 00000000..ba7f3a9f --- /dev/null +++ b/internal/db/migrate_test.go @@ -0,0 +1,47 @@ +package db + +import ( + "context" + "os" + "testing" + "time" + + "github.com/jackc/pgx/v5/pgxpool" +) + +func TestMigrationGate_ExpectedFilesMatchAppliedVersion(t *testing.T) { + migDir := ResolveMigrationsDir() + expected, err := ExpectedMigrationCount(migDir) + if err != nil { + t.Fatalf("count migrations: %v", err) + } + if expected < 1 { + t.Fatal("expected at least one migration file") + } + + dsn := os.Getenv("TEST_DATABASE_URL") + if dsn == "" { + t.Skip("TEST_DATABASE_URL not set; migration gate file count only") + } + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + pool, err := pgxpool.New(ctx, dsn) + if err != nil { + t.Skipf("postgres unavailable: %v", err) + } + defer pool.Close() + if err := pool.Ping(ctx); err != nil { + t.Skipf("postgres ping failed: %v", err) + } + if err := RunMigrations(ctx, pool, migDir); err != nil { + t.Fatalf("RunMigrations: %v", err) + } + version, err := CurrentVersion(ctx, pool) + if err != nil { + t.Fatalf("CurrentVersion: %v", err) + } + if int(version) < expected { + t.Fatalf("applied schema version %d behind migration files %d", version, expected) + } +}