Files

127 lines
4.2 KiB
Go

package httpapi
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"inbox/internal/base/timeutil"
sqlitestore "inbox/internal/store/sqlite"
)
func TestConfigAPIResolveRole(t *testing.T) {
clock := timeutil.FixedClock{Time: time.Date(2026, 3, 13, 15, 0, 0, 0, time.UTC)}
store, err := sqlitestore.OpenInMemory(clock)
if err != nil {
t.Fatalf("OpenInMemory() error = %v", err)
}
defer store.Close()
now := timeutil.FormatRFC3339(clock.Now())
if _, err := store.DB().Exec(`
INSERT INTO projects(id, slug, name, root_path, default_branch, status, created_at, updated_at)
VALUES('proj_1', 'proj', 'Project', '/tmp/project', 'main', 'active', ?, ?)
`, now, now); err != nil {
t.Fatalf("insert project: %v", err)
}
if _, err := store.DB().Exec(`
INSERT INTO workspaces(id, project_id, slug, name, root_path, base_branch, worktree_branch, runtime_backend, status, created_at, updated_at)
VALUES('ws_1', 'proj_1', 'main', 'Main', '/tmp/project', 'main', 'worktree/main', 'local', 'active', ?, ?)
`, now, now); err != nil {
t.Fatalf("insert workspace: %v", err)
}
handler := NewHandler(store, clock)
mustDoJSON(t, handler, http.MethodPut, "/api/v2/config/roles/leader", map[string]any{
"title": "Leader",
"is_enabled": true,
"is_builtin": true,
"updated_by": "tester",
})
mustDoJSON(t, handler, http.MethodPut, "/api/v2/config/roles/leader/prompts/system", map[string]any{
"content_markdown": "global-system",
"updated_by": "tester",
})
mustDoJSON(t, handler, http.MethodPut, "/api/v2/config/roles/leader/prompts/system", map[string]any{
"workspace_id": "ws_1",
"content_markdown": "workspace-system",
"updated_by": "tester",
})
mustDoJSON(t, handler, http.MethodPut, "/api/v2/config/roles/leader/config", map[string]any{
"config_toml": "model = \"gpt-global\"",
"auth_json": "{\"OPENAI_API_KEY\":\"token-1\"}",
"updated_by": "tester",
})
mustDoJSON(t, handler, http.MethodPut, "/api/v2/config/skills/clarify", map[string]any{
"name": "Clarify",
"source_type": "custom",
"content_markdown": "clarify skill",
"status": "active",
"updated_by": "tester",
})
mustDoJSON(t, handler, http.MethodPut, "/api/v2/config/roles/leader/skills/clarify", map[string]any{
"workspace_id": "ws_1",
"is_enabled": true,
"sort_order": 5,
"updated_by": "tester",
})
req := httptest.NewRequest(http.MethodGet, "/api/v2/runtime/roles/leader?workspace_id=ws_1", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("GET resolved role status = %d body=%s", rec.Code, rec.Body.String())
}
var payload struct {
Config struct {
ConfigTOML string `json:"config_toml"`
} `json:"config"`
Prompts map[string]struct {
ContentMarkdown string `json:"content_markdown"`
} `json:"prompts"`
Skills []struct {
Skill struct {
SkillKey string `json:"skill_key"`
} `json:"skill"`
} `json:"skills"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode response: %v", err)
}
if payload.Config.ConfigTOML != "model = \"gpt-global\"" {
t.Fatalf("expected global config_toml, got %q", payload.Config.ConfigTOML)
}
if payload.Prompts["system"].ContentMarkdown != "workspace-system" {
t.Fatalf("expected workspace prompt, got %#v", payload.Prompts["system"])
}
if len(payload.Skills) != 2 {
t.Fatalf("expected 2 skills in payload, got %#v", payload.Skills)
}
if payload.Skills[0].Skill.SkillKey != "clarify" {
t.Fatalf("expected workspace skill clarify first, got %#v", payload.Skills)
}
if payload.Skills[1].Skill.SkillKey != "inbox" {
t.Fatalf("expected builtin inbox skill in payload, got %#v", payload.Skills)
}
}
func mustDoJSON(t *testing.T, handler http.Handler, method, path string, payload map[string]any) {
t.Helper()
body, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal payload: %v", err)
}
req := httptest.NewRequest(method, path, bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("%s %s status = %d body=%s", method, path, rec.Code, rec.Body.String())
}
}