package licenseclient import ( "context" "crypto/ed25519" "crypto/rand" "encoding/json" "net/http" "net/http/httptest" "path/filepath" "testing" "time" "github.com/b1tsblog/ai-disclosure-standard/pkg/licensekit" ) func keys(t *testing.T) (ed25519.PublicKey, ed25519.PrivateKey) { t.Helper() pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } return pub, priv } func licenseToken(t *testing.T, mode licensekit.VerificationMode, store *licensekit.TrustStore) (string, licensekit.Claims) { t.Helper() pub, priv := keys(t) store.LicenseKeys["issuer"] = licensekit.EncodeKey(pub) now := time.Now().UTC() claims := licensekit.Claims{Version: 1, LicenseID: "lic_1", Issuer: "vendor", Customer: "customer", Product: "product", Edition: "pro", Features: []string{"feature_a", "feature_b"}, Domains: []string{"*"}, IssuedAt: now.Unix(), ExpiresAt: now.Add(time.Hour).Unix(), Verification: licensekit.VerificationPolicy{Mode: mode, LeaseTTLSeconds: 600, OfflineGraceSeconds: 3600}} token, err := licensekit.SignLicense(priv, "issuer", claims) if err != nil { t.Fatal(err) } return token, claims } func TestOfflineClient(t *testing.T) { store := licensekit.NewTrustStore() token, _ := licenseToken(t, licensekit.ModeOffline, &store) c := New(context.Background(), Config{Product: "product", Token: token, TrustStore: store, BaseURL: "https://example.org"}) if !c.Status().Licensed || !c.Has("feature_a") { t.Fatalf("unexpected status %#v", c.Status()) } } func TestHybridClientUsesOnlineLeaseAndCache(t *testing.T) { store := licensekit.NewTrustStore() token, claims := licenseToken(t, licensekit.ModeHybrid, &store) leasePub, leasePriv := keys(t) store.LeaseKeys["lease"] = licensekit.EncodeKey(leasePub) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { now := time.Now().UTC() lease := licensekit.LeaseClaims{Version: 1, LeaseID: "lease_1", LicenseID: claims.LicenseID, Product: claims.Product, Customer: claims.Customer, Edition: claims.Edition, Features: claims.Features, Host: "example.org", IssuedAt: now.Unix(), ExpiresAt: now.Add(5 * time.Minute).Unix()} leaseToken, err := licensekit.SignLease(leasePriv, "lease", lease) if err != nil { t.Fatal(err) } _ = json.NewEncoder(w).Encode(map[string]any{"valid": true, "leaseToken": leaseToken}) })) cache := filepath.Join(t.TempDir(), "lease.json") c := New(context.Background(), Config{Product: "product", Token: token, TrustStore: store, BaseURL: "https://example.org", Mode: licensekit.ModeHybrid, ServerURL: server.URL, CacheFile: cache}) if !c.Status().Licensed || c.Status().Source != "online" { t.Fatalf("unexpected online status %#v", c.Status()) } server.Close() c.Refresh(context.Background()) if !c.Status().Licensed || c.Status().Source != "cached-lease" { t.Fatalf("unexpected cached status %#v", c.Status()) } } func TestOnlineModeFailsWithoutServer(t *testing.T) { store := licensekit.NewTrustStore() token, _ := licenseToken(t, licensekit.ModeOnline, &store) c := New(context.Background(), Config{Product: "product", Token: token, TrustStore: store, BaseURL: "https://example.org", Mode: licensekit.ModeOnline}) if c.Status().Licensed { t.Fatalf("online license unexpectedly active %#v", c.Status()) } }