test_http.cpp 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. #include <atomic>
  2. #include <nlohmann_json/json.hpp>
  3. #include "kbf.h"
  4. #include "kbf/wifi_legacy.h"
  5. #include "kbf/http/client.h"
  6. #include "kbf/http/server.h"
  7. #include "kbf/http/exception.h"
  8. #include <unity.h>
  9. #define KBF_TEST_HEADER_VAL "THISis1337"
  10. #define KBF_TEST_HEADER_RESPONSE "X-Secret-Response"
  11. #define KBF_TEST_HEADER_REQUEST "X-Secret-Request"
  12. using namespace kbf;
  13. using namespace std;
  14. using nlohmann::json;
  15. std::atomic<bool> asyncFinished = {false};
  16. std::atomic<int> state;
  17. TEST_CASE("HTTP GET, POST, 404, 405", "[kbf_http]") {
  18. wifi_legacy::start();
  19. auto server = http::Server();
  20. TEST_ASSERT_FALSE(server.isRunning())
  21. http::Response (*handleGet)(const http::Request &, void *) = {[](const http::Request &request, void *) {
  22. TEST_ASSERT_EQUAL(http::GET, request.method);
  23. TEST_ASSERT_EQUAL_STRING("/get-only", request.uri.c_str());
  24. return http::Response("OK");
  25. }};
  26. server.route({http::GET, "/get-only", handleGet, nullptr});
  27. http::Response (*handlePost)(const http::Request &, void *) = {[](const http::Request &request, void *) {
  28. TEST_ASSERT_EQUAL(http::POST, request.method);
  29. TEST_ASSERT_EQUAL_STRING("/post-only", request.uri.c_str());
  30. return http::Response("OK");
  31. }};
  32. server.route({http::POST, "/post-only", handlePost, nullptr});
  33. http::Response (*handleGetAndPost)(const http::Request &, void *) = {[](const http::Request &request, void *) {
  34. TEST_ASSERT_EQUAL_STRING("/get-and-post", request.uri.c_str());
  35. if (request.method == http::GET) {
  36. return http::Response("GET");
  37. } else if (request.method == http::POST) {
  38. TEST_ASSERT_EQUAL_STRING("application/json", request.headers.at("Content-Type").c_str());
  39. auto requestJson = json::parse(request.body);
  40. string response = requestJson["foo"];
  41. return http::Response(response);
  42. } else {
  43. TEST_FAIL();
  44. return http::Response("fail"); // unreachable but the compiler moans otherwise
  45. }
  46. }};
  47. server.route({http::GET, "/get-and-post", handleGetAndPost, nullptr});
  48. server.route({http::POST, "/get-and-post", handleGetAndPost, nullptr});
  49. server.start();
  50. TEST_ASSERT_TRUE(server.isRunning())
  51. auto client = http::Client();
  52. auto response = client.get("http://localhost/get-only");
  53. TEST_ASSERT_EQUAL(200, response->status);
  54. TEST_ASSERT_EQUAL_STRING("OK", response->body.c_str());
  55. response = client.get("http://localhost/non-existent");
  56. TEST_ASSERT_EQUAL(404, response->status);
  57. TEST_ASSERT_EQUAL_STRING("This URI does not exist", response->body.c_str());
  58. response = client.get("http://localhost/post-only");
  59. TEST_ASSERT_EQUAL(405, response->status);
  60. TEST_ASSERT_EQUAL_STRING("Request method for this URI is not handled by server", response->body.c_str());
  61. response = client.get("http://localhost/get-and-post");
  62. TEST_ASSERT_EQUAL(200, response->status);
  63. TEST_ASSERT_EQUAL_STRING("GET", response->body.c_str());
  64. json data = {{"foo", "bar"}};
  65. response = client.post("http://localhost/get-and-post", data);
  66. TEST_ASSERT_EQUAL_STRING("bar", response->body.c_str());
  67. server.stop();
  68. wifi_legacy::stop();
  69. }
  70. TEST_CASE("HTTP custom headers", "[kbf_http]") {
  71. wifi_legacy::start();
  72. auto server = http::Server();
  73. http::Response (*handleContentTypeTest)(const http::Request &, void *) = {[](const http::Request &request, void *) {
  74. auto response = http::Response("OK");
  75. if (!request.query.empty() && request.query.at("type") == "txt") {
  76. response.contentType = "text/plain";
  77. }
  78. return response;
  79. }};
  80. server.route({http::GET, "/content-type-test", handleContentTypeTest, nullptr});
  81. http::Response (*handleHeaderTest)(const http::Request &, void *) = {[](const http::Request &request, void *) {
  82. auto response = http::Response("OK");
  83. const string &header = request.readHeader(KBF_TEST_HEADER_REQUEST);
  84. TEST_ASSERT_EQUAL_STRING(KBF_TEST_HEADER_VAL, header.c_str());
  85. response.headers[KBF_TEST_HEADER_RESPONSE] = header;
  86. return response;
  87. }};
  88. server.route({http::GET, "/custom-header-test", handleHeaderTest, nullptr});
  89. server.start();
  90. auto client = http::Client();
  91. auto response = client.get("http://localhost/content-type-test");
  92. TEST_ASSERT_EQUAL_STRING("text/html", response->headers.at("Content-Type").c_str());
  93. TEST_ASSERT_EQUAL_STRING("OK", response->body.data());
  94. response = client.get("http://localhost/content-type-test?type=txt");
  95. TEST_ASSERT_EQUAL_STRING("text/plain", response->headers.at("Content-Type").c_str());
  96. TEST_ASSERT_EQUAL_STRING("OK", response->body.data());
  97. response = client.get("http://localhost/custom-header-test", {{{KBF_TEST_HEADER_REQUEST, KBF_TEST_HEADER_VAL}}});
  98. TEST_ASSERT_EQUAL_STRING(KBF_TEST_HEADER_VAL, response->headers.at(KBF_TEST_HEADER_RESPONSE).c_str());
  99. TEST_ASSERT_EQUAL_STRING("OK", response->body.data());
  100. server.stop();
  101. wifi_legacy::stop();
  102. }
  103. TEST_CASE("HTTP JSON request / response", "[kbf_http]") {
  104. wifi_legacy::start();
  105. static const string testKey = "key";
  106. static const string testValue = "value";
  107. auto server = http::Server();
  108. http::Response (*handleJson)(const http::Request &, void *) = {[](const http::Request &request, void *) {
  109. TEST_ASSERT_EQUAL(http::POST, request.method);
  110. TEST_ASSERT_EQUAL_STRING("application/json", request.headers.at("Content-Type").c_str());
  111. auto requestJson = request.json();
  112. TEST_ASSERT_NOT_NULL(requestJson)
  113. auto responseJson = json({{testKey, requestJson.find(testKey)->get<string>()}});
  114. return http::Response(responseJson);
  115. }};
  116. http::Response (*handleNotJson)(const http::Request &, void *) = {[](const http::Request &request, void *) {
  117. auto requestJson = request.json();
  118. TEST_ASSERT_NULL(requestJson)
  119. return http::Response("OK");
  120. }};
  121. server.route({http::POST, "/json", handleJson, nullptr});
  122. server.route({http::POST, "/not-json", handleNotJson, nullptr});
  123. server.start();
  124. auto client = http::Client();
  125. auto response = client.post("http://localhost/json", {{testKey, testValue}});
  126. TEST_ASSERT_EQUAL_STRING("application/json", response->headers.at("Content-Type").c_str());
  127. auto responseJson = response->json();
  128. TEST_ASSERT_NOT_NULL(responseJson)
  129. TEST_ASSERT_EQUAL_STRING(testValue.c_str(), responseJson.find(testKey)->get<string>().c_str());
  130. // TODO enable after implementing support for posting data types other than JSON
  131. // response = client.post("http://localhost/not-json", "");
  132. // TEST_ASSERT_NULL(response->json());
  133. server.stop();
  134. wifi_legacy::stop();
  135. }
  136. TEST_CASE("HTTP timeout", "[kbf_http]") {
  137. wifi_legacy::start();
  138. auto server = http::Server();
  139. http::Response (*handler)(const http::Request &, void *) = {[](const http::Request &request, void *) {
  140. state++;
  141. kbf::sleep(stoi(request.query.at("sleep")));
  142. return http::Response("OK");
  143. }};
  144. server.route({http::GET, "/", handler, nullptr});
  145. server.start();
  146. state = 0;
  147. auto client = http::Client(1000);
  148. TEST_ASSERT_EQUAL_STRING("OK", client.get("http://localhost/?sleep=200")->body.c_str());
  149. TEST_ASSERT_EQUAL(1, state);
  150. bool caught = false;
  151. try {
  152. client.get("http://localhost/?sleep=1200");
  153. } catch (http::exception::Timeout &e) {
  154. caught = true;
  155. }
  156. TEST_ASSERT_TRUE(caught);
  157. TEST_ASSERT_EQUAL(2, state);
  158. wifi_legacy::stop();
  159. }
  160. TEST_CASE("HTTP onResponseSent", "[kbf_http]") {
  161. wifi_legacy::start();
  162. auto server = http::Server();
  163. state = 0;
  164. http::Response (*handleGet)(const http::Request &, void *) = {[](const http::Request &request, void *) {
  165. TEST_ASSERT_EQUAL(0, state++);
  166. return http::Response("OK");
  167. }};
  168. void (*onResponseSent)(const http::Response &, void *) = {[](const http::Response &response, void *) {
  169. TEST_ASSERT_EQUAL(1, state++);
  170. TEST_ASSERT_EQUAL(200, response.status);
  171. TEST_ASSERT_EQUAL_STRING("OK", response.body.c_str());
  172. }};
  173. server.route({http::GET, "/", handleGet, nullptr, onResponseSent});
  174. server.start();
  175. auto client = http::Client();
  176. auto response = client.get("http://localhost/");
  177. TEST_ASSERT_EQUAL(200, response->status);
  178. TEST_ASSERT_EQUAL_STRING("OK", response->body.c_str());
  179. TEST_ASSERT_EQUAL(2, state);
  180. server.stop();
  181. wifi_legacy::stop();
  182. }
  183. TEST_CASE("HTTP Client errors", "[kbf_http]") {
  184. wifi_legacy::start();
  185. auto server = http::Server();
  186. http::Response (*testGet)(const http::Request &, void *) = {[](const http::Request &request, void *) {
  187. TEST_FAIL();
  188. return http::Response();
  189. }};
  190. http::Response (*testPost)(const http::Request &, void *) = {[](const http::Request &request, void *) {
  191. TEST_ASSERT_EQUAL_STRING("hax", request.readHeader("X-Hax").c_str());
  192. return http::Response("OK");
  193. }};
  194. server.route({http::GET, "/test", testGet, nullptr});
  195. server.route({http::POST, "/test", testPost, nullptr});
  196. server.start();
  197. auto client = http::Client();
  198. auto response = client.get("http://localhost/non-existent");
  199. TEST_ASSERT_EQUAL(404, response->status);
  200. response = client.post("http://localhost/test", {{"foo", "bar"}}, {{{"X-Hax", "hax"}}});
  201. TEST_ASSERT_EQUAL_STRING("OK", response->body.c_str());
  202. bool caught = false;
  203. try {
  204. response = client.get("http://connection-refused.com/");
  205. } catch (http::exception::ConnectionError &e) {
  206. caught = true;
  207. }
  208. TEST_ASSERT_TRUE(caught);
  209. wifi_legacy::stop();
  210. }
  211. TEST_CASE("HTTP CORS", "[notimplemented]") {
  212. TEST_FAIL_MESSAGE("not yet implemented");
  213. }
  214. TEST_CASE("HTTP SPIFFS static route", "[notimplemented]") {
  215. TEST_FAIL_MESSAGE("not yet implemented");
  216. }