#include #include "csp/app_state.h" #include "csp/controllers/admin_controller.h" #include "csp/controllers/contest_controller.h" #include "csp/controllers/me_controller.h" #include "csp/controllers/season_controller.h" #include "csp/services/auth_service.h" #include #include namespace { drogon::HttpResponsePtr CallSeasonCurrent(csp::controllers::SeasonController& ctl) { auto req = drogon::HttpRequest::newHttpRequest(); req->setMethod(drogon::Get); std::promise p; ctl.currentSeason(req, [&p](const drogon::HttpResponsePtr& resp) { p.set_value(resp); }); return p.get_future().get(); } drogon::HttpResponsePtr CallSeasonMe(csp::controllers::SeasonController& ctl, int64_t season_id, const std::string& token) { auto req = drogon::HttpRequest::newHttpRequest(); req->setMethod(drogon::Get); req->addHeader("Authorization", "Bearer " + token); std::promise p; ctl.mySeasonProgress(req, [&p](const drogon::HttpResponsePtr& resp) { p.set_value(resp); }, season_id); return p.get_future().get(); } drogon::HttpResponsePtr CallSeasonClaim(csp::controllers::SeasonController& ctl, int64_t season_id, const std::string& token, int tier_no, const std::string& reward_type) { Json::Value body; body["tier_no"] = tier_no; body["reward_type"] = reward_type; auto req = drogon::HttpRequest::newHttpJsonRequest(body); req->setMethod(drogon::Post); req->addHeader("Authorization", "Bearer " + token); std::promise p; ctl.claimSeasonReward(req, [&p](const drogon::HttpResponsePtr& resp) { p.set_value(resp); }, season_id); return p.get_future().get(); } drogon::HttpResponsePtr CallMeLoot(csp::controllers::MeController& ctl, const std::string& token) { auto req = drogon::HttpRequest::newHttpRequest(); req->setMethod(drogon::Get); req->addHeader("Authorization", "Bearer " + token); req->setParameter("limit", "20"); std::promise p; ctl.listLootDrops(req, [&p](const drogon::HttpResponsePtr& resp) { p.set_value(resp); }); return p.get_future().get(); } drogon::HttpResponsePtr CallContestList(csp::controllers::ContestController& ctl) { auto req = drogon::HttpRequest::newHttpRequest(); req->setMethod(drogon::Get); std::promise p; ctl.list(req, [&p](const drogon::HttpResponsePtr& resp) { p.set_value(resp); }); return p.get_future().get(); } drogon::HttpResponsePtr CallContestModifiers(csp::controllers::ContestController& ctl, int64_t contest_id, bool include_inactive) { auto req = drogon::HttpRequest::newHttpRequest(); req->setMethod(drogon::Get); if (include_inactive) req->setParameter("include_inactive", "true"); std::promise p; ctl.modifiers(req, [&p](const drogon::HttpResponsePtr& resp) { p.set_value(resp); }, contest_id); return p.get_future().get(); } drogon::HttpResponsePtr CallAdminCreateSeason(csp::controllers::AdminController& ctl, const std::string& token, const Json::Value& body) { auto req = drogon::HttpRequest::newHttpJsonRequest(body); req->setMethod(drogon::Post); req->addHeader("Authorization", "Bearer " + token); std::promise p; ctl.createSeason(req, [&p](const drogon::HttpResponsePtr& resp) { p.set_value(resp); }); return p.get_future().get(); } drogon::HttpResponsePtr CallAdminUpdateSeason(csp::controllers::AdminController& ctl, const std::string& token, int64_t season_id, const Json::Value& body) { auto req = drogon::HttpRequest::newHttpJsonRequest(body); req->setMethod(drogon::Patch); req->addHeader("Authorization", "Bearer " + token); std::promise p; ctl.updateSeason(req, [&p](const drogon::HttpResponsePtr& resp) { p.set_value(resp); }, season_id); return p.get_future().get(); } drogon::HttpResponsePtr CallAdminCreateModifier(csp::controllers::AdminController& ctl, const std::string& token, int64_t contest_id, const Json::Value& body) { auto req = drogon::HttpRequest::newHttpJsonRequest(body); req->setMethod(drogon::Post); req->addHeader("Authorization", "Bearer " + token); std::promise p; ctl.createContestModifier(req, [&p](const drogon::HttpResponsePtr& resp) { p.set_value(resp); }, contest_id); return p.get_future().get(); } drogon::HttpResponsePtr CallAdminUpdateModifier(csp::controllers::AdminController& ctl, const std::string& token, int64_t contest_id, int64_t modifier_id, const Json::Value& body) { auto req = drogon::HttpRequest::newHttpJsonRequest(body); req->setMethod(drogon::Patch); req->addHeader("Authorization", "Bearer " + token); std::promise p; ctl.updateContestModifier(req, [&p](const drogon::HttpResponsePtr& resp) { p.set_value(resp); }, contest_id, modifier_id); return p.get_future().get(); } } // namespace TEST_CASE("season controller current/me/claim and loot endpoint") { csp::AppState::Instance().Init(":memory:"); csp::services::AuthService auth(csp::AppState::Instance().db()); const auto user = auth.Register("season_http_user", "password123"); csp::controllers::SeasonController season_ctl; csp::controllers::MeController me_ctl; auto current_resp = CallSeasonCurrent(season_ctl); REQUIRE(current_resp->statusCode() == drogon::k200OK); auto current_json = current_resp->jsonObject(); REQUIRE(current_json != nullptr); const int64_t season_id = (*current_json)["data"]["season"]["id"].asInt64(); REQUIRE(season_id > 0); REQUIRE((*current_json)["data"]["reward_tracks"].isArray()); REQUIRE((*current_json)["data"]["reward_tracks"].size() >= 1); auto me_resp = CallSeasonMe(season_ctl, season_id, user.token); REQUIRE(me_resp->statusCode() == drogon::k200OK); auto me_json = me_resp->jsonObject(); REQUIRE(me_json != nullptr); REQUIRE((*me_json)["data"]["progress"].isObject()); REQUIRE((*me_json)["data"]["reward_tracks"].isArray()); const int tier_no = (*me_json)["data"]["reward_tracks"][0]["tier_no"].asInt(); const std::string reward_type = (*me_json)["data"]["reward_tracks"][0]["reward_type"].asString(); auto claim_resp = CallSeasonClaim( season_ctl, season_id, user.token, tier_no, reward_type); REQUIRE(claim_resp->statusCode() == drogon::k200OK); auto claim_json = claim_resp->jsonObject(); REQUIRE(claim_json != nullptr); REQUIRE((*claim_json)["data"]["track"]["tier_no"].asInt() == tier_no); auto loot_resp = CallMeLoot(me_ctl, user.token); REQUIRE(loot_resp->statusCode() == drogon::k200OK); auto loot_json = loot_resp->jsonObject(); REQUIRE(loot_json != nullptr); REQUIRE((*loot_json)["data"].isArray()); REQUIRE((*loot_json)["data"].size() >= 1); } TEST_CASE("admin season/modifier endpoints and contest modifier read endpoint") { csp::AppState::Instance().Init(":memory:"); csp::services::AuthService auth(csp::AppState::Instance().db()); const auto admin = auth.Register("admin", "password123"); csp::controllers::AdminController admin_ctl; csp::controllers::ContestController contest_ctl; auto contests_resp = CallContestList(contest_ctl); REQUIRE(contests_resp->statusCode() == drogon::k200OK); auto contests_json = contests_resp->jsonObject(); REQUIRE(contests_json != nullptr); REQUIRE((*contests_json)["data"].isArray()); REQUIRE((*contests_json)["data"].size() >= 1); const int64_t contest_id = (*contests_json)["data"][0]["id"].asInt64(); Json::Value create_season_body; create_season_body["key"] = "season-http-admin"; create_season_body["title"] = "HTTP 管理赛季"; create_season_body["starts_at"] = Json::Int64(1700000000); create_season_body["ends_at"] = Json::Int64(1900000000); create_season_body["status"] = "active"; Json::Value tracks(Json::arrayValue); Json::Value t1; t1["tier_no"] = 1; t1["required_xp"] = 0; t1["reward_type"] = "free"; t1["reward_value"] = 3; tracks.append(t1); create_season_body["reward_tracks"] = tracks; auto create_season_resp = CallAdminCreateSeason(admin_ctl, admin.token, create_season_body); REQUIRE(create_season_resp->statusCode() == drogon::k200OK); auto create_season_json = create_season_resp->jsonObject(); REQUIRE(create_season_json != nullptr); const int64_t new_season_id = (*create_season_json)["data"]["season"]["id"].asInt64(); REQUIRE(new_season_id > 0); Json::Value update_season_body; update_season_body["title"] = "HTTP 管理赛季(更新)"; auto update_season_resp = CallAdminUpdateSeason(admin_ctl, admin.token, new_season_id, update_season_body); REQUIRE(update_season_resp->statusCode() == drogon::k200OK); auto update_season_json = update_season_resp->jsonObject(); REQUIRE(update_season_json != nullptr); REQUIRE((*update_season_json)["data"]["season"]["title"].asString() == "HTTP 管理赛季(更新)"); Json::Value create_modifier_body; create_modifier_body["code"] = "limit10"; create_modifier_body["title"] = "限时十分钟"; create_modifier_body["description"] = "每道题建议 10 分钟内完成。"; create_modifier_body["is_active"] = true; create_modifier_body["rule_json"] = R"({"time_limit_min":10})"; auto create_modifier_resp = CallAdminCreateModifier( admin_ctl, admin.token, contest_id, create_modifier_body); REQUIRE(create_modifier_resp->statusCode() == drogon::k200OK); auto create_modifier_json = create_modifier_resp->jsonObject(); REQUIRE(create_modifier_json != nullptr); const int64_t modifier_id = (*create_modifier_json)["data"]["id"].asInt64(); REQUIRE(modifier_id > 0); Json::Value update_modifier_body; update_modifier_body["is_active"] = false; update_modifier_body["title"] = "限时十分钟(更新)"; auto update_modifier_resp = CallAdminUpdateModifier( admin_ctl, admin.token, contest_id, modifier_id, update_modifier_body); REQUIRE(update_modifier_resp->statusCode() == drogon::k200OK); auto update_modifier_json = update_modifier_resp->jsonObject(); REQUIRE(update_modifier_json != nullptr); REQUIRE((*update_modifier_json)["data"]["is_active"].asBool() == false); REQUIRE((*update_modifier_json)["data"]["title"].asString() == "限时十分钟(更新)"); auto modifiers_resp = CallContestModifiers(contest_ctl, contest_id, true); REQUIRE(modifiers_resp->statusCode() == drogon::k200OK); auto modifiers_json = modifiers_resp->jsonObject(); REQUIRE(modifiers_json != nullptr); REQUIRE((*modifiers_json)["data"].isArray()); REQUIRE((*modifiers_json)["data"].size() >= 1); }