fix(patch): add encoding to patch builders (#13164)

This commit is contained in:
RyanHolstien 2025-04-18 11:02:53 -05:00 committed by GitHub
parent 8ef571b3c4
commit 98c88e7263
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 491 additions and 11 deletions

View File

@ -31,12 +31,16 @@ public class GlobalTagsPatchBuilder extends AbstractMultiFieldPatchBuilder<Globa
value.put(CONTEXT_KEY, context);
}
pathValues.add(ImmutableTriple.of(PatchOperationType.ADD.getValue(), BASE_PATH + urn, value));
pathValues.add(
ImmutableTriple.of(
PatchOperationType.ADD.getValue(), BASE_PATH + encodeValueUrn(urn), value));
return this;
}
public GlobalTagsPatchBuilder removeTag(@Nonnull TagUrn urn) {
pathValues.add(ImmutableTriple.of(PatchOperationType.REMOVE.getValue(), BASE_PATH + urn, null));
pathValues.add(
ImmutableTriple.of(
PatchOperationType.REMOVE.getValue(), BASE_PATH + encodeValueUrn(urn), null));
return this;
}

View File

@ -32,12 +32,16 @@ public class GlossaryTermsPatchBuilder
value.put(CONTEXT_KEY, context);
}
pathValues.add(ImmutableTriple.of(PatchOperationType.ADD.getValue(), BASE_PATH + urn, value));
pathValues.add(
ImmutableTriple.of(
PatchOperationType.ADD.getValue(), BASE_PATH + encodeValueUrn(urn), value));
return this;
}
public GlossaryTermsPatchBuilder removeTerm(@Nonnull GlossaryTermUrn urn) {
pathValues.add(ImmutableTriple.of(PatchOperationType.REMOVE.getValue(), BASE_PATH + urn, null));
pathValues.add(
ImmutableTriple.of(
PatchOperationType.REMOVE.getValue(), BASE_PATH + encodeValueUrn(urn), null));
return this;
}

View File

@ -23,7 +23,9 @@ public class OwnershipPatchBuilder extends AbstractMultiFieldPatchBuilder<Owners
pathValues.add(
ImmutableTriple.of(
PatchOperationType.ADD.getValue(), BASE_PATH + owner + "/" + type, value));
PatchOperationType.ADD.getValue(),
BASE_PATH + encodeValueUrn(owner) + "/" + encodeValue(type.toString()),
value));
return this;
}
@ -36,7 +38,8 @@ public class OwnershipPatchBuilder extends AbstractMultiFieldPatchBuilder<Owners
*/
public OwnershipPatchBuilder removeOwner(@Nonnull Urn owner) {
pathValues.add(
ImmutableTriple.of(PatchOperationType.REMOVE.getValue(), BASE_PATH + owner, null));
ImmutableTriple.of(
PatchOperationType.REMOVE.getValue(), BASE_PATH + encodeValueUrn(owner), null));
return this;
}
@ -53,7 +56,9 @@ public class OwnershipPatchBuilder extends AbstractMultiFieldPatchBuilder<Owners
@Nonnull Urn owner, @Nonnull OwnershipType type) {
pathValues.add(
ImmutableTriple.of(
PatchOperationType.REMOVE.getValue(), BASE_PATH + owner + "/" + type, null));
PatchOperationType.REMOVE.getValue(),
BASE_PATH + encodeValueUrn(owner) + "/" + encodeValue(type.toString()),
null));
return this;
}

View File

@ -46,7 +46,9 @@ public class StructuredPropertiesPatchBuilder
pathValues.add(
ImmutableTriple.of(
PatchOperationType.ADD.getValue(), BASE_PATH + "/" + propertyUrn, newProperty));
PatchOperationType.ADD.getValue(),
BASE_PATH + "/" + encodeValueUrn(propertyUrn),
newProperty));
return this;
}
@ -66,7 +68,9 @@ public class StructuredPropertiesPatchBuilder
pathValues.add(
ImmutableTriple.of(
PatchOperationType.ADD.getValue(), BASE_PATH + "/" + propertyUrn, newProperty));
PatchOperationType.ADD.getValue(),
BASE_PATH + "/" + encodeValueUrn(propertyUrn),
newProperty));
return this;
}
@ -83,7 +87,9 @@ public class StructuredPropertiesPatchBuilder
pathValues.add(
ImmutableTriple.of(
PatchOperationType.ADD.getValue(), BASE_PATH + "/" + propertyUrn, newProperty));
PatchOperationType.ADD.getValue(),
BASE_PATH + "/" + encodeValueUrn(propertyUrn),
newProperty));
return this;
}
@ -103,7 +109,9 @@ public class StructuredPropertiesPatchBuilder
pathValues.add(
ImmutableTriple.of(
PatchOperationType.ADD.getValue(), BASE_PATH + "/" + propertyUrn, newProperty));
PatchOperationType.ADD.getValue(),
BASE_PATH + "/" + encodeValueUrn(propertyUrn),
newProperty));
return this;
}

View File

@ -0,0 +1,141 @@
package com.linkedin.metadata.aspect.patch.builder;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertThrows;
import static org.testng.Assert.assertTrue;
import com.fasterxml.jackson.databind.JsonNode;
import com.linkedin.common.urn.TagUrn;
import com.linkedin.common.urn.Urn;
import java.net.URISyntaxException;
import java.util.List;
import org.apache.commons.lang3.tuple.ImmutableTriple;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class GlobalTagsPatchBuilderTest {
private TestableGlobalTagsPatchBuilder builder;
private static final String TEST_ENTITY_URN =
"urn:li:dataset:(urn:li:dataPlatform:hive,SampleTable,PROD)";
private static final String TEST_TAG_URN = "urn:li:tag:Test/Tag";
// Test helper class to expose protected method
private static class TestableGlobalTagsPatchBuilder extends GlobalTagsPatchBuilder {
public List<ImmutableTriple<String, String, JsonNode>> getTestPathValues() {
return getPathValues();
}
}
@BeforeMethod
public void setup() throws URISyntaxException {
builder = new TestableGlobalTagsPatchBuilder();
builder.urn(Urn.createFromString(TEST_ENTITY_URN));
}
@Test
public void testBuildDoesNotAffectPathValues() throws URISyntaxException {
TagUrn tagUrn = TagUrn.createFromString(TEST_TAG_URN);
builder.addTag(tagUrn, "Test context");
// First call build()
builder.build();
// Then verify we can still access pathValues and they're correct
List<ImmutableTriple<String, String, JsonNode>> pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 1);
// Verify we can call build() again without issues
builder.build();
// And verify pathValues are still accessible and correct
pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 1);
}
@Test
public void testAddTagWithContext() throws URISyntaxException {
TagUrn tagUrn = TagUrn.createFromString(TEST_TAG_URN);
String context = "Test context";
builder.addTag(tagUrn, context);
builder.build();
List<ImmutableTriple<String, String, JsonNode>> pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 1);
ImmutableTriple<String, String, JsonNode> operation = pathValues.get(0);
assertEquals(operation.getLeft(), "add");
assertTrue(operation.getMiddle().startsWith("/tags/"));
assertTrue(operation.getRight().isObject());
assertEquals(operation.getRight().get("urn").asText(), tagUrn.toString());
assertEquals(operation.getRight().get("context").asText(), context);
}
@Test
public void testAddTagWithoutContext() throws URISyntaxException {
TagUrn tagUrn = TagUrn.createFromString(TEST_TAG_URN);
builder.addTag(tagUrn, null);
builder.build();
List<ImmutableTriple<String, String, JsonNode>> pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 1);
ImmutableTriple<String, String, JsonNode> operation = pathValues.get(0);
assertEquals(operation.getLeft(), "add");
assertTrue(operation.getMiddle().startsWith("/tags/"));
assertTrue(operation.getRight().isObject());
assertEquals(operation.getRight().get("urn").asText(), tagUrn.toString());
assertNull(operation.getRight().get("context"));
}
@Test
public void testRemoveTag() throws URISyntaxException {
TagUrn tagUrn = TagUrn.createFromString(TEST_TAG_URN);
builder.removeTag(tagUrn);
builder.build();
List<ImmutableTriple<String, String, JsonNode>> pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 1);
ImmutableTriple<String, String, JsonNode> operation = pathValues.get(0);
assertEquals(operation.getLeft(), "remove");
assertTrue(operation.getMiddle().startsWith("/tags/"));
assertNull(operation.getRight());
}
@Test
public void testMultipleOperations() throws URISyntaxException {
TagUrn tagUrn1 = TagUrn.createFromString(TEST_TAG_URN);
TagUrn tagUrn2 = TagUrn.createFromString("urn:li:tag:AnotherTag");
builder.addTag(tagUrn1, "Context 1").addTag(tagUrn2, null).removeTag(tagUrn1);
builder.build();
List<ImmutableTriple<String, String, JsonNode>> pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 3);
}
@Test
public void testGetEntityTypeWithoutUrnThrowsException() {
TestableGlobalTagsPatchBuilder builderWithoutUrn = new TestableGlobalTagsPatchBuilder();
TagUrn tagUrn;
try {
tagUrn = TagUrn.createFromString(TEST_TAG_URN);
builderWithoutUrn.addTag(tagUrn, null);
assertThrows(IllegalStateException.class, builderWithoutUrn::build);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,141 @@
package com.linkedin.metadata.aspect.patch.builder;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertThrows;
import static org.testng.Assert.assertTrue;
import com.fasterxml.jackson.databind.JsonNode;
import com.linkedin.common.urn.GlossaryTermUrn;
import com.linkedin.common.urn.Urn;
import java.net.URISyntaxException;
import java.util.List;
import org.apache.commons.lang3.tuple.ImmutableTriple;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class GlossaryTermsPatchBuilderTest {
private TestableGlossaryTermsPatchBuilder builder;
private static final String TEST_ENTITY_URN =
"urn:li:dataset:(urn:li:dataPlatform:hive,SampleTable,PROD)";
private static final String TEST_GLOSSARY_TERM_URN = "urn:li:glossaryTerm:Test/Term";
// Test helper class to expose protected method
private static class TestableGlossaryTermsPatchBuilder extends GlossaryTermsPatchBuilder {
public List<ImmutableTriple<String, String, JsonNode>> getTestPathValues() {
return getPathValues();
}
}
@BeforeMethod
public void setup() throws URISyntaxException {
builder = new TestableGlossaryTermsPatchBuilder();
builder.urn(Urn.createFromString(TEST_ENTITY_URN));
}
@Test
public void testBuildDoesNotAffectPathValues() throws URISyntaxException {
GlossaryTermUrn termUrn = GlossaryTermUrn.createFromString(TEST_GLOSSARY_TERM_URN);
builder.addTerm(termUrn, "Test context");
// First call build()
builder.build();
// Then verify we can still access pathValues and they're correct
List<ImmutableTriple<String, String, JsonNode>> pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 1);
// Verify we can call build() again without issues
builder.build();
// And verify pathValues are still accessible and correct
pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 1);
}
@Test
public void testAddTermWithContext() throws URISyntaxException {
GlossaryTermUrn termUrn = GlossaryTermUrn.createFromString(TEST_GLOSSARY_TERM_URN);
String context = "Test context";
builder.addTerm(termUrn, context);
builder.build();
List<ImmutableTriple<String, String, JsonNode>> pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 1);
ImmutableTriple<String, String, JsonNode> operation = pathValues.get(0);
assertEquals(operation.getLeft(), "add");
assertTrue(operation.getMiddle().startsWith("/glossaryTerms/"));
assertTrue(operation.getRight().isObject());
assertEquals(operation.getRight().get("urn").asText(), termUrn.toString());
assertEquals(operation.getRight().get("context").asText(), context);
}
@Test
public void testAddTermWithoutContext() throws URISyntaxException {
GlossaryTermUrn termUrn = GlossaryTermUrn.createFromString(TEST_GLOSSARY_TERM_URN);
builder.addTerm(termUrn, null);
builder.build();
List<ImmutableTriple<String, String, JsonNode>> pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 1);
ImmutableTriple<String, String, JsonNode> operation = pathValues.get(0);
assertEquals(operation.getLeft(), "add");
assertTrue(operation.getMiddle().startsWith("/glossaryTerms/"));
assertTrue(operation.getRight().isObject());
assertEquals(operation.getRight().get("urn").asText(), termUrn.toString());
assertNull(operation.getRight().get("context"));
}
@Test
public void testRemoveTerm() throws URISyntaxException {
GlossaryTermUrn termUrn = GlossaryTermUrn.createFromString(TEST_GLOSSARY_TERM_URN);
builder.removeTerm(termUrn);
builder.build();
List<ImmutableTriple<String, String, JsonNode>> pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 1);
ImmutableTriple<String, String, JsonNode> operation = pathValues.get(0);
assertEquals(operation.getLeft(), "remove");
assertTrue(operation.getMiddle().startsWith("/glossaryTerms/"));
assertNull(operation.getRight());
}
@Test
public void testMultipleOperations() throws URISyntaxException {
GlossaryTermUrn termUrn1 = GlossaryTermUrn.createFromString(TEST_GLOSSARY_TERM_URN);
GlossaryTermUrn termUrn2 = GlossaryTermUrn.createFromString("urn:li:glossaryTerm:AnotherTerm");
builder.addTerm(termUrn1, "Context 1").addTerm(termUrn2, null).removeTerm(termUrn1);
builder.build();
List<ImmutableTriple<String, String, JsonNode>> pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 3);
}
@Test
public void testGetEntityTypeWithoutUrnThrowsException() {
TestableGlossaryTermsPatchBuilder builderWithoutUrn = new TestableGlossaryTermsPatchBuilder();
GlossaryTermUrn termUrn;
try {
termUrn = GlossaryTermUrn.createFromString(TEST_GLOSSARY_TERM_URN);
builderWithoutUrn.addTerm(termUrn, null);
assertThrows(IllegalStateException.class, builderWithoutUrn::build);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,177 @@
package com.linkedin.metadata.aspect.patch.builder;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertThrows;
import static org.testng.Assert.assertTrue;
import com.fasterxml.jackson.databind.JsonNode;
import com.linkedin.common.OwnershipType;
import com.linkedin.common.urn.CorpuserUrn;
import com.linkedin.common.urn.Urn;
import java.net.URISyntaxException;
import java.util.List;
import org.apache.commons.lang3.tuple.ImmutableTriple;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class OwnershipPatchBuilderTest {
private TestableOwnershipPatchBuilder builder;
private static final String TEST_ENTITY_URN =
"urn:li:dataset:(urn:li:dataPlatform:hive,SampleTable,PROD)";
private static final String TEST_OWNER_URN = "urn:li:corpuser:test/User";
// Test helper class to expose protected method
private static class TestableOwnershipPatchBuilder extends OwnershipPatchBuilder {
public List<ImmutableTriple<String, String, JsonNode>> getTestPathValues() {
return getPathValues();
}
}
@BeforeMethod
public void setup() throws URISyntaxException {
builder = new TestableOwnershipPatchBuilder();
builder.urn(Urn.createFromString(TEST_ENTITY_URN));
}
@Test
public void testBuildDoesNotAffectPathValues() throws URISyntaxException {
Urn ownerUrn = CorpuserUrn.createFromString(TEST_OWNER_URN);
builder.addOwner(ownerUrn, OwnershipType.TECHNICAL_OWNER);
// First call build()
builder.build();
// Then verify we can still access pathValues and they're correct
List<ImmutableTriple<String, String, JsonNode>> pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 1);
// Verify we can call build() again without issues
builder.build();
// And verify pathValues are still accessible and correct
pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 1);
}
@Test
public void testAddOwner() throws URISyntaxException {
Urn ownerUrn = CorpuserUrn.createFromString(TEST_OWNER_URN);
OwnershipType ownershipType = OwnershipType.TECHNICAL_OWNER;
builder.addOwner(ownerUrn, ownershipType);
builder.build();
List<ImmutableTriple<String, String, JsonNode>> pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 1);
ImmutableTriple<String, String, JsonNode> operation = pathValues.get(0);
assertEquals(operation.getLeft(), "add");
assertTrue(operation.getMiddle().startsWith("/owners/"));
assertTrue(operation.getMiddle().contains("/" + ownershipType.toString()));
assertTrue(operation.getRight().isObject());
assertEquals(operation.getRight().get("owner").asText(), ownerUrn.toString());
assertEquals(operation.getRight().get("type").asText(), ownershipType.toString());
}
@Test
public void testRemoveOwner() throws URISyntaxException {
Urn ownerUrn = CorpuserUrn.createFromString(TEST_OWNER_URN);
builder.removeOwner(ownerUrn);
builder.build();
List<ImmutableTriple<String, String, JsonNode>> pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 1);
ImmutableTriple<String, String, JsonNode> operation = pathValues.get(0);
assertEquals(operation.getLeft(), "remove");
assertTrue(operation.getMiddle().startsWith("/owners/"));
assertNull(operation.getRight());
}
@Test
public void testRemoveOwnershipType() throws URISyntaxException {
Urn ownerUrn = CorpuserUrn.createFromString(TEST_OWNER_URN);
OwnershipType ownershipType = OwnershipType.TECHNICAL_OWNER;
builder.removeOwnershipType(ownerUrn, ownershipType);
builder.build();
List<ImmutableTriple<String, String, JsonNode>> pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 1);
ImmutableTriple<String, String, JsonNode> operation = pathValues.get(0);
assertEquals(operation.getLeft(), "remove");
assertTrue(operation.getMiddle().startsWith("/owners/"));
assertTrue(operation.getMiddle().contains("/" + ownershipType.toString()));
assertNull(operation.getRight());
}
@Test
public void testMultipleOperations() throws URISyntaxException {
Urn ownerUrn1 = CorpuserUrn.createFromString(TEST_OWNER_URN);
Urn ownerUrn2 = CorpuserUrn.createFromString("urn:li:corpuser:anotherUser");
builder
.addOwner(ownerUrn1, OwnershipType.TECHNICAL_OWNER)
.addOwner(ownerUrn1, OwnershipType.BUSINESS_OWNER)
.addOwner(ownerUrn2, OwnershipType.DATA_STEWARD)
.removeOwnershipType(ownerUrn1, OwnershipType.TECHNICAL_OWNER)
.removeOwner(ownerUrn2);
builder.build();
List<ImmutableTriple<String, String, JsonNode>> pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 5);
}
@Test
public void testGetEntityTypeWithoutUrnThrowsException() {
TestableOwnershipPatchBuilder builderWithoutUrn = new TestableOwnershipPatchBuilder();
Urn ownerUrn;
try {
ownerUrn = CorpuserUrn.createFromString(TEST_OWNER_URN);
builderWithoutUrn.addOwner(ownerUrn, OwnershipType.TECHNICAL_OWNER);
assertThrows(IllegalStateException.class, builderWithoutUrn::build);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
@Test
public void testDifferentOwnershipTypes() throws URISyntaxException {
Urn ownerUrn = CorpuserUrn.createFromString(TEST_OWNER_URN);
// Test different ownership types
builder
.addOwner(ownerUrn, OwnershipType.TECHNICAL_OWNER)
.addOwner(ownerUrn, OwnershipType.BUSINESS_OWNER)
.addOwner(ownerUrn, OwnershipType.DATA_STEWARD);
builder.build();
List<ImmutableTriple<String, String, JsonNode>> pathValues = builder.getTestPathValues();
assertNotNull(pathValues);
assertEquals(pathValues.size(), 3);
// Verify each ownership type has the correct path and value
for (int i = 0; i < pathValues.size(); i++) {
ImmutableTriple<String, String, JsonNode> operation = pathValues.get(i);
assertEquals(operation.getLeft(), "add");
assertTrue(operation.getMiddle().startsWith("/owners/"));
assertTrue(operation.getRight().isObject());
assertEquals(operation.getRight().get("owner").asText(), ownerUrn.toString());
}
}
}