Files
Control-Center/go-backend/internal/handler/mock_repos_test.go
Joshua e8ced74429
All checks were successful
Dev Build / build-test (pull_request) Successful in 2m23s
CUB-123: integrate gateway, wire PostgreSQL repositories, add SSE streaming
- Create repository/ package with pgx-backed CRUD for agents, sessions, tasks, projects
- Define AgentRepo/SessionRepo/TaskRepo/ProjectRepo interfaces
- Update handler to use repository interfaces instead of in-memory stores
- Add SSE broker with GET /api/events endpoint (text/event-stream)
- Add gateway client that polls OpenClaw for agent states
- Add GATEWAY_URL and GATEWAY_POLL_INTERVAL config fields
- Seed 5 demo agents (Otto, Rex, Dex, Hex, Pip) on empty DB
- Update router to wire SSE broker
- All 21 handler tests pass with mock repos
2026-05-08 19:58:06 -04:00

236 lines
5.6 KiB
Go

package handler
import (
"context"
"fmt"
"sync"
"time"
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/models"
)
// mockAgentRepo implements repository.AgentRepo in-memory for testing.
type mockAgentRepo struct {
mu sync.RWMutex
m map[string]models.AgentCardData
}
func newMockAgentRepo() *mockAgentRepo {
return &mockAgentRepo{m: make(map[string]models.AgentCardData)}
}
func (r *mockAgentRepo) Create(ctx context.Context, a models.AgentCardData) error {
r.mu.Lock()
defer r.mu.Unlock()
if _, ok := r.m[a.ID]; ok {
return fmt.Errorf("duplicate key: %s", a.ID)
}
r.m[a.ID] = a
return nil
}
func (r *mockAgentRepo) Get(ctx context.Context, id string) (models.AgentCardData, error) {
r.mu.RLock()
defer r.mu.RUnlock()
a, ok := r.m[id]
if !ok {
return a, fmt.Errorf("not found: %s", id)
}
return a, nil
}
func (r *mockAgentRepo) List(ctx context.Context, statusFilter models.AgentStatus) ([]models.AgentCardData, error) {
r.mu.RLock()
defer r.mu.RUnlock()
result := make([]models.AgentCardData, 0, len(r.m))
for _, a := range r.m {
if statusFilter != "" && a.Status != statusFilter {
continue
}
result = append(result, a)
}
return result, nil
}
func (r *mockAgentRepo) Update(ctx context.Context, id string, req models.UpdateAgentRequest) (models.AgentCardData, error) {
r.mu.Lock()
defer r.mu.Unlock()
a, ok := r.m[id]
if !ok {
return a, fmt.Errorf("not found: %s", id)
}
if req.Status != nil {
a.Status = *req.Status
}
if req.CurrentTask != nil {
a.CurrentTask = req.CurrentTask
}
if req.TaskProgress != nil {
a.TaskProgress = req.TaskProgress
}
if req.TaskElapsed != nil {
a.TaskElapsed = req.TaskElapsed
}
if req.Channel != nil {
a.Channel = *req.Channel
}
if req.ErrorMessage != nil {
a.ErrorMessage = req.ErrorMessage
}
a.LastActivity = time.Now().UTC().Format(time.RFC3339)
r.m[id] = a
return a, nil
}
func (r *mockAgentRepo) Delete(ctx context.Context, id string) error {
r.mu.Lock()
defer r.mu.Unlock()
if _, ok := r.m[id]; !ok {
return fmt.Errorf("not found: %s", id)
}
delete(r.m, id)
return nil
}
func (r *mockAgentRepo) Count(ctx context.Context) (int, error) {
r.mu.RLock()
defer r.mu.RUnlock()
return len(r.m), nil
}
// ─── Mock Session Repo ──────────────────────────────────────────────────────────
type mockSessionRepo struct {
mu sync.RWMutex
m map[string]models.Session
}
func newMockSessionRepo() *mockSessionRepo {
return &mockSessionRepo{m: make(map[string]models.Session)}
}
func (r *mockSessionRepo) Create(ctx context.Context, s models.Session) (models.Session, error) {
r.mu.Lock()
defer r.mu.Unlock()
if s.ID == "" {
s.ID = fmt.Sprintf("sess-%d", len(r.m)+1)
}
if s.StartedAt.IsZero() {
s.StartedAt = time.Now().UTC()
}
if s.LastActivityAt.IsZero() {
s.LastActivityAt = s.StartedAt
}
r.m[s.ID] = s
return s, nil
}
func (r *mockSessionRepo) ListActive(ctx context.Context) ([]models.Session, error) {
r.mu.RLock()
defer r.mu.RUnlock()
result := make([]models.Session, 0)
for _, s := range r.m {
if s.Status == "running" || s.Status == "streaming" {
result = append(result, s)
}
}
return result, nil
}
func (r *mockSessionRepo) Count(ctx context.Context) (int, error) {
r.mu.RLock()
defer r.mu.RUnlock()
return len(r.m), nil
}
// ─── Mock Task Repo ─────────────────────────────────────────────────────────────
type mockTaskRepo struct {
mu sync.RWMutex
m map[string]models.Task
}
func newMockTaskRepo() *mockTaskRepo {
return &mockTaskRepo{m: make(map[string]models.Task)}
}
func (r *mockTaskRepo) Create(ctx context.Context, t models.Task) (models.Task, error) {
r.mu.Lock()
defer r.mu.Unlock()
if t.ID == "" {
t.ID = fmt.Sprintf("task-%d", len(r.m)+1)
}
now := time.Now().UTC()
if t.CreatedAt.IsZero() {
t.CreatedAt = now
}
if t.UpdatedAt.IsZero() {
t.UpdatedAt = now
}
r.m[t.ID] = t
return t, nil
}
func (r *mockTaskRepo) ListRecent(ctx context.Context) ([]models.Task, error) {
r.mu.RLock()
defer r.mu.RUnlock()
result := make([]models.Task, 0, len(r.m))
for _, t := range r.m {
result = append(result, t)
}
return result, nil
}
func (r *mockTaskRepo) Count(ctx context.Context) (int, error) {
r.mu.RLock()
defer r.mu.RUnlock()
return len(r.m), nil
}
// ─── Mock Project Repo ─────────────────────────────────────────────────────────
type mockProjectRepo struct {
mu sync.RWMutex
m map[string]models.Project
}
func newMockProjectRepo() *mockProjectRepo {
return &mockProjectRepo{m: make(map[string]models.Project)}
}
func (r *mockProjectRepo) Create(ctx context.Context, p models.Project) (models.Project, error) {
r.mu.Lock()
defer r.mu.Unlock()
if p.ID == "" {
p.ID = fmt.Sprintf("proj-%d", len(r.m)+1)
}
now := time.Now().UTC()
if p.CreatedAt.IsZero() {
p.CreatedAt = now
}
if p.UpdatedAt.IsZero() {
p.UpdatedAt = now
}
if p.AgentIDs == nil {
p.AgentIDs = []string{}
}
r.m[p.ID] = p
return p, nil
}
func (r *mockProjectRepo) List(ctx context.Context) ([]models.Project, error) {
r.mu.RLock()
defer r.mu.RUnlock()
result := make([]models.Project, 0, len(r.m))
for _, p := range r.m {
result = append(result, p)
}
return result, nil
}
func (r *mockProjectRepo) Count(ctx context.Context) (int, error) {
r.mu.RLock()
defer r.mu.RUnlock()
return len(r.m), nil
}