HH-448: enforce PostgreSQL E2E gate (#87)

* HH-448 enforce PostgreSQL E2E gate

* HH-448 fail CI when concurrency test is missing

---------

Co-authored-by: Rogee <rogee@ipao.vip>
This commit is contained in:
Rogee
2026-08-21 20:00:41 +08:00
committed by GitHub
co-authored by rogee
parent cf263d10b4
commit 7e3872170f
3 changed files with 105 additions and 32 deletions
+63 -3
View File
@@ -49,11 +49,11 @@ jobs:
run: go vet ./...
backend-postgres:
name: Backend (PostgreSQL concurrency)
name: Backend (PostgreSQL)
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
image: pgvector/pgvector:pg16
env:
POSTGRES_DB: gochat_test
POSTGRES_USER: postgres
@@ -76,7 +76,67 @@ jobs:
env:
GOCHAT_TEST_DB: postgres
GOCHAT_TEST_DB_URL: postgres://postgres:postgres@localhost:5432/gochat_test?sslmode=disable
run: go test ./internal/autoassignment -run '^TestAssignmentServiceOnlyOneConcurrentWorkerWinsPostgres$' -count=3
run: |
set -euo pipefail
output="$RUNNER_TEMP/autoassignment-concurrency.json"
go test -json ./internal/autoassignment -run '^TestAssignmentServiceOnlyOneConcurrentWorkerWinsPostgres$' -count=3 | tee "$output"
grep -Eq '"Action":"pass".*"Test":"TestAssignmentServiceOnlyOneConcurrentWorkerWinsPostgres"' "$output" || {
echo "PostgreSQL auto-assignment concurrency test did not run" >&2
exit 1
}
- name: Test PostgreSQL E2E
working-directory: backend
env:
GOCHAT_TEST_DB: postgres
GOCHAT_TEST_DB_URL: postgres://postgres:postgres@localhost:5432/gochat_test?sslmode=disable
run: |
set -euo pipefail
output="$RUNNER_TEMP/postgres-e2e.json"
go test -json -count=1 -timeout 10m ./tests/e2e/... | tee "$output"
python3 - "$output" <<'PY'
import json
import sys
required = {
"TestAuthE2ESuite",
"TestAccountCRUDE2ESuite",
"TestConversationE2ESuite",
"TestCRME2ESuite",
"TestCSRFE2ETestSuite",
"TestRBACE2ESuite",
}
results = {}
with open(sys.argv[1], encoding="utf-8") as events:
for line in events:
event = json.loads(line)
if event.get("Test") and event.get("Action") in {"pass", "skip", "fail"}:
results[event["Test"]] = event["Action"]
missing = sorted(suite for suite in required if results.get(suite) != "pass")
passed_subtests = {suite: 0 for suite in required}
skipped_subtests = {suite: 0 for suite in required}
for test, action in results.items():
suite, separator, _ = test.partition("/")
if separator and suite in required:
if action == "pass":
passed_subtests[suite] += 1
elif action == "skip":
skipped_subtests[suite] += 1
empty = sorted(suite for suite, count in passed_subtests.items() if count == 0)
for suite in sorted(required):
print(f"{suite}: {passed_subtests[suite]} passed, {skipped_subtests[suite]} skipped")
print(
"E2E totals: "
f"{sum(action == 'pass' for action in results.values())} passed, "
f"{sum(action == 'skip' for action in results.values())} skipped"
)
if missing:
raise SystemExit(f"required E2E suites did not pass: {', '.join(missing)}")
if empty:
raise SystemExit(f"required E2E suites ran no passing subtests: {', '.join(empty)}")
PY
frontend:
name: Frontend Build
+23 -29
View File
@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"os"
"strings"
"github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/postgres"
@@ -38,12 +39,9 @@ func RunMigrations(dbURL string, migrationsPath string) error {
}
func runMigrations(dbURL string, migrationsPath string) error {
m, err := migrate.New(
fmt.Sprintf("file://%s", migrationsPath),
dbURL,
)
m, err := newMigrate(dbURL, migrationsPath)
if err != nil {
return fmt.Errorf("failed to create migrate instance: %w", err)
return err
}
defer m.Close()
@@ -95,12 +93,9 @@ func withSanitizedPQEnvironment(fn func() error) error {
// MigrateSteps applies N migration steps (positive = up, negative = down).
func MigrateSteps(dbURL string, migrationsPath string, steps int) error {
m, err := migrate.New(
fmt.Sprintf("file://%s", migrationsPath),
dbURL,
)
m, err := newMigrate(dbURL, migrationsPath)
if err != nil {
return fmt.Errorf("failed to create migrate instance: %w", err)
return err
}
defer m.Close()
@@ -116,16 +111,9 @@ func MigrateSteps(dbURL string, migrationsPath string, steps int) error {
// RollbackMigrations rolls back all migrations (drops all tables).
func RollbackMigrations(dbURL string, migrationsPath string) error {
if migrationsPath == "" {
return errors.New("migrations path is required")
}
m, err := migrate.New(
fmt.Sprintf("file://%s", migrationsPath),
dbURL,
)
m, err := newMigrate(dbURL, migrationsPath)
if err != nil {
return fmt.Errorf("failed to create migrate instance: %w", err)
return err
}
defer m.Close()
@@ -142,12 +130,9 @@ func RollbackMigrations(dbURL string, migrationsPath string) error {
// ForceVersion sets the migration version to a specific number, clearing dirty state.
// Use this to recover from a partially-applied migration.
func ForceVersion(dbURL string, migrationsPath string, version int) error {
m, err := migrate.New(
fmt.Sprintf("file://%s", migrationsPath),
dbURL,
)
m, err := newMigrate(dbURL, migrationsPath)
if err != nil {
return fmt.Errorf("failed to create migrate instance: %w", err)
return err
}
defer m.Close()
@@ -161,12 +146,9 @@ func ForceVersion(dbURL string, migrationsPath string, version int) error {
// CurrentVersion returns the current migration version and whether the DB is in a dirty state.
// When no migrations have been applied (version 0), returns (0, false, nil) instead of an error.
func CurrentVersion(dbURL string, migrationsPath string) (uint, bool, error) {
m, err := migrate.New(
fmt.Sprintf("file://%s", migrationsPath),
dbURL,
)
m, err := newMigrate(dbURL, migrationsPath)
if err != nil {
return 0, false, fmt.Errorf("failed to create migrate instance: %w", err)
return 0, false, err
}
defer m.Close()
@@ -181,3 +163,15 @@ func CurrentVersion(dbURL string, migrationsPath string) (uint, bool, error) {
return version, dirty, nil
}
func newMigrate(dbURL, migrationsPath string) (*migrate.Migrate, error) {
if strings.TrimSpace(migrationsPath) == "" {
return nil, errors.New("migration path must not be empty")
}
m, err := migrate.New(fmt.Sprintf("file://%s", migrationsPath), dbURL)
if err != nil {
return nil, fmt.Errorf("failed to create migrate instance: %w", err)
}
return m, nil
}
+19
View File
@@ -7,6 +7,25 @@ import (
"github.com/stretchr/testify/require"
)
func TestMigrationCommandsRejectBlankPath(t *testing.T) {
tests := map[string]func() error{
"run": func() error { return RunMigrations("invalid-url", " \t") },
"steps": func() error { return MigrateSteps("invalid-url", " \t", 1) },
"rollback": func() error { return RollbackMigrations("invalid-url", " \t") },
"force": func() error { return ForceVersion("invalid-url", " \t", 1) },
"version": func() error {
_, _, err := CurrentVersion("invalid-url", " \t")
return err
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
require.EqualError(t, run(), "migration path must not be empty")
})
}
}
func TestWithSanitizedPQEnvironment(t *testing.T) {
t.Setenv("PGSERVICE", "local-service")
t.Setenv("PGSERVICEFILE", "/tmp/pg_service.conf")