Skip to content

Commit c032373

Browse files
committed
refactor(config): tighten CodeIqConfig setter visibility to package-private
Drop `public` from every setter on CodeIqConfig and its Graph inner class. Production mutation is now restricted at compile time to: - UnifiedConfigAdapter.toCodeIqConfig (once, at Spring startup) - CliStartupConfigOverrides (once per JVM, at CLI entry) Both live in `io.github.randomcodespace.iq.config` and reach the package-private setters directly. Every other code path — controllers, MCP tools, background workers — loses the compile-time ability to mutate the Spring singleton. This is the mutation hazard cleanup #49 called for. Test migration: - Two in-package tests (CodeIqConfigTest, GraphBootstrapperTest) keep working unchanged. - 15 out-of-package test classes across `iq.api`, `iq.cli`, `iq.intelligence.*`, `iq.mcp`, `iq.query` are migrated to route setter calls through a new test-only helper CodeIqConfigTestSupport (lives in src/test/java/io/github/randomcodespace/iq/config/, so tests compile against the package-private setters). Fluent API keeps call sites readable: `CodeIqConfigTestSupport.override(config).rootPath(x).done();` The name makes the test-only intent unmistakable and the helper is not reachable from production code paths. - 51 call sites rewritten; semantics preserved verbatim. Full suite green: 3278 tests, 0 failures, 31 skipped (baseline unchanged).
1 parent 6ea345e commit c032373

17 files changed

Lines changed: 142 additions & 68 deletions

src/main/java/io/github/randomcodespace/iq/config/CodeIqConfig.java

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,19 @@
77
* Task 11 moved bean production to {@link UnifiedConfigBeans#codeIqConfig}, which
88
* adapts a {@link io.github.randomcodespace.iq.config.unified.CodeIqUnifiedConfig}
99
* (single source of truth) via {@link UnifiedConfigAdapter#toCodeIqConfig}. The
10-
* getter/setter surface is preserved unchanged so the ~100 call sites that still
11-
* depend on this bean continue to work.
10+
* getter surface is preserved unchanged so the ~100 call sites that read this
11+
* bean continue to work.
1212
*
1313
* <p>This class is intentionally a plain POJO (no {@code @Configuration},
1414
* no {@code @ConfigurationProperties}); Spring Boot no longer instantiates it
15-
* from {@code application.yml}. Instantiable directly in tests via the public
16-
* no-arg constructor and setters.
15+
* from {@code application.yml}.
16+
*
17+
* <p><b>Setters are package-private.</b> Only {@link UnifiedConfigAdapter}
18+
* (at Spring startup) and {@link CliStartupConfigOverrides} (once per JVM at
19+
* CLI entry) mutate instances of this class. External-package callers go
20+
* through {@link CliStartupConfigOverrides}. External-package tests that need
21+
* a populated instance construct one via
22+
* {@link UnifiedConfigAdapter#toCodeIqConfig(io.github.randomcodespace.iq.config.unified.CodeIqUnifiedConfig)}.
1723
*/
1824
public class CodeIqConfig {
1925

@@ -48,7 +54,7 @@ public static class Graph {
4854
private String path = ".code-iq/graph/graph.db";
4955

5056
public String getPath() { return path; }
51-
public void setPath(String path) { this.path = path; }
57+
void setPath(String path) { this.path = path; }
5258
}
5359

5460
/** Read-only mode for serving — no lock files, no writes. For read-only filesystems (AKS). */
@@ -57,93 +63,93 @@ public static class Graph {
5763
/** Service name tag for multi-repo graph mode. */
5864
private String serviceName;
5965

60-
// --- Getters and Setters ---
66+
// --- Getters (public) and Setters (package-private) ---
6167

6268
public String getRootPath() {
6369
return rootPath;
6470
}
6571

66-
public void setRootPath(String rootPath) {
72+
void setRootPath(String rootPath) {
6773
this.rootPath = rootPath;
6874
}
6975

7076
public String getCacheDir() {
7177
return cacheDir;
7278
}
7379

74-
public void setCacheDir(String cacheDir) {
80+
void setCacheDir(String cacheDir) {
7581
this.cacheDir = cacheDir;
7682
}
7783

7884
public int getMaxDepth() {
7985
return maxDepth;
8086
}
8187

82-
public void setMaxDepth(int maxDepth) {
88+
void setMaxDepth(int maxDepth) {
8389
this.maxDepth = maxDepth;
8490
}
8591

8692
public int getMaxFiles() {
8793
return maxFiles;
8894
}
8995

90-
public void setMaxFiles(int maxFiles) {
96+
void setMaxFiles(int maxFiles) {
9197
this.maxFiles = Math.max(1, maxFiles);
9298
}
9399

94100
public int getMaxRadius() {
95101
return maxRadius;
96102
}
97103

98-
public void setMaxRadius(int maxRadius) {
104+
void setMaxRadius(int maxRadius) {
99105
this.maxRadius = maxRadius;
100106
}
101107

102108
public int getBatchSize() {
103109
return batchSize;
104110
}
105111

106-
public void setBatchSize(int batchSize) {
112+
void setBatchSize(int batchSize) {
107113
this.batchSize = Math.max(1, batchSize);
108114
}
109115

110116
public boolean isReadOnly() {
111117
return readOnly;
112118
}
113119

114-
public void setReadOnly(boolean readOnly) {
120+
void setReadOnly(boolean readOnly) {
115121
this.readOnly = readOnly;
116122
}
117123

118124
public String getServiceName() {
119125
return serviceName;
120126
}
121127

122-
public void setServiceName(String serviceName) {
128+
void setServiceName(String serviceName) {
123129
this.serviceName = serviceName;
124130
}
125131

126132
public Graph getGraph() {
127133
return graph;
128134
}
129135

130-
public void setGraph(Graph graph) {
136+
void setGraph(Graph graph) {
131137
this.graph = graph;
132138
}
133139

134140
public boolean isUiEnabled() {
135141
return uiEnabled;
136142
}
137143

138-
public void setUiEnabled(boolean uiEnabled) {
144+
void setUiEnabled(boolean uiEnabled) {
139145
this.uiEnabled = uiEnabled;
140146
}
141147

142148
public int getMaxSnippetLines() {
143149
return maxSnippetLines;
144150
}
145151

146-
public void setMaxSnippetLines(int maxSnippetLines) {
152+
void setMaxSnippetLines(int maxSnippetLines) {
147153
this.maxSnippetLines = Math.max(1, maxSnippetLines);
148154
}
149155
}

src/test/java/io/github/randomcodespace/iq/api/GraphControllerTest.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import static org.mockito.Mockito.when;
3030
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
3131
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
32+
import io.github.randomcodespace.iq.config.CodeIqConfigTestSupport;
3233

3334
/**
3435
* Tests for the REST API controller using standalone MockMvc (no Spring context needed).
@@ -46,9 +47,9 @@ class GraphControllerTest {
4647
@BeforeEach
4748
void setUp() {
4849
config = new CodeIqConfig();
49-
config.setMaxDepth(10);
50-
config.setMaxRadius(10);
51-
config.setRootPath(".");
50+
CodeIqConfigTestSupport.override(config).maxDepth(10).done();
51+
CodeIqConfigTestSupport.override(config).maxRadius(10).done();
52+
CodeIqConfigTestSupport.override(config).rootPath(".").done();
5253
var controller = new GraphController(queryService, config);
5354
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
5455
}
@@ -499,7 +500,7 @@ void searchGraphShouldReturnResults() throws Exception {
499500
@Test
500501
void readFileShouldReturnContent(@TempDir Path tempDir) throws Exception {
501502
Files.writeString(tempDir.resolve("hello.txt"), "Hello World", StandardCharsets.UTF_8);
502-
config.setRootPath(tempDir.toAbsolutePath().toString());
503+
CodeIqConfigTestSupport.override(config).rootPath(tempDir.toAbsolutePath().toString()).done();
503504
var controller = new GraphController(queryService, config);
504505
var fileMvc = MockMvcBuilders.standaloneSetup(controller).build();
505506

@@ -510,7 +511,7 @@ void readFileShouldReturnContent(@TempDir Path tempDir) throws Exception {
510511

511512
@Test
512513
void readFileShouldReturn404ForMissing(@TempDir Path tempDir) throws Exception {
513-
config.setRootPath(tempDir.toAbsolutePath().toString());
514+
CodeIqConfigTestSupport.override(config).rootPath(tempDir.toAbsolutePath().toString()).done();
514515
var controller = new GraphController(queryService, config);
515516
var fileMvc = MockMvcBuilders.standaloneSetup(controller).build();
516517

@@ -520,7 +521,7 @@ void readFileShouldReturn404ForMissing(@TempDir Path tempDir) throws Exception {
520521

521522
@Test
522523
void readFileShouldBlockPathTraversal(@TempDir Path tempDir) throws Exception {
523-
config.setRootPath(tempDir.toAbsolutePath().toString());
524+
CodeIqConfigTestSupport.override(config).rootPath(tempDir.toAbsolutePath().toString()).done();
524525
var controller = new GraphController(queryService, config);
525526
var fileMvc = MockMvcBuilders.standaloneSetup(controller).build();
526527

@@ -533,7 +534,7 @@ void readFileShouldBlockPathTraversal(@TempDir Path tempDir) throws Exception {
533534
void readFileShouldReturnLineRange(@TempDir Path tempDir) throws Exception {
534535
Files.writeString(tempDir.resolve("multi.txt"), "line1\nline2\nline3\nline4\nline5",
535536
StandardCharsets.UTF_8);
536-
config.setRootPath(tempDir.toAbsolutePath().toString());
537+
CodeIqConfigTestSupport.override(config).rootPath(tempDir.toAbsolutePath().toString()).done();
537538
var controller = new GraphController(queryService, config);
538539
var fileMvc = MockMvcBuilders.standaloneSetup(controller).build();
539540

@@ -548,7 +549,7 @@ void readFileShouldReturnLineRange(@TempDir Path tempDir) throws Exception {
548549
@Test
549550
void readFileShouldReturnFullContentWithoutLineParams(@TempDir Path tempDir) throws Exception {
550551
Files.writeString(tempDir.resolve("full.txt"), "aaa\nbbb\nccc", StandardCharsets.UTF_8);
551-
config.setRootPath(tempDir.toAbsolutePath().toString());
552+
CodeIqConfigTestSupport.override(config).rootPath(tempDir.toAbsolutePath().toString()).done();
552553
var controller = new GraphController(queryService, config);
553554
var fileMvc = MockMvcBuilders.standaloneSetup(controller).build();
554555

src/test/java/io/github/randomcodespace/iq/api/IntelligenceControllerTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
2323
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
2424
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
25+
import io.github.randomcodespace.iq.config.CodeIqConfigTestSupport;
2526

2627
class IntelligenceControllerTest {
2728

@@ -41,7 +42,7 @@ void setUp() {
4142
when(metadataProvider.current()).thenReturn(metadata);
4243

4344
CodeIqConfig config = new CodeIqConfig();
44-
config.setRootPath(System.getProperty("java.io.tmpdir"));
45+
CodeIqConfigTestSupport.override(config).rootPath(System.getProperty("java.io.tmpdir")).done();
4546

4647
IntelligenceController controller = new IntelligenceController(assembler, metadataProvider, config);
4748
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();

src/test/java/io/github/randomcodespace/iq/api/TopologyControllerExtendedTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import static org.mockito.Mockito.*;
3232
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
3333
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
34+
import io.github.randomcodespace.iq.config.CodeIqConfigTestSupport;
3435

3536
/**
3637
* Extended tests for TopologyController that exercise the actual REST endpoints
@@ -60,7 +61,7 @@ class TopologyControllerExtendedTest {
6061
void setUp() {
6162
var config = new CodeIqConfig();
6263
// Use the temp dir as rootPath so H2 fallback finds no cache file
63-
config.setRootPath(tempDir.toString());
64+
CodeIqConfigTestSupport.override(config).rootPath(tempDir.toString()).done();
6465
controller = new TopologyController(topologyService, graphStore, config);
6566
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
6667
}

src/test/java/io/github/randomcodespace/iq/api/TopologyEndpointTest.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import static org.mockito.Mockito.*;
3232
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
3333
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
34+
import io.github.randomcodespace.iq.config.CodeIqConfigTestSupport;
3435

3536
/**
3637
* Tests for the /api/topology endpoint and related components.
@@ -61,9 +62,9 @@ class TopologyEndpointTest {
6162
@BeforeEach
6263
void setUp() {
6364
config = new CodeIqConfig();
64-
config.setMaxDepth(10);
65-
config.setMaxRadius(10);
66-
config.setRootPath(".");
65+
CodeIqConfigTestSupport.override(config).maxDepth(10).done();
66+
CodeIqConfigTestSupport.override(config).maxRadius(10).done();
67+
CodeIqConfigTestSupport.override(config).rootPath(".").done();
6768

6869
objectMapper = new ObjectMapper();
6970

src/test/java/io/github/randomcodespace/iq/cli/BundleCommandExtendedTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static org.junit.jupiter.api.Assertions.*;
2323
import static org.mockito.ArgumentMatchers.anyString;
2424
import static org.mockito.Mockito.when;
25+
import io.github.randomcodespace.iq.config.CodeIqConfigTestSupport;
2526

2627
/**
2728
* Extended tests for BundleCommand covering additional branches:
@@ -249,7 +250,7 @@ void bundleWithH2CacheReportsStats(@TempDir Path tempDir) throws IOException {
249250
Files.createDirectories(cacheDir);
250251

251252
var config = new CodeIqConfig();
252-
config.setCacheDir(".code-iq/cache");
253+
CodeIqConfigTestSupport.override(config).cacheDir(".code-iq/cache").done();
253254
Path zipPath = tempDir.resolve("out.zip");
254255
var cmd = new BundleCommand(config, java.util.Optional.empty(), java.util.Optional.empty());
255256
var cmdLine = new picocli.CommandLine(cmd);

src/test/java/io/github/randomcodespace/iq/cli/BundleCommandTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static org.junit.jupiter.api.Assertions.*;
2323
import static org.mockito.ArgumentMatchers.anyString;
2424
import static org.mockito.Mockito.when;
25+
import io.github.randomcodespace.iq.config.CodeIqConfigTestSupport;
2526

2627
@ExtendWith(MockitoExtension.class)
2728
class BundleCommandTest {
@@ -70,7 +71,7 @@ void bundleCreatesZipWithCorrectStructure(@TempDir Path tempDir) throws IOExcept
7071
Files.writeString(tempDir.resolve("App.java"), "class App {}", StandardCharsets.UTF_8);
7172

7273
var config = new CodeIqConfig();
73-
config.setCacheDir(".code-iq/cache");
74+
CodeIqConfigTestSupport.override(config).cacheDir(".code-iq/cache").done();
7475

7576
when(flowEngine.renderInteractive(anyString())).thenReturn("<html>flow</html>");
7677

@@ -186,7 +187,7 @@ void bundleIncludesH2Cache(@TempDir Path tempDir) throws IOException {
186187
Files.writeString(cacheDir.resolve("analysis-cache.db"), "h2-data", StandardCharsets.UTF_8);
187188

188189
var config = new CodeIqConfig();
189-
config.setCacheDir(".code-iq/cache");
190+
CodeIqConfigTestSupport.override(config).cacheDir(".code-iq/cache").done();
190191

191192
Path zipPath = tempDir.resolve("test-bundle.zip");
192193
var cmd = new BundleCommand(config, (GraphStore) null, (FlowEngine) null);

src/test/java/io/github/randomcodespace/iq/cli/CacheCommandTest.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import static org.junit.jupiter.api.Assertions.assertEquals;
1717
import static org.junit.jupiter.api.Assertions.assertFalse;
1818
import static org.junit.jupiter.api.Assertions.assertTrue;
19+
import io.github.randomcodespace.iq.config.CodeIqConfigTestSupport;
1920

2021
class CacheCommandTest {
2122

@@ -36,7 +37,7 @@ void tearDown() {
3637
@Test
3738
void statsShowsNoCacheWhenMissing(@TempDir Path tempDir) {
3839
var config = new CodeIqConfig();
39-
config.setCacheDir(".code-iq/cache");
40+
CodeIqConfigTestSupport.override(config).cacheDir(".code-iq/cache").done();
4041
var cmd = new CacheCommand.StatsSubcommand(config);
4142

4243
var cmdLine = new picocli.CommandLine(cmd);
@@ -56,7 +57,7 @@ void statsShowsCacheInfo(@TempDir Path tempDir) throws IOException {
5657
StandardCharsets.UTF_8);
5758

5859
var config = new CodeIqConfig();
59-
config.setCacheDir(".code-iq/cache");
60+
CodeIqConfigTestSupport.override(config).cacheDir(".code-iq/cache").done();
6061
var cmd = new CacheCommand.StatsSubcommand(config);
6162

6263
var cmdLine = new picocli.CommandLine(cmd);
@@ -76,7 +77,7 @@ void clearRemovesCache(@TempDir Path tempDir) throws IOException {
7677
StandardCharsets.UTF_8);
7778

7879
var config = new CodeIqConfig();
79-
config.setCacheDir(".code-iq/cache");
80+
CodeIqConfigTestSupport.override(config).cacheDir(".code-iq/cache").done();
8081
var cmd = new CacheCommand.ClearSubcommand(config);
8182

8283
var cmdLine = new picocli.CommandLine(cmd);
@@ -89,7 +90,7 @@ void clearRemovesCache(@TempDir Path tempDir) throws IOException {
8990
@Test
9091
void clearHandlesNoCacheGracefully(@TempDir Path tempDir) {
9192
var config = new CodeIqConfig();
92-
config.setCacheDir(".code-iq/cache");
93+
CodeIqConfigTestSupport.override(config).cacheDir(".code-iq/cache").done();
9394
var cmd = new CacheCommand.ClearSubcommand(config);
9495

9596
var cmdLine = new picocli.CommandLine(cmd);

src/test/java/io/github/randomcodespace/iq/cli/StatsCommandTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.Map;
2323

2424
import static org.junit.jupiter.api.Assertions.*;
25+
import io.github.randomcodespace.iq.config.CodeIqConfigTestSupport;
2526

2627
class StatsCommandTest {
2728

@@ -40,7 +41,7 @@ void setUp() {
4041
System.setErr(new PrintStream(captureErr, true, StandardCharsets.UTF_8));
4142
statsService = new StatsService();
4243
config = new CodeIqConfig();
43-
config.setCacheDir(".code-iq/cache");
44+
CodeIqConfigTestSupport.override(config).cacheDir(".code-iq/cache").done();
4445
}
4546

4647
@AfterEach

0 commit comments

Comments
 (0)