Add Calibrate AI Control UI and fleet LLM backend wiring.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Operators toggle Logic gates vs AI Control on Settings, refresh local Ollama models, and save ai_endpoint settings via Calibrate PUT; server scheduler and agent snapshot/command paths support stateless 60s fleet decisions.
This commit is contained in:
92
server/internal/ai/scheduler_test.go
Normal file
92
server/internal/ai/scheduler_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user