| 
									
										
										
										
											2023-07-25 04:12:59 +08:00
										 |  |  | package api | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2023-07-25 04:12:59 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2023-07-25 04:12:59 +08:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2023-09-26 02:11:24 +08:00
										 |  |  | 	"net/http/httptest" | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 	"testing" | 
					
						
							| 
									
										
										
										
											2023-07-25 04:12:59 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/infra/log" | 
					
						
							| 
									
										
										
										
											2023-07-25 04:12:59 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/accesscontrol" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/services/featuremgmt" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/services/org/orgtest" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/services/user" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/services/user/usertest" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/setting" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/web/webtest" | 
					
						
							|  |  |  | 	"github.com/stretchr/testify/assert" | 
					
						
							|  |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-03 23:59:07 +08:00
										 |  |  | func boolPtr(b bool) *bool { | 
					
						
							|  |  |  | 	return &b | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	truePtr  = boolPtr(true) | 
					
						
							|  |  |  | 	falsePtr = boolPtr(false) | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 04:12:59 +08:00
										 |  |  | func TestGetFeatureToggles(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 	readPermissions := []accesscontrol.Permission{{Action: accesscontrol.ActionFeatureManagementRead}} | 
					
						
							| 
									
										
										
										
											2023-07-25 04:12:59 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 	t.Run("should not be able to get feature toggles without permissions", func(t *testing.T) { | 
					
						
							|  |  |  | 		result := runGetScenario(t, []*featuremgmt.FeatureFlag{}, setting.FeatureMgmtSettings{}, []accesscontrol.Permission{}, http.StatusForbidden) | 
					
						
							|  |  |  | 		assert.Len(t, result, 0) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("should be able to get feature toggles with correct permissions", func(t *testing.T) { | 
					
						
							|  |  |  | 		features := []*featuremgmt.FeatureFlag{ | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				Name:    "toggle1", | 
					
						
							|  |  |  | 				Enabled: true, | 
					
						
							|  |  |  | 				Stage:   featuremgmt.FeatureStageGeneralAvailability, | 
					
						
							|  |  |  | 			}, { | 
					
						
							|  |  |  | 				Name:    "toggle2", | 
					
						
							|  |  |  | 				Enabled: false, | 
					
						
							|  |  |  | 				Stage:   featuremgmt.FeatureStageGeneralAvailability, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		result := runGetScenario(t, features, setting.FeatureMgmtSettings{}, readPermissions, http.StatusOK) | 
					
						
							|  |  |  | 		assert.Len(t, result, 2) | 
					
						
							|  |  |  | 		t1, _ := findResult(t, result, "toggle1") | 
					
						
							|  |  |  | 		assert.True(t, t1.Enabled) | 
					
						
							|  |  |  | 		t2, _ := findResult(t, result, "toggle2") | 
					
						
							|  |  |  | 		assert.False(t, t2.Enabled) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("toggles hidden by config are not present in the response", func(t *testing.T) { | 
					
						
							|  |  |  | 		features := []*featuremgmt.FeatureFlag{ | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				Name:    "toggle1", | 
					
						
							|  |  |  | 				Enabled: true, | 
					
						
							|  |  |  | 				Stage:   featuremgmt.FeatureStageGeneralAvailability, | 
					
						
							|  |  |  | 			}, { | 
					
						
							|  |  |  | 				Name:    "toggle2", | 
					
						
							|  |  |  | 				Enabled: false, | 
					
						
							|  |  |  | 				Stage:   featuremgmt.FeatureStageGeneralAvailability, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		settings := setting.FeatureMgmtSettings{ | 
					
						
							|  |  |  | 			HiddenToggles: map[string]struct{}{"toggle1": {}}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		result := runGetScenario(t, features, settings, readPermissions, http.StatusOK) | 
					
						
							|  |  |  | 		assert.Len(t, result, 1) | 
					
						
							|  |  |  | 		assert.Equal(t, "toggle2", result[0].Name) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("toggles that are read-only by config have the readOnly field set", func(t *testing.T) { | 
					
						
							|  |  |  | 		features := []*featuremgmt.FeatureFlag{ | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				Name:    "toggle1", | 
					
						
							|  |  |  | 				Enabled: true, | 
					
						
							|  |  |  | 				Stage:   featuremgmt.FeatureStageGeneralAvailability, | 
					
						
							|  |  |  | 			}, { | 
					
						
							|  |  |  | 				Name:    "toggle2", | 
					
						
							|  |  |  | 				Enabled: false, | 
					
						
							|  |  |  | 				Stage:   featuremgmt.FeatureStageGeneralAvailability, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		settings := setting.FeatureMgmtSettings{ | 
					
						
							| 
									
										
										
										
											2023-09-26 02:11:24 +08:00
										 |  |  | 			HiddenToggles:   map[string]struct{}{"toggle1": {}}, | 
					
						
							|  |  |  | 			ReadOnlyToggles: map[string]struct{}{"toggle2": {}}, | 
					
						
							|  |  |  | 			AllowEditing:    true, | 
					
						
							|  |  |  | 			UpdateWebhook:   "bogus", | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		result := runGetScenario(t, features, settings, readPermissions, http.StatusOK) | 
					
						
							|  |  |  | 		assert.Len(t, result, 1) | 
					
						
							|  |  |  | 		assert.Equal(t, "toggle2", result[0].Name) | 
					
						
							|  |  |  | 		assert.True(t, result[0].ReadOnly) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("feature toggle defailts", func(t *testing.T) { | 
					
						
							|  |  |  | 		features := []*featuremgmt.FeatureFlag{ | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				Name:  "toggle1", | 
					
						
							|  |  |  | 				Stage: featuremgmt.FeatureStageUnknown, | 
					
						
							|  |  |  | 			}, { | 
					
						
							|  |  |  | 				Name:  "toggle2", | 
					
						
							|  |  |  | 				Stage: featuremgmt.FeatureStageExperimental, | 
					
						
							|  |  |  | 			}, { | 
					
						
							|  |  |  | 				Name:  "toggle3", | 
					
						
							|  |  |  | 				Stage: featuremgmt.FeatureStagePrivatePreview, | 
					
						
							|  |  |  | 			}, { | 
					
						
							| 
									
										
										
										
											2023-11-03 23:59:07 +08:00
										 |  |  | 				Name:           "toggle4", | 
					
						
							|  |  |  | 				Stage:          featuremgmt.FeatureStagePublicPreview, | 
					
						
							|  |  |  | 				AllowSelfServe: truePtr, | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 			}, { | 
					
						
							| 
									
										
										
										
											2023-11-03 23:59:07 +08:00
										 |  |  | 				Name:           "toggle5", | 
					
						
							|  |  |  | 				Stage:          featuremgmt.FeatureStageGeneralAvailability, | 
					
						
							|  |  |  | 				AllowSelfServe: truePtr, | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 			}, { | 
					
						
							| 
									
										
										
										
											2023-11-03 23:59:07 +08:00
										 |  |  | 				Name:           "toggle6", | 
					
						
							|  |  |  | 				Stage:          featuremgmt.FeatureStageDeprecated, | 
					
						
							|  |  |  | 				AllowSelfServe: truePtr, | 
					
						
							|  |  |  | 			}, { | 
					
						
							|  |  |  | 				Name:           "toggle7", | 
					
						
							|  |  |  | 				Stage:          featuremgmt.FeatureStageGeneralAvailability, | 
					
						
							|  |  |  | 				AllowSelfServe: falsePtr, | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("unknown, experimental, and private preview toggles are hidden by default", func(t *testing.T) { | 
					
						
							|  |  |  | 			result := runGetScenario(t, features, setting.FeatureMgmtSettings{}, readPermissions, http.StatusOK) | 
					
						
							| 
									
										
										
										
											2023-11-03 23:59:07 +08:00
										 |  |  | 			assert.Len(t, result, 4) | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			_, ok := findResult(t, result, "toggle1") | 
					
						
							|  |  |  | 			assert.False(t, ok) | 
					
						
							|  |  |  | 			_, ok = findResult(t, result, "toggle2") | 
					
						
							|  |  |  | 			assert.False(t, ok) | 
					
						
							|  |  |  | 			_, ok = findResult(t, result, "toggle3") | 
					
						
							|  |  |  | 			assert.False(t, ok) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-03 23:59:07 +08:00
										 |  |  | 		t.Run("only public preview and GA with AllowSelfServe are writeable", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 			settings := setting.FeatureMgmtSettings{ | 
					
						
							| 
									
										
										
										
											2023-09-26 02:11:24 +08:00
										 |  |  | 				AllowEditing:  true, | 
					
						
							|  |  |  | 				UpdateWebhook: "bogus", | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			result := runGetScenario(t, features, settings, readPermissions, http.StatusOK) | 
					
						
							| 
									
										
										
										
											2023-11-03 23:59:07 +08:00
										 |  |  | 			assert.Len(t, result, 4) | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			t4, ok := findResult(t, result, "toggle4") | 
					
						
							|  |  |  | 			assert.True(t, ok) | 
					
						
							|  |  |  | 			assert.True(t, t4.ReadOnly) | 
					
						
							|  |  |  | 			t5, ok := findResult(t, result, "toggle5") | 
					
						
							|  |  |  | 			assert.True(t, ok) | 
					
						
							|  |  |  | 			assert.False(t, t5.ReadOnly) | 
					
						
							|  |  |  | 			t6, ok := findResult(t, result, "toggle6") | 
					
						
							|  |  |  | 			assert.True(t, ok) | 
					
						
							|  |  |  | 			assert.False(t, t6.ReadOnly) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("all toggles are read-only when server is misconfigured", func(t *testing.T) { | 
					
						
							|  |  |  | 			settings := setting.FeatureMgmtSettings{ | 
					
						
							| 
									
										
										
										
											2023-09-26 02:11:24 +08:00
										 |  |  | 				AllowEditing:  false, | 
					
						
							|  |  |  | 				UpdateWebhook: "", | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			result := runGetScenario(t, features, settings, readPermissions, http.StatusOK) | 
					
						
							| 
									
										
										
										
											2023-11-03 23:59:07 +08:00
										 |  |  | 			assert.Len(t, result, 4) | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			t4, ok := findResult(t, result, "toggle4") | 
					
						
							|  |  |  | 			assert.True(t, ok) | 
					
						
							|  |  |  | 			assert.True(t, t4.ReadOnly) | 
					
						
							|  |  |  | 			t5, ok := findResult(t, result, "toggle5") | 
					
						
							|  |  |  | 			assert.True(t, ok) | 
					
						
							|  |  |  | 			assert.True(t, t5.ReadOnly) | 
					
						
							|  |  |  | 			t6, ok := findResult(t, result, "toggle6") | 
					
						
							|  |  |  | 			assert.True(t, ok) | 
					
						
							|  |  |  | 			assert.True(t, t6.ReadOnly) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestSetFeatureToggles(t *testing.T) { | 
					
						
							|  |  |  | 	writePermissions := []accesscontrol.Permission{{Action: accesscontrol.ActionFeatureManagementWrite}} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("fails without adequate permissions", func(t *testing.T) { | 
					
						
							|  |  |  | 		res := runSetScenario(t, nil, nil, setting.FeatureMgmtSettings{}, []accesscontrol.Permission{}, http.StatusForbidden) | 
					
						
							|  |  |  | 		defer func() { require.NoError(t, res.Body.Close()) }() | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("fails when toggle editing is not enabled", func(t *testing.T) { | 
					
						
							|  |  |  | 		res := runSetScenario(t, nil, nil, setting.FeatureMgmtSettings{}, writePermissions, http.StatusForbidden) | 
					
						
							|  |  |  | 		defer func() { require.NoError(t, res.Body.Close()) }() | 
					
						
							|  |  |  | 		p := readBody(t, res.Body) | 
					
						
							|  |  |  | 		assert.Equal(t, "feature toggles are read-only", p["message"]) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("fails when update toggle url is not set", func(t *testing.T) { | 
					
						
							|  |  |  | 		s := setting.FeatureMgmtSettings{ | 
					
						
							|  |  |  | 			AllowEditing: true, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		res := runSetScenario(t, nil, nil, s, writePermissions, http.StatusInternalServerError) | 
					
						
							|  |  |  | 		defer func() { require.NoError(t, res.Body.Close()) }() | 
					
						
							|  |  |  | 		p := readBody(t, res.Body) | 
					
						
							|  |  |  | 		assert.Equal(t, "feature toggles service is misconfigured", p["message"]) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("fails with non-existent toggle", func(t *testing.T) { | 
					
						
							|  |  |  | 		features := []*featuremgmt.FeatureFlag{ | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				Name:    "toggle1", | 
					
						
							|  |  |  | 				Enabled: true, | 
					
						
							|  |  |  | 				Stage:   featuremgmt.FeatureStageGeneralAvailability, | 
					
						
							|  |  |  | 			}, { | 
					
						
							|  |  |  | 				Name:    "toggle2", | 
					
						
							|  |  |  | 				Enabled: false, | 
					
						
							|  |  |  | 				Stage:   featuremgmt.FeatureStageGeneralAvailability, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		updates := []featuremgmt.FeatureToggleDTO{ | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				Name:    "toggle3", | 
					
						
							|  |  |  | 				Enabled: true, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		s := setting.FeatureMgmtSettings{ | 
					
						
							| 
									
										
										
										
											2023-09-26 02:11:24 +08:00
										 |  |  | 			AllowEditing:  true, | 
					
						
							|  |  |  | 			UpdateWebhook: "random", | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		res := runSetScenario(t, features, updates, s, writePermissions, http.StatusBadRequest) | 
					
						
							|  |  |  | 		defer func() { require.NoError(t, res.Body.Close()) }() | 
					
						
							|  |  |  | 		p := readBody(t, res.Body) | 
					
						
							|  |  |  | 		assert.Equal(t, "invalid toggle passed in", p["message"]) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("fails with read-only toggles", func(t *testing.T) { | 
					
						
							|  |  |  | 		features := []*featuremgmt.FeatureFlag{ | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				Name:    featuremgmt.FlagFeatureToggleAdminPage, | 
					
						
							|  |  |  | 				Enabled: true, | 
					
						
							|  |  |  | 				Stage:   featuremgmt.FeatureStageGeneralAvailability, | 
					
						
							|  |  |  | 			}, { | 
					
						
							|  |  |  | 				Name:    "toggle2", | 
					
						
							|  |  |  | 				Enabled: false, | 
					
						
							|  |  |  | 				Stage:   featuremgmt.FeatureStagePublicPreview, | 
					
						
							|  |  |  | 			}, { | 
					
						
							|  |  |  | 				Name:    "toggle3", | 
					
						
							|  |  |  | 				Enabled: false, | 
					
						
							|  |  |  | 				Stage:   featuremgmt.FeatureStageGeneralAvailability, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		s := setting.FeatureMgmtSettings{ | 
					
						
							| 
									
										
										
										
											2023-09-26 02:11:24 +08:00
										 |  |  | 			AllowEditing:  true, | 
					
						
							|  |  |  | 			UpdateWebhook: "random", | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 			ReadOnlyToggles: map[string]struct{}{ | 
					
						
							|  |  |  | 				"toggle3": {}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("because it is the feature toggle admin page toggle", func(t *testing.T) { | 
					
						
							|  |  |  | 			updates := []featuremgmt.FeatureToggleDTO{ | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					Name:    featuremgmt.FlagFeatureToggleAdminPage, | 
					
						
							|  |  |  | 					Enabled: true, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			res := runSetScenario(t, features, updates, s, writePermissions, http.StatusBadRequest) | 
					
						
							|  |  |  | 			defer func() { require.NoError(t, res.Body.Close()) }() | 
					
						
							|  |  |  | 			p := readBody(t, res.Body) | 
					
						
							| 
									
										
										
										
											2023-10-31 02:06:26 +08:00
										 |  |  | 			assert.Equal(t, "invalid toggle passed in", p["message"]) | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2023-07-25 04:12:59 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 		t.Run("because it is not GA or Deprecated", func(t *testing.T) { | 
					
						
							|  |  |  | 			updates := []featuremgmt.FeatureToggleDTO{ | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					Name:    "toggle2", | 
					
						
							|  |  |  | 					Enabled: true, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			res := runSetScenario(t, features, updates, s, writePermissions, http.StatusBadRequest) | 
					
						
							| 
									
										
										
										
											2023-07-25 04:12:59 +08:00
										 |  |  | 			defer func() { require.NoError(t, res.Body.Close()) }() | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 			p := readBody(t, res.Body) | 
					
						
							| 
									
										
										
										
											2023-10-31 02:06:26 +08:00
										 |  |  | 			assert.Equal(t, "invalid toggle passed in", p["message"]) | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("because it is configured to be read-only", func(t *testing.T) { | 
					
						
							|  |  |  | 			updates := []featuremgmt.FeatureToggleDTO{ | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					Name:    "toggle3", | 
					
						
							|  |  |  | 					Enabled: true, | 
					
						
							|  |  |  | 				}, | 
					
						
							| 
									
										
										
										
											2023-07-25 04:12:59 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 			res := runSetScenario(t, features, updates, s, writePermissions, http.StatusBadRequest) | 
					
						
							|  |  |  | 			defer func() { require.NoError(t, res.Body.Close()) }() | 
					
						
							|  |  |  | 			p := readBody(t, res.Body) | 
					
						
							| 
									
										
										
										
											2023-10-31 02:06:26 +08:00
										 |  |  | 			assert.Equal(t, "invalid toggle passed in", p["message"]) | 
					
						
							| 
									
										
										
										
											2023-07-25 04:12:59 +08:00
										 |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-26 02:11:24 +08:00
										 |  |  | 	t.Run("when all conditions met", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 		features := []*featuremgmt.FeatureFlag{ | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				Name:    featuremgmt.FlagFeatureToggleAdminPage, | 
					
						
							|  |  |  | 				Enabled: true, | 
					
						
							|  |  |  | 				Stage:   featuremgmt.FeatureStageGeneralAvailability, | 
					
						
							|  |  |  | 			}, { | 
					
						
							|  |  |  | 				Name:    "toggle2", | 
					
						
							|  |  |  | 				Enabled: false, | 
					
						
							|  |  |  | 				Stage:   featuremgmt.FeatureStagePublicPreview, | 
					
						
							|  |  |  | 			}, { | 
					
						
							|  |  |  | 				Name:    "toggle3", | 
					
						
							|  |  |  | 				Enabled: false, | 
					
						
							|  |  |  | 				Stage:   featuremgmt.FeatureStageGeneralAvailability, | 
					
						
							|  |  |  | 			}, { | 
					
						
							| 
									
										
										
										
											2023-11-03 23:59:07 +08:00
										 |  |  | 				Name:           "toggle4", | 
					
						
							|  |  |  | 				Enabled:        false, | 
					
						
							|  |  |  | 				Stage:          featuremgmt.FeatureStageGeneralAvailability, | 
					
						
							|  |  |  | 				AllowSelfServe: truePtr, | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 			}, { | 
					
						
							| 
									
										
										
										
											2023-11-03 23:59:07 +08:00
										 |  |  | 				Name:           "toggle5", | 
					
						
							|  |  |  | 				Enabled:        false, | 
					
						
							|  |  |  | 				Stage:          featuremgmt.FeatureStageDeprecated, | 
					
						
							|  |  |  | 				AllowSelfServe: truePtr, | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		s := setting.FeatureMgmtSettings{ | 
					
						
							| 
									
										
										
										
											2023-09-26 02:11:24 +08:00
										 |  |  | 			AllowEditing:       true, | 
					
						
							|  |  |  | 			UpdateWebhook:      "random", | 
					
						
							|  |  |  | 			UpdateWebhookToken: "token", | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 			ReadOnlyToggles: map[string]struct{}{ | 
					
						
							|  |  |  | 				"toggle3": {}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		updates := []featuremgmt.FeatureToggleDTO{ | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				Name:    "toggle4", | 
					
						
							|  |  |  | 				Enabled: true, | 
					
						
							|  |  |  | 			}, { | 
					
						
							|  |  |  | 				Name:    "toggle5", | 
					
						
							|  |  |  | 				Enabled: false, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-09-26 02:11:24 +08:00
										 |  |  | 		t.Run("fail when webhook request is not successful", func(t *testing.T) { | 
					
						
							|  |  |  | 			webhookServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 				w.WriteHeader(http.StatusBadRequest) | 
					
						
							|  |  |  | 			})) | 
					
						
							|  |  |  | 			defer webhookServer.Close() | 
					
						
							|  |  |  | 			s.UpdateWebhook = webhookServer.URL | 
					
						
							|  |  |  | 			res := runSetScenario(t, features, updates, s, writePermissions, http.StatusBadRequest) | 
					
						
							|  |  |  | 			defer func() { require.NoError(t, res.Body.Close()) }() | 
					
						
							|  |  |  | 			assert.Equal(t, http.StatusBadRequest, res.StatusCode) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("succeed when webhook request is successul", func(t *testing.T) { | 
					
						
							|  |  |  | 			webhookServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 				assert.Equal(t, "Bearer "+s.UpdateWebhookToken, r.Header.Get("Authorization")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				var req UpdatePayload | 
					
						
							|  |  |  | 				require.NoError(t, json.NewDecoder(r.Body).Decode(&req)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				assert.Equal(t, "true", req.FeatureToggles["toggle4"]) | 
					
						
							|  |  |  | 				assert.Equal(t, "false", req.FeatureToggles["toggle5"]) | 
					
						
							|  |  |  | 				w.WriteHeader(http.StatusOK) | 
					
						
							|  |  |  | 			})) | 
					
						
							|  |  |  | 			defer webhookServer.Close() | 
					
						
							|  |  |  | 			s.UpdateWebhook = webhookServer.URL | 
					
						
							|  |  |  | 			res := runSetScenario(t, features, updates, s, writePermissions, http.StatusOK) | 
					
						
							|  |  |  | 			defer func() { require.NoError(t, res.Body.Close()) }() | 
					
						
							|  |  |  | 			assert.Equal(t, http.StatusOK, res.StatusCode) | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func findResult(t *testing.T, result []featuremgmt.FeatureToggleDTO, name string) (featuremgmt.FeatureToggleDTO, bool) { | 
					
						
							|  |  |  | 	t.Helper() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, t := range result { | 
					
						
							|  |  |  | 		if t.Name == name { | 
					
						
							|  |  |  | 			return t, true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return featuremgmt.FeatureToggleDTO{}, false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-30 23:46:47 +08:00
										 |  |  | func readBody(t *testing.T, rc io.ReadCloser) map[string]any { | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 	t.Helper() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	b, err := io.ReadAll(rc) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2023-08-30 23:46:47 +08:00
										 |  |  | 	payload := map[string]any{} | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 	require.NoError(t, json.Unmarshal(b, &payload)) | 
					
						
							|  |  |  | 	return payload | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func runGetScenario( | 
					
						
							|  |  |  | 	t *testing.T, | 
					
						
							|  |  |  | 	features []*featuremgmt.FeatureFlag, | 
					
						
							|  |  |  | 	settings setting.FeatureMgmtSettings, | 
					
						
							|  |  |  | 	permissions []accesscontrol.Permission, | 
					
						
							|  |  |  | 	expectedCode int, | 
					
						
							|  |  |  | ) []featuremgmt.FeatureToggleDTO { | 
					
						
							|  |  |  | 	// Set up server and send request
 | 
					
						
							|  |  |  | 	cfg := setting.NewCfg() | 
					
						
							|  |  |  | 	cfg.FeatureManagement = settings | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	server := SetupAPITestServer(t, func(hs *HTTPServer) { | 
					
						
							|  |  |  | 		hs.Cfg = cfg | 
					
						
							|  |  |  | 		hs.Features = featuremgmt.WithFeatureFlags(append([]*featuremgmt.FeatureFlag{{ | 
					
						
							|  |  |  | 			Name:    featuremgmt.FlagFeatureToggleAdminPage, | 
					
						
							|  |  |  | 			Enabled: true, | 
					
						
							|  |  |  | 			Stage:   featuremgmt.FeatureStageGeneralAvailability, | 
					
						
							|  |  |  | 		}}, features...)) | 
					
						
							|  |  |  | 		hs.orgService = orgtest.NewOrgServiceFake() | 
					
						
							|  |  |  | 		hs.userService = &usertest.FakeUserService{ | 
					
						
							|  |  |  | 			ExpectedUser: &user.User{ID: 1}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		hs.log = log.New("test") | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	req := webtest.RequestWithSignedInUser(server.NewGetRequest("/api/featuremgmt"), userWithPermissions(1, permissions)) | 
					
						
							|  |  |  | 	res, err := server.SendJSON(req) | 
					
						
							|  |  |  | 	defer func() { require.NoError(t, res.Body.Close()) }() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Do some general checks for every request
 | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 	require.Equal(t, expectedCode, res.StatusCode) | 
					
						
							|  |  |  | 	if res.StatusCode >= 400 { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var result []featuremgmt.FeatureToggleDTO | 
					
						
							|  |  |  | 	err = json.NewDecoder(res.Body).Decode(&result) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i := 0; i < len(result); { | 
					
						
							|  |  |  | 		ft := result[i] | 
					
						
							|  |  |  | 		// Always make sure admin page toggle is read-only, then remove it to make assertions easier
 | 
					
						
							|  |  |  | 		if ft.Name == featuremgmt.FlagFeatureToggleAdminPage { | 
					
						
							|  |  |  | 			assert.True(t, ft.ReadOnly) | 
					
						
							|  |  |  | 			result = append(result[:i], result[i+1:]...) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Make sure toggles explicitly marked "hidden" by config are hidden
 | 
					
						
							|  |  |  | 		if _, ok := cfg.FeatureManagement.HiddenToggles[ft.Name]; ok { | 
					
						
							|  |  |  | 			t.Fail() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Make sure toggles explicitly marked "read only" by config are read only
 | 
					
						
							|  |  |  | 		if _, ok := cfg.FeatureManagement.ReadOnlyToggles[ft.Name]; ok { | 
					
						
							|  |  |  | 			assert.True(t, ft.ReadOnly) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		i++ | 
					
						
							| 
									
										
										
										
											2023-07-25 04:12:59 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-08-09 23:32:28 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return result | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func runSetScenario( | 
					
						
							|  |  |  | 	t *testing.T, | 
					
						
							|  |  |  | 	serverFeatures []*featuremgmt.FeatureFlag, | 
					
						
							|  |  |  | 	updateFeatures []featuremgmt.FeatureToggleDTO, | 
					
						
							|  |  |  | 	settings setting.FeatureMgmtSettings, | 
					
						
							|  |  |  | 	permissions []accesscontrol.Permission, | 
					
						
							|  |  |  | 	expectedCode int, | 
					
						
							|  |  |  | ) *http.Response { | 
					
						
							|  |  |  | 	// Set up server and send request
 | 
					
						
							|  |  |  | 	cfg := setting.NewCfg() | 
					
						
							|  |  |  | 	cfg.FeatureManagement = settings | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	server := SetupAPITestServer(t, func(hs *HTTPServer) { | 
					
						
							|  |  |  | 		hs.Cfg = cfg | 
					
						
							|  |  |  | 		hs.Features = featuremgmt.WithFeatureFlags(append([]*featuremgmt.FeatureFlag{{ | 
					
						
							|  |  |  | 			Name:    featuremgmt.FlagFeatureToggleAdminPage, | 
					
						
							|  |  |  | 			Enabled: true, | 
					
						
							|  |  |  | 			Stage:   featuremgmt.FeatureStageGeneralAvailability, | 
					
						
							|  |  |  | 		}}, serverFeatures...)) | 
					
						
							|  |  |  | 		hs.orgService = orgtest.NewOrgServiceFake() | 
					
						
							|  |  |  | 		hs.userService = &usertest.FakeUserService{ | 
					
						
							|  |  |  | 			ExpectedUser: &user.User{ID: 1}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		hs.log = log.New("test") | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cmd := featuremgmt.UpdateFeatureTogglesCommand{ | 
					
						
							|  |  |  | 		FeatureToggles: updateFeatures, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	b, err := json.Marshal(cmd) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 	req := webtest.RequestWithSignedInUser(server.NewPostRequest("/api/featuremgmt", bytes.NewReader(b)), userWithPermissions(1, permissions)) | 
					
						
							|  |  |  | 	res, err := server.SendJSON(req) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 	require.NotNil(t, res) | 
					
						
							|  |  |  | 	require.Equal(t, expectedCode, res.StatusCode) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return res | 
					
						
							| 
									
										
										
										
											2023-07-25 04:12:59 +08:00
										 |  |  | } |