1 package de.dlr.shepard.common.neo4j.daos;
2
3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 import static org.junit.jupiter.api.Assertions.assertFalse;
5 import static org.junit.jupiter.api.Assertions.assertTrue;
6 import static org.mockito.ArgumentMatchers.any;
7 import static org.mockito.ArgumentMatchers.eq;
8 import static org.mockito.Mockito.doNothing;
9 import static org.mockito.Mockito.mock;
10 import static org.mockito.Mockito.verify;
11 import static org.mockito.Mockito.when;
12
13 import de.dlr.shepard.BaseTestCase;
14 import de.dlr.shepard.common.util.TraversalRules;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.stream.Stream;
18 import lombok.Data;
19 import org.junit.jupiter.api.Test;
20 import org.junit.jupiter.params.ParameterizedTest;
21 import org.junit.jupiter.params.provider.Arguments;
22 import org.junit.jupiter.params.provider.MethodSource;
23 import org.mockito.ArgumentCaptor;
24 import org.mockito.Captor;
25 import org.mockito.InjectMocks;
26 import org.mockito.Mock;
27 import org.neo4j.ogm.cypher.ComparisonOperator;
28 import org.neo4j.ogm.cypher.Filter;
29 import org.neo4j.ogm.cypher.query.Pagination;
30 import org.neo4j.ogm.model.QueryStatistics;
31 import org.neo4j.ogm.model.Result;
32 import org.neo4j.ogm.session.Session;
33
34 public class GenericDAOTest extends BaseTestCase {
35
36 @Data
37 private static class TestObject {
38
39 private final int a;
40 }
41
42 private static class TestDAO extends GenericDAO<TestObject> {
43
44 @Override
45 public Class<TestObject> getEntityType() {
46 return TestObject.class;
47 }
48 }
49
50 @Mock
51 private Session session;
52
53 @InjectMocks
54 private TestDAO dao = new TestDAO();
55
56 @Captor
57 ArgumentCaptor<Pagination> paginationCaptor;
58
59 @Test
60 public void findAllTest() {
61 var a = new TestObject(1);
62 var b = new TestObject(2);
63
64 when(session.loadAll(TestObject.class, 1)).thenReturn(List.of(a, b));
65 var actual = dao.findAll();
66 assertEquals(List.of(a, b), actual);
67 }
68
69 @Test
70 public void findMatchingTest() {
71 var a = new TestObject(1);
72 var filter = new Filter("a", ComparisonOperator.EQUALS, 1);
73
74 when(session.loadAll(TestObject.class, filter, 1)).thenReturn(List.of(a));
75 var actual = dao.findMatching(filter);
76 assertEquals(List.of(a), actual);
77 }
78
79 @Test
80 public void findTest() {
81 var a = new TestObject(1);
82
83 when(session.load(TestObject.class, 1L, 1)).thenReturn(a);
84 var actual = dao.findByNeo4jId(1L);
85 assertEquals(a, actual);
86 }
87
88 @Test
89 public void findLightTest() {
90 var a = new TestObject(1);
91
92 when(session.load(TestObject.class, 1L, 0)).thenReturn(a);
93 var actual = dao.findLightByNeo4jId(1L);
94 assertEquals(a, actual);
95 }
96
97 @Test
98 public void deleteTest_Successful() {
99 var a = new TestObject(1);
100
101 when(session.load(TestObject.class, 1L)).thenReturn(a);
102 doNothing().when(session).delete(a);
103 var actual = dao.deleteByNeo4jId(1L);
104 assertTrue(actual);
105 }
106
107 @Test
108 public void deleteTest_Error() {
109 var a = new TestObject(1);
110
111 when(session.load(TestObject.class, 1L)).thenReturn(null);
112 doNothing().when(session).delete(a);
113 var actual = dao.deleteByNeo4jId(1L);
114 assertFalse(actual);
115 }
116
117 @Test
118 public void createOrUpdateTest() {
119 var a = new TestObject(1);
120
121 doNothing().when(session).save(a, 1);
122 var actual = dao.createOrUpdate(a);
123 assertEquals(a, actual);
124 }
125
126 @Test
127 public void findByQueryTest() {
128 var a = new TestObject(1);
129 var query = "MATCH (n {a: 1}) RETURN n";
130 Map<String, Object> params = Map.of("a", "b", "c", "d");
131
132 when(session.query(TestObject.class, query, params)).thenReturn(List.of(a));
133 var actual = dao.findByQuery(query, params);
134 assertEquals(List.of(a), actual);
135 }
136
137 @Test
138 public void runQueryTest() {
139 var query = "MATCH (n {a: 1}) RETURN n";
140 Map<String, Object> params = Map.of("a", "b", "c", "d");
141 Result result = mock(Result.class);
142 QueryStatistics stat = mock(QueryStatistics.class);
143
144 when(session.query(query, params)).thenReturn(result);
145 when(result.queryStatistics()).thenReturn(stat);
146 when(stat.containsUpdates()).thenReturn(true);
147
148 var actual = dao.runQuery(query, params);
149 assertTrue(actual);
150 }
151
152 @Test
153 public void getSearchForReachableReferencesQueryStartIdTest() {
154 long startId = 1L;
155 long collectionId = 2L;
156 String userName = "user";
157 String startIdQuery =
158 "MATCH path = (col:Collection)-[:has_dataobject]->(d:DataObject)-[hr:has_reference]->(r:TestObject) WITH nodes(path) as ns, r as ret WHERE id(d) = 1 AND id(col) = 2 AND NONE(node IN ns WHERE (node.deleted = TRUE)) AND (NOT exists((col)-[:has_permissions]->(:Permissions)) OR exists((col)-[:has_permissions]->(:Permissions)-[:readable_by|owned_by]->(:User { username: \"user\" })) OR exists((col)-[:has_permissions]->(:Permissions {permissionType: \"Public\"})) OR exists((col)-[:has_permissions]->(:Permissions {permissionType: \"PublicReadable\"})) OR exists((col)-[:has_permissions]->(:Permissions)-[:readable_by_group]->(:UserGroup)<-[:is_in_group]-(:User { username: \"user\"}))) MATCH path=(ret)-[*0..1]-(n) WHERE n.deleted = FALSE OR n.deleted IS NULL RETURN ret, nodes(path), relationships(path)";
159 var actual = dao.getSearchForReachableReferencesQuery(collectionId, startId, userName);
160 assertEquals(startIdQuery, actual);
161 }
162
163 @Test
164 public void getSearchForReachableReferencesByShepardIdQueryStartIdTest() {
165 long startShepardId = 11L;
166 long collectionShepardId = 21L;
167 String userName = "user";
168 String startIdQuery =
169 "MATCH path = (col:Collection)-[:has_dataobject]->(d:DataObject)-[hr:has_reference]->(r:TestObject) WITH nodes(path) as ns, r as ret WHERE d.shepardId = 11 AND col.shepardId = 21 AND NONE(node IN ns WHERE (node.deleted = TRUE)) AND (NOT exists((col)-[:has_permissions]->(:Permissions)) OR exists((col)-[:has_permissions]->(:Permissions)-[:readable_by|owned_by]->(:User { username: \"user\" })) OR exists((col)-[:has_permissions]->(:Permissions {permissionType: \"Public\"})) OR exists((col)-[:has_permissions]->(:Permissions {permissionType: \"PublicReadable\"})) OR exists((col)-[:has_permissions]->(:Permissions)-[:readable_by_group]->(:UserGroup)<-[:is_in_group]-(:User { username: \"user\"}))) MATCH path=(ret)-[*0..1]-(n) WHERE n.deleted = FALSE OR n.deleted IS NULL RETURN ret, nodes(path), relationships(path)";
170 var actual = dao.getSearchForReachableReferencesByShepardIdQuery(collectionShepardId, startShepardId, userName);
171 assertEquals(startIdQuery, actual);
172 }
173
174 @Test
175 public void getSearchForReachableReferencesByShepardIdWithoutStartIdQueryStartIdTest() {
176 long collectionShepardId = 21L;
177 String username = "Leonard Bernstein";
178 String startIdQuery =
179 "MATCH path = (col:Collection)-[:has_dataobject]->(do:DataObject)-[hr:has_reference]->(r:TestObject) WITH nodes(path) as ns, r as ret WHERE col.shepardId = 21 AND NONE(node IN ns WHERE (node.deleted = TRUE)) AND (NOT exists((col)-[:has_permissions]->(:Permissions)) OR exists((col)-[:has_permissions]->(:Permissions)-[:readable_by|owned_by]->(:User { username: \"Leonard Bernstein\" })) OR exists((col)-[:has_permissions]->(:Permissions {permissionType: \"Public\"})) OR exists((col)-[:has_permissions]->(:Permissions {permissionType: \"PublicReadable\"})) OR exists((col)-[:has_permissions]->(:Permissions)-[:readable_by_group]->(:UserGroup)<-[:is_in_group]-(:User { username: \"Leonard Bernstein\"}))) MATCH path=(ret)-[*0..1]-(n) WHERE n.deleted = FALSE OR n.deleted IS NULL RETURN ret, nodes(path), relationships(path)";
180 var actual = dao.getSearchForReachableReferencesByShepardIdQuery(collectionShepardId, username);
181 assertEquals(startIdQuery, actual);
182 }
183
184 private static Stream<Arguments> getSearchForReachableReferencesByShepardIdQueryTest() {
185 String childrenQuery =
186 "MATCH path = (col:Collection)-[:has_dataobject]->(d:DataObject)-[:has_child*0..]->(e:DataObject)-[hr:has_reference]->(r:TestObject) WITH nodes(path) as ns, r as ret WHERE d.shepardId = 11 AND col.shepardId = 21 AND NONE(node IN ns WHERE (node.deleted = TRUE)) AND (NOT exists((col)-[:has_permissions]->(:Permissions)) OR exists((col)-[:has_permissions]->(:Permissions)-[:readable_by|owned_by]->(:User { username: \"user\" })) OR exists((col)-[:has_permissions]->(:Permissions {permissionType: \"Public\"})) OR exists((col)-[:has_permissions]->(:Permissions {permissionType: \"PublicReadable\"})) OR exists((col)-[:has_permissions]->(:Permissions)-[:readable_by_group]->(:UserGroup)<-[:is_in_group]-(:User { username: \"user\"}))) MATCH path=(ret)-[*0..1]-(n) WHERE n.deleted = FALSE OR n.deleted IS NULL RETURN ret, nodes(path), relationships(path)";
187 String parentsQuery =
188 "MATCH path = (col:Collection)-[:has_dataobject]->(d:DataObject)<-[:has_child*0..]-(e:DataObject)-[hr:has_reference]->(r:TestObject) WITH nodes(path) as ns, r as ret WHERE d.shepardId = 11 AND col.shepardId = 21 AND NONE(node IN ns WHERE (node.deleted = TRUE)) AND (NOT exists((col)-[:has_permissions]->(:Permissions)) OR exists((col)-[:has_permissions]->(:Permissions)-[:readable_by|owned_by]->(:User { username: \"user\" })) OR exists((col)-[:has_permissions]->(:Permissions {permissionType: \"Public\"})) OR exists((col)-[:has_permissions]->(:Permissions {permissionType: \"PublicReadable\"})) OR exists((col)-[:has_permissions]->(:Permissions)-[:readable_by_group]->(:UserGroup)<-[:is_in_group]-(:User { username: \"user\"}))) MATCH path=(ret)-[*0..1]-(n) WHERE n.deleted = FALSE OR n.deleted IS NULL RETURN ret, nodes(path), relationships(path)";
189 String predecessorsQuery =
190 "MATCH path = (col:Collection)-[:has_dataobject]->(d:DataObject)<-[:has_successor*0..]-(e:DataObject)-[hr:has_reference]->(r:TestObject) WITH nodes(path) as ns, r as ret WHERE d.shepardId = 11 AND col.shepardId = 21 AND NONE(node IN ns WHERE (node.deleted = TRUE)) AND (NOT exists((col)-[:has_permissions]->(:Permissions)) OR exists((col)-[:has_permissions]->(:Permissions)-[:readable_by|owned_by]->(:User { username: \"user\" })) OR exists((col)-[:has_permissions]->(:Permissions {permissionType: \"Public\"})) OR exists((col)-[:has_permissions]->(:Permissions {permissionType: \"PublicReadable\"})) OR exists((col)-[:has_permissions]->(:Permissions)-[:readable_by_group]->(:UserGroup)<-[:is_in_group]-(:User { username: \"user\"}))) MATCH path=(ret)-[*0..1]-(n) WHERE n.deleted = FALSE OR n.deleted IS NULL RETURN ret, nodes(path), relationships(path)";
191 String successorsQuery =
192 "MATCH path = (col:Collection)-[:has_dataobject]->(d:DataObject)-[:has_successor*0..]->(e:DataObject)-[hr:has_reference]->(r:TestObject) WITH nodes(path) as ns, r as ret WHERE d.shepardId = 11 AND col.shepardId = 21 AND NONE(node IN ns WHERE (node.deleted = TRUE)) AND (NOT exists((col)-[:has_permissions]->(:Permissions)) OR exists((col)-[:has_permissions]->(:Permissions)-[:readable_by|owned_by]->(:User { username: \"user\" })) OR exists((col)-[:has_permissions]->(:Permissions {permissionType: \"Public\"})) OR exists((col)-[:has_permissions]->(:Permissions {permissionType: \"PublicReadable\"})) OR exists((col)-[:has_permissions]->(:Permissions)-[:readable_by_group]->(:UserGroup)<-[:is_in_group]-(:User { username: \"user\"}))) MATCH path=(ret)-[*0..1]-(n) WHERE n.deleted = FALSE OR n.deleted IS NULL RETURN ret, nodes(path), relationships(path)";
193
194 return Stream.of(
195 Arguments.of(TraversalRules.children, childrenQuery),
196 Arguments.of(TraversalRules.parents, parentsQuery),
197 Arguments.of(TraversalRules.predecessors, predecessorsQuery),
198 Arguments.of(TraversalRules.successors, successorsQuery)
199 );
200
201 }
202
203 @ParameterizedTest
204 @MethodSource
205 public void getSearchForReachableReferencesByShepardIdQueryTest(TraversalRules traversalRules, String expected) {
206 long startShepardId = 11L;
207 long collectionShepardId = 21L;
208 String userName = "user";
209 var actual = dao.getSearchForReachableReferencesByShepardIdQuery(
210 traversalRules,
211 collectionShepardId,
212 startShepardId,
213 userName
214 );
215 assertEquals(expected, actual);
216 }
217
218 @Test
219 public void getSearchForReachableReferencesQueryCollectionIdOnly() {
220 long collectionId = 1L;
221 String userName = "user";
222 String expected =
223 "MATCH path = (col:Collection)-[:has_dataobject]->(do:DataObject)-[hr:has_reference]->(r:TestObject) WITH nodes(path) as ns, r as ret WHERE id(col) = 1 AND NONE(node IN ns WHERE (node.deleted = TRUE)) AND (NOT exists((col)-[:has_permissions]->(:Permissions)) OR exists((col)-[:has_permissions]->(:Permissions)-[:readable_by|owned_by]->(:User { username: \"user\" })) OR exists((col)-[:has_permissions]->(:Permissions {permissionType: \"Public\"})) OR exists((col)-[:has_permissions]->(:Permissions {permissionType: \"PublicReadable\"})) OR exists((col)-[:has_permissions]->(:Permissions)-[:readable_by_group]->(:UserGroup)<-[:is_in_group]-(:User { username: \"user\"}))) MATCH path=(ret)-[*0..1]-(n) WHERE n.deleted = FALSE OR n.deleted IS NULL RETURN ret, nodes(path), relationships(path)";
224 String actual = dao.getSearchForReachableReferencesQuery(collectionId, userName);
225 assertEquals(expected, actual);
226 }
227
228
229 @Test
230 public void deleteRelationTest() {
231 long fromId = 1L;
232 long toId = 2L;
233 String fromType = "fromType";
234 String toType = "toType";
235 String relationName = "relationName";
236 dao.deleteRelation(fromId, toId, fromType, toType, relationName);
237 String expected = "MATCH (a:fromType {shepardId: 1})-[r:relationName]->(b:toType {shepardId: 2}) DELETE r;";
238 verify(session).query(eq(expected), any());
239 }
240 }