Files
goclaw/tests/integration/hooks_phase08_helpers.go
viettranx ee328d4266 refactor: consolidate hooks table migration and drop deprecated agent_id column
- Consolidated PG migration 054: agent_hooks rename + junction table + deprecated column drop
- Updated SQLite schema v19 with final consolidated migration
- Removed obsolete schema rebuild files (v21, v23)
- Updated Go store layer (pg/hooks.go, sqlitestore/hooks.go)
- Updated integration tests to use new table names (hooks, hook_agents)
- Updated TypeScript protocol, UI components, and i18n strings
- Updated gateway methods to reflect schema changes

Tables: agent_hooks → hooks, agent_hook_agents → hook_agents
2026-04-16 14:17:48 +07:00

117 lines
4.2 KiB
Go

//go:build integration
package integration
import (
"context"
"database/sql"
"testing"
"time"
"github.com/google/uuid"
"github.com/nextlevelbuilder/goclaw/internal/edition"
"github.com/nextlevelbuilder/goclaw/internal/hooks"
"github.com/nextlevelbuilder/goclaw/internal/store"
)
// ─── Phase 08 shared test helpers ────────────────────────────────────────────
//
// Helpers scoped to the Phase 08 hook integration tests. Do NOT extend with
// generic store / channel helpers — keep additions hook-specific so the file
// stays focused.
// withEdition flips edition.Current() for the duration of the test, restoring
// the prior value on cleanup. Used by tests that exercise the edition-gated
// migration + validation paths (E bucket, F5).
func withEdition(t *testing.T, e edition.Edition) {
t.Helper()
prev := edition.Current()
edition.SetCurrent(e)
t.Cleanup(func() { edition.SetCurrent(prev) })
}
// seedExtraTenant inserts a tenant row with auto-cleanup. Returns the new id.
// Used by tests that need a SECOND tenant alongside seedTenantAgent's pair so
// we can assert isolation between two real tenants without re-running the
// full agent fixture chain.
func seedExtraTenant(t *testing.T, db *sql.DB) uuid.UUID {
t.Helper()
id := uuid.New()
_, err := db.Exec(
`INSERT INTO tenants (id, name, slug, status) VALUES ($1, $2, $3, 'active')
ON CONFLICT DO NOTHING`,
id, "test-tenant-"+id.String()[:8], "t"+id.String()[:8])
if err != nil {
t.Fatalf("seed extra tenant: %v", err)
}
t.Cleanup(func() { db.Exec("DELETE FROM tenants WHERE id = $1", id) })
return id
}
// purgeAgentHooksByTenant cleans hook + execution rows for one tenant. Used by
// the seed-reconciliation tests that re-run Seed() and need a clean slate.
func purgeAgentHooksByTenant(t *testing.T, db *sql.DB, tenantID uuid.UUID) {
t.Helper()
db.Exec(`DELETE FROM hook_executions WHERE hook_id IN (SELECT id FROM hooks WHERE tenant_id = $1)`, tenantID)
db.Exec(`DELETE FROM hooks WHERE tenant_id = $1`, tenantID)
}
// purgeAgentHookByID removes one hook row + its audit. Standard cleanup
// pattern for tests that create a single hook explicitly.
func purgeAgentHookByID(t *testing.T, db *sql.DB, id uuid.UUID) {
t.Helper()
db.Exec(`DELETE FROM hook_executions WHERE hook_id = $1`, id)
db.Exec(`DELETE FROM hooks WHERE id = $1`, id)
}
// pollAuditDecision polls hook_executions for one row matching (hookID,
// decision) — returns true once observed or false on timeout. Stricter
// equivalent of pollAuditCount(want=1) when callers want a boolean check.
func pollAuditDecision(t *testing.T, db *sql.DB, hookID uuid.UUID, decision string, timeout time.Duration) bool {
t.Helper()
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
var n int
if err := db.QueryRow(
`SELECT COUNT(*) FROM hook_executions WHERE hook_id = $1 AND decision = $2`,
hookID, decision,
).Scan(&n); err == nil && n > 0 {
return true
}
time.Sleep(20 * time.Millisecond)
}
return false
}
// crossTenantOwnerCtx returns a context bypassing tenant scope AND carrying
// the owner role — required for tests that reach into rows across tenants
// (e.g. seed reconciliation, command-autodisable migration which lists
// every tenant's enabled rows).
func crossTenantOwnerCtx() context.Context {
ctx := store.WithTenantID(context.Background(), store.MasterTenantID)
ctx = store.WithCrossTenant(ctx)
ctx = store.WithRole(ctx, store.RoleOwner)
return ctx
}
// hookCfgScript returns a baseline script hook config. Tests override fields
// (scope, source, event, etc.) before passing to Create. Centralizes the
// boilerplate so each test's intent stays one-line-clear.
func hookCfgScript(tenantID uuid.UUID, ev hooks.HookEvent, source string, src string) hooks.HookConfig {
return hooks.HookConfig{
TenantID: tenantID,
Scope: hooks.ScopeTenant,
Event: ev,
HandlerType: hooks.HandlerScript,
Config: map[string]any{"source": src},
TimeoutMS: 2000,
OnTimeout: hooks.DecisionAllow,
Priority: 100,
Enabled: true,
Version: 1,
Source: source,
Metadata: map[string]any{},
}
}