Files
LokiStack/backend/services/license_deactivate_test.go
opsx 8423192719
CI / Backend (go vet + test) (push) Waiting to run
CI / Frontend (lint + build + test) (push) Waiting to run
test(license): 退出授权降级语义测试同步——远端失败清本机+标记而非报错保留
2026-11-01 04:08:12 +08:00

167 lines
4.5 KiB
Go

package services
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"lokistack/config"
"lokistack/models"
"lokistack/repository"
)
func withTempDB(t *testing.T) {
t.Helper()
t.Setenv("DATA_DIR", t.TempDir())
t.Setenv("JWT_SECRET", "test-jwt-secret-with-at-least-32-characters")
t.Setenv("ENCRYPTION_KEY", "test-encryption-key-with-at-least-32-characters")
if err := repository.InitDB(); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = repository.Shutdown() })
}
func TestDeactivateLicenseHMACClearsLocal(t *testing.T) {
withTempDB(t)
key, err := GenerateLicenseKey("pro", "personal", "c", 1, 1, 0)
if err != nil {
t.Fatal(err)
}
if _, err := ActivateLicense(key); err != nil {
t.Fatal(err)
}
if CurrentLicense() == nil {
t.Fatal("expected pro before deactivate")
}
res, err := DeactivateLicense()
if err != nil {
t.Fatal(err)
}
if res.OfflineLocalOnly {
t.Fatal("HMAC should not be offlineLocalOnly")
}
if CurrentLicense() != nil || StoredLicense() != nil {
t.Fatal("expected local license cleared")
}
}
func TestDeactivateLicenseOfflineLocalOnly(t *testing.T) {
withTempDB(t)
instanceID := GetOrCreateInstanceID()
// 无法签发真 LICOFF- 无私钥;直接写入库存模拟离线证
db := repository.GetDB()
db.Lock()
db.License = &models.License{
Plan: "pro", Source: "opsx", LicenseKey: "LICOFF-TEST-FAKE",
InstanceID: instanceID, ActivatedAt: time.Now(), IssuedAt: time.Now(),
}
db.RequestSave()
db.Unlock()
res, err := DeactivateLicense()
if err != nil {
t.Fatal(err)
}
if !res.OfflineLocalOnly {
t.Fatal("expected offlineLocalOnly")
}
if !strings.Contains(res.Message, "后台") && !strings.Contains(strings.ToLower(res.Message), "ops") {
// 中文提示
if res.Message == "" {
t.Fatal("expected offline note message")
}
}
if StoredLicense() != nil {
t.Fatal("expected cleared")
}
}
// 远端解绑失败时不再报错卡死(用户无法自愈),降级为清本机 + offlineLocalOnly 标记;
// 授权服务不返回 activation_token 的现实下,"保留本地等重试"等于永远解不了绑。
func TestDeactivateLicenseOnlineKeepsStateOnRemoteFailure(t *testing.T) {
withTempDB(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]any{"code": 1, "message": "解绑校验失败"})
}))
defer srv.Close()
cfg := config.GetConfig()
prev := cfg.LicenseAPIBase
cfg.LicenseAPIBase = srv.URL
defer func() { cfg.LicenseAPIBase = prev }()
db := repository.GetDB()
db.Lock()
db.License = &models.License{
Plan: "pro", Source: "opsx", LicenseKey: "LIC-KEEP-ON-FAIL",
InstanceID: GetOrCreateInstanceID(), ActivationToken: "tok",
ActivatedAt: time.Now(), IssuedAt: time.Now(), LastVerifiedAt: time.Now().Unix(),
}
db.RequestSave()
db.Unlock()
res, err := DeactivateLicense()
if err != nil {
t.Fatalf("远端失败应降级成功而非报错: %v", err)
}
if !res.OfflineLocalOnly {
t.Fatal("远端失败降级时应标记 OfflineLocalOnly")
}
if StoredLicense() != nil {
t.Fatal("降级后本机授权应已清除")
}
}
func TestDeactivateLicenseOnlineSuccessClears(t *testing.T) {
withTempDB(t)
var gotBody map[string]any
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/license/deactivate" {
t.Errorf("path %s", r.URL.Path)
}
_ = json.NewDecoder(r.Body).Decode(&gotBody)
_ = json.NewEncoder(w).Encode(map[string]any{"code": 0, "message": "ok"})
}))
defer srv.Close()
cfg := config.GetConfig()
prev := cfg.LicenseAPIBase
cfg.LicenseAPIBase = srv.URL
defer func() { cfg.LicenseAPIBase = prev }()
id := GetOrCreateInstanceID()
db := repository.GetDB()
db.Lock()
db.License = &models.License{
Plan: "pro", Source: "opsx", LicenseKey: "LIC-OK",
InstanceID: id, ActivationToken: "act-token",
ActivatedAt: time.Now(), IssuedAt: time.Now(), LastVerifiedAt: time.Now().Unix(),
}
db.RequestSave()
db.Unlock()
res, err := DeactivateLicense()
if err != nil {
t.Fatal(err)
}
if res.OfflineLocalOnly {
t.Fatal("online should not set offlineLocalOnly")
}
if gotBody["license_key"] != "LIC-OK" || gotBody["device_id"] != id || gotBody["activation_token"] != "act-token" {
t.Fatalf("bad deactivate body: %#v", gotBody)
}
if StoredLicense() != nil {
t.Fatal("expected cleared after success")
}
}
func TestDeactivateLicenseEmpty(t *testing.T) {
withTempDB(t)
if _, err := DeactivateLicense(); err == nil {
t.Fatal("expected error when no license")
}
}