154 lines
6.7 KiB
C++
154 lines
6.7 KiB
C++
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
|
|
#include "game_event/GameEvent.h"
|
|
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <span>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
using namespace eve::game_event;
|
|
|
|
namespace {
|
|
|
|
EventId id(const char* text) {
|
|
const auto parsed = EventId::parse(text);
|
|
if (!parsed) return {};
|
|
return *parsed;
|
|
}
|
|
|
|
GameEvent metadata(const char* eventId, const char* schema, uint64_t tick, const char* causation = "",
|
|
const char* correlation = "") {
|
|
GameEvent envelope;
|
|
envelope.eventId = id(eventId);
|
|
envelope.type = "damage.applied";
|
|
envelope.source = "combat:system";
|
|
envelope.subject = "rpg:target-7";
|
|
if (causation[0] != '\0') envelope.causation = CausationRef::fromEventId(id(causation));
|
|
if (correlation[0] != '\0') envelope.correlation = CorrelationId::fromEventId(id(correlation));
|
|
envelope.schemaId = *SchemaId::parse(schema);
|
|
envelope.schemaVersion = SchemaVersion(2);
|
|
envelope.tick = SimulationTick(tick);
|
|
envelope.flags = 4;
|
|
return envelope;
|
|
}
|
|
|
|
struct DamagePayload {
|
|
int amount = 0;
|
|
std::string kind;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("event_envelope.typedPayloadAndCausalMetadata") {
|
|
GameEventLog stream;
|
|
const auto causation = id("018f0f4e-6b3c-7abc-8def-0123456789ab");
|
|
const auto correlation = id("018f0f4e-6b3c-7abd-8def-0123456789ab");
|
|
const auto event = id("018f0f4e-6b3c-7abe-8def-0123456789ab");
|
|
|
|
auto envelope =
|
|
metadata(event.format().c_str(), "combat:damage", 77, causation.format().c_str(), correlation.format().c_str());
|
|
DamagePayload payload{12, "fire"};
|
|
auto appended = stream.appendTyped<DamagePayload>(std::move(envelope), payload, [](const DamagePayload& value) {
|
|
return eve::Result<std::string>::success("{\"amount\":" + std::to_string(value.amount) + ",\"kind\":\"" +
|
|
value.kind + "\"}");
|
|
});
|
|
REQUIRE(appended.ok());
|
|
CHECK_EQ(appended.value().value(), uint64_t{1});
|
|
|
|
const auto* stored = stream.find(EventSequence(1));
|
|
REQUIRE(stored != nullptr);
|
|
CHECK_EQ(stored->eventId, event);
|
|
CHECK_EQ(stored->sequence.value(), uint64_t{1});
|
|
CHECK_EQ(stored->tick.value(), uint64_t{77});
|
|
CHECK_EQ(stored->schemaId.format(), std::string("combat:damage"));
|
|
CHECK_EQ(stored->schemaVersion.value(), uint64_t{2});
|
|
CHECK_EQ(stored->causation.format(), causation.format());
|
|
CHECK_EQ(stored->causation.kind(), CausationRef::Kind::Event);
|
|
CHECK_EQ(stored->correlation.format(), correlation.format());
|
|
CHECK_EQ(stored->correlation.kind(), CorrelationId::Kind::Id);
|
|
CHECK_EQ(stored->payload, std::string("{\"amount\":12,\"kind\":\"fire\"}"));
|
|
}
|
|
|
|
TEST_CASE("event_envelope.uuidV7GeneratorFillsMissingEventId") {
|
|
const auto timestamp = std::chrono::system_clock::time_point(std::chrono::milliseconds(1710000000123));
|
|
GameEventLog stream(
|
|
[](std::span<uint8_t> bytes) {
|
|
for (size_t i = 0; i < bytes.size(); ++i) bytes[i] = static_cast<uint8_t>(i + 1);
|
|
return true;
|
|
},
|
|
[timestamp] { return timestamp; });
|
|
|
|
auto envelope = metadata("00000000-0000-0000-0000-000000000000", "combat:damage", 3);
|
|
envelope.eventId = EventId::nil();
|
|
auto appended = stream.append(std::move(envelope));
|
|
REQUIRE(appended.ok());
|
|
CHECK(!stream.find(EventSequence(1))->eventId.isNil());
|
|
CHECK_EQ(stream.find(EventSequence(1))->eventId.format().substr(14, 1), std::string("7"));
|
|
}
|
|
|
|
TEST_CASE("event_envelope.restorePreservesStreamOrderAndNextSequence") {
|
|
GameEventLog original;
|
|
auto first = metadata("018f0f4e-6b3c-7ab1-8def-0123456789ab", "combat:damage", 10);
|
|
auto second = metadata("018f0f4e-6b3c-7ab2-8def-0123456789ab", "combat:damage", 11,
|
|
"018f0f4e-6b3c-7ab1-8def-0123456789ab", "018f0f4e-6b3c-7ab0-8def-0123456789ab");
|
|
first.payload = "{\"amount\":1}";
|
|
second.payload = "{\"amount\":2}";
|
|
auto firstResult = original.append(std::move(first));
|
|
auto secondResult = original.append(std::move(second));
|
|
REQUIRE(firstResult.ok());
|
|
REQUIRE(secondResult.ok());
|
|
CHECK_EQ(firstResult.value().value(), uint64_t{1});
|
|
CHECK_EQ(secondResult.value().value(), uint64_t{2});
|
|
const std::string snapshot = original.snapshotJson();
|
|
|
|
GameEventLog restored;
|
|
auto restoredResult = restored.restore(snapshot);
|
|
REQUIRE(restoredResult.ok());
|
|
CHECK_EQ(restored.find(EventSequence(1))->eventId, id("018f0f4e-6b3c-7ab1-8def-0123456789ab"));
|
|
CHECK_EQ(restored.find(EventSequence(2))->causation.format(), std::string("018f0f4e-6b3c-7ab1-8def-0123456789ab"));
|
|
CHECK_EQ(restored.find(EventSequence(2))->correlation.format(),
|
|
std::string("018f0f4e-6b3c-7ab0-8def-0123456789ab"));
|
|
|
|
auto third = metadata("018f0f4e-6b3c-7ab3-8def-0123456789ab", "combat:damage", 12);
|
|
auto thirdResult = restored.append(std::move(third));
|
|
REQUIRE(thirdResult.ok());
|
|
CHECK_EQ(thirdResult.value().value(), uint64_t{3});
|
|
CHECK_EQ(restored.find(EventSequence(3))->sequence.value(), uint64_t{3});
|
|
CHECK_EQ(restored.size(), 3);
|
|
}
|
|
|
|
TEST_CASE("event_envelope.restoreFailureIsTransactional") {
|
|
GameEventLog stream;
|
|
auto event = metadata("018f0f4e-6b3c-7ab4-8def-0123456789ab", "combat:damage", 20);
|
|
auto appended = stream.append(std::move(event));
|
|
REQUIRE(appended.ok());
|
|
const std::string before = stream.snapshotJson();
|
|
|
|
auto failed = stream.restore("{\"version\":2,\"nextSequence\":\"2\",\"events\":[");
|
|
CHECK(!failed.ok());
|
|
CHECK_EQ(stream.snapshotJson(), before);
|
|
CHECK_EQ(stream.find(EventSequence(1))->sequence.value(), uint64_t{1});
|
|
}
|
|
|
|
TEST_CASE("event_envelope.restoreRejectsDuplicateNonNilEventId") {
|
|
const std::string duplicate =
|
|
"{\"version\":2,\"nextSequence\":\"3\",\"events\":["
|
|
"{\"eventId\":\"018f0f4e-6b3c-7ab1-8def-0123456789ab\",\"sequence\":\"1\","
|
|
"\"type\":\"a\",\"source\":\"s\",\"subject\":\"x\",\"causationKind\":\"none\","
|
|
"\"causation\":\"\",\"correlationKind\":\"none\",\"correlation\":\"\","
|
|
"\"schemaId\":\"test:a\",\"schemaVersion\":\"1\",\"tick\":\"1\",\"flags\":0,\"payload\":null},"
|
|
"{\"eventId\":\"018f0f4e-6b3c-7ab1-8def-0123456789ab\",\"sequence\":\"2\","
|
|
"\"type\":\"b\",\"source\":\"s\",\"subject\":\"x\",\"causationKind\":\"none\","
|
|
"\"causation\":\"\",\"correlationKind\":\"none\",\"correlation\":\"\","
|
|
"\"schemaId\":\"test:b\",\"schemaVersion\":\"1\",\"tick\":\"2\",\"flags\":0,\"payload\":null}]}";
|
|
|
|
GameEventLog stream;
|
|
auto result = stream.restore(duplicate);
|
|
CHECK(!result.ok());
|
|
CHECK_EQ(result.code(), eve::StatusCode::Conflict);
|
|
CHECK_EQ(stream.size(), 0);
|
|
}
|