Skip to content

Commit 8bf8bb8

Browse files
aksOpsclaude
andcommitted
test(coverage): add targeted tests for SonarCloud new-code backfill
Adds 112 new tests across 6 new classes targeting high-uncovered-line files flagged by SonarCloud + local JaCoCo. Net project LINE coverage moved 89.31% -> 89.94% (+120 covered lines, +0.63pp). Per-file deltas (line coverage): EnvVarOverlay.java 52.9% -> 100.0% (+47.1pp) AzureMessagingDetector.java 76.6% -> 100.0% (+23.4pp) AzureFunctionsDetector.java 79.2% -> 100.0% (+20.8pp) LanguageEnricher.java 74.6% -> 88.1% (+13.5pp) ModuleDepsDetector.java 78.9% -> 88.0% (+9.1pp) ConfigDefDetector.java 74.6% -> 75.4% (+0.8pp) Test additions: EnvVarOverlayExtendedTest — 19 tests, all switch cases, neo4j/mcp/observability overlays, splitCsv edge cases, determinism AzureMessagingDetectorTest — 21 tests (new file), SB + EH clients named + generic fallbacks AzureFunctionsDetectorTest — 15 tests (new file), every trigger branch: HTTP, SB queue/topic, Event Hub, Timer, CosmosDB, unknown ConfigDefDetectorTest — 18 tests (new file), AST branches for Kafka ConfigDef / @value / @ConfigurationProperties with deduplication and discriminator guard checks ModuleDepsDetectorTest — 18 tests (new file), Maven + Gradle (Groovy) + dep extraction LanguageEnricherExtendedTest — 14 tests, file_type skip list, missing file handling, null filePath, minified-file heuristic, fqn/id registry, mjs/cjs/pyw Follow-up (bugs observed while testing, NOT fixed here): - ModuleDepsDetector dispatch order: .gradle ends-with check matches settings.gradle before the dedicated settings.gradle branch, leaving detectGradleSettings() unreachable via detect(). - ExpressRouteDetector defines detectWithAst but the base detect() in AbstractTypeScriptDetector routes only to detectWithRegex, so the AST method is currently dead code. - RepositoryIdentityTest has two pre-existing env-dependent failures on main (resolve_gitRepoWithCommit_commitShaPresent, resolve_detachedHead_branchIsNull) - unrelated to this change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 84e8e65 commit 8bf8bb8

6 files changed

Lines changed: 1993 additions & 0 deletions

File tree

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
package io.github.randomcodespace.iq.config.unified;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.util.LinkedHashMap;
6+
import java.util.List;
7+
import java.util.Map;
8+
9+
import static org.assertj.core.api.Assertions.assertThat;
10+
import static org.junit.jupiter.api.Assertions.*;
11+
12+
/**
13+
* Extended tests for {@link EnvVarOverlay} covering switch-case branches not
14+
* exercised by {@link EnvVarOverlayTest} — project metadata, indexing bounds,
15+
* Neo4j pools, MCP auth + tools, observability, and detectors overlays.
16+
*/
17+
class EnvVarOverlayExtendedTest {
18+
19+
// ---------------------------------------------------------------
20+
// Project section
21+
// ---------------------------------------------------------------
22+
23+
@Test
24+
void readsProjectName() {
25+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of("CODEIQ_PROJECT_NAME", "acme"));
26+
assertEquals("acme", cfg.project().name());
27+
}
28+
29+
@Test
30+
void readsProjectRoot() {
31+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of("CODEIQ_PROJECT_ROOT", "/repo"));
32+
assertEquals("/repo", cfg.project().root());
33+
}
34+
35+
@Test
36+
void readsProjectServiceName() {
37+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of("CODEIQ_PROJECT_SERVICE_NAME", "orders-api"));
38+
assertEquals("orders-api", cfg.project().serviceName());
39+
}
40+
41+
// ---------------------------------------------------------------
42+
// Indexing bounds
43+
// ---------------------------------------------------------------
44+
45+
@Test
46+
void readsIndexingBatchsize() {
47+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of("CODEIQ_INDEXING_BATCHSIZE", "250"));
48+
assertEquals(250, cfg.indexing().batchSize());
49+
}
50+
51+
@Test
52+
void readsIndexingIncludeAndExclude() {
53+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of(
54+
"CODEIQ_INDEXING_INCLUDE", "src/**/*.java",
55+
"CODEIQ_INDEXING_EXCLUDE", "**/target/**,**/build/**"));
56+
assertEquals(List.of("src/**/*.java"), cfg.indexing().include());
57+
assertEquals(List.of("**/target/**", "**/build/**"), cfg.indexing().exclude());
58+
}
59+
60+
@Test
61+
void readsIndexingIncremental() {
62+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of("CODEIQ_INDEXING_INCREMENTAL", "false"));
63+
assertEquals(Boolean.FALSE, cfg.indexing().incremental());
64+
}
65+
66+
@Test
67+
void readsIndexingCacheDir() {
68+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of("CODEIQ_INDEXING_CACHEDIR", ".cache/intel"));
69+
assertEquals(".cache/intel", cfg.indexing().cacheDir());
70+
}
71+
72+
@Test
73+
void readsIndexingMaxDepthRadiusFilesSnippet() {
74+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of(
75+
"CODEIQ_INDEXING_MAX_DEPTH", "5",
76+
"CODEIQ_INDEXING_MAX_RADIUS", "3",
77+
"CODEIQ_INDEXING_MAX_FILES", "10000",
78+
"CODEIQ_INDEXING_MAX_SNIPPET_LINES", "40"));
79+
assertEquals(5, cfg.indexing().maxDepth());
80+
assertEquals(3, cfg.indexing().maxRadius());
81+
assertEquals(10000, cfg.indexing().maxFiles());
82+
assertEquals(40, cfg.indexing().maxSnippetLines());
83+
}
84+
85+
@Test
86+
void malformedIndexingBatchsizeThrowsWithVarName() {
87+
ConfigLoadException e = assertThrows(ConfigLoadException.class,
88+
() -> EnvVarOverlay.from(Map.of("CODEIQ_INDEXING_BATCHSIZE", "oops")));
89+
assertTrue(e.getMessage().contains("CODEIQ_INDEXING_BATCHSIZE"));
90+
}
91+
92+
// ---------------------------------------------------------------
93+
// Serving: bind addr + Neo4j pools
94+
// ---------------------------------------------------------------
95+
96+
@Test
97+
void readsServingBindAddress() {
98+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of("CODEIQ_SERVING_BINDADDRESS", "127.0.0.1"));
99+
assertEquals("127.0.0.1", cfg.serving().bindAddress());
100+
}
101+
102+
@Test
103+
void readsServingNeo4jDir() {
104+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of("CODEIQ_SERVING_NEO4J_DIR", "/var/neo4j"));
105+
assertEquals("/var/neo4j", cfg.serving().neo4j().dir());
106+
}
107+
108+
@Test
109+
void readsServingNeo4jPageCacheAndHeapSizes() {
110+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of(
111+
"CODEIQ_SERVING_NEO4J_PAGECACHEMB", "512",
112+
"CODEIQ_SERVING_NEO4J_HEAPINITIALMB", "256",
113+
"CODEIQ_SERVING_NEO4J_HEAPMAXMB", "1024"));
114+
assertEquals(512, cfg.serving().neo4j().pageCacheMb());
115+
assertEquals(256, cfg.serving().neo4j().heapInitialMb());
116+
assertEquals(1024, cfg.serving().neo4j().heapMaxMb());
117+
}
118+
119+
@Test
120+
void malformedPageCacheThrowsWithVarName() {
121+
ConfigLoadException e = assertThrows(ConfigLoadException.class,
122+
() -> EnvVarOverlay.from(Map.of("CODEIQ_SERVING_NEO4J_PAGECACHEMB", "huge")));
123+
assertTrue(e.getMessage().contains("CODEIQ_SERVING_NEO4J_PAGECACHEMB"));
124+
}
125+
126+
// ---------------------------------------------------------------
127+
// MCP: enabled + transport + basepath + auth + limits + tools
128+
// ---------------------------------------------------------------
129+
130+
@Test
131+
void readsMcpEnabledAndTransport() {
132+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of(
133+
"CODEIQ_MCP_ENABLED", "true",
134+
"CODEIQ_MCP_TRANSPORT", "http"));
135+
assertEquals(Boolean.TRUE, cfg.mcp().enabled());
136+
assertEquals("http", cfg.mcp().transport());
137+
}
138+
139+
@Test
140+
void readsMcpBasePath() {
141+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of("CODEIQ_MCP_BASEPATH", "/mcp"));
142+
assertEquals("/mcp", cfg.mcp().basePath());
143+
}
144+
145+
@Test
146+
void readsMcpAuthModeAndTokenEnv() {
147+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of(
148+
"CODEIQ_MCP_AUTH_MODE", "bearer",
149+
"CODEIQ_MCP_AUTH_TOKENENV", "MCP_TOKEN"));
150+
assertEquals("bearer", cfg.mcp().auth().mode());
151+
assertEquals("MCP_TOKEN", cfg.mcp().auth().tokenEnv());
152+
}
153+
154+
@Test
155+
void readsMcpLimitsAllFields() {
156+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of(
157+
"CODEIQ_MCP_LIMITS_PERTOOLTIMEOUTMS", "5000",
158+
"CODEIQ_MCP_LIMITS_MAXRESULTS", "200",
159+
"CODEIQ_MCP_LIMITS_MAXPAYLOADBYTES", "1048576",
160+
"CODEIQ_MCP_LIMITS_RATEPERMINUTE", "60"));
161+
assertEquals(5000, cfg.mcp().limits().perToolTimeoutMs());
162+
assertEquals(200, cfg.mcp().limits().maxResults());
163+
assertEquals(1_048_576L, cfg.mcp().limits().maxPayloadBytes());
164+
assertEquals(60, cfg.mcp().limits().ratePerMinute());
165+
}
166+
167+
@Test
168+
void malformedMaxPayloadBytesThrowsWithVarName() {
169+
ConfigLoadException e = assertThrows(ConfigLoadException.class,
170+
() -> EnvVarOverlay.from(Map.of("CODEIQ_MCP_LIMITS_MAXPAYLOADBYTES", "big")));
171+
assertTrue(e.getMessage().contains("CODEIQ_MCP_LIMITS_MAXPAYLOADBYTES"));
172+
}
173+
174+
@Test
175+
void readsMcpToolsEnabledAndDisabled() {
176+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of(
177+
"CODEIQ_MCP_TOOLS_ENABLED", "get_stats,find_consumers",
178+
"CODEIQ_MCP_TOOLS_DISABLED", "run_cypher"));
179+
assertEquals(List.of("get_stats", "find_consumers"), cfg.mcp().tools().enabled());
180+
assertEquals(List.of("run_cypher"), cfg.mcp().tools().disabled());
181+
}
182+
183+
// ---------------------------------------------------------------
184+
// Observability
185+
// ---------------------------------------------------------------
186+
187+
@Test
188+
void readsObservabilityMetricsTracingLogFormatLogLevel() {
189+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of(
190+
"CODEIQ_OBSERVABILITY_METRICS", "true",
191+
"CODEIQ_OBSERVABILITY_TRACING", "false",
192+
"CODEIQ_OBSERVABILITY_LOGFORMAT", "json",
193+
"CODEIQ_OBSERVABILITY_LOGLEVEL", "DEBUG"));
194+
assertEquals(Boolean.TRUE, cfg.observability().metrics());
195+
assertEquals(Boolean.FALSE, cfg.observability().tracing());
196+
assertEquals("json", cfg.observability().logFormat());
197+
assertEquals("DEBUG", cfg.observability().logLevel());
198+
}
199+
200+
// ---------------------------------------------------------------
201+
// Detectors profiles (alongside pre-existing categories/include)
202+
// ---------------------------------------------------------------
203+
204+
@Test
205+
void readsDetectorsProfiles() {
206+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of(
207+
"CODEIQ_DETECTORS_PROFILES", "backend,api"));
208+
assertEquals(List.of("backend", "api"), cfg.detectors().profiles());
209+
}
210+
211+
// ---------------------------------------------------------------
212+
// splitCsv edge cases
213+
// ---------------------------------------------------------------
214+
215+
@Test
216+
void emptyCsvResultsInEmptyList() {
217+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of("CODEIQ_INDEXING_LANGUAGES", ""));
218+
assertThat(cfg.indexing().languages()).isEmpty();
219+
}
220+
221+
@Test
222+
void csvWithWhitespaceOnlyEntries_areSkipped() {
223+
// " , ," → three whitespace-only tokens that must be filtered out
224+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of(
225+
"CODEIQ_INDEXING_LANGUAGES", " , ,"));
226+
assertThat(cfg.indexing().languages()).isEmpty();
227+
}
228+
229+
@Test
230+
void csvTrimsWhitespaceFromEntries() {
231+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of(
232+
"CODEIQ_MCP_TOOLS_ENABLED", " get_stats , find_cycles "));
233+
assertEquals(List.of("get_stats", "find_cycles"), cfg.mcp().tools().enabled());
234+
}
235+
236+
// ---------------------------------------------------------------
237+
// Unknown variables under the CODEIQ_ prefix must also be ignored
238+
// (exercises the default switch arm)
239+
// ---------------------------------------------------------------
240+
241+
@Test
242+
void unknownCodeiqSubKey_isIgnored() {
243+
// CODEIQ_FUTURE_FEATURE doesn't match any case → no effect
244+
CodeIqUnifiedConfig cfg = EnvVarOverlay.from(Map.of(
245+
"CODEIQ_FUTURE_FEATURE", "yes",
246+
"CODEIQ_SERVING_PORT", "9999"));
247+
assertEquals(9999, cfg.serving().port());
248+
}
249+
250+
// ---------------------------------------------------------------
251+
// Determinism: same env map, multiple invocations produce equal config
252+
// ---------------------------------------------------------------
253+
254+
@Test
255+
void deterministic_sameEnvProducesEqualConfig() {
256+
var env = new LinkedHashMap<String, String>();
257+
env.put("CODEIQ_SERVING_PORT", "8080");
258+
env.put("CODEIQ_INDEXING_LANGUAGES", "java,python");
259+
env.put("CODEIQ_DETECTORS_CATEGORIES", "endpoints");
260+
var run1 = EnvVarOverlay.from(env);
261+
var run2 = EnvVarOverlay.from(env);
262+
assertEquals(run1, run2);
263+
}
264+
}

0 commit comments

Comments
 (0)