mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-12 20:15:42 +00:00
Closes #1076, #1338. Fix A — tenant backup aborted with SQLSTATE 42703 "column id does not exist" because exportQuery() hardcoded ORDER BY id. Several tenant-scoped tables have composite PKs and no id column. Add a TableDef.OrderBy field, honor it in exportQuery(), and set it for every id-less registry table: config_secrets, agent_team_members, tenant_hook_budget, system_configs, builtin_tool_tenant_configs, skill_tenant_configs, user_agent_profiles. Fix B — hooks, tenant_hook_budget and webhook config were missing from the backup registry, silently dropping their data on backup/restore. Add hooks, tenant_hook_budget, webhooks (preserve) plus the hook_agents junction (via ParentJoin through hooks, composite PK), and mark hook_executions and webhook_calls as ephemeral in the skipped list. Fix C — restoring on a fresh server failed with "N active DB connection(s) detected" because the gateway's own pool connections were counted as active clients. Tag pool connections with application_name='goclaw' (pg.OpenDB) and exclude them in CheckActiveConnections; genuine external clients still block. Adds unit + integration regression tests, including an export-over-every- registered-table test that surfaced the additional id-less tables.
71 lines
2.1 KiB
Go
71 lines
2.1 KiB
Go
//go:build integration
|
|
|
|
package integration
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"os"
|
|
"testing"
|
|
|
|
_ "github.com/jackc/pgx/v5/stdlib"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/backup"
|
|
"github.com/nextlevelbuilder/goclaw/internal/store/pg"
|
|
)
|
|
|
|
// TestBackup_CheckActiveConnections_ExcludesGatewayPool is a regression test for
|
|
// issue #1338: restoring on a fresh server failed with "N active DB connection(s)
|
|
// detected" because the gateway's own pool connections were counted as active
|
|
// clients. Gateway pool connections are tagged application_name='goclaw' and must
|
|
// be excluded; genuinely external connections must still be counted.
|
|
func TestBackup_CheckActiveConnections_ExcludesGatewayPool(t *testing.T) {
|
|
testDB(t) // skips if PG unavailable
|
|
dsn := os.Getenv("TEST_DATABASE_URL")
|
|
if dsn == "" {
|
|
dsn = defaultTestDSN
|
|
}
|
|
ctx := context.Background()
|
|
|
|
base, err := backup.CheckActiveConnections(ctx, dsn)
|
|
if err != nil {
|
|
t.Fatalf("baseline CheckActiveConnections: %v", err)
|
|
}
|
|
|
|
// A gateway-tagged pool connection must NOT be counted.
|
|
pool, err := pg.OpenDB(dsn)
|
|
if err != nil {
|
|
t.Fatalf("open gateway pool: %v", err)
|
|
}
|
|
defer pool.Close()
|
|
var one int
|
|
if err := pool.QueryRowContext(ctx, "SELECT 1").Scan(&one); err != nil {
|
|
t.Fatalf("warm gateway pool: %v", err)
|
|
}
|
|
|
|
after, err := backup.CheckActiveConnections(ctx, dsn)
|
|
if err != nil {
|
|
t.Fatalf("CheckActiveConnections after gateway pool: %v", err)
|
|
}
|
|
if after != base {
|
|
t.Errorf("gateway pool connection was counted as active: base=%d after=%d", base, after)
|
|
}
|
|
|
|
// A non-gateway connection MUST be counted.
|
|
external, err := sql.Open("pgx", dsn)
|
|
if err != nil {
|
|
t.Fatalf("open external conn: %v", err)
|
|
}
|
|
defer external.Close()
|
|
if err := external.QueryRowContext(ctx, "SELECT 1").Scan(&one); err != nil {
|
|
t.Fatalf("warm external conn: %v", err)
|
|
}
|
|
withExternal, err := backup.CheckActiveConnections(ctx, dsn)
|
|
if err != nil {
|
|
t.Fatalf("CheckActiveConnections with external: %v", err)
|
|
}
|
|
if withExternal <= after {
|
|
t.Errorf("external connection not counted: after=%d withExternal=%d", after, withExternal)
|
|
}
|
|
}
|