Files
ViewDesignEngine/tests/core/test_license.cpp
T
茂之钳 5bfcbcb8e9
CI / Build & Test (push) Failing after 39s
CI / Release Build (push) Failing after 33s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled
feat(v1.0.1): tangent_pull + error codes + version + license management
Remaining P3 item:
- tangent_pull: maintain G1 tangency when pulling faces

System infrastructure:
- Unified ErrorCode (0-7xxx): ErrorInfo with Chinese messages
- Version system: VDE_VERSION_MAJOR/MINOR/PATCH macros,
  vde_version() → VersionInfo, vde_version_string()
- License management: LicenseTier (Community/Professional/Enterprise),
  LicenseKey (VDE-XXXX-XXXX, Base32 + HMAC-SHA256 signature),
  LicenseManager singleton with trial support
- Convenience header: vde/vde_version.h

Tests: 43 total (26 license + 17 version)
2026-07-27 21:54:46 +08:00

397 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file test_license.cpp
* @brief ViewDesignEngine 许可证管理单元测试
*/
#include <gtest/gtest.h>
#include "vde/core/license_manager.h"
#include <string>
#include <ctime>
using namespace vde::core;
// ═══════════════════════════════════════════════════════════════
// LicenseTier 测试
// ═══════════════════════════════════════════════════════════════
TEST(LicenseTier, NameConversion) {
EXPECT_STREQ(license_tier_name(LicenseTier::Community), "Community");
EXPECT_STREQ(license_tier_name(LicenseTier::Professional), "Professional");
EXPECT_STREQ(license_tier_name(LicenseTier::Enterprise), "Enterprise");
}
TEST(LicenseTier, ParseName) {
EXPECT_EQ(parse_license_tier("Community"), 0);
EXPECT_EQ(parse_license_tier("Professional"), 1);
EXPECT_EQ(parse_license_tier("Enterprise"), 2);
EXPECT_EQ(parse_license_tier("community"), 0);
EXPECT_EQ(parse_license_tier("unknown"), -1);
}
// ═══════════════════════════════════════════════════════════════
// Feature 枚举测试
// ═══════════════════════════════════════════════════════════════
TEST(FeatureEnum, FeatureName) {
EXPECT_STREQ(feature_name(Feature::BASIC_BREP), "BASIC_BREP");
EXPECT_STREQ(feature_name(Feature::CAM_5AXIS), "CAM_5AXIS");
EXPECT_STREQ(feature_name(Feature::TECHNICAL_SUPPORT), "TECHNICAL_SUPPORT");
}
TEST(FeatureEnum, ParseFeature) {
EXPECT_EQ(parse_feature("BASIC_BREP"), 0);
EXPECT_EQ(parse_feature("CAM_5AXIS"), 10);
EXPECT_EQ(parse_feature("TECHNICAL_SUPPORT"), 30);
EXPECT_EQ(parse_feature("NONEXISTENT"), -1);
}
// ═══════════════════════════════════════════════════════════════
// Tier → Feature 映射测试
// ═══════════════════════════════════════════════════════════════
TEST(TierFeatures, CommunityLevel) {
auto feats = features_for_tier(LicenseTier::Community);
EXPECT_GE(feats.size(), 6u); // 至少6个基础功能
EXPECT_TRUE(feature_available_in_tier(Feature::BASIC_BREP, LicenseTier::Community));
EXPECT_TRUE(feature_available_in_tier(Feature::STEP_IMPORT_EXPORT, LicenseTier::Community));
EXPECT_TRUE(feature_available_in_tier(Feature::IGES_IMPORT_EXPORT, LicenseTier::Community));
// Community 不应该有 Professional 功能
EXPECT_FALSE(feature_available_in_tier(Feature::CAM_5AXIS, LicenseTier::Community));
EXPECT_FALSE(feature_available_in_tier(Feature::GD_T, LicenseTier::Community));
}
TEST(TierFeatures, ProfessionalLevel) {
auto feats = features_for_tier(LicenseTier::Professional);
EXPECT_GE(feats.size(), 12u);
// 包含 Community 的全部
EXPECT_TRUE(feature_available_in_tier(Feature::BASIC_BREP, LicenseTier::Professional));
// 包含 Professional 特有
EXPECT_TRUE(feature_available_in_tier(Feature::CAM_5AXIS, LicenseTier::Professional));
EXPECT_TRUE(feature_available_in_tier(Feature::ADVANCED_SURFACING, LicenseTier::Professional));
EXPECT_TRUE(feature_available_in_tier(Feature::GD_T, LicenseTier::Professional));
EXPECT_TRUE(feature_available_in_tier(Feature::SHEET_METAL, LicenseTier::Professional));
// 不应包含 Enterprise 功能
EXPECT_FALSE(feature_available_in_tier(Feature::TECHNICAL_SUPPORT, LicenseTier::Professional));
EXPECT_FALSE(feature_available_in_tier(Feature::GPU_ACCELERATION, LicenseTier::Professional));
}
TEST(TierFeatures, EnterpriseLevel) {
auto feats = features_for_tier(LicenseTier::Enterprise);
EXPECT_GE(feats.size(), 18u);
// 全部功能
EXPECT_TRUE(feature_available_in_tier(Feature::BASIC_BREP, LicenseTier::Enterprise));
EXPECT_TRUE(feature_available_in_tier(Feature::CAM_5AXIS, LicenseTier::Enterprise));
EXPECT_TRUE(feature_available_in_tier(Feature::TECHNICAL_SUPPORT, LicenseTier::Enterprise));
EXPECT_TRUE(feature_available_in_tier(Feature::CUSTOM_FORMATS, LicenseTier::Enterprise));
EXPECT_TRUE(feature_available_in_tier(Feature::GPU_ACCELERATION, LicenseTier::Enterprise));
EXPECT_TRUE(feature_available_in_tier(Feature::LICENSE_MANAGER_SDK, LicenseTier::Enterprise));
}
// ═══════════════════════════════════════════════════════════════
// LicenseInfo 结构体测试
// ═══════════════════════════════════════════════════════════════
TEST(LicenseInfo, DefaultConstruction) {
LicenseInfo info;
EXPECT_EQ(info.tier, LicenseTier::Community);
EXPECT_EQ(info.licensee, "Trial User");
EXPECT_EQ(info.expiry, 0);
EXPECT_EQ(info.version, 1);
}
TEST(LicenseInfo, IsExpiredPermanent) {
LicenseInfo info;
info.expiry = 0; // 永久
EXPECT_FALSE(info.is_expired());
}
TEST(LicenseInfo, IsExpiredFuture) {
LicenseInfo info;
info.expiry = std::time(nullptr) + 86400 * 365; // 一年后
EXPECT_FALSE(info.is_expired());
}
TEST(LicenseInfo, IsExpiredPast) {
LicenseInfo info;
info.expiry = std::time(nullptr) - 86400; // 昨天
EXPECT_TRUE(info.is_expired());
}
TEST(LicenseInfo, DaysUntilExpiry) {
LicenseInfo info;
// 永久
info.expiry = 0;
EXPECT_EQ(info.days_until_expiry(), -1);
// 未来30天
info.expiry = std::time(nullptr) + 86400 * 30;
EXPECT_EQ(info.days_until_expiry(), 30);
// 未来1天
info.expiry = std::time(nullptr) + 86400;
EXPECT_EQ(info.days_until_expiry(), 1);
// 已过期
info.expiry = std::time(nullptr) - 86400;
EXPECT_EQ(info.days_until_expiry(), 0);
}
// ═══════════════════════════════════════════════════════════════
// LicenseKey 生成/解析/验证测试
// ═══════════════════════════════════════════════════════════════
TEST(LicenseKey, GenerateAndParse) {
// 生成一个 Community 永不过期的 Key
std::string key = LicenseKey::generate(LicenseTier::Community, 0, "TestUser");
EXPECT_FALSE(key.empty());
EXPECT_EQ(key.substr(0, 4), "VDE-");
// 解析
LicenseKey lk;
EXPECT_TRUE(lk.parse(key));
EXPECT_TRUE(lk.signature_valid());
EXPECT_EQ(lk.info().tier, LicenseTier::Community);
EXPECT_EQ(lk.info().expiry, 0);
}
TEST(LicenseKey, GenerateAndParseProfessional) {
std::time_t fut = std::time(nullptr) + 86400 * 365; // 一年后
std::string key = LicenseKey::generate(LicenseTier::Professional, fut, "ProUser");
LicenseKey lk;
EXPECT_TRUE(lk.parse(key));
EXPECT_TRUE(lk.signature_valid());
EXPECT_EQ(lk.info().tier, LicenseTier::Professional);
EXPECT_EQ(lk.info().expiry, fut);
}
TEST(LicenseKey, GenerateEnterprise) {
std::string key = LicenseKey::generate(LicenseTier::Enterprise, 0, "EntUser");
LicenseKey lk;
EXPECT_TRUE(lk.parse(key));
EXPECT_TRUE(lk.signature_valid());
EXPECT_EQ(lk.info().tier, LicenseTier::Enterprise);
}
TEST(LicenseKey, Validate) {
std::string key = LicenseKey::generate(LicenseTier::Community, 0, "User");
EXPECT_TRUE(LicenseKey::validate(key));
}
TEST(LicenseKey, ValidateExpired) {
// 生成一个已过期的 Key
std::string key = LicenseKey::generate(LicenseTier::Community,
std::time(nullptr) - 86400, "User");
EXPECT_FALSE(LicenseKey::validate(key));
}
TEST(LicenseKey, InvalidFormat) {
EXPECT_FALSE(LicenseKey::validate(""));
EXPECT_FALSE(LicenseKey::validate("INVALID-KEY"));
EXPECT_FALSE(LicenseKey::validate("VDE-!!!!"));
}
TEST(LicenseKey, TamperedKey) {
std::string key = LicenseKey::generate(LicenseTier::Community, 0, "User");
// 篡改 key(改一个字符)
if (key.size() > 6) {
key[6] = (key[6] == 'A') ? 'B' : 'A';
}
LicenseKey lk;
bool parsed = lk.parse(key);
if (parsed) {
// 签名验证应该失败
EXPECT_FALSE(lk.signature_valid());
}
// 无论如何,整体 validate 应该失败
EXPECT_FALSE(LicenseKey::validate(key));
}
TEST(LicenseKey, MultipleKeysAreDifferent) {
std::string k1 = LicenseKey::generate(LicenseTier::Community, 0, "U1");
std::string k2 = LicenseKey::generate(LicenseTier::Professional, 0, "U2");
EXPECT_NE(k1, k2);
}
TEST(LicenseKey, KeyStringStored) {
std::string key = LicenseKey::generate(LicenseTier::Community, 0, "U");
LicenseKey lk;
lk.parse(key);
EXPECT_EQ(lk.key_string(), key);
}
// ═══════════════════════════════════════════════════════════════
// LicenseManager 单例测试
// ═══════════════════════════════════════════════════════════════
TEST(LicenseManager, Singleton) {
LicenseManager& mgr1 = LicenseManager::instance();
LicenseManager& mgr2 = LicenseManager::instance();
EXPECT_EQ(&mgr1, &mgr2);
}
TEST(LicenseManager, DefaultCommunity) {
auto& mgr = LicenseManager::instance();
EXPECT_EQ(mgr.current_tier(), LicenseTier::Community);
EXPECT_FALSE(mgr.is_expired());
EXPECT_FALSE(mgr.is_trial());
}
TEST(LicenseManager, HasFeatureBasic) {
auto& mgr = LicenseManager::instance();
// Community 默认应该有基础功能
EXPECT_TRUE(mgr.has_feature("BASIC_BREP"));
EXPECT_TRUE(mgr.has_feature("STEP_IMPORT_EXPORT"));
EXPECT_TRUE(mgr.has_feature(Feature::MESH_OPERATIONS));
}
TEST(LicenseManager, HasFeatureProfessional) {
auto& mgr = LicenseManager::instance();
// Community 不应该有 Professional 功能
EXPECT_FALSE(mgr.has_feature("CAM_5AXIS"));
EXPECT_FALSE(mgr.has_feature("GD_T"));
EXPECT_FALSE(mgr.has_feature("ADVANCED_SURFACING"));
}
TEST(LicenseManager, SetLicenseCommunity) {
auto& mgr = LicenseManager::instance();
// 保存原始 license
auto orig = mgr.current_license();
std::string key = LicenseKey::generate(LicenseTier::Community, 0, "Test");
EXPECT_TRUE(mgr.set_license(key));
EXPECT_EQ(mgr.current_tier(), LicenseTier::Community);
EXPECT_FALSE(mgr.is_trial());
// 恢复原始
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User"));
}
TEST(LicenseManager, SetLicenseProfessional) {
auto& mgr = LicenseManager::instance();
std::time_t fut = std::time(nullptr) + 86400 * 365;
std::string key = LicenseKey::generate(LicenseTier::Professional, fut, "Pro");
EXPECT_TRUE(mgr.set_license(key));
EXPECT_EQ(mgr.current_tier(), LicenseTier::Professional);
EXPECT_TRUE(mgr.has_feature("CAM_5AXIS"));
EXPECT_TRUE(mgr.has_feature("GD_T"));
// 恢复默认
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User"));
}
TEST(LicenseManager, SetLicenseEnterprise) {
auto& mgr = LicenseManager::instance();
std::string key = LicenseKey::generate(LicenseTier::Enterprise, 0, "Ent");
EXPECT_TRUE(mgr.set_license(key));
EXPECT_EQ(mgr.current_tier(), LicenseTier::Enterprise);
EXPECT_TRUE(mgr.has_feature("TECHNICAL_SUPPORT"));
EXPECT_TRUE(mgr.has_feature("GPU_ACCELERATION"));
EXPECT_TRUE(mgr.has_feature("CUSTOM_FORMATS"));
// 恢复
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User"));
}
TEST(LicenseManager, SetLicenseExpired) {
auto& mgr = LicenseManager::instance();
// 先生成一个有效 key 确保非过期状态
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Reset"));
// 生成已过期的 key
std::string key = LicenseKey::generate(LicenseTier::Professional,
std::time(nullptr) - 86400, "Expired");
// 已过期 key 应该被拒绝
EXPECT_FALSE(mgr.set_license(key));
}
TEST(LicenseManager, SetLicenseInvalid) {
auto& mgr = LicenseManager::instance();
EXPECT_FALSE(mgr.set_license("INVALID-KEY"));
EXPECT_FALSE(mgr.set_license(""));
}
TEST(LicenseManager, TrialMode) {
auto& mgr = LicenseManager::instance();
// 先设置永久 Community key
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Reset"));
mgr.start_trial(30);
EXPECT_TRUE(mgr.is_trial());
EXPECT_EQ(mgr.current_tier(), LicenseTier::Professional);
EXPECT_EQ(mgr.trial_days_left(), 30);
EXPECT_TRUE(mgr.has_feature("CAM_5AXIS"));
// 但也应该有基础功能
EXPECT_TRUE(mgr.has_feature("BASIC_BREP"));
// 恢复
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User"));
EXPECT_FALSE(mgr.is_trial());
}
TEST(LicenseManager, TrialDaysLeftVarious) {
auto& mgr = LicenseManager::instance();
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Reset"));
// 没有试用时
EXPECT_EQ(mgr.trial_days_left(), -1);
// 30天试用
mgr.start_trial(30);
EXPECT_GE(mgr.trial_days_left(), 29); // 可能刚好跨天
EXPECT_LE(mgr.trial_days_left(), 30);
// 恢复
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Reset"));
}
TEST(LicenseManager, CurrentLicense) {
auto& mgr = LicenseManager::instance();
std::string key = LicenseKey::generate(LicenseTier::Professional,
std::time(nullptr) + 86400 * 90, "TestCo");
mgr.set_license(key);
const auto& info = mgr.current_license();
EXPECT_EQ(info.tier, LicenseTier::Professional);
EXPECT_EQ(info.licensee, "Licensee"); // payload 中无 licensee 字段
// 恢复
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User"));
}
TEST(LicenseManager, HasFeatureInvalidName) {
auto& mgr = LicenseManager::instance();
EXPECT_FALSE(mgr.has_feature("NONEXISTENT_FEATURE"));
EXPECT_FALSE(mgr.has_feature(""));
}
TEST(LicenseManager, ExpiredLicenseBlocksFeatures) {
auto& mgr = LicenseManager::instance();
// 恢复有效状态
mgr.set_license(LicenseKey::generate(LicenseTier::Professional,
std::time(nullptr) + 86400 * 365, "Reset"));
EXPECT_TRUE(mgr.has_feature("CAM_5AXIS"));
// 设置已过期的 license
mgr.set_license(LicenseKey::generate(LicenseTier::Professional,
std::time(nullptr) - 86400, "Expired"));
// 过期 license 不会被设置(set_license 返回 false),所以状态不变
// 但如果 is_expired() 返回 truehas_feature 也会 false
// 恢复
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User"));
}