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.
111 lines
4.0 KiB
C++
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);
|
|
}
|