Files
HomeAgent/internal/api/handler_test.go

385 lines
9.5 KiB
Go

package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"gitcode.com/JianFeeeee/HomeAgent/internal/knowledge"
"gitcode.com/JianFeeeee/HomeAgent/internal/supervisor"
"gitcode.com/JianFeeeee/HomeAgent/internal/tracker"
"gitcode.com/JianFeeeee/HomeAgent/pkg/types"
)
func newTestHandler(t *testing.T) (*Handler, *supervisor.Daemon) {
t.Helper()
cfg := &types.Config{
Daemon: types.DaemonConfig{
CheckInterval: time.Minute,
HeartbeatInterval: 30 * time.Second,
},
}
sup := supervisor.New(cfg)
sup.Start()
return NewHandler(sup, nil, nil, nil, cfg, nil, nil, nil, nil), sup
}
func TestHandleStatus(t *testing.T) {
h, sup := newTestHandler(t)
defer sup.Shutdown()
req := httptest.NewRequest(http.MethodGet, "/api/v1/status", nil)
w := httptest.NewRecorder()
h.handleStatus(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d", w.Code)
}
var resp map[string]interface{}
json.NewDecoder(w.Body).Decode(&resp)
if resp["status"] != "running" {
t.Errorf("expected running, got %v", resp["status"])
}
}
func TestHandleStatusMethodNotAllowed(t *testing.T) {
h, sup := newTestHandler(t)
defer sup.Shutdown()
req := httptest.NewRequest(http.MethodPost, "/api/v1/status", nil)
w := httptest.NewRecorder()
h.handleStatus(w, req)
if w.Code != http.StatusMethodNotAllowed {
t.Errorf("expected 405, got %d", w.Code)
}
}
func TestHandleAgents(t *testing.T) {
h, sup := newTestHandler(t)
defer sup.Shutdown()
sup.RegisterAgent("test_agent")
req := httptest.NewRequest(http.MethodGet, "/api/v1/agents", nil)
w := httptest.NewRecorder()
h.handleAgents(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d", w.Code)
}
var resp map[string]interface{}
json.NewDecoder(w.Body).Decode(&resp)
agents, ok := resp["agents"].([]interface{})
if !ok || len(agents) == 0 {
t.Error("expected agents list")
}
}
func TestHandleAgentByID(t *testing.T) {
h, sup := newTestHandler(t)
defer sup.Shutdown()
sup.RegisterAgent("my_agent")
req := httptest.NewRequest(http.MethodGet, "/api/v1/agents/my_agent", nil)
w := httptest.NewRecorder()
h.handleAgentByID(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d", w.Code)
}
var resp map[string]interface{}
json.NewDecoder(w.Body).Decode(&resp)
if resp["id"] != "my_agent" {
t.Errorf("expected my_agent, got %v", resp["id"])
}
}
func TestHandleAgentByIDNotFound(t *testing.T) {
h, sup := newTestHandler(t)
defer sup.Shutdown()
req := httptest.NewRequest(http.MethodGet, "/api/v1/agents/nonexistent", nil)
w := httptest.NewRecorder()
h.handleAgentByID(w, req)
if w.Code != http.StatusNotFound {
t.Errorf("expected 404, got %d", w.Code)
}
}
func TestHandleKnowledgeSearch(t *testing.T) {
ks := knowledge.NewStore(t.TempDir())
ks.Start()
ks.Add("test_doc", "this is test content for searching")
cfg := &types.Config{
Daemon: types.DaemonConfig{
CheckInterval: time.Minute,
HeartbeatInterval: 30 * time.Second,
},
}
sup := supervisor.New(cfg)
sup.Start()
defer sup.Shutdown()
h := NewHandler(sup, nil, nil, nil, cfg, nil, nil, ks, nil)
req := httptest.NewRequest(http.MethodGet, "/api/v1/knowledge?q=test", nil)
w := httptest.NewRecorder()
h.handleKnowledge(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d", w.Code)
}
var resp map[string]interface{}
json.NewDecoder(w.Body).Decode(&resp)
results, ok := resp["results"].([]interface{})
if !ok || len(results) == 0 {
t.Error("expected search results")
}
}
func TestHandleKnowledgeCreate(t *testing.T) {
ks := knowledge.NewStore(t.TempDir())
ks.Start()
cfg := &types.Config{
Daemon: types.DaemonConfig{
CheckInterval: time.Minute,
HeartbeatInterval: 30 * time.Second,
},
}
sup := supervisor.New(cfg)
sup.Start()
defer sup.Shutdown()
h := NewHandler(sup, nil, nil, nil, cfg, nil, nil, ks, nil)
body := `{"name":"new_doc","content":"fresh content"}`
req := httptest.NewRequest(http.MethodPost, "/api/v1/knowledge", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
h.handleKnowledge(w, req)
if w.Code != http.StatusCreated {
t.Errorf("expected 201, got %d", w.Code)
}
}
func TestHandleKnowledgeUnavailable(t *testing.T) {
h, sup := newTestHandler(t)
defer sup.Shutdown()
req := httptest.NewRequest(http.MethodGet, "/api/v1/knowledge?q=test", nil)
w := httptest.NewRecorder()
h.handleKnowledge(w, req)
if w.Code != http.StatusServiceUnavailable {
t.Errorf("expected 503, got %d", w.Code)
}
}
func TestHandleMemoryUnavailable(t *testing.T) {
h, sup := newTestHandler(t)
defer sup.Shutdown()
req := httptest.NewRequest(http.MethodGet, "/api/v1/memory?q=test", nil)
w := httptest.NewRecorder()
h.handleMemory(w, req)
if w.Code != http.StatusServiceUnavailable {
t.Errorf("expected 503, got %d", w.Code)
}
}
func TestHandleTrackerNotAvailable(t *testing.T) {
h, sup := newTestHandler(t)
defer sup.Shutdown()
req := httptest.NewRequest(http.MethodGet, "/api/v1/tracker", nil)
w := httptest.NewRecorder()
h.handleTracker(w, req)
if w.Code != http.StatusServiceUnavailable {
t.Errorf("expected 503, got %d", w.Code)
}
}
func TestHandleTrackerStats(t *testing.T) {
tr := tracker.NewTracker(t.TempDir(), t.TempDir())
cfg := &types.Config{
Daemon: types.DaemonConfig{
CheckInterval: time.Minute,
HeartbeatInterval: 30 * time.Second,
},
}
sup := supervisor.New(cfg)
sup.Start()
defer sup.Shutdown()
h := NewHandler(sup, nil, nil, nil, cfg, nil, nil, nil, tr)
req := httptest.NewRequest(http.MethodGet, "/api/v1/tracker", nil)
w := httptest.NewRecorder()
h.handleTracker(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d", w.Code)
}
}
func TestHandleOpenAICompletionsNoMessages(t *testing.T) {
h, sup := newTestHandler(t)
defer sup.Shutdown()
body := `{"model":"test"}`
req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
h.handleOpenAICompletions(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("expected 400, got %d", w.Code)
}
}
func TestHandleOpenAICompletionsLastMsgNotUser(t *testing.T) {
h, sup := newTestHandler(t)
defer sup.Shutdown()
body := `{"messages":[{"role":"assistant","content":"hi"}]}`
req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
h.handleOpenAICompletions(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("expected 400, got %d", w.Code)
}
}
func TestHandleOpenAICompletionsMethodNotAllowed(t *testing.T) {
h, sup := newTestHandler(t)
defer sup.Shutdown()
req := httptest.NewRequest(http.MethodGet, "/v1/chat/completions", nil)
w := httptest.NewRecorder()
h.handleOpenAICompletions(w, req)
if w.Code != http.StatusMethodNotAllowed {
t.Errorf("expected 405, got %d", w.Code)
}
}
func TestHandleStaticServesHTML(t *testing.T) {
h, sup := newTestHandler(t)
defer sup.Shutdown()
req := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
h.handleStatic(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d", w.Code)
}
if !strings.Contains(w.Body.String(), "HomeAgent Dashboard") {
t.Error("expected dashboard HTML")
}
}
func TestHandleStaticNotFound(t *testing.T) {
h, sup := newTestHandler(t)
defer sup.Shutdown()
req := httptest.NewRequest(http.MethodGet, "/nonexistent", nil)
w := httptest.NewRecorder()
h.handleStatic(w, req)
if w.Code != http.StatusNotFound {
t.Errorf("expected 404, got %d", w.Code)
}
}
func TestHandleConfigGet(t *testing.T) {
h, sup := newTestHandler(t)
defer sup.Shutdown()
req := httptest.NewRequest(http.MethodGet, "/api/v1/config", nil)
w := httptest.NewRecorder()
h.handleConfig(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d", w.Code)
}
}
func TestRegisterRoutes(t *testing.T) {
h, sup := newTestHandler(t)
defer sup.Shutdown()
mux := http.NewServeMux()
h.RegisterRoutes(mux)
tests := []struct {
path string
method string
code int
}{
{"/api/v1/status", http.MethodGet, http.StatusOK},
{"/api/v1/agents", http.MethodGet, http.StatusOK},
{"/api/v1/config", http.MethodGet, http.StatusOK},
{"/api/v1/network", http.MethodGet, http.StatusOK},
{"/", http.MethodGet, http.StatusOK},
{"/api/v1/memory", http.MethodGet, http.StatusServiceUnavailable},
{"/api/v1/knowledge", http.MethodGet, http.StatusServiceUnavailable},
{"/api/v1/tracker", http.MethodGet, http.StatusServiceUnavailable},
{"/api/v1/adapters", http.MethodGet, http.StatusServiceUnavailable},
}
for _, tt := range tests {
req := httptest.NewRequest(tt.method, tt.path, nil)
w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
if w.Code != tt.code {
t.Errorf("%s %s: expected %d, got %d", tt.method, tt.path, tt.code, w.Code)
}
}
}
func TestHandleAdapterByIDNotFound(t *testing.T) {
h, sup := newTestHandler(t)
defer sup.Shutdown()
req := httptest.NewRequest(http.MethodGet, "/api/v1/adapters/nonexistent", nil)
w := httptest.NewRecorder()
h.handleAdapterByID(w, req)
// Returns 503 when lua VM is not available
if w.Code != http.StatusServiceUnavailable {
t.Errorf("expected 503, got %d", w.Code)
}
}
func TestHandleAdaptersUnavailable(t *testing.T) {
h, sup := newTestHandler(t)
defer sup.Shutdown()
req := httptest.NewRequest(http.MethodGet, "/api/v1/adapters", nil)
w := httptest.NewRecorder()
h.handleAdapters(w, req)
if w.Code != http.StatusServiceUnavailable {
t.Errorf("expected 503, got %d", w.Code)
}
}