Redesign document approval flow
Replace the per-approver add/remove model with a quorum-based approval system. Documents now have default approvers that are pre-populated when requesting approval, and the publish dialog lets users adjust the list before submitting. Key changes: - Add PENDING_APPROVAL document version status with dedicated transitions - Introduce approval quorums with request/approve/reject/void lifecycle - Add default approvers per document (stored in document_default_approvers) with MERGE-based upsert for efficient sync - Add NoDuplicates validator for slice fields - Split ALTER TYPE ADD VALUE migrations into separate files (required by PostgreSQL when run inside transactions) - Use VOIDED consistently for both quorum status and decision state enums - Expose void/approve/reject through GraphQL and MCP, with e2e tests - Add approval management UI: publish dialog with approver selection, approval list with void support, and external approve/reject page Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -264,3 +264,65 @@ func TestRequired(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNoDuplicates(t *testing.T) {
|
||||
t.Run("nil slice", func(t *testing.T) {
|
||||
var slice []string
|
||||
err := NoDuplicates()(slice)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error for nil slice, got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty slice", func(t *testing.T) {
|
||||
slice := []string{}
|
||||
err := NoDuplicates()(slice)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error for empty slice, got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unique strings", func(t *testing.T) {
|
||||
slice := []string{"a", "b", "c"}
|
||||
err := NoDuplicates()(slice)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("duplicate strings", func(t *testing.T) {
|
||||
slice := []string{"a", "b", "a"}
|
||||
err := NoDuplicates()(slice)
|
||||
if err == nil {
|
||||
t.Fatal("expected validation error for duplicates")
|
||||
} else if err.Code != ErrorCodeInvalidFormat {
|
||||
t.Errorf("expected error code %s, got %s", ErrorCodeInvalidFormat, err.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unique ints", func(t *testing.T) {
|
||||
slice := []int{1, 2, 3}
|
||||
err := NoDuplicates()(slice)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("duplicate ints", func(t *testing.T) {
|
||||
slice := []int{1, 2, 1}
|
||||
err := NoDuplicates()(slice)
|
||||
if err == nil {
|
||||
t.Fatal("expected validation error for duplicates")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("non-comparable elements", func(t *testing.T) {
|
||||
slice := []map[string]string{{"a": "b"}}
|
||||
err := NoDuplicates()(slice)
|
||||
if err == nil {
|
||||
t.Fatal("expected validation error for non-comparable elements")
|
||||
} else if err.Code != ErrorCodeInvalidFormat {
|
||||
t.Errorf("expected error code %s, got %s", ErrorCodeInvalidFormat, err.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user