Warning screen, smarter license re-validation, version reporting
Warning screen - New reusable WarningPage (word-wrapped message, ENTER acknowledges and continues to the screen you were about to enter). - First use: pressing Play while the selected audio is still downloading. Dynamic Audio requires every file; a fixed track requires its own file. License re-validation - No longer re-verifies on every Home render. Checks now run only when the email/license key is entered or changed, on the manual option, at startup when already due, and hourly while valid. - Invalid + unchanged credentials are never re-checked (persisted pair). - Valid re-checks at most once per hour, anchored on a DB timestamp so an overdue check runs immediately after a power cycle. - New system_kv keys track the checked pair, outcome, and last-check time. - Added a System Settings row "Manually Validate License Key OK|Invalid" (OK green, Invalid red, label white) that runs a check immediately. Portal / local integration - License validation request now sends the app version as `appVersion`. - The entered license key is mirrored to ~/.local/radiantwave/license.txt. Misc - "Please connect to WiFi" only shows when no WiFi is joined AND the internet is unreachable (fixes the NetworkManager-missing and Ethernet cases). - Added unit tests for the audio-download warning, license scheduling policy, license request shape, and the new settings-menu entry.
This commit is contained in:
@@ -97,6 +97,46 @@ TEST(LicenseStatusTest, StatusForResultWithGraceExpired) {
|
||||
EXPECT_EQ(statusForResultWithGrace(Result::NetworkError, tmp.db), Status::Invalid);
|
||||
}
|
||||
|
||||
// ─── Scheduling policy (pure) ───────────────────────────────────────────
|
||||
|
||||
TEST(LicenseStatusPolicyTest, ForcedAlwaysRuns) {
|
||||
EXPECT_TRUE(shouldValidate(true, false, false, Status::Invalid, 1000, 999));
|
||||
EXPECT_TRUE(shouldValidate(true, false, false, Status::Unknown, 1000, 0));
|
||||
}
|
||||
|
||||
TEST(LicenseStatusPolicyTest, CredentialChangeRuns) {
|
||||
EXPECT_TRUE(shouldValidate(false, true, false, Status::Invalid, 1000, 999));
|
||||
}
|
||||
|
||||
TEST(LicenseStatusPolicyTest, OverdueRecheckRuns) {
|
||||
EXPECT_TRUE(shouldValidate(false, false, true, Status::Invalid, 1000, 999));
|
||||
}
|
||||
|
||||
TEST(LicenseStatusPolicyTest, InvalidUnchangedDoesNotRun) {
|
||||
// An invalid license with unchanged credentials is never re-checked, no
|
||||
// matter how old the last attempt is.
|
||||
EXPECT_FALSE(shouldValidate(false, false, false, Status::Invalid, 1000, 999));
|
||||
EXPECT_FALSE(shouldValidate(false, false, false, Status::Invalid,
|
||||
1000, 1000 - 10 * RECHECK_INTERVAL_SECONDS));
|
||||
EXPECT_FALSE(shouldValidate(false, false, false, Status::Unknown, 1000, 0));
|
||||
}
|
||||
|
||||
TEST(LicenseStatusPolicyTest, ValidRechecksAfterInterval) {
|
||||
const long long now = 1000000;
|
||||
EXPECT_FALSE(shouldValidate(false, false, false, Status::Valid, now, now - 10));
|
||||
EXPECT_FALSE(shouldValidate(false, false, false, Status::Valid, now,
|
||||
now - (RECHECK_INTERVAL_SECONDS - 1)));
|
||||
EXPECT_TRUE(shouldValidate(false, false, false, Status::Valid, now,
|
||||
now - RECHECK_INTERVAL_SECONDS));
|
||||
// Off for several hours: still immediately due, not another full hour away.
|
||||
EXPECT_TRUE(shouldValidate(false, false, false, Status::Valid, now,
|
||||
now - 5 * RECHECK_INTERVAL_SECONDS));
|
||||
}
|
||||
|
||||
TEST(LicenseStatusPolicyTest, ValidWithMissingStampRechecks) {
|
||||
EXPECT_TRUE(shouldValidate(false, false, false, Status::Valid, 1000, 0));
|
||||
}
|
||||
|
||||
// ─── Worker lifecycle (no network) ──────────────────────────────────────
|
||||
|
||||
TEST(LicenseStatusTest, UnknownBeforeStart) {
|
||||
@@ -130,6 +170,50 @@ TEST(LicenseStatusTest, InvalidWhenDatabaseClosed) {
|
||||
tracker.shutdown();
|
||||
}
|
||||
|
||||
TEST(LicenseStatusTest, LoadsPersistedValidWithinGraceWithoutChecking) {
|
||||
TempDb tmp;
|
||||
tmp.db.setKV(KV_LICENSE_KEY, "k");
|
||||
tmp.db.setKV(KV_EMAIL_ADDRESS, "e");
|
||||
tmp.db.setKV(KV_LICENSE_CHECKED_KEY, "k");
|
||||
tmp.db.setKV(KV_LICENSE_CHECKED_EMAIL, "e");
|
||||
tmp.db.setKV(KV_LICENSE_CHECK_STATUS, "valid");
|
||||
tmp.db.setKV(KV_LICENSE_LAST_VERIFIED, std::to_string(epochSeconds()));
|
||||
tmp.db.setKV(KV_LICENSE_LAST_CHECK, std::to_string(epochSeconds()));
|
||||
|
||||
LicenseStatusTracker tracker;
|
||||
tracker.start(tmp.db);
|
||||
// Seeded valid + recently checked + unchanged credentials: the worker must
|
||||
// stay idle (no network), and the status is valid immediately.
|
||||
EXPECT_EQ(tracker.status(), Status::Valid);
|
||||
tracker.shutdown();
|
||||
}
|
||||
|
||||
TEST(LicenseStatusTest, RecordResultUpdatesStatusAndPersistsCheckedPair) {
|
||||
TempDb tmp;
|
||||
const std::string key = "AAAA-BBBB-CCCC-DDDD";
|
||||
const std::string email = "user@example.com";
|
||||
tmp.db.setKV(KV_LICENSE_KEY, key);
|
||||
tmp.db.setKV(KV_EMAIL_ADDRESS, email);
|
||||
// Seed the checked pair as already-invalid so the worker does not start a
|
||||
// network check; this exercises recordResult() in isolation.
|
||||
tmp.db.setKV(KV_LICENSE_CHECKED_KEY, key);
|
||||
tmp.db.setKV(KV_LICENSE_CHECKED_EMAIL, email);
|
||||
tmp.db.setKV(KV_LICENSE_CHECK_STATUS, "invalid");
|
||||
|
||||
LicenseStatusTracker tracker;
|
||||
tracker.start(tmp.db);
|
||||
EXPECT_EQ(tracker.status(), Status::Invalid);
|
||||
|
||||
tracker.recordResult(Result::Ok);
|
||||
EXPECT_EQ(tracker.status(), Status::Valid);
|
||||
EXPECT_TRUE(tracker.isValid());
|
||||
EXPECT_EQ(tmp.db.getKV(KV_LICENSE_CHECKED_KEY), key);
|
||||
EXPECT_EQ(tmp.db.getKV(KV_LICENSE_CHECKED_EMAIL), email);
|
||||
EXPECT_EQ(tmp.db.getKV(KV_LICENSE_CHECK_STATUS), "valid");
|
||||
EXPECT_FALSE(tmp.db.getKV(KV_LICENSE_LAST_CHECK).empty());
|
||||
tracker.shutdown();
|
||||
}
|
||||
|
||||
// ─── Live: confirmed-valid license (requires env credentials) ───────────
|
||||
|
||||
TEST(LicenseStatusLiveTest, ValidWhenLicensePairAccepted) {
|
||||
|
||||
Reference in New Issue
Block a user