package licenseserver import ( "bytes" "crypto/ed25519" "crypto/rand" "encoding/json" "io" "log/slog" "net/http" "net/http/httptest" "testing" "time" "github.com/b1tsblog/ai-disclosure-standard/pkg/licensekit" ) func keyPair(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 TestRegisterIntrospectAndRevoke(t *testing.T) { issuerPub, issuerPriv := keyPair(t) leasePub, leasePriv := keyPair(t) storeKeys := licensekit.NewTrustStore() storeKeys.LicenseKeys["issuer"] = licensekit.EncodeKey(issuerPub) storeKeys.LeaseKeys["lease"] = licensekit.EncodeKey(leasePub) registry, err := OpenStore("") if err != nil { t.Fatal(err) } server, err := New(Config{TrustStore: storeKeys, LeasePrivateKey: leasePriv, LeaseKeyID: "lease", AdminToken: "secret", DefaultLeaseTTL: time.Hour}, registry, slog.New(slog.NewTextHandler(io.Discard, nil))) if err != nil { t.Fatal(err) } httpServer := httptest.NewServer(server.Handler()) defer httpServer.Close() now := time.Now().UTC() claims := licensekit.Claims{Version: 1, LicenseID: "lic_1", Issuer: "vendor", Customer: "customer", Product: "product", Edition: "pro", Features: []string{"feature"}, Domains: []string{"*"}, IssuedAt: now.Unix(), ExpiresAt: now.Add(time.Hour).Unix(), Verification: licensekit.VerificationPolicy{Mode: licensekit.ModeHybrid, LeaseTTLSeconds: 300}} token, err := licensekit.SignLicense(issuerPriv, "issuer", claims) if err != nil { t.Fatal(err) } registerBody, _ := json.Marshal(map[string]string{"token": token}) req, _ := http.NewRequest(http.MethodPost, httpServer.URL+"/v1/admin/licenses", bytes.NewReader(registerBody)) req.Header.Set("Authorization", "Bearer secret") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } resp.Body.Close() if resp.StatusCode != http.StatusCreated { t.Fatalf("register status %d", resp.StatusCode) } introBody, _ := json.Marshal(map[string]string{"token": token, "product": "product", "baseUrl": "https://example.org"}) resp, err = http.Post(httpServer.URL+"/v1/introspect", "application/json", bytes.NewReader(introBody)) if err != nil { t.Fatal(err) } var intro map[string]any _ = json.NewDecoder(resp.Body).Decode(&intro) resp.Body.Close() if resp.StatusCode != http.StatusOK || intro["valid"] != true { t.Fatalf("introspection failed: %d %#v", resp.StatusCode, intro) } req, _ = http.NewRequest(http.MethodPost, httpServer.URL+"/v1/admin/licenses/lic_1/revoke", bytes.NewBufferString(`{"reason":"payment"}`)) req.Header.Set("Authorization", "Bearer secret") req.Header.Set("Content-Type", "application/json") resp, err = http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("revoke status %d", resp.StatusCode) } resp, err = http.Post(httpServer.URL+"/v1/introspect", "application/json", bytes.NewReader(introBody)) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusForbidden { t.Fatalf("expected revoked status, got %d", resp.StatusCode) } }