Move pending signature requests on minor publish
When a new minor version is published, still-REQUESTED signature requests on the previous version now move onto the newly published version, keeping the same signature row so the notification schedule (count and last-notified time) is preserved. SIGNED signatures are left untouched. Signed-off-by: Sacha Al Himdani <sacha@probo.com>
This commit is contained in:
@@ -2052,6 +2052,94 @@ func TestDocumentVersion_RequestSignatureDeduplicatesAcrossMinors(t *testing.T)
|
||||
assertRequestedSignatureCount(t, owner, v11ID, 1)
|
||||
}
|
||||
|
||||
// requestedSignatureVersions returns a map of signature node ID -> the document
|
||||
// version ID it is currently attached to, for the REQUESTED signatures visible
|
||||
// from versionID (the signatures connection aggregates across every minor of
|
||||
// the major).
|
||||
func requestedSignatureVersions(
|
||||
t *testing.T,
|
||||
owner *testutil.Client,
|
||||
versionID string,
|
||||
) map[string]string {
|
||||
t.Helper()
|
||||
|
||||
var result struct {
|
||||
Node struct {
|
||||
Signatures struct {
|
||||
Edges []struct {
|
||||
Node struct {
|
||||
ID string `json:"id"`
|
||||
State string `json:"state"`
|
||||
DocumentVersion struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"documentVersion"`
|
||||
} `json:"node"`
|
||||
} `json:"edges"`
|
||||
} `json:"signatures"`
|
||||
} `json:"node"`
|
||||
}
|
||||
|
||||
err := owner.Execute(`
|
||||
query($id: ID!) {
|
||||
node(id: $id) {
|
||||
... on DocumentVersion {
|
||||
signatures(first: 50, filter: { states: [REQUESTED] }) {
|
||||
edges { node { id state documentVersion { id } } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, map[string]any{"id": versionID}, &result)
|
||||
require.NoError(t, err)
|
||||
|
||||
out := make(map[string]string, len(result.Node.Signatures.Edges))
|
||||
for _, edge := range result.Node.Signatures.Edges {
|
||||
out[edge.Node.ID] = edge.Node.DocumentVersion.ID
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// TestDocumentVersion_MinorPublishMovesSignatureRequestsToNewVersion verifies
|
||||
// that publishing a new minor version carries the still-pending signature
|
||||
// requests from the previous version onto the newly published version. The same
|
||||
// signature row is reused (its ID is preserved), so its notification schedule
|
||||
// (count and last-notified time) is kept intact rather than reset.
|
||||
func TestDocumentVersion_MinorPublishMovesSignatureRequestsToNewVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
signer := testutil.NewClientInOrg(t, testutil.RoleViewer, owner)
|
||||
|
||||
docID, _ := createTestDocument(t, owner)
|
||||
|
||||
v10ID := publishMajorDocumentVersion(t, owner, docID)
|
||||
requestDocumentSignature(t, owner, v10ID, signer.GetProfileID().String())
|
||||
|
||||
// The pending request starts attached to v1.0.
|
||||
before := requestedSignatureVersions(t, owner, v10ID)
|
||||
require.Len(t, before, 1)
|
||||
|
||||
var signatureID string
|
||||
for id, versionID := range before {
|
||||
signatureID = id
|
||||
|
||||
assert.Equal(t, v10ID, versionID, "the request must start on v1.0")
|
||||
}
|
||||
|
||||
// A minor bump publishes v1.1 within the same major.
|
||||
updateDocumentContent(t, owner, docID, "Updated content for 1.1")
|
||||
v11ID := publishMinorDocumentVersion(t, owner, docID)
|
||||
require.NotEqual(t, v10ID, v11ID)
|
||||
|
||||
// The same request row is now attached to the newly published v1.1.
|
||||
after := requestedSignatureVersions(t, owner, v11ID)
|
||||
require.Len(t, after, 1)
|
||||
|
||||
movedVersionID, ok := after[signatureID]
|
||||
require.True(t, ok, "the signature request must keep its identity across the minor publish")
|
||||
assert.Equal(t, v11ID, movedVersionID, "the pending request must move to the newly published minor version")
|
||||
}
|
||||
|
||||
// signDocumentVersion signs the version as the authenticated client and returns
|
||||
// the resulting signature node's id, state and signing time.
|
||||
func signDocumentVersion(t *testing.T, signer *testutil.Client, versionID string) (id, state, signedAt string) {
|
||||
|
||||
@@ -728,6 +728,47 @@ WHERE
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pvss *DocumentVersionSignatures) MoveRequestedToVersionWithinMajor(
|
||||
ctx context.Context,
|
||||
conn pg.Tx,
|
||||
scope Scoper,
|
||||
targetVersionID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE document_version_signatures
|
||||
SET
|
||||
document_version_id = @target_version_id,
|
||||
updated_at = @now
|
||||
WHERE
|
||||
%s
|
||||
AND state = @state
|
||||
AND document_version_id <> @target_version_id
|
||||
AND document_version_id IN (
|
||||
SELECT dv.id
|
||||
FROM document_versions dv
|
||||
INNER JOIN document_versions target
|
||||
ON target.document_id = dv.document_id
|
||||
AND target.major = dv.major
|
||||
WHERE target.id = @target_version_id
|
||||
)
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"target_version_id": targetVersionID,
|
||||
"state": DocumentVersionSignatureStateRequested,
|
||||
"now": time.Now(),
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
if _, err := conn.Exec(ctx, q, args); err != nil {
|
||||
return fmt.Errorf("cannot move requested document version signatures to the newly published version: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pvss *DocumentVersionSignatures) DeleteRequestedByDocumentIDBelowMajor(
|
||||
ctx context.Context,
|
||||
conn pg.Tx,
|
||||
|
||||
@@ -2889,9 +2889,31 @@ func (s *DocumentService) publishMinorVersionInTx(
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := s.moveRequestedSignaturesToVersionInTx(ctx, scope, tx, documentVersion.ID); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return document, documentVersion, nil
|
||||
}
|
||||
|
||||
// moveRequestedSignaturesToVersionInTx carries every still-pending signature
|
||||
// request from a prior minor of the same major onto the newly published minor
|
||||
// version. The new minor supersedes the previous one while keeping the same
|
||||
// signing obligations, so REQUESTED signatures follow along with their
|
||||
// notification schedule (time and count) intact. SIGNED signatures stay put.
|
||||
func (s *DocumentService) moveRequestedSignaturesToVersionInTx(
|
||||
ctx context.Context, scope coredata.Scoper,
|
||||
tx pg.Tx,
|
||||
documentVersionID gid.GID,
|
||||
) error {
|
||||
signatures := &coredata.DocumentVersionSignatures{}
|
||||
if err := signatures.MoveRequestedToVersionWithinMajor(ctx, tx, scope, documentVersionID); err != nil {
|
||||
return fmt.Errorf("cannot move signature requests to the newly published minor version: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DocumentService) generateAndUploadPublicationPDF(
|
||||
ctx context.Context, scope coredata.Scoper,
|
||||
documentVersion *coredata.DocumentVersion,
|
||||
|
||||
Reference in New Issue
Block a user