Files
SEQUA-Engine/core/tests/server/test_protocol.cpp
T
Ange-Marie MAURIN - A2M Systèmes 142e563fcc feat(core): serveur WebSocket du protocole Studio - Core
sequa::core_server : ProtocolSession (poignée de main hello -> welcome/error, JSON provisoire identique à Studio, même règle de compatibilité) séparée du transport CoreServer (IXWebSocket 12.1.0, écoute 127.0.0.1:9100). Exécutable sequa-core. Vérification exclusive du port sous Windows (IXWebSocket active SO_REUSEADDR). Sanitizers appliqués à tout le build (ASan MSVC exige des dépendances instrumentées). ADR 0005. 30 tests (Debug, Release, ASan), dont 4 sur vrais sockets.
2026-11-06 21:23:37 +01:00

111 lines
4.0 KiB
C++

// SPDX-License-Identifier: MPL-2.0
#include "sequa/core/server/protocol.hpp"
#include <doctest/doctest.h>
#include <nlohmann/json.hpp>
#include <string>
using namespace sequa::core::server;
using nlohmann::json;
namespace {
const std::string hello = encode_hello(protocol_version, "sequa-studio");
json single_message(const Reply& reply)
{
REQUIRE(reply.messages.size() == 1);
return json::parse(reply.messages.front());
}
void check_rejected(ProtocolSession& session, const std::string& text, const std::string& expected_code)
{
const Reply reply = session.on_text(text);
const json message = single_message(reply);
CHECK(message["type"] == "error");
CHECK(message["payload"]["code"] == expected_code);
CHECK(reply.close);
CHECK(reply.close_reason.size() <= 123);
CHECK(session.state() == ProtocolSession::State::closed);
}
} // namespace
TEST_CASE("Protocole : compatibilite des versions, meme regle que Studio")
{
CHECK(is_compatible_protocol_version("0.1.0"));
CHECK(is_compatible_protocol_version("0.1.5"));
CHECK(is_compatible_protocol_version("0.1.0-alpha.1"));
CHECK_FALSE(is_compatible_protocol_version("0.2.0"));
CHECK_FALSE(is_compatible_protocol_version("0.9.2"));
CHECK_FALSE(is_compatible_protocol_version("1.0.0"));
CHECK_FALSE(is_compatible_protocol_version("0.1"));
CHECK_FALSE(is_compatible_protocol_version("0.1.0x"));
CHECK_FALSE(is_compatible_protocol_version("inconnue"));
CHECK_FALSE(is_compatible_protocol_version(""));
}
TEST_CASE("Protocole : hello valide -> welcome, puis session prete")
{
ProtocolSession session("1.2.3");
const Reply reply = session.on_text(hello);
const json welcome = single_message(reply);
CHECK_FALSE(reply.close);
CHECK(welcome["v"] == envelope_version);
CHECK(welcome["type"] == "welcome");
CHECK(welcome["payload"]["protocolVersion"] == std::string(protocol_version));
CHECK(welcome["payload"]["coreVersion"] == "1.2.3");
CHECK(session.state() == ProtocolSession::State::ready);
}
TEST_CASE("Protocole : poignee de main refusee")
{
ProtocolSession session("1.0.0");
SUBCASE("JSON invalide") { check_rejected(session, "{pas du json", "invalid-json"); }
SUBCASE("enveloppe non objet") { check_rejected(session, "[1,2]", "invalid-envelope"); }
SUBCASE("v manquant") { check_rejected(session, R"({"type":"hello","payload":{}})", "invalid-envelope"); }
SUBCASE("v non pris en charge")
{
check_rejected(session, R"({"v":2,"type":"hello","payload":{}})", "unsupported-version");
}
SUBCASE("payload absent") { check_rejected(session, R"({"v":1,"type":"hello"})", "invalid-payload"); }
SUBCASE("autre message avant hello")
{
check_rejected(session, encode_welcome("0.1.0", "x"), "handshake-required");
}
SUBCASE("client inconnu")
{
check_rejected(session, encode_hello(protocol_version, "autre-outil"), "invalid-payload");
}
SUBCASE("version incompatible")
{
check_rejected(session, encode_hello("0.2.0", "sequa-studio"), "incompatible-protocol");
}
SUBCASE("version tres longue : raison de fermeture tronquee")
{
check_rejected(session, encode_hello("9." + std::string(300, '9') + ".0", "sequa-studio"),
"incompatible-protocol");
}
}
TEST_CASE("Protocole : binaire refuse, session fermee muette")
{
ProtocolSession session("1.0.0");
const Reply reply = session.on_binary();
CHECK(reply.close);
CHECK(reply.close_code == close_code::unsupported_data);
CHECK(session.on_text(hello).messages.empty());
}
TEST_CASE("Protocole : message apres la poignee de main -> not-implemented sans fermer")
{
ProtocolSession session("1.0.0");
(void)session.on_text(hello);
const Reply reply = session.on_text(R"({"v":1,"type":"scene/get","payload":{}})");
CHECK(single_message(reply)["payload"]["code"] == "not-implemented");
CHECK_FALSE(reply.close);
CHECK(session.state() == ProtocolSession::State::ready);
}