package ai import ( "context" "sync" "testing" "time" ) type mockSnap struct { ids []string snap AgentSnapshot } func (m *mockSnap) ConnectedAgentIDs() []string { return m.ids } func (m *mockSnap) AgentSnapshot(string) (AgentSnapshot, bool) { return m.snap, true } type mockExec struct { mu sync.Mutex calls []Command } func (m *mockExec) Execute(_ string, cmd Command) (string, error) { m.mu.Lock() m.calls = append(m.calls, cmd) m.mu.Unlock() return cmd.Type, nil } type mockCfg struct{ cfg Config } func (m *mockCfg) AIConfig() Config { return m.cfg } type mockStore struct { mu sync.Mutex rows []string } func (m *mockStore) InsertAIDecision(_, _, _, executed string) error { m.mu.Lock() m.rows = append(m.rows, executed) m.mu.Unlock() return nil } func TestSchedulerExecutesRestartCommand(t *testing.T) { old := DecideFunc defer func() { DecideFunc = old }() DecideFunc = func(_ context.Context, _, _, _, _ string) (string, error) { return `{"commands":[{"type":"restart_mining","args":{}}]}`, nil } exec := &mockExec{} store := &mockStore{} sched := NewScheduler( &mockCfg{cfg: Config{Enabled: true, Endpoint: "http://test/v1", IntervalSec: 1}}, &mockSnap{ids: []string{"agent-1"}, snap: AgentSnapshot{AgentID: "agent-1", Name: "host"}}, exec, store, ) sched.lastRun["agent-1"] = time.Now().Add(-2 * time.Minute) sched.Tick() exec.mu.Lock() n := len(exec.calls) call := exec.calls exec.mu.Unlock() if n != 1 || call[0].Type != CmdRestartMining { t.Fatalf("calls: %+v", call) } store.mu.Lock() defer store.mu.Unlock() if len(store.rows) != 1 || store.rows[0] != "restart_mining:restart_mining" { t.Fatalf("store: %v", store.rows) } } func TestSchedulerNoOpWhenDisabled(t *testing.T) { exec := &mockExec{} sched := NewScheduler( &mockCfg{cfg: Config{Enabled: false}}, &mockSnap{ids: []string{"agent-1"}}, exec, nil, ) sched.Tick() if len(exec.calls) != 0 { t.Fatalf("expected no calls") } }