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 }