TEST(DeleteTests, SessionDeleteUnallowedTest) { auto url = Url{base + "/delete_unallowed.html"}; Session session; session.SetUrl(url); auto response = session.Delete(); auto expected_text = std::string{"Method unallowed"}; EXPECT_EQ(expected_text, response.text); EXPECT_EQ(url, response.url); EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); EXPECT_EQ(405, response.status_code); }
TEST(DeleteTests, SessionDeleteTest) { auto url = Url{base + "/delete.html"}; Session session; session.SetUrl(url); auto response = session.Delete(); auto expected_text = std::string{"Delete success"}; EXPECT_EQ(expected_text, response.text); EXPECT_EQ(url, response.url); EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); EXPECT_EQ(200, response.status_code); }
TEST(DeleteTests, SessionDeleteJsonBodyTest) { auto url = Url{base + "/delete.html"}; Session session; session.SetUrl(url); session.SetHeader(cpr::Header{{"Content-Type", "application/json"}}); session.SetBody(cpr::Body{"{'foo': 'bar'}"}); auto response = session.Delete(); auto expected_text = std::string{"{'foo': 'bar'}"}; EXPECT_EQ(expected_text, response.text); EXPECT_EQ(url, response.url); EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); EXPECT_EQ(200, response.status_code); EXPECT_EQ(ErrorCode::OK, response.error.code); }
TEST(DeleteTests, SessionDeleteUnallowedAfterPostTest) { Session session; { auto url = Url{base + "/url_post.html"}; auto payload = Payload{{"x", "5"}}; session.SetUrl(url); auto response = session.Post(); } auto url = Url{base + "/delete_unallowed.html"}; session.SetUrl(url); auto response = session.Delete(); auto expected_text = std::string{"Method unallowed"}; EXPECT_EQ(expected_text, response.text); EXPECT_EQ(url, response.url); EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); EXPECT_EQ(405, response.status_code); }
TEST(DeleteTests, SessionDeleteAfterPostTest) { Session session; { auto url = Url{base + "/url_post.html"}; auto payload = Payload{{"x", "5"}}; session.SetUrl(url); auto response = session.Post(); } auto url = Url{base + "/delete.html"}; session.SetUrl(url); auto response = session.Delete(); auto expected_text = std::string{"Delete success"}; EXPECT_EQ(expected_text, response.text); EXPECT_EQ(url, response.url); EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); EXPECT_EQ(200, response.status_code); EXPECT_EQ(ErrorCode::OK, response.error.code); }
Response BaseDiscordClient::request(const RequestMethod method, Route path, const std::string jsonParameters/*, cpr::Parameters httpParameters*/, const std::initializer_list<Part>& multipartParameters) { //check if rate limited Response response; const time_t currentTime = getEpochTimeMillisecond(); if (isGlobalRateLimited) { if (nextRetry <= currentTime) { isGlobalRateLimited = false; } else { onExceededRateLimit(isGlobalRateLimited, nextRetry - currentTime, { *this, method, path, jsonParameters, multipartParameters }); response.statusCode = TOO_MANY_REQUESTS; setError(response.statusCode); return response; } } const std::string bucket = path.bucket(method); auto bucketResetTimestamp = buckets.find(bucket); if (bucketResetTimestamp != buckets.end()) { if (bucketResetTimestamp->second <= currentTime) { buckets.erase(bucketResetTimestamp); } else { onExceededRateLimit(false, bucketResetTimestamp->second - currentTime, { *this, method, path, jsonParameters, multipartParameters }); response.statusCode = TOO_MANY_REQUESTS; setError(response.statusCode); return response; } } { //the { is used so that onResponse is called after session is removed to make debugging performance issues easier //request starts here Session session; session.setUrl("https://discordapp.com/api/v6/" + path.url()); std::vector<HeaderPair> header = { { "Authorization", bot ? "Bot " + getToken() : getToken() }, { "User-Agent", "DiscordBot (https://github.com/yourWaifu/SleepyDiscord, vtheBestVersion)" }, }; if (jsonParameters != "") { session.setBody(&jsonParameters); header.push_back({ "Content-Type" , "application/json" }); header.push_back({ "Content-Length", std::to_string(jsonParameters.length()) }); //} else if (httpParameters.content != "") { //this is broken for now // session.SetParameters(httpParameters); } else if (0 < multipartParameters.size()) { session.setMultipart(multipartParameters); header.push_back({ "Content-Type", "multipart/form-data" }); } else { header.push_back({ "Content-Length", "0" }); } session.setHeader(header); //Response response; switch (method) { case Post: response = session.Post(); break; case Patch: response = session.Patch(); break; case Delete: response = session.Delete(); break; case Get: response = session.Get(); break; case Put: response = session.Put(); break; default: response.statusCode = BAD_REQUEST; break; //unexpected method } //status checking switch (response.statusCode) { case OK: case CREATED: case NO_CONTENT: case NOT_MODIFIED: break; case TOO_MANY_REQUESTS: { //this should fall down to default int retryAfter = std::stoi(response.header["Retry-After"]); isGlobalRateLimited = response.header["X-RateLimit-Global"] == "true"; nextRetry = getEpochTimeMillisecond() + retryAfter; if (!isGlobalRateLimited) buckets[bucket] = nextRetry; onExceededRateLimit(isGlobalRateLimited, retryAfter, { *this, method, path, jsonParameters, multipartParameters }); } default: { //error const ErrorCode code = static_cast<ErrorCode>(response.statusCode); setError(code); //https error std::vector<std::string> values = json::getValues(response.text.c_str(), { "code", "message" }); //parse json to get code and message if (!values.empty() && values[0] != "") onError(static_cast<ErrorCode>(std::stoi(values[0])), values[1]); //send message to the error event else onError(ERROR_NOTE, response.text); #if defined(__cpp_exceptions) || defined(__EXCEPTIONS) throw code; #endif } break; } //rate limit check if (response.header["X-RateLimit-Remaining"] == "0" && response.statusCode != TOO_MANY_REQUESTS) { std::tm date = {}; //for some reason std::get_time requires gcc 5 std::istringstream dateStream(response.header["Date"]); dateStream >> std::get_time(&date, "%a, %d %b %Y %H:%M:%S GMT"); const time_t reset = std::stoi(response.header["X-RateLimit-Reset"]); #if defined(_WIN32) || defined(_WIN64) std::tm gmTM; std::tm*const resetGM = &gmTM; gmtime_s(resetGM, &reset); #else std::tm* resetGM = std::gmtime(&reset); #endif const time_t resetDelta = (std::mktime(resetGM) - std::mktime(&date)) * 1000; buckets[bucket] = resetDelta + getEpochTimeMillisecond(); onDepletedRequestSupply(resetDelta, { *this, method, path, jsonParameters, multipartParameters }); } }