diff --git a/cmd/xpmt/main.go b/cmd/xpmt/main.go index 9d3d656..832ba69 100644 --- a/cmd/xpmt/main.go +++ b/cmd/xpmt/main.go @@ -90,7 +90,7 @@ func fetch() { client := remote.Initialize(*apiKey, config) start := time.Now() - variants, err := client.Fetch(user) + variants, err := client.FetchV2(user) if err != nil { fmt.Printf("error: %v\n", err) os.Exit(1) diff --git a/pkg/experiment/remote/client.go b/pkg/experiment/remote/client.go index 5ad832f..456a70b 100644 --- a/pkg/experiment/remote/client.go +++ b/pkg/experiment/remote/client.go @@ -1,8 +1,8 @@ package remote import ( - "bytes" "context" + "encoding/base64" "encoding/json" "fmt" "math" @@ -47,7 +47,19 @@ func Initialize(apiKey string, config *Config) *Client { return client } +// Deprecated: Use FetchV2 func (c *Client) Fetch(user *experiment.User) (map[string]experiment.Variant, error) { + variants, err := c.FetchV2(user) + if err != nil { + return nil, err + } + results := filterDefaultVariants(variants) + return results, nil +} + +// FetchV2 fetches variants for a user from the remote evaluation service. +// Unlike Fetch, this method returns all variants, including default variants. +func (c *Client) FetchV2(user *experiment.User) (map[string]experiment.Variant, error) { variants, err := c.doFetch(user, c.config.FetchTimeout) if err != nil { c.log.Error("fetch error: %v", err) @@ -66,7 +78,7 @@ func (c *Client) doFetch(user *experiment.User, timeout time.Duration) (map[stri if err != nil { return nil, err } - endpoint.Path = "sdk/vardata" + endpoint.Path = "sdk/v2/vardata" if c.config.Debug { endpoint.RawQuery = fmt.Sprintf("d=%s", randStringRunes(5)) } @@ -77,13 +89,13 @@ func (c *Client) doFetch(user *experiment.User, timeout time.Duration) (map[stri c.log.Debug("fetch variants for user %s", string(jsonBytes)) ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() - req, err := http.NewRequest("POST", endpoint.String(), bytes.NewBuffer(jsonBytes)) + req, err := http.NewRequestWithContext(ctx, "GET", endpoint.String(), nil) if err != nil { return nil, err } - req = req.WithContext(ctx) req.Header.Set("Authorization", fmt.Sprintf("Api-Key %s", c.apiKey)) req.Header.Set("Content-Type", "application/json; charset=UTF-8") + req.Header.Set("X-Amp-Exp-User", base64.StdEncoding.EncodeToString(jsonBytes)) c.log.Debug("fetch request: %v", req) resp, err := c.client.Do(req) if err != nil { @@ -122,24 +134,11 @@ func (c *Client) retryFetch(user *experiment.User) (map[string]experiment.Varian } func (c *Client) parseResponse(resp *http.Response) (map[string]experiment.Variant, error) { - interop := make(interopVariants) - err := json.NewDecoder(resp.Body).Decode(&interop) + variants := make(map[string]experiment.Variant) + err := json.NewDecoder(resp.Body).Decode(&variants) if err != nil { return nil, err } - variants := make(map[string]experiment.Variant) - for k, iv := range interop { - var value string - if iv.Value != "" { - value = iv.Value - } else if iv.Key != "" { - value = iv.Key - } - variants[k] = experiment.Variant{ - Value: value, - Payload: iv.Payload, - } - } c.log.Debug("parsed variants from response: %v", variants) return variants, nil } @@ -172,3 +171,21 @@ func shouldRetryFetch(err error) bool { } return true } + +func filterDefaultVariants(variants map[string]experiment.Variant) map[string]experiment.Variant { + results := make(map[string]experiment.Variant) + for key, variant := range variants { + isDefault, ok := variant.Metadata["default"].(bool) + if !ok { + isDefault = false + } + isDeployed, ok := variant.Metadata["deployed"].(bool) + if !ok { + isDeployed = true + } + if !isDefault && isDeployed { + results[key] = variant + } + } + return results +} diff --git a/pkg/experiment/remote/client_test.go b/pkg/experiment/remote/client_test.go index 5e0b625..59d9d45 100644 --- a/pkg/experiment/remote/client_test.go +++ b/pkg/experiment/remote/client_test.go @@ -12,6 +12,28 @@ import ( "github.com/stretchr/testify/require" ) +func TestClient_Fetch_DoesNotReturnDefaultVariants(t *testing.T) { + client := Initialize("server-qz35UwzJ5akieoAdIgzM4m9MIiOLXLoz", nil) + user := &experiment.User{} + result, err := client.Fetch(user) + require.NoError(t, err) + require.NotNil(t, result) + variant := result["sdk-ci-test"] + require.Empty(t, variant) +} + + +func TestClient_FetchV2_ReturnsDefaultVariants(t *testing.T) { + client := Initialize("server-qz35UwzJ5akieoAdIgzM4m9MIiOLXLoz", nil) + user := &experiment.User{} + result, err := client.FetchV2(user) + require.NoError(t, err) + require.NotNil(t, result) + variant := result["sdk-ci-test"] + require.NotNil(t, variant) + require.Equal(t, "off", variant.Key) +} + func TestClient_FetchRetryWithDifferentResponseCodes(t *testing.T) { // Test data: Response code, error message, and expected number of fetch calls testData := []struct { diff --git a/pkg/experiment/remote/types.go b/pkg/experiment/remote/types.go deleted file mode 100644 index e823503..0000000 --- a/pkg/experiment/remote/types.go +++ /dev/null @@ -1,9 +0,0 @@ -package remote - -type interopVariant struct { - Value string `json:"value,omitempty"` - Key string `json:"key,omitempty"` - Payload interface{} `json:"payload,omitempty"` -} - -type interopVariants = map[string]interopVariant