diff --git a/src/main/java/com/progys/interview/quiz/commands/ActionCommandFactory.java b/src/main/java/com/progys/interview/quiz/commands/ActionCommandFactory.java index a7f2b47..ff93fe0 100644 --- a/src/main/java/com/progys/interview/quiz/commands/ActionCommandFactory.java +++ b/src/main/java/com/progys/interview/quiz/commands/ActionCommandFactory.java @@ -30,6 +30,6 @@ public interface ActionCommandFactory { Command getPointCommand(@Assisted Point point); @Named("shape") - Command getShapeCommand(@Assisted Boolean silent, @Assisted Shape shape); + Command getShapeCommand(@Assisted ShapeOutputMode outputMode, @Assisted Shape shape); } diff --git a/src/main/java/com/progys/interview/quiz/commands/CommandFactory.java b/src/main/java/com/progys/interview/quiz/commands/CommandFactory.java index 915fca0..a0f5e57 100644 --- a/src/main/java/com/progys/interview/quiz/commands/CommandFactory.java +++ b/src/main/java/com/progys/interview/quiz/commands/CommandFactory.java @@ -6,5 +6,5 @@ * @author progys */ public interface CommandFactory { - Command getCommand(ParsedObject parsed, boolean silentCommands); + Command getCommand(ParsedObject parsed, ShapeOutputMode outputMode); } diff --git a/src/main/java/com/progys/interview/quiz/commands/GeneralCommandFactory.java b/src/main/java/com/progys/interview/quiz/commands/GeneralCommandFactory.java index cfafc15..b88724f 100644 --- a/src/main/java/com/progys/interview/quiz/commands/GeneralCommandFactory.java +++ b/src/main/java/com/progys/interview/quiz/commands/GeneralCommandFactory.java @@ -20,13 +20,13 @@ public class GeneralCommandFactory implements CommandFactory { this.actionCommandFactory = actionCommandFactory; } - public Command getCommand(ParsedObject parsed, boolean silentCommands) { + public Command getCommand(ParsedObject parsed, ShapeOutputMode outputMode) { return switch (parsed) { case null -> actionCommandFactory.getEmptyCommand(); case ParsedPoint parsedPoint -> actionCommandFactory.getPointCommand(parsedPoint.point()); case ParsedShape parsedShape -> - actionCommandFactory.getShapeCommand(silentCommands, parsedShape.shape()); + actionCommandFactory.getShapeCommand(outputMode, parsedShape.shape()); case ParsedAction parsedAction -> switch (parsedAction.name()) { case exit -> actionCommandFactory.getExitCommand(); case help -> actionCommandFactory.getHelpCommand(); diff --git a/src/main/java/com/progys/interview/quiz/commands/HelpCommand.java b/src/main/java/com/progys/interview/quiz/commands/HelpCommand.java index 2bcad22..66910f7 100644 --- a/src/main/java/com/progys/interview/quiz/commands/HelpCommand.java +++ b/src/main/java/com/progys/interview/quiz/commands/HelpCommand.java @@ -27,8 +27,8 @@ public void process() { " exit - terminates the program\n", "Interactive commands examples: ", " triangle 4.5 1 -2.5 -33 23 0.3 - creates triangle (4.5,1) (-2.5, -33) (23, 0.3)", - " donut 1.1 7.8 2 1.8 - creates donut with center at (1.1, 7.8) inner radius 1.8 and outer radius 2", - " circle 3 5 2 - creates circle with center at (3, 5) inner radius 1.8 and outer radius 2", + " donut 1.1 7.8 1.8 2 - creates donut with center at (1.1, 7.8) inner radius 1.8 and outer radius 2", + " circle 3 5 2 - creates circle with center at (3, 5) and radius 2", " 5.1 6.2 - prints all shapes which include given point (5.1, 6.2) with their surface area and also total area." }; diff --git a/src/main/java/com/progys/interview/quiz/commands/ShapeCommand.java b/src/main/java/com/progys/interview/quiz/commands/ShapeCommand.java index 43df901..6f8b0df 100644 --- a/src/main/java/com/progys/interview/quiz/commands/ShapeCommand.java +++ b/src/main/java/com/progys/interview/quiz/commands/ShapeCommand.java @@ -16,28 +16,29 @@ public class ShapeCommand extends AbstractCommand { private final Store persistence; private final Shape shape; - private final boolean silent; + private final ShapeOutputMode outputMode; @Inject - public ShapeCommand(Store persistence, @Assisted Shape shape, @Assisted boolean silent, - PrintStream output) { + public ShapeCommand(Store persistence, @Assisted ShapeOutputMode outputMode, + @Assisted Shape shape, PrintStream output) { super(output); this.persistence = persistence; this.shape = shape; - this.silent = silent; + this.outputMode = outputMode; } @Override public void process() { StoredShape storedShape = persistence.put(shape); - if (!silent) { + if (outputMode == ShapeOutputMode.VERBOSE) { output.println(storedShape); } } @Override protected void printSeparator() { - if (!silent) + if (outputMode == ShapeOutputMode.VERBOSE) { super.printSeparator(); + } } } diff --git a/src/main/java/com/progys/interview/quiz/commands/ShapeOutputMode.java b/src/main/java/com/progys/interview/quiz/commands/ShapeOutputMode.java new file mode 100644 index 0000000..bd678ed --- /dev/null +++ b/src/main/java/com/progys/interview/quiz/commands/ShapeOutputMode.java @@ -0,0 +1,10 @@ +package com.progys.interview.quiz.commands; + +/** + * Controls whether shape creation output is printed. + * + * @author progys + */ +public enum ShapeOutputMode { + SILENT, VERBOSE +} diff --git a/src/main/java/com/progys/interview/quiz/model/Triangle.java b/src/main/java/com/progys/interview/quiz/model/Triangle.java index a5e941f..2fd50d4 100644 --- a/src/main/java/com/progys/interview/quiz/model/Triangle.java +++ b/src/main/java/com/progys/interview/quiz/model/Triangle.java @@ -44,7 +44,7 @@ public boolean inShape(Point point) { * (v0.y * v2.x - v0.x * v2.y + (v2.y - v0.y) * point.x + (v0.x - v2.x) * point.y); double t = d * (v0.x * v1.y - v0.y * v1.x + (v0.y - v1.y) * point.x + (v1.x - v0.x) * point.y); - return s >= 0 && t >= 0 && (s + t) < 1; + return s > 0 && t > 0 && (s + t) < 1; } @Override diff --git a/src/main/java/com/progys/interview/quiz/persistence/ObjectStore.java b/src/main/java/com/progys/interview/quiz/persistence/ObjectStore.java index 7e27f47..73061fe 100644 --- a/src/main/java/com/progys/interview/quiz/persistence/ObjectStore.java +++ b/src/main/java/com/progys/interview/quiz/persistence/ObjectStore.java @@ -8,13 +8,17 @@ import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.TypedQuery; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** - * Defines an object storage layer. + * Object storage layer. The database is the source of truth across restarts, but a copy of all + * shapes is kept in memory so point queries never hit the database (the quiz requires queries to + * scale to tens of millions of shapes held in program memory). * * @author progys */ @@ -24,11 +28,13 @@ public class ObjectStore implements Store { private final EntityManagerFactory entityManagerFactory; private final EntityManager manager; + private final List shapes; @Inject ObjectStore(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; this.manager = entityManagerFactory.createEntityManager(); + this.shapes = new ArrayList<>(loadShapesFromDatabase()); } @Override @@ -40,7 +46,9 @@ public StoredShape put(Shape shape) { manager.persist(entity); manager.flush(); transaction.commit(); - return new StoredShape(entity.getId(), shape); + StoredShape stored = new StoredShape(entity.getId(), shape); + shapes.add(stored); + return stored; } catch (RuntimeException e) { if (transaction.isActive()) { transaction.rollback(); @@ -57,6 +65,7 @@ public void clear() { try { manager.createQuery("delete from ShapeEntity").executeUpdate(); transaction.commit(); + shapes.clear(); } catch (RuntimeException e) { if (transaction.isActive()) { transaction.rollback(); @@ -68,10 +77,13 @@ public void clear() { @Override public Collection getAll() { + return Collections.unmodifiableList(shapes); + } + + private List loadShapesFromDatabase() { TypedQuery query = manager.createQuery( "SELECT e FROM " + ShapeEntity.class.getName() + " e", ShapeEntity.class); - List entities = query.getResultList(); - return entities.stream() + return query.getResultList().stream() .map(entity -> new StoredShape(entity.getId(), entity.toShape())) .toList(); } diff --git a/src/main/java/com/progys/interview/quiz/processor/ConsoleInputProcessor.java b/src/main/java/com/progys/interview/quiz/processor/ConsoleInputProcessor.java index db45e68..f2df74e 100644 --- a/src/main/java/com/progys/interview/quiz/processor/ConsoleInputProcessor.java +++ b/src/main/java/com/progys/interview/quiz/processor/ConsoleInputProcessor.java @@ -2,6 +2,7 @@ import com.google.inject.Inject; import com.progys.interview.quiz.commands.CommandFactory; +import com.progys.interview.quiz.commands.ShapeOutputMode; import com.progys.interview.quiz.parser.ParsedObject; import com.progys.interview.quiz.parser.Parser; import com.progys.interview.quiz.parser.ParserFactory; @@ -40,7 +41,7 @@ private void readLines(Scanner scanner) { try { String command = scanner.nextLine(); Parser parser = parserFactory.create(command); - commandFactory.getCommand(parser.parse(), false).execute(); + commandFactory.getCommand(parser.parse(), ShapeOutputMode.VERBOSE).execute(); } catch (Exception e) { System.err.println(e.getMessage()); } diff --git a/src/main/java/com/progys/interview/quiz/processor/FileInputProcessor.java b/src/main/java/com/progys/interview/quiz/processor/FileInputProcessor.java index 6f24d14..1839e88 100644 --- a/src/main/java/com/progys/interview/quiz/processor/FileInputProcessor.java +++ b/src/main/java/com/progys/interview/quiz/processor/FileInputProcessor.java @@ -3,6 +3,7 @@ import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import com.progys.interview.quiz.commands.CommandFactory; +import com.progys.interview.quiz.commands.ShapeOutputMode; import com.progys.interview.quiz.model.Point; import com.progys.interview.quiz.model.Shape; import com.progys.interview.quiz.parser.ConcreteParserFactory; @@ -11,29 +12,32 @@ import java.io.File; import java.io.FileNotFoundException; +import java.io.PrintStream; import java.util.Scanner; public class FileInputProcessor implements InputProcessor { private final File file; private final CommandFactory commandFactory; private final ConcreteParserFactory concreteParserFactory; + private final PrintStream output; @Inject FileInputProcessor(CommandFactory commandFactory, @Assisted File input, - ConcreteParserFactory concreteParserFactory) { + ConcreteParserFactory concreteParserFactory, PrintStream output) { this.file = input; this.commandFactory = commandFactory; this.concreteParserFactory = concreteParserFactory; + this.output = output; } public void process() { try (Scanner scanner = new Scanner(file)) { - System.out.println("Reading provided input file: " + file.getAbsolutePath()); + output.println("Reading provided input file: " + file.getAbsolutePath()); while (scanner.hasNextLine()) { processLine(scanner.nextLine()); } } catch (FileNotFoundException e) { - System.out.println("File not found: " + file.getAbsolutePath() + "\n"); + output.println("File not found: " + file.getAbsolutePath() + "\n"); } } @@ -42,9 +46,10 @@ private void processLine(String line) { Parser pointParser = concreteParserFactory.getPointParser(lineScanner); Parser shapeParser = concreteParserFactory.getShapeParser(lineScanner, pointParser); - commandFactory.getCommand(new ParsedShape(shapeParser.parse()), true).execute(); + commandFactory.getCommand(new ParsedShape(shapeParser.parse()), + ShapeOutputMode.SILENT).execute(); } catch (Exception e) { - System.out.println("Exception while reading input from file: " + e.getMessage()); + output.println("Exception while reading input from file: " + e.getMessage()); } } } diff --git a/src/test/java/com/progys/interview/quiz/commands/HelpCommandTest.java b/src/test/java/com/progys/interview/quiz/commands/HelpCommandTest.java index ef397ec..ccfb0dd 100644 --- a/src/test/java/com/progys/interview/quiz/commands/HelpCommandTest.java +++ b/src/test/java/com/progys/interview/quiz/commands/HelpCommandTest.java @@ -46,8 +46,8 @@ public void printsCommandExamples() { String output = outputContent.toString(); assertThat(output).contains( "triangle 4.5 1 -2.5 -33 23 0.3 - creates triangle (4.5,1) (-2.5, -33) (23, 0.3)", - "donut 1.1 7.8 2 1.8 - creates donut with center at (1.1, 7.8) inner radius 1.8 and outer radius 2", - "circle 3 5 2 - creates circle with center at (3, 5) inner radius 1.8 and outer radius 2" + "donut 1.1 7.8 1.8 2 - creates donut with center at (1.1, 7.8) inner radius 1.8 and outer radius 2", + "circle 3 5 2 - creates circle with center at (3, 5) and radius 2" ); } } \ No newline at end of file diff --git a/src/test/java/com/progys/interview/quiz/model/CircleTest.java b/src/test/java/com/progys/interview/quiz/model/CircleTest.java index a7167e4..5850a20 100644 --- a/src/test/java/com/progys/interview/quiz/model/CircleTest.java +++ b/src/test/java/com/progys/interview/quiz/model/CircleTest.java @@ -20,4 +20,20 @@ public void calculatesAreaWithRadiusTwo() { assertThat(circleWithRadiusTwo.getArea()) .isCloseTo(Math.PI * 4, Assertions.within(0.01)); } + + @Test + public void containsPointInsideCircle() { + assertThat(circleWithRadiusOne.inShape(new Point(1, 1))).isTrue(); + assertThat(circleWithRadiusOne.inShape(new Point(1.5, 1))).isTrue(); + } + + @Test + public void doesNotContainPointOutsideCircle() { + assertThat(circleWithRadiusOne.inShape(new Point(3, 1))).isFalse(); + } + + @Test + public void doesNotContainPointOnBoundary() { + assertThat(circleWithRadiusOne.inShape(new Point(2, 1))).isFalse(); + } } \ No newline at end of file diff --git a/src/test/java/com/progys/interview/quiz/parser/CommandParserTest.java b/src/test/java/com/progys/interview/quiz/parser/CommandParserTest.java new file mode 100644 index 0000000..8267ab5 --- /dev/null +++ b/src/test/java/com/progys/interview/quiz/parser/CommandParserTest.java @@ -0,0 +1,33 @@ +package com.progys.interview.quiz.parser; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class CommandParserTest { + + @Test + void parsesKnownCommands() { + assertThat(new CommandParser("list").parse()) + .isEqualTo(new ParsedAction(ActionNames.list)); + assertThat(new CommandParser("exit").parse()) + .isEqualTo(new ParsedAction(ActionNames.exit)); + assertThat(new CommandParser("help").parse()) + .isEqualTo(new ParsedAction(ActionNames.help)); + assertThat(new CommandParser("clear").parse()) + .isEqualTo(new ParsedAction(ActionNames.clear)); + } + + @Test + void parsesEmptyInputAsEmptyCommand() { + assertThat(new CommandParser("").parse()) + .isEqualTo(new ParsedAction(ActionNames.empty)); + } + + @Test + void throwsOnUnknownCommand() { + assertThatThrownBy(() -> new CommandParser("bogus").parse()) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/com/progys/interview/quiz/parser/GeneralParserFactoryTest.java b/src/test/java/com/progys/interview/quiz/parser/GeneralParserFactoryTest.java new file mode 100644 index 0000000..4838d58 --- /dev/null +++ b/src/test/java/com/progys/interview/quiz/parser/GeneralParserFactoryTest.java @@ -0,0 +1,65 @@ +package com.progys.interview.quiz.parser; + +import com.progys.interview.quiz.exceptions.ParseException; +import com.progys.interview.quiz.model.Point; +import com.progys.interview.quiz.model.Shape; +import org.junit.jupiter.api.Test; + +import java.util.Scanner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class GeneralParserFactoryTest { + private final GeneralParserFactory parserFactory = + new GeneralParserFactory(new ConcreteParserFactory() { + @Override + public Parser getPointParser(Scanner scanner) { + return new PointParser(scanner); + } + + @Override + public Parser getShapeParser(Scanner scanner, Parser pointParser) { + return new ShapeParser(scanner, pointParser); + } + + @Override + public Parser getCommandParser(String command) { + return new CommandParser(command); + } + }); + + @Test + void parsesActionCommand() { + assertThat(parserFactory.create("list").parse()) + .isEqualTo(new ParsedAction(ActionNames.list)); + } + + @Test + void parsesEmptyInputAsActionCommand() { + assertThat(parserFactory.create("").parse()) + .isEqualTo(new ParsedAction(ActionNames.empty)); + } + + @Test + void parsesPointQuery() { + ParsedObject parsed = parserFactory.create("1 2").parse(); + + assertThat(parsed).isInstanceOf(ParsedPoint.class); + assertThat(((ParsedPoint) parsed).point().x).isEqualTo(1); + assertThat(((ParsedPoint) parsed).point().y).isEqualTo(2); + } + + @Test + void parsesShape() { + ParsedObject parsed = parserFactory.create("circle 0 0 1").parse(); + + assertThat(parsed).isInstanceOf(ParsedShape.class); + } + + @Test + void throwsParseExceptionOnInvalidShape() { + assertThatThrownBy(() -> parserFactory.create("circle 0 0 0").parse()) + .isInstanceOf(ParseException.class); + } +} diff --git a/src/test/java/com/progys/interview/quiz/parser/PointParserTest.java b/src/test/java/com/progys/interview/quiz/parser/PointParserTest.java new file mode 100644 index 0000000..97bb42a --- /dev/null +++ b/src/test/java/com/progys/interview/quiz/parser/PointParserTest.java @@ -0,0 +1,35 @@ +package com.progys.interview.quiz.parser; + +import com.progys.interview.quiz.exceptions.ParseException; +import com.progys.interview.quiz.model.Point; +import org.junit.jupiter.api.Test; + +import java.util.Scanner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class PointParserTest { + + @Test + void parsesPoint() { + Point point = new PointParser(new Scanner("1.5 2.5")).parse(); + + assertThat(point.x).isEqualTo(1.5); + assertThat(point.y).isEqualTo(2.5); + } + + @Test + void throwsParseExceptionOnNonNumericInput() { + PointParser parser = new PointParser(new Scanner("abc 1")); + + assertThatThrownBy(parser::parse).isInstanceOf(ParseException.class); + } + + @Test + void throwsParseExceptionOnMissingCoordinate() { + PointParser parser = new PointParser(new Scanner("1")); + + assertThatThrownBy(parser::parse).isInstanceOf(ParseException.class); + } +} diff --git a/src/test/java/com/progys/interview/quiz/parser/ShapeParserTest.java b/src/test/java/com/progys/interview/quiz/parser/ShapeParserTest.java new file mode 100644 index 0000000..93bbf58 --- /dev/null +++ b/src/test/java/com/progys/interview/quiz/parser/ShapeParserTest.java @@ -0,0 +1,62 @@ +package com.progys.interview.quiz.parser; + +import com.progys.interview.quiz.exceptions.ParseException; +import com.progys.interview.quiz.model.Circle; +import com.progys.interview.quiz.model.Donut; +import com.progys.interview.quiz.model.Shape; +import com.progys.interview.quiz.model.Triangle; +import org.junit.jupiter.api.Test; + +import java.util.Scanner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ShapeParserTest { + + private Shape parse(String input) { + Scanner scanner = new Scanner(input); + return new ShapeParser(scanner, new PointParser(scanner)).parse(); + } + + @Test + void parsesCircle() { + Circle circle = (Circle) parse("circle 1 2 3"); + + assertThat(circle.getCenter().x).isEqualTo(1); + assertThat(circle.getCenter().y).isEqualTo(2); + assertThat(circle.getRadius()).isEqualTo(3); + } + + @Test + void parsesTriangle() { + Triangle triangle = (Triangle) parse("triangle 0 0 1 1 2 0"); + + assertThat(triangle.getV0().x).isEqualTo(0); + assertThat(triangle.getV1().x).isEqualTo(1); + assertThat(triangle.getV2().x).isEqualTo(2); + } + + @Test + void parsesDonut() { + Donut donut = (Donut) parse("donut 0 0 1 2"); + + assertThat(donut.getInnerCircle().getRadius()).isEqualTo(1); + assertThat(donut.getOuterCircle().getRadius()).isEqualTo(2); + } + + @Test + void throwsParseExceptionOnUnrecognizedShapeName() { + assertThatThrownBy(() -> parse("hexagon 1 2 3")).isInstanceOf(ParseException.class); + } + + @Test + void throwsParseExceptionOnInvalidRadius() { + assertThatThrownBy(() -> parse("circle 0 0 0")).isInstanceOf(ParseException.class); + } + + @Test + void throwsParseExceptionOnMissingArguments() { + assertThatThrownBy(() -> parse("triangle 0 0")).isInstanceOf(ParseException.class); + } +} diff --git a/src/test/java/com/progys/interview/quiz/persistence/ObjectStoreTest.java b/src/test/java/com/progys/interview/quiz/persistence/ObjectStoreTest.java new file mode 100644 index 0000000..e853723 --- /dev/null +++ b/src/test/java/com/progys/interview/quiz/persistence/ObjectStoreTest.java @@ -0,0 +1,90 @@ +package com.progys.interview.quiz.persistence; + +import com.progys.interview.quiz.model.Circle; +import com.progys.interview.quiz.model.Point; +import com.progys.interview.quiz.model.Triangle; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Comparator; + +import static org.assertj.core.api.Assertions.assertThat; + +class ObjectStoreTest { + private Path dbPath; + private ObjectStore store; + + @BeforeAll + static void enhancePersistenceClasses() { + com.objectdb.Enhancer.enhance("com.progys.interview.quiz.persistence.*"); + } + + @BeforeEach + void setUp() throws IOException { + dbPath = Files.createTempDirectory("odbtest").resolve("test.odb"); + store = new ObjectStore(createEntityManagerFactory()); + } + + @AfterEach + void tearDown() throws IOException { + try { + store.close(); + } catch (RuntimeException ignored) { + } + if (dbPath.getParent() != null && Files.exists(dbPath.getParent())) { + try (var paths = Files.walk(dbPath.getParent())) { + paths.sorted(Comparator.reverseOrder()).forEach(path -> { + try { + Files.deleteIfExists(path); + } catch (IOException ignored) { + } + }); + } + } + } + + private EntityManagerFactory createEntityManagerFactory() { + return Persistence.createEntityManagerFactory("objectdb:" + dbPath); + } + + @Test + void putAssignsIdAndStoresShape() { + StoredShape stored = store.put(new Circle(new Point(1, 2), 3)); + + assertThat(stored.id()).isNotNull(); + Collection all = store.getAll(); + assertThat(all).hasSize(1); + assertThat(all.iterator().next().shape()).isInstanceOf(Circle.class); + } + + @Test + void clearEmptiesStore() { + store.put(new Circle(new Point(0, 0), 1)); + + store.clear(); + + assertThat(store.getAll()).isEmpty(); + } + + @Test + void loadsPersistedShapesFromDatabaseOnStartup() { + store.put(new Triangle(new Point(0, 0), new Point(1, 0), new Point(0, 1))); + store.close(); + + ObjectStore reopened = new ObjectStore(createEntityManagerFactory()); + try { + assertThat(reopened.getAll()).hasSize(1); + assertThat(reopened.getAll().iterator().next().shape()).isInstanceOf(Triangle.class); + } finally { + reopened.close(); + } + } +} diff --git a/src/test/java/com/progys/interview/quiz/persistence/ShapeEntityTest.java b/src/test/java/com/progys/interview/quiz/persistence/ShapeEntityTest.java new file mode 100644 index 0000000..cda3f66 --- /dev/null +++ b/src/test/java/com/progys/interview/quiz/persistence/ShapeEntityTest.java @@ -0,0 +1,48 @@ +package com.progys.interview.quiz.persistence; + +import com.progys.interview.quiz.model.Circle; +import com.progys.interview.quiz.model.Donut; +import com.progys.interview.quiz.model.Point; +import com.progys.interview.quiz.model.Triangle; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ShapeEntityTest { + + @Test + void mapsCircleRoundTrip() { + Circle circle = (Circle) new ShapeEntity(new Circle(new Point(1, 2), 3)).toShape(); + + assertThat(circle.getCenter().x).isEqualTo(1); + assertThat(circle.getCenter().y).isEqualTo(2); + assertThat(circle.getRadius()).isEqualTo(3); + } + + @Test + void mapsTriangleRoundTrip() { + Triangle triangle = (Triangle) new ShapeEntity( + new Triangle(new Point(0, 0), new Point(1, 1), new Point(2, 0))).toShape(); + + assertThat(triangle.getV0().x).isEqualTo(0); + assertThat(triangle.getV1().x).isEqualTo(1); + assertThat(triangle.getV2().x).isEqualTo(2); + } + + @Test + void mapsDonutRoundTrip() { + Donut donut = (Donut) new ShapeEntity(new Donut(1, 2, new Point(5, 5))).toShape(); + + assertThat(donut.getInnerCircle().getRadius()).isEqualTo(1); + assertThat(donut.getOuterCircle().getRadius()).isEqualTo(2); + assertThat(donut.getCenter().x).isEqualTo(5); + assertThat(donut.getCenter().y).isEqualTo(5); + } + + @Test + void rejectsNullShape() { + assertThatThrownBy(() -> new ShapeEntity(null)) + .isInstanceOf(IllegalArgumentException.class); + } +}