use base64::Engine;
use ed25519_dalek::{Signer, SigningKey};
use keylight::http::{HttpResponse, Transport, TransportOutcome};
use keylight::store::device::FixedDeviceIdentity;
use keylight::store::encrypted_file::EncryptedFileStore;
use keylight::store::{LicenseStore, account};
use keylight::{Keylight, KeylightConfig, LicenseState};
use std::sync::Arc;
const KID: &str = "k1";
fn now() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64
}
fn signing_key() -> SigningKey {
SigningKey::from_bytes(&[7u8; 32])
}
fn lease_json(signing: &SigningKey, status: &str, expires_at: i64) -> String {
let payload = format!("v3|{KID}|hash|i1|0|{expires_at}|{status}|pro");
let sig = signing.sign(payload.as_bytes());
let sig_b64 = base64::engine::general_purpose::STANDARD.encode(sig.to_bytes());
serde_json::json!({
"kid": KID, "licenseKeyHash": "hash", "instanceId": "i1",
"issuedAt": 0, "expiresAt": expires_at, "status": status,
"signature": sig_b64, "entitlements": ["pro"],
})
.to_string()
}
fn config_trusting(signing: &SigningKey, max_offline_days: Option<u32>) -> KeylightConfig {
let pub_b64 =
base64::engine::general_purpose::STANDARD.encode(signing.verifying_key().to_bytes());
let mut cfg = KeylightConfig::builder("t", "p", "sdk_live_test")
.trusted_key(KID, pub_b64)
.build();
cfg.max_offline_days = max_offline_days;
cfg
}
fn store_with_active_lease(
dir: &str,
signing: &SigningKey,
last_validated_online: i64,
) -> Arc<EncryptedFileStore> {
let d = std::env::temp_dir().join(dir);
let _ = std::fs::remove_dir_all(&d);
let store =
Arc::new(EncryptedFileStore::at_dir(d, &FixedDeviceIdentity("dev".into())).unwrap());
store.set_string(account::LICENSE_KEY, "PRO-KEY").unwrap();
store.set_string(account::INSTANCE_ID, "i1").unwrap();
store
.set_string(
account::LEASE,
&lease_json(signing, "active", now() + 100_000),
)
.unwrap();
store
.set_string(account::LAST_SEEN, &now().to_string())
.unwrap();
store
.set_string(
account::LAST_VALIDATED_ONLINE,
&last_validated_online.to_string(),
)
.unwrap();
store
}
struct FixedResponse(String);
impl Transport for FixedResponse {
fn post_json(&self, _: &str, _: &[(String, String)], _: &str) -> TransportOutcome {
TransportOutcome::Response(HttpResponse {
status: 200,
body: self.0.clone(),
retry_after: None,
})
}
fn get(&self, _: &str, _: &[(String, String)]) -> TransportOutcome {
TransportOutcome::Response(HttpResponse {
status: 200,
body: "{}".into(),
retry_after: None,
})
}
}
struct AlwaysDown;
impl Transport for AlwaysDown {
fn post_json(&self, _: &str, _: &[(String, String)], _: &str) -> TransportOutcome {
TransportOutcome::Terminal("offline".into())
}
fn get(&self, _: &str, _: &[(String, String)]) -> TransportOutcome {
TransportOutcome::Terminal("offline".into())
}
}
struct FixedStatusResponse(u16, String);
impl Transport for FixedStatusResponse {
fn post_json(&self, _: &str, _: &[(String, String)], _: &str) -> TransportOutcome {
TransportOutcome::Response(HttpResponse {
status: self.0,
body: self.1.clone(),
retry_after: None,
})
}
fn get(&self, _: &str, _: &[(String, String)]) -> TransportOutcome {
TransportOutcome::Response(HttpResponse {
status: 200,
body: "{}".into(),
retry_after: None,
})
}
}
#[test]
fn revoke_is_caught_on_launch() {
let signing = signing_key();
let store = store_with_active_lease("kl-launch-revoke", &signing, now());
let cfg = config_trusting(&signing, Some(15));
let revoked_lease = lease_json(&signing, "expired", now() - 1000);
let body = format!(r#"{{"valid":false,"lease":{revoked_lease}}}"#);
let kl = Keylight::with_parts(cfg, store, Arc::new(FixedResponse(body)));
assert_eq!(
kl.state(),
LicenseState::Licensed,
"precondition: cached lease starts out valid"
);
kl.check_on_launch()
.expect("check_on_launch should not error on a definitive server rejection");
assert_ne!(
kl.state(),
LicenseState::Licensed,
"a dashboard revoke must be caught on the very next launch"
);
}
#[test]
fn transient_failure_keeps_access_within_cap() {
let signing = signing_key();
let store = store_with_active_lease("kl-launch-transient", &signing, now());
let cfg = config_trusting(&signing, Some(15));
let lease_before = store.get_string(account::LEASE);
let kl = Keylight::with_parts(cfg, store.clone(), Arc::new(AlwaysDown));
let result = kl.check_on_launch();
assert!(
result.is_err(),
"a genuine transport failure should surface as an error to the caller"
);
assert_eq!(
kl.state(),
LicenseState::Licensed,
"a transient failure must not deny a license that is still within the offline cap"
);
assert_eq!(
store.get_string(account::LEASE),
lease_before,
"a transient failure must not mutate the cached lease"
);
}
#[test]
fn past_offline_cap_denies_even_with_valid_cached_lease() {
let signing = signing_key();
let sixteen_days_ago = now() - 16 * 86400;
let store = store_with_active_lease("kl-launch-cap-exceeded", &signing, sixteen_days_ago);
let cfg = config_trusting(&signing, Some(15));
let kl = Keylight::with_parts(cfg, store, Arc::new(AlwaysDown));
assert_ne!(
kl.state(),
LicenseState::Licensed,
"a license unseen by the server for >15 days must not resolve as Licensed"
);
}
#[test]
fn real_422_no_lease_revoke_clears_stale_lease_and_denies() {
let signing = signing_key();
let store = store_with_active_lease("kl-launch-422-no-lease", &signing, now());
let cfg = config_trusting(&signing, Some(15));
let kl = Keylight::with_parts(
cfg,
store.clone(),
Arc::new(FixedStatusResponse(
422,
r#"{"error":"Instance not found or deactivated"}"#.into(),
)),
);
assert_eq!(
kl.state(),
LicenseState::Licensed,
"precondition: cached lease starts out valid"
);
kl.check_on_launch()
.expect("a definitive 422 rejection is not a transport error");
assert_ne!(
kl.state(),
LicenseState::Licensed,
"a real revoke/deactivation (422, no lease, no `valid` field) must deny"
);
assert!(
store.get_string(account::LEASE).is_none(),
"the stale 'active' lease must be cleared, not left behind for state() to reuse"
);
}
#[test]
fn real_422_with_expired_lease_keeps_lease_and_resolves_expired() {
let signing = signing_key();
let store = store_with_active_lease("kl-launch-422-with-lease", &signing, now());
let cfg = config_trusting(&signing, Some(15));
let expired_lease = lease_json(&signing, "expired", now() - 1000);
let body = format!(r#"{{"valid":false,"lease":{expired_lease}}}"#);
let kl = Keylight::with_parts(cfg, store.clone(), Arc::new(FixedStatusResponse(422, body)));
kl.check_on_launch()
.expect("a definitive 422 rejection is not a transport error");
assert_eq!(
kl.state(),
LicenseState::Expired,
"a 422 with an expired lease must resolve to Expired, with the lease kept"
);
assert!(
store.get_string(account::LEASE).is_some(),
"the server-sent expired lease must be persisted, not cleared"
);
}
#[test]
fn missing_anchor_with_cap_denies_fail_closed() {
let signing = signing_key();
let store = store_with_active_lease("kl-launch-missing-anchor", &signing, now());
store.delete(account::LAST_VALIDATED_ONLINE).unwrap();
let cfg = config_trusting(&signing, Some(15));
let kl = Keylight::with_parts(cfg, store, Arc::new(AlwaysDown));
assert!(
kl.cached_lease().is_none(),
"cached_lease() must deny when the online anchor is missing and the cap is set"
);
let s = kl.state();
assert_ne!(
s,
LicenseState::Licensed,
"a missing online anchor with the cap set must not resolve as Licensed"
);
assert_ne!(
s,
LicenseState::Limited,
"nor as any other entitled/grace state"
);
}
#[test]
fn missing_anchor_without_cap_still_allows() {
let signing = signing_key();
let store = store_with_active_lease("kl-launch-missing-anchor-nocap", &signing, now());
store.delete(account::LAST_VALIDATED_ONLINE).unwrap();
let cfg = config_trusting(&signing, None);
let kl = Keylight::with_parts(cfg, store, Arc::new(AlwaysDown));
assert!(
kl.cached_lease().is_some(),
"cached_lease() must allow with the cap disabled, anchor or not"
);
assert_eq!(
kl.state(),
LicenseState::Licensed,
"max_offline_days = None means unlimited offline, missing anchor notwithstanding"
);
}
#[test]
fn disabled_cap_never_denies_for_age() {
let signing = signing_key();
let ancient = now() - 1000 * 86400;
let store = store_with_active_lease("kl-launch-cap-disabled", &signing, ancient);
let cfg = config_trusting(&signing, None);
let kl = Keylight::with_parts(cfg, store, Arc::new(AlwaysDown));
assert_eq!(
kl.state(),
LicenseState::Licensed,
"max_offline_days = None must disable the offline cap entirely"
);
}