traefik/pkg/provider/aggregator/aggregator_test.go
2025-10-03 14:30:05 +01:00

128 lines
3.1 KiB
Go

package aggregator
import (
"bytes"
"testing"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/provider"
"github.com/traefik/traefik/v3/pkg/safe"
)
func TestProviderAggregator_Provide(t *testing.T) {
aggregator := ProviderAggregator{
internalProvider: &providerMock{"internal"},
fileProvider: &providerMock{"file"},
providers: []provider.Provider{
&providerMock{"salad"},
&providerMock{"tomato"},
&providerMock{"onion"},
},
}
cfgCh := make(chan dynamic.Message)
errCh := make(chan error)
pool := safe.NewPool(t.Context())
t.Cleanup(pool.Stop)
go func() {
errCh <- aggregator.Provide(cfgCh, pool)
}()
// Make sure the file provider is always called first.
requireReceivedMessageFromProviders(t, cfgCh, []string{"file"})
// Check if all providers have been called, the order doesn't matter.
requireReceivedMessageFromProviders(t, cfgCh, []string{"salad", "tomato", "onion", "internal"})
require.NoError(t, <-errCh)
}
func TestLaunchNamespacedProvider(t *testing.T) {
// Capture log output
var buf bytes.Buffer
originalLogger := log.Logger
log.Logger = zerolog.New(&buf).Level(zerolog.InfoLevel)
providerWithNamespace := &mockNamespacedProvider{namespace: "test-namespace"}
aggregator := ProviderAggregator{
internalProvider: providerWithNamespace,
}
cfgCh := make(chan dynamic.Message)
pool := safe.NewPool(t.Context())
t.Cleanup(func() {
pool.Stop()
log.Logger = originalLogger
})
err := aggregator.Provide(cfgCh, pool)
require.NoError(t, err)
output := buf.String()
assert.Contains(t, output, "Starting provider *aggregator.mockNamespacedProvider (namespace: test-namespace)")
}
// requireReceivedMessageFromProviders makes sure the given providers have emitted a message on the given message channel.
// Providers order is not enforced.
func requireReceivedMessageFromProviders(t *testing.T, cfgCh <-chan dynamic.Message, names []string) {
t.Helper()
var msg dynamic.Message
var receivedMessagesFrom []string
for range names {
select {
case <-time.After(100 * time.Millisecond):
require.Fail(t, "Timeout while waiting for configuration.")
case msg = <-cfgCh:
receivedMessagesFrom = append(receivedMessagesFrom, msg.ProviderName)
}
}
require.ElementsMatch(t, names, receivedMessagesFrom)
}
type providerMock struct {
Name string
}
func (p *providerMock) Init() error {
return nil
}
func (p *providerMock) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
configurationChan <- dynamic.Message{
ProviderName: p.Name,
Configuration: &dynamic.Configuration{},
}
return nil
}
// mockNamespacedProvider is a mock implementation of NamespacedProvider for testing.
type mockNamespacedProvider struct {
namespace string
}
func (m *mockNamespacedProvider) Namespace() string {
return m.namespace
}
func (m *mockNamespacedProvider) Provide(_ chan<- dynamic.Message, _ *safe.Pool) error {
return nil
}
func (m *mockNamespacedProvider) Init() error {
return nil
}