- New dto/InternalDto.hpp with JsonErrorDto, WsEntityEventDto, WsPresenceUpdateDto, WsClientMsgDto. - JsonErrorHandler: now takes a shared ObjectMapper (DI). Body built via writeToString on JsonErrorDto. Closes the audit's concrete bug where status.description was embedded raw — a Status with a `"`/`\\` in the description previously emitted invalid JSON. - AuthInterceptor: takes an optional ObjectMapper ctor arg (defaults to a fresh mapper). makeForbidden's `msg` is now serialised via JsonErrorDto + ObjectMapper, so a `"` in a forbidden-reason no longer breaks the response envelope. - Hub: process-wide sharedMapper() with optional setObjectMapper() override. buildPresenceMsg / notifyBooking / notifyPerson all go through ObjectMapper-emitted DTOs. User-supplied IDs / property IDs / usernames containing `"`/`\\`/control chars are now escaped. - Listener: jsonStr/jsonInt regex parsers gone. handleMessage parses inbound frames via ObjectMapper::readFromString into WsClientMsgDto. Malformed JSON / nested objects / escaped quotes — previously silent corruption — now produce a clean drop of the frame. - test/test_json_serialization.cpp: 4 cases pinning the round-trip behaviour (special chars in usernames, IDs, status.description, and malformed-input rejection). Bump to 0.4.0 — ctor signatures changed (additive defaults, but the behaviour of the JSON envelopes is now governed by ObjectMapper). Closes #6 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
97 lines
3.8 KiB
C++
97 lines
3.8 KiB
C++
// Tests for the #6 ObjectMapper migration — verifies that the JSON envelopes
|
|
// produced by JsonErrorHandler / Hub::buildEntityEvent / Hub::buildPresenceMsg
|
|
// escape special characters instead of emitting raw text. The previous
|
|
// hand-rolled concatenations broke when given a `"`/`\\`/control-char string.
|
|
|
|
// Avoid pulling Hub.hpp here — it includes oatpp-websocket, which is a
|
|
// separate optional dependency not necessarily on the test target's include
|
|
// path. The escaping behaviour we care about is purely a property of
|
|
// ObjectMapper round-tripping the InternalDto types, so we exercise the
|
|
// DTOs directly.
|
|
#include "oatpp-authkit/handler/JsonErrorHandler.hpp"
|
|
#include "oatpp-authkit/dto/InternalDto.hpp"
|
|
|
|
#include "oatpp/parser/json/mapping/ObjectMapper.hpp"
|
|
#include "oatpp/web/protocol/http/Http.hpp"
|
|
|
|
#include <cstdio>
|
|
#include <set>
|
|
#include <string>
|
|
|
|
namespace {
|
|
|
|
int g_failures = 0;
|
|
|
|
#define REQUIRE(expr) do { \
|
|
if (!(expr)) { \
|
|
std::fprintf(stderr, "FAIL %s:%d %s\n", __FILE__, __LINE__, #expr); \
|
|
++g_failures; \
|
|
} \
|
|
} while (0)
|
|
|
|
void test_presence_dto_round_trips_special_chars() {
|
|
auto m = oatpp::parser::json::mapping::ObjectMapper::createShared();
|
|
auto dto = oatpp_authkit::dto::WsPresenceUpdateDto::createShared();
|
|
dto->type = oatpp::String("presence_update");
|
|
dto->booking_id = oatpp::String("id-with-\"-quote");
|
|
dto->users = {};
|
|
dto->users->push_back(oatpp::String("al\"ice"));
|
|
dto->users->push_back(oatpp::String("bo\\b"));
|
|
auto json = m->writeToString(dto);
|
|
|
|
auto rt = m->readFromString<oatpp::Object<oatpp_authkit::dto::WsPresenceUpdateDto>>(json);
|
|
REQUIRE(rt);
|
|
REQUIRE(std::string(*rt->booking_id) == "id-with-\"-quote");
|
|
REQUIRE(rt->users->size() == 2);
|
|
auto it = rt->users->begin();
|
|
REQUIRE(std::string(**it++) == "al\"ice");
|
|
REQUIRE(std::string(**it) == "bo\\b");
|
|
}
|
|
|
|
void test_entity_event_dto_round_trip() {
|
|
auto m = oatpp::parser::json::mapping::ObjectMapper::createShared();
|
|
auto dto = oatpp_authkit::dto::WsEntityEventDto::createShared();
|
|
dto->type = oatpp::String("booking_updated");
|
|
dto->id = oatpp::String("id-with-\"-and-\\");
|
|
auto json = m->writeToString(dto);
|
|
auto rt = m->readFromString<oatpp::Object<oatpp_authkit::dto::WsEntityEventDto>>(json);
|
|
REQUIRE(rt);
|
|
REQUIRE(std::string(*rt->id) == "id-with-\"-and-\\");
|
|
}
|
|
|
|
void test_client_msg_dto_rejects_malformed() {
|
|
auto m = oatpp::parser::json::mapping::ObjectMapper::createShared();
|
|
bool threw = false;
|
|
try {
|
|
m->readFromString<oatpp::Object<oatpp_authkit::dto::WsClientMsgDto>>(
|
|
oatpp::String("{not json"));
|
|
} catch (...) { threw = true; }
|
|
REQUIRE(threw);
|
|
}
|
|
|
|
void test_json_error_dto_round_trip() {
|
|
auto m = oatpp::parser::json::mapping::ObjectMapper::createShared();
|
|
auto dto = oatpp_authkit::dto::JsonErrorDto::createShared();
|
|
dto->status = oatpp::String("I'm a \"teapot\"");
|
|
dto->code = 418;
|
|
dto->message = oatpp::String("brew\nfailure");
|
|
auto json = m->writeToString(dto);
|
|
|
|
auto rt = m->readFromString<oatpp::Object<oatpp_authkit::dto::JsonErrorDto>>(json);
|
|
REQUIRE(rt);
|
|
REQUIRE(std::string(*rt->status) == "I'm a \"teapot\"");
|
|
REQUIRE(rt->code == 418);
|
|
REQUIRE(std::string(*rt->message) == "brew\nfailure");
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main() {
|
|
test_presence_dto_round_trips_special_chars();
|
|
test_entity_event_dto_round_trip();
|
|
test_client_msg_dto_rejects_malformed();
|
|
test_json_error_dto_round_trip();
|
|
|
|
std::printf("%s (%d failures)\n", g_failures ? "FAIL" : "OK", g_failures);
|
|
return g_failures ? 1 : 0;
|
|
}
|