mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-10-01 12:31:59 +00:00
Implements the fixes from audit/pkg/dbmanager.audit.md. - Stop closing the shared *sql.DB to recover from errors. Adapter factories and the health checker no longer call Reconnect; Reconnect is atomic and operator-only. - Postgres uses a custom driver.Connector: Reconnect retires pooled connections by generation without closing the pool, so held Bun/GORM handles keep working. Verified against a live server restart. - Add TCP keepalive, TCP_USER_TIMEOUT, a bounded reuse ping and statement_timeout as a runtime parameter; drop the 2 min timeout floor. - Health check pings without holding the connection lock. - Listener: single goroutine pair, bounded Close without UNLISTEN, and serialised use of the pgx connection (fixes conn busy and a close race). - Fix Connect/Close/Connect/Close panic, idempotent Connect, dial outside the manager lock, clean up on partial failure. - SQLite: pin :memory: to one connection, pragmas via DSN. - Escape credentials in Postgres/MSSQL/Mongo DSNs; sslmode defaults to prefer. Wire retry settings, publish metrics, fix logger calls. - NewConnectionFromDB: Close is a no-op with a warning (caller owns the pool); Reconnect only pings. - Document correct usage in the README; mark the audit with what was done. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
94 lines
2.3 KiB
Go
94 lines
2.3 KiB
Go
package dbmanager
|
|
|
|
import (
|
|
"context"
|
|
"github.com/bitechdev/ResolveSpec/pkg/dbmanager/providers"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestLivePostgresRefreshKeepsHandles(t *testing.T) {
|
|
if os.Getenv("PG_LIVE") == "" {
|
|
t.Skip("PG_LIVE not set")
|
|
}
|
|
mgr, err := NewManager(ManagerConfig{
|
|
DefaultConnection: "pg",
|
|
Connections: map[string]ConnectionConfig{"pg": {
|
|
Name: "pg", Type: DatabaseTypePostgreSQL, Host: "127.0.0.1", Port: 54329,
|
|
User: "postgres", Database: "postgres", QueryTimeout: 30 * time.Second,
|
|
}},
|
|
HealthCheckInterval: -1,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctx := context.Background()
|
|
if err := mgr.Connect(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer mgr.Close()
|
|
conn, _ := mgr.GetDefault()
|
|
held, _ := conn.Bun()
|
|
gormDB, _ := conn.GORM()
|
|
|
|
var pid1, pid2 int
|
|
var st string
|
|
if err := held.DB.QueryRow("select pg_backend_pid(), current_setting('statement_timeout')").Scan(&pid1, &st); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if st != "30s" {
|
|
t.Errorf("statement_timeout = %q", st)
|
|
}
|
|
if err := conn.Reconnect(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := held.DB.QueryRow("select pg_backend_pid()").Scan(&pid2); err != nil {
|
|
t.Fatalf("held bun handle broken after reconnect: %v", err)
|
|
}
|
|
if pid1 == pid2 {
|
|
t.Error("expected a new backend after reconnect")
|
|
}
|
|
var n int
|
|
if err := gormDB.Raw("select 1").Scan(&n).Error; err != nil || n != 1 {
|
|
t.Fatalf("held gorm handle broken: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLiveListenerListenNotify(t *testing.T) {
|
|
if os.Getenv("PG_LIVE") == "" {
|
|
t.Skip("PG_LIVE not set")
|
|
}
|
|
cc := ConnectionConfig{Name: "pg", Type: DatabaseTypePostgreSQL, Host: "127.0.0.1", Port: 54329,
|
|
User: "postgres", Database: "postgres", ConnectTimeout: 5 * time.Second}
|
|
p := providers.NewPostgresProvider()
|
|
ctx := context.Background()
|
|
if err := p.Connect(ctx, &cc); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer p.Close()
|
|
l, err := p.GetListener(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := make(chan string, 4)
|
|
for _, ch := range []string{"a", "b"} {
|
|
if err := l.Listen(ch, func(c, payload string) { got <- c + ":" + payload }); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for i := 0; i < 3; i++ {
|
|
if err := l.Notify(ctx, "a", "x"); err != nil {
|
|
t.Fatalf("notify: %v", err)
|
|
}
|
|
}
|
|
select {
|
|
case v := <-got:
|
|
if v != "a:x" {
|
|
t.Fatalf("got %q", v)
|
|
}
|
|
case <-time.After(3 * time.Second):
|
|
t.Fatal("no notification")
|
|
}
|
|
}
|