mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-25 10:21:47 +00:00
- 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
187 lines
5.6 KiB
Go
187 lines
5.6 KiB
Go
package hooks_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/edition"
|
|
"github.com/nextlevelbuilder/goclaw/internal/hooks"
|
|
)
|
|
|
|
// migStore is a minimal HookStore backing the command-autodisable unit tests.
|
|
// Only List + Update are exercised by the migration; Create/Delete/etc. are
|
|
// stubbed to zero values.
|
|
type migStore struct {
|
|
rows []hooks.HookConfig
|
|
listErr error
|
|
}
|
|
|
|
func (m *migStore) Create(context.Context, hooks.HookConfig) (uuid.UUID, error) {
|
|
return uuid.Nil, nil
|
|
}
|
|
func (m *migStore) GetByID(_ context.Context, id uuid.UUID) (*hooks.HookConfig, error) {
|
|
for i := range m.rows {
|
|
if m.rows[i].ID == id {
|
|
c := m.rows[i]
|
|
return &c, nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
func (m *migStore) List(_ context.Context, f hooks.ListFilter) ([]hooks.HookConfig, error) {
|
|
if m.listErr != nil {
|
|
return nil, m.listErr
|
|
}
|
|
out := []hooks.HookConfig{}
|
|
for _, r := range m.rows {
|
|
if f.Enabled != nil && r.Enabled != *f.Enabled {
|
|
continue
|
|
}
|
|
out = append(out, r)
|
|
}
|
|
return out, nil
|
|
}
|
|
func (m *migStore) Update(_ context.Context, id uuid.UUID, updates map[string]any) error {
|
|
for i := range m.rows {
|
|
if m.rows[i].ID != id {
|
|
continue
|
|
}
|
|
if v, ok := updates["enabled"].(bool); ok {
|
|
m.rows[i].Enabled = v
|
|
}
|
|
return nil
|
|
}
|
|
return errors.New("row not found")
|
|
}
|
|
func (m *migStore) Delete(context.Context, uuid.UUID) error { return nil }
|
|
func (m *migStore) ResolveForEvent(context.Context, hooks.Event) ([]hooks.HookConfig, error) {
|
|
return nil, nil
|
|
}
|
|
func (m *migStore) WriteExecution(context.Context, hooks.HookExecution) error { return nil }
|
|
func (m *migStore) SetHookAgents(context.Context, uuid.UUID, []uuid.UUID) error { return nil }
|
|
func (m *migStore) GetHookAgents(context.Context, uuid.UUID) ([]uuid.UUID, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// fixtureRows covers every branch of the migration:
|
|
//
|
|
// A: enabled command + ui → should be disabled
|
|
// B: already-disabled command + ui → skip (not in filter result)
|
|
// C: enabled http + ui → skip (not command)
|
|
// D: enabled script + builtin → skip (builtin carve-out)
|
|
// E: enabled command + builtin → skip (defensive carve-out even though
|
|
// Phase 04/05 never seed command builtins)
|
|
func fixtureRows() []hooks.HookConfig {
|
|
mk := func(ht hooks.HandlerType, src string, enabled bool) hooks.HookConfig {
|
|
return hooks.HookConfig{
|
|
ID: uuid.New(),
|
|
TenantID: uuid.New(),
|
|
Event: hooks.EventPreToolUse,
|
|
HandlerType: ht,
|
|
Scope: hooks.ScopeTenant,
|
|
Source: src,
|
|
Enabled: enabled,
|
|
Version: 1,
|
|
}
|
|
}
|
|
return []hooks.HookConfig{
|
|
mk(hooks.HandlerCommand, "ui", true),
|
|
mk(hooks.HandlerCommand, "ui", false),
|
|
mk(hooks.HandlerHTTP, "ui", true),
|
|
mk(hooks.HandlerScript, hooks.SourceBuiltin, true),
|
|
mk(hooks.HandlerCommand, hooks.SourceBuiltin, true),
|
|
}
|
|
}
|
|
|
|
func TestDisableLegacyCommandHooks_StandardDisablesOnlyCommandUI(t *testing.T) {
|
|
s := &migStore{rows: fixtureRows()}
|
|
n, err := hooks.DisableLegacyCommandHooks(context.Background(), s, edition.Standard)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if n != 1 {
|
|
t.Fatalf("disabled=%d, want 1 (only the enabled+command+ui row)", n)
|
|
}
|
|
|
|
var cntCommandEnabled, cntCommandBuiltin, cntHTTP, cntScriptBuiltin int
|
|
for _, r := range s.rows {
|
|
switch {
|
|
case r.HandlerType == hooks.HandlerCommand && r.Source == "ui" && r.Enabled:
|
|
cntCommandEnabled++
|
|
case r.HandlerType == hooks.HandlerCommand && r.Source == hooks.SourceBuiltin:
|
|
if r.Enabled {
|
|
cntCommandBuiltin++
|
|
}
|
|
case r.HandlerType == hooks.HandlerHTTP && r.Enabled:
|
|
cntHTTP++
|
|
case r.HandlerType == hooks.HandlerScript && r.Source == hooks.SourceBuiltin && r.Enabled:
|
|
cntScriptBuiltin++
|
|
}
|
|
}
|
|
if cntCommandEnabled != 0 {
|
|
t.Errorf("command/ui rows still enabled: %d", cntCommandEnabled)
|
|
}
|
|
if cntCommandBuiltin != 1 {
|
|
t.Errorf("command/builtin row should stay enabled: got %d", cntCommandBuiltin)
|
|
}
|
|
if cntHTTP != 1 {
|
|
t.Errorf("http row touched: got %d enabled", cntHTTP)
|
|
}
|
|
if cntScriptBuiltin != 1 {
|
|
t.Errorf("script/builtin row touched: got %d enabled", cntScriptBuiltin)
|
|
}
|
|
}
|
|
|
|
func TestDisableLegacyCommandHooks_Idempotent(t *testing.T) {
|
|
s := &migStore{rows: fixtureRows()}
|
|
if _, err := hooks.DisableLegacyCommandHooks(context.Background(), s, edition.Standard); err != nil {
|
|
t.Fatalf("first run: %v", err)
|
|
}
|
|
n, err := hooks.DisableLegacyCommandHooks(context.Background(), s, edition.Standard)
|
|
if err != nil {
|
|
t.Fatalf("second run: %v", err)
|
|
}
|
|
if n != 0 {
|
|
t.Fatalf("second run disabled=%d, want 0 (idempotent)", n)
|
|
}
|
|
}
|
|
|
|
func TestDisableLegacyCommandHooks_LiteNoOp(t *testing.T) {
|
|
s := &migStore{rows: fixtureRows()}
|
|
n, err := hooks.DisableLegacyCommandHooks(context.Background(), s, edition.Lite)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n != 0 {
|
|
t.Fatalf("Lite returned disabled=%d, want 0", n)
|
|
}
|
|
// Nothing touched.
|
|
for _, r := range s.rows {
|
|
if r.HandlerType == hooks.HandlerCommand && r.Source == "ui" && !r.Enabled {
|
|
// This row started disabled; fine.
|
|
continue
|
|
}
|
|
if r.HandlerType == hooks.HandlerCommand && r.Source == "ui" && !r.Enabled {
|
|
t.Errorf("Lite disabled a command row: %+v", r)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDisableLegacyCommandHooks_NilStore(t *testing.T) {
|
|
n, err := hooks.DisableLegacyCommandHooks(context.Background(), nil, edition.Standard)
|
|
if err != nil || n != 0 {
|
|
t.Fatalf("nil store: n=%d err=%v", n, err)
|
|
}
|
|
}
|
|
|
|
func TestDisableLegacyCommandHooks_ListErrorPropagates(t *testing.T) {
|
|
s := &migStore{listErr: errors.New("db down")}
|
|
_, err := hooks.DisableLegacyCommandHooks(context.Background(), s, edition.Standard)
|
|
if err == nil {
|
|
t.Fatal("want error from List propagation")
|
|
}
|
|
}
|