Files
Agent-System-Backend/internal/store/document_delivery_test.go
weichen fec2d16cc6 Close Task 10 renderer delivery gaps
Why:
- Independent review found trusted-boundary, semantic parity, replay-budget,
  comparison, persistence, cancellation, provenance, and routing gaps.
- Storage migration must include the new durable delivery table.

Evidence:
- go test -count=1 ./...
- go test -race ./internal/docgen ./internal/tools ./internal/runmgr
- go test ./... (deps/agent-document-renderer)
- go vet ./...
- go build ./...
- CGO_ENABLED=0 go build -trimpath ./cmd/agent-system
- Python house-style unittest suite and git diff --check

Impact:
- Word delivery now uses host-owned freezes, Markdown-first resumable persistence,
  closed registration boundaries, semantic shared rendering, and real comparison.
- Controlled/project documents still fail closed to RDM and non-Word flows remain
  unchanged.

Refs:
- Unified Document Renderer implementation plan Task 10
- /tmp/task10-backend-review-last.md
- Renderer d5cd8c126045a27d5a5cc5da23f64d3c737e116a
2026-10-15 21:44:03 +00:00

230 lines
10 KiB
Go

package store
import (
"crypto/sha256"
"encoding/hex"
"errors"
"path/filepath"
"reflect"
"testing"
core "agent-system-core"
)
func TestDocumentDeliveryFreezesOnceAndRejectsOperationConflict(t *testing.T) {
st := openTestStore(t)
conversation := seedConversation(t, st)
input := deliveryBeginFixture(conversation, "operation-freeze")
first, err := st.BeginDocumentDeliveryAccountChecked(OwnerDefault, input, true)
if err != nil {
t.Fatal(err)
}
second, err := st.BeginDocumentDeliveryAccountChecked(OwnerDefault, input, true)
if err != nil {
t.Fatal(err)
}
if first.FrozenAt.IsZero() || !first.FrozenAt.Equal(second.FrozenAt) || first.State != DocumentDeliveryFrozen {
t.Fatalf("freeze records differ: first=%#v second=%#v", first, second)
}
conflict := input
conflict.RequestSHA256 = hashText("different request")
if _, err := st.BeginDocumentDeliveryAccountChecked(OwnerDefault, conflict, true); !errors.Is(err, ErrDocumentDeliveryConflict) {
t.Fatalf("conflicting operation error = %v, want ErrDocumentDeliveryConflict", err)
}
}
func TestDocumentDeliveryPersistsMarkdownFirstAndCommitsDerivedSetAtomically(t *testing.T) {
st := openTestStore(t)
conversation := seedConversation(t, st)
input := deliveryBeginFixture(conversation, "operation-complete")
record, err := st.BeginDocumentDeliveryAccountChecked(OwnerDefault, input, true)
if err != nil {
t.Fatal(err)
}
markdown := deliveryArtifactFixture(DocumentArtifactMarkdown, "report.md", "text/markdown; charset=utf-8", []byte("# report\n"))
record, err = st.PersistDocumentDeliveryMarkdownAccountChecked(OwnerDefault, input.OperationID, markdown, hashText("canonical"), true)
if err != nil {
t.Fatal(err)
}
if record.State != DocumentDeliverySourceReady || len(record.Artifacts) != 1 || record.Artifacts[0].Role != DocumentArtifactMarkdown {
t.Fatalf("source receipt = %#v", record)
}
if raw, err := st.ArtifactContent(OwnerDefault, record.Artifacts[0].Artifact.ID); err != nil || string(raw) != "# report\n" {
t.Fatalf("persisted Markdown = %q err=%v", raw, err)
}
derived := []DocumentDeliveryArtifactInput{
deliveryArtifactFixture(DocumentArtifactDOCX, "report.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", []byte("docx")),
deliveryArtifactFixture(DocumentArtifactManifest, "report.manifest.json", "application/json", []byte("manifest")),
deliveryArtifactFixture(DocumentArtifactArchive, "report.zip", "application/zip", []byte("archive")),
}
record, err = st.CommitDocumentDeliveryDerivedAccountChecked(OwnerDefault, input.OperationID, "delivery-key", "profile-hash", "review-hash", derived, true)
if err != nil {
t.Fatal(err)
}
if record.State != DocumentDeliveryComplete || len(record.Artifacts) != 4 || record.DeliveryKey != "delivery-key" {
t.Fatalf("complete receipt = %#v", record)
}
firstIDs := deliveryArtifactIDs(record.Artifacts)
retry, err := st.CommitDocumentDeliveryDerivedAccountChecked(OwnerDefault, input.OperationID, "delivery-key", "profile-hash", "review-hash", derived, true)
if err != nil || !reflect.DeepEqual(firstIDs, deliveryArtifactIDs(retry.Artifacts)) {
t.Fatalf("idempotent derived retry = %#v err=%v", retry, err)
}
artifacts, err := st.ListArtifacts(OwnerDefault, string(conversation.ProjectID))
if err != nil || len(artifacts) != 4 {
t.Fatalf("visible artifact set = %d err=%v", len(artifacts), err)
}
}
func TestDocumentDeliveryFailureAfterEveryWriteIsDurableAndRetryable(t *testing.T) {
stages := []string{
DocumentDeliveryFaultAfterMarkdownBlob,
DocumentDeliveryFaultBeforeMarkdownCommit,
DocumentDeliveryFaultAfterDOCXBlob,
DocumentDeliveryFaultAfterManifestBlob,
DocumentDeliveryFaultAfterArchiveBlob,
DocumentDeliveryFaultBeforeDerivedCommit,
DocumentDeliveryFaultAfterDerivedCommit,
}
for _, stage := range stages {
t.Run(stage, func(t *testing.T) {
st := openTestStore(t)
conversation := seedConversation(t, st)
operationID := "fault-" + stage
input := deliveryBeginFixture(conversation, operationID)
if _, err := st.BeginDocumentDeliveryAccountChecked(OwnerDefault, input, true); err != nil {
t.Fatal(err)
}
markdown := deliveryArtifactFixture(DocumentArtifactMarkdown, "fault.md", "text/markdown", []byte("source"))
derived := []DocumentDeliveryArtifactInput{
deliveryArtifactFixture(DocumentArtifactDOCX, "fault.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", []byte("docx")),
deliveryArtifactFixture(DocumentArtifactManifest, "fault.manifest.json", "application/json", []byte("manifest")),
deliveryArtifactFixture(DocumentArtifactArchive, "fault.zip", "application/zip", []byte("archive")),
}
st.SetDocumentDeliveryFault(stage, errors.New("injected "+stage))
var receipt DocumentDeliveryRecord
var err error
if stage == DocumentDeliveryFaultAfterMarkdownBlob || stage == DocumentDeliveryFaultBeforeMarkdownCommit {
receipt, err = st.PersistDocumentDeliveryMarkdownAccountChecked(OwnerDefault, operationID, markdown, hashText("canonical"), true)
} else {
if _, err = st.PersistDocumentDeliveryMarkdownAccountChecked(OwnerDefault, operationID, markdown, hashText("canonical"), true); err != nil {
t.Fatal(err)
}
receipt, err = st.CommitDocumentDeliveryDerivedAccountChecked(OwnerDefault, operationID, "delivery-key", "profile-hash", "review-hash", derived, true)
}
if err == nil || receipt.OperationID != operationID {
t.Fatalf("fault receipt=%#v err=%v", receipt, err)
}
if stage == DocumentDeliveryFaultAfterDerivedCommit {
if !errors.Is(err, ErrDocumentDeliveryOutcomeUnknown) || receipt.State != DocumentDeliveryComplete {
t.Fatalf("post-commit outcome=%#v err=%v", receipt, err)
}
} else if stage != DocumentDeliveryFaultAfterMarkdownBlob && stage != DocumentDeliveryFaultBeforeMarkdownCommit && receipt.MarkdownArtifactID == "" {
t.Fatalf("derived failure lost authoritative Markdown: %#v", receipt)
}
visible, listErr := st.ListArtifacts(OwnerDefault, string(conversation.ProjectID))
wantVisible := 1
if stage == DocumentDeliveryFaultAfterMarkdownBlob || stage == DocumentDeliveryFaultBeforeMarkdownCommit {
wantVisible = 0
}
if stage == DocumentDeliveryFaultAfterDerivedCommit {
wantVisible = 4
}
if listErr != nil || len(visible) != wantVisible {
t.Fatalf("failure exposed %d artifacts, want %d: %v", len(visible), wantVisible, listErr)
}
st.SetDocumentDeliveryFault("", nil)
if receipt.MarkdownArtifactID == "" {
if _, err := st.PersistDocumentDeliveryMarkdownAccountChecked(OwnerDefault, operationID, markdown, hashText("canonical"), true); err != nil {
t.Fatal(err)
}
}
complete, err := st.CommitDocumentDeliveryDerivedAccountChecked(OwnerDefault, operationID, "delivery-key", "profile-hash", "review-hash", derived, true)
if err != nil || complete.State != DocumentDeliveryComplete || len(complete.Artifacts) != 4 {
t.Fatalf("retry did not complete: %#v err=%v", complete, err)
}
})
}
}
func TestDocumentDeliveryRetryAfterRestartReturnsSameIDsAndHashes(t *testing.T) {
directory := t.TempDir()
database := filepath.Join(directory, "delivery.db")
st, err := OpenSQLite(database)
if err != nil {
t.Fatal(err)
}
project, err := st.CreateProject(OwnerDefault, "delivery")
if err != nil {
t.Fatal(err)
}
conversation, err := st.CreateConversation(OwnerDefault, project.ID, "delivery", "")
if err != nil {
t.Fatal(err)
}
input := deliveryBeginFixture(conversation, "restart-operation")
if _, err := st.BeginDocumentDeliveryAccountChecked(OwnerDefault, input, true); err != nil {
t.Fatal(err)
}
markdown := deliveryArtifactFixture(DocumentArtifactMarkdown, "restart.md", "text/markdown", []byte("source"))
if _, err := st.PersistDocumentDeliveryMarkdownAccountChecked(OwnerDefault, input.OperationID, markdown, hashText("canonical"), true); err != nil {
t.Fatal(err)
}
derived := []DocumentDeliveryArtifactInput{
deliveryArtifactFixture(DocumentArtifactDOCX, "restart.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", []byte("docx")),
deliveryArtifactFixture(DocumentArtifactManifest, "restart.manifest.json", "application/json", []byte("manifest")),
deliveryArtifactFixture(DocumentArtifactArchive, "restart.zip", "application/zip", []byte("archive")),
}
first, err := st.CommitDocumentDeliveryDerivedAccountChecked(OwnerDefault, input.OperationID, "restart-key", "profile", "review", derived, true)
if err != nil {
t.Fatal(err)
}
if err := st.Close(); err != nil {
t.Fatal(err)
}
reopened, err := OpenSQLite(database)
if err != nil {
t.Fatal(err)
}
defer reopened.Close()
begin, err := reopened.BeginDocumentDeliveryAccountChecked(OwnerDefault, input, true)
if err != nil {
t.Fatal(err)
}
retry, err := reopened.CommitDocumentDeliveryDerivedAccountChecked(OwnerDefault, input.OperationID, "restart-key", "profile", "review", derived, true)
if err != nil || !begin.FrozenAt.Equal(first.FrozenAt) || !reflect.DeepEqual(deliveryArtifactIDs(first.Artifacts), deliveryArtifactIDs(retry.Artifacts)) {
t.Fatalf("restart retry changed identity: first=%#v begin=%#v retry=%#v err=%v", first, begin, retry, err)
}
}
func deliveryBeginFixture(conversation core.Conversation, operationID string) DocumentDeliveryBeginInput {
return DocumentDeliveryBeginInput{
OperationID: operationID, RequestSHA256: hashText("request-" + operationID),
ProjectID: string(conversation.ProjectID), ConversationID: string(conversation.ID),
SourceRun: "run-1", DocumentID: "document-1", Mode: "preview", RendererIdentity: "renderer@revision",
}
}
func deliveryArtifactFixture(role DocumentDeliveryArtifactRole, name, mime string, content []byte) DocumentDeliveryArtifactInput {
return DocumentDeliveryArtifactInput{Role: role, Name: name, Kind: "document", MIME: mime, SHA256: hashBytesForTest(content), Content: content}
}
func deliveryArtifactIDs(artifacts []DocumentDeliveryArtifact) map[DocumentDeliveryArtifactRole]string {
ids := make(map[DocumentDeliveryArtifactRole]string, len(artifacts))
for _, artifact := range artifacts {
ids[artifact.Role] = string(artifact.Artifact.ID) + ":" + artifact.SHA256
}
return ids
}
func hashText(value string) string { return hashBytesForTest([]byte(value)) }
func hashBytesForTest(value []byte) string {
sum := sha256.Sum256(value)
return hex.EncodeToString(sum[:])
}