From 1e6d83825881f89dffeafcb2e3c0799f67fa0245 Mon Sep 17 00:00:00 2001 From: mlangkabel Date: Tue, 28 Apr 2026 21:38:20 +0200 Subject: [PATCH] add tests for blueprint serialization --- src/test/BlueprintSerializerTest.cpp | 187 +++++++++++++++++++++++++++ src/test/CMakeLists.txt | 1 + 2 files changed, 188 insertions(+) create mode 100644 src/test/BlueprintSerializerTest.cpp diff --git a/src/test/BlueprintSerializerTest.cpp b/src/test/BlueprintSerializerTest.cpp new file mode 100644 index 0000000..7edc694 --- /dev/null +++ b/src/test/BlueprintSerializerTest.cpp @@ -0,0 +1,187 @@ +#include "catch.hpp" + +#include +#include + +#include +#include + +#include "Blueprint.h" +#include "BlueprintSerializer.h" +#include "BuildingType.h" +#include "Rotation.h" + +namespace +{ + +Blueprint makeBlueprintWith(const std::vector& buildings, + const QString& name = "Test") +{ + Blueprint bp; + bp.name = name; + bp.buildings = buildings; + return bp; +} + +BlueprintBuilding makeBuilding(BuildingType type, + Rotation rotation, + QPoint offset, + std::string recipeId = "") +{ + BlueprintBuilding b; + b.type = type; + b.rotation = rotation; + b.offset = offset; + b.recipeId = std::move(recipeId); + return b; +} + +} // namespace + +// --------------------------------------------------------------------------- +// Case 1 — single blueprint, one building: all fields survive round-trip +// --------------------------------------------------------------------------- + +TEST_CASE("BlueprintSerializer: single blueprint round-trips all fields", "[serializer]") +{ + const BlueprintBuilding building = + makeBuilding(BuildingType::Miner, Rotation::East, QPoint(2, -1), "mine_iron_ore"); + const Blueprint original = makeBlueprintWith({building}, "My Miner"); + + const std::string toml = BlueprintSerializer::serialize({original}); + const std::vector loaded = BlueprintSerializer::deserialize(toml); + + REQUIRE(loaded.size() == 1); + REQUIRE(loaded[0].name == "My Miner"); + REQUIRE(loaded[0].buildings.size() == 1); + + const BlueprintBuilding& b = loaded[0].buildings[0]; + REQUIRE(b.type == BuildingType::Miner); + REQUIRE(b.rotation == Rotation::East); + REQUIRE(b.offset == QPoint(2, -1)); + REQUIRE(b.recipeId == "mine_iron_ore"); +} + +// --------------------------------------------------------------------------- +// Case 2 — multiple blueprints: count and order preserved +// --------------------------------------------------------------------------- + +TEST_CASE("BlueprintSerializer: multiple blueprints preserve count and order", "[serializer]") +{ + const Blueprint first = makeBlueprintWith( + {makeBuilding(BuildingType::Belt, Rotation::North, QPoint(0, 0))}, "First"); + const Blueprint second = makeBlueprintWith( + {makeBuilding(BuildingType::Smelter, Rotation::South, QPoint(1, 0))}, "Second"); + const Blueprint third = makeBlueprintWith( + {makeBuilding(BuildingType::Assembler, Rotation::West, QPoint(0, 1))}, "Third"); + + const std::string toml = BlueprintSerializer::serialize({first, second, third}); + const std::vector loaded = BlueprintSerializer::deserialize(toml); + + REQUIRE(loaded.size() == 3); + REQUIRE(loaded[0].name == "First"); + REQUIRE(loaded[1].name == "Second"); + REQUIRE(loaded[2].name == "Third"); +} + +// --------------------------------------------------------------------------- +// Case 3 — all four Rotation values round-trip +// --------------------------------------------------------------------------- + +TEST_CASE("BlueprintSerializer: all rotations round-trip", "[serializer]") +{ + const std::vector rotations = { + Rotation::North, Rotation::East, Rotation::South, Rotation::West + }; + + for (const Rotation rot : rotations) + { + const Blueprint bp = makeBlueprintWith( + {makeBuilding(BuildingType::Belt, rot, QPoint(0, 0))}); + + const std::string toml = BlueprintSerializer::serialize({bp}); + const std::vector loaded = BlueprintSerializer::deserialize(toml); + + REQUIRE(loaded.size() == 1); + REQUIRE(loaded[0].buildings.size() == 1); + REQUIRE(loaded[0].buildings[0].rotation == rot); + } +} + +// --------------------------------------------------------------------------- +// Case 5 — negative and zero offsets survive intact +// --------------------------------------------------------------------------- + +TEST_CASE("BlueprintSerializer: negative and zero offsets round-trip", "[serializer]") +{ + const Blueprint bp = makeBlueprintWith({ + makeBuilding(BuildingType::Belt, Rotation::North, QPoint(0, 0)), + makeBuilding(BuildingType::Belt, Rotation::North, QPoint(-3, -2)), + makeBuilding(BuildingType::Belt, Rotation::North, QPoint(5, 4)), + }); + + const std::string toml = BlueprintSerializer::serialize({bp}); + const std::vector loaded = BlueprintSerializer::deserialize(toml); + + REQUIRE(loaded[0].buildings[0].offset == QPoint(0, 0)); + REQUIRE(loaded[0].buildings[1].offset == QPoint(-3, -2)); + REQUIRE(loaded[0].buildings[2].offset == QPoint(5, 4)); +} + +// --------------------------------------------------------------------------- +// Case 6 — empty and non-empty recipeId both round-trip correctly +// --------------------------------------------------------------------------- + +TEST_CASE("BlueprintSerializer: empty and non-empty recipeId round-trip", "[serializer]") +{ + const Blueprint bp = makeBlueprintWith({ + makeBuilding(BuildingType::Miner, Rotation::North, QPoint(0, 0), "mine_iron_ore"), + makeBuilding(BuildingType::Assembler, Rotation::North, QPoint(3, 0), ""), + }); + + const std::string toml = BlueprintSerializer::serialize({bp}); + const std::vector loaded = BlueprintSerializer::deserialize(toml); + + REQUIRE(loaded[0].buildings[0].recipeId == "mine_iron_ore"); + REQUIRE(loaded[0].buildings[1].recipeId == ""); +} + +// --------------------------------------------------------------------------- +// Case 8 — empty blueprint list round-trips to empty vector +// --------------------------------------------------------------------------- + +TEST_CASE("BlueprintSerializer: empty list round-trips to empty vector", "[serializer]") +{ + const std::string toml = BlueprintSerializer::serialize({}); + const std::vector loaded = BlueprintSerializer::deserialize(toml); + + REQUIRE(loaded.empty()); +} + +// --------------------------------------------------------------------------- +// Case 9 — malformed TOML throws std::runtime_error +// --------------------------------------------------------------------------- + +TEST_CASE("BlueprintSerializer: malformed TOML throws", "[serializer]") +{ + REQUIRE_THROWS_AS( + BlueprintSerializer::deserialize("[[blueprint\nname = 42"), + std::runtime_error); +} + +// --------------------------------------------------------------------------- +// Case 10 — unknown building type string throws std::runtime_error +// --------------------------------------------------------------------------- + +TEST_CASE("BlueprintSerializer: unknown building type throws", "[serializer]") +{ + const std::string badToml = + "[[blueprint]]\n" + "name = \"Broken\"\n" + "buildings = [{type = \"unicorn\", rotation = \"north\"," + " offset_x = 0, offset_y = 0, recipe_id = \"\"}]\n"; + + REQUIRE_THROWS_AS( + BlueprintSerializer::deserialize(badToml), + std::runtime_error); +} diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index ec5d8ac..dd2bd43 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -16,4 +16,5 @@ add_files( CombatSystemTest.cpp ShipyardTest.cpp BlueprintTest.cpp + BlueprintSerializerTest.cpp )