Files
tasks/common/headers/headers_test.go
c67e770ed2 CHASM Scheduler Experimental Flag (#8467)
What changed?

Added support for enabling experimental features via gRPC headers and
namespace configuration:

  1. New experimental feature infrastructure:
    - Added temporal-experimental gRPC header support 
- supports comma-separated values, wildcards (*), and case-insensitive
matching
- Added FrontendEnabledExperiments dynamic config setting to allow
per-namespace control of which experiments can be enabled
  2. CHASM scheduler experiment:
    - Defined ChasmSchedulerExperiment = "chasm-sch" constant
  4. Test improvements:
- Refactored common/headers/headers_test.go to remove testify suite
pattern and use standard table-driven tests with t.Parallel()

  Why?

We need a safe, controlled way to roll out experimental features like
the CHASM scheduler (v2). This infrastructure provides:

- Dual gating mechanism: Experiments require both client opt-in (via
header) AND server-side namespace configuration, preventing accidental
enablement
- Flexibility: Supports multiple experiments, wildcard enablement for
testing, and per-namespace control

  How did you test it?

  - built
  - added new unit tests

---------

Co-authored-by: alex stanfield <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2025-10-16 11:10:57 -05:00

201 lines
5.9 KiB
Go

package headers
import (
"context"
"strings"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/metadata"
)
func TestPropagate_CreateNewOutgoingContext(t *testing.T) {
t.Parallel()
ctx := context.Background()
ctx = metadata.NewIncomingContext(ctx, metadata.New(map[string]string{
ClientVersionHeaderName: "22.08.78",
SupportedServerVersionsHeaderName: ">21.04.16",
ClientNameHeaderName: "28.08.14",
SupportedFeaturesHeaderName: "my-feature",
}))
ctx = Propagate(ctx)
md, ok := metadata.FromOutgoingContext(ctx)
require.True(t, ok)
require.Equal(t, "22.08.78", md.Get(ClientVersionHeaderName)[0])
require.Equal(t, ">21.04.16", md.Get(SupportedServerVersionsHeaderName)[0])
require.Equal(t, "28.08.14", md.Get(ClientNameHeaderName)[0])
require.Equal(t, "my-feature", md.Get(SupportedFeaturesHeaderName)[0])
}
func TestPropagate_CreateNewOutgoingContext_SomeMissing(t *testing.T) {
t.Parallel()
ctx := context.Background()
ctx = metadata.NewIncomingContext(ctx, metadata.New(map[string]string{
ClientVersionHeaderName: "22.08.78",
ClientNameHeaderName: "28.08.14",
}))
ctx = Propagate(ctx)
md, ok := metadata.FromOutgoingContext(ctx)
require.True(t, ok)
require.Equal(t, "22.08.78", md.Get(ClientVersionHeaderName)[0])
require.Empty(t, md.Get(SupportedServerVersionsHeaderName))
require.Equal(t, "28.08.14", md.Get(ClientNameHeaderName)[0])
require.Empty(t, md.Get(SupportedFeaturesHeaderName))
}
func TestPropagate_UpdateExistingEmptyOutgoingContext(t *testing.T) {
t.Parallel()
ctx := context.Background()
ctx = metadata.NewIncomingContext(ctx, metadata.New(map[string]string{
ClientVersionHeaderName: "22.08.78",
SupportedServerVersionsHeaderName: "<21.04.16",
ClientNameHeaderName: "28.08.14",
SupportedFeaturesHeaderName: "my-feature",
}))
ctx = metadata.NewOutgoingContext(ctx, metadata.MD{})
ctx = Propagate(ctx)
md, ok := metadata.FromOutgoingContext(ctx)
require.True(t, ok)
require.Equal(t, "22.08.78", md.Get(ClientVersionHeaderName)[0])
require.Equal(t, "<21.04.16", md.Get(SupportedServerVersionsHeaderName)[0])
require.Equal(t, "28.08.14", md.Get(ClientNameHeaderName)[0])
require.Equal(t, "my-feature", md.Get(SupportedFeaturesHeaderName)[0])
}
func TestPropagate_UpdateExistingNonEmptyOutgoingContext(t *testing.T) {
t.Parallel()
ctx := context.Background()
ctx = metadata.NewIncomingContext(ctx, metadata.New(map[string]string{
ClientVersionHeaderName: "07.08.78", // Must be ignored
SupportedServerVersionsHeaderName: "<07.04.16", // Must be ignored
SupportedFeaturesHeaderName: "my-feature", // Passed through
}))
ctx = metadata.NewOutgoingContext(ctx, metadata.New(map[string]string{
ClientVersionHeaderName: "22.08.78",
SupportedServerVersionsHeaderName: "<21.04.16",
ClientNameHeaderName: "28.08.14",
}))
ctx = Propagate(ctx)
md, ok := metadata.FromOutgoingContext(ctx)
require.True(t, ok)
require.Equal(t, "22.08.78", md.Get(ClientVersionHeaderName)[0])
require.Equal(t, "<21.04.16", md.Get(SupportedServerVersionsHeaderName)[0])
require.Equal(t, "28.08.14", md.Get(ClientNameHeaderName)[0])
require.Equal(t, "my-feature", md.Get(SupportedFeaturesHeaderName)[0])
}
func TestPropagate_EmptyIncomingContext(t *testing.T) {
t.Parallel()
ctx := context.Background()
ctx = metadata.NewOutgoingContext(ctx, metadata.New(map[string]string{
ClientVersionHeaderName: "22.08.78",
SupportedServerVersionsHeaderName: "<21.04.16",
ClientNameHeaderName: "28.08.14",
}))
ctx = Propagate(ctx)
md, ok := metadata.FromOutgoingContext(ctx)
require.True(t, ok)
require.Equal(t, "22.08.78", md.Get(ClientVersionHeaderName)[0])
require.Equal(t, "<21.04.16", md.Get(SupportedServerVersionsHeaderName)[0])
require.Equal(t, "28.08.14", md.Get(ClientNameHeaderName)[0])
}
func TestIsExperimentRequested(t *testing.T) {
t.Parallel()
tests := []struct {
name string
headerValues []string
checkExperiment string
expected bool
}{
{
name: "no header returns false",
headerValues: nil,
checkExperiment: "chasm-scheduler",
expected: false,
},
{
name: "exact match returns true",
headerValues: []string{"chasm-scheduler"},
checkExperiment: "chasm-scheduler",
expected: true,
},
{
name: "comma separated list finds match",
headerValues: []string{"chasm-scheduler, other-exp, third-exp"},
checkExperiment: "other-exp",
expected: true,
},
{
name: "wildcard matches any experiment",
headerValues: []string{"*"},
checkExperiment: "any-experiment",
expected: true,
},
{
name: "wildcard in list matches",
headerValues: []string{"chasm-scheduler,*,other-exp"},
checkExperiment: "random-experiment",
expected: true,
},
{
name: "multiple header values finds match",
headerValues: []string{"chasm-scheduler", "other-exp,third-exp"},
checkExperiment: "third-exp",
expected: true,
},
{
name: "max experiment size limit match",
headerValues: []string{strings.Repeat("a,", 49)},
checkExperiment: "a",
expected: true, // 98 chars, under 100 char limit
},
{
name: "at max experiment size limit no match",
headerValues: []string{strings.Repeat("a,", 51)},
checkExperiment: "a",
expected: false, // exceeds 100 char limit, should be skipped
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
ctx := context.Background()
md := metadata.MD{}
for _, val := range tt.headerValues {
md.Append(ExperimentHeaderName, val)
}
ctx = metadata.NewIncomingContext(ctx, md)
result := IsExperimentRequested(ctx, tt.checkExperiment)
require.Equal(t, tt.expected, result)
})
}
}