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

128 行
4.1 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include "csp/db/sqlite_db.h"
#include "csp/services/auth_service.h"
#include "csp/services/kb_service.h"
#include "csp/services/user_service.h"
#include <string>
#include <unordered_set>
TEST_CASE("kb service list/detail") {
auto db = csp::db::SqliteDb::OpenMemory();
csp::db::ApplyMigrations(db);
csp::db::SeedDemoData(db);
csp::services::KbService svc(db);
const auto rows = svc.ListArticles();
REQUIRE(rows.size() >= 2);
const auto detail = svc.GetBySlug(rows.front().slug);
REQUIRE(detail.has_value());
REQUIRE(detail->article.slug == rows.front().slug);
}
TEST_CASE("kb skill claim requires prerequisites") {
auto db = csp::db::SqliteDb::OpenMemory();
csp::db::ApplyMigrations(db);
csp::db::SeedDemoData(db);
csp::services::AuthService auth(db);
const auto login = auth.Register("kb_pre_user", "password123");
csp::services::KbService svc(db);
const auto detail = svc.GetBySlug("cpp14-skill-tree");
REQUIRE(detail.has_value());
REQUIRE(detail->article.id > 0);
bool prerequisite_throw = false;
try {
(void)svc.ClaimSkillPoint(login.user_id, detail->article.id, detail->article.slug,
"cpp14-type-02");
} catch (const std::runtime_error& e) {
prerequisite_throw = true;
REQUIRE(std::string(e.what()).find("prerequisite not completed") != std::string::npos);
}
REQUIRE(prerequisite_throw);
const auto first =
svc.ClaimSkillPoint(login.user_id, detail->article.id, detail->article.slug,
"cpp14-io-01");
REQUIRE(first.claimed);
REQUIRE(first.reward == 1);
const auto second =
svc.ClaimSkillPoint(login.user_id, detail->article.id, detail->article.slug,
"cpp14-type-02");
REQUIRE(second.claimed);
REQUIRE(second.reward == 1);
const auto second_again =
svc.ClaimSkillPoint(login.user_id, detail->article.id, detail->article.slug,
"cpp14-type-02");
REQUIRE_FALSE(second_again.claimed);
REQUIRE(second_again.reward == 0);
}
TEST_CASE("kb weekly tasks auto-generate and bonus awarded at 100 percent") {
auto db = csp::db::SqliteDb::OpenMemory();
csp::db::ApplyMigrations(db);
csp::db::SeedDemoData(db);
csp::services::AuthService auth(db);
const auto login = auth.Register("kb_weekly_user", "password123");
csp::services::KbService svc(db);
csp::services::UserService users(db);
auto plan = svc.GetWeeklyPlan(login.user_id);
REQUIRE_FALSE(plan.tasks.empty());
REQUIRE(plan.tasks.size() <= 8);
REQUIRE(plan.completion_percent == 0);
std::unordered_set<std::string> unlocked;
for (const auto& task : plan.tasks) {
for (const auto& pre : task.prerequisites) {
REQUIRE(unlocked.count(pre) > 0);
}
unlocked.insert(task.knowledge_key);
}
bool bonus_throw = false;
try {
(void)svc.ClaimWeeklyBonus(login.user_id);
} catch (const std::runtime_error& e) {
bonus_throw = true;
REQUIRE(std::string(e.what()).find("100% completed") != std::string::npos);
}
REQUIRE(bonus_throw);
int weekly_reward_sum = 0;
for (const auto& task : plan.tasks) {
const auto claim = svc.ClaimSkillPoint(login.user_id, task.article_id,
task.article_slug, task.knowledge_key);
weekly_reward_sum += claim.reward;
REQUIRE(claim.claimed);
}
plan = svc.GetWeeklyPlan(login.user_id);
REQUIRE(plan.completion_percent == 100);
REQUIRE(plan.gained_reward == plan.total_reward);
const auto bonus = svc.ClaimWeeklyBonus(login.user_id);
REQUIRE(bonus.claimed);
REQUIRE(bonus.reward == plan.bonus_reward);
REQUIRE(bonus.completion_percent == 100);
REQUIRE(bonus.week_key == plan.week_key);
const auto user = users.GetById(login.user_id);
REQUIRE(user.has_value());
// Register auto-login grants +1 via login check-in task.
REQUIRE(user->rating == 1 + weekly_reward_sum + plan.bonus_reward);
const auto bonus_again = svc.ClaimWeeklyBonus(login.user_id);
REQUIRE_FALSE(bonus_again.claimed);
REQUIRE(bonus_again.reward == 0);
REQUIRE(bonus_again.rating_after == user->rating);
}