Skip to content

Commit acc0f82

Browse files
prestoncabeclaude
andcommitted
library-api: migrate tests to Bruno and add JUnit validation tests
Replace Thunder Client tests with Bruno API tests and add comprehensive JUnit validation: **Bruno API tests** (test/bdt/): - Collection-based organization mirroring DMN file structure - Environment support (local dev, prod) - Benefit tests: PhlHomesteadExemption, PhlSeniorCitizenTaxFreeze - Check tests: PersonMinAge, PersonEnrolledInBenefit, PersonNotEnrolledInBenefit - CLI-friendly for CI/CD integration **JUnit validation tests** (src/test/java/): - ModelDiscoveryTest: Validates DMN model discovery and naming conventions - DynamicEndpointPatternTest: Verifies path mapping and URL patterns - OpenAPISchemaPatternTest: Tests OpenAPI schema generation - SituationTypeValidationTest: Ensures tSituation type consistency across models **Removed:** - All Thunder Client tests (thunder-tests/) **Configuration:** - Updated .gitignore to exclude .claude/settings.local.json Bruno provides better collection management, version control friendly format, and CLI support compared to Thunder Client. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent fd8afcc commit acc0f82

File tree

314 files changed

+1075
-6672
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

314 files changed

+1075
-6672
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,5 @@ node_modules/
8080
.quarkus/
8181

8282
.devboxrc
83+
84+
.claude/settings.local.json

library-api/.gitignore

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,2 @@
11
target/
2-
3-
thunder-tests/thunderResponses.json
4-
thunder-tests/res-files/
5-
thunder-tests/thunderActivity.json
2+
.claude/settings.local.json
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package org.codeforphilly.bdt.api;
2+
3+
import io.quarkus.test.junit.QuarkusTest;
4+
import io.restassured.common.mapper.TypeRef;
5+
import io.restassured.http.ContentType;
6+
import io.restassured.path.json.JsonPath;
7+
import io.restassured.response.Response;
8+
import org.junit.jupiter.api.Test;
9+
10+
import javax.inject.Inject;
11+
import java.util.List;
12+
import java.util.Map;
13+
import java.util.stream.Collectors;
14+
15+
import static io.restassured.RestAssured.given;
16+
import static org.hamcrest.Matchers.notNullValue;
17+
import static org.junit.jupiter.api.Assertions.*;
18+
19+
@QuarkusTest
20+
public class DynamicEndpointPatternTest {
21+
22+
@Inject
23+
ModelRegistry modelRegistry;
24+
25+
@Inject
26+
DMNSchemaResolver schemaResolver;
27+
28+
@Test
29+
public void testAllCheckEndpointsReturnCheckResult() {
30+
Map<String, ModelInfo> allModels = modelRegistry.getAllModels();
31+
32+
List<ModelInfo> checkModels = allModels.values().stream()
33+
.filter(model -> model.getPath().startsWith("checks/"))
34+
.filter(model -> model.getDecisionServices().contains(model.getModelName() + "Service"))
35+
.collect(Collectors.toList());
36+
37+
assertTrue(checkModels.size() > 0, "Should have at least one check model");
38+
39+
for (ModelInfo model : checkModels) {
40+
String serviceName = model.getModelName() + "Service";
41+
String inputRef = schemaResolver.findInputSchemaRef(model.getModelName(), serviceName);
42+
assertNotNull(inputRef, "Should find input schema for " + serviceName);
43+
44+
// Generate example request
45+
Map<String, Object> exampleRequest = schemaResolver.generateExample(inputRef);
46+
47+
String path = "/api/v1/" + model.getPath();
48+
49+
given()
50+
.contentType(ContentType.JSON)
51+
.body(exampleRequest)
52+
.when()
53+
.post(path)
54+
.then()
55+
.statusCode(200)
56+
.body("checkResult", notNullValue())
57+
.body("situation", notNullValue())
58+
.body("parameters", notNullValue());
59+
}
60+
}
61+
62+
@Test
63+
public void testAllBenefitEndpointsReturnExpectedStructure() {
64+
Map<String, ModelInfo> allModels = modelRegistry.getAllModels();
65+
66+
List<ModelInfo> benefitModels = allModels.values().stream()
67+
.filter(model -> model.getPath().startsWith("benefits/"))
68+
.filter(model -> model.getDecisionServices().contains(model.getModelName() + "Service"))
69+
.collect(Collectors.toList());
70+
71+
assertTrue(benefitModels.size() > 0, "Should have at least one benefit model");
72+
73+
for (ModelInfo model : benefitModels) {
74+
String serviceName = model.getModelName() + "Service";
75+
String inputRef = schemaResolver.findInputSchemaRef(model.getModelName(), serviceName);
76+
77+
if (inputRef != null) {
78+
Map<String, Object> exampleRequest = schemaResolver.generateExample(inputRef);
79+
String path = "/api/v1/" + model.getPath();
80+
81+
given()
82+
.contentType(ContentType.JSON)
83+
.body(exampleRequest)
84+
.when()
85+
.post(path)
86+
.then()
87+
.statusCode(200)
88+
.body("checks", notNullValue())
89+
.body("isEligible", notNullValue())
90+
.body("situation", notNullValue());
91+
}
92+
}
93+
}
94+
95+
@Test
96+
public void testActualResponsesMatchOpenAPIExamples() {
97+
// For each endpoint, verify actual response structure matches OpenAPI example structure
98+
Map<String, ModelInfo> allModels = modelRegistry.getAllModels();
99+
100+
List<ModelInfo> exposedModels = allModels.values().stream()
101+
.filter(model -> model.getPath().startsWith("checks/") || model.getPath().startsWith("benefits/"))
102+
.filter(model -> model.getDecisionServices().contains(model.getModelName() + "Service"))
103+
.collect(Collectors.toList());
104+
105+
JsonPath openApiSpec = given()
106+
.queryParam("format", "JSON")
107+
.when()
108+
.get("/q/openapi")
109+
.then()
110+
.extract()
111+
.jsonPath();
112+
113+
for (ModelInfo model : exposedModels) {
114+
String serviceName = model.getModelName() + "Service";
115+
String inputRef = schemaResolver.findInputSchemaRef(model.getModelName(), serviceName);
116+
117+
if (inputRef != null) {
118+
Map<String, Object> exampleRequest = schemaResolver.generateExample(inputRef);
119+
String path = "/api/v1/" + model.getPath();
120+
121+
// Get actual response
122+
Response response = given()
123+
.contentType(ContentType.JSON)
124+
.body(exampleRequest)
125+
.when()
126+
.post(path)
127+
.then()
128+
.extract()
129+
.response();
130+
131+
assertEquals(200, response.statusCode(), path + " should return 200");
132+
133+
Map<String, Object> actualResponse = response.as(new TypeRef<Map<String, Object>>() {});
134+
135+
// Get OpenAPI example
136+
String examplePath = "paths.'" + path + "'.post.responses.'200'.content.'application/json'.examples.'Example response'.value";
137+
Map<String, Object> openApiExample = openApiSpec.getMap(examplePath);
138+
139+
if (openApiExample != null) {
140+
// Verify same keys
141+
assertEquals(openApiExample.keySet(), actualResponse.keySet(),
142+
path + ": actual response keys should match OpenAPI example keys");
143+
}
144+
}
145+
}
146+
}
147+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package org.codeforphilly.bdt.api;
2+
3+
import io.quarkus.test.junit.QuarkusTest;
4+
import org.junit.jupiter.api.Test;
5+
6+
import javax.inject.Inject;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.stream.Collectors;
10+
11+
import static io.restassured.RestAssured.given;
12+
import static org.hamcrest.Matchers.hasKey;
13+
import static org.junit.jupiter.api.Assertions.*;
14+
15+
@QuarkusTest
16+
public class ModelDiscoveryTest {
17+
18+
@Inject
19+
ModelRegistry modelRegistry;
20+
21+
@Test
22+
public void testAllDMNModelsAreDiscovered() {
23+
Map<String, ModelInfo> models = modelRegistry.getAllModels();
24+
assertNotNull(models);
25+
assertTrue(models.size() > 0, "Should discover at least one DMN model");
26+
27+
// Log discovered models for debugging
28+
models.values().forEach(model -> {
29+
System.out.println("Discovered: " + model.getModelName() +
30+
" at " + model.getPath() +
31+
" with services: " + model.getDecisionServices());
32+
});
33+
}
34+
35+
@Test
36+
public void testAllModelsFollowNamingConvention() {
37+
Map<String, ModelInfo> allModels = modelRegistry.getAllModels();
38+
39+
for (ModelInfo model : allModels.values()) {
40+
// Models that are exposed via API should have {ModelName}Service
41+
String expectedService = model.getModelName() + "Service";
42+
43+
if (model.getDecisionServices().contains(expectedService)) {
44+
// This model should appear in OpenAPI
45+
String path = "/api/v1/" + model.getPath();
46+
47+
given()
48+
.queryParam("format", "JSON")
49+
.when()
50+
.get("/q/openapi")
51+
.then()
52+
.statusCode(200)
53+
.body("paths", hasKey(path));
54+
}
55+
}
56+
}
57+
58+
@Test
59+
public void testCheckModelsHaveCorrectCategory() {
60+
Map<String, ModelInfo> allModels = modelRegistry.getAllModels();
61+
62+
List<ModelInfo> checkModels = allModels.values().stream()
63+
.filter(model -> model.getPath().startsWith("checks/"))
64+
.collect(Collectors.toList());
65+
66+
for (ModelInfo model : checkModels) {
67+
String category = model.getCategory();
68+
assertTrue(category.endsWith("Checks"),
69+
"Check model " + model.getModelName() +
70+
" should have category ending with 'Checks', got: " + category);
71+
}
72+
}
73+
74+
@Test
75+
public void testBenefitModelsHaveCorrectCategory() {
76+
Map<String, ModelInfo> allModels = modelRegistry.getAllModels();
77+
78+
List<ModelInfo> benefitModels = allModels.values().stream()
79+
.filter(model -> model.getPath().startsWith("benefits/"))
80+
.collect(Collectors.toList());
81+
82+
for (ModelInfo model : benefitModels) {
83+
String category = model.getCategory();
84+
assertEquals("Benefits", category,
85+
"Benefit model " + model.getModelName() + " should have 'Benefits' category");
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)