fix(httpapi): 封存队列作业 id 按 jobs.id 列宽拼短
build / build (push) Successful in 1m17s

真实请求 POST /api/v1/library/captures 一律 500:拼出来的
"library-capture-<scope>-<nano>" 是 45 个字符,而 jobs.id 是 varchar(36),
PG 直接拒绝(value too long)。此前页面一直停在"未接线",这条路径从没被
真实请求覆盖过,所以只有接上 agent 之后才暴露。

改为 libcap-<scope>-<nano>(35 字符),并用测试锁住列宽与形状。

试过:go test ./internal/httpapi -run LibraryCapture 全绿;修复前用真实
DATABASE_URL 复现 enqueue error: value too long for type character varying(36)。
This commit is contained in:
姚章浩
2026-10-29 06:05:16 +08:00
parent 37a8370f7b
commit f2d6f3f646
2 changed files with 25 additions and 1 deletions
+4 -1
View File
@@ -97,7 +97,10 @@ func libraryCaptureEnqueue(w http.ResponseWriter, r *http.Request, config Config
}
}
}
requestedID := fmt.Sprintf("library-capture-%s-%d", request.Scope, now.UnixNano())
// 队列作业 id 落进 jobs.id(varchar(36)):原来拼 "library-capture-<scope>-<nano>"
// 是 45 个字符,真实请求 100% 被 PG 拒(value too long)——只是页面一直停在
// "未接线"上,没人走到这里。这里按列宽拼短 id,并留出 scope 全称。
requestedID := fmt.Sprintf("libcap-%s-%d", request.Scope, now.UnixNano())
// The idempotency key is the capture job id: the same logical capture must map
// to one queue row. Binding the scope into the key would silently create a
// second job instead of reporting a conflicting request, so the conflict is
+21
View File
@@ -86,6 +86,27 @@ func TestLibraryCaptureRejectsInvalidRequest(t *testing.T) {
}
}
func TestLibraryCaptureQueueIDFitsTheColumn(t *testing.T) {
// jobs.id 是 varchar(36):拼长的 id 会在真实 PG 上 500(value too long)。
// 页面长期停在"未接线",这条从没被真实请求覆盖过,所以用测试锁住列宽。
for _, scope := range []string{"internal", "external"} {
store := &fakeJobStore{enqueueEchoRequestedID: true}
recorder := httptest.NewRecorder()
libraryCaptureEnqueue(recorder, captureRequest(`{"scope":"`+scope+`"}`),
Config{Store: store, LibraryAgentSocket: "/tmp/agent.sock"})
if recorder.Code != http.StatusAccepted {
t.Fatalf("%s status=%d body=%s", scope, recorder.Code, recorder.Body.String())
}
enqueued := store.enqueued
if len(enqueued.ID) > 36 {
t.Fatalf("%s 队列作业 id 超长(%d):%q", scope, len(enqueued.ID), enqueued.ID)
}
if !strings.HasPrefix(enqueued.ID, "libcap-"+scope+"-") {
t.Fatalf("%s 队列作业 id 形状变了:%q", scope, enqueued.ID)
}
}
}
func TestLibraryCaptureJobsAsksStoreForItsOwnType(t *testing.T) {
// The global queue is busy with unrelated traffic; the capture job must still
// be visible because the query is type-scoped, not filtered after the fact.