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>
125 lines
4.3 KiB
Go
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)
|
|
})
|
|
}
|