From 557a52f507b0ac0153c465533e45b3a22dbc8456 Mon Sep 17 00:00:00 2001 From: Ram Narayan Balaji Date: Tue, 2 Sep 2025 13:00:00 +0530 Subject: [PATCH] Test cases for custom workflows related to glossaryTermApprovalWorkflow --- .../glossary/GlossaryTermResourceTest.java | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/glossary/GlossaryTermResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/glossary/GlossaryTermResourceTest.java index e641c1dd291..99c4c34d268 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/glossary/GlossaryTermResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/glossary/GlossaryTermResourceTest.java @@ -2494,6 +2494,216 @@ public class GlossaryTermResourceTest extends EntityResourceTest reviewers = Arrays.asList(reviewer1, reviewer2); + + // Patch workflow to set approval threshold to 2 BEFORE creating entities + // Node at index 12 is "ApproveGlossaryTerm" userApprovalTask + String patchOp = + "[{\"op\":\"replace\",\"path\":\"/nodes/12/config/approvalThreshold\",\"value\":2}]"; + patchWorkflowDefinition("GlossaryTermApprovalWorkflow", patchOp); + + // Create glossary with reviewers + Glossary glossary = createGlossary(test, reviewers, null); + + // Create glossary term with reviewers + CreateGlossaryTerm createRequest = + createRequest(getEntityName(test)) + .withDescription("Test term for multi-approval") + .withGlossary(glossary.getFullyQualifiedName()) + .withReviewers(reviewers) + .withSynonyms(null) + .withRelatedTerms(null); + GlossaryTerm term = createEntity(createRequest, ADMIN_AUTH_HEADERS); + + // Term should be in DRAFT status initially + assertEquals(Status.DRAFT, term.getStatus()); + + // Wait for workflow to process and task to be created + waitForTaskToBeCreated(term.getFullyQualifiedName()); + + // After workflow processing, term should be IN_REVIEW + assertEquals(Status.IN_REVIEW, getEntity(term.getId(), ADMIN_AUTH_HEADERS).getStatus()); + + // Get the task + String entityLink = + new MessageParser.EntityLink(Entity.GLOSSARY_TERM, term.getFullyQualifiedName()) + .getLinkString(); + ThreadList threads = + taskTest.listTasks(entityLink, null, null, null, 100, authHeaders(reviewer1.getName())); + assertFalse(threads.getData().isEmpty()); + Thread task = threads.getData().getFirst(); + int taskId = task.getTask().getId(); + + // First reviewer approves + ResolveTask resolveTask = new ResolveTask().withNewValue(Status.APPROVED.value()); + taskTest.resolveTask(taskId, resolveTask, authHeaders(reviewer1.getName())); + + // After first approval, term should still be IN_REVIEW + java.lang.Thread.sleep(2000); // Wait for async processing + GlossaryTerm termAfterFirstApproval = getEntity(term.getId(), ADMIN_AUTH_HEADERS); + assertEquals(Status.IN_REVIEW, termAfterFirstApproval.getStatus()); + + // Second reviewer approves + taskTest.resolveTask(taskId, resolveTask, authHeaders(reviewer2.getName())); + + // After second approval, term should be APPROVED + java.lang.Thread.sleep(2000); // Wait for async processing + GlossaryTerm termAfterSecondApproval = getEntity(term.getId(), ADMIN_AUTH_HEADERS); + assertEquals(Status.APPROVED, termAfterSecondApproval.getStatus()); + + // Reset workflow back to threshold of 1 + patchOp = "[{\"op\":\"replace\",\"path\":\"/nodes/12/config/approvalThreshold\",\"value\":1}]"; + patchWorkflowDefinition("GlossaryTermApprovalWorkflow", patchOp); + } + + @Test + void test_RollbackOnRejection(TestInfo test) throws Exception { + // Test 2: Rollback on rejection scenario + EntityReference reviewer = USER1.getEntityReference(); + List reviewers = List.of(reviewer); + + // Create glossary with reviewer + Glossary glossary = createGlossary(test, reviewers, null); + + // Create glossary term + String initialDescription = "Initial approved description"; + CreateGlossaryTerm createRequest = + createRequest(getEntityName(test)) + .withDescription(initialDescription) + .withGlossary(glossary.getFullyQualifiedName()) + .withReviewers(reviewers) + .withSynonyms(null) + .withRelatedTerms(null); + GlossaryTerm term = createEntity(createRequest, ADMIN_AUTH_HEADERS); + + // Wait for task and approve it + waitForTaskToBeCreated(term.getFullyQualifiedName()); + String entityLink = + new MessageParser.EntityLink(Entity.GLOSSARY_TERM, term.getFullyQualifiedName()) + .getLinkString(); + ThreadList threads = + taskTest.listTasks(entityLink, null, null, null, 100, authHeaders(reviewer.getName())); + Thread task = threads.getData().getFirst(); + int taskId = task.getTask().getId(); + + ResolveTask approveTask = new ResolveTask().withNewValue(Status.APPROVED.value()); + taskTest.resolveTask(taskId, approveTask, authHeaders(reviewer.getName())); + + java.lang.Thread.sleep(2000); + GlossaryTerm approvedTerm = getEntity(term.getId(), ADMIN_AUTH_HEADERS); + assertEquals(Status.APPROVED, approvedTerm.getStatus()); + double version1 = approvedTerm.getVersion(); + + // Update term with non-reviewer (should trigger workflow) + String updatedDescription = "Updated description by non-reviewer"; + String origJson = JsonUtils.pojoToJson(approvedTerm); + approvedTerm.setDescription(updatedDescription); + GlossaryTerm updatedTerm = + patchEntityUsingFqn(approvedTerm.getFullyQualifiedName(), origJson, approvedTerm, authHeaders(USER2.getName())); + + // Wait for new task to be created for the update + waitForDetailedTaskToBeCreated(term.getFullyQualifiedName(), 60000L); + + // Get the new task + threads = + taskTest.listTasks(entityLink, null, null, null, 100, authHeaders(reviewer.getName())); + Thread updateTask = threads.getData().getFirst(); + int updateTaskId = updateTask.getTask().getId(); + + // Reject the changes + ResolveTask rejectTask = new ResolveTask().withNewValue(Status.REJECTED.value()); + taskTest.resolveTask(updateTaskId, rejectTask, authHeaders(reviewer.getName())); + + java.lang.Thread.sleep(2000); + GlossaryTerm rolledBackTerm = getEntity(term.getId(), ADMIN_AUTH_HEADERS); + + // Verify rollback: description should be back to initial, status should be approved + assertEquals(initialDescription, rolledBackTerm.getDescription()); + assertEquals(Status.APPROVED, rolledBackTerm.getStatus()); + // Version should be bumped for audit trail + assertTrue( + rolledBackTerm.getVersion() > version1, "Version should be incremented for audit trail"); + } + + @Test + void test_ReviewerSuggestionApplication(TestInfo test) throws Exception { + // Test 3: Reviewer suggestion application + EntityReference reviewer = USER1.getEntityReference(); + List reviewers = List.of(reviewer); + + // Create glossary with reviewer + Glossary glossary = createGlossary(test, reviewers, null); + + // Create glossary term + String initialDescription = "Initial description"; + CreateGlossaryTerm createRequest = + createRequest(getEntityName(test)) + .withDescription(initialDescription) + .withGlossary(glossary.getFullyQualifiedName()) + .withReviewers(reviewers) + .withSynonyms(null) + .withRelatedTerms(null); + GlossaryTerm term = createEntity(createRequest, ADMIN_AUTH_HEADERS); + + // Wait for task to be created + waitForTaskToBeCreated(term.getFullyQualifiedName()); + + // Get the task + String entityLink = + new MessageParser.EntityLink(Entity.GLOSSARY_TERM, term.getFullyQualifiedName()) + .getLinkString(); + ThreadList threads = + taskTest.listTasks(entityLink, null, null, null, 100, authHeaders(reviewer.getName())); + Thread task = threads.getData().getFirst(); + int taskId = task.getTask().getId(); + + // Approve initially + ResolveTask approveTask = new ResolveTask().withNewValue(Status.APPROVED.value()); + taskTest.resolveTask(taskId, approveTask, authHeaders(reviewer.getName())); + + java.lang.Thread.sleep(2000); + GlossaryTerm approvedTerm = getEntity(term.getId(), ADMIN_AUTH_HEADERS); + assertEquals(Status.APPROVED, approvedTerm.getStatus()); + + // Update term to trigger a new approval workflow + String updateDescription = "Updated description for review"; + String origJson = JsonUtils.pojoToJson(approvedTerm); + approvedTerm.setDescription(updateDescription); + GlossaryTerm updatedTerm = + patchEntityUsingFqn(approvedTerm.getFullyQualifiedName(), origJson, approvedTerm, authHeaders(USER2.getName())); + + // Wait for detailed task to be created + waitForDetailedTaskToBeCreated(term.getFullyQualifiedName(), 60000L); + + // Get the new task + threads = + taskTest.listTasks(entityLink, null, null, null, 100, authHeaders(reviewer.getName())); + Thread updateTask = threads.getData().getFirst(); + int updateTaskId = updateTask.getTask().getId(); + + // Reviewer suggests changes and approves with modifications + String suggestedDescription = "Multi Approver Term Updated By Reviewer [Don't update Further]"; + String resolveValue = + String.format("Description:

%s

\nstatus: approved", suggestedDescription); + ResolveTask resolveWithSuggestion = new ResolveTask().withNewValue(resolveValue); + taskTest.resolveTask(updateTaskId, resolveWithSuggestion, authHeaders(reviewer.getName())); + + java.lang.Thread.sleep(2000); + GlossaryTerm finalTerm = getEntity(term.getId(), ADMIN_AUTH_HEADERS); + + // Verify the term has the suggested description and is approved + assertEquals(Status.APPROVED, finalTerm.getStatus()); + assertTrue( + finalTerm.getDescription().contains(suggestedDescription), + "Term should have the reviewer's suggested description"); + } + /** * Helper method to receive move entity message via WebSocket */