initial commit
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
#include "network/license_status.h"
|
||||
#include "db/db_keys.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
using namespace radiantwave::license;
|
||||
using namespace radiantwave::db;
|
||||
|
||||
namespace {
|
||||
|
||||
// A temporary database created fresh per test and cleaned up on exit.
|
||||
struct TempDb {
|
||||
TempDb() {
|
||||
path = (std::filesystem::temp_directory_path() /
|
||||
("test_rw_license_status_" + 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();
|
||||
}
|
||||
|
||||
// Waits until `status()` no longer reports Checking (or `timeout` elapses).
|
||||
// Returns the final status.
|
||||
Status waitForResult(LicenseStatusTracker& tracker, int timeoutMs = 2000) {
|
||||
const auto deadline = std::chrono::steady_clock::now() +
|
||||
std::chrono::milliseconds(timeoutMs);
|
||||
while (tracker.status() == Status::Checking) {
|
||||
if (std::chrono::steady_clock::now() >= deadline) break;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
return tracker.status();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ─── Pure mapping (offline) ─────────────────────────────────────────────
|
||||
|
||||
TEST(LicenseStatusTest, StatusForResultMapping) {
|
||||
EXPECT_EQ(statusForResult(Result::Ok), Status::Valid);
|
||||
EXPECT_EQ(statusForResult(Result::NotConfigured), Status::Invalid);
|
||||
EXPECT_EQ(statusForResult(Result::Invalid), Status::Invalid);
|
||||
EXPECT_EQ(statusForResult(Result::NetworkError), Status::Invalid);
|
||||
}
|
||||
|
||||
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(LicenseStatusTest, StatusForResultWithGraceNoPriorValidation) {
|
||||
TempDb tmp;
|
||||
// No "last verified" stamp: a network error still invalidates because the
|
||||
// device has no record of ever confirming the license.
|
||||
EXPECT_EQ(statusForResultWithGrace(Result::Ok, tmp.db), Status::Valid);
|
||||
EXPECT_EQ(statusForResultWithGrace(Result::NotConfigured, tmp.db), Status::Invalid);
|
||||
EXPECT_EQ(statusForResultWithGrace(Result::Invalid, tmp.db), Status::Invalid);
|
||||
EXPECT_EQ(statusForResultWithGrace(Result::NetworkError, tmp.db), Status::Invalid);
|
||||
}
|
||||
|
||||
TEST(LicenseStatusTest, StatusForResultWithGraceWithinWindow) {
|
||||
TempDb tmp;
|
||||
tmp.db.setKV(KV_LICENSE_LAST_VERIFIED, std::to_string(epochSeconds()));
|
||||
|
||||
// A network error keeps a recently-confirmed license valid...
|
||||
EXPECT_EQ(statusForResultWithGrace(Result::NetworkError, tmp.db), Status::Valid);
|
||||
// ...but the grace period never rescues an explicit rejection.
|
||||
EXPECT_EQ(statusForResultWithGrace(Result::Invalid, tmp.db), Status::Invalid);
|
||||
EXPECT_EQ(statusForResultWithGrace(Result::NotConfigured, tmp.db), Status::Invalid);
|
||||
}
|
||||
|
||||
TEST(LicenseStatusTest, StatusForResultWithGraceExpired) {
|
||||
TempDb tmp;
|
||||
tmp.db.setKV(KV_LICENSE_LAST_VERIFIED, std::to_string(epochSeconds(-8 * 24 * 3600)));
|
||||
EXPECT_EQ(statusForResultWithGrace(Result::NetworkError, tmp.db), Status::Invalid);
|
||||
}
|
||||
|
||||
// ─── Worker lifecycle (no network) ──────────────────────────────────────
|
||||
|
||||
TEST(LicenseStatusTest, UnknownBeforeStart) {
|
||||
LicenseStatusTracker tracker;
|
||||
EXPECT_EQ(tracker.status(), Status::Unknown);
|
||||
EXPECT_FALSE(tracker.isValid());
|
||||
|
||||
// refresh() before start() must not spawn a worker.
|
||||
tracker.refresh();
|
||||
EXPECT_EQ(tracker.status(), Status::Unknown);
|
||||
}
|
||||
|
||||
TEST(LicenseStatusTest, InvalidWhenNoCredentials) {
|
||||
TempDb tmp;
|
||||
LicenseStatusTracker tracker;
|
||||
tracker.start(tmp.db);
|
||||
tracker.refresh();
|
||||
|
||||
// NotConfigured resolves without touching the network.
|
||||
EXPECT_EQ(waitForResult(tracker), Status::Invalid);
|
||||
EXPECT_FALSE(tracker.isValid());
|
||||
tracker.shutdown();
|
||||
}
|
||||
|
||||
TEST(LicenseStatusTest, InvalidWhenDatabaseClosed) {
|
||||
TempDb tmp;
|
||||
LicenseStatusTracker tracker;
|
||||
tracker.start(tmp.db);
|
||||
tracker.refresh();
|
||||
EXPECT_EQ(waitForResult(tracker), Status::Invalid);
|
||||
tracker.shutdown();
|
||||
}
|
||||
|
||||
// ─── Live: confirmed-valid license (requires env credentials) ───────────
|
||||
|
||||
TEST(LicenseStatusLiveTest, ValidWhenLicensePairAccepted) {
|
||||
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);
|
||||
|
||||
LicenseStatusTracker tracker;
|
||||
tracker.start(tmp.db);
|
||||
tracker.refresh();
|
||||
|
||||
EXPECT_EQ(waitForResult(tracker, 20000), Status::Valid);
|
||||
EXPECT_TRUE(tracker.isValid());
|
||||
tracker.shutdown();
|
||||
}
|
||||
Reference in New Issue
Block a user