#include #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 #include 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 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); }