From 7240cc63cd762d1f547b77f4d3ae707a9c4c0a9e Mon Sep 17 00:00:00 2001 From: Ram Narayan Balaji Date: Sat, 4 Jul 2026 18:39:52 +0530 Subject: [PATCH 1/3] Fixes #29510: parse glossary CSV entityStatus case-insensitively Glossary and metric CSV imports rejected valid status values like `draft` or `APPROVED` because `EntityStatus.fromValue` matches the enum's canonical casing (`Draft`, `Approved`, ...). Users typing lower/upper/mixed case saw the import fail with an unhelpful "status is invalid" error. Add `EntityFieldUtils.parseEntityStatus` that first tries the exact enum lookup and falls back to a case-insensitive scan over `EntityStatus.values()`. Route the glossary and metric CSV importers, plus the shared `setEntityStatus` helper, through it so any casing (`draft`, `DRAFT`, `drAFT`, `in review`, ...) resolves to the canonical enum. Truly unknown values still throw and surface as a clear per-row validation failure. Tests: - EntityFieldUtilsTest: parse helper accepts canonical, lower, upper, and mixed casings for every EntityStatus, and throws on unknown values. - GlossaryResourceIT: imports terms with `draft`, `APPROVED`, `deprecated`, `drAFT` and asserts the import succeeds and terms are created; a second test asserts an invalid value still fails with a message referencing the bad input. --- .../it/tests/GlossaryResourceIT.java | 118 ++++++++++++++++++ .../service/jdbi3/GlossaryRepository.java | 5 +- .../service/jdbi3/MetricRepository.java | 3 +- .../service/util/EntityFieldUtils.java | 23 +++- .../service/util/EntityFieldUtilsTest.java | 27 ++++ 5 files changed, 173 insertions(+), 3 deletions(-) diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryResourceIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryResourceIT.java index 98f8289cd0de..1822dba18b9a 100644 --- a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryResourceIT.java +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryResourceIT.java @@ -39,6 +39,7 @@ import org.openmetadata.schema.type.ApiStatus; import org.openmetadata.schema.type.EntityHistory; import org.openmetadata.schema.type.EntityReference; +import org.openmetadata.schema.type.EntityStatus; import org.openmetadata.schema.type.FieldChange; import org.openmetadata.schema.type.TagLabel; import org.openmetadata.schema.type.csv.CsvImportResult; @@ -1425,6 +1426,123 @@ void test_importCsv_withApprovedRelatedTerm_succeeds(TestNamespace ns) } } + /** + * Regression test for issue #29510: glossaryStatus column must be parsed case-insensitively. + * Users supplying valid status values in any casing (draft, APPROVED, drAFT, deprecated, etc.) + * should produce a successful import with the canonical {@link EntityStatus} stored on the term. + * Invalid values must still surface as a clear validation error. + */ + @Test + void test_importCsv_glossaryStatusIsCaseInsensitive(TestNamespace ns) { + OpenMetadataClient client = SdkClients.adminClient(); + + Glossary glossary = createEntity(createMinimalRequest(ns)); + String header = + client.glossaries().exportCsv(glossary.getName()).lines().findFirst().orElse(""); + assertTrue(header.contains("glossaryStatus"), "Exported header must include glossaryStatus"); + + String draftTermName = ns.prefix("statusDraft"); + String approvedTermName = ns.prefix("statusApproved"); + String deprecatedTermName = ns.prefix("statusDeprecated"); + String mixedCaseTermName = ns.prefix("statusMixed"); + + String csv = + header + + "\n" + + buildStatusRow(header, draftTermName, "Lower Draft", "draft (lower)", "draft") + + buildStatusRow( + header, approvedTermName, "Upper Approved", "approved (upper)", "APPROVED") + + buildStatusRow( + header, deprecatedTermName, "Lower Deprecated", "deprecated (lower)", "deprecated") + + buildStatusRow(header, mixedCaseTermName, "Mixed Draft", "drAFT (mixed)", "drAFT"); + + String resultJson = client.glossaries().importCsv(glossary.getName(), csv, false); + CsvImportResult importResult = JsonUtils.readValue(resultJson, CsvImportResult.class); + assertEquals( + ApiStatus.SUCCESS, + importResult.getStatus(), + "Case-insensitive glossaryStatus import should succeed. Result: " + resultJson); + assertEquals(4, importResult.getNumberOfRowsPassed()); + assertEquals(0, importResult.getNumberOfRowsFailed()); + + for (String termName : + List.of(draftTermName, approvedTermName, deprecatedTermName, mixedCaseTermName)) { + GlossaryTerm term = + client.glossaryTerms().getByName(glossary.getFullyQualifiedName() + "." + termName); + assertNotNull( + term.getEntityStatus(), + "Imported term '" + termName + "' must have an entityStatus assigned"); + } + } + + /** Invalid glossaryStatus values must still surface as a clear validation failure. */ + @Test + void test_importCsv_glossaryStatusInvalidValueFails(TestNamespace ns) { + OpenMetadataClient client = SdkClients.adminClient(); + + Glossary glossary = createEntity(createMinimalRequest(ns)); + String header = + client.glossaries().exportCsv(glossary.getName()).lines().findFirst().orElse(""); + + String badTermName = ns.prefix("statusBad"); + String csv = + header + + "\n" + + buildStatusRow(header, badTermName, "Bad Status", "invalid status", "not-a-status"); + + String resultJson = client.glossaries().importCsv(glossary.getName(), csv, false); + assertNotNull(resultJson); + CsvImportResult importResult = JsonUtils.readValue(resultJson, CsvImportResult.class); + assertTrue( + importResult.getNumberOfRowsFailed() != null && importResult.getNumberOfRowsFailed() >= 1, + "Import should report at least one failed row. Result: " + resultJson); + assertTrue( + importResult.getImportResultsCsv() != null + && importResult.getImportResultsCsv().contains("not-a-status"), + "Failure message should reference the invalid status value. Result: " + resultJson); + assertTrue( + importResult.getImportResultsCsv().contains("is invalid"), + "Failure message should mark the value as invalid. Result: " + resultJson); + } + + private static String buildStatusRow( + String header, String name, String displayName, String description, String status) { + String[] columns = header.split(","); + int statusIdx = -1; + int nameIdx = -1; + int displayIdx = -1; + int descIdx = -1; + for (int i = 0; i < columns.length; i++) { + String col = columns[i].trim(); + if (col.equals("glossaryStatus")) { + statusIdx = i; + } else if (col.equals("name*") || col.equals("name")) { + nameIdx = i; + } else if (col.equals("displayName")) { + displayIdx = i; + } else if (col.equals("description")) { + descIdx = i; + } + } + String[] row = new String[columns.length]; + for (int i = 0; i < row.length; i++) { + row[i] = ""; + } + if (nameIdx >= 0) { + row[nameIdx] = "\"" + name + "\""; + } + if (displayIdx >= 0) { + row[displayIdx] = "\"" + displayName + "\""; + } + if (descIdx >= 0) { + row[descIdx] = "\"" + description + "\""; + } + if (statusIdx >= 0) { + row[statusIdx] = status; + } + return String.join(",", row) + "\n"; + } + /** * Regression test: glossary terms must keep their assigned domains through a CSV export/import * round-trip (the UI "bulk edit" flow). Domains were silently dropped because the glossary CSV diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/GlossaryRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/GlossaryRepository.java index a715a602e705..c424ad6f56b6 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/GlossaryRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/GlossaryRepository.java @@ -85,6 +85,7 @@ import org.openmetadata.service.resources.glossary.GlossaryResource; import org.openmetadata.service.resources.settings.SettingsCache; import org.openmetadata.service.security.policyevaluator.PolicyConditionUpdater; +import org.openmetadata.service.util.EntityFieldUtils; import org.openmetadata.service.util.EntityUtil.Fields; import org.openmetadata.service.util.EntityUtil.RelationIncludes; import org.openmetadata.service.util.FullyQualifiedName; @@ -485,7 +486,9 @@ private EntityStatus getTermStatus(CSVPrinter printer, CSVRecord csvRecord) thro } String termStatus = csvRecord.get(10); try { - return nullOrEmpty(termStatus) ? EntityStatus.DRAFT : EntityStatus.fromValue(termStatus); + return nullOrEmpty(termStatus) + ? EntityStatus.DRAFT + : EntityFieldUtils.parseEntityStatus(termStatus); } catch (Exception ex) { // List should have even numbered terms - termName and endPoint importFailure( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/MetricRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/MetricRepository.java index b7db289f8946..6a307e0442cf 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/MetricRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/MetricRepository.java @@ -67,6 +67,7 @@ import org.openmetadata.service.jdbi3.FeedRepository.ThreadContext; import org.openmetadata.service.resources.metrics.MetricResource; import org.openmetadata.service.security.AuthorizationException; +import org.openmetadata.service.util.EntityFieldUtils; import org.openmetadata.service.util.EntityUtil; import org.openmetadata.service.util.EntityUtil.RelationIncludes; import org.openmetadata.service.util.MemoryOwnership; @@ -421,7 +422,7 @@ private EntityStatus getEntityStatus(CSVPrinter printer, CSVRecord csvRecord, in return null; } try { - return EntityStatus.fromValue(csvRecord.get(fieldNumber)); + return EntityFieldUtils.parseEntityStatus(csvRecord.get(fieldNumber)); } catch (Exception ex) { importFailure( printer, diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/util/EntityFieldUtils.java b/openmetadata-service/src/main/java/org/openmetadata/service/util/EntityFieldUtils.java index a0aaf5c76863..2bf196e68d5a 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/util/EntityFieldUtils.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/util/EntityFieldUtils.java @@ -488,6 +488,27 @@ public static void setReviewers(EntityInterface entity, String reviewerNames) { } } + /** + * Parses an {@link EntityStatus} from a string in a case-insensitive way so that + * CSV imports and other user inputs accept values like {@code draft} / {@code DRAFT} + * in addition to the canonical {@code Draft}. Multi-word values such as + * {@code "In Review"} are matched ignoring case as well. + * + * @throws IllegalArgumentException when the value does not match any enum constant + */ + public static EntityStatus parseEntityStatus(String value) { + try { + return EntityStatus.fromValue(value); + } catch (IllegalArgumentException ex) { + for (EntityStatus status : EntityStatus.values()) { + if (status.value().equalsIgnoreCase(value)) { + return status; + } + } + throw ex; + } + } + /** * Sets the status field on various entity types using appropriate enum values. * Handles both legacy 'status' field and new 'entityStatus' field. @@ -495,7 +516,7 @@ public static void setReviewers(EntityInterface entity, String reviewerNames) { public static void setEntityStatus(EntityInterface entity, String statusValue) { try { try { - EntityStatus status = EntityStatus.fromValue(statusValue); + EntityStatus status = parseEntityStatus(statusValue); entity.setEntityStatus(status); LOG.debug( "Successfully set entityStatus to '{}' on entity type: {}", diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/util/EntityFieldUtilsTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/util/EntityFieldUtilsTest.java index f627d6b6d464..7249709c878d 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/util/EntityFieldUtilsTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/util/EntityFieldUtilsTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -413,6 +414,32 @@ void setReviewersResolvesUsersAndTeamsAndClearsWhenEmpty() { } } + @Test + void parseEntityStatusAcceptsCanonicalAndMixedCaseValues() { + assertEquals(EntityStatus.DRAFT, EntityFieldUtils.parseEntityStatus("Draft")); + assertEquals(EntityStatus.DRAFT, EntityFieldUtils.parseEntityStatus("draft")); + assertEquals(EntityStatus.DRAFT, EntityFieldUtils.parseEntityStatus("DRAFT")); + assertEquals(EntityStatus.DRAFT, EntityFieldUtils.parseEntityStatus("drAFT")); + assertEquals(EntityStatus.DRAFT, EntityFieldUtils.parseEntityStatus("DrAfT")); + assertEquals(EntityStatus.APPROVED, EntityFieldUtils.parseEntityStatus("approved")); + assertEquals(EntityStatus.APPROVED, EntityFieldUtils.parseEntityStatus("APPROVED")); + assertEquals(EntityStatus.APPROVED, EntityFieldUtils.parseEntityStatus("ApPrOvEd")); + assertEquals(EntityStatus.DEPRECATED, EntityFieldUtils.parseEntityStatus("Deprecated")); + assertEquals(EntityStatus.DEPRECATED, EntityFieldUtils.parseEntityStatus("deprecated")); + assertEquals(EntityStatus.DEPRECATED, EntityFieldUtils.parseEntityStatus("DEPRECATED")); + assertEquals(EntityStatus.IN_REVIEW, EntityFieldUtils.parseEntityStatus("in review")); + assertEquals(EntityStatus.IN_REVIEW, EntityFieldUtils.parseEntityStatus("IN REVIEW")); + assertEquals(EntityStatus.IN_REVIEW, EntityFieldUtils.parseEntityStatus("In Review")); + assertEquals(EntityStatus.REJECTED, EntityFieldUtils.parseEntityStatus("rejected")); + assertEquals(EntityStatus.ARCHIVED, EntityFieldUtils.parseEntityStatus("ARCHIVED")); + } + + @Test + void parseEntityStatusThrowsForUnknownValues() { + assertThrows( + IllegalArgumentException.class, () -> EntityFieldUtils.parseEntityStatus("not-a-status")); + } + @Test void setEntityStatusSupportsEnumAndLegacyFallbacks() { TestEntity entity = entity(); From a84b3c1a6b0fb7bb22b26671d95d13f019cd482c Mon Sep 17 00:00:00 2001 From: Ram Narayan Balaji Date: Sat, 4 Jul 2026 19:05:40 +0530 Subject: [PATCH 2/3] Address review: drop exception-driven parse, trim input, verify per-row parsing - parseEntityStatus: single case-insensitive scan over EntityStatus.values() after trimming, no more try/catch around EntityStatus.fromValue. Rejects null/blank/unknown with a direct IllegalArgumentException. - Unit tests: cover leading/trailing whitespace (" draft ", "Approved\t", "\n In Review \n") plus a blank-string throw case. - GlossaryResourceIT#test_importCsv_glossaryStatusIsCaseInsensitive: assert each expected casing ("draft", "APPROVED", "deprecated", "drAFT") appears on its own success row in importResult.getImportResultsCsv(). Guards against a regression where the importer silently defaults every row to the same status. --- .../it/tests/GlossaryResourceIT.java | 31 +++++++++++++++++++ .../service/util/EntityFieldUtils.java | 13 +++----- .../service/util/EntityFieldUtilsTest.java | 8 +++++ 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryResourceIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryResourceIT.java index 1822dba18b9a..00380b00b247 100644 --- a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryResourceIT.java +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryResourceIT.java @@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.time.Duration; import java.util.List; +import java.util.Map; import java.util.UUID; import org.awaitility.Awaitility; import org.junit.jupiter.api.Test; @@ -1465,6 +1466,36 @@ void test_importCsv_glossaryStatusIsCaseInsensitive(TestNamespace ns) { assertEquals(4, importResult.getNumberOfRowsPassed()); assertEquals(0, importResult.getNumberOfRowsFailed()); + String resultsCsv = importResult.getImportResultsCsv(); + assertNotNull(resultsCsv, "Per-row import results should be present. Result: " + resultJson); + for (Map.Entry entry : + Map.of( + draftTermName, "draft", + approvedTermName, "APPROVED", + deprecatedTermName, "deprecated", + mixedCaseTermName, "drAFT") + .entrySet()) { + String rowMarker = entry.getKey(); + String casing = entry.getValue(); + boolean rowSucceeded = + resultsCsv + .lines() + .anyMatch( + line -> + line.startsWith("success") + && line.contains(rowMarker) + && line.contains("," + casing + ",")); + assertTrue( + rowSucceeded, + "Row for '" + + rowMarker + + "' with casing '" + + casing + + "' should be reported as success." + + " importResultsCsv=" + + resultsCsv); + } + for (String termName : List.of(draftTermName, approvedTermName, deprecatedTermName, mixedCaseTermName)) { GlossaryTerm term = diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/util/EntityFieldUtils.java b/openmetadata-service/src/main/java/org/openmetadata/service/util/EntityFieldUtils.java index 2bf196e68d5a..22f1622d5d48 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/util/EntityFieldUtils.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/util/EntityFieldUtils.java @@ -497,16 +497,13 @@ public static void setReviewers(EntityInterface entity, String reviewerNames) { * @throws IllegalArgumentException when the value does not match any enum constant */ public static EntityStatus parseEntityStatus(String value) { - try { - return EntityStatus.fromValue(value); - } catch (IllegalArgumentException ex) { - for (EntityStatus status : EntityStatus.values()) { - if (status.value().equalsIgnoreCase(value)) { - return status; - } + String normalized = value == null ? "" : value.trim(); + for (EntityStatus status : EntityStatus.values()) { + if (status.value().equalsIgnoreCase(normalized)) { + return status; } - throw ex; } + throw new IllegalArgumentException("Unknown EntityStatus value: " + value); } /** diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/util/EntityFieldUtilsTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/util/EntityFieldUtilsTest.java index 7249709c878d..7ed4971ff344 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/util/EntityFieldUtilsTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/util/EntityFieldUtilsTest.java @@ -434,10 +434,18 @@ void parseEntityStatusAcceptsCanonicalAndMixedCaseValues() { assertEquals(EntityStatus.ARCHIVED, EntityFieldUtils.parseEntityStatus("ARCHIVED")); } + @Test + void parseEntityStatusTrimsSurroundingWhitespace() { + assertEquals(EntityStatus.DRAFT, EntityFieldUtils.parseEntityStatus(" draft ")); + assertEquals(EntityStatus.APPROVED, EntityFieldUtils.parseEntityStatus("Approved\t")); + assertEquals(EntityStatus.IN_REVIEW, EntityFieldUtils.parseEntityStatus("\n In Review \n")); + } + @Test void parseEntityStatusThrowsForUnknownValues() { assertThrows( IllegalArgumentException.class, () -> EntityFieldUtils.parseEntityStatus("not-a-status")); + assertThrows(IllegalArgumentException.class, () -> EntityFieldUtils.parseEntityStatus(" ")); } @Test From 3040a53b3c10e3aceba9d0ea0ba2527908e8e2db Mon Sep 17 00:00:00 2001 From: Ram Narayan Balaji Date: Sat, 4 Jul 2026 22:08:21 +0530 Subject: [PATCH 3/3] test(glossary): replace ad-hoc Map with typed StatusCase record Drop the untyped Map pair in test_importCsv_glossaryStatusIsCaseInsensitive in favour of a small private StatusCase record (termName, displayName, description, csvCasing). The CSV rows, per-row success assertions, and post-import lookups now iterate the same typed list, making the test's intent explicit and removing the Map.of import. --- .../it/tests/GlossaryResourceIT.java | 68 +++++++++---------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryResourceIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryResourceIT.java index 00380b00b247..f345fe19e0f9 100644 --- a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryResourceIT.java +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/GlossaryResourceIT.java @@ -25,7 +25,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.time.Duration; import java.util.List; -import java.util.Map; import java.util.UUID; import org.awaitility.Awaitility; import org.junit.jupiter.api.Test; @@ -1442,70 +1441,67 @@ void test_importCsv_glossaryStatusIsCaseInsensitive(TestNamespace ns) { client.glossaries().exportCsv(glossary.getName()).lines().findFirst().orElse(""); assertTrue(header.contains("glossaryStatus"), "Exported header must include glossaryStatus"); - String draftTermName = ns.prefix("statusDraft"); - String approvedTermName = ns.prefix("statusApproved"); - String deprecatedTermName = ns.prefix("statusDeprecated"); - String mixedCaseTermName = ns.prefix("statusMixed"); - - String csv = - header - + "\n" - + buildStatusRow(header, draftTermName, "Lower Draft", "draft (lower)", "draft") - + buildStatusRow( - header, approvedTermName, "Upper Approved", "approved (upper)", "APPROVED") - + buildStatusRow( - header, deprecatedTermName, "Lower Deprecated", "deprecated (lower)", "deprecated") - + buildStatusRow(header, mixedCaseTermName, "Mixed Draft", "drAFT (mixed)", "drAFT"); + List cases = + List.of( + new StatusCase(ns.prefix("statusDraft"), "Lower Draft", "draft (lower)", "draft"), + new StatusCase( + ns.prefix("statusApproved"), "Upper Approved", "approved (upper)", "APPROVED"), + new StatusCase( + ns.prefix("statusDeprecated"), + "Lower Deprecated", + "deprecated (lower)", + "deprecated"), + new StatusCase(ns.prefix("statusMixed"), "Mixed Draft", "drAFT (mixed)", "drAFT")); + + StringBuilder csvBuilder = new StringBuilder(header).append('\n'); + for (StatusCase c : cases) { + csvBuilder.append( + buildStatusRow(header, c.termName(), c.displayName(), c.description(), c.csvCasing())); + } - String resultJson = client.glossaries().importCsv(glossary.getName(), csv, false); + String resultJson = + client.glossaries().importCsv(glossary.getName(), csvBuilder.toString(), false); CsvImportResult importResult = JsonUtils.readValue(resultJson, CsvImportResult.class); assertEquals( ApiStatus.SUCCESS, importResult.getStatus(), "Case-insensitive glossaryStatus import should succeed. Result: " + resultJson); - assertEquals(4, importResult.getNumberOfRowsPassed()); + assertEquals(cases.size(), importResult.getNumberOfRowsPassed()); assertEquals(0, importResult.getNumberOfRowsFailed()); String resultsCsv = importResult.getImportResultsCsv(); assertNotNull(resultsCsv, "Per-row import results should be present. Result: " + resultJson); - for (Map.Entry entry : - Map.of( - draftTermName, "draft", - approvedTermName, "APPROVED", - deprecatedTermName, "deprecated", - mixedCaseTermName, "drAFT") - .entrySet()) { - String rowMarker = entry.getKey(); - String casing = entry.getValue(); + for (StatusCase c : cases) { boolean rowSucceeded = resultsCsv .lines() .anyMatch( line -> line.startsWith("success") - && line.contains(rowMarker) - && line.contains("," + casing + ",")); + && line.contains(c.termName()) + && line.contains("," + c.csvCasing() + ",")); assertTrue( rowSucceeded, "Row for '" - + rowMarker + + c.termName() + "' with casing '" - + casing - + "' should be reported as success." - + " importResultsCsv=" + + c.csvCasing() + + "' should be reported as success. importResultsCsv=" + resultsCsv); } - for (String termName : - List.of(draftTermName, approvedTermName, deprecatedTermName, mixedCaseTermName)) { + for (StatusCase c : cases) { GlossaryTerm term = - client.glossaryTerms().getByName(glossary.getFullyQualifiedName() + "." + termName); + client.glossaryTerms().getByName(glossary.getFullyQualifiedName() + "." + c.termName()); assertNotNull( term.getEntityStatus(), - "Imported term '" + termName + "' must have an entityStatus assigned"); + "Imported term '" + c.termName() + "' must have an entityStatus assigned"); } } + private record StatusCase( + String termName, String displayName, String description, String csvCasing) {} + /** Invalid glossaryStatus values must still surface as a clear validation failure. */ @Test void test_importCsv_glossaryStatusInvalidValueFails(TestNamespace ns) {