910c0fcd76
build / build (push) Failing after 16s
- libraryagent:0700 目录内 0600 unix socket + SO_PEERCRED 同 uid, 固定 binary/归档/日志/项目/写入者声明,请求只能选范围与作业号 - API:POST/GET /api/v1/library/captures(入队 library.capture + 读队列状态) - worker:library.capture handler 走 socket;启动先 recover;不挂 Docker socket、 不持有来源凭据 - web:整库版本页显示真实作业状态并可发起封存(未接线/无权限给出原因) - scripts/library-agent-install.sh:构建、安装 systemd user 服务并读回权限
112 lines
3.8 KiB
Go
112 lines
3.8 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"knowledge-governance/internal/jobs"
|
|
)
|
|
|
|
func captureRequest(body string) *http.Request {
|
|
request := httptest.NewRequest(http.MethodPost, "/api/v1/library/captures", strings.NewReader(body))
|
|
request.Header.Set("Content-Type", "application/json")
|
|
return request
|
|
}
|
|
|
|
// An unwired agent must refuse clearly instead of queueing a job nobody can run.
|
|
func TestLibraryCaptureRefusedWhenAgentUnwired(t *testing.T) {
|
|
store := &fakeJobStore{}
|
|
recorder := httptest.NewRecorder()
|
|
libraryCaptureEnqueue(recorder, captureRequest(`{"scope":"internal"}`), Config{Store: store})
|
|
if recorder.Code != http.StatusServiceUnavailable {
|
|
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
|
}
|
|
var body map[string]any
|
|
if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if body["code"] != "library_capture_unwired" || body["reason"] == "" {
|
|
t.Fatalf("unexpected body %v", body)
|
|
}
|
|
if store.enqueued.Type != "" {
|
|
t.Fatal("unwired agent still enqueued a job")
|
|
}
|
|
}
|
|
|
|
func TestLibraryCaptureEnqueuesFixedJobShape(t *testing.T) {
|
|
store := &fakeJobStore{enqueueEchoRequestedID: true}
|
|
recorder := httptest.NewRecorder()
|
|
libraryCaptureEnqueue(recorder, captureRequest(`{"scope":"external","job":"lib-external-review"}`),
|
|
Config{Store: store, LibraryAgentSocket: "/run/library-agent/agent.sock"})
|
|
if recorder.Code != http.StatusAccepted {
|
|
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
|
}
|
|
enqueued := store.enqueued
|
|
if enqueued.Type != libraryCaptureJobType {
|
|
t.Fatalf("job type %q", enqueued.Type)
|
|
}
|
|
if enqueued.IdempotencyKey != "library.capture:lib-external-review" {
|
|
t.Fatalf("idempotency key %q", enqueued.IdempotencyKey)
|
|
}
|
|
var payload map[string]string
|
|
if err := json.Unmarshal(enqueued.Payload, &payload); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if payload["scope"] != "external" || payload["capture_job"] != "lib-external-review" {
|
|
t.Fatalf("payload %v", payload)
|
|
}
|
|
var body map[string]any
|
|
if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if body["capture_job"] != "lib-external-review" || body["scope"] != "external" {
|
|
t.Fatalf("response %v", body)
|
|
}
|
|
}
|
|
|
|
func TestLibraryCaptureRejectsInvalidRequest(t *testing.T) {
|
|
for _, body := range []string{
|
|
`{"scope":"production"}`,
|
|
`{"scope":"internal","job":"bad job; rm -rf /"}`,
|
|
`not json`,
|
|
} {
|
|
store := &fakeJobStore{}
|
|
recorder := httptest.NewRecorder()
|
|
libraryCaptureEnqueue(recorder, captureRequest(body), Config{Store: store, LibraryAgentSocket: "/tmp/agent.sock"})
|
|
if recorder.Code != http.StatusBadRequest {
|
|
t.Fatalf("body %q status=%d", body, recorder.Code)
|
|
}
|
|
if store.enqueued.Type != "" {
|
|
t.Fatalf("invalid request %q still enqueued", body)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLibraryCaptureJobsListsOnlyCaptureJobs(t *testing.T) {
|
|
store := &fakeJobStore{listResult: []jobs.Job{
|
|
{ID: "a", Type: libraryCaptureJobType, State: "completed",
|
|
Payload: json.RawMessage(`{"scope":"internal","capture_job":"lib-internal-1","requested_by":"yaozhanghao"}`),
|
|
CreatedAt: time.Date(2026, 10, 28, 3, 0, 0, 0, time.UTC)},
|
|
{ID: "b", Type: "artifact.fetch", State: "completed"},
|
|
}}
|
|
recorder := httptest.NewRecorder()
|
|
libraryCaptureJobs(recorder, httptest.NewRequest(http.MethodGet, "/api/v1/library/captures", nil), Config{Store: store})
|
|
if recorder.Code != http.StatusOK {
|
|
t.Fatalf("status=%d", recorder.Code)
|
|
}
|
|
var body struct {
|
|
Available bool `json:"available"`
|
|
Jobs []map[string]any `json:"jobs"`
|
|
}
|
|
if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !body.Available || len(body.Jobs) != 1 || body.Jobs[0]["capture_job"] != "lib-internal-1" {
|
|
t.Fatalf("unexpected body %+v", body)
|
|
}
|
|
}
|