127 lines
4.2 KiB
Go
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())
|
|
}
|
|
}
|