fix(tags): run validation during dryRun add, only skip the writes

Flagged by Copilot review on #28005. The previous dryRun branch in
TagRepository.bulkAddAndValidateTagsToAssets returned per-asset
successes immediately after populateEntityReferences, bypassing the
mutually-exclusive tag check and the column FQN/lookup validation. A
dryRun=true call would falsely report that all assets would pass even
when the real call would fail validation, violating the dryRun contract
("validated but no changes").

Mirror the GlossaryTermRepository.bulkAddAndValidateGlossaryToAssets
pattern: drop the early-return dryRun branch, let the existing
validation loop run, and gate only the write side-effects (applyTags,
searchRepository.updateEntity, tag_usage writes) on !dryRun. Same
treatment for addTagToColumn.

The Tag add endpoint is async (returns jobId, ships BulkOperationResult
via WebSocket), so end-to-end IT coverage for the validation outcome
would require WebSocket subscription. The existing
test_bulkAddTagToAssets_dryRunTrue_doesNotApply continues to cover the
no-mutation invariant. The validation-runs invariant is covered by the
gold-standard sync glossary pattern this fix now mirrors.
This commit is contained in:
sonika-shah 2026-05-15 01:38:28 +05:30
parent 37c68dbd36
commit 03c6f5002f

View file

@ -363,15 +363,6 @@ public class TagRepository extends EntityRepository<Tag> {
// Validation for entityReferences
EntityUtil.populateEntityReferences(request.getAssets());
if (dryRun) {
for (EntityReference ref : request.getAssets()) {
result.setNumberOfRowsProcessed(result.getNumberOfRowsProcessed() + 1);
success.add(new BulkResponse().withRequest(ref));
result.setNumberOfRowsPassed(result.getNumberOfRowsPassed() + 1);
}
return result.withStatus(ApiStatus.SUCCESS).withSuccessRequest(success);
}
TagLabel tagLabel =
new TagLabel()
.withTagFQN(tag.getFullyQualifiedName())
@ -385,7 +376,7 @@ public class TagRepository extends EntityRepository<Tag> {
// Handle column assets specially - columns don't have their own repository
if (Entity.TABLE_COLUMN.equals(ref.getType())) {
try {
addTagToColumn(ref, tagLabel, success, failures, result);
addTagToColumn(ref, tagLabel, dryRun, success, failures, result);
} catch (Exception ex) {
failures.add(new BulkResponse().withRequest(ref).withMessage(ex.getMessage()));
result.withFailedRequest(failures);
@ -414,8 +405,9 @@ public class TagRepository extends EntityRepository<Tag> {
result.withFailedRequest(failures);
result.setNumberOfRowsFailed(result.getNumberOfRowsFailed() + 1);
}
// Validate and Store Tags
if (nullOrEmpty(result.getFailedRequest())) {
// Validate and Store Tags skip the write side-effects on dryRun so the preview
// surfaces the same validation outcome a real call would without mutating state.
if (!dryRun && nullOrEmpty(result.getFailedRequest())) {
List<TagLabel> tempList = new ArrayList<>(asset.getTags());
tempList.add(tagLabel);
// Apply Tags to Entities
@ -447,6 +439,7 @@ public class TagRepository extends EntityRepository<Tag> {
private void addTagToColumn(
EntityReference columnRef,
TagLabel tagLabel,
boolean dryRun,
List<BulkResponse> success,
List<BulkResponse> failures,
BulkOperationResult result) {
@ -479,7 +472,7 @@ public class TagRepository extends EntityRepository<Tag> {
new ArrayList<>(Collections.singleton(tagLabel)),
false);
if (nullOrEmpty(result.getFailedRequest())) {
if (!dryRun && nullOrEmpty(result.getFailedRequest())) {
List<TagLabel> columnTags = new ArrayList<>(listOrEmpty(targetColumn.getTags()));
columnTags.add(tagLabel);
applyTags(getUniqueTags(columnTags), columnFqn);