Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9f3e018
Return singular PermissionsException from public Permissions Service …
Mythicaeda Jan 8, 2026
e180318
Minor: close httpClient created while checking permissions
Mythicaeda Jan 20, 2026
5f78f51
Move FormattedError to parsing-utilities
Mythicaeda Jan 23, 2026
5bfe9eb
Create PermissionsFormattedError to format Permissions Exceptions
Mythicaeda Jan 23, 2026
7bb89dc
Collect all exceptions thrown while checking permissions into single …
Mythicaeda Jan 20, 2026
a30c127
Create WorkspaceFormattedError to format workspace-specific errors
Mythicaeda Jan 23, 2026
1e89889
Create MerlinFormattedError
Mythicaeda Jan 20, 2026
d022d57
Remove InvalidJsonException and rename InvalidEntityException to Inva…
Mythicaeda Jan 21, 2026
cee8858
Remove MissionModelRepository's Copy of NoSuchMissionModelException
Mythicaeda Jan 21, 2026
6c29993
Remove LocalMissionModelService's Copy of MissionModelLoadException
Mythicaeda Jan 21, 2026
cde2795
Remove Unused MissionModelAccessException
Mythicaeda Jan 23, 2026
2e63431
Use FormattedErrors instead of Error ResponseSerializers
Mythicaeda Jan 13, 2026
e809b88
Update SchedulerBindings
Mythicaeda Feb 3, 2026
5b25aee
Update endpoints added in rebase
Mythicaeda Feb 3, 2026
53ccb33
Remove object wrapping in NoSuchPlanException and NoSuchPlanDatasetEx…
Mythicaeda Feb 3, 2026
b91f89d
Include the id of the missing object for "No such X"-type exceptions,
Mythicaeda Feb 3, 2026
013be91
Update E2E Tests
Mythicaeda Feb 3, 2026
897e054
Update Worker AppDrivers to Format Errors
Mythicaeda Mar 2, 2026
2be97fa
Remove Swallowed NoSuchConstraintException, ProcedureLoadException
Mythicaeda Mar 2, 2026
f7f56ec
Apply `EffectModel` Annotation to ExceptionActivity's EffectModel
Mythicaeda Mar 2, 2026
7e3f9a8
Merlin: Use Switch Pattern Matching for new Serializers
Mythicaeda May 13, 2026
8505a7f
Scheduling: Use Switch Pattern Matching for new Serializers
Mythicaeda May 13, 2026
c7d1498
Merlin: Additional InvalidEntityException -> InvalidJsonEntityExcepti…
Mythicaeda May 13, 2026
596e109
Add a per-service toggle to write FormattedErrors in a digestable way…
Mythicaeda May 14, 2026
cb29393
Update E2ETests
Mythicaeda May 18, 2026
6e0cba7
Update log message for catch-all exceptions
Mythicaeda May 19, 2026
62e60ea
Fix typo
Mythicaeda May 19, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ void noSuchActivityType() throws IOException, InterruptedException {
Thread.sleep(1000); // TODO consider a while loop here
final var activityValidations = hasura.getActivityValidations(planId);
final ActivityValidation activityValidation = activityValidations.get((long) activityId);
assertEquals(new ActivityValidation.NoSuchActivityTypeFailure("no such activity type", "NopeBanana"), activityValidation);
assertEquals(new ActivityValidation.NoSuchActivityTypeFailure("No such activity type NopeBanana", "NopeBanana"), activityValidation);
}

@Test
Expand Down Expand Up @@ -150,7 +150,7 @@ void noSuchMissionModelError() throws IOException, InterruptedException {
final var activityValidations = hasura.getActivityValidations(planId);
final ActivityValidation activityValidation = activityValidations.get((long) activityId);
assertEquals(
new ActivityValidation.NoSuchMissionModelFailure("no such mission model", 0),
new ActivityValidation.NoSuchMissionModelFailure("No mission model exists with id `0`", 0),
activityValidation
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import gov.nasa.jpl.aerie.e2e.types.ExternalDataset.ProfileInput;
import gov.nasa.jpl.aerie.e2e.types.ExternalDataset.ProfileInput.ProfileSegmentInput;
import gov.nasa.jpl.aerie.e2e.types.ValueSchema;
import gov.nasa.jpl.aerie.e2e.types.workspaces.HasuraRequestFailure;
import gov.nasa.jpl.aerie.e2e.utils.GatewayRequests;
import gov.nasa.jpl.aerie.e2e.utils.HasuraRequests;
import org.junit.jupiter.api.*;
Expand Down Expand Up @@ -94,13 +95,17 @@ void afterEach() throws IOException {

@Test
void constraintsFailNoSimData() {
final var exception = assertThrows(RuntimeException.class, () -> hasura.checkConstraints(planId));
final var message = exception.getMessage().split("\"message\":\"")[1].split("\"}]")[0];
// Hasura strips the cause message ("Assumption falsified -- mission model for existing plan does not exist")
// from the error it returns
if (!message.equals("input mismatch exception")) {
throw exception;
}
final var exception = assertThrows(HasuraRequestFailure.class, () -> hasura.checkConstraints(planId));

// Check the response message
final var expectedMessage = "plan with id " + planId + " has not yet been simulated at its current revision";
assertEquals(expectedMessage, exception.getMessage());

// Check the attached extensions object
final var extensions = exception.getExtensions();
assertEquals("INPUT_MISMATCH_EXCEPTION", extensions.getString("type"));
assertEquals(expectedMessage, extensions.getString("message"));
assertEquals("aerie_merlin", extensions.getString("service"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ void invalidPlanId() {
.toString();
final var response = request.post("/getSimulationResults", RequestOptions.create().setData(data));
assertEquals(404, response.status());
assertEquals("no such plan", getBody(response).getString("message"));
assertEquals("No plan exists with id `-1`", getBody(response).getString("message"));
}

@Test
Expand Down Expand Up @@ -184,7 +184,6 @@ class ResourceSamples {
@Test
void invalidPlanId() {
// Returns a 404 if the PlanId is invalid
// message is "no such plan"
final String data = Json.createObjectBuilder()
.add("action", Json.createObjectBuilder().add("name", "resource_samples"))
.add("input", Json.createObjectBuilder().add("planId", -1))
Expand All @@ -194,7 +193,7 @@ void invalidPlanId() {
.toString();
final var response = request.post("/resourceSamples", RequestOptions.create().setData(data));
assertEquals(404, response.status());
assertEquals("no such plan", getBody(response).getString("message"));
assertEquals("No plan exists with id `-1`", getBody(response).getString("message"));
}

@Test
Expand Down Expand Up @@ -259,13 +258,12 @@ void invalidPlanId() {
.toString();
final var response = request.post("/constraintViolations", RequestOptions.create().setData(data));
assertEquals(404, response.status());
assertEquals("no such plan", getBody(response).getString("message"));
assertEquals("No plan exists with id `-1`", getBody(response).getString("message"));
}

@Test
void invalidSimDatasetId() throws IOException {
// Returns a 404 if the SimDatasetId is invalid
// Message is an "input mismatch exception"
hasura.awaitSimulation(planId);
final String data = Json.createObjectBuilder()
.add("action", Json.createObjectBuilder().add("name", "check_constraints"))
Expand All @@ -279,15 +277,17 @@ void invalidSimDatasetId() throws IOException {
.toString();
final var response = request.post("/constraintViolations", RequestOptions.create().setData(data));
assertEquals(404, response.status());
final var expectedResponse = Json.createObjectBuilder()
.add("message", "input mismatch exception")
.add(
"extensions", Json.createObjectBuilder()
.add(
"cause",
"simulation dataset with id `-1` does not exist"))
.build();
assertEquals(expectedResponse, getBody(response));

final var body = getBody(response);
final var extensions = body.getJsonObject("extensions");
// Check the message field
final var expectedMessage = "simulation dataset with id `-1` does not exist";
assertEquals(expectedMessage, body.getString("message"));

// Check the extensions
assertEquals("INPUT_MISMATCH_EXCEPTION", extensions.getString("type"));
assertEquals(expectedMessage, extensions.getString("message"));
assertEquals("aerie_merlin", extensions.getString("service"));
}

@Test
Expand All @@ -303,7 +303,6 @@ void incorrectSimDatasetId() throws IOException {
final int simDatasetId = hasura.awaitSimulation(secondPlanId).simDatasetId();

// Returns a 404 because the simDataset belonged to a different plan
// Message is 'simulation dataset mismatch exception'
final String data = Json.createObjectBuilder()
.add("action", Json.createObjectBuilder().add("name", "check_constraints"))
.add(
Expand All @@ -316,13 +315,19 @@ void incorrectSimDatasetId() throws IOException {
.toString();
final var response = request.post("/constraintViolations", RequestOptions.create().setData(data));
assertEquals(404, response.status());
final var expectedCause =
"Simulation Dataset with id `" + simDatasetId + "` does not belong to Plan with id `" + planId + "`";
final var expectedResponse = Json.createObjectBuilder()
.add("message", "simulation dataset mismatch exception")
.add("extensions", Json.createObjectBuilder().add("cause", expectedCause))
.build();
assertEquals(expectedResponse, getBody(response));

// Check the response
final var body = getBody(response);
final var extensions = body.getJsonObject("extensions");

// Check the message field
final var expectedMessage = "Simulation Dataset with id `" + simDatasetId + "` does not belong to Plan with id `" + planId + "`";
assertEquals(expectedMessage, body.getString("message"));

// Check the extensions object
assertEquals("SIM_DATASET_MISMATCH_EXCEPTION", extensions.getString("type"));
assertEquals(expectedMessage, extensions.getString("message"));
assertEquals("aerie_merlin", extensions.getString("service"));
} finally {
hasura.deletePlan(secondPlanId);
}
Expand Down Expand Up @@ -358,12 +363,19 @@ void noSimDatasets() {
.toString();
final var response = request.post("/constraintViolations", RequestOptions.create().setData(data));
assertEquals(404, response.status());
final var expectedCause = "plan with id " + planId + " has not yet been simulated at its current revision";
final var expectedBody = Json.createObjectBuilder()
.add("message", "input mismatch exception")
.add("extensions", Json.createObjectBuilder().add("cause", expectedCause))
.build();
assertEquals(expectedBody, getBody(response));

// Check the response
final var body = getBody(response);
final var extensions = body.getJsonObject("extensions");

// Check the message field
final var expectedMessage = "plan with id " + planId + " has not yet been simulated at its current revision";
assertEquals(expectedMessage, body.getString("message"));

// Check the extensions object
assertEquals("INPUT_MISMATCH_EXCEPTION", extensions.getString("type"));
assertEquals(expectedMessage, extensions.getString("message"));
assertEquals("aerie_merlin", extensions.getString("service"));
}

@Test
Expand Down Expand Up @@ -434,7 +446,7 @@ void invalidMissionModelId() {
.toString();
final var response = request.post("/refreshModelParameters", RequestOptions.create().setData(data));
assertEquals(404, response.status());
assertEquals("no such mission model", getBody(response).getString("message"));
assertEquals("No mission model exists with id `-1`", getBody(response).getString("message"));
}

@Test
Expand Down Expand Up @@ -474,7 +486,7 @@ void invalidMissionModelId() {
.toString();
final var response = request.post("/refreshActivityTypes", RequestOptions.create().setData(data));
assertEquals(404, response.status());
assertEquals("no such mission model", getBody(response).getString("message"));
assertEquals("No mission model exists with id `-1`", getBody(response).getString("message"));
}

@Test
Expand Down Expand Up @@ -502,7 +514,6 @@ class RefreshResourceTypes {
@Test
void invalidMissionModelId() {
// Returns a 404 if the MissionModelId is invalid
// message is "no such mission model"
final String data = Json.createObjectBuilder()
.add(
"event", Json.createObjectBuilder()
Expand All @@ -514,7 +525,7 @@ void invalidMissionModelId() {
.toString();
final var response = request.post("/refreshResourceTypes", RequestOptions.create().setData(data));
assertEquals(404, response.status());
assertEquals("no such mission model", getBody(response).getString("message"));
assertEquals("No mission model exists with id `-1`", getBody(response).getString("message"));
}

@Test
Expand Down Expand Up @@ -542,7 +553,6 @@ class ValidateActivityArguments {
@Test
void invalidMissionModelId() {
// Returns a 404 if the MissionModelId is invalid
// message is "no such mission model"
final String data = Json.createObjectBuilder()
.add("action", Json.createObjectBuilder().add("name", "validateActivityArguments"))
.add(
Expand All @@ -556,7 +566,10 @@ void invalidMissionModelId() {
.toString();
final var response = request.post("/validateActivityArguments", RequestOptions.create().setData(data));
assertEquals(404, response.status());
assertEquals("no such mission model", getBody(response).getString("message"));
final var body = getBody(response);
assertEquals("No mission model exists with id `-1`", body.getString("message"));
assertTrue(body.containsKey("extensions"));
assertEquals("NO_SUCH_MISSION_MODEL", body.getJsonObject("extensions").getString("type"));
}

@Test
Expand Down Expand Up @@ -585,7 +598,6 @@ class ValidateModelArguments {
@Test
void invalidMissionModelId() {
// Returns a 404 if the MissionModelId is invalid
// message is "no such mission model"
final String data = Json.createObjectBuilder()
.add("action", Json.createObjectBuilder().add("name", "validateModelArguments"))
.add(
Expand All @@ -598,7 +610,7 @@ void invalidMissionModelId() {
.toString();
final var response = request.post("/validateModelArguments", RequestOptions.create().setData(data));
assertEquals(404, response.status());
assertEquals("no such mission model", getBody(response).getString("message"));
assertEquals("No mission model exists with id `-1`", getBody(response).getString("message"));
}

@Test
Expand Down Expand Up @@ -636,7 +648,7 @@ void invalidPlanId() {
.toString();
final var response = request.post("/validatePlan", RequestOptions.create().setData(data));
assertEquals(404, response.status());
assertEquals("no such plan", getBody(response).getString("message"));
assertEquals("No plan exists with id `-1`", getBody(response).getString("message"));
}

@Test
Expand Down Expand Up @@ -674,7 +686,7 @@ void invalidMissionModelId() {
.toString();
final var response = request.post("/getModelEffectiveArguments", RequestOptions.create().setData(data));
assertEquals(404, response.status());
assertEquals("no such mission model", getBody(response).getString("message"));
assertEquals("No mission model exists with id `-1`", getBody(response).getString("message"));
}

@Test
Expand Down Expand Up @@ -733,7 +745,7 @@ void invalidMissionModelId() {
.toString();
final var response = request.post("/getActivityEffectiveArgumentsBulk", RequestOptions.create().setData(data));
assertEquals(404, response.status());
assertEquals("no such mission model", getBody(response).getString("message"));
assertEquals("No mission model exists with id `-1`", getBody(response).getString("message"));
}

@Test
Expand Down Expand Up @@ -818,7 +830,7 @@ void invalidPlanId() {
.toString();
final var response = request.post("/addExternalDataset", RequestOptions.create().setData(data));
assertEquals(404, response.status());
assertEquals("no such plan", getBody(response).getString("message"));
assertEquals("No plan exists with id `-1`", getBody(response).getString("message"));
}

@Test
Expand Down Expand Up @@ -885,7 +897,7 @@ void invalidDatasetId() {
.toString();
final var response = request.post("/extendExternalDataset", RequestOptions.create().setData(data));
assertEquals(404, response.status());
assertEquals("no such plan dataset", getBody(response).getString("message"));
assertEquals("No plan dataset exists with id `-1`", getBody(response).getString("message"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ class Schedule{
@Test
void invalidSpecId(){
// Returns a 404 if the SpecId is invalid
// message is "no such scheduling specification"
final String data = Json.createObjectBuilder()
.add("action", Json.createObjectBuilder().add("name", "scheduler"))
.add("input", Json.createObjectBuilder().add("specificationId", -1))
Expand All @@ -99,7 +98,19 @@ void invalidSpecId(){
.toString();
final var response = request.post("/schedule", RequestOptions.create().setData(data));
assertEquals(404, response.status());
assertEquals("no such scheduling specification", getBody(response).getString("message"));

// Check the response
final var body = getBody(response);
final var extensions = body.getJsonObject("extensions");

// Check the message field
final var expectedMessage = "Could not check permissions on scheduling specification -1: specification does not exist.";
assertEquals(expectedMessage, body.getString("message"));

// Check the extensions object
assertEquals("NO_SUCH_SCHEDULING_SPECIFICATION", extensions.getString("type"));
assertEquals(expectedMessage, extensions.getString("message"));
assertEquals("aerie_permissions", extensions.getString("service"));
}
@Test
void forbidden(){
Expand Down Expand Up @@ -172,7 +183,7 @@ void invalidPlanId() {
assertEquals(200, response.status());
final var expectedBody = Json.createObjectBuilder()
.add("status", "failure")
.add("reason", "No plan exists with id `PlanId[id=-1]`")
.add("reason", "No plan exists with id `-1`")
.build();
assertEquals(expectedBody, getBody(response));
}
Expand Down
Loading
Loading