Skip to content

Commit d45153d

Browse files
authored
Merge pull request #33 from RandomCodeSpace/coverage/boost-80pct
2 parents 91c032e + a683877 commit d45153d

35 files changed

Lines changed: 8239 additions & 87 deletions

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,12 @@
266266
<groupId>org.jacoco</groupId>
267267
<artifactId>jacoco-maven-plugin</artifactId>
268268
<version>${jacoco.version}</version>
269+
<configuration>
270+
<!-- Exclude ANTLR-generated grammar parsers from coverage -->
271+
<excludes>
272+
<exclude>io/github/randomcodespace/iq/grammar/**</exclude>
273+
</excludes>
274+
</configuration>
269275
<executions>
270276
<execution>
271277
<id>prepare-agent</id>
Lines changed: 377 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,377 @@
1+
package io.github.randomcodespace.iq.analyzer.linker;
2+
3+
import io.github.randomcodespace.iq.model.CodeEdge;
4+
import io.github.randomcodespace.iq.model.CodeNode;
5+
import io.github.randomcodespace.iq.model.EdgeKind;
6+
import io.github.randomcodespace.iq.model.NodeKind;
7+
import org.junit.jupiter.api.Nested;
8+
import org.junit.jupiter.api.Test;
9+
10+
import java.util.List;
11+
12+
import static org.junit.jupiter.api.Assertions.*;
13+
14+
/**
15+
* Additional coverage tests for linker classes — branches not hit by
16+
* existing tests.
17+
*/
18+
class LinkersCoverageTest {
19+
20+
// =====================================================================
21+
// GuardLinker
22+
// =====================================================================
23+
@Nested
24+
class GuardLinkerCoverage {
25+
private final GuardLinker linker = new GuardLinker();
26+
27+
@Test
28+
void linksGuardToEndpointInSameFile() {
29+
var guard = new CodeNode("guard:auth1", NodeKind.GUARD, "AuthGuard");
30+
guard.setFilePath("src/UserController.java");
31+
32+
var endpoint = new CodeNode("ep:getUser", NodeKind.ENDPOINT, "GET /users/{id}");
33+
endpoint.setFilePath("src/UserController.java");
34+
35+
LinkResult result = linker.link(List.of(guard, endpoint), List.of());
36+
37+
assertEquals(1, result.edges().size());
38+
CodeEdge edge = result.edges().getFirst();
39+
assertEquals(EdgeKind.PROTECTS, edge.getKind());
40+
assertEquals("guard:auth1", edge.getSourceId());
41+
assertEquals("ep:getUser", edge.getTarget().getId());
42+
assertEquals(true, edge.getProperties().get("inferred"));
43+
}
44+
45+
@Test
46+
void linksMiddlewareToEndpoint() {
47+
var middleware = new CodeNode("mw:jwt", NodeKind.MIDDLEWARE, "JwtMiddleware");
48+
middleware.setFilePath("src/SecureController.java");
49+
50+
var endpoint = new CodeNode("ep:secure", NodeKind.ENDPOINT, "POST /secure");
51+
endpoint.setFilePath("src/SecureController.java");
52+
53+
LinkResult result = linker.link(List.of(middleware, endpoint), List.of());
54+
55+
assertEquals(1, result.edges().size());
56+
assertEquals(EdgeKind.PROTECTS, result.edges().getFirst().getKind());
57+
}
58+
59+
@Test
60+
void noLinkBetweenDifferentFiles() {
61+
var guard = new CodeNode("guard:g1", NodeKind.GUARD, "Guard");
62+
guard.setFilePath("src/GuardConfig.java");
63+
64+
var endpoint = new CodeNode("ep:e1", NodeKind.ENDPOINT, "GET /data");
65+
endpoint.setFilePath("src/DataController.java");
66+
67+
LinkResult result = linker.link(List.of(guard, endpoint), List.of());
68+
69+
assertTrue(result.edges().isEmpty());
70+
}
71+
72+
@Test
73+
void noGuardsReturnsEmpty() {
74+
var endpoint = new CodeNode("ep:e1", NodeKind.ENDPOINT, "GET /x");
75+
endpoint.setFilePath("src/Ctrl.java");
76+
77+
LinkResult result = linker.link(List.of(endpoint), List.of());
78+
assertTrue(result.edges().isEmpty());
79+
}
80+
81+
@Test
82+
void noEndpointsReturnsEmpty() {
83+
var guard = new CodeNode("g:g1", NodeKind.GUARD, "G");
84+
guard.setFilePath("src/Ctrl.java");
85+
86+
LinkResult result = linker.link(List.of(guard), List.of());
87+
assertTrue(result.edges().isEmpty());
88+
}
89+
90+
@Test
91+
void nodeWithNullFilePathSkipped() {
92+
var guard = new CodeNode("g:g1", NodeKind.GUARD, "G");
93+
// no filePath set
94+
95+
var endpoint = new CodeNode("ep:e1", NodeKind.ENDPOINT, "GET /x");
96+
// no filePath set
97+
98+
LinkResult result = linker.link(List.of(guard, endpoint), List.of());
99+
assertTrue(result.edges().isEmpty());
100+
}
101+
102+
@Test
103+
void nodeWithBlankFilePathSkipped() {
104+
var guard = new CodeNode("g:g1", NodeKind.GUARD, "G");
105+
guard.setFilePath(" ");
106+
107+
var endpoint = new CodeNode("ep:e1", NodeKind.ENDPOINT, "GET /x");
108+
endpoint.setFilePath(" ");
109+
110+
LinkResult result = linker.link(List.of(guard, endpoint), List.of());
111+
assertTrue(result.edges().isEmpty());
112+
}
113+
114+
@Test
115+
void avoidsDuplicateProtectsEdges() {
116+
var guard = new CodeNode("g:g1", NodeKind.GUARD, "G");
117+
guard.setFilePath("src/Ctrl.java");
118+
119+
var endpoint = new CodeNode("ep:e1", NodeKind.ENDPOINT, "GET /x");
120+
endpoint.setFilePath("src/Ctrl.java");
121+
122+
// Pre-existing PROTECTS edge
123+
var existing = new CodeEdge();
124+
existing.setId("existing");
125+
existing.setKind(EdgeKind.PROTECTS);
126+
existing.setSourceId("g:g1");
127+
existing.setTarget(endpoint);
128+
129+
LinkResult result = linker.link(List.of(guard, endpoint), List.of(existing));
130+
assertTrue(result.edges().isEmpty());
131+
}
132+
133+
@Test
134+
void multipleGuardsAndEndpointsCrossLinked() {
135+
var guard1 = new CodeNode("g:g1", NodeKind.GUARD, "Auth");
136+
guard1.setFilePath("src/Ctrl.java");
137+
var guard2 = new CodeNode("g:g2", NodeKind.MIDDLEWARE, "Logging");
138+
guard2.setFilePath("src/Ctrl.java");
139+
var ep1 = new CodeNode("ep:e1", NodeKind.ENDPOINT, "GET /a");
140+
ep1.setFilePath("src/Ctrl.java");
141+
var ep2 = new CodeNode("ep:e2", NodeKind.ENDPOINT, "POST /b");
142+
ep2.setFilePath("src/Ctrl.java");
143+
144+
LinkResult result = linker.link(List.of(guard1, guard2, ep1, ep2), List.of());
145+
// 2 guards x 2 endpoints = 4 edges
146+
assertEquals(4, result.edges().size());
147+
}
148+
149+
@Test
150+
void deterministic() {
151+
var guard = new CodeNode("g:g1", NodeKind.GUARD, "G");
152+
guard.setFilePath("f.java");
153+
var ep1 = new CodeNode("ep:e1", NodeKind.ENDPOINT, "GET /a");
154+
ep1.setFilePath("f.java");
155+
var ep2 = new CodeNode("ep:e2", NodeKind.ENDPOINT, "GET /b");
156+
ep2.setFilePath("f.java");
157+
158+
LinkResult r1 = linker.link(List.of(guard, ep1, ep2), List.of());
159+
LinkResult r2 = linker.link(List.of(guard, ep1, ep2), List.of());
160+
161+
assertEquals(r1.edges().size(), r2.edges().size());
162+
for (int i = 0; i < r1.edges().size(); i++) {
163+
assertEquals(r1.edges().get(i).getId(), r2.edges().get(i).getId());
164+
}
165+
}
166+
}
167+
168+
// =====================================================================
169+
// EntityLinker — additional branches
170+
// =====================================================================
171+
@Nested
172+
class EntityLinkerCoverage {
173+
private final EntityLinker linker = new EntityLinker();
174+
175+
@Test
176+
void matchesFqnSimpleNameForEntity() {
177+
// Entity has fqn "com.example.User" — repo uses label "User"
178+
var entity = new CodeNode("entity:com.example.User", NodeKind.ENTITY, "UserEntity");
179+
entity.setFqn("com.example.User");
180+
var repo = new CodeNode("repo:UserRepository", NodeKind.REPOSITORY, "UserRepository");
181+
182+
LinkResult result = linker.link(List.of(entity, repo), List.of());
183+
184+
// "user" (from fqn "com.example.User" -> "user") should match "user" (from "UserRepository" - "Repository")
185+
assertEquals(1, result.edges().size());
186+
}
187+
188+
@Test
189+
void matchesByLabelLowercase() {
190+
var entity = new CodeNode("entity:Product", NodeKind.ENTITY, "Product");
191+
var repo = new CodeNode("repo:ProductRepository", NodeKind.REPOSITORY, "ProductRepository");
192+
193+
LinkResult result = linker.link(List.of(entity, repo), List.of());
194+
assertEquals(1, result.edges().size());
195+
assertEquals("entity:Product", result.edges().getFirst().getTarget().getId());
196+
}
197+
198+
@Test
199+
void multipleReposForSameEntity() {
200+
var entity = new CodeNode("entity:Order", NodeKind.ENTITY, "Order");
201+
var repo1 = new CodeNode("repo:OrderRepository", NodeKind.REPOSITORY, "OrderRepository");
202+
var repo2 = new CodeNode("repo:OrderRepo", NodeKind.REPOSITORY, "OrderRepo");
203+
204+
LinkResult result = linker.link(List.of(entity, repo1, repo2), List.of());
205+
assertEquals(2, result.edges().size());
206+
}
207+
208+
@Test
209+
void entityWithNullFqnUsesLabel() {
210+
var entity = new CodeNode("entity:Item", NodeKind.ENTITY, "Item");
211+
// fqn is null
212+
var repo = new CodeNode("repo:ItemDao", NodeKind.REPOSITORY, "ItemDao");
213+
214+
LinkResult result = linker.link(List.of(entity, repo), List.of());
215+
assertEquals(1, result.edges().size());
216+
}
217+
218+
@Test
219+
void noPrefixMatchSkips() {
220+
// "SalesDAO" doesn't match "Product" entity
221+
var entity = new CodeNode("entity:Product", NodeKind.ENTITY, "Product");
222+
var repo = new CodeNode("repo:SalesDAO", NodeKind.REPOSITORY, "SalesDAO");
223+
224+
LinkResult result = linker.link(List.of(entity, repo), List.of());
225+
assertTrue(result.edges().isEmpty());
226+
}
227+
228+
@Test
229+
void deterministic() {
230+
var entity1 = new CodeNode("entity:Alpha", NodeKind.ENTITY, "Alpha");
231+
var entity2 = new CodeNode("entity:Beta", NodeKind.ENTITY, "Beta");
232+
var repo1 = new CodeNode("repo:AlphaRepository", NodeKind.REPOSITORY, "AlphaRepository");
233+
var repo2 = new CodeNode("repo:BetaRepo", NodeKind.REPOSITORY, "BetaRepo");
234+
235+
LinkResult r1 = linker.link(List.of(entity1, entity2, repo1, repo2), List.of());
236+
LinkResult r2 = linker.link(List.of(entity1, entity2, repo1, repo2), List.of());
237+
238+
assertEquals(r1.edges().size(), r2.edges().size());
239+
for (int i = 0; i < r1.edges().size(); i++) {
240+
assertEquals(r1.edges().get(i).getId(), r2.edges().get(i).getId());
241+
}
242+
}
243+
}
244+
245+
// =====================================================================
246+
// ModuleContainmentLinker — additional branches
247+
// =====================================================================
248+
@Nested
249+
class ModuleContainmentCoverage {
250+
private final ModuleContainmentLinker linker = new ModuleContainmentLinker();
251+
252+
@Test
253+
void multipleKindsInSameModule() {
254+
var cls = new CodeNode("cls:A", NodeKind.CLASS, "A");
255+
cls.setModule("org.example");
256+
var iface = new CodeNode("iface:B", NodeKind.INTERFACE, "B");
257+
iface.setModule("org.example");
258+
var enm = new CodeNode("enum:C", NodeKind.ENUM, "C");
259+
enm.setModule("org.example");
260+
261+
LinkResult result = linker.link(List.of(cls, iface, enm), List.of());
262+
263+
assertEquals(1, result.nodes().size()); // one module node
264+
assertEquals(3, result.edges().size()); // 3 CONTAINS edges
265+
}
266+
267+
@Test
268+
void nullModuleSkipped() {
269+
var node = new CodeNode("cls:A", NodeKind.CLASS, "A");
270+
// module not set — should be null
271+
272+
LinkResult result = linker.link(List.of(node), List.of());
273+
assertTrue(result.nodes().isEmpty());
274+
assertTrue(result.edges().isEmpty());
275+
}
276+
277+
@Test
278+
void deterministic() {
279+
var n1 = new CodeNode("cls:X", NodeKind.CLASS, "X");
280+
n1.setModule("com.mod");
281+
var n2 = new CodeNode("cls:Y", NodeKind.CLASS, "Y");
282+
n2.setModule("com.mod");
283+
284+
LinkResult r1 = linker.link(List.of(n1, n2), List.of());
285+
LinkResult r2 = linker.link(List.of(n1, n2), List.of());
286+
287+
assertEquals(r1.nodes().size(), r2.nodes().size());
288+
assertEquals(r1.edges().size(), r2.edges().size());
289+
}
290+
}
291+
292+
// =====================================================================
293+
// TopicLinker — additional branches
294+
// =====================================================================
295+
@Nested
296+
class TopicLinkerCoverage {
297+
private final TopicLinker linker = new TopicLinker();
298+
299+
@Test
300+
void emptyNodesAndEdgesReturnsEmpty() {
301+
LinkResult result = linker.link(List.of(), List.of());
302+
assertTrue(result.edges().isEmpty());
303+
assertTrue(result.nodes().isEmpty());
304+
}
305+
306+
@Test
307+
void topicWithNoProducersOrConsumersReturnsEmpty() {
308+
var topic = new CodeNode("topic:orphan", NodeKind.TOPIC, "orphan");
309+
LinkResult result = linker.link(List.of(topic), List.of());
310+
assertTrue(result.edges().isEmpty());
311+
}
312+
313+
@Test
314+
void multipleConsumersForOneTopic() {
315+
var topic = new CodeNode("topic:updates", NodeKind.TOPIC, "updates");
316+
var producer = new CodeNode("svc:Prod", NodeKind.CLASS, "Prod");
317+
var consumer1 = new CodeNode("svc:Con1", NodeKind.CLASS, "Con1");
318+
var consumer2 = new CodeNode("svc:Con2", NodeKind.CLASS, "Con2");
319+
320+
var producesEdge = new CodeEdge();
321+
producesEdge.setId("e1");
322+
producesEdge.setKind(EdgeKind.PRODUCES);
323+
producesEdge.setSourceId("svc:Prod");
324+
producesEdge.setTarget(topic);
325+
326+
var consumesEdge1 = new CodeEdge();
327+
consumesEdge1.setId("e2");
328+
consumesEdge1.setKind(EdgeKind.CONSUMES);
329+
consumesEdge1.setSourceId("svc:Con1");
330+
consumesEdge1.setTarget(topic);
331+
332+
var consumesEdge2 = new CodeEdge();
333+
consumesEdge2.setId("e3");
334+
consumesEdge2.setKind(EdgeKind.CONSUMES);
335+
consumesEdge2.setSourceId("svc:Con2");
336+
consumesEdge2.setTarget(topic);
337+
338+
LinkResult result = linker.link(
339+
List.of(topic, producer, consumer1, consumer2),
340+
List.of(producesEdge, consumesEdge1, consumesEdge2));
341+
342+
assertEquals(2, result.edges().size());
343+
}
344+
345+
@Test
346+
void multipleProducersForOneTopic() {
347+
var topic = new CodeNode("topic:orders", NodeKind.TOPIC, "orders");
348+
var prod1 = new CodeNode("svc:Prod1", NodeKind.CLASS, "Prod1");
349+
var prod2 = new CodeNode("svc:Prod2", NodeKind.CLASS, "Prod2");
350+
var consumer = new CodeNode("svc:Con", NodeKind.CLASS, "Con");
351+
352+
var e1 = new CodeEdge();
353+
e1.setId("e1");
354+
e1.setKind(EdgeKind.PRODUCES);
355+
e1.setSourceId("svc:Prod1");
356+
e1.setTarget(topic);
357+
358+
var e2 = new CodeEdge();
359+
e2.setId("e2");
360+
e2.setKind(EdgeKind.PRODUCES);
361+
e2.setSourceId("svc:Prod2");
362+
e2.setTarget(topic);
363+
364+
var e3 = new CodeEdge();
365+
e3.setId("e3");
366+
e3.setKind(EdgeKind.CONSUMES);
367+
e3.setSourceId("svc:Con");
368+
e3.setTarget(topic);
369+
370+
LinkResult result = linker.link(
371+
List.of(topic, prod1, prod2, consumer),
372+
List.of(e1, e2, e3));
373+
374+
assertEquals(2, result.edges().size());
375+
}
376+
}
377+
}

0 commit comments

Comments
 (0)