S18 Added OR Tools rules for scheduling show
All checks were successful
Build, test and publish / verify (push) Successful in 6m12s
All checks were successful
Build, test and publish / verify (push) Successful in 6m12s
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
package nl.agilecc.agiledancers;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import nl.agilecc.agiledancers.model.DanceGroup;
|
||||
import nl.agilecc.agiledancers.model.DayPart;
|
||||
import nl.agilecc.agiledancers.model.Show;
|
||||
import nl.agilecc.agiledancers.model.scheduling.SchedulingInput;
|
||||
import nl.agilecc.agiledancers.model.scheduling.SchedulingResult;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupType;
|
||||
import nl.agilecc.agiledancers.model.specifications.DayPartType;
|
||||
import nl.agilecc.agiledancers.service.ManagementException;
|
||||
import nl.agilecc.agiledancers.service.SchedulingInputs;
|
||||
import nl.agilecc.agiledancers.service.SchedulingService;
|
||||
import nl.agilecc.agiledancers.service.SchoolManagementService;
|
||||
import nl.agilecc.agiledancers.service.ShowSchedules;
|
||||
import nl.agilecc.agiledancers.service.model.GroupDetails;
|
||||
import nl.agilecc.agiledancers.service.model.SchoolSnapshot;
|
||||
import nl.agilecc.agiledancers.solver.OrToolsSchedulingEngine;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class S18LifecycleTest {
|
||||
private final SchoolManagementService schools = new SchoolManagementService();
|
||||
private final long school = schools.createSchool("Studio").getId();
|
||||
private final ShowSchedules schedules = new ShowSchedules(schools);
|
||||
|
||||
private List<DayPart> parts(final int n) {
|
||||
return java.util.stream.IntStream.range(0, n)
|
||||
.mapToObj((final int i) -> DayPart.builder()
|
||||
.date(LocalDate.of(2027, 1, 1).plusDays(i))
|
||||
.type(DayPartType.MORNING)
|
||||
.danceGroups(List.of())
|
||||
.build())
|
||||
.toList();
|
||||
}
|
||||
|
||||
private Show settings(final int n, final Integer x) {
|
||||
return Show.builder()
|
||||
.name("Show")
|
||||
.dayParts(parts(n))
|
||||
.preferredDayParts(x)
|
||||
.build();
|
||||
}
|
||||
|
||||
private DanceGroup group(final DanceGroupType type) {
|
||||
final DanceGroup group = schools.createGroup(school, new GroupDetails("Group", type, 0, 99, Set.of()));
|
||||
final long dancer =
|
||||
schools.createDancer(school, "Dancer", LocalDate.of(2000, 1, 2)).getId();
|
||||
schools.addGroupDancer(school, group.getId(), dancer);
|
||||
return group;
|
||||
}
|
||||
|
||||
public @Test void draftEditingValidationFinalizationAndImportPreserveOrder() {
|
||||
final DanceGroup group = group(DanceGroupType.REGULAR);
|
||||
final Show show = schedules.configure(school, null, settings(2, 1));
|
||||
assertThrows(ManagementException.class, () -> schedules.finalizeSchedule(school, show.getId()));
|
||||
schedules.saveDraft(school, show.getId(), List.of(List.of(group.getId()), List.of()));
|
||||
assertTrue(schedules.violations(school, show.getId()).isEmpty());
|
||||
schedules.finalizeSchedule(school, show.getId());
|
||||
assertTrue(show.isFinalized());
|
||||
final SchoolSnapshot snapshot = schools.exportSchool(school);
|
||||
assertTrue(snapshot.shows().getFirst().finalized());
|
||||
final Show imported = snapshot.restore().getShows().iterator().next();
|
||||
assertFalse(imported.isFinalized());
|
||||
assertEquals(1, imported.getPreferredDayParts());
|
||||
assertEquals(1, imported.getDayParts().getFirst().getDanceGroups().size());
|
||||
schedules.configure(school, show.getId(), settings(2, 1));
|
||||
assertTrue(show.isFinalized());
|
||||
schedules.saveDraft(school, show.getId(), List.of(List.of(), List.of(group.getId())));
|
||||
assertFalse(show.isFinalized());
|
||||
schedules.finalizeSchedule(school, show.getId());
|
||||
schools.removeGroupDancer(
|
||||
school, group.getId(), group.getDancers().iterator().next().getId());
|
||||
assertFalse(show.isFinalized());
|
||||
assertFalse(schedules.violations(school, show.getId()).isEmpty());
|
||||
}
|
||||
|
||||
public @Test void invalidDraftsAreSavedButStructuralErrorsAreAtomic() {
|
||||
final DanceGroup group = group(DanceGroupType.REGULAR);
|
||||
final Show show = schedules.configure(school, null, settings(2, 1));
|
||||
schedules.saveDraft(school, show.getId(), List.of(List.of(group.getId(), group.getId()), List.of()));
|
||||
assertFalse(schedules.violations(school, show.getId()).isEmpty());
|
||||
final SchoolSnapshot before = schools.exportSchool(school);
|
||||
assertThrows(ManagementException.class, () -> schedules.saveDraft(school, show.getId(), List.of()));
|
||||
assertThrows(
|
||||
ManagementException.class,
|
||||
() -> schedules.saveDraft(school, show.getId(), List.of(List.of(), List.of(999L))));
|
||||
assertEquals(before, schools.exportSchool(school));
|
||||
}
|
||||
|
||||
public @Test void xDefaultsRetainedOnDaypartReductionAndInvalidConfigurationIsAtomic() {
|
||||
final Show one = schedules.configure(school, null, settings(1, null));
|
||||
assertEquals(1, one.getPreferredDayParts());
|
||||
final Show show = schedules.configure(school, null, settings(3, null));
|
||||
assertEquals(2, show.getPreferredDayParts());
|
||||
schedules.configure(school, show.getId(), settings(1, null));
|
||||
assertEquals(2, show.getPreferredDayParts());
|
||||
assertThrows(ManagementException.class, () -> schools.beginScheduling(school, show.getId()));
|
||||
schedules.configure(school, show.getId(), settings(1, 1));
|
||||
assertTrue(schedules.violations(school, show.getId()).isEmpty());
|
||||
final int before = schools.shows(school).size();
|
||||
assertThrows(ManagementException.class, () -> schedules.configure(school, null, settings(1, 2)));
|
||||
assertEquals(before, schools.shows(school).size());
|
||||
schedules.configure(school, show.getId(), settings(0, null));
|
||||
assertFalse(schedules.violations(school, show.getId()).isEmpty());
|
||||
}
|
||||
|
||||
public @Test void timeoutPreservesAssignmentsAndIsNotReportedAsInfeasible() {
|
||||
group(DanceGroupType.REGULAR);
|
||||
final Show show = schedules.configure(school, null, settings(1, 1));
|
||||
new SchedulingService(schools, new OrToolsSchedulingEngine()).schedule(school, show.getId());
|
||||
final SchoolSnapshot before = schools.exportSchool(school);
|
||||
final ManagementException error = assertThrows(
|
||||
ManagementException.class,
|
||||
() -> new SchedulingService(schools, new OrToolsSchedulingEngine(0)).schedule(school, show.getId()));
|
||||
assertTrue(error.getMessage().contains("limit"));
|
||||
assertFalse(error.getMessage().contains("infeasible"));
|
||||
assertEquals(before, schools.exportSchool(school));
|
||||
}
|
||||
|
||||
public @Test void emptyGroupsSkippedAndAgeUsesFirstShowDateAcrossDates() {
|
||||
final DanceGroup ordinary = group(DanceGroupType.PRE_EDUCATION);
|
||||
group(DanceGroupType.PRE_SCHOOLER);
|
||||
schools.createGroup(school, new GroupDetails("Empty", DanceGroupType.SHOW_CASE, 0, 99, Set.of()));
|
||||
final Show show = schedules.configure(school, null, settings(3, 1));
|
||||
final SchedulingInput input = schools.beginScheduling(school, show.getId());
|
||||
assertEquals(2, input.groups().size());
|
||||
assertEquals(26, input.groups().getFirst().averageAge());
|
||||
assertEquals(1, input.groups().getFirst().appearances());
|
||||
schools.endScheduling(show.getId());
|
||||
new SchedulingService(schools, new OrToolsSchedulingEngine()).schedule(school, show.getId());
|
||||
assertFalse(show.isFinalized());
|
||||
final SchedulingInput stale = schools.beginScheduling(school, show.getId());
|
||||
final SchedulingResult result = new OrToolsSchedulingEngine().solve(stale);
|
||||
schools.updateDancer(
|
||||
school, ordinary.getDancers().iterator().next().getId(), "Dancer", LocalDate.of(2001, 1, 1));
|
||||
assertThrows(ManagementException.class, () -> schools.finishScheduling(school, stale, result));
|
||||
schools.endScheduling(show.getId());
|
||||
assertEquals(
|
||||
2, SchedulingInputs.of(schools.school(school), show, 0).groups().size());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package nl.agilecc.agiledancers;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
class S18ResourceTest {
|
||||
private static final String ROOT = "/api/dance-schools";
|
||||
|
||||
private static long create(final String url, final Object body) {
|
||||
return given().contentType("application/json")
|
||||
.body(body)
|
||||
.post(url)
|
||||
.then()
|
||||
.statusCode(201)
|
||||
.extract()
|
||||
.jsonPath()
|
||||
.getLong("id");
|
||||
}
|
||||
|
||||
private static long dancer(final String school, final String name) {
|
||||
return create(school + "/dancers", Map.of("name", name, "birthDate", "1990-01-01"));
|
||||
}
|
||||
|
||||
private static long group(final String school, final long dancer) {
|
||||
final long group = create(
|
||||
school + "/dance-groups",
|
||||
Map.of("category", "Group", "type", "REGULAR", "startAge", 0, "endAge", 99, "styles", List.of()));
|
||||
given().put(school + "/dance-groups/" + group + "/dancers/" + dancer)
|
||||
.then()
|
||||
.statusCode(204);
|
||||
return group;
|
||||
}
|
||||
|
||||
private static String show(final String school) {
|
||||
return school + "/shows/"
|
||||
+ create(
|
||||
school + "/shows",
|
||||
Map.of(
|
||||
"name",
|
||||
"Show",
|
||||
"preferredDayParts",
|
||||
1,
|
||||
"dayParts",
|
||||
List.of(Map.of("date", "2027-01-01", "type", "MORNING"))));
|
||||
}
|
||||
|
||||
public @Test void draftApiFinalizesOnlyValidSchedulesAndRelevantEditsInvalidateThem() {
|
||||
final String school = ROOT + "/" + create(ROOT, Map.of("name", "S18 lifecycle"));
|
||||
try {
|
||||
final long dancer = dancer(school, "Dancer");
|
||||
final long group = group(school, dancer);
|
||||
final String show = show(school);
|
||||
given().post(show + "/finalize").then().statusCode(400);
|
||||
given().contentType("application/json")
|
||||
.body(Map.of("assignments", List.of(List.of(group))))
|
||||
.put(show + "/assignments")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.body("finalized", equalTo(false), "violations", empty());
|
||||
given().post(show + "/finalize").then().statusCode(200).body("finalized", equalTo(true));
|
||||
given().delete(school + "/dance-groups/" + group + "/dancers/" + dancer)
|
||||
.then()
|
||||
.statusCode(204);
|
||||
given().get(show).then().body("finalized", equalTo(false), "violations", hasSize(1));
|
||||
given().contentType("application/json")
|
||||
.body(Map.of("assignments", List.of(List.of(group, group))))
|
||||
.put(show + "/assignments")
|
||||
.then()
|
||||
.statusCode(200);
|
||||
final String snapshot = given().get(school + "/export").asString();
|
||||
given().contentType("application/json")
|
||||
.body(snapshot)
|
||||
.post(ROOT + "/import/preview")
|
||||
.then()
|
||||
.statusCode(200);
|
||||
} finally {
|
||||
given().delete(school).then().statusCode(204);
|
||||
}
|
||||
}
|
||||
|
||||
public @Test void coachIdentityIsExplicitSchoolScopedAndRoundTrips() {
|
||||
final String school = ROOT + "/" + create(ROOT, Map.of("name", "S18 coaches"));
|
||||
try {
|
||||
final long dancer = dancer(school, "Coach");
|
||||
group(school, dancer);
|
||||
final String show = show(school);
|
||||
given().post(show + "/schedule").then().statusCode(200);
|
||||
given().post(show + "/finalize").then().statusCode(200);
|
||||
final long coach = given().contentType("application/json")
|
||||
.body(Map.of("dancerId", dancer))
|
||||
.post(school + "/coaches")
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.extract()
|
||||
.jsonPath()
|
||||
.getLong("id");
|
||||
given().get(show).then().body("finalized", equalTo(false));
|
||||
given().get(school + "/coaches").then().body("[0].dancerId", equalTo((int) dancer));
|
||||
final String exported = given().get(school + "/export").asString();
|
||||
given().contentType("application/json")
|
||||
.body(exported)
|
||||
.post(ROOT + "/import?replaceId=" + school.substring(school.lastIndexOf('/') + 1))
|
||||
.then()
|
||||
.statusCode(200);
|
||||
given().get(school + "/export").then().body("coaches[0].dancerId", equalTo("1"));
|
||||
final long importedDancer =
|
||||
given().get(school + "/dancers").jsonPath().getLong("[0].id");
|
||||
given().contentType("application/json")
|
||||
.body(Map.of("dancerId", importedDancer))
|
||||
.put(school + "/coaches/0")
|
||||
.then()
|
||||
.statusCode(200);
|
||||
given().delete(school + "/coaches/0/dancer").then().statusCode(204);
|
||||
given().contentType("application/json")
|
||||
.body(Map.of("dancerId", -1))
|
||||
.post(school + "/coaches")
|
||||
.then()
|
||||
.statusCode(404);
|
||||
given().contentType("application/json")
|
||||
.body(Map.of("dancerId", importedDancer))
|
||||
.put(school + "/coaches/9999")
|
||||
.then()
|
||||
.statusCode(404);
|
||||
} finally {
|
||||
given().delete(school).then().statusCode(204);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package nl.agilecc.agiledancers;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import nl.agilecc.agiledancers.model.scheduling.GroupRequirement;
|
||||
import nl.agilecc.agiledancers.model.scheduling.ScheduleRules;
|
||||
import nl.agilecc.agiledancers.model.scheduling.SchedulingInput;
|
||||
import nl.agilecc.agiledancers.model.scheduling.SchedulingResult;
|
||||
import nl.agilecc.agiledancers.model.scheduling.VarietyScore;
|
||||
import nl.agilecc.agiledancers.model.specifications.DanceGroupStyle;
|
||||
import nl.agilecc.agiledancers.service.ManagementException;
|
||||
import nl.agilecc.agiledancers.solver.OrToolsSchedulingEngine;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class S18SolverTest {
|
||||
private GroupRequirement group(final long id, final int count, final boolean showcase, final long dancer) {
|
||||
return new GroupRequirement(id, count, showcase, Set.of(dancer), Set.of(), 20);
|
||||
}
|
||||
|
||||
private SchedulingResult solve(final SchedulingInput input) {
|
||||
final SchedulingResult result = new OrToolsSchedulingEngine().solve(input);
|
||||
assertTrue(ScheduleRules.violations(input, result.assignments()).isEmpty());
|
||||
assertTrue(result.optimal());
|
||||
return result;
|
||||
}
|
||||
|
||||
public @Test void showcasesBookendEveryPartAndSharedDancersHaveChangingSpace() {
|
||||
final List<GroupRequirement> groups = List.of(
|
||||
group(1, 2, true, 1),
|
||||
group(2, 2, true, 2),
|
||||
group(3, 2, true, 3),
|
||||
group(4, 1, false, 1),
|
||||
group(5, 1, false, 5),
|
||||
group(6, 1, false, 6),
|
||||
group(7, 1, false, 7));
|
||||
final SchedulingInput input = new SchedulingInput(1, 0, 2, groups, 1, Set.of());
|
||||
final SchedulingResult result = solve(input);
|
||||
for (final List<Long> part : result.assignments()) {
|
||||
assertTrue(part.getFirst() <= 3);
|
||||
assertTrue(part.getLast() <= 3);
|
||||
}
|
||||
assertEquals(
|
||||
1,
|
||||
result.assignments().stream()
|
||||
.filter((final List<Long> p) -> p.contains(4L))
|
||||
.count());
|
||||
}
|
||||
|
||||
public @Test void ordinaryDancerMustShareDaypartsAcrossTheirGroups() {
|
||||
final SchedulingInput input = new SchedulingInput(
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
List.of(group(1, 1, false, 1), group(2, 1, false, 1), group(3, 1, false, 3), group(4, 1, false, 4)),
|
||||
1,
|
||||
Set.of());
|
||||
assertThrows(ManagementException.class, () -> solve(input));
|
||||
final SchedulingInput coach = new SchedulingInput(1, 0, 2, input.groups(), 1, Set.of(1L));
|
||||
final SchedulingResult result = solve(coach);
|
||||
assertTrue(result.assignments().stream().allMatch((final List<Long> p) -> p.size() == 2));
|
||||
}
|
||||
|
||||
public @Test void showcaseExcessTakesPriorityOverPerfectBalance() {
|
||||
final SchedulingInput input = new SchedulingInput(
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
List.of(
|
||||
group(1, 2, true, 1),
|
||||
group(2, 1, false, 1),
|
||||
group(3, 1, false, 1),
|
||||
group(4, 1, false, 4),
|
||||
group(5, 1, false, 5),
|
||||
group(6, 1, false, 6),
|
||||
group(7, 1, false, 7),
|
||||
group(8, 1, false, 8),
|
||||
group(9, 1, false, 9),
|
||||
group(10, 1, false, 10),
|
||||
group(11, 1, false, 11)),
|
||||
1,
|
||||
Set.of());
|
||||
final SchedulingResult result = solve(input);
|
||||
assertTrue(result.assignments().stream().anyMatch((final List<Long> p) -> p.containsAll(List.of(2L, 3L))));
|
||||
assertEquals(
|
||||
2,
|
||||
Math.abs(result.assignments().get(0).size()
|
||||
- result.assignments().get(1).size()));
|
||||
}
|
||||
|
||||
public @Test void changingTimeAppliesToCoachesAndShowcasesWithoutExceptions() {
|
||||
final SchedulingInput input =
|
||||
new SchedulingInput(1, 0, 1, List.of(group(1, 1, true, 1), group(2, 1, false, 1)), 1, Set.of(1L));
|
||||
assertThrows(ManagementException.class, () -> solve(input));
|
||||
assertTrue(ScheduleRules.violations(input, List.of(List.of(1L, 2L))).stream()
|
||||
.anyMatch((final String s) -> s.contains("two intervening")));
|
||||
}
|
||||
|
||||
public @Test void singleShowcaseCanUseEitherBoundaryAndNoShowcaseHasNoBoundaryRule() {
|
||||
solve(new SchedulingInput(1, 0, 1, List.of(group(1, 1, true, 1), group(2, 1, false, 2)), 1, Set.of()));
|
||||
solve(new SchedulingInput(1, 0, 1, List.of(group(1, 1, false, 1)), 1, Set.of()));
|
||||
}
|
||||
|
||||
public @Test void sharedValidatorRejectsMalformedAndIncompleteDrafts() {
|
||||
final SchedulingInput input = new SchedulingInput(1, 0, 1, List.of(group(1, 1, false, 1)), 1, Set.of());
|
||||
assertFalse(ScheduleRules.violations(input, null).isEmpty());
|
||||
assertFalse(ScheduleRules.violations(input, List.of()).isEmpty());
|
||||
assertFalse(ScheduleRules.violations(input, List.of(List.of())).isEmpty());
|
||||
assertFalse(ScheduleRules.violations(input, List.of(List.of(1L, 1L))).isEmpty());
|
||||
assertFalse(ScheduleRules.violations(input, List.of(List.of(99L))).isEmpty());
|
||||
final SchedulingInput empty = new SchedulingInput(1, 0, 0, List.of(), 1, Set.of());
|
||||
assertFalse(ScheduleRules.violations(empty, List.of()).isEmpty());
|
||||
}
|
||||
|
||||
public @Test void varietyUsesStyleOverlapAndFiveYearLinearAgePenalty() {
|
||||
final GroupRequirement hiphop =
|
||||
new GroupRequirement(1, 1, false, Set.of(1L), Set.of(DanceGroupStyle.HIPHOP), 10);
|
||||
final GroupRequirement mixed = new GroupRequirement(
|
||||
2, 1, false, Set.of(2L), Set.of(DanceGroupStyle.HIPHOP, DanceGroupStyle.MODERN), 12.5);
|
||||
final GroupRequirement modern =
|
||||
new GroupRequirement(3, 1, false, Set.of(3L), Set.of(DanceGroupStyle.MODERN), 15);
|
||||
assertEquals(1_000_000, VarietyScore.penalty(hiphop, mixed));
|
||||
assertEquals(0, VarietyScore.penalty(hiphop, modern));
|
||||
final List<Long> order = solve(new SchedulingInput(1, 0, 1, List.of(hiphop, mixed, modern), 1, Set.of()))
|
||||
.assignments()
|
||||
.getFirst();
|
||||
assertEquals(1, Math.abs(order.indexOf(1L) - order.indexOf(3L)));
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,12 @@ class SchedulingTest {
|
||||
}
|
||||
|
||||
private DanceGroup group(final DanceGroupType type) {
|
||||
return service.createGroup(school, new GroupDetails("Group", type, 0, 99, Set.of()));
|
||||
final DanceGroup group = service.createGroup(school, new GroupDetails("Group", type, 0, 99, Set.of()));
|
||||
service.addGroupDancer(
|
||||
school,
|
||||
group.getId(),
|
||||
service.createDancer(school, "Dancer", LocalDate.of(2000, 1, 1)).getId());
|
||||
return group;
|
||||
}
|
||||
|
||||
private void run(final Show show) {
|
||||
@@ -86,7 +91,7 @@ class SchedulingTest {
|
||||
final Show show = service.createShow(school, "Show", List.of(), null);
|
||||
assertTrue(show.getAppearances().isEmpty());
|
||||
service.updateShow(school, show.getId(), "Show", parts(3), null);
|
||||
assertEquals(3, show.getAppearances().get(DanceGroupType.PRE_EDUCATION));
|
||||
assertEquals(2, show.getAppearances().get(DanceGroupType.PRE_EDUCATION));
|
||||
assertEquals(1, show.getAppearances().get(DanceGroupType.PRE_SCHOOLER));
|
||||
final var counts = new EnumMap<>(show.getAppearances());
|
||||
counts.put(DanceGroupType.PRE_SCHOOLER, 2);
|
||||
|
||||
@@ -171,15 +171,17 @@ class SchoolManagementResourceTest {
|
||||
final String show = school + "/shows/" + create(school + "/shows", SHOW);
|
||||
given().post(show + "/schedule").then().statusCode(400);
|
||||
final long group = create(school + "/dance-groups", GROUP);
|
||||
final long dancer = create(school + "/dancers", DANCER);
|
||||
given().put(school + "/dance-groups/" + group + "/dancers/" + dancer)
|
||||
.then()
|
||||
.statusCode(204);
|
||||
given().post(show + "/schedule").then().statusCode(200).body("optimal", equalTo(true));
|
||||
given().get(show).then().body("dayParts[0].danceGroupIds", contains((int) group));
|
||||
final String before = given().get(show).asString();
|
||||
final String counts =
|
||||
"{\"SHOW_CASE\":1,\"PRE_EDUCATION\":1,\"SELECTION\":1,\"REGULAR\":%s,\"PRE_SCHOOLER\":1}";
|
||||
for (final String count : new String[] {"0", "2", "1.5", "null"}) {
|
||||
for (final String count : new String[] {"0", "2", "1.5", "7"}) {
|
||||
final String body =
|
||||
"{\"name\":\"Show\",\"dayParts\":[{\"date\":\"2027-06-12\",\"type\":\"AFTERNOON\"}],\"appearances\":"
|
||||
+ counts.formatted(count) + "}";
|
||||
"{\"name\":\"Show\",\"dayParts\":[{\"date\":\"2027-06-12\",\"type\":\"AFTERNOON\"}],\"preferredDayParts\":"
|
||||
+ count + "}";
|
||||
put(show, body).statusCode(400);
|
||||
given().get(show).then().body(equalTo(before));
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class SchoolSnapshotResourceTest {
|
||||
.then()
|
||||
.statusCode(200)
|
||||
.header("Content-Disposition", containsString(".json"))
|
||||
.body("formatVersion", equalTo(2))
|
||||
.body("formatVersion", equalTo(3))
|
||||
.extract()
|
||||
.asString();
|
||||
try {
|
||||
@@ -58,7 +58,7 @@ class SchoolSnapshotResourceTest {
|
||||
for (final String bad : new String[] {
|
||||
"{",
|
||||
"null",
|
||||
json.replace("\"formatVersion\" : 2", "\"formatVersion\" : 1"),
|
||||
json.replace("\"formatVersion\" : 3", "\"formatVersion\" : 1"),
|
||||
json.replace("\"name\" :", "\"unknown\" :"),
|
||||
json + " {}",
|
||||
json.replace("\"dancers\" : [ ]", "\"dancers\" : null")
|
||||
|
||||
@@ -58,7 +58,7 @@ class SnapshotCollectionsTest {
|
||||
@Test
|
||||
public void requestCollectionsRetainTheirJsonValuesAndCannotBeMutatedThroughAliases() {
|
||||
final Map<DanceGroupType, Integer> counts = new LinkedHashMap<>(Map.of(DanceGroupType.REGULAR, 1));
|
||||
final ShowRequest show = new ShowRequest("Show", List.of(), counts);
|
||||
final ShowRequest show = new ShowRequest("Show", List.of(), counts, null);
|
||||
counts.clear();
|
||||
assertEquals(1, show.appearances().get(DanceGroupType.REGULAR));
|
||||
assertThrows(
|
||||
|
||||
@@ -29,7 +29,7 @@ class SchedulingServiceTest {
|
||||
public @Test void failedAndNoResultRunsPreserveAssignmentsAndReleaseTheRunGuard() {
|
||||
final var schools = new SchoolManagementService();
|
||||
final long school = schools.createSchool("School").getId();
|
||||
schools.createGroup(school, new GroupDetails("Group", DanceGroupType.REGULAR, 0, 99, Set.of()));
|
||||
populatedGroup(schools, school);
|
||||
final Show show = schools.createShow(
|
||||
school,
|
||||
"Show",
|
||||
@@ -62,7 +62,7 @@ class SchedulingServiceTest {
|
||||
public @Test void concurrentRunIsRejectedWhileEditsRemainPossibleAndInvalidateTheFirstResult() throws Exception {
|
||||
final var schools = new SchoolManagementService();
|
||||
final long school = schools.createSchool("School").getId();
|
||||
schools.createGroup(school, new GroupDetails("Group", DanceGroupType.REGULAR, 0, 99, Set.of()));
|
||||
populatedGroup(schools, school);
|
||||
final Show show = schools.createShow(
|
||||
school,
|
||||
"Show",
|
||||
@@ -84,7 +84,7 @@ class SchedulingServiceTest {
|
||||
ManagementExceptionKind.CONFLICT,
|
||||
assertThrows(ManagementException.class, () -> scheduling.schedule(school, show.getId()))
|
||||
.getKind());
|
||||
schools.createGroup(school, new GroupDetails("New group", DanceGroupType.REGULAR, 0, 99, Set.of()));
|
||||
populatedGroup(schools, school);
|
||||
} finally {
|
||||
release.countDown();
|
||||
}
|
||||
@@ -98,6 +98,16 @@ class SchedulingServiceTest {
|
||||
assertEquals(2, show.getDayParts().getFirst().getDanceGroups().size());
|
||||
}
|
||||
|
||||
private static void populatedGroup(final SchoolManagementService schools, final long school) {
|
||||
final long group = schools.createGroup(
|
||||
school, new GroupDetails("Group", DanceGroupType.REGULAR, 0, 99, Set.of()))
|
||||
.getId();
|
||||
schools.addGroupDancer(
|
||||
school,
|
||||
group,
|
||||
schools.createDancer(school, "Dancer", LocalDate.of(2000, 1, 1)).getId());
|
||||
}
|
||||
|
||||
private static SchedulingEngine blockingEngine(final CountDownLatch entered, final CountDownLatch release) {
|
||||
final SchedulingEngine engine = (final SchedulingInput input) -> {
|
||||
entered.countDown();
|
||||
|
||||
Reference in New Issue
Block a user