设置→授权 Pro 态管理员可退出:在线 LIC- 先调 opsx deactivate(含 activation_token),成功才清本地;失败保留现状。LICOFF- 仅解本机并提示后台名额可能仍占。禁静默清码。
166 lines
4.4 KiB
Go
166 lines
4.4 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")
|
|
}
|
|
}
|
|
|
|
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()
|
|
|
|
_, err := DeactivateLicense()
|
|
if err == nil {
|
|
t.Fatal("expected remote failure")
|
|
}
|
|
if !strings.Contains(err.Error(), "远端解绑失败") {
|
|
t.Fatalf("unexpected err: %v", err)
|
|
}
|
|
stored := StoredLicense()
|
|
if stored == nil || stored.LicenseKey != "LIC-KEEP-ON-FAIL" || stored.Plan != "pro" {
|
|
t.Fatalf("must keep local state on failure: %+v", stored)
|
|
}
|
|
}
|
|
|
|
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")
|
|
}
|
|
}
|