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

116 行
3.8 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include "csp/db/sqlite_db.h"
#include "csp/services/auth_service.h"
#include "csp/services/contest_service.h"
#include "csp/services/season_service.h"
#include "csp/services/user_service.h"
#include <string>
TEST_CASE("season reward claim is idempotent and writes loot log") {
auto db = csp::db::SqliteDb::OpenMemory();
csp::db::ApplyMigrations(db);
csp::db::SeedDemoData(db);
csp::services::AuthService auth(db);
const auto login = auth.Register("season_user_1", "password123");
csp::services::SeasonService seasons(db);
const auto season = seasons.GetCurrentSeason();
REQUIRE(season.has_value());
const auto tracks = seasons.ListRewardTracks(season->id);
REQUIRE_FALSE(tracks.empty());
const auto target_track = tracks.back();
db.Exec("UPDATE users SET rating=200 WHERE id=" + std::to_string(login.user_id));
const auto before_progress =
seasons.GetOrSyncUserProgress(season->id, login.user_id);
REQUIRE(before_progress.xp >= target_track.required_xp);
const auto first_claim = seasons.ClaimReward(
season->id, login.user_id, target_track.tier_no, target_track.reward_type);
REQUIRE(first_claim.claimed);
REQUIRE(first_claim.claim.has_value());
REQUIRE(first_claim.rating_after >= 200 + target_track.reward_value);
const auto second_claim = seasons.ClaimReward(
season->id, login.user_id, target_track.tier_no, target_track.reward_type);
REQUIRE_FALSE(second_claim.claimed);
REQUIRE(second_claim.claim.has_value());
REQUIRE(second_claim.rating_after == first_claim.rating_after);
const auto loot = seasons.ListLootDropsByUser(login.user_id, 20);
REQUIRE_FALSE(loot.empty());
REQUIRE(loot.front().source_type == "season");
REQUIRE(loot.front().source_id == season->id);
csp::services::UserService users(db);
const auto user = users.GetById(login.user_id);
REQUIRE(user.has_value());
REQUIRE(user->rating == first_claim.rating_after);
}
TEST_CASE("contest modifiers create update and filtered list") {
auto db = csp::db::SqliteDb::OpenMemory();
csp::db::ApplyMigrations(db);
csp::db::SeedDemoData(db);
csp::services::ContestService contests(db);
const auto contest_list = contests.ListContests();
REQUIRE_FALSE(contest_list.empty());
const int64_t contest_id = contest_list.front().id;
csp::services::SeasonService seasons(db);
csp::services::ContestModifierWrite create;
create.code = "no_recursion";
create.title = "禁用递归";
create.description = "仅允许循环写法。";
create.rule_json = R"({"forbid":["recursion"]})";
create.is_active = true;
const auto created = seasons.CreateContestModifier(contest_id, create);
REQUIRE(created.id > 0);
REQUIRE(created.contest_id == contest_id);
REQUIRE(created.is_active);
const auto active_list = seasons.ListContestModifiers(contest_id, false);
bool found_created = false;
for (const auto& one : active_list) {
if (one.id == created.id) {
found_created = true;
break;
}
}
REQUIRE(found_created);
csp::services::ContestModifierPatch patch;
patch.title = "禁用递归(更新)";
patch.is_active = false;
const auto updated =
seasons.UpdateContestModifier(contest_id, created.id, patch);
REQUIRE(updated.title == "禁用递归(更新)");
REQUIRE_FALSE(updated.is_active);
const auto active_after = seasons.ListContestModifiers(contest_id, false);
bool still_active = false;
for (const auto& one : active_after) {
if (one.id == created.id) {
still_active = true;
break;
}
}
REQUIRE_FALSE(still_active);
const auto all_after = seasons.ListContestModifiers(contest_id, true);
bool found_updated = false;
for (const auto& one : all_after) {
if (one.id == created.id && one.title == "禁用递归(更新)" &&
!one.is_active) {
found_updated = true;
break;
}
}
REQUIRE(found_updated);
}