Stage generation without a real document must not panic or enter the ledger. Unknown orphan IDs are denied, write paths check author/lease/lifecycle, PATCH no longer returns a fake 200, and overlay matching survives client/server block id drift. Co-authored-by: Cursor <cursoragent@cursor.com>
87 lines
2.9 KiB
Go
87 lines
2.9 KiB
Go
package docrevision_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"chat-agent-system/internal/db"
|
|
"chat-agent-system/internal/docrevision"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
func TestPGPersistSurvivesNewServiceInstance(t *testing.T) {
|
|
dsn := db.SkipIfNoTestDatabaseURL(t)
|
|
ctx := context.Background()
|
|
pool, err := pgxpool.New(ctx, dsn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer pool.Close()
|
|
|
|
// Best-effort: skip if schema 00028 tables missing.
|
|
var n int
|
|
if err := pool.QueryRow(ctx, `SELECT COUNT(*) FROM information_schema.tables WHERE table_name='document_versions'`).Scan(&n); err != nil || n == 0 {
|
|
t.Skip("document_versions table missing; migrate 00028 required")
|
|
}
|
|
|
|
docID := "doc_pg_persist_test"
|
|
_, _ = pool.Exec(ctx, `DELETE FROM document_change_events WHERE doc_id=$1`, docID)
|
|
_, _ = pool.Exec(ctx, `DELETE FROM document_agent_candidates WHERE doc_id=$1`, docID)
|
|
_, _ = pool.Exec(ctx, `DELETE FROM document_change_reports WHERE doc_id=$1`, docID)
|
|
_, _ = pool.Exec(ctx, `DELETE FROM document_versions WHERE doc_id=$1`, docID)
|
|
_, _ = pool.Exec(ctx, `DELETE FROM document_snapshots WHERE doc_id=$1`, docID)
|
|
|
|
svc1 := docrevision.NewService(pool)
|
|
st, err := svc1.OpenTemplateVersion(ctx, docID, "# persist\n\nhello\n", docrevision.ActorSnapshot{DisplayName: "t"}, 1, "ws/persist")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = svc1.CommitHumanRevision(ctx, docrevision.HumanRevisionRequest{
|
|
DocID: docID, Markdown: "# persist\n\nhello edited\n",
|
|
ExpectedHeadSnapshot: &st.HeadSnapshotID, ExpectedRevisionNo: &st.HeadRevisionNo,
|
|
IdempotencyKey: "pg-e1", ActorUserID: 1, LifecycleState: "drafting", WorkDir: "ws/persist", ProjectID: 1,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Simulate process restart: brand-new service, same pool.
|
|
svc2 := docrevision.NewService(pool)
|
|
got, err := svc2.GetRevisionState(ctx, docID)
|
|
if err != nil {
|
|
t.Fatalf("hydrate after restart: %v", err)
|
|
}
|
|
if got.HeadRevisionNo != 1 {
|
|
t.Fatalf("want revision 1 after restart, got %+v", got)
|
|
}
|
|
ov, err := svc2.ManualOverlay(ctx, docID)
|
|
if err != nil || ov == nil {
|
|
t.Fatalf("overlay after restart: %v", err)
|
|
}
|
|
|
|
st2, err := svc2.GetRevisionState(ctx, docID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cand, _, err := svc2.CreateAgentCandidate(ctx, docrevision.AgentCandidateRequest{
|
|
DocID: docID, RunID: "pg-cand-1", InputSnapshotID: st2.HeadSnapshotID,
|
|
OutputMarkdown: "# persist\n\nhello edited by agent\n",
|
|
ActorKind: docrevision.ActorAgent,
|
|
})
|
|
if err != nil || cand == nil {
|
|
t.Fatalf("create candidate: %v", err)
|
|
}
|
|
|
|
svc3 := docrevision.NewService(pool)
|
|
gotCand, err := svc3.GetCandidate(ctx, docID, cand.ID)
|
|
if err != nil || gotCand == nil || gotCand.DocID != docID {
|
|
t.Fatalf("hydrate candidate after restart: cand=%v err=%v", gotCand, err)
|
|
}
|
|
if _, err := svc3.CommitAgentVersion(ctx, docID, cand.ID, docrevision.CommitAgentDecision{
|
|
Decision: "reject", ActorUserID: 1,
|
|
}); err != nil {
|
|
t.Fatalf("decision after restart: %v", err)
|
|
}
|
|
}
|