da12a9ec1c
- bj_exchange и «кран» Fancy убраны: данные модуля ЛК в PostgreSQL (схема lk) - трансферные таблицы FTV_M2M: создание при установке, события, справочник участников - сверка с DEPO: счёт, ФИО, дата рождения, ИИС по договору, квал-инвестор из опции DEPO - боевой ЛК ЕСИА Финанс при тестовом ИШ: журнал, отказ через DEPO, статусы в ЛК - онлайн-лицензирование в bj, план тестирования M2M для контрагента (интерактивный HTML) - модули QUIK МНП, экспорт QUIK, НПР/УДС; документы и тестовые материалы Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
138 lines
4.9 KiB
Go
138 lines
4.9 KiB
Go
package lkgateway
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ed25519"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/license"
|
|
)
|
|
|
|
// testLicenseKey подменяет зашитый ключ проверки тестовой парой.
|
|
func testLicenseKey(t *testing.T) ed25519.PrivateKey {
|
|
t.Helper()
|
|
pub, priv, err := ed25519.GenerateKey(nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
prev := DefaultLicensePublicKey
|
|
DefaultLicensePublicKey = base64.StdEncoding.EncodeToString(pub)
|
|
t.Cleanup(func() { DefaultLicensePublicKey = prev })
|
|
return priv
|
|
}
|
|
|
|
func testRC(t *testing.T) *RuntimeConfig {
|
|
t.Helper()
|
|
rc, err := NewRuntimeConfig(filepath.Join(t.TempDir(), "setup.json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return rc
|
|
}
|
|
|
|
func signTestLicense(t *testing.T, priv ed25519.PrivateKey, l *license.License) string {
|
|
t.Helper()
|
|
tok, err := license.Sign(l, priv, "main")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return tok.Encode()
|
|
}
|
|
|
|
func TestLicenseActivateAndRenew(t *testing.T) {
|
|
priv := testLicenseKey(t)
|
|
rc := testRC(t)
|
|
var gotInstall string
|
|
renewed := 0
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
var in map[string]string
|
|
_ = json.NewDecoder(r.Body).Decode(&in)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if r.URL.Path == "/v1/activate" && in["code"] != "BJ-TEST-CODE-0001" {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
_, _ = w.Write([]byte(`{"error":"unknown_code","message":"код активации не найден"}`))
|
|
return
|
|
}
|
|
if r.URL.Path == "/v1/renew" {
|
|
renewed++
|
|
}
|
|
gotInstall = in["install_id"]
|
|
now := time.Now().UTC()
|
|
tok := signTestLicense(t, priv, &license.License{Schema: 1, ID: "lic-1", Tenant: "Тест", Product: "bj-server", Plan: license.PlanPro,
|
|
IssuedAt: now, ExpiresAt: now.Add(60 * 24 * time.Hour), Features: []string{"npr"}, InstallID: in["install_id"], ContractUntil: now.AddDate(1, 0, 0)})
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"token": tok, "license_id": "lic-1", "tenant": "Тест"})
|
|
}))
|
|
defer srv.Close()
|
|
prev := licenseServer
|
|
licenseServer = srv.URL
|
|
t.Cleanup(func() { licenseServer = prev })
|
|
ctx := context.Background()
|
|
|
|
if err := LicenseActivate(ctx, rc, "BJ-XXXX-XXXX-XXXX"); err == nil || !strings.Contains(err.Error(), "не найден") {
|
|
t.Fatalf("неверный код: ожидали отказ сервера, получили %v", err)
|
|
}
|
|
if licenseStatus(rc).Present {
|
|
t.Fatal("после отказа ключ не должен сохраняться")
|
|
}
|
|
if err := LicenseActivate(ctx, rc, "BJ-TEST-CODE-0001"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
st := licenseStatus(rc)
|
|
if !st.Valid || !st.Online || st.Tenant != "Тест" {
|
|
t.Fatalf("после активации: %+v", st)
|
|
}
|
|
if gotInstall == "" || gotInstall != rc.Snapshot().License.InstallID {
|
|
t.Fatalf("install_id в запросе %q, в настройках %q", gotInstall, rc.Snapshot().License.InstallID)
|
|
}
|
|
if !moduleAllowed(rc, "npr") || moduleAllowed(rc, "quik-mnp") {
|
|
t.Fatal("модули должны следовать признакам лицензии pro")
|
|
}
|
|
if licenseNeedsRenew(rc) {
|
|
t.Fatal("сразу после активации продлевать не нужно")
|
|
}
|
|
if err := LicenseRenew(ctx, rc); err != nil || renewed != 1 {
|
|
t.Fatalf("продление: %v, обращений %d", err, renewed)
|
|
}
|
|
|
|
s := rc.Snapshot().License
|
|
s.InstallID = "00000000-0000-4000-8000-000000000000"
|
|
_ = rc.SaveLicense(s)
|
|
if st := licenseStatus(rc); st.Valid || !strings.Contains(st.Message, "другой установке") {
|
|
t.Fatalf("ключ чужой установки должен быть недействителен: %+v", st)
|
|
}
|
|
}
|
|
|
|
func TestLicenseOfflineEnterpriseAndForeignKey(t *testing.T) {
|
|
priv := testLicenseKey(t)
|
|
rc := testRC(t)
|
|
now := time.Now().UTC()
|
|
s := rc.Snapshot().License
|
|
s.Key = signTestLicense(t, priv, &license.License{Schema: 1, ID: "lic-2", Tenant: "Офлайн", Product: "bj-server", Plan: license.PlanEnterprise,
|
|
IssuedAt: now, ExpiresAt: now.AddDate(1, 0, 0)})
|
|
_ = rc.SaveLicense(s)
|
|
st := licenseStatus(rc)
|
|
if !st.Valid || st.Online {
|
|
t.Fatalf("офлайн-ключ: %+v", st)
|
|
}
|
|
if !moduleAllowed(rc, "quik-export") || !moduleAllowed(rc, "npr") {
|
|
t.Fatal("enterprise должен открывать все модули")
|
|
}
|
|
if err := LicenseRenew(context.Background(), rc); err != errLicenseOffline {
|
|
t.Fatalf("офлайн-ключ не продлевается: %v", err)
|
|
}
|
|
|
|
_, other, _ := ed25519.GenerateKey(nil)
|
|
s.Key = signTestLicense(t, other, &license.License{Schema: 1, ID: "x", Plan: license.PlanEnterprise, IssuedAt: now, ExpiresAt: now.AddDate(1, 0, 0)})
|
|
_ = rc.SaveLicense(s)
|
|
if licenseStatus(rc).Valid || moduleAllowed(rc, "npr") {
|
|
t.Fatal("ключ, подписанный чужим ключом, не должен приниматься")
|
|
}
|
|
}
|