From 7d382862185cdfa9f00a80958391b0b1b516cf93 Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Thu, 23 May 2024 14:54:37 +0200 Subject: [PATCH] docs: Add additional tests proving bidi relationships with properties work as intended. Closes #2905. --- .../integration/issues/gh2905/BugFrom.java | 78 +++++++++ .../issues/gh2905/BugRelationship.java | 77 +++++++++ .../integration/issues/gh2905/BugTarget.java | 73 +++++++++ .../issues/gh2905/BugTargetBase.java | 43 +++++ .../integration/issues/gh2905/Gh2905IT.java | 148 ++++++++++++++++++ 5 files changed, 419 insertions(+) create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/BugFrom.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/BugRelationship.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/BugTarget.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/BugTargetBase.java create mode 100644 src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/Gh2905IT.java diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/BugFrom.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/BugFrom.java new file mode 100644 index 000000000..aa195bbda --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/BugFrom.java @@ -0,0 +1,78 @@ +/* + * Copyright 2011-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.integration.issues.gh2905; + +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Relationship; +import org.springframework.data.neo4j.core.support.UUIDStringGenerator; + +/** + * @author Mathias Kühn + */ +@SuppressWarnings("HiddenField") // Not worth cleaning up the Delomboked version +class BugFrom { + @Id + @GeneratedValue(UUIDStringGenerator.class) + protected String uuid; + + private String name; + + @Relationship(type = "RELI", direction = Relationship.Direction.INCOMING) + private BugRelationship reli; + + BugFrom(String uuid, String name, BugRelationship reli) { + this.uuid = uuid; + this.name = name; + this.reli = reli; + } + + public static BugFromBuilder builder() { + return new BugFromBuilder(); + } + + public static class BugFromBuilder { + private String uuid; + private String name; + private BugRelationship reli; + + BugFromBuilder() { + } + + public BugFromBuilder uuid(String uuid) { + this.uuid = uuid; + return this; + } + + public BugFromBuilder name(String name) { + this.name = name; + return this; + } + + public BugFromBuilder reli(BugRelationship reli) { + this.reli = reli; + return this; + } + + public BugFrom build() { + return new BugFrom(this.uuid, this.name, this.reli); + } + + public String toString() { + return "BugFrom.BugFromBuilder(uuid=" + this.uuid + ", name=" + this.name + ", reli=" + this.reli + ")"; + } + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/BugRelationship.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/BugRelationship.java new file mode 100644 index 000000000..5c0af92d7 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/BugRelationship.java @@ -0,0 +1,77 @@ +/* + * Copyright 2011-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.integration.issues.gh2905; + +import org.springframework.data.neo4j.core.schema.RelationshipId; +import org.springframework.data.neo4j.core.schema.RelationshipProperties; +import org.springframework.data.neo4j.core.schema.TargetNode; + +/** + * @author Mathias Kühn + */ +@SuppressWarnings("HiddenField") // Not worth cleaning up the Delomboked version +@RelationshipProperties +class BugRelationship { + @RelationshipId + protected Long id; + + protected String comment; + + @TargetNode + private BugTargetBase target; + + BugRelationship(Long id, String comment, BugTargetBase target) { + this.id = id; + this.comment = comment; + this.target = target; + } + + public static BugRelationshipBuilder builder() { + return new BugRelationshipBuilder(); + } + + public static class BugRelationshipBuilder { + private Long id; + private String comment; + private BugTargetBase target; + + BugRelationshipBuilder() { + } + + public BugRelationshipBuilder id(Long id) { + this.id = id; + return this; + } + + public BugRelationshipBuilder comment(String comment) { + this.comment = comment; + return this; + } + + public BugRelationshipBuilder target(BugTargetBase target) { + this.target = target; + return this; + } + + public BugRelationship build() { + return new BugRelationship(this.id, this.comment, this.target); + } + + public String toString() { + return "BugRelationship.BugRelationshipBuilder(id=" + this.id + ", comment=" + this.comment + ", target=" + this.target + ")"; + } + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/BugTarget.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/BugTarget.java new file mode 100644 index 000000000..74dbeb723 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/BugTarget.java @@ -0,0 +1,73 @@ +/* + * Copyright 2011-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.integration.issues.gh2905; + +import java.util.Set; + +/** + * @author Mathias Kühn + */ +@SuppressWarnings("HiddenField") // Not worth cleaning up the Delomboked version +class BugTarget extends BugTargetBase { + private String type; + + BugTarget(String uuid, String name, Set relatedBugs, String type) { + super(uuid, name, relatedBugs); + this.type = type; + } + + public static BugTargetBuilder builder() { + return new BugTargetBuilder(); + } + + public static class BugTargetBuilder { + private String uuid; + private String name; + private Set relatedBugs; + private String type; + + BugTargetBuilder() { + } + + public BugTargetBuilder uuid(String uuid) { + this.uuid = uuid; + return this; + } + + public BugTargetBuilder name(String name) { + this.name = name; + return this; + } + + public BugTargetBuilder relatedBugs(Set relatedBugs) { + this.relatedBugs = relatedBugs; + return this; + } + + public BugTargetBuilder type(String type) { + this.type = type; + return this; + } + + public BugTarget build() { + return new BugTarget(this.uuid, this.name, this.relatedBugs, this.type); + } + + public String toString() { + return "BugTarget.BugTargetBuilder(uuid=" + this.uuid + ", name=" + this.name + ", relatedBugs=" + this.relatedBugs + ", type=" + this.type + ")"; + } + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/BugTargetBase.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/BugTargetBase.java new file mode 100644 index 000000000..2a0c4c3ae --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/BugTargetBase.java @@ -0,0 +1,43 @@ +/* + * Copyright 2011-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.integration.issues.gh2905; + +import java.util.Set; + +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Relationship; +import org.springframework.data.neo4j.core.support.UUIDStringGenerator; + +/** + * @author Mathias Kühn + */ +abstract class BugTargetBase { + @Id + @GeneratedValue(UUIDStringGenerator.class) + protected String uuid; + + private String name; + + @Relationship(type = "RELI", direction = Relationship.Direction.OUTGOING) + Set relatedBugs; + + BugTargetBase(String uuid, String name, Set relatedBugs) { + this.uuid = uuid; + this.name = name; + this.relatedBugs = relatedBugs; + } +} diff --git a/src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/Gh2905IT.java b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/Gh2905IT.java new file mode 100644 index 000000000..d6f061450 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/integration/issues/gh2905/Gh2905IT.java @@ -0,0 +1,148 @@ +/* + * Copyright 2011-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.integration.issues.gh2905; + +import static org.assertj.core.api.Assertions.as; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.neo4j.driver.Driver; +import org.neo4j.driver.Value; +import org.neo4j.driver.types.TypeSystem; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.neo4j.repository.Neo4jRepository; +import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; +import org.springframework.data.neo4j.test.Neo4jExtension; +import org.springframework.data.neo4j.test.Neo4jImperativeTestConfiguration; +import org.springframework.data.neo4j.test.Neo4jIntegrationTest; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/** + * @author Michael J. Simons + */ +@Neo4jIntegrationTest +public class Gh2905IT { + + protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport; + + interface FromRepository extends Neo4jRepository { + + } + + interface ToRepository extends Neo4jRepository { + + } + + @BeforeEach + void clearDatabase(@Autowired Driver driver) { + driver.executableQuery("MATCH (n) DETACH DELETE n").execute(); + } + + @Test + void storeFromRootAggregate(@Autowired ToRepository toRepository, @Autowired Driver driver) { + var to1 = BugTarget.builder().name("T1").type("BUG").build(); + + var from1 = BugFrom.builder() + .name("F1") + .reli(BugRelationship.builder().target(to1).comment("F1<-T1").build()) + .build(); + var from2 = BugFrom.builder() + .name("F2") + .reli(BugRelationship.builder().target(to1).comment("F2<-T1").build()) + .build(); + var from3 = BugFrom.builder() + .name("F3") + .reli(BugRelationship.builder().target(to1).comment("F3<-T1").build()) + .build(); + + to1.relatedBugs = Set.of(from1, from2, from3); + toRepository.save(to1); + + assertGraph(driver); + } + + @Test + void saveSingleEntities(@Autowired FromRepository fromRepository, @Autowired ToRepository toRepository, @Autowired Driver driver) { + var to1 = BugTarget.builder().name("T1").type("BUG").build(); + to1.relatedBugs = new HashSet<>(); + to1 = toRepository.save(to1); + + var from1 = BugFrom.builder() + .name("F1") + .reli(BugRelationship.builder().target(to1).comment("F1<-T1").build()) + .build(); + // This is the key to solve 2905 when you had the annotation previously, you must maintain both ends of the bidirectional relationship. + // SDN does not do this for you. + to1.relatedBugs.add(from1); + from1 = fromRepository.save(from1); + + var from2 = BugFrom.builder() + .name("F2") + .reli(BugRelationship.builder().target(to1).comment("F2<-T1").build()) + .build(); + // See above + to1.relatedBugs.add(from2); + + var from3 = BugFrom.builder() + .name("F3") + .reli(BugRelationship.builder().target(to1).comment("F3<-T1").build()) + .build(); + to1.relatedBugs.add(from3); + // See above + fromRepository.saveAll(List.of(from1, from2, from3)); + + assertGraph(driver); + } + + private static void assertGraph(Driver driver) { + var result = driver.executableQuery("MATCH (t:BugTarget) -[:RELI] ->(f:BugFrom) RETURN t, collect(f) AS f").execute().records(); + assertThat(result) + .hasSize(1) + .element(0).satisfies(r -> { + assertThat(r.get("t")).matches(TypeSystem.getDefault().NODE()::isTypeOf); + assertThat(r.get("f")) + .matches(TypeSystem.getDefault().LIST()::isTypeOf) + .extracting(Value::asList, as(InstanceOfAssertFactories.LIST)) + .hasSize(3); + }); + } + + @Configuration + @EnableTransactionManagement + @EnableNeo4jRepositories(considerNestedRepositories = true) + static class Config extends Neo4jImperativeTestConfiguration { + + @Bean + public Driver driver() { + + return neo4jConnectionSupport.getDriver(); + } + + + @Override + public boolean isCypher5Compatible() { + return false; + } + } +}