|
| 1 | +// Copyright 2025 Tetrate |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +package middleware_test |
| 5 | + |
| 6 | +import ( |
| 7 | + "context" |
| 8 | + "io" |
| 9 | + "path/filepath" |
| 10 | + "strings" |
| 11 | + "testing" |
| 12 | + |
| 13 | + "github.com/stretchr/testify/require" |
| 14 | + |
| 15 | + func_e "github.com/tetratelabs/func-e" |
| 16 | + "github.com/tetratelabs/func-e/api" |
| 17 | + "github.com/tetratelabs/func-e/experimental/middleware" |
| 18 | + internalmiddleware "github.com/tetratelabs/func-e/internal/middleware" |
| 19 | + "github.com/tetratelabs/func-e/internal/version" |
| 20 | +) |
| 21 | + |
| 22 | +type arbitrary struct{} |
| 23 | + |
| 24 | +// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors. |
| 25 | +var testCtx = context.WithValue(context.Background(), arbitrary{}, "arbitrary") |
| 26 | + |
| 27 | +func TestWithRunMiddleware(t *testing.T) { |
| 28 | + tests := []struct { |
| 29 | + name string |
| 30 | + input middleware.RunMiddleware |
| 31 | + expected bool |
| 32 | + }{ |
| 33 | + { |
| 34 | + name: "returns input when middleware nil", |
| 35 | + input: nil, |
| 36 | + expected: false, |
| 37 | + }, |
| 38 | + { |
| 39 | + name: "decorates with middleware", |
| 40 | + input: func(next api.RunFunc) api.RunFunc { |
| 41 | + return next |
| 42 | + }, |
| 43 | + expected: true, |
| 44 | + }, |
| 45 | + } |
| 46 | + |
| 47 | + for _, tt := range tests { |
| 48 | + t.Run(tt.name, func(t *testing.T) { |
| 49 | + actual := middleware.WithRunMiddleware(testCtx, tt.input) |
| 50 | + if tt.expected { |
| 51 | + val := actual.Value(internalmiddleware.Key{}) |
| 52 | + mw, ok := val.(func(api.RunFunc) api.RunFunc) |
| 53 | + require.NotNil(t, mw) |
| 54 | + require.True(t, ok) |
| 55 | + } else { |
| 56 | + require.Equal(t, testCtx, actual) |
| 57 | + } |
| 58 | + }) |
| 59 | + } |
| 60 | +} |
| 61 | + |
| 62 | +func TestWithStartupHook(t *testing.T) { |
| 63 | + // Test that middleware.WithStartupHook returns a valid RunOption |
| 64 | + customHook := func(ctx context.Context, runDir, adminAddress string) error { |
| 65 | + return nil |
| 66 | + } |
| 67 | + |
| 68 | + actual := middleware.WithStartupHook(customHook) |
| 69 | + require.NotNil(t, actual) |
| 70 | +} |
| 71 | + |
| 72 | +func TestMiddleware_E2E(t *testing.T) { |
| 73 | + ctx, cancel := context.WithCancel(t.Context()) |
| 74 | + defer cancel() |
| 75 | + |
| 76 | + // Setup: known temp dir |
| 77 | + expectedHomeDir := t.TempDir() |
| 78 | + var actualRunDir string |
| 79 | + var actualAdminAddress string |
| 80 | + |
| 81 | + // Define middleware that: |
| 82 | + // 1. Overrides Out/EnvoyOut/EnvoyErr to io.Discard |
| 83 | + // 2. Sets HomeDir to known temp dir |
| 84 | + // 3. Injects StartupHook to capture runDir |
| 85 | + testMiddleware := func(next api.RunFunc) api.RunFunc { |
| 86 | + return func(ctx context.Context, args []string, options ...api.RunOption) error { |
| 87 | + // Override options |
| 88 | + options = append(options, |
| 89 | + api.EnvoyVersion(version.LastKnownEnvoy.String()), |
| 90 | + api.Out(io.Discard), |
| 91 | + api.EnvoyOut(io.Discard), |
| 92 | + api.EnvoyErr(io.Discard), |
| 93 | + api.HomeDir(expectedHomeDir), |
| 94 | + ) |
| 95 | + |
| 96 | + // Inject startup hook that captures runDir and adminAddress |
| 97 | + startupHook := func(ctx context.Context, runDir, adminAddress string) error { |
| 98 | + actualRunDir = runDir |
| 99 | + actualAdminAddress = adminAddress |
| 100 | + // Cancel immediately to stop Envoy and complete test quickly |
| 101 | + cancel() |
| 102 | + return nil |
| 103 | + } |
| 104 | + options = append(options, middleware.WithStartupHook(startupHook)) |
| 105 | + |
| 106 | + return next(ctx, args, options...) |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + // Inject middleware via context |
| 111 | + ctx = middleware.WithRunMiddleware(ctx, testMiddleware) |
| 112 | + |
| 113 | + // Run with minimal Envoy config |
| 114 | + err := func_e.Run(ctx, []string{ |
| 115 | + "--config-yaml", |
| 116 | + "admin: {address: {socket_address: {address: '127.0.0.1', port_value: 0}}}", |
| 117 | + }) |
| 118 | + |
| 119 | + // Expect nil error since Run returns nil on context cancellation (documented behavior) |
| 120 | + require.NoError(t, err) |
| 121 | + require.Equal(t, filepath.Join(expectedHomeDir, "runs"), filepath.Dir(actualRunDir)) |
| 122 | + |
| 123 | + // Should get a real admin address, not the ephemeral input |
| 124 | + require.True(t, strings.HasPrefix(actualAdminAddress, "127.0.0.1:")) |
| 125 | + require.NotEqual(t, "127.0.0.1:0", actualAdminAddress) |
| 126 | +} |
0 commit comments