Browse Source

Add basic config and start on torrents post

master
Jørgen Lien Sellæg 9 years ago
parent
commit
b024da1847
  1. 11
      toREST/include/config.hpp
  2. 59
      toREST/include/torrents.hpp
  3. 18
      toREST/src/main.cxx
  4. 7
      toREST/tests/include/ConfigContext.hpp
  5. 6
      toREST/tests/include/ServerContext.hpp
  6. 2
      toREST/tests/include/SessionContext.hpp
  7. 69
      toREST/tests/stubs/ServerContext.cpp
  8. 5
      toREST/tests/stubs/SessionContext.cpp
  9. 64
      toREST/tests/torrents_test.cpp

11
toREST/include/config.hpp

@ -0,0 +1,11 @@
#ifndef _TR_CONFIG_HPP_
#define _TR_CONFIG_HPP_
#include <boost/filesystem.hpp>
struct Config {
const boost::filesystem::path root_dir = "/home/zalox/downloads";
const boost::filesystem::path default_download_dir = root_dir / "toREST";
};
#endif

59
toREST/include/torrents.hpp

@ -1,6 +1,8 @@
#ifndef _TR_TORRENTS_HPP_ #ifndef _TR_TORRENTS_HPP_
#define _TR_TORRENTS_HPP_ #define _TR_TORRENTS_HPP_
#include <http.hpp> #include <http.hpp>
#include <libtorrent/magnet_uri.hpp>
#include <util.hpp>
namespace tr { namespace tr {
namespace session { namespace session {
@ -36,6 +38,63 @@ void get(torrent_session &session, response resp, request req) {
} }
*resp << http_response; *resp << http_response;
} }
template <class settings, class torrent_session, class request, class response>
void post(settings opts, torrent_session &session, response resp, request req) {
auto http_response = http::response();
const auto respond = [&](http::status status) {
const auto response_code = http::code(status);
http_response.set_body({{"code", response_code.first}, {"status", response_code.second}});
http_response.set_status(response_code.first);
*resp << http_response;
};
if (!session.is_valid()) {
return respond(http::service_unavailable);
}
auto request_object = util::json::parse(req->content);
if (request_object.is_null()) {
return respond(http::bad_request);
}
if (!request_object.is_object()) {
return respond(http::bad_request);
}
if (request_object.find("magnet_uri") == request_object.end()) {
return respond(http::bad_request);
}
std::string uri = request_object.at("magnet_uri");
boost::system::error_code ec;
libtorrent::add_torrent_params params;
libtorrent::parse_magnet_uri(uri, params, ec);
auto magnet = util::uri::parse(uri);
if (ec) {
return respond(http::bad_request);
}
params.save_path = opts.default_download_dir.string();
http_response.add_header({"Location", "/session/torrents/hash"});
return respond(http::created);
// if(ec){
// auto response_code = http::code(http::bad_request);
// http_response.set_body({{"code", response_code.first}, {"status", response_code.second + ec.message()}});
// http_response.set_status(response_code.first);
// }
// if(request_object.find("save_path") != request_object.end())
// params.save_path=request_object.at("save_path");
// else
// params.save_path=opts.default_download_dir.string();
// session.async_add_torrent(params);
// http_response.add_header({"Location",params.info_hash.to_string()});
}
} }
} }
} }

18
toREST/src/main.cxx

@ -1,8 +1,9 @@
#include <boost/filesystem.hpp>
#include <http.hpp> #include <http.hpp>
#include <libtorrent/session.hpp> #include <libtorrent/session.hpp>
#include <libtorrent/session_stats.hpp> #include <libtorrent/session_stats.hpp>
#include <libtorrent/session_status.hpp> #include <libtorrent/session_status.hpp>
#include <config.hpp>
#include <server_http.hpp> #include <server_http.hpp>
#include <session.hpp> #include <session.hpp>
#include <torrents.hpp> #include <torrents.hpp>
@ -11,18 +12,21 @@ typedef SimpleWeb::Server<SimpleWeb::HTTP> HttpServer;
using namespace std; using namespace std;
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
int http_port = 8080, http_threads = 4; int http_port = 8080, http_threads = 4;
const boost::filesystem::path root_dir = "/home/zalox/downloads";
const boost::filesystem::path default_download_dir = root_dir / "toREST"; Config options;
if (!boost::filesystem::exists(default_download_dir)) {
if (!boost::filesystem::exists(options.root_dir)) {
boost::system::error_code ec; boost::system::error_code ec;
boost::filesystem::create_directories(default_download_dir, ec); boost::filesystem::create_directories(options.default_download_dir, ec);
if (ec) { if (ec) {
std::cout << ec.message() << "\n"; std::cout << ec.message() << "\n";
return 1; return 1;
} }
} }
HttpServer http_server(http_port, http_threads); HttpServer http_server(http_port, http_threads);
libtorrent::session session; libtorrent::session session;
const auto stats_metrics = libtorrent::session_stats_metrics(); const auto stats_metrics = libtorrent::session_stats_metrics();
@ -43,8 +47,8 @@ int main(int argc, char *argv[]) {
http_server.resource["^/session/torrents(\\?.*)?\\/?$"]["GET"] = [&](std::shared_ptr<HttpServer::Response> resp, std::shared_ptr<HttpServer::Request> req) { http_server.resource["^/session/torrents(\\?.*)?\\/?$"]["GET"] = [&](std::shared_ptr<HttpServer::Response> resp, std::shared_ptr<HttpServer::Request> req) {
tr::session::torrents::get(session, resp, req); tr::session::torrents::get(session, resp, req);
}; };
http_server.resource["^/session/torrents(\\?.*)?\\/?$"]["PATCH"] = [&](std::shared_ptr<HttpServer::Response> resp, std::shared_ptr<HttpServer::Request> req) { http_server.resource["^/session/torrents(\\?.*)?\\/?$"]["POST"] = [&](std::shared_ptr<HttpServer::Response> resp, std::shared_ptr<HttpServer::Request> req) {
tr::session::torrents::post(options, session, resp, req);
}; };
std::thread server_thread([&http_server]() { std::thread server_thread([&http_server]() {

7
toREST/tests/include/ConfigContext.hpp

@ -0,0 +1,7 @@
#ifndef _TR_TEST_CONFIG_CONTEXT_HPP_
#define _TR_TEST_CONFIG_CONTEXT_HPP_
#include <config.hpp>
#endif

6
toREST/tests/include/ServerContext.hpp

@ -2,7 +2,7 @@
#define _TR_TEST_SERVER_CONTEXT_HPP_ #define _TR_TEST_SERVER_CONTEXT_HPP_
#include <Catch/fakeit.hpp> #include <Catch/fakeit.hpp>
#include <json.hpp> #include <json.hpp>
#include <memory> #include <unordered_map>
#include <sstream> #include <sstream>
class TestRequest { class TestRequest {
@ -13,9 +13,12 @@ public:
class TestResponse : public std::stringstream { class TestResponse : public std::stringstream {
public: public:
std::string buffer; std::string buffer;
std::unordered_map<std::string,std::string> headers_;
std::string string(); std::string string();
std::string message(); std::string message();
std::string code(); std::string code();
std::string content();
const std::unordered_map<std::string,std::string>& headers();
}; };
class CommonResponse { class CommonResponse {
@ -24,6 +27,7 @@ public:
static void ok(std::shared_ptr<TestResponse> response, const nlohmann::json &data); static void ok(std::shared_ptr<TestResponse> response, const nlohmann::json &data);
static void bad_request(std::shared_ptr<TestResponse> response); static void bad_request(std::shared_ptr<TestResponse> response);
static void accepted(std::shared_ptr<TestResponse> response); static void accepted(std::shared_ptr<TestResponse> response);
static void created(std::shared_ptr<TestResponse> response, std::shared_ptr<TestRequest> request, const std::string &location);
}; };
#endif #endif

2
toREST/tests/include/SessionContext.hpp

@ -4,6 +4,7 @@
#include <TorrentContext.hpp> #include <TorrentContext.hpp>
#include <sstream> #include <sstream>
#include <vector> #include <vector>
#include <libtorrent/magnet_uri.hpp>
class TestSessionSettings { class TestSessionSettings {
public: public:
@ -38,6 +39,7 @@ public:
bool is_dht_running() const; bool is_dht_running() const;
std::vector<TestTorrent> &get_torrents(); std::vector<TestTorrent> &get_torrents();
void apply_settings(TestSessionSettings settings); void apply_settings(TestSessionSettings settings);
void async_add_torrent(const libtorrent::add_torrent_params &params);
}; };
#endif #endif

69
toREST/tests/stubs/ServerContext.cpp

@ -15,29 +15,80 @@ std::string TestResponse::code() {
string(); string();
return buffer.substr(9, 3); return buffer.substr(9, 3);
} }
std::string TestResponse::content() {
if (buffer.empty())
string();
auto start = buffer.find("\r\n\r\n") + 4;
return buffer.substr(buffer.find("\r\n\r\n") + 4, buffer.size() - start);
}
const std::unordered_map<std::string, std::string> &TestResponse::headers() {
size_t end = buffer.find("\r\n\r\n") + 4;
size_t start = buffer.find("\r\n") + 2;
std::string header;
std::string header_content;
for (size_t c = start; c < end; c++) {
if (buffer[c] == ':') {
header = buffer.substr(start, c - start);
c += 2;
start = c;
}
if (buffer[c] == '\r') {
header_content = buffer.substr(start, c - start);
c += 2;
start = c;
headers_.emplace(header, header_content);
header = "";
header_content = "";
}
}
return headers_;
}
void header_present(std::shared_ptr<TestResponse> response, const std::string &header, const std::string &content) {
const auto loc_itr = response->headers().find(header);
const auto end = response->headers().end();
const auto header_found = loc_itr != end;
REQUIRE(header_found);
REQUIRE(loc_itr->first == header);
REQUIRE(loc_itr->second == content);
}
void is_json_request(std::shared_ptr<TestResponse> response) {
const auto content = response->content();
auto obj = nlohmann::json::parse(content);
REQUIRE(obj.is_object());
header_present(response, "Content-Type", "application/json");
header_present(response, "Content-Length", std::to_string(content.length()));
}
void CommonResponse::service_unavailable(std::shared_ptr<TestResponse> response) { void CommonResponse::service_unavailable(std::shared_ptr<TestResponse> response) {
const std::string service_unavailable_json = "HTTP/1.1 503 Service Unavailable\r\nContent-Type: application/json\r\nContent-Length: 43\r\n\r\n{\"code\":503,\"status\":\"Service Unavailable\"}";
REQUIRE(response->string() == service_unavailable_json);
REQUIRE(response->code() == "503"); REQUIRE(response->code() == "503");
REQUIRE(response->message() == "Service Unavailable"); REQUIRE(response->message() == "Service Unavailable");
is_json_request(response);
}; };
void CommonResponse::ok(std::shared_ptr<TestResponse> response, const nlohmann::json &data) { void CommonResponse::ok(std::shared_ptr<TestResponse> response, const nlohmann::json &data) {
std::string str(data.dump()); std::string data_dump(data.dump());
const std::string res = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: " + std::to_string(str.size()) + "\r\n\r\n" + str;
REQUIRE(response->code() == "200"); REQUIRE(response->code() == "200");
REQUIRE(response->message() == "OK"); REQUIRE(response->message() == "OK");
REQUIRE(response->string() == res); REQUIRE(response->content() == data_dump);
is_json_request(response);
}; };
void CommonResponse::bad_request(std::shared_ptr<TestResponse> response) { void CommonResponse::bad_request(std::shared_ptr<TestResponse> response) {
const std::string bad_request_json = "HTTP/1.1 400 Bad Request\r\nContent-Type: application/json\r\nContent-Length: 35\r\n\r\n{\"code\":400,\"status\":\"Bad Request\"}";
REQUIRE(response->string() == bad_request_json);
REQUIRE(response->code() == "400"); REQUIRE(response->code() == "400");
REQUIRE(response->message() == "Bad Request"); REQUIRE(response->message() == "Bad Request");
is_json_request(response);
}; };
void CommonResponse::accepted(std::shared_ptr<TestResponse> response) { void CommonResponse::accepted(std::shared_ptr<TestResponse> response) {
const std::string accepted = "HTTP/1.1 202 Accepted\r\nContent-Type: application/json\r\nContent-Length: 32\r\n\r\n{\"code\":202,\"status\":\"Accepted\"}";
REQUIRE(response->string() == accepted);
REQUIRE(response->code() == "202"); REQUIRE(response->code() == "202");
REQUIRE(response->message() == "Accepted"); REQUIRE(response->message() == "Accepted");
is_json_request(response);
}; };
void CommonResponse::created(std::shared_ptr<TestResponse> response, std::shared_ptr<TestRequest> request, const std::string &location) {
REQUIRE(response->code() == "201");
REQUIRE(response->message() == "Created");
is_json_request(response);
header_present(response, "Location", "/session/torrents/hash");
};

5
toREST/tests/stubs/SessionContext.cpp

@ -66,3 +66,8 @@ std::vector<TestTorrent> &TestSession::get_torrents() {
void TestSession::apply_settings(TestSessionSettings settings) { void TestSession::apply_settings(TestSessionSettings settings) {
settings_ = settings; settings_ = settings;
}; };
void TestSession::async_add_torrent(const libtorrent::add_torrent_params &settings) {
TestTorrent torrent;
};

64
toREST/tests/torrents_test.cpp

@ -1,4 +1,5 @@
#include <Catch/fakeit.hpp> #include <Catch/fakeit.hpp>
#include <ConfigContext.hpp>
#include <ServerContext.hpp> #include <ServerContext.hpp>
#include <SessionContext.hpp> #include <SessionContext.hpp>
#include <torrents.hpp> #include <torrents.hpp>
@ -44,4 +45,65 @@ SCENARIO("We are running a GET /session/torrents resource") {
} }
} }
} }
} }
SCENARIO("We are running a POST /session/torrents resource") {
auto torrent_session = TestSession();
auto settings = Config();
auto response = std::make_shared<TestResponse>();
auto request = std::make_shared<TestRequest>();
GIVEN("the server is not working properly") {
AND_WHEN("we recive a request") {
tr::session::torrents::post(settings, torrent_session, response, request);
THEN("the server should reply with service unavailable") {
CommonResponse::service_unavailable(response);
}
}
}
GIVEN("the server is working properly") {
torrent_session.valid = true;
WHEN("we recive a valid request") {
GIVEN("we use the default download directory") {
request->content << nlohmann::json::object({{"magnet_uri", "magnet:?xt=urn:btih:c0b0a90089710812fe8c37385a4cc2978eabf3e8&dn=Taylor Swift&tr=http://tracker.sout.no"}});
THEN("the server should reply with created") {
tr::session::torrents::post(settings, torrent_session, response, request);
CommonResponse::created(response, request, "/session/torrents/hash");
}
}
GIVEN("the torrent doesn't exist") {
// tr::session::torrents::post(settings, torrent_session, response, request);
// tr::session::torrents::get(torrent_session, response, request);
// CommonResponse::ok(response, {{"torrents", nlohmann::json::array()}});
// }
// }
// GIVEN("we have at least one torrent") {
// auto t_torrent = TestTorrent();
// torrent_session.get_torrents().emplace_back(t_torrent);
// THEN("the server should reply with an array of torrents") {
// tr::session::torrents::get(torrent_session, response, request);
// CommonResponse::ok(response, {{"torrents", {torrent}}});
// };
}
GIVEN("the torrent exists from before") {
}
}
WHEN("we recive an invalid request") {
GIVEN("the request doesn't contain valid json") {
request->content << "Not valid";
THEN("the server should reply with bad request") {
tr::session::torrents::post(settings, torrent_session, response, request);
CommonResponse::bad_request(response);
}
}
GIVEN("it is valid json but are missing requred field") {
request->content << nlohmann::json({{"up_speed", "100"}});
THEN("the server should reply with bad request") {
tr::session::torrents::post(settings, torrent_session, response, request);
CommonResponse::bad_request(response);
}
}
}
}
}

Loading…
Cancel
Save