feat: 完成源晶权限与经验系统并优化 me/admin 交互
这个提交包含在:
274
backend/tests/season_http_test.cc
普通文件
274
backend/tests/season_http_test.cc
普通文件
@@ -0,0 +1,274 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#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 <drogon/HttpRequest.h>
|
||||
|
||||
#include <future>
|
||||
|
||||
namespace {
|
||||
|
||||
drogon::HttpResponsePtr CallSeasonCurrent(csp::controllers::SeasonController& ctl) {
|
||||
auto req = drogon::HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
std::promise<drogon::HttpResponsePtr> 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<drogon::HttpResponsePtr> 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<drogon::HttpResponsePtr> 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<drogon::HttpResponsePtr> 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<drogon::HttpResponsePtr> 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<drogon::HttpResponsePtr> 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<drogon::HttpResponsePtr> 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<drogon::HttpResponsePtr> 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<drogon::HttpResponsePtr> 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<drogon::HttpResponsePtr> 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);
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户