From b14a3d2554274006712a8fc0e055b8e46f1b2ec7 Mon Sep 17 00:00:00 2001 From: mmd-osm Date: Mon, 20 Nov 2023 21:21:52 +0100 Subject: [PATCH] Added testcase for compressed changeset uploads Also added a few missing throws --- test/test_apidb_backend_changeset_uploads.cpp | 56 +++++++++++++++++-- test/test_parse_changeset_input.cpp | 16 +++--- test/test_request.cpp | 47 +++++++++++++++- 3 files changed, 105 insertions(+), 14 deletions(-) diff --git a/test/test_apidb_backend_changeset_uploads.cpp b/test/test_apidb_backend_changeset_uploads.cpp index 4ca450a1..7279ba30 100644 --- a/test/test_apidb_backend_changeset_uploads.cpp +++ b/test/test_apidb_backend_changeset_uploads.cpp @@ -14,6 +14,7 @@ #include "cgimap/routes.hpp" #include "cgimap/process_request.hpp" #include "cgimap/output_buffer.hpp" +#include "cgimap/zlib.hpp" #include "cgimap/api06/changeset_upload/osmchange_handler.hpp" #include "cgimap/api06/changeset_upload/osmchange_input_format.hpp" @@ -24,6 +25,29 @@ namespace { +std::string get_compressed_payload() +{ + std::stringstream body; + std::stringstream output; + + std::string payload = R"( + + + + + + + )"; + + // gzip compress payload + test_output_buffer tob(output, body); + zlib_output_buffer zlib_ob(tob, zlib_output_buffer::gzip); + zlib_ob.write(payload.data(), payload.size()); + zlib_ob.close(); + + return body.str(); +} + template void assert_equal(const T& a, const T&b, const std::string &message) { if (a != b) { @@ -2369,7 +2393,7 @@ namespace { process_request(req, limiter, generator, route, *sel_factory, upd_factory.get(), nullptr); if (req.response_status() != 409) - std::runtime_error("Expected HTTP 409 Conflict: Cannot add more elements to changeset"); + throw std::runtime_error("Expected HTTP 409 Conflict: Cannot add more elements to changeset"); } // Try to add a node to a changeset that is already closed @@ -2390,7 +2414,7 @@ namespace { process_request(req, limiter, generator, route, *sel_factory, upd_factory.get(), nullptr); if (req.response_status() != 409) - std::runtime_error("Expected HTTP 409 Conflict: Changeset already closed"); + throw std::runtime_error("Expected HTTP 409 Conflict: Changeset already closed"); } // Try to add a nodes, ways, relations to a changeset @@ -2453,7 +2477,7 @@ namespace { // std::cout << "Response was:\n----------------------\n" << req.buffer().str() << "\n"; if (req.response_status() != 200) - std::runtime_error("Expected HTTP 200 OK: Create new node"); + throw std::runtime_error("Expected HTTP 200 OK: Create new node"); } // Try to add, modify and delete nodes, ways, relations in changeset @@ -2509,7 +2533,7 @@ namespace { // std::cout << "Response was:\n----------------------\n" << req.buffer().str() << "\n"; if (req.response_status() != 200) - std::runtime_error("Expected HTTP 200 OK: add, modify and delete nodes, ways, relations in changeset"); + throw std::runtime_error("Expected HTTP 200 OK: add, modify and delete nodes, ways, relations in changeset"); } // Multiple operations on the same node id -1 @@ -2545,8 +2569,30 @@ namespace { // std::cout << "Response was:\n----------------------\n" << req.buffer().str() << "\n"; if (req.response_status() != 200) - std::runtime_error("Expected HTTP 200 OK: Multiple operations on the same node id -1"); + throw std::runtime_error("Expected HTTP 200 OK: Multiple operations on the same node id -1"); + + } + + // Compressed upload + { + // set up request headers from test case + test_request req; + req.set_header("REQUEST_METHOD", "POST"); + req.set_header("REQUEST_URI", "/api/0.6/changeset/1/upload"); + req.set_header("HTTP_AUTHORIZATION", baseauth); + req.set_header("REMOTE_ADDR", "127.0.0.1"); + req.set_header("HTTP_CONTENT_ENCODING", "gzip"); + + req.set_payload(get_compressed_payload()); + + // execute the request + process_request(req, limiter, generator, route, *sel_factory, upd_factory.get(), nullptr); + +// std::cerr << "Response status: " << req.response_status() << "\n"; +// std::cerr << "Response was:\n----------------------\n" << req.buffer().str() << "\n"; + if (req.response_status() != 200) + throw std::runtime_error("Expected HTTP 200 OK: Compressed upload"); } } diff --git a/test/test_parse_changeset_input.cpp b/test/test_parse_changeset_input.cpp index e849c020..b65e2d37 100644 --- a/test/test_parse_changeset_input.cpp +++ b/test/test_parse_changeset_input.cpp @@ -31,7 +31,7 @@ void test_changeset_xml() { "test_changeset_xml::001: Expected exception"); } catch (http::exception &e) { if (e.code() != 400) { - std::runtime_error("test_changeset_xml::001: Expected HTTP 400"); + throw std::runtime_error("test_changeset_xml::001: Expected HTTP 400"); } } @@ -42,7 +42,7 @@ void test_changeset_xml() { "test_changeset_xml::invalid:001: Expected exception"); } catch (http::exception &e) { if (e.code() != 400) { - std::runtime_error("test_changeset_xml::invalid:001: Expected HTTP 400"); + throw std::runtime_error("test_changeset_xml::invalid:001: Expected HTTP 400"); } } @@ -53,7 +53,7 @@ void test_changeset_xml() { "test_changeset_xml::invalid:002: Expected exception"); } catch (http::exception &e) { if (e.code() != 400) { - std::runtime_error("test_changeset_xml::invalid:002: Expected HTTP 400"); + throw std::runtime_error("test_changeset_xml::invalid:002: Expected HTTP 400"); } } @@ -64,7 +64,7 @@ void test_changeset_xml() { "test_changeset_xml::invalid:003 Expected exception"); } catch (http::exception &e) { if (e.code() != 400) { - std::runtime_error("test_changeset_xml::invalid:003: Expected HTTP 400"); + throw std::runtime_error("test_changeset_xml::invalid:003: Expected HTTP 400"); } } @@ -75,7 +75,7 @@ void test_changeset_xml() { "test_changeset_xml::invalid:004 Expected exception"); } catch (http::exception &e) { if (e.code() != 400) { - std::runtime_error("test_changeset_xml::invalid:004 Expected HTTP 400"); + throw std::runtime_error("test_changeset_xml::invalid:004 Expected HTTP 400"); } } @@ -86,7 +86,7 @@ void test_changeset_xml() { "test_changeset_xml::invalid:005 Expected exception"); } catch (http::exception &e) { if (e.code() != 400) { - std::runtime_error("test_changeset_xml::invalid:005: Expected HTTP 400"); + throw std::runtime_error("test_changeset_xml::invalid:005: Expected HTTP 400"); } } @@ -97,7 +97,7 @@ void test_changeset_xml() { "test_changeset_xml::invalid:006 Expected exception"); } catch (http::exception &e) { if (e.code() != 400) { - std::runtime_error("test_changeset_xml::invalid:006: Expected HTTP 400"); + throw std::runtime_error("test_changeset_xml::invalid:006: Expected HTTP 400"); } } @@ -108,7 +108,7 @@ void test_changeset_xml() { "test_changeset_xml::invalid:007: Expected exception"); } catch (http::exception &e) { if (e.code() != 400) { - std::runtime_error("test_changeset_xml::invalid:007: Expected HTTP 400"); + throw std::runtime_error("test_changeset_xml::invalid:007: Expected HTTP 400"); } } diff --git a/test/test_request.cpp b/test/test_request.cpp index de1c5d2a..41c8e6ab 100644 --- a/test/test_request.cpp +++ b/test/test_request.cpp @@ -1,7 +1,9 @@ #include "test_request.hpp" +#include "cgimap/options.hpp" #include "cgimap/request_helpers.hpp" #include +#include test_output_buffer::test_output_buffer(std::ostream &out, std::ostream &body) : m_out(out), m_body(body), m_written(0) { @@ -41,7 +43,50 @@ const char *test_request::get_param(const char *key) const { } const std::string test_request::get_payload() { - return m_payload; + + + // TODO: still a bit too much duplication from fcgi_request.cpp::get_payload + + const unsigned int BUFFER_LEN = 512000; + + // fetch and parse the content length + const char *content_length_str = m_params.find("CONTENT_LENGTH") != m_params.end() ? m_params["CONTENT_LENGTH"].c_str() : nullptr; + const char *content_encoding = m_params.find("HTTP_CONTENT_ENCODING") != m_params.end() ? m_params["HTTP_CONTENT_ENCODING"].c_str() : nullptr; + + auto content_encoding_handler = http::get_content_encoding_handler( + std::string(content_encoding == nullptr ? "" : content_encoding)); + + unsigned long content_length = 0; + unsigned long curr_content_length = 0; + unsigned long result_length = 0; + + if (content_length_str) + content_length = http::parse_content_length(content_length_str); + + std::array content_buffer{}; + + std::string result; + + std::string content(m_payload); + result_length += content.length(); + + // Decompression according to Content-Encoding header (null op, if header is not set) + try { + std::string content_decompressed = content_encoding_handler->decompress(content); + result += content_decompressed; + } catch (std::bad_alloc& e) { + throw http::server_error("Decompression failed due to memory issue"); + } catch (std::runtime_error& e) { + throw http::bad_request("Payload cannot be decompressed according to Content-Encoding"); + } + + if (result.length() > global_settings::get_payload_max_size()) + throw http::payload_too_large(fmt::format("Payload exceeds limit of {:d} bytes", global_settings::get_payload_max_size())); + + if (content_length > 0 && result_length != content_length) + throw http::server_error("HTTP Header field 'Content-Length' differs from actual payload length"); + + return result; } void test_request::set_payload(const std::string& payload) {