Skip to content

Commit 1ccc48a

Browse files
committed
Implement REST API pre-conditions to use public API
1 parent e2891d9 commit 1ccc48a

12 files changed

Lines changed: 204 additions & 136 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
/.idea
99
*.iml
10+
.java-version
1011

1112
# Compiled class file
1213
*.class

memex/src/main/java/com/johnlpage/memex/controller/VehicleInspectionController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public ResponseEntity<PageDto<VehicleInspection>> getInspectionsByModel(
131131
* This is a very "Raw" API interface that lets the caller design their own query and projection
132132
* etc.
133133
*/
134-
@PostMapping("/inspections/query")
134+
@PostMapping(value ="/inspections/query", produces = MediaType.APPLICATION_JSON_VALUE)
135135
public ResponseEntity<String> mongoQuery(@RequestBody String requestBody) {
136136
List<VehicleInspection> result = queryService.mongoDbNativeQuery(requestBody);
137137
try {
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package com.johnlpage.memex.cucumber.steps;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import com.johnlpage.memex.cucumber.service.VehicleInspectionIdRangeValidator;
6+
import com.johnlpage.memex.model.VehicleInspection;
7+
import io.cucumber.datatable.DataTable;
8+
import io.cucumber.java.en.Given;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.springframework.beans.factory.annotation.Autowired;
11+
import org.springframework.beans.factory.annotation.Value;
12+
import org.springframework.core.ParameterizedTypeReference;
13+
import org.springframework.http.MediaType;
14+
import org.springframework.http.ResponseEntity;
15+
import org.springframework.web.client.RestClient;
16+
17+
import java.util.ArrayList;
18+
import java.util.Collections;
19+
import java.util.List;
20+
import java.util.Map;
21+
import java.util.stream.Collectors;
22+
import java.util.stream.LongStream;
23+
24+
import static org.junit.jupiter.api.Assertions.assertNotNull;
25+
26+
@Slf4j
27+
public class InspectionsPreConditionSteps {
28+
29+
@Value("${memex.base-url}")
30+
private String apiBaseUrl;
31+
32+
private final RestClient restClient;
33+
private final ObjectMapper objectMapper;
34+
private final VehicleInspectionIdRangeValidator idRangeValidator;
35+
36+
@Autowired
37+
public InspectionsPreConditionSteps(RestClient.Builder restClientBuilder, ObjectMapper objectMapper,
38+
VehicleInspectionIdRangeValidator idRangeValidator) {
39+
this.restClient = restClientBuilder.baseUrl(apiBaseUrl).build();
40+
this.objectMapper = objectMapper;
41+
this.idRangeValidator = idRangeValidator;
42+
}
43+
44+
@Given("the following vehicle inspections exist:")
45+
public void givenVehicleInspectionsExist(DataTable dataTable) throws JsonProcessingException {
46+
List<Map<String, String>> rows = dataTable.asMaps(String.class, String.class);
47+
List<VehicleInspection> inspections = new ArrayList<>();
48+
49+
for (Map<String, String> row : rows) {
50+
String json = row.get("vehicleinspection");
51+
VehicleInspection inspection = objectMapper.readValue(json, VehicleInspection.class);
52+
inspections.add(inspection);
53+
Long testId = inspection.getTestid();
54+
assertNotNull(testId, "testid is expected to be part of the data input");
55+
idRangeValidator.validate(testId);
56+
}
57+
58+
upsertInspectionsViaApi(inspections);
59+
}
60+
61+
@Given("the vehicle inspection with id {long} does not exist")
62+
public void theFollowingVehicleInspectionDoesNotExist(long testId) throws JsonProcessingException {
63+
idRangeValidator.validate(testId);
64+
deleteInspectionsViaApi(Collections.singletonList(testId));
65+
}
66+
67+
@Given("the vehicle inspections in range {long}-{long} do not exist")
68+
public void theFollowingVehicleInspectionsInRangeDoesNotExist(long startId, long endId) throws JsonProcessingException {
69+
idRangeValidator.validateRange(startId, endId);
70+
List<Long> idsForDeletion = LongStream.rangeClosed(startId, endId)
71+
.boxed()
72+
.collect(Collectors.toList());
73+
74+
deleteInspectionsViaApi(idsForDeletion);
75+
}
76+
77+
@Given("the following vehicle inspections do not exist:")
78+
public void givenTheFollowingVehicleInspectionsDoNotExist(DataTable dataTable) throws JsonProcessingException {
79+
List<Map<String, String>> rows = dataTable.asMaps(String.class, String.class);
80+
List<Long> idsToDelete = new ArrayList<>();
81+
82+
for (Map<String, String> row : rows) {
83+
if (row.size() != 1) {
84+
throw new IllegalArgumentException("Only one column per row is supported in this step for deletion criteria.");
85+
}
86+
Map.Entry<String, String> entry = row.entrySet().iterator().next();
87+
String key = entry.getKey();
88+
String inputQuery = entry.getValue();
89+
90+
String rangeCheck = "\"_id\": {\"$gte\": " + idRangeValidator.getRangeStart() + ", \"$lte\": " + idRangeValidator.getRangeEnd() + "}";
91+
String mongoQueryJson = String.format("{\"filter\": {%s, %s}}", rangeCheck, inputQuery.substring(inputQuery.indexOf('{') + 1, inputQuery.lastIndexOf('}'))); // Basic example, might need more robust query building
92+
93+
try {
94+
ResponseEntity<List<VehicleInspection>> responseEntity = restClient.post()
95+
.uri(apiBaseUrl + "/api/inspections/query")
96+
.contentType(MediaType.APPLICATION_JSON)
97+
.body(mongoQueryJson)
98+
.retrieve()
99+
.toEntity(new ParameterizedTypeReference<List<VehicleInspection>>() {
100+
});
101+
102+
if (responseEntity.getStatusCode().is2xxSuccessful() && responseEntity.getBody() != null) {
103+
List<VehicleInspection> inspections = responseEntity.getBody();
104+
inspections.forEach(insp -> idsToDelete.add(insp.getTestid()));
105+
} else {
106+
log.warn("Query {} returned status {} or empty body. No IDs added for deletion.",
107+
mongoQueryJson, responseEntity.getStatusCode());
108+
}
109+
} catch (Exception e) {
110+
log.error("Error querying API for a query {}", mongoQueryJson, e);
111+
throw new RuntimeException("Error querying API for a query: " + mongoQueryJson, e);
112+
}
113+
}
114+
115+
deleteInspectionsViaApi(idsToDelete);
116+
}
117+
118+
private void deleteInspectionsViaApi(List<Long> idsToDelete) throws JsonProcessingException {
119+
if (idsToDelete.isEmpty()) {
120+
log.info("Delete IDs list is empty, skipping API call.");
121+
return;
122+
}
123+
124+
List<Map<String, Object>> payload = idsToDelete.stream()
125+
.map(id -> Map.<String, Object>of("testid", id, "deleted", true))
126+
.collect(Collectors.toList());
127+
128+
String jsonPayload = objectMapper.writeValueAsString(payload);
129+
130+
try {
131+
log.info("apiBaseUrl: {}", apiBaseUrl);
132+
ResponseEntity<String> response = restClient.post()
133+
.uri(apiBaseUrl + "/api/inspections?updateStrategy=UPDATEWITHHISTORY&futz=true")
134+
.contentType(MediaType.APPLICATION_JSON)
135+
.body(jsonPayload)
136+
.retrieve()
137+
.toEntity(String.class);
138+
139+
if (response.getStatusCode().is2xxSuccessful()) {
140+
log.info("Successfully marked inspections for deletion. IDs count: {}", idsToDelete.size());
141+
} else {
142+
log.error("Failed to mark inspections for deletion. Status: {}, Body: {}",
143+
response.getStatusCode(), response.getBody());
144+
throw new RuntimeException("Failed to mark inspections for deletion: " + response.getStatusCode());
145+
}
146+
} catch (Exception e) {
147+
log.error("Error calling API to mark inspections for deletion. IDs count: {}", idsToDelete.size(), e);
148+
throw new RuntimeException("Error calling API to mark inspections for deletion: " + e.getMessage());
149+
}
150+
}
151+
152+
private void upsertInspectionsViaApi(List<VehicleInspection> inspections) throws JsonProcessingException {
153+
if (inspections.isEmpty()) {
154+
log.info("No inspections to upsert, skipping API call.");
155+
return;
156+
}
157+
158+
String jsonPayload = objectMapper.writeValueAsString(inspections);
159+
160+
try {
161+
ResponseEntity<String> response = restClient.post()
162+
.uri(apiBaseUrl + "/api/inspections?updateStrategy=UPDATEWITHHISTORY&futz=true")
163+
.contentType(MediaType.APPLICATION_JSON)
164+
.body(jsonPayload)
165+
.retrieve()
166+
.toEntity(String.class);
167+
168+
if (response.getStatusCode().is2xxSuccessful()) {
169+
log.info("Successfully upserted {} inspections.", inspections.size());
170+
} else {
171+
log.error("Failed to upsert inspections. Status: {}, Body: {}",
172+
response.getStatusCode(), response.getBody());
173+
throw new RuntimeException("Failed to upsert inspections: " + response.getStatusCode());
174+
}
175+
} catch (Exception e) {
176+
log.error("Error calling API to upsert inspections. Count: {}", inspections.size(), e);
177+
throw new RuntimeException("Error calling API to upsert inspections: " + e.getMessage());
178+
}
179+
}
180+
}

memex/src/test/java/com/johnlpage/memex/cucumber/steps/KafkaConsumerSteps.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public class KafkaConsumerSteps {
3333
@Autowired
3434
private VehicleInspectionIdRangeValidator idRangeValidator;
3535

36+
// TODO: Use public API to save the vehicle inspections
3637
@When("I send {int} vehicle inspections starting with id {long} to kafka with:")
3738
public void sendVehicleInspectionsToKafka(int count, long startId, String jsonTemplate) throws JsonProcessingException {
3839
idRangeValidator.validate(startId);
@@ -49,6 +50,7 @@ public void sendVehicleInspectionsToKafka(int count, long startId, String jsonTe
4950
}
5051
}
5152

53+
// TODO: Use public API to verify the vehicle inspections are saved
5254
@Then("verify {int} vehicle inspections are saved starting from id {long} in mongo with:")
5355
public void verifyVehicleInspectionsSaved(int count, long startId, String expectedJson) throws JsonProcessingException {
5456
long endId = startId + count - 1;

memex/src/test/java/com/johnlpage/memex/cucumber/steps/MongoPreConditionSteps.java

Lines changed: 0 additions & 112 deletions
This file was deleted.

memex/src/test/java/com/johnlpage/memex/cucumber/steps/RestApiSteps.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,18 @@
1818
import java.util.Map;
1919

2020
import org.springframework.beans.factory.annotation.Autowired;
21-
import org.springframework.boot.test.web.server.LocalServerPort;
21+
import org.springframework.beans.factory.annotation.Value;
2222

2323
public class RestApiSteps {
2424

25-
@LocalServerPort
26-
private int port;
25+
@Value("${memex.base-url}")
26+
private String baseUrl;
2727

2828
@Autowired
2929
MacrosRegister macroRegister;
3030

3131
private Response response;
3232

33-
public String baseUrl() {
34-
return "http://localhost:" + port;
35-
}
36-
3733
@ParameterType("true|false")
3834
public Boolean bool(String bool) {
3935
return Boolean.parseBoolean(bool);
@@ -43,7 +39,7 @@ public Boolean bool(String bool) {
4339
public void iSendAPOSTRequestTo(String localUrl, String payload) {
4440
String processedUrl = macroRegister.replaceMacros(localUrl);
4541
response = given()
46-
.baseUri(baseUrl())
42+
.baseUri(baseUrl)
4743
.contentType(ContentType.JSON)
4844
.body(payload)
4945
.post(processedUrl);
@@ -53,7 +49,7 @@ public void iSendAPOSTRequestTo(String localUrl, String payload) {
5349
public void userSendsGetRequest(String localUrl) {
5450
String processedUrl = macroRegister.replaceMacros(localUrl);
5551
response = given()
56-
.baseUri(baseUrl())
52+
.baseUri(baseUrl)
5753
.get(processedUrl);
5854
}
5955

memex/src/test/resources/application-test.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@ spring.data.mongodb.uri=
33
spring.data.mongodb.database=memex
44
memex.kafkaexmple.enabled=true
55

6+
# To run tests against non-local environment, override memex.base-url with the actual URL of the server under test.
7+
memex.base-url=http://localhost:${local.server.port}
8+
69
memex.test.data.vehicleinspection-testid-range.start=10000
710
memex.test.data.vehicleinspection-testid-range.end=11000

memex/src/test/resources/features/inspections.rest.asof.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Feature: Vehicle Inspection REST API - Point-in-Time History (As Of)
66
@get @as_of @sunny_day
77
Scenario: Successfully retrieve vehicle inspection history as of a specific date
88
Given the following vehicle inspections exist:
9-
| vehicleInspection |
9+
| vehicleinspection |
1010
| {"testid": 10001, "vehicle": {"make": "Ford", "model": "Focus"}} |
1111
And I wait for 1 second
1212
And I capture the current timestamp to "<timestamp>" with "yyyyMMddHHmmss" pattern

memex/src/test/resources/features/inspections.rest.atlas-search.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Feature: Vehicle Inspection REST API - Atlas Search Functionality
66
@post @sunny_day
77
Scenario: Successfully execute an Atlas Search query
88
Given the following vehicle inspections exist:
9-
| vehicleInspection |
9+
| vehicleinspection |
1010
| {"testid": 10001, "vehicle": {"model": "Corolla"}} |
1111
And I wait for 1 second
1212
When I send a POST request to "/api/inspections/search" with the payload:

0 commit comments

Comments
 (0)