Unverified Commit 493e1077 authored by Dominik Charousset's avatar Dominik Charousset Committed by GitHub

Merge pull request #1459

Refactor test cases and unify mock socket app
parents c3b094f7 5985e72c
......@@ -6,7 +6,7 @@
#include "caf/detail/rfc6455.hpp"
#include "caf/test/dsl.hpp"
#include "net-test.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/span.hpp"
......@@ -39,15 +39,15 @@ struct fixture {
} // namespace
CAF_TEST_FIXTURE_SCOPE(rfc6455_tests, fixture)
BEGIN_FIXTURE_SCOPE(fixture)
CAF_TEST(masking) {
TEST_CASE("masking") {
auto key = uint32_t{0xDEADC0DE};
auto data = bytes({0x12, 0x34, 0x45, 0x67, 0x89, 0x9A});
auto masked_data = data;
CAF_MESSAGE("masking XORs the repeated key to data");
MESSAGE("masking XORs the repeated key to data");
impl::mask_data(key, masked_data);
CAF_CHECK_EQUAL(masked_data, bytes({
CHECK_EQ(masked_data, bytes({
0x12 ^ 0xDE,
0x34 ^ 0xAD,
0x45 ^ 0xC0,
......@@ -55,125 +55,125 @@ CAF_TEST(masking) {
0x89 ^ 0xDE,
0x9A ^ 0xAD,
}));
CAF_MESSAGE("masking masked data again gives the original data");
MESSAGE("masking masked data again gives the original data");
impl::mask_data(key, masked_data);
CAF_CHECK_EQUAL(masked_data, data);
CHECK_EQ(masked_data, data);
}
CAF_TEST(no mask key and no data) {
std::vector<uint8_t> data{};
TEST_CASE("no mask key and no data") {
std::vector<uint8_t> data;
byte_buffer out;
impl::assemble_frame(impl::binary_frame, 0, as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(out, bytes({
CHECK_EQ(out, bytes({
0x82, // FIN + binary frame opcode
0x00, // data size = 0
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 2);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0u);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
CHECK_EQ(impl::decode_header(out, hdr), 2);
CHECK_EQ(hdr.fin, true);
CHECK_EQ(hdr.mask_key, 0u);
CHECK_EQ(hdr.opcode, impl::binary_frame);
CHECK_EQ(hdr.payload_len, data.size());
}
CAF_TEST(valid mask key and no data) {
std::vector<uint8_t> data{};
TEST_CASE("valid mask key and no data") {
std::vector<uint8_t> data;
byte_buffer out;
impl::assemble_frame(impl::binary_frame, 0xDEADC0DE,
as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(out, bytes({
CHECK_EQ(out, bytes({
0x82, // FIN + binary frame opcode
0x80, // MASKED + data size = 0
0xDE, 0xAD, 0xC0, 0xDE, // mask key,
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 6);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0xDEADC0DEu);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
CHECK_EQ(impl::decode_header(out, hdr), 6);
CHECK_EQ(hdr.fin, true);
CHECK_EQ(hdr.mask_key, 0xDEADC0DE);
CHECK_EQ(hdr.opcode, impl::binary_frame);
CHECK_EQ(hdr.payload_len, data.size());
}
CAF_TEST(no mask key plus small data) {
TEST_CASE("no mask key plus small data") {
std::vector<uint8_t> data{0x12, 0x34, 0x45, 0x67};
byte_buffer out;
impl::assemble_frame(impl::binary_frame, 0, as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(out, bytes({
CHECK_EQ(out, bytes({
0x82, // FIN + binary frame opcode
0x04, // data size = 4
0x12, 0x34, 0x45, 0x67, // masked data
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 2);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0u);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
CHECK_EQ(impl::decode_header(out, hdr), 2);
CHECK_EQ(hdr.fin, true);
CHECK_EQ(hdr.mask_key, 0u);
CHECK_EQ(hdr.opcode, impl::binary_frame);
CHECK_EQ(hdr.payload_len, data.size());
}
CAF_TEST(valid mask key plus small data) {
TEST_CASE("valid mask key plus small data") {
std::vector<uint8_t> data{0x12, 0x34, 0x45, 0x67};
byte_buffer out;
impl::assemble_frame(impl::binary_frame, 0xDEADC0DE,
as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(out, bytes({
CHECK_EQ(out, bytes({
0x82, // FIN + binary frame opcode
0x84, // MASKED + data size = 4
0xDE, 0xAD, 0xC0, 0xDE, // mask key,
0x12, 0x34, 0x45, 0x67, // masked data
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 6);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0xDEADC0DEu);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
CHECK_EQ(impl::decode_header(out, hdr), 6);
CHECK_EQ(hdr.fin, true);
CHECK_EQ(hdr.mask_key, 0xDEADC0DE);
CHECK_EQ(hdr.opcode, impl::binary_frame);
CHECK_EQ(hdr.payload_len, data.size());
}
CAF_TEST(no mask key plus upper bound on small data) {
TEST_CASE("no mask key plus upper bound on small data") {
std::vector<uint8_t> data;
data.resize(125, 0xFF);
byte_buffer out;
impl::assemble_frame(impl::binary_frame, 0, as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(take(out, 6), bytes({
CHECK_EQ(take(out, 6), bytes({
0x82, // FIN + binary frame opcode
0x7D, // data size = 125
0xFF, 0xFF, 0xFF, 0xFF, // masked data
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 2);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0u);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
CHECK_EQ(impl::decode_header(out, hdr), 2);
CHECK_EQ(hdr.fin, true);
CHECK_EQ(hdr.mask_key, 0u);
CHECK_EQ(hdr.opcode, impl::binary_frame);
CHECK_EQ(hdr.payload_len, data.size());
}
CAF_TEST(valid mask key plus upper bound on small data) {
TEST_CASE("valid mask key plus upper bound on small data") {
std::vector<uint8_t> data;
data.resize(125, 0xFF);
byte_buffer out;
impl::assemble_frame(impl::binary_frame, 0xDEADC0DE,
as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(take(out, 10), bytes({
CHECK_EQ(take(out, 10), bytes({
0x82, // FIN + binary frame opcode
0xFD, // MASKED + data size = 125
0xDE, 0xAD, 0xC0, 0xDE, // mask key,
0xFF, 0xFF, 0xFF, 0xFF, // masked data
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 6);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0xDEADC0DEu);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
CHECK_EQ(impl::decode_header(out, hdr), 6);
CHECK_EQ(hdr.fin, true);
CHECK_EQ(hdr.mask_key, 0xDEADC0DE);
CHECK_EQ(hdr.opcode, impl::binary_frame);
CHECK_EQ(hdr.payload_len, data.size());
}
CAF_TEST(no mask key plus medium data) {
TEST_CASE("no mask key plus medium data") {
std::vector<uint8_t> data;
data.insert(data.end(), 126, 0xFF);
data.resize(126, 0xFF);
byte_buffer out;
impl::assemble_frame(impl::binary_frame, 0, as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(take(out, 8),
CHECK_EQ(take(out, 8),
bytes({
0x82, // FIN + binary frame opcode
0x7E, // 126 -> uint16 size
......@@ -181,20 +181,20 @@ CAF_TEST(no mask key plus medium data) {
0xFF, 0xFF, 0xFF, 0xFF, // first 4 masked bytes
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 4);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0u);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
CHECK_EQ(impl::decode_header(out, hdr), 4);
CHECK_EQ(hdr.fin, true);
CHECK_EQ(hdr.mask_key, 0u);
CHECK_EQ(hdr.opcode, impl::binary_frame);
CHECK_EQ(hdr.payload_len, data.size());
}
CAF_TEST(valid mask key plus medium data) {
TEST_CASE("valid mask key plus medium data") {
std::vector<uint8_t> data;
data.insert(data.end(), 126, 0xFF);
data.resize(126, 0xFF);
byte_buffer out;
impl::assemble_frame(impl::binary_frame, 0xDEADC0DE,
as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(take(out, 12),
CHECK_EQ(take(out, 12),
bytes({
0x82, // FIN + binary frame opcode
0xFE, // MASKED + 126 -> uint16 size
......@@ -203,19 +203,19 @@ CAF_TEST(valid mask key plus medium data) {
0xFF, 0xFF, 0xFF, 0xFF, // first 4 masked bytes
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 8);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0xDEADC0DEu);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
CHECK_EQ(impl::decode_header(out, hdr), 8);
CHECK_EQ(hdr.fin, true);
CHECK_EQ(hdr.mask_key, 0xDEADC0DE);
CHECK_EQ(hdr.opcode, impl::binary_frame);
CHECK_EQ(hdr.payload_len, data.size());
}
CAF_TEST(no mask key plus upper bound on medium data) {
TEST_CASE("no mask key plus upper bound on medium data") {
std::vector<uint8_t> data;
data.insert(data.end(), 65535, 0xFF);
data.resize(65535, 0xFF);
byte_buffer out;
impl::assemble_frame(impl::binary_frame, 0, as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(take(out, 8),
CHECK_EQ(take(out, 8),
bytes({
0x82, // FIN + binary frame opcode
0x7E, // 126 -> uint16 size
......@@ -223,20 +223,20 @@ CAF_TEST(no mask key plus upper bound on medium data) {
0xFF, 0xFF, 0xFF, 0xFF, // first 4 masked bytes
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 4);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0u);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
CHECK_EQ(impl::decode_header(out, hdr), 4);
CHECK_EQ(hdr.fin, true);
CHECK_EQ(hdr.mask_key, 0u);
CHECK_EQ(hdr.opcode, impl::binary_frame);
CHECK_EQ(hdr.payload_len, data.size());
}
CAF_TEST(valid mask key plus upper bound on medium data) {
TEST_CASE("valid mask key plus upper bound on medium data") {
std::vector<uint8_t> data;
data.insert(data.end(), 65535, 0xFF);
data.resize(65535, 0xFF);
byte_buffer out;
impl::assemble_frame(impl::binary_frame, 0xDEADC0DE,
as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(take(out, 12),
CHECK_EQ(take(out, 12),
bytes({
0x82, // FIN + binary frame opcode
0xFE, // 126 -> uint16 size
......@@ -245,19 +245,19 @@ CAF_TEST(valid mask key plus upper bound on medium data) {
0xFF, 0xFF, 0xFF, 0xFF, // first 4 masked bytes
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 8);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0xDEADC0DEu);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
CHECK_EQ(impl::decode_header(out, hdr), 8);
CHECK_EQ(hdr.fin, true);
CHECK_EQ(hdr.mask_key, 0xDEADC0DE);
CHECK_EQ(hdr.opcode, impl::binary_frame);
CHECK_EQ(hdr.payload_len, data.size());
}
CAF_TEST(no mask key plus large data) {
TEST_CASE("no mask key plus large data") {
std::vector<uint8_t> data;
data.insert(data.end(), 65536, 0xFF);
data.resize(65536, 0xFF);
byte_buffer out;
impl::assemble_frame(impl::binary_frame, 0, as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(take(out, 14),
CHECK_EQ(take(out, 14),
bytes({
0x82, // FIN + binary frame opcode
0x7F, // 127 -> uint64 size
......@@ -265,20 +265,20 @@ CAF_TEST(no mask key plus large data) {
0xFF, 0xFF, 0xFF, 0xFF, // first 4 masked bytes
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 10);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0u);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
CHECK_EQ(impl::decode_header(out, hdr), 10);
CHECK_EQ(hdr.fin, true);
CHECK_EQ(hdr.mask_key, 0u);
CHECK_EQ(hdr.opcode, impl::binary_frame);
CHECK_EQ(hdr.payload_len, data.size());
}
CAF_TEST(valid mask key plus large data) {
TEST_CASE("valid mask key plus large data") {
std::vector<uint8_t> data;
data.insert(data.end(), 65536, 0xFF);
data.resize(65536, 0xFF);
byte_buffer out;
impl::assemble_frame(impl::binary_frame, 0xDEADC0DE,
as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(take(out, 18),
CHECK_EQ(take(out, 18),
bytes({
0x82, // FIN + binary frame opcode
0xFF, // MASKED + 127 -> uint64 size
......@@ -287,11 +287,11 @@ CAF_TEST(valid mask key plus large data) {
0xFF, 0xFF, 0xFF, 0xFF, // first 4 masked bytes
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 14);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0xDEADC0DEu);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
CHECK_EQ(impl::decode_header(out, hdr), 14);
CHECK_EQ(hdr.fin, true);
CHECK_EQ(hdr.mask_key, 0xDEADC0DE);
CHECK_EQ(hdr.opcode, impl::binary_frame);
CHECK_EQ(hdr.payload_len, data.size());
}
CAF_TEST_FIXTURE_SCOPE_END()
END_FIXTURE_SCOPE()
......@@ -118,6 +118,61 @@ ptrdiff_t mock_stream_transport::handle_input() {
return result;
}
// -- mock_web_socket_app ------------------------------------------------------
mock_web_socket_app::mock_web_socket_app(bool request_messages_on_start)
: request_messages_on_start(request_messages_on_start) {
// nop
}
caf::error mock_web_socket_app::start(caf::net::web_socket::lower_layer* ll) {
down = ll;
if (request_messages_on_start)
down->request_messages();
return caf::none;
}
void mock_web_socket_app::prepare_send() {
// nop
}
bool mock_web_socket_app::done_sending() {
return true;
}
caf::error
mock_web_socket_app::accept(const caf::net::http::request_header& hdr) {
// Store the request information in cfg to evaluate them later.
auto& ws = cfg["web-socket"].as_dictionary();
put(ws, "method", to_rfc_string(hdr.method()));
put(ws, "path", std::string{hdr.path()});
put(ws, "query", hdr.query());
put(ws, "fragment", hdr.fragment());
put(ws, "http-version", hdr.version());
if (hdr.num_fields() > 0) {
auto& fields = ws["fields"].as_dictionary();
hdr.for_each_field([&fields](auto key, auto val) {
put(fields, std::string{key}, std::string{val});
});
}
return caf::none;
}
void mock_web_socket_app::abort(const caf::error& reason) {
abort_reason = reason;
down->shutdown(reason);
}
ptrdiff_t mock_web_socket_app::consume_text(std::string_view text) {
text_input.insert(text_input.end(), text.begin(), text.end());
return static_cast<ptrdiff_t>(text.size());
}
ptrdiff_t mock_web_socket_app::consume_binary(caf::byte_span bytes) {
binary_input.insert(binary_input.end(), bytes.begin(), bytes.end());
return static_cast<ptrdiff_t>(bytes.size());
}
// -- barrier ------------------------------------------------------------------
void barrier::arrive_and_wait() {
......
......@@ -5,11 +5,16 @@
#include "caf/net/octet_stream/upper_layer.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/web_socket/server.hpp"
#include "caf/settings.hpp"
#include "caf/span.hpp"
#include "caf/string_view.hpp"
#include "caf/test/bdd_dsl.hpp"
/// Implements a trivial transport layer that stores the contents of all
/// received frames in a respective output buffer, it can propagate the content
/// of the input buffer to the upper layer, and switch protocols if configured
/// so.
class mock_stream_transport : public caf::net::octet_stream::lower_layer {
public:
// -- member types -----------------------------------------------------------
......@@ -105,6 +110,67 @@ private:
caf::net::multiplexer* mpx_;
};
/// Tag used to configure mock_web_socket_app to request messages on start
struct request_messages_on_start_t {};
constexpr auto request_messages_on_start = request_messages_on_start_t{};
/// Implements a trivial WebSocket application that stores the contents of all
/// received messages in respective text/binary buffers. It can take both
/// roles, server and client, request messages and track whether the
/// lower layer was aborted.
class mock_web_socket_app : public caf::net::web_socket::upper_layer::server {
public:
// -- constructor ------------------------------------------------------------
explicit mock_web_socket_app(bool request_messages_on_start);
// -- factories --------------------------------------------------------------
static auto make(request_messages_on_start_t) {
return std::make_unique<mock_web_socket_app>(true);
}
static auto make() {
return std::make_unique<mock_web_socket_app>(false);
}
// -- initialization ---------------------------------------------------------
caf::error start(caf::net::web_socket::lower_layer* ll) override;
// -- implementation ---------------------------------------------------------
caf::error accept(const caf::net::http::request_header& hdr) override;
void prepare_send() override;
bool done_sending() override;
void abort(const caf::error& reason) override;
ptrdiff_t consume_text(std::string_view text) override;
ptrdiff_t consume_binary(caf::byte_span bytes) override;
bool has_aborted() const noexcept {
return !abort_reason.empty();
}
// -- member variables -------------------------------------------------------
std::string text_input;
caf::byte_buffer binary_input;
caf::net::web_socket::lower_layer* down = nullptr;
caf::settings cfg;
bool request_messages_on_start = false;
caf::error abort_reason;
};
// Drop-in replacement for std::barrier (based on the TS API as of 2020).
class barrier {
public:
......
......@@ -14,43 +14,6 @@ namespace {
using svec = std::vector<std::string>;
class app_t : public net::web_socket::upper_layer {
public:
static auto make() {
return std::make_unique<app_t>();
}
std::string text_input;
caf::byte_buffer binary_input;
error start(net::web_socket::lower_layer*) override {
return none;
}
void prepare_send() override {
// nop
}
bool done_sending() override {
return true;
}
void abort(const error& reason) override {
CAF_FAIL("app::abort called: " << reason);
}
ptrdiff_t consume_text(std::string_view text) override {
text_input.insert(text_input.end(), text.begin(), text.end());
return static_cast<ptrdiff_t>(text.size());
}
ptrdiff_t consume_binary(byte_span bytes) override {
binary_input.insert(binary_input.end(), bytes.begin(), bytes.end());
return static_cast<ptrdiff_t>(bytes.size());
}
};
constexpr std::string_view key = "the sample nonce";
constexpr std::string_view http_request
......@@ -93,7 +56,8 @@ auto make_handshake() {
SCENARIO("the client performs the WebSocket handshake on startup") {
GIVEN("valid WebSocket handshake data") {
WHEN("starting a WebSocket client") {
auto app = app_t::make();
auto app = mock_web_socket_app::make();
auto app_ptr = app.get();
auto ws = net::web_socket::client::make(make_handshake(), std::move(app));
auto uut = mock_stream_transport::make(std::move(ws));
THEN("the client sends its HTTP request when initializing it") {
......@@ -105,6 +69,9 @@ SCENARIO("the client performs the WebSocket handshake on startup") {
CHECK_EQ(uut->handle_input(),
static_cast<ptrdiff_t>(http_response.size()));
}
AND("the client did not abort") {
CHECK(!app_ptr->has_aborted());
}
}
}
}
......@@ -13,59 +13,13 @@ using namespace std::literals;
namespace {
class app_t : public net::web_socket::upper_layer {
public:
static auto make() {
return std::make_unique<app_t>();
}
std::string text_input;
caf::byte_buffer binary_input;
error err;
bool aborted = false;
net::web_socket::lower_layer* down = nullptr;
error start(net::web_socket::lower_layer* ll) override {
down = ll;
return none;
}
void prepare_send() override {
// nop
}
bool done_sending() override {
return true;
}
void abort(const error& reason) override {
aborted = true;
err = reason;
down->shutdown(reason);
}
ptrdiff_t consume_text(std::string_view text) override {
text_input.insert(text_input.end(), text.begin(), text.end());
return static_cast<ptrdiff_t>(text.size());
}
ptrdiff_t consume_binary(byte_span bytes) override {
binary_input.insert(binary_input.end(), bytes.begin(), bytes.end());
return static_cast<ptrdiff_t>(bytes.size());
}
};
struct fixture {
app_t* app;
mock_web_socket_app* app;
net::web_socket::framing* uut;
std::unique_ptr<mock_stream_transport> transport;
fixture() {
auto app_layer = app_t::make();
auto app_layer = mock_web_socket_app::make();
app = app_layer.get();
auto uut_layer
= net::web_socket::framing::make_server(std::move(app_layer));
......@@ -100,6 +54,9 @@ SCENARIO("the client sends a ping and receives a pong response") {
pong_frame);
CHECK_EQ(transport->output_buffer(), pong_frame);
}
AND("the client did not abort") {
CHECK(!app->has_aborted());
}
}
transport->output_buffer().clear();
WHEN("the client sends a ping with some data") {
......@@ -114,6 +71,9 @@ SCENARIO("the client sends a ping and receives a pong response") {
pong_frame);
CHECK_EQ(transport->output_buffer(), pong_frame);
}
AND("the client did not abort") {
CHECK(!app->has_aborted());
}
}
transport->output_buffer().clear();
WHEN("the client sends a ping with the maximum allowed 125 bytes") {
......@@ -128,6 +88,9 @@ SCENARIO("the client sends a ping and receives a pong response") {
pong_frame);
CHECK_EQ(transport->output_buffer(), pong_frame);
}
AND("the client did not abort") {
CHECK(!app->has_aborted());
}
}
}
}
......@@ -141,6 +104,7 @@ TEST_CASE("calling shutdown with protocol_error sets status in close header") {
auto status = (std::to_integer<int>(transport->output_buffer().at(2)) << 8)
+ std::to_integer<int>(transport->output_buffer().at(3));
CHECK_EQ(status, static_cast<int>(net::web_socket::status::protocol_error));
CHECK(!app->has_aborted());
}
SCENARIO("the client sends an invalid ping that closes the connection") {
......@@ -153,9 +117,9 @@ SCENARIO("the client sends an invalid ping that closes the connection") {
transport->push(ping_frame);
THEN("the server aborts the application") {
CHECK_EQ(transport->handle_input(), 0);
CHECK(app->aborted);
CHECK_EQ(app->err, sec::protocol_error);
MESSAGE("Aborted with: " << app->err);
CHECK(app->has_aborted());
CHECK_EQ(app->abort_reason, sec::protocol_error);
MESSAGE("Aborted with: " << app->abort_reason);
}
AND("the server closes the connection with a protocol error") {
detail::rfc6455::header hdr;
......
......@@ -20,67 +20,10 @@ namespace {
using svec = std::vector<std::string>;
class app_t : public net::web_socket::upper_layer::server {
public:
std::string text_input;
byte_buffer binary_input;
settings cfg;
static auto make() {
return std::make_unique<app_t>();
}
error start(net::web_socket::lower_layer* down) override {
down->request_messages();
return none;
}
error accept(const net::http::request_header& hdr) override {
// Store the request information in cfg to evaluate them later.
auto& ws = cfg["web-socket"].as_dictionary();
put(ws, "method", to_rfc_string(hdr.method()));
put(ws, "path", std::string{hdr.path()});
put(ws, "query", hdr.query());
put(ws, "fragment", hdr.fragment());
put(ws, "http-version", hdr.version());
if (hdr.num_fields() > 0) {
auto& fields = ws["fields"].as_dictionary();
hdr.for_each_field([&fields](auto key, auto val) {
put(fields, std::string{key}, std::string{val});
});
}
return none;
}
void prepare_send() override {
// nop
}
bool done_sending() override {
return true;
}
void abort(const error& reason) override {
CAF_FAIL("app::abort called: " << reason);
}
ptrdiff_t consume_text(std::string_view text) override {
text_input.insert(text_input.end(), text.begin(), text.end());
return static_cast<ptrdiff_t>(text.size());
}
ptrdiff_t consume_binary(byte_span bytes) override {
binary_input.insert(binary_input.end(), bytes.begin(), bytes.end());
return static_cast<ptrdiff_t>(bytes.size());
}
};
struct fixture {
fixture() {
using namespace caf::net;
auto app_ptr = app_t::make();
auto app_ptr = mock_web_socket_app::make(request_messages_on_start);
app = app_ptr.get();
auto ws_ptr = net::web_socket::server::make(std::move(app_ptr));
transport = mock_stream_transport::make(std::move(ws_ptr));
......@@ -126,7 +69,7 @@ struct fixture {
std::unique_ptr<mock_stream_transport> transport;
app_t* app;
mock_web_socket_app* app;
std::minstd_rand rng;
};
......@@ -148,9 +91,9 @@ constexpr std::string_view opening_handshake
if (CHECK(holds_alternative<std::string>(app->cfg, key))) \
CHECK_EQ(get<std::string>(app->cfg, key), expected_value);
CAF_TEST_FIXTURE_SCOPE(web_socket_tests, fixture)
BEGIN_FIXTURE_SCOPE(fixture)
CAF_TEST(applications receive handshake data via config) {
TEST_CASE("applications receive handshake data via config") {
transport->push(opening_handshake);
{
auto consumed = transport->handle_input();
......@@ -174,9 +117,10 @@ CAF_TEST(applications receive handshake data via config) {
using str_map = std::map<std::string, std::string>;
if (auto query = get_as<str_map>(app->cfg, "web-socket.query"); CHECK(query))
CHECK_EQ(*query, str_map({{"room"s, "lounge"s}}));
CHECK(!app->has_aborted());
}
CAF_TEST(the server responds with an HTTP response on success) {
TEST_CASE("the server responds with an HTTP response on success") {
transport->push(opening_handshake);
CHECK_EQ(transport->handle_input(),
static_cast<ptrdiff_t>(opening_handshake.size()));
......@@ -185,9 +129,10 @@ CAF_TEST(the server responds with an HTTP response on success) {
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n");
CHECK(!app->has_aborted());
}
CAF_TEST(handshakes may arrive in chunks) {
TEST_CASE("handshakes may arrive in chunks") {
svec bufs;
size_t chunk_size = opening_handshake.size() / 3;
auto i = opening_handshake.begin();
......@@ -203,9 +148,10 @@ CAF_TEST(handshakes may arrive in chunks) {
transport->push(bufs[2]);
CHECK_EQ(static_cast<size_t>(transport->handle_input()),
opening_handshake.size());
CHECK(!app->has_aborted());
}
CAF_TEST(data may follow the handshake immediately) {
TEST_CASE("data may follow the handshake immediately") {
auto hs_bytes = as_bytes(make_span(opening_handshake));
byte_buffer buf{hs_bytes.begin(), hs_bytes.end()};
rfc6455_append("Hello WebSocket!\n"sv, buf);
......@@ -213,18 +159,20 @@ CAF_TEST(data may follow the handshake immediately) {
transport->push(buf);
CHECK_EQ(transport->handle_input(), static_cast<ptrdiff_t>(buf.size()));
CHECK_EQ(app->text_input, "Hello WebSocket!\nBye WebSocket!\n");
CHECK(!app->has_aborted());
}
CAF_TEST(data may arrive later) {
TEST_CASE("data may arrive later") {
transport->push(opening_handshake);
CHECK_EQ(transport->handle_input(),
static_cast<ptrdiff_t>(opening_handshake.size()));
push("Hello WebSocket!\nBye WebSocket!\n"sv);
transport->handle_input();
CHECK_EQ(app->text_input, "Hello WebSocket!\nBye WebSocket!\n");
CHECK(!app->has_aborted());
}
CAF_TEST(data may arrive fragmented) {
TEST_CASE("data may arrive fragmented") {
transport->push(opening_handshake);
CHECK_EQ(transport->handle_input(),
static_cast<ptrdiff_t>(opening_handshake.size()));
......@@ -237,6 +185,7 @@ CAF_TEST(data may arrive fragmented) {
transport->push(buf);
CHECK_EQ(transport->handle_input(), static_cast<ptrdiff_t>(buf.size()));
CHECK_EQ(app->text_input, "Hello WebSocket!\nBye WebSocket!\n");
CHECK(!app->has_aborted());
}
CAF_TEST_FIXTURE_SCOPE_END()
END_FIXTURE_SCOPE()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment