Consolidate document draft management into updateDocument
Replace the three separate draft mutations (createDraftDocumentVersion, updateDocumentVersion, deleteDraftDocumentVersion) with automatic draft lifecycle management inside updateDocument. The backend now auto-creates a draft when a published document is edited, updates the existing draft on subsequent edits, and auto-deletes the draft when content reverts to match the published version. A new deleteDocumentDraft mutation provides explicit draft deletion. Backend: - Merge version-level fields (content, title, classification, documentType) into UpdateDocumentRequest - Convert CreateDraft, UpdateVersion, DeleteDraft into private transaction helpers called from Update - Update returns (*Document, *DocumentVersion, error) with the version present only when a draft exists Frontend: - Remove all create/update/delete draft mutations from components - Auto-save via updateDocument with layout refetch on draft status transitions while preserving editor cursor (data-generation key) - Title, type, and classification editable on published versions (backend auto-creates draft) - Forms use react-hook-form values option to stay synced with Relay fragment data across draft/publish transitions API surface (GraphQL, MCP, CLI, n8n) updated consistently: - Removed: createDraftDocumentVersion, updateDocumentVersion, deleteDraftDocumentVersion - Added: deleteDocumentDraft (document-level) - Updated: updateDocument accepts content, classification, documentType Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -290,18 +290,17 @@ func TestDocument_Update(t *testing.T) {
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
|
||||
t.Run(
|
||||
"update title via document version",
|
||||
"update title via document",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
doc := factory.NewDocument(owner).
|
||||
WithTitle("Document to Update")
|
||||
doc.Create()
|
||||
versionID := doc.VersionID()
|
||||
documentID := doc.Create()
|
||||
|
||||
query := `
|
||||
mutation UpdateDocumentVersion($input: UpdateDocumentVersionInput!) {
|
||||
updateDocumentVersion(input: $input) {
|
||||
mutation UpdateDocument($input: UpdateDocumentInput!) {
|
||||
updateDocument(input: $input) {
|
||||
documentVersion {
|
||||
id
|
||||
title
|
||||
@@ -311,33 +310,32 @@ func TestDocument_Update(t *testing.T) {
|
||||
`
|
||||
|
||||
var result struct {
|
||||
UpdateDocumentVersion struct {
|
||||
UpdateDocument struct {
|
||||
DocumentVersion struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
} `json:"documentVersion"`
|
||||
} `json:"updateDocumentVersion"`
|
||||
} `json:"updateDocument"`
|
||||
}
|
||||
|
||||
err := owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"documentVersionId": versionID,
|
||||
"title": "Updated Document Title",
|
||||
"id": documentID,
|
||||
"title": "Updated Document Title",
|
||||
},
|
||||
}, &result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Updated Document Title", result.UpdateDocumentVersion.DocumentVersion.Title)
|
||||
assert.Equal(t, "Updated Document Title", result.UpdateDocument.DocumentVersion.Title)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestDocumentVersion_Update_TitleValidation(t *testing.T) {
|
||||
func TestDocument_Update_TitleValidation(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
|
||||
doc := factory.NewDocument(owner).WithTitle("Validation Test Document")
|
||||
doc.Create()
|
||||
baseVersionID := doc.VersionID()
|
||||
baseDocumentID := doc.Create()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -347,41 +345,41 @@ func TestDocumentVersion_Update_TitleValidation(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "title with HTML tags",
|
||||
setup: func() string { return baseVersionID },
|
||||
setup: func() string { return baseDocumentID },
|
||||
input: func(id string) map[string]any {
|
||||
return map[string]any{"documentVersionId": id, "title": "<script>alert('xss')</script>"}
|
||||
return map[string]any{"id": id, "title": "<script>alert('xss')</script>"}
|
||||
},
|
||||
wantErrorContains: "HTML",
|
||||
},
|
||||
{
|
||||
name: "title with newline",
|
||||
setup: func() string { return baseVersionID },
|
||||
setup: func() string { return baseDocumentID },
|
||||
input: func(id string) map[string]any {
|
||||
return map[string]any{"documentVersionId": id, "title": "Test\nDocument"}
|
||||
return map[string]any{"id": id, "title": "Test\nDocument"}
|
||||
},
|
||||
wantErrorContains: "newline",
|
||||
},
|
||||
{
|
||||
name: "title with carriage return",
|
||||
setup: func() string { return baseVersionID },
|
||||
setup: func() string { return baseDocumentID },
|
||||
input: func(id string) map[string]any {
|
||||
return map[string]any{"documentVersionId": id, "title": "Test\rDocument"}
|
||||
return map[string]any{"id": id, "title": "Test\rDocument"}
|
||||
},
|
||||
wantErrorContains: "carriage return",
|
||||
},
|
||||
{
|
||||
name: "title with null byte",
|
||||
setup: func() string { return baseVersionID },
|
||||
setup: func() string { return baseDocumentID },
|
||||
input: func(id string) map[string]any {
|
||||
return map[string]any{"documentVersionId": id, "title": "Test\x00Document"}
|
||||
return map[string]any{"id": id, "title": "Test\x00Document"}
|
||||
},
|
||||
wantErrorContains: "control character",
|
||||
},
|
||||
{
|
||||
name: "title with zero-width space",
|
||||
setup: func() string { return baseVersionID },
|
||||
setup: func() string { return baseDocumentID },
|
||||
input: func(id string) map[string]any {
|
||||
return map[string]any{"documentVersionId": id, "title": "Test\u200BDocument"}
|
||||
return map[string]any{"id": id, "title": "Test\u200BDocument"}
|
||||
},
|
||||
wantErrorContains: "zero-width",
|
||||
},
|
||||
@@ -389,11 +387,11 @@ func TestDocumentVersion_Update_TitleValidation(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
versionID := tt.setup()
|
||||
documentID := tt.setup()
|
||||
|
||||
query := `
|
||||
mutation UpdateDocumentVersion($input: UpdateDocumentVersionInput!) {
|
||||
updateDocumentVersion(input: $input) {
|
||||
mutation UpdateDocument($input: UpdateDocumentInput!) {
|
||||
updateDocument(input: $input) {
|
||||
documentVersion {
|
||||
id
|
||||
}
|
||||
@@ -401,7 +399,7 @@ func TestDocumentVersion_Update_TitleValidation(t *testing.T) {
|
||||
}
|
||||
`
|
||||
|
||||
_, err := owner.Do(query, map[string]any{"input": tt.input(versionID)})
|
||||
_, err := owner.Do(query, map[string]any{"input": tt.input(documentID)})
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.wantErrorContains)
|
||||
})
|
||||
@@ -979,14 +977,13 @@ func TestDocument_MaxLength_Validation(t *testing.T) {
|
||||
assert.Contains(t, err.Error(), "title")
|
||||
})
|
||||
|
||||
t.Run("update version", func(t *testing.T) {
|
||||
t.Run("update document with long title", func(t *testing.T) {
|
||||
doc := factory.NewDocument(owner).WithTitle("Max Length Test")
|
||||
doc.Create()
|
||||
versionID := doc.VersionID()
|
||||
documentID := doc.Create()
|
||||
|
||||
query := `
|
||||
mutation UpdateDocumentVersion($input: UpdateDocumentVersionInput!) {
|
||||
updateDocumentVersion(input: $input) {
|
||||
mutation UpdateDocument($input: UpdateDocumentInput!) {
|
||||
updateDocument(input: $input) {
|
||||
documentVersion { id }
|
||||
}
|
||||
}
|
||||
@@ -994,8 +991,8 @@ func TestDocument_MaxLength_Validation(t *testing.T) {
|
||||
|
||||
_, err := owner.Do(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"documentVersionId": versionID,
|
||||
"title": longTitle,
|
||||
"id": documentID,
|
||||
"title": longTitle,
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
@@ -1028,15 +1025,14 @@ func TestDocument_MaxLength_Validation(t *testing.T) {
|
||||
assert.Contains(t, err.Error(), "content")
|
||||
})
|
||||
|
||||
t.Run("update version with long content", func(t *testing.T) {
|
||||
docID, versionID := createTestDocument(t, owner)
|
||||
t.Run("update document with long content", func(t *testing.T) {
|
||||
docID, _ := createTestDocument(t, owner)
|
||||
require.NotEmpty(t, docID)
|
||||
require.NotEmpty(t, versionID)
|
||||
|
||||
query := `
|
||||
mutation UpdateDocumentVersion($input: UpdateDocumentVersionInput!) {
|
||||
updateDocumentVersion(input: $input) {
|
||||
documentVersion { id }
|
||||
mutation UpdateDocument($input: UpdateDocumentInput!) {
|
||||
updateDocument(input: $input) {
|
||||
document { id }
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -1045,8 +1041,8 @@ func TestDocument_MaxLength_Validation(t *testing.T) {
|
||||
|
||||
_, err := owner.Do(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"documentVersionId": versionID,
|
||||
"content": longContent,
|
||||
"id": docID,
|
||||
"content": longContent,
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
|
||||
@@ -220,7 +220,7 @@ func TestDocumentVersion_PublishVersion(t *testing.T) {
|
||||
assert.Equal(t, 0, result.Node.Versions.Edges[0].Node.Minor)
|
||||
}
|
||||
|
||||
func TestDocumentVersion_CreateDraft(t *testing.T) {
|
||||
func TestDocumentVersion_AutoCreateDraft(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
|
||||
@@ -228,38 +228,112 @@ func TestDocumentVersion_CreateDraft(t *testing.T) {
|
||||
docID, _ := createTestDocument(t, owner)
|
||||
approveTestDocument(t, owner, docID)
|
||||
|
||||
// Updating content should auto-create a draft
|
||||
query := `
|
||||
mutation CreateDraftDocumentVersion($input: CreateDraftDocumentVersionInput!) {
|
||||
createDraftDocumentVersion(input: $input) {
|
||||
documentVersionEdge {
|
||||
node {
|
||||
id
|
||||
status
|
||||
}
|
||||
mutation UpdateDocument($input: UpdateDocumentInput!) {
|
||||
updateDocument(input: $input) {
|
||||
document {
|
||||
id
|
||||
}
|
||||
documentVersion {
|
||||
id
|
||||
status
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var result struct {
|
||||
CreateDraftDocumentVersion struct {
|
||||
DocumentVersionEdge struct {
|
||||
Node struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
} `json:"node"`
|
||||
} `json:"documentVersionEdge"`
|
||||
} `json:"createDraftDocumentVersion"`
|
||||
UpdateDocument struct {
|
||||
Document struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"document"`
|
||||
DocumentVersion *struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
Content string `json:"content"`
|
||||
} `json:"documentVersion"`
|
||||
} `json:"updateDocument"`
|
||||
}
|
||||
|
||||
err := owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"documentID": docID,
|
||||
"id": docID,
|
||||
"content": testutil.ProseMirrorTextDoc("Updated content"),
|
||||
},
|
||||
}, &result)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "DRAFT", result.CreateDraftDocumentVersion.DocumentVersionEdge.Node.Status)
|
||||
require.NotNil(t, result.UpdateDocument.DocumentVersion)
|
||||
assert.Equal(t, "DRAFT", result.UpdateDocument.DocumentVersion.Status)
|
||||
}
|
||||
|
||||
func TestDocumentVersion_AutoDeleteDraft(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
|
||||
// Create and approve a document (auto-publishes on approval)
|
||||
docID, _ := createTestDocument(t, owner)
|
||||
approveTestDocument(t, owner, docID)
|
||||
|
||||
// First update to create a draft
|
||||
query := `
|
||||
mutation UpdateDocument($input: UpdateDocumentInput!) {
|
||||
updateDocument(input: $input) {
|
||||
document {
|
||||
id
|
||||
}
|
||||
documentVersion {
|
||||
id
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var createResult struct {
|
||||
UpdateDocument struct {
|
||||
Document struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"document"`
|
||||
DocumentVersion *struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
} `json:"documentVersion"`
|
||||
} `json:"updateDocument"`
|
||||
}
|
||||
|
||||
err := owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"id": docID,
|
||||
"content": testutil.ProseMirrorTextDoc("Updated content"),
|
||||
},
|
||||
}, &createResult)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, createResult.UpdateDocument.DocumentVersion)
|
||||
|
||||
// Now revert content to match the published version — draft should be auto-deleted
|
||||
var revertResult struct {
|
||||
UpdateDocument struct {
|
||||
Document struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"document"`
|
||||
DocumentVersion *struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"documentVersion"`
|
||||
} `json:"updateDocument"`
|
||||
}
|
||||
|
||||
err = owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"id": docID,
|
||||
"content": testutil.ProseMirrorTextDoc("Initial content"),
|
||||
},
|
||||
}, &revertResult)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Nil(t, revertResult.UpdateDocument.DocumentVersion)
|
||||
}
|
||||
|
||||
func TestDocumentVersion_RequestSignature(t *testing.T) {
|
||||
@@ -536,18 +610,17 @@ func TestDocumentVersion_BulkPublishMinorSkipsPendingApproval(t *testing.T) {
|
||||
docID, _ := createTestDocument(t, owner)
|
||||
approveTestDocument(t, owner, docID)
|
||||
|
||||
// Create a draft so we can publish minor
|
||||
// Create a draft by updating content (auto-creates draft)
|
||||
_, err := owner.Do(`
|
||||
mutation($input: CreateDraftDocumentVersionInput!) {
|
||||
createDraftDocumentVersion(input: $input) {
|
||||
documentVersionEdge {
|
||||
node { id }
|
||||
}
|
||||
mutation($input: UpdateDocumentInput!) {
|
||||
updateDocument(input: $input) {
|
||||
documentVersion { id }
|
||||
}
|
||||
}
|
||||
`, map[string]any{
|
||||
"input": map[string]any{
|
||||
"documentID": docID,
|
||||
"id": docID,
|
||||
"content": testutil.ProseMirrorTextDoc("Updated content to create a draft"),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -645,6 +718,128 @@ func TestDocumentVersion_BulkRequestSignatures(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocumentVersion_AutoCreateDraftOnClassificationOrTypeUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
|
||||
t.Run("documentType update creates draft", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create and approve a document (auto-publishes on approval)
|
||||
docID, _ := createTestDocument(t, owner)
|
||||
approveTestDocument(t, owner, docID)
|
||||
|
||||
// Updating documentType should auto-create a draft
|
||||
query := `
|
||||
mutation UpdateDocument($input: UpdateDocumentInput!) {
|
||||
updateDocument(input: $input) {
|
||||
document {
|
||||
id
|
||||
}
|
||||
documentVersion {
|
||||
id
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var result struct {
|
||||
UpdateDocument struct {
|
||||
Document struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"document"`
|
||||
DocumentVersion *struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
} `json:"documentVersion"`
|
||||
} `json:"updateDocument"`
|
||||
}
|
||||
|
||||
err := owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"id": docID,
|
||||
"documentType": "PROCEDURE",
|
||||
},
|
||||
}, &result)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotNil(t, result.UpdateDocument.DocumentVersion)
|
||||
assert.Equal(t, "DRAFT", result.UpdateDocument.DocumentVersion.Status)
|
||||
})
|
||||
|
||||
t.Run("classification update creates draft", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create and approve a document (auto-publishes on approval)
|
||||
docID, _ := createTestDocument(t, owner)
|
||||
approveTestDocument(t, owner, docID)
|
||||
|
||||
// Updating classification should auto-create a draft
|
||||
query := `
|
||||
mutation UpdateDocument($input: UpdateDocumentInput!) {
|
||||
updateDocument(input: $input) {
|
||||
document {
|
||||
id
|
||||
}
|
||||
documentVersion {
|
||||
id
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var result struct {
|
||||
UpdateDocument struct {
|
||||
Document struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"document"`
|
||||
DocumentVersion *struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
} `json:"documentVersion"`
|
||||
} `json:"updateDocument"`
|
||||
}
|
||||
|
||||
err := owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"id": docID,
|
||||
"classification": "CONFIDENTIAL",
|
||||
},
|
||||
}, &result)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotNil(t, result.UpdateDocument.DocumentVersion)
|
||||
assert.Equal(t, "DRAFT", result.UpdateDocument.DocumentVersion.Status)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDocumentVersion_ViewerCannotUpdateDocument(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
viewer := testutil.NewClientInOrg(t, testutil.RoleViewer, owner)
|
||||
|
||||
// Create and approve a document (auto-publishes on approval)
|
||||
docID, _ := createTestDocument(t, owner)
|
||||
approveTestDocument(t, owner, docID)
|
||||
|
||||
// Viewer attempts to update content on the published document
|
||||
_, err := viewer.Do(`
|
||||
mutation UpdateDocument($input: UpdateDocumentInput!) {
|
||||
updateDocument(input: $input) {
|
||||
document { id }
|
||||
}
|
||||
}
|
||||
`, map[string]any{
|
||||
"input": map[string]any{
|
||||
"id": docID,
|
||||
"content": testutil.ProseMirrorTextDoc("Viewer updated content"),
|
||||
},
|
||||
})
|
||||
testutil.RequireForbiddenError(t, err, "viewer should not be able to update document")
|
||||
}
|
||||
|
||||
func TestDocumentVersion_BulkDelete(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
@@ -1143,3 +1338,148 @@ func TestDocument_DefaultApprovers(t *testing.T) {
|
||||
assert.Empty(t, result.UpdateDocument.Document.DefaultApprovers)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDocumentVersion_DeleteDraft(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
|
||||
const query = `
|
||||
mutation DeleteDocumentDraft($input: DeleteDocumentDraftInput!) {
|
||||
deleteDocumentDraft(input: $input) {
|
||||
document {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
t.Run(
|
||||
"delete draft after publishing",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
docID, _ := createTestDocument(t, owner)
|
||||
approveTestDocument(t, owner, docID)
|
||||
|
||||
// Create a draft by updating content
|
||||
updateQuery := `
|
||||
mutation UpdateDocument($input: UpdateDocumentInput!) {
|
||||
updateDocument(input: $input) {
|
||||
document { id }
|
||||
documentVersion { id status }
|
||||
}
|
||||
}
|
||||
`
|
||||
var updateResult struct {
|
||||
UpdateDocument struct {
|
||||
Document struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"document"`
|
||||
DocumentVersion *struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
} `json:"documentVersion"`
|
||||
} `json:"updateDocument"`
|
||||
}
|
||||
err := owner.Execute(updateQuery, map[string]any{
|
||||
"input": map[string]any{
|
||||
"id": docID,
|
||||
"content": testutil.ProseMirrorTextDoc("Draft content"),
|
||||
},
|
||||
}, &updateResult)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, updateResult.UpdateDocument.DocumentVersion)
|
||||
assert.Equal(t, "DRAFT", updateResult.UpdateDocument.DocumentVersion.Status)
|
||||
|
||||
// Now delete the draft
|
||||
var result struct {
|
||||
DeleteDocumentDraft struct {
|
||||
Document struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"document"`
|
||||
} `json:"deleteDocumentDraft"`
|
||||
}
|
||||
err = owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{"documentId": docID},
|
||||
}, &result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, docID, result.DeleteDocumentDraft.Document.ID)
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"cannot delete initial v0.1 draft",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
docID, _ := createTestDocument(t, owner)
|
||||
|
||||
var result struct{}
|
||||
err := owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{"documentId": docID},
|
||||
}, &result)
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"cannot delete when latest is published",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
docID, _ := createTestDocument(t, owner)
|
||||
approveTestDocument(t, owner, docID)
|
||||
|
||||
var result struct{}
|
||||
err := owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{"documentId": docID},
|
||||
}, &result)
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"viewer cannot delete draft",
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
viewer := testutil.NewClientInOrg(t, testutil.RoleViewer, owner)
|
||||
|
||||
docID, _ := createTestDocument(t, owner)
|
||||
approveTestDocument(t, owner, docID)
|
||||
|
||||
// Create a draft
|
||||
updateQuery := `
|
||||
mutation UpdateDocument($input: UpdateDocumentInput!) {
|
||||
updateDocument(input: $input) {
|
||||
document { id }
|
||||
documentVersion { id }
|
||||
}
|
||||
}
|
||||
`
|
||||
var updateResult struct {
|
||||
UpdateDocument struct {
|
||||
Document struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"document"`
|
||||
DocumentVersion *struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"documentVersion"`
|
||||
} `json:"updateDocument"`
|
||||
}
|
||||
err := owner.Execute(updateQuery, map[string]any{
|
||||
"input": map[string]any{
|
||||
"id": docID,
|
||||
"content": testutil.ProseMirrorTextDoc("Draft content"),
|
||||
},
|
||||
}, &updateResult)
|
||||
require.NoError(t, err)
|
||||
|
||||
var result struct{}
|
||||
err = viewer.Execute(query, map[string]any{
|
||||
"input": map[string]any{"documentId": docID},
|
||||
}, &result)
|
||||
testutil.RequireForbiddenError(t, err, "viewer should not be able to delete document draft")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user