multica/server/internal/daemon/daemon_test.go
Jiayuan Zhang 02df33803a feat: structured skills system with meta skill runtime injection
Replace agent.skills TEXT field with structured skill/skill_file/agent_skill
tables. Skills are workspace-level entities with supporting files, reusable
across agents via many-to-many bindings.

Backend: migration 008, sqlc queries, CRUD handler, agent-skill junction,
structured skill loading in task context snapshot.

Daemon: meta skill injection via runtime-native config (.claude/CLAUDE.md
for Claude, AGENTS.md for Codex) so agents discover .agent_context/ skills
through their native mechanism. Lean prompt without inlined skill content.

Frontend: Skills management page, agent Skills tab picker, SDK methods,
TypeScript types, workspace store integration.

Also removes auto-creation of init issues when creating agents.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 15:17:59 +08:00

97 lines
2.4 KiB
Go

package daemon
import (
"net/http"
"strings"
"testing"
)
func TestNormalizeServerBaseURL(t *testing.T) {
t.Parallel()
got, err := NormalizeServerBaseURL("ws://localhost:8080/ws")
if err != nil {
t.Fatalf("NormalizeServerBaseURL returned error: %v", err)
}
if got != "http://localhost:8080" {
t.Fatalf("expected http://localhost:8080, got %s", got)
}
}
func TestBuildPromptIncludesIssueAndContext(t *testing.T) {
t.Parallel()
prompt := BuildPrompt(Task{
Context: TaskContext{
Issue: IssueContext{
Title: "Fix failing test",
Description: "Investigate and fix the test failure.",
AcceptanceCriteria: []string{"tests pass"},
},
Agent: AgentContext{
Name: "Local Codex",
Skills: []SkillData{
{Name: "Concise", Content: "Be concise."},
},
},
},
})
// Lean prompt: issue + acceptance criteria only. No inlined skill content.
for _, want := range []string{
"Fix failing test",
"Investigate and fix the test failure.",
"tests pass",
} {
if !strings.Contains(prompt, want) {
t.Fatalf("prompt missing %q", want)
}
}
// Skills should NOT be inlined in the prompt (they're in runtime config).
for _, absent := range []string{"## Agent Skills", "Be concise."} {
if strings.Contains(prompt, absent) {
t.Fatalf("prompt should NOT contain %q (skills are in runtime config)", absent)
}
}
}
func TestBuildPromptTruncatesLongDescription(t *testing.T) {
t.Parallel()
longDesc := strings.Repeat("x", 300)
prompt := BuildPrompt(Task{
Context: TaskContext{
Issue: IssueContext{
Title: "Long desc",
Description: longDesc,
},
Agent: AgentContext{Name: "Test"},
},
})
if strings.Contains(prompt, longDesc) {
t.Fatal("expected long description to be truncated in prompt")
}
if !strings.Contains(prompt, "...") {
t.Fatal("expected truncation marker")
}
}
func TestIsWorkspaceNotFoundError(t *testing.T) {
t.Parallel()
err := &requestError{
Method: http.MethodPost,
Path: "/api/daemon/register",
StatusCode: http.StatusNotFound,
Body: `{"error":"workspace not found"}`,
}
if !isWorkspaceNotFoundError(err) {
t.Fatal("expected workspace not found error to be recognized")
}
if isWorkspaceNotFoundError(&requestError{StatusCode: http.StatusInternalServerError, Body: `{"error":"workspace not found"}`}) {
t.Fatal("did not expect 500 to be treated as workspace not found")
}
}