Files
o11y/pkg/authz/iamauthz/provider_lifecycle_test.go
zeekayandGitHub 69c616dfbb fix(iamauthz): block in Start() until Stop — was crashing SigNoz supervisor (#13)
iamauthz.Start() returned nil immediately. factory.Registry.Wait treats any
service Start() return as a service exit and tears the whole process down, so
the query-service crashed at boot ('caught service error, exiting', exception
nil) even with a healthy datastore + schema. Every other no-loop provider
(auditor, meterreporter, licensing, analytics) blocks in Start() until Stop;
iamauthz must too. Block on ctx.Done()/stopC; close stopC in Stop().

Adds provider_lifecycle_test.go covering: Start blocks until Stop, Start
returns on ctx cancel, provider healthy immediately.
2026-07-02 18:13:52 -07:00

78 lines
2.2 KiB
Go

// Copyright (C) 2025-2026, Hanzo Industries Inc. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause
package iamauthz
import (
"context"
"testing"
"time"
)
// TestProviderStartBlocksUntilStop guards the supervisor contract: factory.Registry
// treats ANY Start() return as a service exit and tears the process down. The IAM
// authz provider has no background loop, so Start MUST block until Stop — a bare
// `return nil` crashed the whole server at boot ("caught service error, exiting").
func TestProviderStartBlocksUntilStop(t *testing.T) {
p := &provider{stopC: make(chan struct{})}
done := make(chan error, 1)
go func() { done <- p.Start(context.Background()) }()
select {
case <-done:
t.Fatal("Start returned before Stop — supervisor would tear down the server at boot")
case <-time.After(50 * time.Millisecond):
// expected: still blocking
}
if err := p.Stop(context.Background()); err != nil {
t.Fatalf("Stop: %v", err)
}
select {
case err := <-done:
if err != nil {
t.Fatalf("Start returned error after Stop: %v", err)
}
case <-time.After(time.Second):
t.Fatal("Start did not return after Stop")
}
}
// TestProviderStartReturnsOnContextCancel verifies Start also unblocks on context
// cancellation, so shutdown does not hang if Stop is never called.
func TestProviderStartReturnsOnContextCancel(t *testing.T) {
p := &provider{stopC: make(chan struct{})}
ctx, cancel := context.WithCancel(context.Background())
done := make(chan error, 1)
go func() { done <- p.Start(ctx) }()
cancel()
select {
case err := <-done:
if err != nil {
t.Fatalf("Start returned error on ctx cancel: %v", err)
}
case <-time.After(time.Second):
t.Fatal("Start did not return on context cancellation")
}
}
// TestProviderHealthyImmediately verifies the provider reports healthy right away
// (IAM is external; readiness is not gated on a live IAM round-trip at boot).
func TestProviderHealthyImmediately(t *testing.T) {
healthy := make(chan struct{})
close(healthy)
p := &provider{healthy: healthy, stopC: make(chan struct{})}
select {
case <-p.Healthy():
// expected
case <-time.After(50 * time.Millisecond):
t.Fatal("provider not healthy immediately")
}
}