增加支付逻辑
This commit is contained in:
1 parent
4ac786aafb
commit
dfb2c91b8d
77 files changed
+6370
-45
No files matched your search
@@ -0,0 +1,162 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
// Requires an independently disposable database with no valuable contents.
|
||||
// It never consults the application's DATABASE_URL or .env files.
|
||||
func TestIntegrationRunOnDisposableDatabase(t *testing.T) {
|
||||
dsn := os.Getenv("NIAN_TEST_MIGRATION_DATABASE_URL")
|
||||
if dsn == "" {
|
||||
t.Skip("set NIAN_TEST_MIGRATION_DATABASE_URL for disposable PostgreSQL integration test")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
|
||||
defer cancel()
|
||||
conn, err := pgx.Connect(ctx, dsn)
|
||||
if err != nil {
|
||||
t.Fatal("connect disposable database:", err)
|
||||
}
|
||||
defer conn.Close(context.Background())
|
||||
if _, err := conn.Exec(ctx, `DO $$ BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'nian_migration_test') THEN
|
||||
CREATE ROLE nian_migration_test NOLOGIN;
|
||||
END IF;
|
||||
END $$`); err != nil {
|
||||
t.Fatal("create disposable test role:", err)
|
||||
}
|
||||
all, err := discover(sqlFiles)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Repair only markers this test might have left if an earlier assertion
|
||||
// stopped execution before normal cleanup.
|
||||
var trackingTable *string
|
||||
if err := conn.QueryRow(ctx, "SELECT to_regclass('public.schema_migrations')::text").Scan(&trackingTable); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if trackingTable != nil {
|
||||
if _, err := conn.Exec(ctx, "UPDATE schema_migrations SET checksum=$1 WHERE version=$2 AND checksum='test-mismatch'", all[0].checksum, all[0].version); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := conn.Exec(ctx, "DELETE FROM schema_migrations WHERE version='test_unknown'"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
env := func(key string) string {
|
||||
switch key {
|
||||
case "DATABASE_AUTO_MIGRATE":
|
||||
return "true"
|
||||
case "ZHINIAN_DATA_BACKEND":
|
||||
return "postgres"
|
||||
case "DATABASE_MIGRATION_URL", "DATABASE_URL":
|
||||
return dsn
|
||||
case "DATABASE_APP_ROLE":
|
||||
return "nian_migration_test"
|
||||
case "NODE_ENV":
|
||||
return "production"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
results := make(chan error, 2)
|
||||
for i := 0; i < 2; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
results <- Run(ctx, env)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
close(results)
|
||||
for err := range results {
|
||||
if err != nil {
|
||||
t.Fatal("concurrent migration:", err)
|
||||
}
|
||||
}
|
||||
if err := Run(ctx, env); err != nil {
|
||||
t.Fatal("repeat migration:", err)
|
||||
}
|
||||
var count int
|
||||
if err := conn.QueryRow(ctx, "SELECT count(*) FROM schema_migrations").Scan(&count); err != nil || count != len(all) {
|
||||
t.Fatalf("recorded migrations: count=%d, err=%v", count, err)
|
||||
}
|
||||
var privilege bool
|
||||
if err := conn.QueryRow(ctx, "SELECT has_table_privilege('nian_migration_test', 'public.payment_orders', 'SELECT,INSERT,UPDATE')").Scan(&privilege); err != nil || !privilege {
|
||||
t.Fatalf("payment order privileges: granted=%v, err=%v", privilege, err)
|
||||
}
|
||||
// A mismatch must prevent any later migration or startup.
|
||||
first := all[0]
|
||||
if _, err := conn.Exec(ctx, "UPDATE schema_migrations SET checksum='test-mismatch' WHERE version=$1", first.version); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cleanupCancel()
|
||||
_, _ = conn.Exec(cleanupCtx, "UPDATE schema_migrations SET checksum=$1 WHERE version=$2", first.checksum, first.version)
|
||||
_, _ = conn.Exec(cleanupCtx, "DELETE FROM schema_migrations WHERE version='test_unknown'")
|
||||
}()
|
||||
if err := Run(ctx, env); err == nil || !strings.Contains(err.Error(), "checksum differs") {
|
||||
t.Fatalf("expected checksum rejection, got %v", err)
|
||||
}
|
||||
if _, err := conn.Exec(ctx, "UPDATE schema_migrations SET checksum=$1 WHERE version=$2", first.checksum, first.version); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := conn.Exec(ctx, "INSERT INTO schema_migrations(version, checksum) VALUES ('test_unknown', 'test')"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := Run(ctx, env); err == nil || !strings.Contains(err.Error(), "absent from bundle") {
|
||||
t.Fatalf("expected unknown-version rejection, got %v", err)
|
||||
}
|
||||
if _, err := conn.Exec(ctx, "DELETE FROM schema_migrations WHERE version='test_unknown'"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := conn.Exec(ctx, "SELECT pg_advisory_lock($1)", migrationLockID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Exec(context.Background(), "SELECT pg_advisory_unlock($1)", migrationLockID)
|
||||
shortCtx, shortCancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer shortCancel()
|
||||
if err := Run(shortCtx, env); err == nil || !strings.Contains(err.Error(), "timed out") {
|
||||
t.Fatalf("expected advisory-lock timeout, got %v", err)
|
||||
}
|
||||
if _, err := conn.Exec(ctx, "SELECT pg_advisory_unlock($1)", migrationLockID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// The outer BEGIN/COMMIT in a legacy migration must not commit its DDL
|
||||
// before the version/checksum row is inserted by apply.
|
||||
const syntheticVersion = "9999_atomicity_test"
|
||||
const syntheticTable = "public.nian_migration_atomicity_test"
|
||||
if _, err := conn.Exec(ctx, "DROP TABLE IF EXISTS "+syntheticTable); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := conn.Exec(ctx, "DELETE FROM schema_migrations WHERE version=$1", syntheticVersion); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
synthetic := migration{
|
||||
version: syntheticVersion,
|
||||
sql: "-- synthetic migration\nBEGIN;\nCREATE TABLE " + syntheticTable + " (id integer PRIMARY KEY);\nSELECT 1 / 0;\nCOMMIT;\n",
|
||||
}
|
||||
if err := apply(ctx, conn, append(all, synthetic), ""); err == nil || !strings.Contains(err.Error(), syntheticVersion) {
|
||||
t.Fatalf("expected synthetic migration failure with version, got %v", err)
|
||||
}
|
||||
var objectName *string
|
||||
if err := conn.QueryRow(ctx, "SELECT to_regclass($1)::text", syntheticTable).Scan(&objectName); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if objectName != nil {
|
||||
t.Fatalf("failed migration left DDL object %s", *objectName)
|
||||
}
|
||||
var recorded int
|
||||
if err := conn.QueryRow(ctx, "SELECT count(*) FROM schema_migrations WHERE version=$1", syntheticVersion).Scan(&recorded); err != nil || recorded != 0 {
|
||||
t.Fatalf("failed migration recorded version: count=%d, err=%v", recorded, err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user