163 lines
6.5 KiB
Go
163 lines
6.5 KiB
Go
package application
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"softbox.local/core/licensing"
|
||
|
|
)
|
||
|
|
|
||
|
|
const applicationTestMachineHash = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||
|
|
|
||
|
|
func TestAuthorizationServiceUsesCurrentGraceAndRevocationStates(t *testing.T) {
|
||
|
|
now := time.Date(2026, 7, 20, 12, 0, 0, 0, time.UTC)
|
||
|
|
store := &authorizationStoreFake{
|
||
|
|
licenses: []licensing.License{{
|
||
|
|
LicenseID: "lic-active", Products: []string{"product-active"}, Perpetual: true, RebindPolicy: "support-only",
|
||
|
|
}, {
|
||
|
|
LicenseID: "lic-revoked", Products: []string{"product-revoked"}, RebindPolicy: "support-only",
|
||
|
|
}},
|
||
|
|
revocations: licensing.RevocationList{
|
||
|
|
GeneratedAt: now.Add(-2 * time.Hour), ExpiresAt: now.Add(time.Hour), RevokedLicenseIDs: []string{"lic-revoked"},
|
||
|
|
},
|
||
|
|
found: true,
|
||
|
|
}
|
||
|
|
service := newAuthorizationServiceForTest(t, store, now)
|
||
|
|
snapshot, err := service.Snapshot(context.Background())
|
||
|
|
if err != nil || snapshot.State != AuthorizationStateReady || len(snapshot.Products) != 1 || snapshot.Products[0].ProductID != "product-active" {
|
||
|
|
t.Fatalf("Snapshot() = (%#v, %v)", snapshot, err)
|
||
|
|
}
|
||
|
|
if authorized, err := service.IsAuthorized("product-active"); err != nil || !authorized {
|
||
|
|
t.Fatalf("IsAuthorized(active) = (%t, %v)", authorized, err)
|
||
|
|
}
|
||
|
|
if authorized, err := service.IsAuthorized("product-revoked"); err != nil || authorized {
|
||
|
|
t.Fatalf("IsAuthorized(revoked) = (%t, %v)", authorized, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
store.revocations.ExpiresAt = now.Add(-time.Hour)
|
||
|
|
snapshot, err = service.Snapshot(context.Background())
|
||
|
|
if err != nil || snapshot.State != AuthorizationStateGrace {
|
||
|
|
t.Fatalf("Snapshot(grace) = (%#v, %v)", snapshot, err)
|
||
|
|
}
|
||
|
|
store.revocations.ExpiresAt = now.Add(-licensing.RevocationGrace - time.Second)
|
||
|
|
snapshot, err = service.Snapshot(context.Background())
|
||
|
|
if !errors.Is(err, ErrAuthorizationUnavailable) || snapshot.State != AuthorizationStateUnavailable {
|
||
|
|
t.Fatalf("Snapshot(stale) = (%#v, %v)", snapshot, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestAuthorizationServiceFailsClosedWithoutRevocations(t *testing.T) {
|
||
|
|
now := time.Date(2026, 7, 20, 12, 0, 0, 0, time.UTC)
|
||
|
|
store := &authorizationStoreFake{licenses: []licensing.License{{
|
||
|
|
LicenseID: "lic-active", Products: []string{"product-active"}, RebindPolicy: "support-only",
|
||
|
|
}}}
|
||
|
|
service := newAuthorizationServiceForTest(t, store, now)
|
||
|
|
snapshot, err := service.Snapshot(context.Background())
|
||
|
|
if !errors.Is(err, ErrAuthorizationUnavailable) || snapshot.State != AuthorizationStateUnavailable {
|
||
|
|
t.Fatalf("Snapshot() = (%#v, %v)", snapshot, err)
|
||
|
|
}
|
||
|
|
if authorized, err := service.IsAuthorized("product-active"); authorized || !errors.Is(err, ErrAuthorizationUnavailable) {
|
||
|
|
t.Fatalf("IsAuthorized() = (%t, %v)", authorized, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestAuthorizationEventsAndBootstrapAreSanitized(t *testing.T) {
|
||
|
|
snapshot := AuthorizationSnapshot{
|
||
|
|
State: AuthorizationStateReady, MachineHash: applicationTestMachineHash,
|
||
|
|
Products: []AuthorizedProduct{{ProductID: "product-test", Kind: LicenseKindPerpetual, RebindPolicy: "support-only"}},
|
||
|
|
}
|
||
|
|
event := NewAuthorizationEvent(snapshot)
|
||
|
|
snapshot.Products[0].ProductID = "mutated"
|
||
|
|
parsed, handled, err := ParseAuthorizationEvent(event)
|
||
|
|
if err != nil || !handled || parsed.Snapshot.Products[0].ProductID != "product-test" {
|
||
|
|
t.Fatalf("ParseAuthorizationEvent() = (%#v, %t, %v)", parsed, handled, err)
|
||
|
|
}
|
||
|
|
if _, _, err := ParseAuthorizationEvent(Event{Type: EventLicenseChanged, Payload: "raw license"}); !errors.Is(err, ErrAuthorizationEventPayload) {
|
||
|
|
t.Fatalf("ParseAuthorizationEvent(bad) error = %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
runtime := NewRuntime(1)
|
||
|
|
err = NewAuthorizationBootstrap(UnconfiguredAuthorizationLoader{}, runtime).Run(context.Background())
|
||
|
|
if !errors.Is(err, ErrAuthorizationSourceUnconfigured) {
|
||
|
|
t.Fatalf("Bootstrap.Run() error = %v", err)
|
||
|
|
}
|
||
|
|
event = <-runtime.Events()
|
||
|
|
payload, handled, err := ParseAuthorizationEvent(event)
|
||
|
|
if err != nil || !handled || payload.Snapshot.State != AuthorizationStateUnconfigured {
|
||
|
|
t.Fatalf("unconfigured event = (%#v, %t, %v)", payload, handled, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLicenseImportPublishesSanitizedFailure(t *testing.T) {
|
||
|
|
now := time.Date(2026, 7, 20, 12, 0, 0, 0, time.UTC)
|
||
|
|
service := newAuthorizationServiceForTest(t, &authorizationStoreFake{}, now)
|
||
|
|
runtime := NewRuntime(1)
|
||
|
|
err := NewLicenseImport(service, licenseSourceFake{document: []byte(`{"secret":"never publish"}`)}, runtime).Run(context.Background())
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("Run() unexpectedly succeeded")
|
||
|
|
}
|
||
|
|
payload, handled, parseErr := ParseAuthorizationEvent(<-runtime.Events())
|
||
|
|
if parseErr != nil || !handled || payload.Snapshot.State != AuthorizationStateImportFailed ||
|
||
|
|
payload.Snapshot.MachineHash != applicationTestMachineHash || len(payload.Snapshot.Products) != 0 {
|
||
|
|
t.Fatalf("import event = (%#v, %t, %v)", payload, handled, parseErr)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func newAuthorizationServiceForTest(t *testing.T, store *authorizationStoreFake, now time.Time) *AuthorizationService {
|
||
|
|
t.Helper()
|
||
|
|
service, err := NewAuthorizationService(AuthorizationServiceConfig{
|
||
|
|
Store: store, MachineHash: applicationTestMachineHash, Clock: func() time.Time { return now },
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
return service
|
||
|
|
}
|
||
|
|
|
||
|
|
type authorizationStoreFake struct {
|
||
|
|
licenses []licensing.License
|
||
|
|
revocations licensing.RevocationList
|
||
|
|
found bool
|
||
|
|
listErr error
|
||
|
|
revokedErr error
|
||
|
|
}
|
||
|
|
|
||
|
|
type licenseSourceFake struct {
|
||
|
|
document []byte
|
||
|
|
err error
|
||
|
|
}
|
||
|
|
|
||
|
|
func (source licenseSourceFake) ReadLicense(context.Context) ([]byte, error) {
|
||
|
|
return append([]byte(nil), source.document...), source.err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (store *authorizationStoreFake) Import(_ []byte, _ licensing.Verifier, _ string) (licensing.License, bool, error) {
|
||
|
|
return licensing.License{}, false, errors.New("not used")
|
||
|
|
}
|
||
|
|
|
||
|
|
func (store *authorizationStoreFake) List(_ licensing.Verifier, _ string) ([]licensing.License, error) {
|
||
|
|
if store.listErr != nil {
|
||
|
|
return nil, store.listErr
|
||
|
|
}
|
||
|
|
licenses := append([]licensing.License(nil), store.licenses...)
|
||
|
|
for index := range licenses {
|
||
|
|
licenses[index].Products = append([]string(nil), licenses[index].Products...)
|
||
|
|
}
|
||
|
|
return licenses, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (store *authorizationStoreFake) StoreRevocations(_ []byte, _ licensing.RevocationVerifier) (licensing.RevocationList, error) {
|
||
|
|
return licensing.RevocationList{}, errors.New("not used")
|
||
|
|
}
|
||
|
|
|
||
|
|
func (store *authorizationStoreFake) LoadRevocations(_ licensing.RevocationVerifier) (licensing.RevocationList, bool, error) {
|
||
|
|
if store.revokedErr != nil {
|
||
|
|
return licensing.RevocationList{}, false, store.revokedErr
|
||
|
|
}
|
||
|
|
list := store.revocations
|
||
|
|
list.RevokedLicenseIDs = append([]string(nil), list.RevokedLicenseIDs...)
|
||
|
|
return list, store.found, nil
|
||
|
|
}
|