#include "network/license_service.h" #include "db/db_keys.h" #include #include #include #include #include using namespace radiantwave::license; using namespace radiantwave::db; namespace { // A temporary database that is created fresh per test and cleaned up on exit. struct TempDb { TempDb() { path = (std::filesystem::temp_directory_path() / ("test_rw_license_" + std::to_string(counter++) + ".db")) .string(); std::filesystem::remove(path); db.initialize(path); } ~TempDb() { db.close(); std::filesystem::remove(path); } std::string path; DatabaseManager db; static int counter; }; int TempDb::counter = 0; std::string env(const char* name) { const char* value = std::getenv(name); return value ? std::string(value) : std::string(); } } // namespace // ─── Offline: not-configured paths (no network) ──────────────────────── TEST(LicenseServiceTest, NotConfiguredWhenNoEmail) { TempDb tmp; tmp.db.setKV(KV_LICENSE_KEY, "ABCD-EFGH-IJKL-MNOP"); std::string levelName = "stale"; std::string errorMessage = "stale"; EXPECT_EQ(validateStoredLicense(tmp.db, "", &levelName, &errorMessage), Result::NotConfigured); EXPECT_EQ(levelName, ""); EXPECT_EQ(errorMessage, ""); EXPECT_FALSE(tmp.db.hasKV(KV_SUBSCRIPTION_LEVEL_NAME)); } TEST(LicenseServiceTest, NotConfiguredWhenNoLicense) { TempDb tmp; tmp.db.setKV(KV_EMAIL_ADDRESS, "user@example.com"); std::string levelName = "stale"; EXPECT_EQ(validateStoredLicense(tmp.db, "", &levelName, nullptr), Result::NotConfigured); EXPECT_EQ(levelName, ""); } TEST(LicenseServiceTest, NotConfiguredWhenBothMissing) { TempDb tmp; EXPECT_EQ(validateStoredLicense(tmp.db), Result::NotConfigured); } // ─── Level helpers (no network) ───────────────────────────────────────── TEST(LicenseServiceTest, StoredLevelIdReadsPersistedValue) { TempDb tmp; EXPECT_EQ(getStoredSubscriptionLevelId(tmp.db), 0); tmp.db.setKV(KV_SUBSCRIPTION_LEVEL_ID, "2"); EXPECT_EQ(getStoredSubscriptionLevelId(tmp.db), 2); tmp.db.setKV(KV_SUBSCRIPTION_LEVEL_ID, "not-a-number"); EXPECT_EQ(getStoredSubscriptionLevelId(tmp.db), 0); } TEST(LicenseServiceTest, LevelNames) { EXPECT_EQ(subscriptionLevelName(1), "Basic"); EXPECT_EQ(subscriptionLevelName(2), "Advanced"); EXPECT_EQ(subscriptionLevelName(3), "Elite"); EXPECT_EQ(subscriptionLevelName(4), "Subscription Level 4"); } TEST(LicenseServiceTest, LevelIdForName) { EXPECT_EQ(subscriptionLevelIdForName("Basic"), 1); EXPECT_EQ(subscriptionLevelIdForName("advanced"), 2); EXPECT_EQ(subscriptionLevelIdForName("ELITE"), 3); EXPECT_EQ(subscriptionLevelIdForName(" Elite "), 0); // unknown/whitespace EXPECT_EQ(subscriptionLevelIdForName("Enterprise"), 0); EXPECT_EQ(subscriptionLevelIdForName(""), 0); } // ─── Offline grace period ─────────────────────────────────────────────── long long epochSeconds(long long offsetSeconds = 0) { const auto now = std::chrono::system_clock::now(); return std::chrono::duration_cast(now.time_since_epoch()) .count() + offsetSeconds; } TEST(LicenseServiceTest, MarkVerifiedStampsNowAndGrantsGrace) { TempDb tmp; EXPECT_FALSE(tmp.db.hasKV(KV_LICENSE_LAST_VERIFIED)); EXPECT_FALSE(isLicenseWithinGracePeriod(tmp.db)); markLicenseVerified(tmp.db); EXPECT_TRUE(tmp.db.hasKV(KV_LICENSE_LAST_VERIFIED)); EXPECT_TRUE(isLicenseWithinGracePeriod(tmp.db)); } TEST(LicenseServiceTest, GracePeriodExpiresAfterSevenDays) { TempDb tmp; tmp.db.setKV(KV_LICENSE_LAST_VERIFIED, std::to_string(epochSeconds(-6 * 24 * 3600))); EXPECT_TRUE(isLicenseWithinGracePeriod(tmp.db)); tmp.db.setKV(KV_LICENSE_LAST_VERIFIED, std::to_string(epochSeconds(-8 * 24 * 3600))); EXPECT_FALSE(isLicenseWithinGracePeriod(tmp.db)); } TEST(LicenseServiceTest, GracePeriodIgnoresCorruptStamp) { TempDb tmp; tmp.db.setKV(KV_LICENSE_LAST_VERIFIED, "not-a-timestamp"); EXPECT_FALSE(isLicenseWithinGracePeriod(tmp.db)); tmp.db.setKV(KV_LICENSE_LAST_VERIFIED, "-42"); EXPECT_FALSE(isLicenseWithinGracePeriod(tmp.db)); } // ─── Live: full validation + persistence (requires env credentials) ───── TEST(LicenseServiceLiveTest, ValidatesAndStoresSubscriptionLevel) { const std::string key = env("RW_LIVE_LICENSE_KEY"); const std::string email = env("RW_LIVE_LICENSE_EMAIL"); if (key.empty() || email.empty()) { GTEST_SKIP() << "RW_LIVE_LICENSE_KEY / RW_LIVE_LICENSE_EMAIL not set"; } TempDb tmp; tmp.db.setKV(KV_LICENSE_KEY, key); tmp.db.setKV(KV_EMAIL_ADDRESS, email); std::string levelName; std::string errorMessage; const Result result = validateStoredLicense(tmp.db, "", &levelName, &errorMessage); EXPECT_EQ(result, Result::Ok) << "error=" << errorMessage; // The subscription level name (and id/description when available) must be // persisted to the local database. EXPECT_EQ(levelName, tmp.db.getKV(KV_SUBSCRIPTION_LEVEL_NAME)); EXPECT_FALSE(tmp.db.getKV(KV_SUBSCRIPTION_LEVEL_NAME).empty()); ASSERT_TRUE(tmp.db.hasKV(KV_SUBSCRIPTION_LEVEL_ID)); EXPECT_GT(std::atoi(tmp.db.getKV(KV_SUBSCRIPTION_LEVEL_ID).c_str()), 0); // A successful validation must record the "last verified" stamp that drives // the offline grace period. EXPECT_TRUE(tmp.db.hasKV(KV_LICENSE_LAST_VERIFIED)); EXPECT_TRUE(isLicenseWithinGracePeriod(tmp.db)); } TEST(LicenseServiceLiveTest, InvalidPairReportsInvalid) { TempDb tmp; tmp.db.setKV(KV_LICENSE_KEY, "AAAA-AAAA-AAAA-AAAA"); tmp.db.setKV(KV_EMAIL_ADDRESS, "nobody@example.com"); std::string errorMessage; EXPECT_EQ(validateStoredLicense(tmp.db, "", nullptr, &errorMessage), Result::Invalid); EXPECT_FALSE(errorMessage.empty()); EXPECT_FALSE(tmp.db.hasKV(KV_SUBSCRIPTION_LEVEL_NAME)); }