Rework arena sign data store.

This commit constitutes a major rewrite of how arena signs are stored
and loaded. It fixes an issue where an unloaded or missing world would
cause MobArena to throw errors if there were any arena signs recorded in
said world.

The solution is to load signs of a given world when it is available,
rather than loading all signs at once indiscriminately. At startup,
signs for all _currently available_ worlds are loaded. This fixes the
errors. When a world loads and a WorldLoadEvent is fired, signs in that
world are loaded. This ensures that all valid signs in existing worlds
will _eventually_ load. To keep things tidy, a WorldUnloadEvent will
unload all signs for the unloaded world.

Bukkit's own YAML deserialization implementation doesn't re-throw all
deserialization errors, which means we can't actually catch the problem
of missing worlds without doing an awkward "scan" of the deserialized
objects. This prompted a rewrite of the serialization and data storage
into a custom CSV-like format which is both simpler and also provides a
lot more control over the process. Instead of relying on world _names_,
the new format uses world _UUIDs_. While the rest of the plugin won't
necessarily adapt well to a world being renamed, the signs data store
should be resilient enough to handle it.

Most of the actual sign rendering code and almost all of the template
code is intact, but quite a lot of other classes have been rewritten or
replaced. Some of the rewrites weren't strictly necessary, but because
the components were already fairly small, rewrites were much faster and
a lot less awkward than attempting to adapt existing code.

Fixes #645
This commit is contained in:
Andreas Troelsen
2021-03-25 10:20:49 +01:00
parent cf296704b0
commit 0da90f3963
48 changed files with 2714 additions and 1015 deletions
@@ -1,57 +0,0 @@
package com.garbagemule.MobArena.signs;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import org.bukkit.Location;
import org.bukkit.World;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.HashMap;
import java.util.Map;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class ArenaSignTest {
@Test
public void serialize() {
World world = mock(World.class);
Location location = new Location(world, 1, 2, 3);
String templateId = "a good template";
String arenaId = "cool arena";
String type = "join";
ArenaSign sign = new ArenaSign(location, templateId, arenaId, type);
Map<String, Object> result = sign.serialize();
assertThat(result.get("location"), equalTo(location));
assertThat(result.get("templateId"), equalTo(templateId));
assertThat(result.get("arenaId"), equalTo(arenaId));
assertThat(result.get("type"), equalTo(type));
}
@Test
public void deserialize() {
World world = mock(World.class);
Location location = new Location(world, 1, 2, 3);
String templateId = "a good template";
String arenaId = "cool arena";
String type = "join";
Map<String, Object> map = new HashMap<>();
map.put("location", location);
map.put("templateId", templateId);
map.put("arenaId", arenaId);
map.put("type", type);
ArenaSign result = ArenaSign.deserialize(map);
assertThat(result.location, is(equalTo(location)));
assertThat(result.templateId, is(equalTo(templateId)));
assertThat(result.arenaId, is(equalTo(arenaId)));
assertThat(result.type, is(equalTo(type)));
}
}
@@ -1,7 +1,5 @@
package com.garbagemule.MobArena.signs;
import static org.mockito.Mockito.*;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.Chest;
@@ -13,7 +11,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Optional;
import static org.mockito.Mockito.*;
@SuppressWarnings("WeakerAccess")
@RunWith(MockitoJUnitRunner.StrictStubs.class)
@@ -27,8 +25,6 @@ public class HandlesSignClicksTest {
@Before
public void setup() {
signStore = mock(SignStore.class);
when(signStore.findByLocation(any()))
.thenReturn(Optional.empty());
invokesSignAction = mock(InvokesSignAction.class);
subject = new HandlesSignClicks(signStore, invokesSignAction);
@@ -40,7 +36,7 @@ public class HandlesSignClicksTest {
subject.on(event);
verifyZeroInteractions(signStore);
verifyNoInteractions(signStore, invokesSignAction);
}
@Test
@@ -51,18 +47,19 @@ public class HandlesSignClicksTest {
subject.on(event);
verifyZeroInteractions(signStore);
verifyNoInteractions(signStore, invokesSignAction);
}
@Test
public void nonArenaSignNoFun() {
Block block = mock(Block.class);
when(block.getState()).thenReturn(mock(Sign.class));
when(signStore.findByLocation(any())).thenReturn(null);
PlayerInteractEvent event = event(null, block);
subject.on(event);
verifyZeroInteractions(signStore);
verifyNoInteractions(invokesSignAction);
}
@Test
@@ -72,8 +69,7 @@ public class HandlesSignClicksTest {
when(block.getLocation()).thenReturn(location);
when(block.getState()).thenReturn(mock(Sign.class));
ArenaSign sign = new ArenaSign(location, "", "", "");
when(signStore.findByLocation(location))
.thenReturn(Optional.of(sign));
when(signStore.findByLocation(location)).thenReturn(sign);
Player player = mock(Player.class);
PlayerInteractEvent event = event(player, block);
@@ -1,10 +1,8 @@
package com.garbagemule.MobArena.signs;
import static org.mockito.Mockito.*;
import com.garbagemule.MobArena.Messenger;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.block.SignChangeEvent;
import org.junit.Before;
@@ -12,135 +10,139 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@SuppressWarnings("WeakerAccess")
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class HandlesSignCreationTest {
StoresNewSign storesNewSign;
RendersTemplateById rendersTemplate;
SignCreator creator;
SignWriter writer;
SignStore store;
SignRenderer renderer;
Messenger messenger;
Logger log;
HandlesSignCreation subject;
@Before
public void setup() {
storesNewSign = mock(StoresNewSign.class);
rendersTemplate = mock(RendersTemplateById.class);
when(rendersTemplate.render(any(), any()))
.thenReturn(new String[]{"", "", "", ""});
creator = mock(SignCreator.class);
writer = mock(SignWriter.class);
store = mock(SignStore.class);
renderer = mock(SignRenderer.class);
messenger = mock(Messenger.class);
log = mock(Logger.class);
subject = new HandlesSignCreation(
storesNewSign,
rendersTemplate,
messenger
creator,
writer,
store,
renderer,
messenger,
log
);
}
@Test
public void noHeaderNoAction() {
String[] lines = {"why", "so", "serious", "?"};
SignChangeEvent event = event(lines, null);
public void noSignCreationNoAction() {
SignChangeEvent event = new SignChangeEvent(null, null, null);
when(creator.create(event)).thenReturn(null);
subject.on(event);
verifyZeroInteractions(storesNewSign, rendersTemplate, messenger);
verifyNoInteractions(writer, store, messenger, log);
}
@Test
public void nullLinesHandledGracefully() {
String[] lines = {"[MA]", null, null, null};
SignChangeEvent event = event(lines, null);
subject.on(event);
}
@Test
public void useSignTypeIfTemplateNotAvailable() {
String arenaId = "castle";
String type = "join";
String[] lines = {"[MA]", arenaId, type, null};
Location location = mock(Location.class);
SignChangeEvent event = event(lines, location);
subject.on(event);
verify(storesNewSign).store(location, arenaId, type, type);
}
@Test
public void useTemplateIfAvailable() {
String arenaId = "castle";
String type = "join";
String templateId = "potato";
String[] lines = {"[MA]", arenaId, type, templateId};
Location location = mock(Location.class);
SignChangeEvent event = event(lines, location);
subject.on(event);
verify(storesNewSign).store(location, arenaId, templateId, type);
}
@Test
public void typeIsLowercased() {
String type = "JOIN";
String[] lines = {"[MA]", "", type, ""};
SignChangeEvent event = event(lines, null);
subject.on(event);
String lower = type.toLowerCase();
verify(storesNewSign).store(any(), any(), any(), eq(lower));
}
@Test
public void templateIdIsLowercased() {
String templateId = "BEST-TEMPLATE";
String[] lines = {"[MA]", "", "", templateId};
SignChangeEvent event = event(lines, null);
subject.on(event);
String lower = templateId.toLowerCase();
verify(storesNewSign).store(any(), any(), eq(lower), any());
}
@Test
public void rendersTemplateAfterStoring() {
String arenaId = "castle";
String type = "join";
String templateId = "potato";
String[] lines = {"[MA]", arenaId, type, templateId};
Location location = mock(Location.class);
SignChangeEvent event = event(lines, location);
subject.on(event);
verify(rendersTemplate).render(templateId, arenaId);
}
@Test
public void errorPassedToMessenger() {
String msg = "you messed up";
doThrow(new IllegalArgumentException(msg))
.when(storesNewSign).store(any(), any(), any(), any());
String[] lines = {"[MA]", "", "", ""};
SignChangeEvent event = event(lines, null);
subject.on(event);
verifyZeroInteractions(rendersTemplate);
verify(messenger).tell(event.getPlayer(), msg);
}
private SignChangeEvent event(String[] lines, Location location) {
Block block = mock(Block.class);
when(block.getLocation()).thenReturn(location);
public void passesSignFromCreator() throws Exception {
Player player = mock(Player.class);
return new SignChangeEvent(block, player, lines);
SignChangeEvent event = new SignChangeEvent(null, player, null);
ArenaSign sign = new ArenaSign(location(), null, null, null);
when(creator.create(event)).thenReturn(sign);
subject.on(event);
verify(writer).write(sign);
verify(store).add(sign);
verify(renderer).render(sign, event);
}
@Test
public void successMessageOnCreation() {
Player player = mock(Player.class);
SignChangeEvent event = new SignChangeEvent(null, player, null);
ArenaSign sign = new ArenaSign(location(), null, "castle", "join");
when(creator.create(event)).thenReturn(sign);
subject.on(event);
verify(messenger).tell(eq(player), anyString());
verify(log).info(anyString());
}
@Test
public void noWriteIfCreatorThrows() {
SignChangeEvent event = new SignChangeEvent(null, null, null);
doThrow(IllegalArgumentException.class).when(creator).create(event);
subject.on(event);
verifyNoInteractions(writer, store, renderer, log);
}
@Test
public void errorMessageIfCreatorThrows() {
Player player = mock(Player.class);
SignChangeEvent event = new SignChangeEvent(null, player, null);
String message = "it's bad";
doThrow(new IllegalArgumentException(message)).when(creator).create(event);
subject.on(event);
verify(messenger).tell(player, message);
}
@Test
public void noStorageIfWriterThrows() throws Exception {
SignChangeEvent event = new SignChangeEvent(null, null, null);
ArenaSign sign = new ArenaSign(null, null, null, null);
when(creator.create(event)).thenReturn(sign);
doThrow(IOException.class).when(writer).write(sign);
subject.on(event);
verifyNoInteractions(store, renderer);
}
@Test
public void errorMessageIfWriterThrows() throws Exception {
Player player = mock(Player.class);
SignChangeEvent event = new SignChangeEvent(null, player, null);
ArenaSign sign = new ArenaSign(null, null, null, null);
when(creator.create(event)).thenReturn(sign);
IOException exception = new IOException("it's bad");
doThrow(exception).when(writer).write(sign);
subject.on(event);
verify(messenger).tell(eq(player), anyString());
verify(log).log(eq(Level.SEVERE), anyString(), eq(exception));
}
private Location location() {
World world = mock(World.class);
when(world.getName()).thenReturn("world");
Location location = mock(Location.class);
when(location.getBlockX()).thenReturn(1);
when(location.getBlockY()).thenReturn(2);
when(location.getBlockZ()).thenReturn(3);
when(location.getWorld()).thenReturn(world);
return location;
}
}
@@ -1,8 +1,8 @@
package com.garbagemule.MobArena.signs;
import static org.mockito.Mockito.*;
import com.garbagemule.MobArena.Messenger;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.block.BlockBreakEvent;
@@ -11,53 +11,109 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Optional;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@SuppressWarnings("WeakerAccess")
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class HandlesSignDestructionTest {
RemovesSignAtLocation removesSignAtLocation;
SignStore store;
SignWriter writer;
Messenger messenger;
Logger log;
HandlesSignDestruction subject;
@Before
public void setup() {
removesSignAtLocation = mock(RemovesSignAtLocation.class);
store = mock(SignStore.class);
writer = mock(SignWriter.class);
messenger = mock(Messenger.class);
log = mock(Logger.class);
subject = new HandlesSignDestruction(
removesSignAtLocation,
messenger
store,
writer,
messenger,
log
);
}
@Test
public void doesNothingWithNonArenaSign() {
public void noSignNoAction() {
Location location = mock(Location.class);
Block block = mock(Block.class);
BlockBreakEvent event = new BlockBreakEvent(block, null);
when(block.getLocation()).thenReturn(location);
when(store.removeByLocation(location)).thenReturn(null);
subject.on(event);
verifyNoInteractions(writer, messenger, log);
}
@Test
public void erasesFoundSign() throws IOException {
Location location = location();
Block block = mock(Block.class);
Player player = mock(Player.class);
when(removesSignAtLocation.remove(any()))
.thenReturn(Optional.empty());
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
when(block.getLocation()).thenReturn(location);
when(store.removeByLocation(location)).thenReturn(sign);
BlockBreakEvent event = new BlockBreakEvent(block, player);
subject.on(event);
verifyZeroInteractions(messenger);
verify(writer).erase(sign);
}
@Test
public void reportsBreakageWithArenaSign() {
public void successMessageOnDestruction() {
Location location = location();
Block block = mock(Block.class);
Player player = mock(Player.class);
ArenaSign sign = new ArenaSign(null, "", "", "");
when(removesSignAtLocation.remove(any()))
.thenReturn(Optional.of(sign));
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
when(block.getLocation()).thenReturn(location);
when(store.removeByLocation(location)).thenReturn(sign);
BlockBreakEvent event = new BlockBreakEvent(block, player);
subject.on(event);
verify(messenger).tell(eq(player), anyString());
verify(log).info(anyString());
}
@Test
public void errorMessageIfWriterThrows() throws Exception {
Location location = mock(Location.class);
Block block = mock(Block.class);
Player player = mock(Player.class);
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
IOException exception = new IOException("it's bad");
when(block.getLocation()).thenReturn(location);
when(store.removeByLocation(location)).thenReturn(sign);
doThrow(exception).when(writer).erase(sign);
BlockBreakEvent event = new BlockBreakEvent(block, player);
subject.on(event);
verify(messenger).tell(eq(player), anyString());
verify(log).log(eq(Level.SEVERE), anyString(), eq(exception));
}
private Location location() {
World world = mock(World.class);
when(world.getName()).thenReturn("world");
Location location = mock(Location.class);
when(location.getBlockX()).thenReturn(1);
when(location.getBlockY()).thenReturn(2);
when(location.getBlockZ()).thenReturn(3);
when(location.getWorld()).thenReturn(world);
return location;
}
}
@@ -0,0 +1,122 @@
package com.garbagemule.MobArena.signs;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.event.world.WorldLoadEvent;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.IOException;
import java.util.Collections;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class HandlesWorldLoadTest {
SignDataMigrator migrator;
SignReader reader;
SignStore store;
Logger log;
HandlesWorldLoad subject;
@Before
public void setup() {
migrator = mock(SignDataMigrator.class);
reader = mock(SignReader.class);
store = mock(SignStore.class);
log = mock(Logger.class);
subject = new HandlesWorldLoad(
migrator,
reader,
store,
log
);
}
@Test
public void errorMessageIfDataMigrationThrows() throws IOException {
World world = mock(World.class);
when(world.getName()).thenReturn("world");
IOException exception = new IOException("it's bad");
doThrow(exception).when(migrator).migrate(world);
WorldLoadEvent event = new WorldLoadEvent(world);
subject.on(event);
verify(log).log(eq(Level.SEVERE), anyString(), eq(exception));
}
@Test
public void addsLoadedSignsToStore() throws IOException {
World world = mock(World.class);
when(world.getName()).thenReturn("world");
when(world.getUID()).thenReturn(UUID.fromString("cafebabe-ea75-dead-beef-deadcafebabe"));
Location location = mock(Location.class);
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
when(reader.read(world)).thenReturn(Collections.singletonList(sign));
WorldLoadEvent event = new WorldLoadEvent(world);
subject.on(event);
verify(store).add(sign);
}
@Test
public void logsMessageOnSignAddition() throws IOException {
World world = mock(World.class);
when(world.getName()).thenReturn("world");
when(world.getUID()).thenReturn(UUID.fromString("cafebabe-ea75-dead-beef-deadcafebabe"));
Location location = mock(Location.class);
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
when(reader.read(world)).thenReturn(Collections.singletonList(sign));
WorldLoadEvent event = new WorldLoadEvent(world);
subject.on(event);
verify(log).info(anyString());
}
@Test
public void addsNothingIfNoSignsLoaded() throws IOException {
World world = mock(World.class);
when(reader.read(world)).thenReturn(Collections.emptyList());
WorldLoadEvent event = new WorldLoadEvent(world);
subject.on(event);
verifyNoInteractions(store);
}
@Test
public void logsNothingIfNoSignsLoaded() throws IOException {
World world = mock(World.class);
when(reader.read(world)).thenReturn(Collections.emptyList());
WorldLoadEvent event = new WorldLoadEvent(world);
subject.on(event);
verifyNoInteractions(log);
}
@Test
public void errorMessageIfReaderThrows() throws IOException {
World world = mock(World.class);
IOException exception = new IOException("it's bad");
when(reader.read(world)).thenThrow(exception);
WorldLoadEvent event = new WorldLoadEvent(world);
subject.on(event);
verify(log).log(eq(Level.SEVERE), anyString(), eq(exception));
}
}
@@ -0,0 +1,65 @@
package com.garbagemule.MobArena.signs;
import org.bukkit.World;
import org.bukkit.event.world.WorldUnloadEvent;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.logging.Logger;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class HandlesWorldUnloadTest {
SignStore store;
Logger log;
HandlesWorldUnload subject;
@Before
public void setup() {
store = mock(SignStore.class);
log = mock(Logger.class);
subject = new HandlesWorldUnload(
store,
log
);
}
@Test
public void logsMessagesOnSignRemoval() {
UUID id = UUID.fromString("cafebabe-ea75-dead-beef-deadcafebabe");
String name = "world";
World world = mock(World.class);
when(world.getUID()).thenReturn(id);
when(world.getName()).thenReturn(name);
List<ArenaSign> signs = Arrays.asList(null, null);
when(store.removeByWorld(world)).thenReturn(signs);
WorldUnloadEvent event = new WorldUnloadEvent(world);
subject.on(event);
verify(log).info(anyString());
}
@Test
public void logsNothingIfNoSignsRemoved() {
World world = mock(World.class);
when(store.removeByWorld(world)).thenReturn(Collections.emptyList());
WorldUnloadEvent event = new WorldUnloadEvent(world);
subject.on(event);
verifyNoInteractions(log);
}
}
@@ -1,7 +1,5 @@
package com.garbagemule.MobArena.signs;
import static org.mockito.Mockito.*;
import com.garbagemule.MobArena.Messenger;
import com.garbagemule.MobArena.framework.Arena;
import com.garbagemule.MobArena.framework.ArenaMaster;
@@ -11,6 +9,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.*;
@SuppressWarnings("WeakerAccess")
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class InvokesSignActionTest {
@@ -36,7 +36,7 @@ public class InvokesSignActionTest {
subject.invoke(sign, player);
verifyZeroInteractions(arenaMaster);
verifyNoInteractions(arenaMaster);
}
@Test
@@ -1,141 +0,0 @@
package com.garbagemule.MobArena.signs;
import static org.mockito.Mockito.*;
import com.garbagemule.MobArena.framework.Arena;
import org.bukkit.Location;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@SuppressWarnings("WeakerAccess")
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class RedrawsArenaSignsTest {
SignStore signStore;
TemplateStore templateStore;
RendersTemplate rendersTemplate;
SetsLines setsSignLines;
RedrawsArenaSigns subject;
@Before
public void setup() {
signStore = mock(SignStore.class);
when(signStore.findByArenaId(any()))
.thenReturn(Collections.emptyList());
templateStore = mock(TemplateStore.class);
when(templateStore.findById(any()))
.thenReturn(Optional.empty());
rendersTemplate = mock(RendersTemplate.class);
when(rendersTemplate.render(any(), any()))
.thenReturn(new String[]{"a", "b", "c", "d"});
setsSignLines = mock(SetsLines.class);
subject = new RedrawsArenaSigns(
signStore,
templateStore,
rendersTemplate,
setsSignLines
);
}
@Test
public void noSignsMeansNoRenderingOrLineSetting() {
Arena arena = arena("castle");
subject.redraw(arena);
verifyZeroInteractions(rendersTemplate);
verifyZeroInteractions(setsSignLines);
}
@Test
public void renderFoundTemplate() {
String arenaId = "castle";
Arena arena = arena(arenaId);
ArenaSign sign = sign("join", arenaId);
when(signStore.findByArenaId(arenaId))
.thenReturn(Collections.singletonList(sign));
Template template = template("template", "some", "info", "about", "arena");
when(templateStore.findById(sign.templateId))
.thenReturn(Optional.of(template));
subject.redraw(arena);
verify(rendersTemplate).render(template, arena);
}
@Test
public void setRenderedTemplateOnSign() {
String arenaId = "castle";
Arena arena = arena(arenaId);
ArenaSign sign = sign("join", arenaId);
when(signStore.findByArenaId(sign.arenaId))
.thenReturn(Collections.singletonList(sign));
Template template = template("template", "try", "with", "more", "fireballs");
when(templateStore.findById(sign.templateId))
.thenReturn(Optional.of(template));
String[] lines = new String[]{"this", "is", "a", "sign"};
when(rendersTemplate.render(template, arena))
.thenReturn(lines);
subject.redraw(arena);
verify(setsSignLines).set(sign.location, lines);
}
@Test
public void renderEachTemplateOnlyOnce() {
String arenaId = "castle";
Arena arena = arena(arenaId);
String templateId1 = "join";
String templateId2 = "info";
List<ArenaSign> signs = new ArrayList<>();
signs.add(sign(templateId1, arenaId));
signs.add(sign(templateId1, arenaId));
signs.add(sign(templateId1, arenaId));
signs.add(sign(templateId2, arenaId));
when(signStore.findByArenaId(arenaId))
.thenReturn(signs);
Template template1 = template(templateId1, "join", "a", "MobArena", "today!");
Template template2 = template(templateId2, "join", "another", "MobArena", "tomorrow!");
when(templateStore.findById(templateId1))
.thenReturn(Optional.of(template1));
when(templateStore.findById(templateId2))
.thenReturn(Optional.of(template2));
subject.redraw(arena);
verify(rendersTemplate, times(1)).render(template1, arena);
verify(rendersTemplate, times(1)).render(template2, arena);
verify(setsSignLines, times(signs.size())).set(any(), any());
}
private Arena arena(String arenaId) {
Arena arena = mock(Arena.class);
when(arena.configName()).thenReturn(arenaId);
return arena;
}
private ArenaSign sign(String templateId, String arenaId) {
Location location = mock(Location.class);
return new ArenaSign(location, templateId, arenaId, "join");
}
private Template template(String id, String l1, String l2, String l3, String l4) {
return new Template.Builder(id)
.withBase(new String[]{l1, l2, l3, l4})
.build();
}
}
@@ -1,56 +0,0 @@
package com.garbagemule.MobArena.signs;
import static org.mockito.Mockito.*;
import org.bukkit.Location;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Optional;
@SuppressWarnings("WeakerAccess")
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class RemovesSignAtLocationTest {
SignStore signStore;
SavesSignStore savesSignStore;
RemovesSignAtLocation subject;
@Before
public void setup() {
signStore = mock(SignStore.class);
savesSignStore = mock(SavesSignStore.class);
subject = new RemovesSignAtLocation(
signStore,
savesSignStore
);
}
@Test
public void noSignMeansNoWrite() {
Location location = mock(Location.class);
when(signStore.remove(location))
.thenReturn(Optional.empty());
subject.remove(location);
verifyZeroInteractions(savesSignStore);
}
@Test
public void signRemovedWritesStore() {
Location location = mock(Location.class);
ArenaSign sign = new ArenaSign(location, "", "", "");
when(signStore.remove(location))
.thenReturn(Optional.of(sign));
subject.remove(location);
verify(savesSignStore).save(signStore);
}
}
@@ -1,9 +1,5 @@
package com.garbagemule.MobArena.signs;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import com.garbagemule.MobArena.framework.Arena;
import com.garbagemule.MobArena.waves.WaveManager;
import org.bukkit.entity.Player;
@@ -14,6 +10,10 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.util.Collections;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
@SuppressWarnings("WeakerAccess")
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class RendersTemplateTest {
@@ -0,0 +1,211 @@
package com.garbagemule.MobArena.signs;
import com.garbagemule.MobArena.framework.Arena;
import com.garbagemule.MobArena.framework.ArenaMaster;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.block.SignChangeEvent;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Optional;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.*;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class SignCreatorTest {
ArenaMaster arenaMaster;
TemplateStore templateStore;
SignCreator subject;
@Before
public void setup() {
arenaMaster = mock(ArenaMaster.class);
templateStore = mock(TemplateStore.class);
subject = new SignCreator(
arenaMaster,
templateStore
);
}
@Test
public void noHeaderNoAction() {
String[] lines = {"ma", "castle", "join", "cool-sign"};
SignChangeEvent event = new SignChangeEvent(null, null, lines);
ArenaSign result = subject.create(event);
assertThat(result, nullValue());
verifyNoInteractions(arenaMaster, templateStore);
}
@Test
public void throwsOnMissingArena() {
String[] lines = {"[MA]", null, "join", "cool-sign"};
SignChangeEvent event = event(lines, null);
assertThrows(
IllegalArgumentException.class,
() -> subject.create(event)
);
verifyNoInteractions(arenaMaster);
}
@Test
public void throwsIfArenaNotFound() {
String arenaId = "castle";
String[] lines = {"[MA]", arenaId, "join", "cool-sign"};
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(null);
SignChangeEvent event = event(lines, null);
assertThrows(
IllegalArgumentException.class,
() -> subject.create(event)
);
}
@Test
public void throwsOnMissingType() {
String arenaId = "castle";
String[] lines = {"[MA]", arenaId, null, "cool-sign"};
Arena arena = mock(Arena.class);
when(arena.getSlug()).thenReturn(arenaId);
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
SignChangeEvent event = event(lines, null);
assertThrows(
IllegalArgumentException.class,
() -> subject.create(event)
);
}
@Test
public void throwsOnInvalidType() {
String arenaId = "castle";
String[] lines = {"[MA]", arenaId, "bob", "cool-sign"};
Arena arena = mock(Arena.class);
when(arena.getSlug()).thenReturn(arenaId);
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
SignChangeEvent event = event(lines, null);
assertThrows(
IllegalArgumentException.class,
() -> subject.create(event)
);
}
@Test
public void throwsIfTemplateNotFound() {
String arenaId = "castle";
String templateId = "cool-sign";
String[] lines = {"[MA]", arenaId, "join", templateId};
Arena arena = mock(Arena.class);
when(arena.getSlug()).thenReturn(arenaId);
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
when(templateStore.findById(templateId)).thenReturn(Optional.empty());
SignChangeEvent event = event(lines, null);
assertThrows(
IllegalArgumentException.class,
() -> subject.create(event)
);
}
@Test
public void completeSignDefinition() {
String arenaId = "castle";
String signType = "join";
String templateId = "cool-sign";
String[] lines = {"[MA]", arenaId, signType, templateId};
Location location = mock(Location.class);
Arena arena = mock(Arena.class);
when(arena.getSlug()).thenReturn(arenaId);
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
when(templateStore.findById(templateId)).thenReturn(Optional.of(mock(Template.class)));
SignChangeEvent event = event(lines, location);
ArenaSign result = subject.create(event);
assertThat(result.location, equalTo(location));
assertThat(result.arenaId, equalTo(arenaId));
assertThat(result.type, equalTo(signType));
assertThat(result.templateId, equalTo(templateId));
}
@Test
public void slugifiedArenaId() {
String arenaId = "Area 52";
String arenaSlug = "area-52";
String signType = "join";
String templateId = "cool-sign";
String[] lines = {"[MA]", arenaId, signType, templateId};
Location location = mock(Location.class);
Arena arena = mock(Arena.class);
when(arena.getSlug()).thenReturn(arenaSlug);
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
when(templateStore.findById(templateId)).thenReturn(Optional.of(mock(Template.class)));
SignChangeEvent event = event(lines, location);
ArenaSign result = subject.create(event);
assertThat(result.location, equalTo(location));
assertThat(result.arenaId, equalTo(arenaSlug));
assertThat(result.type, equalTo(signType.toLowerCase()));
assertThat(result.templateId, equalTo(templateId));
}
@Test
public void caseInsensitiveSignType() {
String arenaId = "castle";
String signType = "jOiN";
String templateId = "cool-sign";
String[] lines = {"[MA]", arenaId, signType, templateId};
Location location = mock(Location.class);
Arena arena = mock(Arena.class);
when(arena.getSlug()).thenReturn(arenaId);
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
when(templateStore.findById(templateId)).thenReturn(Optional.of(mock(Template.class)));
SignChangeEvent event = event(lines, location);
ArenaSign result = subject.create(event);
assertThat(result.location, equalTo(location));
assertThat(result.arenaId, equalTo(arenaId));
assertThat(result.type, equalTo(signType.toLowerCase()));
assertThat(result.templateId, equalTo(templateId));
}
@Test
public void signWithoutTemplateUsesType() {
String arenaId = "castle";
String signType = "join";
String[] lines = {"[MA]", arenaId, signType, null};
Location location = mock(Location.class);
Arena arena = mock(Arena.class);
when(arena.getSlug()).thenReturn(arenaId);
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
when(templateStore.findById(signType)).thenReturn(Optional.of(mock(Template.class)));
SignChangeEvent event = event(lines, location);
ArenaSign result = subject.create(event);
assertThat(result.templateId, equalTo(signType));
}
private SignChangeEvent event(String[] lines, Location location) {
Block block = mock(Block.class);
when(block.getLocation()).thenReturn(location);
Player player = mock(Player.class);
return new SignChangeEvent(block, player, lines);
}
}
@@ -0,0 +1,270 @@
package com.garbagemule.MobArena.signs;
import org.bukkit.World;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.yaml.snakeyaml.Yaml;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Logger;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class SignDataMigratorTest {
Path legacyFile;
Path pendingFile;
Yaml yaml;
SignFile signFile;
Logger log;
SignDataMigrator subject;
@Before
public void setup() throws IOException {
legacyFile = Files.createTempFile("SignDataMigratorTest-", ".data.tmp");
pendingFile = Files.createTempFile("SignDataMigratorTest-", ".tmp.tmp");
yaml = mock(Yaml.class);
signFile = mock(SignFile.class);
log = mock(Logger.class);
subject = new SignDataMigrator(
legacyFile,
pendingFile,
yaml,
signFile,
log
);
}
@After
public void teardown() throws IOException {
Files.deleteIfExists(legacyFile);
Files.deleteIfExists(pendingFile);
}
@Test
public void initDoesNothingIfNoLegacyFile() throws IOException {
Files.delete(pendingFile);
Files.delete(legacyFile);
subject.init();
verifyNoInteractions(yaml, log);
assertThat(Files.exists(legacyFile), equalTo(false));
assertThat(Files.exists(pendingFile), equalTo(false));
}
@Test
public void initCreatesPendingFileAndDeletesLegacyFile() throws IOException {
Files.delete(pendingFile);
Map<String, Object> map = new LinkedHashMap<>();
when(yaml.load(anyString())).thenReturn(map);
subject.init();
assertThat(Files.exists(legacyFile), equalTo(false));
assertThat(Files.exists(pendingFile), equalTo(true));
}
@Test
public void initLogsInfoMessagesOnConversion() throws IOException {
Files.delete(pendingFile);
Map<String, Object> map = new LinkedHashMap<>();
when(yaml.load(anyString())).thenReturn(map);
subject.init();
verify(log, times(2)).info(anyString());
}
@Test
public void initMigratesToPartialFormat() throws IOException {
Files.delete(pendingFile);
List<Map<String, Object>> signs = new ArrayList<>();
signs.add(sign("world", 46.0, 101.0, -191.0, "Area 52", "join", "cool-sign"));
signs.add(sign("lazy", 47.0, 102.0, -192.0, "Mr. Bob's Bus", "info", "status"));
signs.add(sign("eager", 48.0, 103.0, -194.0, "Mission: Impossible", "leave", "coward"));
signs.add(sign("world", 49.0, 104.0, -198.0, "castle", "leave", "bye-bye"));
Map<String, Object> root = new LinkedHashMap<>();
root.put("signs", signs);
when(yaml.load(anyString())).thenReturn(root);
subject.init();
List<String> lines = Files.readAllLines(pendingFile);
assertThat(lines.size(), equalTo(signs.size()));
assertThat(lines, hasItems(
"world;46;101;-191;area-52;join;cool-sign",
"lazy;47;102;-192;mr-bobs-bus;info;status",
"eager;48;103;-194;mission-impossible;leave;coward",
"world;49;104;-198;castle;leave;bye-bye"
));
}
@Test
public void migrateDoesNothingIfNoPendingFile() throws IOException {
Files.delete(pendingFile);
World world = mock(World.class);
subject.migrate(world);
verifyNoInteractions(signFile, log);
}
@Test
public void migrateDoesNothingIfNoMatchingLines() throws IOException {
List<String> lines = Arrays.asList(
"world;46;101;-191;castle;join;cool-sign",
"lazy;47;102;-192;jungle;info;status",
"world;48;103;-194;island;leave;coward"
);
Files.write(pendingFile, lines);
String name = "not-world";
World world = mock(World.class);
when(world.getName()).thenReturn(name);
subject.migrate(world);
verifyNoInteractions(signFile, log);
}
@Test
public void migratePassesMatchingLinesToSignFile() throws IOException {
List<String> lines = Arrays.asList(
"world;46;101;-191;castle;join;cool-sign",
"lazy;47;102;-192;jungle;info;status",
"world;48;103;-194;island;leave;coward"
);
Files.write(pendingFile, lines);
String name = "world";
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
World world = mock(World.class);
when(world.getName()).thenReturn(name);
when(world.getUID()).thenReturn(UUID.fromString(id));
subject.migrate(world);
verify(signFile).append(id + ";" + lines.get(0));
verify(signFile).append(id + ";" + lines.get(2));
verify(signFile).save();
verifyNoMoreInteractions(signFile);
}
@Test
public void migrateDeletesMatchingLinesFromPendingFile() throws IOException {
List<String> lines = Arrays.asList(
"world;46;101;-191;castle;join;cool-sign",
"lazy;47;102;-192;jungle;info;status",
"world;48;103;-194;island;leave;coward"
);
Files.write(pendingFile, lines);
String name = "world";
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
World world = mock(World.class);
when(world.getName()).thenReturn(name);
when(world.getUID()).thenReturn(UUID.fromString(id));
subject.migrate(world);
List<String> remaining = Files.readAllLines(pendingFile);
assertThat(remaining, equalTo(lines.subList(1, 2)));
}
@Test
public void migrateLogsInfoMessageOnMigration() throws IOException {
List<String> lines = Arrays.asList(
"world;46;101;-191;castle;join;cool-sign",
"lazy;47;102;-192;jungle;info;status",
"world;48;103;-194;island;leave;coward"
);
Files.write(pendingFile, lines);
String name = "world";
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
World world = mock(World.class);
when(world.getName()).thenReturn(name);
when(world.getUID()).thenReturn(UUID.fromString(id));
subject.migrate(world);
verify(log, times(2)).info(anyString());
}
@Test
public void migrateDeletesPendingFileOnCompletion() throws IOException {
List<String> lines = Arrays.asList(
"world;46;101;-191;castle;join;cool-sign",
"world;48;103;-194;island;leave;coward"
);
Files.write(pendingFile, lines);
String name = "world";
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
World world = mock(World.class);
when(world.getName()).thenReturn(name);
when(world.getUID()).thenReturn(UUID.fromString(id));
subject.migrate(world);
assertThat(Files.exists(pendingFile), equalTo(false));
}
@Test
public void migrateLogsAdditionallyOnCompletion() throws IOException {
List<String> lines = Arrays.asList(
"world;46;101;-191;castle;join;cool-sign",
"world;48;103;-194;island;leave;coward"
);
Files.write(pendingFile, lines);
String name = "world";
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
World world = mock(World.class);
when(world.getName()).thenReturn(name);
when(world.getUID()).thenReturn(UUID.fromString(id));
subject.migrate(world);
verify(log, times(3)).info(anyString());
}
private Map<String, Object> sign(
String world,
double x,
double y,
double z,
String arenaId,
String type,
String templateId
) {
Map<String, Object> location = new LinkedHashMap<>();
location.put("==", "org.bukkit.Location");
location.put("world", world);
location.put("x", x);
location.put("y", y);
location.put("z", z);
location.put("pitch", 0.0);
location.put("yaw", 0.0);
Map<String, Object> result = new LinkedHashMap<>();
result.put("==", "com.garbagemule.MobArena.signs.ArenaSign");
result.put("arenaId", arenaId);
result.put("location", location);
result.put("templateId", templateId);
result.put("type", type);
return result;
}
}
@@ -0,0 +1,98 @@
package com.garbagemule.MobArena.signs;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.*;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class SignFileTest {
Path file;
SignFile subject;
@Before
public void setup() throws IOException {
file = Files.createTempFile("SignFileTest-", ".tmp");
subject = new SignFile(file);
}
@After
public void teardown() throws IOException {
Files.delete(file);
}
@Test
public void emptyFileEmptyLines() throws IOException {
List<String> result = subject.lines();
assertThat(result.isEmpty(), equalTo(true));
}
@Test
public void emptyLinesIfAppendWithoutSave() throws IOException {
String line1 = "We will";
String line2 = "not be saved!";
subject.append(line1);
subject.append(line2);
List<String> result = subject.lines();
assertThat(result.isEmpty(), equalTo(true));
}
@Test
public void linePersistedOnSave() throws IOException {
String line1 = "We will";
String line2 = "be saved!";
subject.append(line1);
subject.append(line2);
subject.save();
List<String> result = subject.lines();
assertThat(result, equalTo(Arrays.asList(line1, line2)));
}
@Test
public void lineLingersWithoutSave() throws IOException {
String line1 = "We will";
String line2 = "be saved!";
subject.append(line1);
subject.append(line2);
subject.save();
subject.erase(line1);
List<String> result = subject.lines();
assertThat(result, equalTo(Arrays.asList(line1, line2)));
}
@Test
public void lineRemovedOnSave() throws IOException {
String line1 = "We will";
String line2 = "be saved!";
subject.append(line1);
subject.append(line2);
subject.save();
subject.erase(line1);
subject.save();
List<String> result = subject.lines();
assertThat(result, equalTo(Collections.singletonList(line2)));
}
}
@@ -0,0 +1,154 @@
package com.garbagemule.MobArena.signs;
import org.bukkit.World;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class SignReaderTest {
SignFile file;
SignSerializer serializer;
Logger log;
SignReader subject;
@Before
public void setup() {
file = mock(SignFile.class);
serializer = mock(SignSerializer.class);
log = mock(Logger.class);
subject = new SignReader(
file,
serializer,
log
);
}
@Test
public void deserializesNothingIfEmptyFile() throws IOException {
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
World world = mock(World.class);
when(world.getUID()).thenReturn(UUID.fromString(id));
when(file.lines()).thenReturn(Collections.emptyList());
subject.read(world);
verifyNoInteractions(serializer);
}
@Test
public void logsNothingIfEmptyFile() throws IOException {
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
World world = mock(World.class);
when(world.getUID()).thenReturn(UUID.fromString(id));
when(file.lines()).thenReturn(Collections.emptyList());
subject.read(world);
verifyNoInteractions(log);
}
@Test
public void deserializesLinesWithMatchingWorldId() throws IOException {
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
World world = mock(World.class);
when(world.getUID()).thenReturn(UUID.fromString(id));
String line1 = "cafebeef-ea75-dead-babe-deadcafebeef;world;1;2;3;jungle;info;status";
String line2 = id + ";world;1;2;3;castle;join;cool-sign";
when(file.lines()).thenReturn(Arrays.asList(line1, line2));
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
when(serializer.deserialize(line2, world)).thenReturn(sign);
List<ArenaSign> result = subject.read(world);
assertThat(result.size(), equalTo(1));
assertThat(result, hasItem(sign));
verifyNoMoreInteractions(serializer);
}
@Test
public void logsNothingOnSuccess() throws IOException {
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
World world = mock(World.class);
when(world.getUID()).thenReturn(UUID.fromString(id));
String line = id + ";world;1;2;3;castle;join;cool-sign";
when(file.lines()).thenReturn(Collections.singletonList(line));
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
when(serializer.deserialize(line, world)).thenReturn(sign);
subject.read(world);
verifyNoInteractions(log);
}
@Test
public void skipLineIfSerializerThrows() throws IOException {
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
World world = mock(World.class);
when(world.getUID()).thenReturn(UUID.fromString(id));
String line1 = id + ";world;1;2;3;jungle;info;status";
String line2 = id + ";world;4;5;6;castle;join;cool-sign";
when(file.lines()).thenReturn(Arrays.asList(line1, line2));
IllegalArgumentException exception = new IllegalArgumentException("it's bad");
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
when(serializer.deserialize(line1, world)).thenThrow(exception);
when(serializer.deserialize(line2, world)).thenReturn(sign);
List<ArenaSign> result = subject.read(world);
assertThat(result.size(), equalTo(1));
assertThat(result, hasItem(sign));
}
@Test
public void errorMessageIfSerializerThrows() throws IOException {
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
World world = mock(World.class);
when(world.getUID()).thenReturn(UUID.fromString(id));
String line1 = id + ";world;1;2;3;jungle;info;status";
String line2 = id + ";world;4;5;6;castle;join;cool-sign";
when(file.lines()).thenReturn(Arrays.asList(line1, line2));
IllegalArgumentException exception = new IllegalArgumentException("it's bad");
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
when(serializer.deserialize(line1, world)).thenThrow(exception);
when(serializer.deserialize(line2, world)).thenReturn(sign);
subject.read(world);
verify(log).log(eq(Level.SEVERE), anyString(), eq(exception));
verifyNoMoreInteractions(log);
}
@Test
public void propagatesExceptionsFromFile() throws IOException {
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
World world = mock(World.class);
when(world.getUID()).thenReturn(UUID.fromString(id));
IOException exception = new IOException("it's bad");
when(file.lines()).thenThrow(exception);
Assert.assertThrows(
IOException.class,
() -> subject.read(world)
);
}
}
@@ -0,0 +1,161 @@
package com.garbagemule.MobArena.signs;
import com.garbagemule.MobArena.framework.Arena;
import com.garbagemule.MobArena.framework.ArenaMaster;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.Sign;
import org.bukkit.event.block.SignChangeEvent;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Optional;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class SignRendererTest {
TemplateStore templateStore;
ArenaMaster arenaMaster;
RendersTemplate rendersTemplate;
SignRenderer subject;
@Before
public void setup() {
templateStore = mock(TemplateStore.class);
arenaMaster = mock(ArenaMaster.class);
rendersTemplate = mock(RendersTemplate.class);
subject = new SignRenderer(
templateStore,
arenaMaster,
rendersTemplate
);
}
@Test
public void rendersErrorMessageOnSignIfTemplateNotFound() {
Location location = mock(Location.class);
Block block = mock(Block.class);
Sign target = mock(Sign.class);
when(location.getBlock()).thenReturn(block);
when(block.getState()).thenReturn(target);
String templateId = "cool-sign";
when(templateStore.findById(templateId)).thenReturn(Optional.empty());
ArenaSign sign = new ArenaSign(location, templateId, "castle", "join");
subject.render(sign);
verify(target).setLine(0, ChatColor.RED + "[ERROR]");
verify(target).setLine(1, "Template");
verify(target).setLine(2, ChatColor.YELLOW + "cool-sign");
verify(target).setLine(3, "not found :(");
}
@Test
public void rendersErrorMessageOnSignIfArenaNotFound() {
Location location = mock(Location.class);
Block block = mock(Block.class);
Sign target = mock(Sign.class);
when(location.getBlock()).thenReturn(block);
when(block.getState()).thenReturn(target);
String templateId = "cool-sign";
Template template = mock(Template.class);
String arenaId = "castle";
when(templateStore.findById(templateId)).thenReturn(Optional.of(template));
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(null);
ArenaSign sign = new ArenaSign(location, templateId, arenaId, "join");
subject.render(sign);
verify(target).setLine(0, ChatColor.RED + "[ERROR]");
verify(target).setLine(1, "Arena");
verify(target).setLine(2, ChatColor.YELLOW + arenaId);
verify(target).setLine(3, "not found :(");
}
@Test
public void rendersResultsFromDelegateOnSign() {
Location location = mock(Location.class);
Block block = mock(Block.class);
Sign target = mock(Sign.class);
when(location.getBlock()).thenReturn(block);
when(block.getState()).thenReturn(target);
String templateId = "cool-sign";
Template template = mock(Template.class);
String arenaId = "castle";
Arena arena = mock(Arena.class);
String[] lines = new String[]{"this", "is", "a", "sign"};
when(templateStore.findById(templateId)).thenReturn(Optional.of(template));
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
when(rendersTemplate.render(template, arena)).thenReturn(lines);
ArenaSign sign = new ArenaSign(location, templateId, arenaId, "join");
subject.render(sign);
verify(target).setLine(0, lines[0]);
verify(target).setLine(1, lines[1]);
verify(target).setLine(2, lines[2]);
verify(target).setLine(3, lines[3]);
}
@Test
public void rendersErrorMessageInEventIfTemplateNotFound() {
String templateId = "cool-sign";
when(templateStore.findById(templateId)).thenReturn(Optional.empty());
ArenaSign sign = new ArenaSign(null, templateId, "castle", "join");
SignChangeEvent event = mock(SignChangeEvent.class);
subject.render(sign, event);
verify(event).setLine(0, ChatColor.RED + "[ERROR]");
verify(event).setLine(1, "Template");
verify(event).setLine(2, ChatColor.YELLOW + "cool-sign");
verify(event).setLine(3, "not found :(");
}
@Test
public void rendersErrorMessageInEventIfArenaNotFound() {
String templateId = "cool-sign";
Template template = mock(Template.class);
String arenaId = "castle";
when(templateStore.findById(templateId)).thenReturn(Optional.of(template));
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(null);
ArenaSign sign = new ArenaSign(null, templateId, arenaId, "join");
SignChangeEvent event = mock(SignChangeEvent.class);
subject.render(sign, event);
verify(event).setLine(0, ChatColor.RED + "[ERROR]");
verify(event).setLine(1, "Arena");
verify(event).setLine(2, ChatColor.YELLOW + arenaId);
verify(event).setLine(3, "not found :(");
}
@Test
public void rendersResultsFromDelegateInEvent() {
String templateId = "cool-sign";
Template template = mock(Template.class);
String arenaId = "castle";
Arena arena = mock(Arena.class);
String[] lines = new String[]{"this", "is", "a", "sign"};
when(templateStore.findById(templateId)).thenReturn(Optional.of(template));
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
when(rendersTemplate.render(template, arena)).thenReturn(lines);
ArenaSign sign = new ArenaSign(null, templateId, arenaId, "join");
SignChangeEvent event = mock(SignChangeEvent.class);
subject.render(sign, event);
verify(event).setLine(0, lines[0]);
verify(event).setLine(1, lines[1]);
verify(event).setLine(2, lines[2]);
verify(event).setLine(3, lines[3]);
}
}
@@ -0,0 +1,165 @@
package com.garbagemule.MobArena.signs;
import org.bukkit.Location;
import org.bukkit.World;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.UUID;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.*;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class SignSerializerTest {
SignSerializer subject;
@Before
public void setup() {
subject = new SignSerializer();
}
@Test
public void serializeReturnsSemicolonSeparatedRepresentation() {
World world = mock(World.class);
Location location = new Location(world, 1, 2, 3);
String arenaId = "castle";
String type = "join";
String templateId = "status";
when(world.getUID()).thenReturn(UUID.fromString("cafebabe-ea75-dead-beef-deadcafebabe"));
when(world.getName()).thenReturn("world");
ArenaSign sign = new ArenaSign(location, templateId, arenaId, type);
String result = subject.serialize(sign);
String expected = "cafebabe-ea75-dead-beef-deadcafebabe;world;1;2;3;castle;join;status";
assertThat(result, equalTo(expected));
}
@Test
public void deserializeThrowsIfLengthLessThan8() {
World world = mock(World.class);
String input = "1;2;3;4;5;6;7";
Assert.assertThrows(
"Invalid input; expected 8 parts, got 7",
IllegalArgumentException.class,
() -> subject.deserialize(input, world)
);
}
@Test
public void deserializeThrowsIfLengthGreaterThan8() {
World world = mock(World.class);
String input = "1;2;3;4;5;6;7;8;9";
Assert.assertThrows(
"Invalid input; expected 8 parts, got 9",
IllegalArgumentException.class,
() -> subject.deserialize(input, world)
);
}
@Test
public void deserializeThrowsIfWorldDoesNotMatchId() {
World world = mock(World.class);
when(world.getUID()).thenReturn(UUID.fromString("deadbeef-ea75-cafe-babe-deadcafebeef"));
String input = "wrong-id;world;1;2;3;castle;join;status";
Assert.assertThrows(
"World mismatch",
IllegalArgumentException.class,
() -> subject.deserialize(input, world)
);
}
@Test
public void deserializeReturnsIfWorldMatchesId() {
World world = mock(World.class);
when(world.getUID()).thenReturn(UUID.fromString("cafebabe-ea75-dead-beef-deadcafebabe"));
String input = "cafebabe-ea75-dead-beef-deadcafebabe;wrong-world;1;2;3;castle;join;status";
ArenaSign result = subject.deserialize(input, world);
Location location = new Location(world, 1, 2, 3);
assertThat(result.location, equalTo(location));
assertThat(result.arenaId, equalTo("castle"));
assertThat(result.type, equalTo("join"));
assertThat(result.templateId, equalTo("status"));
}
@Test
public void deserializeSerializeReflexivity() {
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
String name = "world";
World world = mock(World.class);
when(world.getName()).thenReturn(name);
when(world.getUID()).thenReturn(UUID.fromString(id));
String line = id + ";" + name + ";1;2;3;castle;join;cool-sign";
String result = subject.serialize(subject.deserialize(line, world));
assertThat(result, equalTo(line));
}
@Test
public void serializeDeserializeReflexivity() {
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
World world = mock(World.class);
when(world.getUID()).thenReturn(UUID.fromString(id));
Location location = new Location(world, 1, 2, 3);
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
ArenaSign result = subject.deserialize(subject.serialize(sign), world);
assertThat(result.location, equalTo(location));
assertThat(result.arenaId, equalTo("castle"));
assertThat(result.type, equalTo("join"));
assertThat(result.templateId, equalTo("cool-sign"));
}
@Test
public void equalReturnsTrueIfStringsAreEqual() {
String line = "cafebabe-ea75-dead-beef-deadcafebabe;world;1;2;3;castle;join;status";
boolean result = subject.equal(line, line);
assertThat(result, equalTo(true));
}
@Test
public void equalReturnsTrueIfWorldIdAndLocationMatch() {
String line1 = "cafebabe-ea75-dead-beef-deadcafebabe;right-world;1;2;3;jungle;leave;out";
String line2 = "cafebabe-ea75-dead-beef-deadcafebabe;wrong-world;1;2;3;castle;join;status";
boolean result = subject.equal(line1, line2);
assertThat(result, equalTo(true));
}
@Test
public void equalReturnsFalseIfOnlyWorldIdIsDifferent() {
String line1 = "cafebabe-ea75-dead-beef-deadcafebabe;right-world;1;2;3;castle;join;status";
String line2 = "deadbeef-feed-cafe-babe-a70ff1cecafe;right-world;1;2;3;castle;join;status";
boolean result = subject.equal(line1, line2);
assertThat(result, equalTo(false));
}
@Test
public void equalReturnsFalseIfOnlyCoordsAreDifferent() {
String line1 = "cafebabe-ea75-dead-beef-deadcafebabe;right-world;1;2;3;castle;join;status";
String line2 = "cafebabe-ea75-dead-beef-deadcafebabe;right-world;4;5;6;castle;join;status";
boolean result = subject.equal(line1, line2);
assertThat(result, equalTo(false));
}
}
@@ -0,0 +1,154 @@
package com.garbagemule.MobArena.signs;
import org.bukkit.Location;
import org.bukkit.World;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.List;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.*;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class SignStoreTest {
SignStore subject;
@Before
public void setup() {
subject = new SignStore();
}
@Test
public void findByLocationReturnsNullIfSignDoesNotExist() {
Location location = mock(Location.class);
ArenaSign result = subject.findByLocation(location);
assertThat(result, nullValue());
}
@Test
public void findByLocationReturnsSignIfItExists() {
Location location = mock(Location.class);
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
subject.add(sign);
ArenaSign result = subject.findByLocation(location);
assertThat(result, equalTo(sign));
}
@Test
public void findByArenaIdReturnsEmptyListIfNoSignsMatch() {
Location location1 = mock(Location.class);
Location location2 = mock(Location.class);
ArenaSign sign1 = new ArenaSign(location1, "cool-sign", "castle", "join");
ArenaSign sign2 = new ArenaSign(location2, "lame-sign", "island", "leave");
subject.add(sign1);
subject.add(sign2);
List<ArenaSign> result = subject.findByArenaId("jungle");
assertThat(result.isEmpty(), equalTo(true));
}
@Test
public void findByArenaIdReturnsOnlyMatchingSigns() {
Location location1 = mock(Location.class);
Location location2 = mock(Location.class);
Location location3 = mock(Location.class);
ArenaSign sign1 = new ArenaSign(location1, "cool-sign", "castle", "join");
ArenaSign sign2 = new ArenaSign(location2, "lame-sign", "island", "leave");
ArenaSign sign3 = new ArenaSign(location3, "very-sign", "jungle", "info");
subject.add(sign1);
subject.add(sign2);
subject.add(sign3);
List<ArenaSign> result = subject.findByArenaId("island");
assertThat(result.size(), equalTo(1));
assertThat(result, hasItem(sign2));
}
@Test
public void removeReturnsNullIfSignDoesNotExist() {
Location location = mock(Location.class);
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
ArenaSign result = subject.removeByLocation(location);
assertThat(result, nullValue());
subject.add(sign);
assertThat(subject.removeByLocation(location), equalTo(sign));
assertThat(subject.removeByLocation(location), nullValue());
}
@Test
public void removeReturnsSignIfItExists() {
Location location = mock(Location.class);
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
subject.add(sign);
ArenaSign result = subject.removeByLocation(location);
assertThat(result, equalTo(sign));
}
@Test
public void removeReturnsNullIfSignWasRemoved() {
Location location = mock(Location.class);
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
subject.add(sign);
subject.removeByLocation(location);
ArenaSign result = subject.removeByLocation(location);
assertThat(result, nullValue());
}
@Test
public void removeByWorldReturnsOnlyMatchingSigns() {
World world1 = mock(World.class);
World world2 = mock(World.class);
Location location1 = mock(Location.class);
Location location2 = mock(Location.class);
when(location1.getWorld()).thenReturn(world1);
when(location2.getWorld()).thenReturn(world2);
ArenaSign sign1 = new ArenaSign(location1, "lame-sign", "jungle", "info");
ArenaSign sign2 = new ArenaSign(location2, "cool-sign", "castle", "join");
subject.add(sign1);
subject.add(sign2);
List<ArenaSign> result = subject.removeByWorld(world2);
assertThat(result, not(hasItem(sign1)));
assertThat(result, hasItem(sign2));
}
@Test
public void removeByWorldReturnsEmptyListIfSignsWereRemoved() {
World world1 = mock(World.class);
World world2 = mock(World.class);
Location location1 = mock(Location.class);
Location location2 = mock(Location.class);
when(location1.getWorld()).thenReturn(world1);
when(location2.getWorld()).thenReturn(world2);
ArenaSign sign1 = new ArenaSign(location1, "lame-sign", "jungle", "info");
ArenaSign sign2 = new ArenaSign(location2, "cool-sign", "castle", "join");
subject.add(sign1);
subject.add(sign2);
subject.removeByWorld(world2);
List<ArenaSign> result = subject.removeByWorld(world2);
assertThat(result.isEmpty(), equalTo(true));
}
}
@@ -0,0 +1,161 @@
package com.garbagemule.MobArena.signs;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class SignWriterTest {
SignFile file;
SignSerializer serializer;
Logger log;
SignWriter subject;
@Before
public void setup() {
file = mock(SignFile.class);
serializer = mock(SignSerializer.class);
log = mock(Logger.class);
subject = new SignWriter(
file,
serializer,
log
);
}
@Test
public void writeCallsAppendWithSerializedSign() throws IOException {
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
String line = "some arbitrary serialization";
when(serializer.serialize(sign)).thenReturn(line);
subject.write(sign);
verify(file).append(line);
verify(file).save();
}
@Test
public void writeCallsEraseOnConflicts() throws IOException {
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
String line = "some arbitrary serialization";
String c1 = "some conflicting line";
String c2 = "some non-conflicting line";
String c3 = "some other conflicting line";
when(serializer.serialize(sign)).thenReturn(line);
when(serializer.equal(c1, line)).thenReturn(true);
when(serializer.equal(c2, line)).thenReturn(false);
when(serializer.equal(c3, line)).thenReturn(true);
when(file.lines()).thenReturn(Arrays.asList(c1, c2, c3));
subject.write(sign);
verify(file).erase(c1);
verify(file).erase(c3);
}
@Test
public void writeLogsWarningOnConflicts() throws IOException {
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
String line = "some arbitrary serialization";
String c1 = "some conflicting line";
String c2 = "some non-conflicting line";
String c3 = "some other conflicting line";
when(serializer.serialize(sign)).thenReturn(line);
when(serializer.equal(c1, line)).thenReturn(true);
when(serializer.equal(c2, line)).thenReturn(false);
when(serializer.equal(c3, line)).thenReturn(true);
when(file.lines()).thenReturn(Arrays.asList(c1, c2, c3));
subject.write(sign);
verify(log, times(2)).warning(anyString());
}
@Test
public void successfulWriteLogsNothing() throws IOException {
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
String line = "some arbitrary serialization";
when(serializer.serialize(sign)).thenReturn(line);
subject.write(sign);
verifyNoInteractions(log);
}
@Test
public void eraseCallsEraseWithIdentifiedLineOnly() throws IOException {
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
String line = "right-id;wrong-name;some other stuff";
String c1 = "wrong-id;wrong-name;some stuff";
String c2 = "right-id;right-name;some other stuff";
String c3 = "wrong-id;wrong-name;some more stuff";
when(serializer.serialize(sign)).thenReturn(line);
when(serializer.equal(anyString(), anyString())).thenReturn(false);
when(serializer.equal(c2, line)).thenReturn(true);
when(file.lines()).thenReturn(Arrays.asList(c1, c2, c3));
subject.erase(sign);
verify(file).erase(c2);
verify(file).save();
verifyNoMoreInteractions(file);
}
@Test
public void successfulEraseLogsNothing() throws IOException {
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
String line = "right-id;wrong-name;some other stuff";
String candiate1 = "wrong-id;wrong-name;some stuff";
String candiate2 = "right-id;right-name;some other stuff";
String candiate3 = "wrong-id;wrong-name;some more stuff";
when(serializer.serialize(sign)).thenReturn(line);
when(serializer.equal(anyString(), anyString())).thenReturn(false);
when(serializer.equal(candiate2, line)).thenReturn(true);
when(file.lines()).thenReturn(Arrays.asList(candiate1, candiate2, candiate3));
subject.erase(sign);
verifyNoInteractions(log);
}
@Test
public void eraseBailsOnNoMatch() throws IOException {
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
String line = "right-id;right-name;some right stuff";
List<String> candidates = Arrays.asList("a", "b", "c");
when(serializer.serialize(sign)).thenReturn(line);
when(serializer.equal(anyString(), anyString())).thenReturn(false);
when(file.lines()).thenReturn(candidates);
subject.erase(sign);
verify(file, never()).erase(anyString());
}
@Test
public void eraseLogsWarningOnNoMatch() throws IOException {
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
String line = "right-id;right-name;some right stuff";
List<String> candidates = Arrays.asList("a", "b", "c");
when(serializer.serialize(sign)).thenReturn(line);
when(serializer.equal(anyString(), anyString())).thenReturn(false);
when(file.lines()).thenReturn(candidates);
subject.erase(sign);
verify(log).warning(anyString());
}
}
@@ -1,126 +0,0 @@
package com.garbagemule.MobArena.signs;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import com.garbagemule.MobArena.framework.Arena;
import com.garbagemule.MobArena.framework.ArenaMaster;
import org.bukkit.Location;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Optional;
@SuppressWarnings("WeakerAccess")
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class StoresNewSignTest {
ArenaMaster arenaMaster;
TemplateStore templateStore;
SignStore signStore;
SavesSignStore savesSignStore;
StoresNewSign subject;
@Rule
public ExpectedException exception = ExpectedException.none();
@Before
public void setup() {
arenaMaster = mock(ArenaMaster.class);
when(arenaMaster.getArenaWithName(any()))
.thenReturn(null);
templateStore = mock(TemplateStore.class);
when(templateStore.findById(any()))
.thenReturn(Optional.empty());
signStore = mock(SignStore.class);
savesSignStore = mock(SavesSignStore.class);
subject = new StoresNewSign(
arenaMaster,
templateStore,
signStore,
savesSignStore
);
}
@Test
public void throwOnNonExistentArena() {
Location location = mock(Location.class);
String arenaId = "castle";
exception.expect(IllegalArgumentException.class);
subject.store(location, arenaId, "", "");
}
@Test
public void throwOnNonExistentTemplate() {
Location location = mock(Location.class);
String arenaId = "castle";
Arena arena = mock(Arena.class);
when(arenaMaster.getArenaWithName(arenaId))
.thenReturn(arena);
String templateId = "template";
exception.expect(IllegalArgumentException.class);
subject.store(location, arenaId, templateId, "");
}
@Test
public void throwOnNonInvalidSignType() {
Location location = mock(Location.class);
String arenaId = "castle";
Arena arena = mock(Arena.class);
when(arenaMaster.getArenaWithName(arenaId))
.thenReturn(arena);
String templateId = "a very nice template";
Template template = template(templateId);
when(templateStore.findById(templateId))
.thenReturn(Optional.of(template));
String signType = "not a real sign type";
exception.expect(IllegalArgumentException.class);
subject.store(location, arenaId, templateId, signType);
}
@Test
public void storesSignAndWritesToDisk() {
Location location = mock(Location.class);
String arenaId = "castle";
Arena arena = mock(Arena.class);
when(arenaMaster.getArenaWithName(arenaId))
.thenReturn(arena);
String templateId = "a very nice template";
Template template = template(templateId);
when(templateStore.findById(templateId))
.thenReturn(Optional.of(template));
String signType = "join";
subject.store(location, arenaId, templateId, signType);
ArgumentCaptor<ArenaSign> captor = ArgumentCaptor.forClass(ArenaSign.class);
verify(signStore).store(captor.capture());
verify(savesSignStore).save(signStore);
ArenaSign sign = captor.getValue();
assertThat(sign.location, equalTo(location));
assertThat(sign.arenaId, equalTo(arenaId));
assertThat(sign.templateId, equalTo(templateId));
assertThat(sign.type, equalTo(signType));
}
private Template template(String id) {
return new Template.Builder(id)
.withBase(new String[]{"", "", "", ""})
.build();
}
}