- internal/controlcenter/importer: scan .doc/ops/{short-term,long-term}
and .doc/backlog for task docs, upsert cc_tasks keyed by slug; maps doc
status/stage, auto-creates the three plans, follows doc status only
while a task is planned/in_progress (terminal statuses never regress),
skips unchanged docs, and resolves cross-directory slug collisions by
fixed directory precedence with a warning
- cmd/control-center: import-docs subcommand (also runs at serve startup
and on the doc-sync ticker) plus the agent CLI: next / instruction /
claim / heartbeat / release / event / artifact / advance-request /
decision / report, thin net/http client, compact JSON output
- console API POST /api/v1/tasks/{id}/export-doc renders a task per
.doc/ops/templates/short-term-task.md into .doc/ops/short-term/, refuses
overwrite (409) and unsafe slugs, then backfills doc_path
- fix pre-existing TestAgentHasNoAdvanceRoute: SPA catch-all GET / makes
the mux answer 405 for unmatched POST paths; accept 404 or 405
- archtest allowlist: controlcenter/importer -> pipeline, store
86 lines
2.9 KiB
Go
86 lines
2.9 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"chat-agent-system/internal/controlcenter/config"
|
|
"chat-agent-system/internal/controlcenter/store"
|
|
)
|
|
|
|
func newExportTestServer(t *testing.T, repoRoot string) *httptest.Server {
|
|
t.Helper()
|
|
dsn := os.Getenv("TEST_CC_DB_DSN")
|
|
if dsn == "" {
|
|
t.Skip("TEST_CC_DB_DSN not set")
|
|
}
|
|
_, thisFile, _, _ := runtime.Caller(0)
|
|
migrationsDir := filepath.Join(filepath.Dir(thisFile), "..", "..", "..", "deploy", "control-center", "goose")
|
|
ctx := t.Context()
|
|
require.NoError(t, store.Migrate(ctx, dsn, migrationsDir))
|
|
st, err := store.Connect(ctx, dsn)
|
|
require.NoError(t, err)
|
|
t.Cleanup(st.Close)
|
|
|
|
srv := New(config.Config{ConsoleToken: testConsoleToken, RepoRoot: repoRoot}, st)
|
|
ts := httptest.NewServer(srv.Handler())
|
|
t.Cleanup(ts.Close)
|
|
return ts
|
|
}
|
|
|
|
func TestTaskExportDoc(t *testing.T) {
|
|
repoRoot := t.TempDir()
|
|
ts := newExportTestServer(t, repoRoot)
|
|
|
|
taskSlug := slug("export")
|
|
code, raw := doReq(t, "POST", ts.URL+"/api/v1/tasks", testConsoleToken, map[string]any{
|
|
"slug": taskSlug, "title": "导出任务", "instruction_md": "## 目标\n\n验证导出。\n",
|
|
})
|
|
require.Equal(t, http.StatusCreated, code, string(raw))
|
|
id := decodeBody(t, raw)["id"].(string)
|
|
|
|
code, raw = doReq(t, "POST", ts.URL+"/api/v1/tasks/"+id+"/export-doc", testConsoleToken, nil)
|
|
require.Equal(t, http.StatusCreated, code, string(raw))
|
|
docPath := decodeBody(t, raw)["doc_path"].(string)
|
|
today := time.Now().Format("2006-01-02")
|
|
assert.Equal(t, ".doc/ops/short-term/"+today+"-"+taskSlug+".md", docPath)
|
|
|
|
content, err := os.ReadFile(filepath.Join(repoRoot, filepath.FromSlash(docPath)))
|
|
require.NoError(t, err)
|
|
md := string(content)
|
|
assert.True(t, strings.HasPrefix(md, "---\n"), "front matter first")
|
|
assert.Contains(t, md, `title: "导出任务"`)
|
|
assert.Contains(t, md, "status: planned\n")
|
|
assert.Contains(t, md, "created: "+today+"\n")
|
|
assert.Contains(t, md, "# 导出任务")
|
|
assert.Contains(t, md, "## 目标")
|
|
|
|
// task doc_path updated
|
|
code, raw = doReq(t, "GET", ts.URL+"/api/v1/tasks/"+id, testConsoleToken, nil)
|
|
require.Equal(t, http.StatusOK, code)
|
|
assert.Equal(t, docPath, decodeBody(t, raw)["doc_path"])
|
|
|
|
// second export conflicts, file untouched
|
|
code, raw = doReq(t, "POST", ts.URL+"/api/v1/tasks/"+id+"/export-doc", testConsoleToken, nil)
|
|
assert.Equal(t, http.StatusConflict, code, string(raw))
|
|
assert.Contains(t, string(raw), "already exists")
|
|
|
|
// unsafe slug rejected
|
|
code, raw = doReq(t, "POST", ts.URL+"/api/v1/tasks", testConsoleToken, map[string]any{
|
|
"slug": slug("Bad_Slug"), "title": "不可导出",
|
|
})
|
|
require.Equal(t, http.StatusCreated, code, string(raw))
|
|
badID := decodeBody(t, raw)["id"].(string)
|
|
code, raw = doReq(t, "POST", ts.URL+"/api/v1/tasks/"+badID+"/export-doc", testConsoleToken, nil)
|
|
assert.Equal(t, http.StatusBadRequest, code, string(raw))
|
|
}
|