Verified Commit b168670f authored by José M. Requena Plens's avatar José M. Requena Plens Committed by GitLab
Browse files

fix(feature_flags): omit unset gates from SetFeatureFlagOptions

Changelog: Improvements
parent f9fef569
Loading
Loading
Loading
Loading
+8 −8
Changes for feature_flags.go: 8 added lines, 8 removed lines.
Original line number Diff line number Diff line
@@ -114,14 +114,14 @@ func (s *FeaturesService) ListFeatureDefinitions(options ...RequestOptionFunc) (
// https://docs.gitlab.com/api/features/#set-or-create-a-feature
type SetFeatureFlagOptions struct {
	Value        any    `url:"value" json:"value"`
	Key          string `url:"key" json:"key"`
	FeatureGroup string `url:"feature_group" json:"feature_group"`
	User         string `url:"user" json:"user"`
	Group        string `url:"group" json:"group"`
	Namespace    string `url:"namespace" json:"namespace"`
	Project      string `url:"project" json:"project"`
	Repository   string `url:"repository" json:"repository"`
	Force        bool   `url:"force" json:"force"`
	Key          string `url:"key,omitempty" json:"key,omitempty"`
	FeatureGroup string `url:"feature_group,omitempty" json:"feature_group,omitempty"`
	User         string `url:"user,omitempty" json:"user,omitempty"`
	Group        string `url:"group,omitempty" json:"group,omitempty"`
	Namespace    string `url:"namespace,omitempty" json:"namespace,omitempty"`
	Project      string `url:"project,omitempty" json:"project,omitempty"`
	Repository   string `url:"repository,omitempty" json:"repository,omitempty"`
	Force        bool   `url:"force,omitempty" json:"force,omitempty"`
}

// SetFeatureFlag sets or creates a feature flag gate
+49
Changes for feature_flags_test.go: 49 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -147,6 +147,55 @@ func TestSetFeatureFlag(t *testing.T) {
	assert.Equal(t, want, feature)
}

func TestSetFeatureFlagOmitsUnsetGates(t *testing.T) {
	t.Parallel()
	mux, client := setup(t)

	mux.HandleFunc("/api/v4/features/new_library", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodPost)
		// THEN: The body carries the value alone. An empty key beside an
		// empty feature_group, user or group is refused by GitLab as
		// mutually exclusive, whatever the caller asked for
		testBodyJSON(t, r, map[string]any{
			"value": false,
		})
		fmt.Fprint(w, `{"name": "new_library", "state": "off", "gates": [{"key": "boolean", "value": false}]}`)
	})

	// GIVEN: An instance-wide boolean gate and no other option
	opt := &SetFeatureFlagOptions{Value: false}

	// WHEN: The flag is set
	feature, _, err := client.Features.SetFeatureFlag("new_library", opt)
	assert.NoError(t, err)
	assert.Equal(t, "off", feature.State)
}

func TestSetFeatureFlagSendsTheOptionsItIsGiven(t *testing.T) {
	t.Parallel()
	mux, client := setup(t)

	mux.HandleFunc("/api/v4/features/new_library", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodPost)
		// THEN: Every option the caller set is in the body, so omitempty
		// drops only what was left unset
		testBodyJSON(t, r, map[string]any{
			"value": float64(30),
			"key":   "percentage_of_actors",
			"force": true,
		})
		fmt.Fprint(w, `{"name": "new_library", "state": "conditional", "gates": [{"key": "percentage_of_actors", "value": 30}]}`)
	})

	// GIVEN: A percentage of actors gate, forced past validation
	opt := &SetFeatureFlagOptions{Value: 30, Key: "percentage_of_actors", Force: true}

	// WHEN: The flag is set
	feature, _, err := client.Features.SetFeatureFlag("new_library", opt)
	assert.NoError(t, err)
	assert.Equal(t, "conditional", feature.State)
}

func TestDeleteFeatureFlag(t *testing.T) {
	t.Parallel()
	mux, client := setup(t)
+99
Changes for gitlab_test/feature_flags_integration_test.go: 99 added lines, 0 removed lines.
Original line number Diff line number Diff line
//go:build integration

package gitlab_test

import (
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	gitlab "gitlab.com/gitlab-org/api/client-go/v3"
)

// Integration tests for the Features API. Setting a feature flag requires an
// administrator token.

// testFeatureFlagName returns the name of a feature flag that only the calling
// test sets, and deletes the flag when the test finishes.
func testFeatureFlagName(t *testing.T, client *gitlab.Client) string {
	t.Helper()

	name := fmt.Sprintf("client_go_integration_%d", time.Now().UnixNano())

	t.Cleanup(func() {
		_, err := client.Features.DeleteFeatureFlag(name)
		require.NoError(t, err, "Failed to delete test feature flag")
	})

	return name
}

func Test_FeaturesSetFeatureFlag_Boolean_Integration(t *testing.T) {
	t.Parallel()

	client := SetupIntegrationClient(t)

	// GIVEN a feature flag no other test sets
	name := testFeatureFlagName(t, client)

	// WHEN the flag is enabled for the whole instance, with no other option
	feature, _, err := client.Features.SetFeatureFlag(name, &gitlab.SetFeatureFlagOptions{Value: true})

	// THEN GitLab accepts the request and the flag is on
	require.NoError(t, err, "Failed to enable the feature flag")
	assert.Equal(t, name, feature.Name)
	assert.Equal(t, "on", feature.State)
	assert.Contains(t, feature.Gates, gitlab.Gate{Key: "boolean", Value: true})

	// AND WHEN it is disabled the same way
	feature, _, err = client.Features.SetFeatureFlag(name, &gitlab.SetFeatureFlagOptions{Value: false})

	// THEN the flag is off
	require.NoError(t, err, "Failed to disable the feature flag")
	assert.Equal(t, "off", feature.State)
	assert.Contains(t, feature.Gates, gitlab.Gate{Key: "boolean", Value: false})
}

func Test_FeaturesSetFeatureFlag_PercentageOfActors_Integration(t *testing.T) {
	t.Parallel()

	client := SetupIntegrationClient(t)

	// GIVEN a feature flag no other test sets
	name := testFeatureFlagName(t, client)

	// WHEN it is enabled for a percentage of actors, which sets key and no gate
	feature, _, err := client.Features.SetFeatureFlag(name, &gitlab.SetFeatureFlagOptions{
		Value: 30,
		Key:   "percentage_of_actors",
	})

	// THEN GitLab accepts the request and records the percentage
	require.NoError(t, err, "Failed to set the percentage of actors")
	assert.Equal(t, "conditional", feature.State)
	assert.Contains(t, feature.Gates, gitlab.Gate{Key: "percentage_of_actors", Value: float64(30)})
}

func Test_FeaturesSetFeatureFlag_User_Integration(t *testing.T) {
	t.Parallel()

	client := SetupIntegrationClient(t)

	// GIVEN a feature flag no other test sets, and a user
	name := testFeatureFlagName(t, client)
	user, err := CreateTestUser(t, client)
	require.NoError(t, err)

	// WHEN it is enabled for that user, which sets a gate and no key
	feature, _, err := client.Features.SetFeatureFlag(name, &gitlab.SetFeatureFlagOptions{
		Value: true,
		User:  user.Username,
	})

	// THEN GitLab accepts the request and enables the flag for the user
	require.NoError(t, err, "Failed to enable the feature flag for a user")
	assert.Equal(t, "conditional", feature.State)
	assert.Contains(t, feature.Gates, gitlab.Gate{Key: "actors", Value: []any{fmt.Sprintf("User:%d", user.ID)}})
}