/** * @file tests/unit/test_license.cpp * @brief Tests for the provider-neutral license API. */ // test includes #include // local includes #include "platform/shared/lvh_broker_license_policy.hpp" // lib includes #include // standard includes #include #if defined(_WIN32) // local includes #include "platform/windows/windows_broker_client.hpp" #endif TEST(LicenseStatusTest, LicensedReflectsCurrentState) { lvh::LicenseStatus status; EXPECT_FALSE(status.licensed()); status.state = lvh::LicenseState::licensed; EXPECT_TRUE(status.licensed()); status.state = lvh::LicenseState::expired; EXPECT_FALSE(status.licensed()); } TEST(BrokerLicensePolicyTest, AcceptsOnlyConfiguredBenefits) { const auto *yearly = lvh::broker_license::benefit(lvh::broker_license::allowed_benefits[0].id); const auto *lifetime = lvh::broker_license::benefit(lvh::broker_license::allowed_benefits[1].id); ASSERT_NE(yearly, nullptr); ASSERT_NE(lifetime, nullptr); EXPECT_TRUE(yearly->subscription_backed); EXPECT_FALSE(lifetime->subscription_backed); EXPECT_EQ(lvh::broker_license::plan_name(yearly->id), "Yearly"); EXPECT_EQ(lvh::broker_license::plan_name(lifetime->id), "Lifetime"); EXPECT_EQ(lvh::broker_license::benefit("unrecognized-benefit"), nullptr); } TEST(BrokerLicensePolicyTest, EnforcesSubscriptionAndOutageBoundaries) { using namespace std::chrono_literals; constexpr auto start = 1000U; constexpr auto max_age = std::chrono::duration_cast(lvh::broker_license::subscription_max_age).count(); EXPECT_TRUE(lvh::broker_license::subscription_current(start, start)); EXPECT_TRUE(lvh::broker_license::subscription_current(start, start + max_age - 1)); EXPECT_FALSE(lvh::broker_license::subscription_current(start, start + max_age)); EXPECT_FALSE(lvh::broker_license::subscription_current(start, start - 1)); EXPECT_FALSE(lvh::broker_license::subscription_current(0, start)); EXPECT_FALSE(lvh::broker_license::outage_retention_elapsed(1h - 1ms)); EXPECT_TRUE(lvh::broker_license::outage_retention_elapsed(1h)); } TEST(BrokerLicensePolicyTest, RetainsOneDeviceAfterAnOutage) { lvh::broker_license::OutageDeviceSelector selector; EXPECT_TRUE(selector.keep(1)); EXPECT_TRUE(selector.keep(1)); EXPECT_FALSE(selector.keep(2)); selector.remove(2); EXPECT_FALSE(selector.keep(2)); selector.remove(1); EXPECT_TRUE(selector.keep(2)); selector.reset(); EXPECT_TRUE(selector.keep(3)); EXPECT_FALSE(selector.keep(2)); } TEST(GitHubActionsEvaluationTest, IsActiveOnlyInsideFiveMinuteWindow) { using namespace std::chrono_literals; using lvh::broker_license::github_actions_evaluation::active; const auto started_at = lvh::broker_license::github_actions_evaluation::Clock::time_point {1000s}; EXPECT_TRUE(active(started_at, started_at)); EXPECT_TRUE(active(started_at, started_at + 5min - 1s)); EXPECT_FALSE(active(started_at, started_at + 5min)); EXPECT_FALSE(active(started_at, started_at - 1s)); } TEST(GitHubActionsEvaluationTest, RemainingTimeClampsAtWindowBoundaries) { using namespace std::chrono_literals; using lvh::broker_license::github_actions_evaluation::remaining; const auto started_at = lvh::broker_license::github_actions_evaluation::Clock::time_point {1000s}; EXPECT_EQ(remaining(started_at, started_at), 5min); EXPECT_EQ(remaining(started_at, started_at + 4min), 1min); EXPECT_EQ(remaining(started_at, started_at + 5min - 500ms), 1s); EXPECT_EQ(remaining(started_at, started_at + 5min), 0s); EXPECT_EQ(remaining(started_at, started_at - 1s), 0s); } #if !defined(_WIN32) && !defined(__APPLE__) TEST(LicenseApiTest, UnsupportedPlatformReturnsExplicitFailure) { const auto queried = lvh::get_license_status(); EXPECT_FALSE(queried); EXPECT_EQ(queried.status.code(), lvh::ErrorCode::backend_unavailable); EXPECT_EQ(queried.license.state, lvh::LicenseState::unavailable); EXPECT_FALSE(queried.license.service_available); EXPECT_FALSE(lvh::activate_license("test-key")); EXPECT_FALSE(lvh::validate_license()); EXPECT_FALSE(lvh::deactivate_license()); } #endif #if defined(_WIN32) TEST(WindowsBrokerClientTest, BuildsVersionedRequestHeader) { const auto header = lvh::detail::windows_broker::make_request_header( LvhWindowsBrokerRequestType::validate_license, sizeof(LvhWindowsBrokerLicenseRequest) ); EXPECT_EQ(header.version, LVH_WINDOWS_BROKER_PROTOCOL_VERSION); EXPECT_EQ(header.size, sizeof(LvhWindowsBrokerLicenseRequest)); EXPECT_EQ(header.type, static_cast(LvhWindowsBrokerRequestType::validate_license)); EXPECT_EQ(header.reserved0, 0U); EXPECT_EQ(LVH_WINDOWS_BROKER_PROTOCOL_VERSION, 5U); } TEST(WindowsBrokerClientTest, PreservesFixedWireLayout) { EXPECT_EQ(sizeof(LvhWindowsBrokerRequestHeader), 16U); EXPECT_EQ(sizeof(LvhWindowsBrokerLicenseStatus), 796U); EXPECT_EQ(sizeof(LvhWindowsBrokerStatusRequest), 16U); EXPECT_EQ(sizeof(LvhWindowsBrokerStatusResponse), 1324U); EXPECT_EQ(sizeof(LvhWindowsBrokerCreateDeviceRequest), 2528U); EXPECT_EQ(sizeof(LvhWindowsBrokerCreateDeviceResponse), 1648U); EXPECT_EQ(sizeof(LvhWindowsBrokerDestroyDeviceRequest), 64U); EXPECT_EQ(sizeof(LvhWindowsBrokerDestroyDeviceResponse), 1324U); EXPECT_EQ(sizeof(LvhWindowsBrokerLicenseRequest), 272U); EXPECT_EQ(sizeof(LvhWindowsBrokerLicenseResponse), 1324U); } TEST(WindowsBrokerClientTest, MapsLicenseAndTransportStatuses) { using lvh::detail::windows_broker::response_status; EXPECT_TRUE(response_status(static_cast(LvhWindowsBrokerStatusCode::success), "").ok()); EXPECT_EQ(response_status(static_cast(LvhWindowsBrokerStatusCode::invalid_argument), "bad").code(), lvh::ErrorCode::invalid_argument); EXPECT_EQ(response_status(static_cast(LvhWindowsBrokerStatusCode::unsupported_profile), "bad").code(), lvh::ErrorCode::unsupported_profile); EXPECT_EQ(response_status(static_cast(LvhWindowsBrokerStatusCode::device_not_found), "bad").code(), lvh::ErrorCode::device_closed); EXPECT_EQ(response_status(static_cast(LvhWindowsBrokerStatusCode::backend_unavailable), "bad").code(), lvh::ErrorCode::backend_unavailable); EXPECT_EQ(response_status(static_cast(LvhWindowsBrokerStatusCode::license_required), "bad").code(), lvh::ErrorCode::license_required); EXPECT_EQ(response_status(static_cast(LvhWindowsBrokerStatusCode::license_invalid), "bad").code(), lvh::ErrorCode::license_invalid); EXPECT_EQ(response_status(static_cast(LvhWindowsBrokerStatusCode::activation_limit_reached), "bad").code(), lvh::ErrorCode::activation_limit_reached); EXPECT_EQ(response_status(static_cast(LvhWindowsBrokerStatusCode::network_unavailable), "bad").code(), lvh::ErrorCode::network_unavailable); EXPECT_EQ(response_status(static_cast(LvhWindowsBrokerStatusCode::backend_failure), "bad").code(), lvh::ErrorCode::backend_failure); EXPECT_EQ(response_status(999U, "bad").code(), lvh::ErrorCode::backend_failure); EXPECT_EQ(response_status(static_cast(LvhWindowsBrokerStatusCode::backend_failure), "").message(), "Windows broker request failed"); } #endif