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) }) }