Files
soft_quay/core/application/authorization_test.go
T

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
}