123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- #include <atomic>
- #include <nlohmann_json/json.hpp>
- #include "kbf.h"
- #include "kbf/wifi_legacy.h"
- #include "kbf/http/client.h"
- #include "kbf/http/server.h"
- #include "kbf/http/exception.h"
- #include <unity.h>
- #define KBF_TEST_HEADER_VAL "THISis1337"
- #define KBF_TEST_HEADER_RESPONSE "X-Secret-Response"
- #define KBF_TEST_HEADER_REQUEST "X-Secret-Request"
- using namespace kbf;
- using namespace std;
- using nlohmann::json;
- std::atomic<bool> asyncFinished = {false};
- std::atomic<int> state;
- TEST_CASE("HTTP GET, POST, 404, 405", "[kbf_http]") {
- wifi_legacy::start();
- auto server = http::Server();
- TEST_ASSERT_FALSE(server.isRunning())
- http::Response (*handleGet)(const http::Request &, void *) = {[](const http::Request &request, void *) {
- TEST_ASSERT_EQUAL(http::GET, request.method);
- TEST_ASSERT_EQUAL_STRING("/get-only", request.uri.c_str());
- return http::Response("OK");
- }};
- server.route({http::GET, "/get-only", handleGet, nullptr});
- http::Response (*handlePost)(const http::Request &, void *) = {[](const http::Request &request, void *) {
- TEST_ASSERT_EQUAL(http::POST, request.method);
- TEST_ASSERT_EQUAL_STRING("/post-only", request.uri.c_str());
- return http::Response("OK");
- }};
- server.route({http::POST, "/post-only", handlePost, nullptr});
- http::Response (*handleGetAndPost)(const http::Request &, void *) = {[](const http::Request &request, void *) {
- TEST_ASSERT_EQUAL_STRING("/get-and-post", request.uri.c_str());
- if (request.method == http::GET) {
- return http::Response("GET");
- } else if (request.method == http::POST) {
- TEST_ASSERT_EQUAL_STRING("application/json", request.headers.at("Content-Type").c_str());
- auto requestJson = json::parse(request.body);
- string response = requestJson["foo"];
- return http::Response(response);
- } else {
- TEST_FAIL();
- return http::Response("fail"); // unreachable but the compiler moans otherwise
- }
- }};
- server.route({http::GET, "/get-and-post", handleGetAndPost, nullptr});
- server.route({http::POST, "/get-and-post", handleGetAndPost, nullptr});
- server.start();
- TEST_ASSERT_TRUE(server.isRunning())
- auto client = http::Client();
- auto response = client.get("http://localhost/get-only");
- TEST_ASSERT_EQUAL(200, response->status);
- TEST_ASSERT_EQUAL_STRING("OK", response->body.c_str());
- response = client.get("http://localhost/non-existent");
- TEST_ASSERT_EQUAL(404, response->status);
- TEST_ASSERT_EQUAL_STRING("This URI does not exist", response->body.c_str());
- response = client.get("http://localhost/post-only");
- TEST_ASSERT_EQUAL(405, response->status);
- TEST_ASSERT_EQUAL_STRING("Request method for this URI is not handled by server", response->body.c_str());
- response = client.get("http://localhost/get-and-post");
- TEST_ASSERT_EQUAL(200, response->status);
- TEST_ASSERT_EQUAL_STRING("GET", response->body.c_str());
- json data = {{"foo", "bar"}};
- response = client.post("http://localhost/get-and-post", data);
- TEST_ASSERT_EQUAL_STRING("bar", response->body.c_str());
- server.stop();
- wifi_legacy::stop();
- }
- TEST_CASE("HTTP custom headers", "[kbf_http]") {
- wifi_legacy::start();
- auto server = http::Server();
- http::Response (*handleContentTypeTest)(const http::Request &, void *) = {[](const http::Request &request, void *) {
- auto response = http::Response("OK");
- if (!request.query.empty() && request.query.at("type") == "txt") {
- response.contentType = "text/plain";
- }
- return response;
- }};
- server.route({http::GET, "/content-type-test", handleContentTypeTest, nullptr});
- http::Response (*handleHeaderTest)(const http::Request &, void *) = {[](const http::Request &request, void *) {
- auto response = http::Response("OK");
- const string &header = request.readHeader(KBF_TEST_HEADER_REQUEST);
- TEST_ASSERT_EQUAL_STRING(KBF_TEST_HEADER_VAL, header.c_str());
- response.headers[KBF_TEST_HEADER_RESPONSE] = header;
- return response;
- }};
- server.route({http::GET, "/custom-header-test", handleHeaderTest, nullptr});
- server.start();
- auto client = http::Client();
- auto response = client.get("http://localhost/content-type-test");
- TEST_ASSERT_EQUAL_STRING("text/html", response->headers.at("Content-Type").c_str());
- TEST_ASSERT_EQUAL_STRING("OK", response->body.data());
- response = client.get("http://localhost/content-type-test?type=txt");
- TEST_ASSERT_EQUAL_STRING("text/plain", response->headers.at("Content-Type").c_str());
- TEST_ASSERT_EQUAL_STRING("OK", response->body.data());
- response = client.get("http://localhost/custom-header-test", {{{KBF_TEST_HEADER_REQUEST, KBF_TEST_HEADER_VAL}}});
- TEST_ASSERT_EQUAL_STRING(KBF_TEST_HEADER_VAL, response->headers.at(KBF_TEST_HEADER_RESPONSE).c_str());
- TEST_ASSERT_EQUAL_STRING("OK", response->body.data());
- server.stop();
- wifi_legacy::stop();
- }
- TEST_CASE("HTTP JSON request / response", "[kbf_http]") {
- wifi_legacy::start();
- static const string testKey = "key";
- static const string testValue = "value";
- auto server = http::Server();
- http::Response (*handleJson)(const http::Request &, void *) = {[](const http::Request &request, void *) {
- TEST_ASSERT_EQUAL(http::POST, request.method);
- TEST_ASSERT_EQUAL_STRING("application/json", request.headers.at("Content-Type").c_str());
- auto requestJson = request.json();
- TEST_ASSERT_NOT_NULL(requestJson)
- auto responseJson = json({{testKey, requestJson.find(testKey)->get<string>()}});
- return http::Response(responseJson);
- }};
- http::Response (*handleNotJson)(const http::Request &, void *) = {[](const http::Request &request, void *) {
- auto requestJson = request.json();
- TEST_ASSERT_NULL(requestJson)
- return http::Response("OK");
- }};
- server.route({http::POST, "/json", handleJson, nullptr});
- server.route({http::POST, "/not-json", handleNotJson, nullptr});
- server.start();
- auto client = http::Client();
- auto response = client.post("http://localhost/json", {{testKey, testValue}});
- TEST_ASSERT_EQUAL_STRING("application/json", response->headers.at("Content-Type").c_str());
- auto responseJson = response->json();
- TEST_ASSERT_NOT_NULL(responseJson)
- TEST_ASSERT_EQUAL_STRING(testValue.c_str(), responseJson.find(testKey)->get<string>().c_str());
- // TODO enable after implementing support for posting data types other than JSON
- // response = client.post("http://localhost/not-json", "");
- // TEST_ASSERT_NULL(response->json());
- server.stop();
- wifi_legacy::stop();
- }
- TEST_CASE("HTTP timeout", "[kbf_http]") {
- wifi_legacy::start();
- auto server = http::Server();
- http::Response (*handler)(const http::Request &, void *) = {[](const http::Request &request, void *) {
- state++;
- kbf::sleep(stoi(request.query.at("sleep")));
- return http::Response("OK");
- }};
- server.route({http::GET, "/", handler, nullptr});
- server.start();
- state = 0;
- auto client = http::Client(1000);
- TEST_ASSERT_EQUAL_STRING("OK", client.get("http://localhost/?sleep=200")->body.c_str());
- TEST_ASSERT_EQUAL(1, state);
- bool caught = false;
- try {
- client.get("http://localhost/?sleep=1200");
- } catch (http::exception::Timeout &e) {
- caught = true;
- }
- TEST_ASSERT_TRUE(caught);
- TEST_ASSERT_EQUAL(2, state);
- wifi_legacy::stop();
- }
- TEST_CASE("HTTP onResponseSent", "[kbf_http]") {
- wifi_legacy::start();
- auto server = http::Server();
- state = 0;
- http::Response (*handleGet)(const http::Request &, void *) = {[](const http::Request &request, void *) {
- TEST_ASSERT_EQUAL(0, state++);
- return http::Response("OK");
- }};
- void (*onResponseSent)(const http::Response &, void *) = {[](const http::Response &response, void *) {
- TEST_ASSERT_EQUAL(1, state++);
- TEST_ASSERT_EQUAL(200, response.status);
- TEST_ASSERT_EQUAL_STRING("OK", response.body.c_str());
- }};
- server.route({http::GET, "/", handleGet, nullptr, onResponseSent});
- server.start();
- auto client = http::Client();
- auto response = client.get("http://localhost/");
- TEST_ASSERT_EQUAL(200, response->status);
- TEST_ASSERT_EQUAL_STRING("OK", response->body.c_str());
- TEST_ASSERT_EQUAL(2, state);
- server.stop();
- wifi_legacy::stop();
- }
- TEST_CASE("HTTP Client errors", "[kbf_http]") {
- wifi_legacy::start();
- auto server = http::Server();
- http::Response (*testGet)(const http::Request &, void *) = {[](const http::Request &request, void *) {
- TEST_FAIL();
- return http::Response();
- }};
- http::Response (*testPost)(const http::Request &, void *) = {[](const http::Request &request, void *) {
- TEST_ASSERT_EQUAL_STRING("hax", request.readHeader("X-Hax").c_str());
- return http::Response("OK");
- }};
- server.route({http::GET, "/test", testGet, nullptr});
- server.route({http::POST, "/test", testPost, nullptr});
- server.start();
- auto client = http::Client();
- auto response = client.get("http://localhost/non-existent");
- TEST_ASSERT_EQUAL(404, response->status);
- response = client.post("http://localhost/test", {{"foo", "bar"}}, {{{"X-Hax", "hax"}}});
- TEST_ASSERT_EQUAL_STRING("OK", response->body.c_str());
- bool caught = false;
- try {
- response = client.get("http://connection-refused.com/");
- } catch (http::exception::ConnectionError &e) {
- caught = true;
- }
- TEST_ASSERT_TRUE(caught);
- wifi_legacy::stop();
- }
- TEST_CASE("HTTP CORS", "[notimplemented]") {
- TEST_FAIL_MESSAGE("not yet implemented");
- }
- TEST_CASE("HTTP SPIFFS static route", "[notimplemented]") {
- TEST_FAIL_MESSAGE("not yet implemented");
- }
|