Files
SEQUA-Engine/studio/host/client/tests/test_wire.cpp
T
Ange-Marie MAURIN - A2M Systèmes ea9f5709fa feat(studio/host): brique client, protocole 0.2 côté host
CoreClient (IXWebSocket) : connexion + hello sequa-studio-host, requêtes corrélées par id avec délais, requêtes concurrentes, erreur immédiate à la déconnexion, rappel on_disconnect. wire : hex64, base64 RFC 4648 strict, décodage des empreintes, first_divergence. 15 cas (Debug, ASan) contre un faux serveur conforme à la spec. Écrit par un subagent (DeepSeek V4.1 Flash), relu.
2026-11-06 22:43:30 +01:00

121 lines
4.4 KiB
C++

// SPDX-License-Identifier: MPL-2.0
//
// Tests des aides de représentation du protocole (spec §2, §7.6) : hex64, base64 RFC 4648,
// décodage d'empreintes et first_divergence.
#include "sequa/studio/host/wire.hpp"
#include <doctest/doctest.h>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <vector>
using namespace sequa::studio::host;
namespace {
std::vector<std::uint8_t> bytes_of(std::string_view text)
{
std::vector<std::uint8_t> out;
out.reserve(text.size());
for (const char c : text) {
out.push_back(static_cast<std::uint8_t>(c));
}
return out;
}
} // namespace
TEST_CASE("hostclient: hex64 aller-retour et refus stricts")
{
CHECK(to_hex64(0) == "0000000000000000");
CHECK(to_hex64(0x0123456789abcdefULL) == "0123456789abcdef");
CHECK(to_hex64(std::numeric_limits<std::uint64_t>::max()) == "ffffffffffffffff");
CHECK(from_hex64("0000000000000000") == 0U);
CHECK(from_hex64("0123456789abcdef") == 0x0123456789abcdefULL);
CHECK(from_hex64("ffffffffffffffff") == std::numeric_limits<std::uint64_t>::max());
// Refus strict : majuscules, longueur incorrecte, caractère hors alphabet.
CHECK_FALSE(from_hex64("0123456789ABCDEF").has_value());
CHECK_FALSE(from_hex64("0123456789abcde").has_value());
CHECK_FALSE(from_hex64("0123456789abcdef0").has_value());
CHECK_FALSE(from_hex64("0123456789abcdeg").has_value());
CHECK_FALSE(from_hex64("").has_value());
const std::uint64_t values[] = {1U, 255U, 65536U, 0xdeadbeefcafef00dULL};
for (const std::uint64_t value : values) {
CHECK(from_hex64(to_hex64(value)) == value);
CHECK(to_hex64(value).size() == hex64_length);
}
}
TEST_CASE("hostclient: base64 RFC 4648 (vecteurs et refus)")
{
// Vecteurs de test de la RFC 4648 §10.
CHECK(base64_encode("") == "");
CHECK(base64_encode("f") == "Zg==");
CHECK(base64_encode("fo") == "Zm8=");
CHECK(base64_encode("foo") == "Zm9v");
CHECK(base64_encode("foob") == "Zm9vYg==");
CHECK(base64_encode("fooba") == "Zm9vYmE=");
CHECK(base64_encode("foobar") == "Zm9vYmFy");
const auto decoded = base64_decode("Zm9vYmFy");
REQUIRE(decoded.has_value());
CHECK(*decoded == bytes_of("foobar"));
CHECK(base64_decode("") == std::optional<std::vector<std::uint8_t>>(std::vector<std::uint8_t>{}));
const auto one = base64_decode("Zg==");
REQUIRE(one.has_value());
CHECK(*one == bytes_of("f"));
// Refus stricts.
CHECK_FALSE(base64_decode("Zm9vYmF").has_value()); // longueur non multiple de 4
CHECK_FALSE(base64_decode("Zm9v!mFy").has_value()); // caractère hors alphabet
CHECK_FALSE(base64_decode("Zg=").has_value()); // remplissage incomplet
CHECK_FALSE(base64_decode("=Zg=").has_value()); // '=' en tête
CHECK_FALSE(base64_decode("Zg==Zg==").has_value()); // '=' au milieu
CHECK_FALSE(base64_decode("Zh==").has_value()); // bits de queue non nuls
// Aller-retour sur des octets binaires quelconques.
const std::vector<std::uint8_t> raw{0U, 1U, 2U, 250U, 255U};
const auto round_trip = base64_decode(base64_encode(std::span<const std::uint8_t>(raw)));
REQUIRE(round_trip.has_value());
CHECK(*round_trip == raw);
}
TEST_CASE("hostclient: decode_hashes et first_divergence")
{
const std::vector<std::uint64_t> a{1U, 2U, 3U, 4U};
CHECK(first_divergence(a, a) == std::nullopt);
const std::vector<std::uint64_t> b{1U, 2U, 99U, 4U};
CHECK(first_divergence(a, b) == std::size_t{2});
CHECK(first_divergence(b, a) == std::size_t{2});
const std::vector<std::uint64_t> prefix{1U, 2U};
CHECK(first_divergence(a, prefix) == std::size_t{2});
CHECK(first_divergence(prefix, a) == std::size_t{2});
// decode_hashes : tableau {1, 2} en uint64 petit-boutiste.
const std::vector<std::uint8_t> little_endian{1U, 0U, 0U, 0U, 0U, 0U, 0U, 0U,
2U, 0U, 0U, 0U, 0U, 0U, 0U, 0U};
const auto hashes = decode_hashes(base64_encode(std::span<const std::uint8_t>(little_endian)));
REQUIRE(hashes.has_value());
CHECK(*hashes == (std::vector<std::uint64_t>{1U, 2U}));
}
TEST_CASE("hostclient: decode_hashes refuse les longueurs invalides")
{
// 5 octets : pas un multiple de 8.
CHECK_FALSE(decode_hashes(base64_encode("trois")).has_value());
// Base64 invalide.
CHECK_FALSE(decode_hashes("!!").has_value());
}