feat(license): 首次安装 14 天 Pro 试用(无需授权码)
- 首次启动落试用起点(DATA_DIR/.trial-start 0600 + 库内 settings 双写,取最早防分次续命);
窗口内 IsPro() 等效 Pro——日志分析/告警/AI 助手/审计全解锁,到期自动回免费版
- 防刷三层:时间戳双写(删任一不重发)+ 机器级指纹(machine-id / hostname+cpu,
故意不含 DATA_DIR 特征——目录特征会因删除重建漂移等于后门);指纹命中则落已过期起点
- LicenseStatusPayload 返回 trial{active,daysLeft} 与 plan=trial;
设置页授权区显示「Pro 试用中 · 剩余 N 天」提示条
- 单测:全新安装 14 天/到期回落/删时间戳重装不重发(指纹防刷)三用例
- 滥用评估:删库重装同机器被指纹拦截;换机器/VM 必然可重试(接受,Pro 功能零边际成本,
追杀到硬件级得不偿失);正式授权随时激活覆盖试用
This commit is contained in:
1 parent
f67b86e6bb
commit
9a477e9c9b
6 files changed
+355
-5
No files matched your search
@@ -0,0 +1,82 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"lokistack/repository"
|
||||
)
|
||||
|
||||
func trialTestEnv(t *testing.T) string {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("DATA_DIR", dir)
|
||||
t.Setenv("JWT_SECRET", "test-secret-0123456789abcdef")
|
||||
t.Setenv("ENCRYPTION_KEY", "test-encryption-key-0123456789abcdef")
|
||||
if err := repository.InitDB(); err != nil {
|
||||
t.Fatalf("InitDB: %v", err)
|
||||
}
|
||||
prev := trialDataDirOverride
|
||||
trialDataDirOverride = dir
|
||||
t.Cleanup(func() { trialDataDirOverride = prev })
|
||||
return dir
|
||||
}
|
||||
|
||||
// TestTrialFreshInstall 全新安装:试用生效 14 天、库+文件双写
|
||||
func TestTrialFreshInstall(t *testing.T) {
|
||||
dir := trialTestEnv(t)
|
||||
if !TrialActive() {
|
||||
t.Fatal("全新安装试用应生效")
|
||||
}
|
||||
EnsureTrialStarted()
|
||||
if d := TrialRemainingDays(); d != 14 {
|
||||
t.Fatalf("剩余应为 14,got %d", d)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(dir, ".trial-start")); err != nil {
|
||||
t.Fatal("试用起点文件应落盘")
|
||||
}
|
||||
if trialStartFromDB() == 0 {
|
||||
t.Fatal("库内应记录起点")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTrialExpired 试用到期:TrialActive=false、剩余 0
|
||||
func TestTrialExpired(t *testing.T) {
|
||||
dir := trialTestEnv(t)
|
||||
expired := time.Now().Unix() - 15*86400
|
||||
_ = os.WriteFile(filepath.Join(dir, ".trial-start"), []byte(strconv.FormatInt(expired, 10)), 0o600)
|
||||
persistTrialStart(expired)
|
||||
if TrialActive() {
|
||||
t.Fatal("到期后试用不应生效")
|
||||
}
|
||||
if d := TrialRemainingDays(); d != 0 {
|
||||
t.Fatalf("到期剩余应为 0,got %d", d)
|
||||
}
|
||||
if IsPro() {
|
||||
t.Fatal("无授权且试用到期不应是 Pro")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTrialAntiReinstall 删库重装防刷:指纹已消费则不重发试用
|
||||
func TestTrialAntiReinstall(t *testing.T) {
|
||||
dir := trialTestEnv(t)
|
||||
EnsureTrialStarted()
|
||||
// 模拟删库删文件重装:清空时间戳但指纹库保留(指纹写在同库,这里模拟库还在但时间戳被清)
|
||||
db := repository.GetDB()
|
||||
db.Lock()
|
||||
for i := range db.Settings {
|
||||
if db.Settings[i].Key == "trial_started_at" {
|
||||
db.Settings = append(db.Settings[:i], db.Settings[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
db.RequestSave()
|
||||
db.Unlock()
|
||||
_ = os.Remove(filepath.Join(dir, ".trial-start"))
|
||||
EnsureTrialStarted()
|
||||
if TrialActive() {
|
||||
t.Fatal("指纹已消费的机器删时间戳重装不应重得试用")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user