文件
csp/backend/tests/me_http_test.cc
2026-02-23 20:02:46 +08:00

221 行
8.6 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include "csp/app_state.h"
#include "csp/controllers/me_controller.h"
#include "csp/services/auth_service.h"
#include "csp/services/daily_task_service.h"
#include "csp/services/problem_service.h"
#include "csp/services/user_service.h"
#include <drogon/HttpRequest.h>
#include <sqlite3.h>
#include <future>
namespace {
drogon::HttpResponsePtr CallProfile(csp::controllers::MeController& ctl,
const std::string& token) {
auto req = drogon::HttpRequest::newHttpRequest();
req->setMethod(drogon::Get);
req->addHeader("Authorization", "Bearer " + token);
std::promise<drogon::HttpResponsePtr> p;
ctl.profile(req, [&p](const drogon::HttpResponsePtr& resp) { p.set_value(resp); });
return p.get_future().get();
}
drogon::HttpResponsePtr CallProfileWithAuthHeader(
csp::controllers::MeController& ctl,
const std::string& auth_header) {
auto req = drogon::HttpRequest::newHttpRequest();
req->setMethod(drogon::Get);
req->addHeader("Authorization", auth_header);
std::promise<drogon::HttpResponsePtr> p;
ctl.profile(req, [&p](const drogon::HttpResponsePtr& resp) { p.set_value(resp); });
return p.get_future().get();
}
drogon::HttpResponsePtr CallListWrongBook(csp::controllers::MeController& ctl,
const std::string& token) {
auto req = drogon::HttpRequest::newHttpRequest();
req->setMethod(drogon::Get);
req->addHeader("Authorization", "Bearer " + token);
std::promise<drogon::HttpResponsePtr> p;
ctl.listWrongBook(req,
[&p](const drogon::HttpResponsePtr& resp) { p.set_value(resp); });
return p.get_future().get();
}
drogon::HttpResponsePtr CallPatchWrongBook(csp::controllers::MeController& ctl,
const std::string& token,
int64_t problem_id,
const std::string& note) {
Json::Value body;
body["note"] = note;
auto req = drogon::HttpRequest::newHttpJsonRequest(body);
req->setMethod(drogon::Patch);
req->addHeader("Authorization", "Bearer " + token);
std::promise<drogon::HttpResponsePtr> p;
ctl.upsertWrongBookNote(req,
[&p](const drogon::HttpResponsePtr& resp) {
p.set_value(resp);
},
problem_id);
return p.get_future().get();
}
drogon::HttpResponsePtr CallDeleteWrongBook(csp::controllers::MeController& ctl,
const std::string& token,
int64_t problem_id) {
auto req = drogon::HttpRequest::newHttpRequest();
req->setMethod(drogon::Delete);
req->addHeader("Authorization", "Bearer " + token);
std::promise<drogon::HttpResponsePtr> p;
ctl.deleteWrongBookItem(req,
[&p](const drogon::HttpResponsePtr& resp) {
p.set_value(resp);
},
problem_id);
return p.get_future().get();
}
drogon::HttpResponsePtr CallScoreWrongBook(csp::controllers::MeController& ctl,
const std::string& token,
int64_t problem_id,
const std::string& note) {
Json::Value body;
body["note"] = note;
auto req = drogon::HttpRequest::newHttpJsonRequest(body);
req->setMethod(drogon::Post);
req->addHeader("Authorization", "Bearer " + token);
std::promise<drogon::HttpResponsePtr> p;
ctl.scoreWrongBookNote(req,
[&p](const drogon::HttpResponsePtr& resp) {
p.set_value(resp);
},
problem_id);
return p.get_future().get();
}
int QueryDailyTaskCount(csp::db::SqliteDb& db,
int64_t user_id,
const std::string& task_code,
const std::string& day_key) {
sqlite3_stmt* stmt = nullptr;
const char* sql =
"SELECT COUNT(1) FROM daily_task_logs WHERE user_id=? AND task_code=? AND day_key=?";
REQUIRE(sqlite3_prepare_v2(db.raw(), sql, -1, &stmt, nullptr) == SQLITE_OK);
REQUIRE(sqlite3_bind_int64(stmt, 1, user_id) == SQLITE_OK);
REQUIRE(sqlite3_bind_text(stmt, 2, task_code.c_str(), -1, SQLITE_TRANSIENT) ==
SQLITE_OK);
REQUIRE(sqlite3_bind_text(stmt, 3, day_key.c_str(), -1, SQLITE_TRANSIENT) ==
SQLITE_OK);
REQUIRE(sqlite3_step(stmt) == SQLITE_ROW);
const int count = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
return count;
}
} // namespace
TEST_CASE("me controller profile and wrong-book") {
csp::AppState::Instance().Init(":memory:");
csp::services::AuthService auth(csp::AppState::Instance().db());
const auto login = auth.Register("me_http_user", "password123");
csp::services::ProblemService problems(csp::AppState::Instance().db());
const auto list = problems.List(csp::services::ProblemQuery{});
REQUIRE_FALSE(list.items.empty());
const int64_t problem_id = list.items.front().id;
csp::controllers::MeController ctl;
auto profile = CallProfile(ctl, login.token);
REQUIRE(profile->statusCode() == drogon::k200OK);
auto profile_json = profile->jsonObject();
REQUIRE(profile_json != nullptr);
REQUIRE((*profile_json)["ok"].asBool());
auto profile_basic =
CallProfileWithAuthHeader(ctl, "Basic bWVfaHR0cF91c2VyOnBhc3N3b3JkMTIz");
REQUIRE(profile_basic->statusCode() == drogon::k200OK);
auto patch = CallPatchWrongBook(ctl, login.token, problem_id, "复盘记录");
REQUIRE(patch->statusCode() == drogon::k200OK);
auto score = CallScoreWrongBook(ctl, login.token, problem_id, "题意+思路+踩坑总结");
REQUIRE(score->statusCode() == drogon::k200OK);
const auto score_json = score->jsonObject();
REQUIRE(score_json != nullptr);
REQUIRE((*score_json)["ok"].asBool());
REQUIRE((*score_json)["data"].isObject());
REQUIRE((*score_json)["data"]["note_score"].asInt() >= 0);
REQUIRE((*score_json)["data"]["note_score"].asInt() <= 60);
REQUIRE((*score_json)["data"]["note_rating"].asInt() >= 0);
REQUIRE((*score_json)["data"]["note_rating"].asInt() <= 6);
auto list_resp = CallListWrongBook(ctl, login.token);
REQUIRE(list_resp->statusCode() == drogon::k200OK);
auto list_json = list_resp->jsonObject();
REQUIRE(list_json != nullptr);
REQUIRE((*list_json)["data"].isArray());
REQUIRE((*list_json)["data"].size() >= 1);
auto del = CallDeleteWrongBook(ctl, login.token, problem_id);
REQUIRE(del->statusCode() == drogon::k200OK);
}
TEST_CASE("me profile auto recovers daily login checkin for stale session") {
csp::AppState::Instance().Init(":memory:");
csp::services::AuthService auth(csp::AppState::Instance().db());
const auto login = auth.Register("me_daily_user", "password123");
csp::controllers::MeController ctl;
csp::services::DailyTaskService daily(csp::AppState::Instance().db());
csp::services::UserService users(csp::AppState::Instance().db());
const auto day_key = daily.CurrentDayKey();
// Simulate a stale session user whose today's login_checkin wasn't recorded.
{
sqlite3* db = csp::AppState::Instance().db().raw();
sqlite3_stmt* del = nullptr;
const char* del_sql =
"DELETE FROM daily_task_logs WHERE user_id=? AND task_code=? AND day_key=?";
REQUIRE(sqlite3_prepare_v2(db, del_sql, -1, &del, nullptr) == SQLITE_OK);
REQUIRE(sqlite3_bind_int64(del, 1, login.user_id) == SQLITE_OK);
REQUIRE(sqlite3_bind_text(
del, 2, csp::services::DailyTaskService::kTaskLoginCheckin, -1,
SQLITE_STATIC) == SQLITE_OK);
REQUIRE(sqlite3_bind_text(del, 3, day_key.c_str(), -1, SQLITE_TRANSIENT) ==
SQLITE_OK);
REQUIRE(sqlite3_step(del) == SQLITE_DONE);
sqlite3_finalize(del);
// Keep rating consistent with removed daily task log.
csp::AppState::Instance().db().Exec("UPDATE users SET rating=rating-1 WHERE id=" +
std::to_string(login.user_id));
}
REQUIRE(QueryDailyTaskCount(csp::AppState::Instance().db(), login.user_id,
csp::services::DailyTaskService::kTaskLoginCheckin,
day_key) == 0);
auto profile = CallProfile(ctl, login.token);
REQUIRE(profile->statusCode() == drogon::k200OK);
const auto user = users.GetById(login.user_id);
REQUIRE(user.has_value());
REQUIRE(user->rating == 1);
REQUIRE(QueryDailyTaskCount(csp::AppState::Instance().db(), login.user_id,
csp::services::DailyTaskService::kTaskLoginCheckin,
day_key) == 1);
}