Skip to content

Commit

Permalink
Merge pull request #1088 from NASA-AMMOS/feat/user-plan-snapshots
Browse files Browse the repository at this point in the history
User Plan Snapshots
  • Loading branch information
Mythicaeda authored Aug 21, 2023
2 parents 0d19770 + cd15ed5 commit 4a8e144
Show file tree
Hide file tree
Showing 18 changed files with 1,494 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,28 @@
public class PermissionsTest {
private static final File initSqlScriptFile = new File("../merlin-server/sql/merlin/init.sql");
private enum FunctionPermissionKey {
apply_preset, branch_plan, create_merge_rq, withdraw_merge_rq, begin_merge, cancel_merge,
commit_merge, deny_merge, get_conflicting_activities, get_non_conflicting_activities, set_resolution,
set_resolution_bulk, delete_activity_subtree, delete_activity_subtree_bulk, delete_activity_reanchor_plan,
delete_activity_reanchor_plan_bulk, delete_activity_reanchor, delete_activity_reanchor_bulk, get_plan_history,
restore_activity_changelog
apply_preset,
begin_merge,
branch_plan,
cancel_merge,
create_merge_rq,
create_snapshot,
commit_merge,
delete_activity_reanchor,
delete_activity_reanchor_bulk,
delete_activity_reanchor_plan,
delete_activity_reanchor_plan_bulk,
delete_activity_subtree,
delete_activity_subtree_bulk,
deny_merge,
get_conflicting_activities,
get_non_conflicting_activities,
get_plan_history,
restore_activity_changelog,
restore_snapshot,
set_resolution,
set_resolution_bulk,
withdraw_merge_rq
}
private enum GeneralPermission {
OWNER, MISSION_MODEL_OWNER, PLAN_OWNER, PLAN_COLLABORATOR, PLAN_OWNER_COLLABORATOR
Expand Down Expand Up @@ -176,25 +193,27 @@ void aerieAdminAlwaysReturnsNoCheck(FunctionPermissionKey function) throws SQLEx
@Test
void getFunctionReturnsAssignedValueUser() throws SQLException {
assertEquals("PLAN_OWNER_COLLABORATOR", getFunctionPermission("apply_preset", merlinHelper.user.session()));
assertEquals("NO_CHECK", getFunctionPermission("branch_plan", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_SOURCE", getFunctionPermission("create_merge_rq", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_SOURCE", getFunctionPermission("withdraw_merge_rq", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_TARGET", getFunctionPermission("begin_merge", merlinHelper.user.session()));
assertEquals("NO_CHECK", getFunctionPermission("branch_plan", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_TARGET", getFunctionPermission("cancel_merge", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_SOURCE", getFunctionPermission("create_merge_rq", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_COLLABORATOR", getFunctionPermission("create_snapshot", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_TARGET", getFunctionPermission("commit_merge", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_COLLABORATOR", getFunctionPermission("delete_activity_reanchor", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_COLLABORATOR", getFunctionPermission("delete_activity_reanchor_bulk", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_COLLABORATOR", getFunctionPermission("delete_activity_reanchor_plan", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_COLLABORATOR", getFunctionPermission("delete_activity_reanchor_plan_bulk", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_COLLABORATOR", getFunctionPermission("delete_activity_subtree", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_COLLABORATOR", getFunctionPermission("delete_activity_subtree_bulk", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_TARGET", getFunctionPermission("deny_merge", merlinHelper.user.session()));
assertEquals("NO_CHECK", getFunctionPermission("get_conflicting_activities", merlinHelper.user.session()));
assertEquals("NO_CHECK", getFunctionPermission("get_non_conflicting_activities", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_TARGET", getFunctionPermission("set_resolution", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_TARGET", getFunctionPermission("set_resolution_bulk", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_COLLABORATOR", getFunctionPermission("delete_activity_subtree", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_COLLABORATOR", getFunctionPermission("delete_activity_subtree_bulk", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_COLLABORATOR", getFunctionPermission("delete_activity_reanchor_plan", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_COLLABORATOR", getFunctionPermission("delete_activity_reanchor_plan_bulk", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_COLLABORATOR", getFunctionPermission("delete_activity_reanchor", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_COLLABORATOR", getFunctionPermission("delete_activity_reanchor_bulk", merlinHelper.user.session()));
assertEquals("NO_CHECK", getFunctionPermission("get_plan_history", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_COLLABORATOR", getFunctionPermission("restore_activity_changelog", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_COLLABORATOR", getFunctionPermission("restore_snapshot", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_TARGET", getFunctionPermission("set_resolution", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_TARGET", getFunctionPermission("set_resolution_bulk", merlinHelper.user.session()));
assertEquals("PLAN_OWNER_SOURCE", getFunctionPermission("withdraw_merge_rq", merlinHelper.user.session()));
}

@ParameterizedTest
Expand Down Expand Up @@ -425,7 +444,7 @@ void testOwner() throws SQLException {

// MISSION_MODEL_OWNER: The user must be Owner of the model
@Test
void testMissionModel() throws SQLException {
void testMissionModelOwner() throws SQLException {
// Setup
final int missionModelId = merlinHelper.insertMissionModel(fileId, merlinHelper.user.name());
final int basePlan = merlinHelper.insertPlan(missionModelId, merlinHelper.viewer.name());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,15 @@ int createSnapshot(final int planId) throws SQLException {
}
}

void restoreFromSnapshot(final int planId, final int snapshotId) throws SQLException {
try (final var statement = connection.createStatement()) {
statement.execute(
"""
call restore_from_snapshot(%d, %d)
""".formatted(planId, snapshotId));
}
}

int getParentPlanId(final int planId) throws SQLException{
try (final var statement = connection.createStatement()) {
final var res = statement.executeQuery("""
Expand Down Expand Up @@ -452,6 +461,7 @@ private void setMergeRequestStatus(final int requestId, final String newStatus)

public static void assertActivityEquals(final Activity expected, final Activity actual) {
// validate all shared properties
assertEquals(expected.activityId, actual.activityId);
assertEquals(expected.name, actual.name);
assertEquals(expected.sourceSchedulingGoalId, actual.sourceSchedulingGoalId);
assertEquals(expected.createdAt, actual.createdAt);
Expand Down Expand Up @@ -631,6 +641,126 @@ void snapshotFailsForNonexistentPlanId() throws SQLException{
}
}

@Nested
class RestorePlanSnapshotTests{
@Test
void restoreFailsForNonexistentPlan() throws SQLException {
final int snapshotId = createSnapshot(merlinHelper.insertPlan(missionModelId));
try {
restoreFromSnapshot(-1, snapshotId);
} catch (SQLException ex) {
if (!ex.getMessage().contains("Cannot Restore: Plan with ID -1 does not exist.")) {
throw ex;
}
}
}

@Test
void restoreFailsForNonexistentSnapshot() throws SQLException {
final int planId = merlinHelper.insertPlan(missionModelId);
try {
restoreFromSnapshot(planId, -1);
} catch (SQLException ex) {
if (!ex.getMessage().contains("Cannot Restore: Snapshot with ID -1 does not exist.")) {
throw ex;
}
}
}

@Test
void cannotRestoreSnapshotOfDifferentPlan() throws SQLException {
final int wrongPlan = merlinHelper.insertPlan(missionModelId);
final int snapshotId = createSnapshot(wrongPlan);
final int planId = merlinHelper.insertPlan(missionModelId, merlinHelper.user.name(), "Other Plan");

try {
restoreFromSnapshot(planId, snapshotId);
} catch (SQLException ex) {
if (!ex.getMessage().contains("Cannot Restore: Snapshot %d is not a snapshot of Plan 'Other Plan' (ID %d)"
.formatted(snapshotId, planId))) {
throw ex;
}
}
}

@Test
void cannotRestoreBranchToParentSnapshot() throws SQLException {
final int wrongPlan = merlinHelper.insertPlan(missionModelId);
final int snapshotId = createSnapshot(wrongPlan);
final int branchId = duplicatePlan(wrongPlan, "Different Plan");

try{
restoreFromSnapshot(branchId, snapshotId);
} catch (SQLException ex) {
if (!ex.getMessage().contains("Cannot Restore: Snapshot %d is not a snapshot of Plan 'Different Plan' (ID %d)"
.formatted(snapshotId, branchId))) {
throw ex;
}
}
}

@Test
void restoresDeletedActivities() throws SQLException {
final int planId = merlinHelper.insertPlan(missionModelId);
final Activity deletedDirective = getActivity(planId, merlinHelper.insertActivity(planId));
final int snapshotId = createSnapshot(planId);

// Empty Plan
deleteActivityDirective(planId, deletedDirective.activityId);
assertEquals(0, getActivities(planId).size());

// Restore Plan from Snapshot
restoreFromSnapshot(planId, snapshotId);
final var planActivities = getActivities(planId);
assertEquals(1, planActivities.size());

// Assert that restored directive equals what it did before (aside from last_updated fields)
final Activity restoredDirective = planActivities.get(0);
assertActivityEquals(deletedDirective, restoredDirective);
}

@Test
void restoreDeletesAddedActivities() throws SQLException {
final int planId = merlinHelper.insertPlan(missionModelId);
final Activity stableDirective = getActivity(planId, merlinHelper.insertActivity(planId));
final int snapshotId = createSnapshot(planId);

// Add new Directive
merlinHelper.insertActivity(planId);
assertEquals(2, getActivities(planId).size());

// Restore Plan from Snapshot
restoreFromSnapshot(planId, snapshotId);
final var planActivities = getActivities(planId);
assertEquals(1, planActivities.size());

// Assert that remaining directive is the one that was there at the time of the snapshot
final Activity restoredDirective = planActivities.get(0);
assertActivityEquals(stableDirective, restoredDirective);
}

@Test
void restoresChangedActivities() throws SQLException {
final int planId = merlinHelper.insertPlan(missionModelId);
final int oldDirectiveId = merlinHelper.insertActivity(planId);
updateActivityName("old name", oldDirectiveId, planId);
final Activity oldDirective = getActivity(planId, oldDirectiveId);
final int snapshotId = createSnapshot(planId);

// Modify Directive
updateActivityName("new name", oldDirective.activityId, planId);

// Restore Plan from Snapshot
restoreFromSnapshot(planId, snapshotId);
final var planActivities = getActivities(planId);
assertEquals(1, planActivities.size());

// Assert that directive's state has been restored
final Activity restoredDirective = planActivities.get(0);
assertActivityEquals(oldDirective, restoredDirective);
}
}

@Nested
class DuplicatePlanTests{
@Test
Expand Down
Loading

0 comments on commit 4a8e144

Please sign in to comment.