Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"prefersReducedMotion": true,
"spinnerTipsEnabled": false
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ private void scanSpringConfig(Path root, InfrastructureRegistry registry) {
private void parseSpringYaml(Path file, InfrastructureRegistry registry) {
try {
String content = Files.readString(file, StandardCharsets.UTF_8);
Yaml yaml = new Yaml();
Yaml yaml = new Yaml(new org.yaml.snakeyaml.constructor.SafeConstructor(new org.yaml.snakeyaml.LoaderOptions()));
Object loaded = yaml.load(content);
if (!(loaded instanceof Map<?, ?> raw)) return;

Expand Down Expand Up @@ -224,7 +224,7 @@ private void scanDockerCompose(Path root, InfrastructureRegistry registry) {
private void parseDockerCompose(Path file, InfrastructureRegistry registry) {
try {
String content = Files.readString(file, StandardCharsets.UTF_8);
Yaml yaml = new Yaml();
Yaml yaml = new Yaml(new org.yaml.snakeyaml.constructor.SafeConstructor(new org.yaml.snakeyaml.LoaderOptions()));
Object loaded = yaml.load(content);
if (!(loaded instanceof Map<?, ?> data)) return;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public Object parse(String language, String content, String filePath) {

@SuppressWarnings("unchecked")
private Object parseYaml(String content) {
var yaml = new Yaml();
var yaml = new Yaml(new org.yaml.snakeyaml.constructor.SafeConstructor(new org.yaml.snakeyaml.LoaderOptions()));
var docs = new java.util.ArrayList<>();
for (Object doc : yaml.loadAll(content)) {
docs.add(doc);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ private String renderYaml(List<CodeNode> nodes) {
graphData.put("nodes", nodeList);
graphData.put("count", nodes.size());

Yaml yaml = new Yaml();
Yaml yaml = new Yaml(new org.yaml.snakeyaml.constructor.SafeConstructor(new org.yaml.snakeyaml.LoaderOptions()));
return yaml.dump(graphData);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.randomcodespace.iq.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
Expand All @@ -9,17 +10,22 @@
@Configuration
@Profile("serving")
public class CorsConfig {

@Value("${codeiq.cors.allowed-origin-patterns:http://localhost:[*],http://127.0.0.1:[*]}")
private String allowedOriginPatterns = "http://localhost:[*],http://127.0.0.1:[*]";

@Bean
public WebMvcConfigurer corsConfigurer() {
String[] patterns = allowedOriginPatterns.split(",");
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("*")
.allowedOriginPatterns(patterns)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*");
registry.addMapping("/mcp/**")
.allowedOrigins("*")
.allowedOriginPatterns(patterns)
.allowedMethods("GET", "POST", "OPTIONS")
.allowedHeaders("*");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static boolean loadIfPresent(Path directory, CodeIqConfig config) {
if (Files.isRegularFile(configFile)) {
try {
String content = Files.readString(configFile, StandardCharsets.UTF_8);
Yaml yaml = new Yaml();
Yaml yaml = new Yaml(new org.yaml.snakeyaml.constructor.SafeConstructor(new org.yaml.snakeyaml.LoaderOptions()));
Map<String, Object> data = yaml.load(content);
if (data != null) {
applyOverrides(data, config);
Expand Down Expand Up @@ -74,7 +74,7 @@ public static ProjectConfig loadProjectConfig(Path directory) {
if (Files.isRegularFile(configFile)) {
try {
String content = Files.readString(configFile, StandardCharsets.UTF_8);
Yaml yaml = new Yaml();
Yaml yaml = new Yaml(new org.yaml.snakeyaml.constructor.SafeConstructor(new org.yaml.snakeyaml.LoaderOptions()));
Map<String, Object> data = yaml.load(content);
if (data != null) {
log.info("Loaded project config from {}", configFile);
Expand Down
110 changes: 110 additions & 0 deletions src/test/java/io/github/randomcodespace/iq/config/CorsConfigTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package io.github.randomcodespace.iq.config;

import org.junit.jupiter.api.Test;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

class CorsConfigTest {

static class TestableCorsRegistry extends CorsRegistry {
@Override
public Map<String, CorsConfiguration> getCorsConfigurations() {
return super.getCorsConfigurations();
}
}

private CorsConfig createCorsConfig() {
return new CorsConfig();
}

@Test
void corsConfigurerReturnsWebMvcConfigurer() {
WebMvcConfigurer configurer = createCorsConfig().corsConfigurer();
assertNotNull(configurer);
}

@Test
void corsConfigurerDoesNotThrowWhenAddingMappings() {
WebMvcConfigurer configurer = createCorsConfig().corsConfigurer();
TestableCorsRegistry registry = new TestableCorsRegistry();
assertDoesNotThrow(() -> configurer.addCorsMappings(registry));
}

@Test
void corsRegistryContainsApiAndMcpMappings() {
WebMvcConfigurer configurer = createCorsConfig().corsConfigurer();
TestableCorsRegistry registry = new TestableCorsRegistry();
configurer.addCorsMappings(registry);

var configurations = registry.getCorsConfigurations();
assertTrue(configurations.containsKey("/api/**"),
"Should register CORS mapping for /api/**");
assertTrue(configurations.containsKey("/mcp/**"),
"Should register CORS mapping for /mcp/**");
}

@Test
void apiMappingAllowsExpectedMethods() {
WebMvcConfigurer configurer = createCorsConfig().corsConfigurer();
TestableCorsRegistry registry = new TestableCorsRegistry();
configurer.addCorsMappings(registry);

var configurations = registry.getCorsConfigurations();
var apiCors = configurations.get("/api/**");
assertNotNull(apiCors);
var methods = apiCors.getAllowedMethods();
assertNotNull(methods);
assertTrue(methods.contains("GET"));
assertTrue(methods.contains("OPTIONS"));
}

@Test
void mcpMappingAllowsGetPostOptions() {
WebMvcConfigurer configurer = createCorsConfig().corsConfigurer();
TestableCorsRegistry registry = new TestableCorsRegistry();
configurer.addCorsMappings(registry);

var configurations = registry.getCorsConfigurations();
var mcpCors = configurations.get("/mcp/**");
assertNotNull(mcpCors);
var methods = mcpCors.getAllowedMethods();
assertNotNull(methods);
assertTrue(methods.contains("GET"));
assertTrue(methods.contains("POST"));
assertTrue(methods.contains("OPTIONS"));
}

@Test
void apiMappingRestrictsToLocalhostOrigins() {
WebMvcConfigurer configurer = createCorsConfig().corsConfigurer();
TestableCorsRegistry registry = new TestableCorsRegistry();
configurer.addCorsMappings(registry);

var configurations = registry.getCorsConfigurations();
var apiCors = configurations.get("/api/**");
assertNotNull(apiCors);
var patterns = apiCors.getAllowedOriginPatterns();
assertNotNull(patterns);
assertTrue(patterns.stream().anyMatch(p -> p.contains("localhost")),
"CORS should restrict to localhost origins");
}

@Test
void apiMappingAllowsAllHeaders() {
WebMvcConfigurer configurer = createCorsConfig().corsConfigurer();
TestableCorsRegistry registry = new TestableCorsRegistry();
configurer.addCorsMappings(registry);

var configurations = registry.getCorsConfigurations();
var apiCors = configurations.get("/api/**");
assertNotNull(apiCors);
var headers = apiCors.getAllowedHeaders();
assertNotNull(headers);
assertTrue(headers.contains("*"));
}
}
Loading
Loading