Files
Agent-System/internal/freedoc/create_document_pg_test.go
T
weichenandCursor 9c7d6d49b2 feat(freedoc): CreateDocument with client_key idempotency
Support direct freeform create for upload fact-source registration while
keeping source_content_sha256 as a content fingerprint only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 11:12:03 +00:00

125 lines
4.3 KiB
Go

package freedoc_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"chat-agent-system/internal/db"
"chat-agent-system/internal/docrevision"
"chat-agent-system/internal/freedoc"
)
func TestCreateDocument_PG_ClientKeyIdempotentAndLedgerSurvivesNewService(t *testing.T) {
dsn := db.SkipIfNoTestDatabaseURL(t)
ctx := context.Background()
pool, err := pgxpool.New(ctx, dsn)
require.NoError(t, err)
defer pool.Close()
var hasClientKey bool
err = pool.QueryRow(ctx, `
SELECT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'freeform_documents' AND column_name = 'client_key'
)
`).Scan(&hasClientKey)
if err != nil || !hasClientKey {
t.Skip("freeform_documents.client_key missing; migrate 00029 required")
}
var hasVersions bool
_ = pool.QueryRow(ctx, `
SELECT EXISTS (
SELECT 1 FROM information_schema.tables WHERE table_name = 'document_versions'
)
`).Scan(&hasVersions)
if !hasVersions {
t.Skip("document_versions missing; migrate 00028 required")
}
userID := 91001
clientKey := fmt.Sprintf("doc-upload-pg-%d", time.Now().UnixNano())
rev1 := docrevision.NewService(pool)
svc1 := freedoc.NewService(pool, nil)
svc1.SetDocRevision(rev1)
detail, err := svc1.CreateDocument(ctx, userID, freedoc.CreateFreeformDocRequest{
ProjectID: 1,
Title: "PG上传规格",
ContentMarkdown: "# PG上传规格\n\n基线正文\n",
Space: "my_private",
ClientKey: clientKey,
})
require.NoError(t, err)
docID := detail.Document.DocID
require.NotEmpty(t, docID)
require.NotNil(t, detail.Document.ClientKey)
assert.Equal(t, clientKey, *detail.Document.ClientKey)
assert.NotContains(t, detail.Document.SourceContentSHA256, "doc-upload")
assert.Len(t, detail.Document.SourceContentSHA256, 64)
// client_key 幂等:同一 owner+key 返回同一 doc
again, err := svc1.CreateDocument(ctx, userID, freedoc.CreateFreeformDocRequest{
ProjectID: 1,
Title: "PG上传规格",
ContentMarkdown: "# PG上传规格\n\n基线正文\n",
ClientKey: clientKey,
})
require.NoError(t, err)
assert.Equal(t, docID, again.Document.DocID)
st1, err := rev1.GetRevisionState(ctx, docID)
require.NoError(t, err)
assert.GreaterOrEqual(t, st1.CurrentVersionNo, 1)
patched := "# PG上传规格\n\n基线正文\n\nPG人工修订\n"
_, err = rev1.CommitHumanRevision(ctx, docrevision.HumanRevisionRequest{
DocID: docID, Markdown: patched,
ExpectedHeadSnapshot: &st1.HeadSnapshotID, ExpectedRevisionNo: &st1.HeadRevisionNo,
IdempotencyKey: fmt.Sprintf("pg-upload-patch-%d", time.Now().UnixNano()),
ActorUserID: int64(userID), LifecycleState: "drafting", ProjectID: 1,
})
require.NoError(t, err)
_, err = svc1.UpdateDocument(ctx, userID, docID, freedoc.UpdateFreeformDocRequest{
ContentMarkdown: &patched,
ChangeSummary: "pg patch",
})
require.NoError(t, err)
// 新 Service 实例:client_key 仍可解析,revision-state 与正文可恢复
rev2 := docrevision.NewService(pool)
svc2 := freedoc.NewService(pool, nil)
svc2.SetDocRevision(rev2)
byKey, err := svc2.CreateDocument(ctx, userID, freedoc.CreateFreeformDocRequest{
ProjectID: 1,
Title: "ignored",
ContentMarkdown: "# ignored\n",
ClientKey: clientKey,
})
require.NoError(t, err)
assert.Equal(t, docID, byKey.Document.DocID)
st2, err := rev2.GetRevisionState(ctx, docID)
require.NoError(t, err, "revision-state must hydrate after new Service")
assert.GreaterOrEqual(t, st2.HeadRevisionNo, 1)
got, err := svc2.GetDocument(ctx, userID, docID)
require.NoError(t, err)
assert.Contains(t, got.Content, "PG人工修订")
t.Cleanup(func() {
_, _ = pool.Exec(context.Background(), `DELETE FROM freeform_document_versions WHERE doc_id=$1`, docID)
_, _ = pool.Exec(context.Background(), `DELETE FROM freeform_documents WHERE doc_id=$1`, docID)
_, _ = pool.Exec(context.Background(), `DELETE FROM document_change_events WHERE doc_id=$1`, docID)
_, _ = pool.Exec(context.Background(), `DELETE FROM document_versions WHERE doc_id=$1`, docID)
_, _ = pool.Exec(context.Background(), `DELETE FROM document_snapshots WHERE doc_id=$1`, docID)
_, _ = pool.Exec(context.Background(), `DELETE FROM document_access_audit WHERE doc_id=$1`, docID)
})
}