182 lines
6.2 KiB
C++
182 lines
6.2 KiB
C++
#include "network/license_service.h"
|
|
#include "db/db_keys.h"
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <cstdlib>
|
|
#include <chrono>
|
|
#include <filesystem>
|
|
#include <string>
|
|
|
|
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<std::chrono::seconds>(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));
|
|
}
|