Skip to content

Commit

Permalink
Handle brute-forced deletion of timer in the holder
Browse files Browse the repository at this point in the history
  • Loading branch information
tedyoung committed Apr 24, 2024
1 parent 9ab9e5e commit a180026
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@ public EnsembleTimerLifecycleController(EnsembleTimerHolder ensembleTimerHolder)
}

@PostMapping("/create-timer/{ensembleId}")
public String createTimerView(@PathVariable("ensembleId") Long id) {
public String createTimer(@PathVariable("ensembleId") Long id) {
ensembleTimerHolder.createTimerFor(EnsembleId.of(id), shuffler);
return "redirect:/member/timer-view/" + id;
}

@PostMapping("/delete-timer/{ensembleId}")
@ResponseBody
public String deleteTimerView(@PathVariable("ensembleId") Long id) {
return "";
public String deleteTimer(@PathVariable("ensembleId") Long id) {
ensembleTimerHolder.deleteTimer(EnsembleId.of(id));
return timerState(id);
}

@GetMapping("/ensemble-timer-state/{ensembleId}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ public void resumeTimerFor(EnsembleId ensembleId) {
broadcaster.sendCurrentTimer(ensembleTimer);
}

public void deleteTimer(EnsembleId ensembleId) {
ensembleTimers.remove(ensembleId);
}

static class SingleEntryHashMap<K, V> extends HashMap<K, V> {
@Override
public V put(K key, V value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,49 @@
@SuppressWarnings("unchecked")
class EnsembleTimerLifecycleControllerTest {

@Test
void createAndRedirectToTimerSessionForSpecificEnsemble() {
Ensemble ensemble = new EnsembleBuilder().id(87)
.startsNow()
.build();
TestEnsembleServiceBuilder builder = new TestEnsembleServiceBuilder()
.saveEnsemble(ensemble)
.withThreeParticipants();
EnsembleTimerHolder ensembleTimerHolder = EnsembleTimerHolder.createNull(builder.ensembleRepository(), builder.memberRepository());
EnsembleTimerLifecycleController ensembleTimerController = new EnsembleTimerLifecycleController(ensembleTimerHolder);

String redirectPage = ensembleTimerController.createTimerView(87L);

assertThat(redirectPage)
.isEqualTo("redirect:/member/timer-view/87");
assertThat(ensembleTimerHolder.hasTimerFor(EnsembleId.of(87)))
.isTrue();
@Nested
class Lifecycle {
@Test
void createAndRedirectToTimerSessionForSpecificEnsemble() {
Fixture fixture = createEnsembleAndTimerHolder(87);

String redirectPage = fixture.ensembleTimerController().createTimer(87L);

assertThat(redirectPage)
.isEqualTo("redirect:/member/timer-view/87");
assertThat(fixture.ensembleTimerHolder().hasTimerFor(EnsembleId.of(87)))
.isTrue();
}

@Test
void updatedTimerStateHtmlReturnedWhenExistingTimerDeleted() {
Fixture fixture = createEnsembleAndTimerHolder(135);
fixture.ensembleTimerHolder().createTimerFor(EnsembleId.of(135), new NoOpShuffler());

String actualHtml = fixture.ensembleTimerController().deleteTimer(135L);

assertThat(fixture.ensembleTimerHolder().hasTimerFor(EnsembleId.of(135)))
.isFalse();
assertThat(actualHtml)
.contains("Create Timer");
}

private Fixture createEnsembleAndTimerHolder(int id) {
Ensemble ensemble = new EnsembleBuilder().id(id)
.startsNow()
.build();
TestEnsembleServiceBuilder builder = new TestEnsembleServiceBuilder()
.saveEnsemble(ensemble)
.withThreeParticipants();
EnsembleTimerHolder ensembleTimerHolder = EnsembleTimerHolder.createNull(builder.ensembleRepository(), builder.memberRepository());
EnsembleTimerLifecycleController ensembleTimerController = new EnsembleTimerLifecycleController(ensembleTimerHolder);
return new Fixture(ensembleTimerHolder, ensembleTimerController);
}

private record Fixture(EnsembleTimerHolder ensembleTimerHolder,
EnsembleTimerLifecycleController ensembleTimerController) {
}

}

@Nested
Expand All @@ -43,18 +69,18 @@ void returnsOnlyCreateButtonWhenNoTimerExists() {
String actualHtml = ensembleTimerController.timerState(109L);

String expectedHtml = """
<swap id="timer-status-container" hx-swap-oob="innerHTML">
<p>No timer currently exists.</p>
</swap>
<swap id="timer-button-container" hx-swap-oob="innerHTML">
<form action="/admin/create-timer/109" method="post">
<button class="inline-flex items-center rounded-md bg-indigo-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500"
>
Create Timer
</button>
</form>
</swap>
""";
<swap id="timer-status-container" hx-swap-oob="innerHTML">
<p>No timer currently exists.</p>
</swap>
<swap id="timer-button-container" hx-swap-oob="innerHTML">
<form action="/admin/create-timer/109" method="post">
<button class="inline-flex items-center rounded-md bg-indigo-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500"
>
Create Timer
</button>
</form>
</swap>
""";
assertThat(actualHtml)
.isEqualTo(expectedHtml);
}
Expand All @@ -70,30 +96,28 @@ private static EnsembleTimerLifecycleController createEnsembleAndTimerHolder(int
return new EnsembleTimerLifecycleController(ensembleTimerHolder);
}

// timer exists for THIS ensemble: no CREATE button, only DELETE button

@Test
void returnsOnlyDeleteButtonAndLinkToTimerWhenTimerExistsForThisEnsemble() {
EnsembleTimerLifecycleController ensembleTimerController = createEnsembleAndTimerHolder(362);
ensembleTimerController.createTimerView(362L);
ensembleTimerController.createTimer(362L);

String actualHtml = ensembleTimerController.timerState(362L);

String expectedHtml = """
<swap id="timer-status-container" hx-swap-oob="innerHTML">
<p>Timer exists and can be seen
<a class="underline font-semibold text-blue-600"
href="/member/timer-view/362">here</a>.
</p>
</swap>
<swap id="timer-button-container" hx-swap-oob="innerHTML">
<button class="inline-flex justify-center rounded-md border border-transparent bg-red-500 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-300 focus:ring-offset-2"
hx-post="/admin/delete-timer/362"
>
Delete Timer
</button>
</swap>
""";
<swap id="timer-status-container" hx-swap-oob="innerHTML">
<p>Timer exists and can be seen
<a class="underline font-semibold text-blue-600"
href="/member/timer-view/362">here</a>.
</p>
</swap>
<swap id="timer-button-container" hx-swap-oob="innerHTML">
<button class="inline-flex justify-center rounded-md border border-transparent bg-red-500 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-300 focus:ring-offset-2"
hx-post="/admin/delete-timer/362"
>
Delete Timer
</button>
</swap>
""";

assertThat(actualHtml)
.isEqualTo(expectedHtml);
Expand All @@ -106,8 +130,8 @@ void noButtonsOnlyStatusWithLinkToEnsembleDetailsWhenAnotherEnsembleTimerExists(
.startsNow()
.build();
Ensemble ensemble96 = new EnsembleBuilder().id(96)
.startsNow()
.build();
.startsNow()
.build();
TestEnsembleServiceBuilder builder = new TestEnsembleServiceBuilder()
.saveEnsemble(ensemble583)
.saveEnsemble(ensemble96)
Expand All @@ -119,14 +143,14 @@ void noButtonsOnlyStatusWithLinkToEnsembleDetailsWhenAnotherEnsembleTimerExists(
String actualHtml = ensembleTimerController.timerState(583L);

String expectedHtml = """
<swap id="timer-status-container" hx-swap-oob="innerHTML">
<p>Timer exists for
<a class="underline font-semibold text-blue-600"
href="/admin/ensemble/96">another ensemble</a>.
</swap>
<swap id="timer-button-container" hx-swap-oob="innerHTML">
</swap>
""";
<swap id="timer-status-container" hx-swap-oob="innerHTML">
<p>Timer exists for
<a class="underline font-semibold text-blue-600"
href="/admin/ensemble/96">another ensemble</a>.
</swap>
<swap id="timer-button-container" hx-swap-oob="innerHTML">
</swap>
""";

assertThat(actualHtml)
.isEqualTo(expectedHtml);
Expand Down

0 comments on commit a180026

Please sign in to comment.