Expose SettingToggle and SettingDictToggle objects in the API

Note that settings for which a corresponding SettingToggle or
SettingDictToggle exists are no longer exposed in the "django_settings"
list of the API.
This commit is contained in:
Régis Behmo
2020-10-13 20:35:00 +02:00
parent e4cb3dfcbd
commit da0623107c
2 changed files with 141 additions and 19 deletions

View File

@@ -1,7 +1,10 @@
"""
Tests for waffle utils views.
"""
from django.conf import settings
from django.test import TestCase
from django.test.utils import override_settings
from edx_toggles.toggles import SettingDictToggle, SettingToggle
from edx_toggles.toggles.testutils import override_waffle_flag
from rest_framework.test import APIRequestFactory
from waffle.testutils import override_switch
@@ -11,8 +14,8 @@ from student.tests.factories import UserFactory
from .. import WaffleFlag, WaffleFlagNamespace
from ..views import ToggleStateView
TEST_WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace('test')
TEST_WAFFLE_FLAG = WaffleFlag(TEST_WAFFLE_FLAG_NAMESPACE, 'flag', __name__)
TEST_WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace("test")
TEST_WAFFLE_FLAG = WaffleFlag(TEST_WAFFLE_FLAG_NAMESPACE, "flag", __name__)
# TODO: Missing coverage for:
@@ -21,7 +24,7 @@ TEST_WAFFLE_FLAG = WaffleFlag(TEST_WAFFLE_FLAG_NAMESPACE, 'flag', __name__)
class ToggleStateViewTests(TestCase):
def test_success_for_staff(self):
response = self._get_toggle_state_response(is_staff=True)
response = self._get_toggle_state_response()
self.assertEqual(response.status_code, 200)
self.assertTrue(response.data)
@@ -31,19 +34,105 @@ class ToggleStateViewTests(TestCase):
@override_waffle_flag(TEST_WAFFLE_FLAG, True)
def test_response_with_waffle_flag(self):
response = self._get_toggle_state_response(is_staff=True)
response = self._get_toggle_state_response()
self.assertIn('waffle_flags', response.data)
self.assertTrue(response.data['waffle_flags'])
# This is no longer the first flag
#self.assertEqual(response.data['waffle_flags'][0]['name'], 'test.flag')
waffle_names = [waffle["name"] for waffle in response.data['waffle_flags']]
self.assertIn('test.flag', waffle_names)
@override_switch('test.switch', True)
def test_response_with_waffle_switch(self):
response = self._get_toggle_state_response(is_staff=True)
response = self._get_toggle_state_response()
self.assertIn('waffle_switches', response.data)
self.assertTrue(response.data['waffle_switches'])
# This is no longer the first switch
#self.assertEqual(response.data['waffle_switches'][0]['name'], 'test.switch')
waffle_names = [waffle["name"] for waffle in response.data['waffle_switches']]
self.assertIn('test.switch', waffle_names)
def test_response_with_setting_toggle(self):
_toggle = SettingToggle("MYSETTING", default=False, module_name="module1")
with override_settings(MYSETTING=True):
response = self._get_toggle_state_response()
self.assertIn(
{
"name": "MYSETTING",
"is_active": True,
"module": "module1",
"class": "SettingToggle",
},
response.data["django_settings"],
)
def test_response_with_existing_setting_dict_toggle(self):
response = self._get_toggle_state_response()
self.assertIn(
{
"name": "FEATURES['MILESTONES_APP']",
"is_active": True,
"module": "util.milestones_helpers",
"class": "SettingDictToggle",
},
response.data["django_settings"],
)
def test_response_with_new_setting_dict_toggle(self):
_toggle = SettingDictToggle(
"CUSTOM_FEATURES", "MYSETTING", default=False, module_name="module1"
)
with override_settings(CUSTOM_FEATURES={"MYSETTING": True}):
response = self._get_toggle_state_response()
setting_dict = {toggle["name"]: toggle for toggle in response.data["django_settings"]}
self.assertEqual(
{
"name": "CUSTOM_FEATURES['MYSETTING']",
"is_active": True,
"module": "module1",
"class": "SettingDictToggle",
},
setting_dict["CUSTOM_FEATURES['MYSETTING']"],
)
def test_setting_overridden_by_setting_toggle(self):
_toggle2 = SettingToggle(
"MYSETTING2", module_name="module1"
)
_toggle3 = SettingDictToggle(
"MYDICT", "MYSETTING3", module_name="module1"
)
with override_settings(MYSETTING1=True, MYSETTING2=False, MYDICT={"MYSETTING3": False}):
# Need to pre-load settings, otherwise they are not picked up by the view
self.assertTrue(settings.MYSETTING1)
response = self._get_toggle_state_response()
setting_dict = {toggle["name"]: toggle for toggle in response.data["django_settings"]}
# Check that Django settings for which a SettingToggle exists have both the correct is_active and class values
self.assertTrue(setting_dict["MYSETTING1"]["is_active"])
self.assertNotIn("class", setting_dict["MYSETTING1"])
self.assertFalse(setting_dict["MYSETTING2"]["is_active"])
self.assertEqual("SettingToggle", setting_dict["MYSETTING2"]["class"])
self.assertFalse(setting_dict["MYDICT['MYSETTING3']"]["is_active"])
self.assertEqual("SettingDictToggle", setting_dict["MYDICT['MYSETTING3']"]["class"])
def test_no_duplicate_setting_toggle(self):
_toggle1 = SettingToggle(
"MYSETTING1", module_name="module1"
)
_toggle2 = SettingDictToggle(
"MYDICT", "MYSETTING2", module_name="module1"
)
with override_settings(MYSETTING1=True, MYDICT={"MYSETTING2": False}):
response = self._get_toggle_state_response()
# Check there are no duplicate setting/toggle
response_toggles_1 = [toggle for toggle in response.data["django_settings"] if toggle["name"] == "MYSETTING1"]
response_toggles_2 = [
toggle for toggle in response.data["django_settings"] if toggle["name"] == "MYDICT['MYSETTING2']"
]
self.assertEqual(1, len(response_toggles_1))
self.assertEqual(1, len(response_toggles_2))
def test_code_owners_without_module_information(self):
# Create a waffle flag without any associated module_name

View File

@@ -7,6 +7,7 @@ from django.conf import settings
from edx_django_utils.monitoring import get_code_owner_from_module
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from edx_rest_framework_extensions.permissions import IsStaff
from edx_toggles.toggles import SettingDictToggle, SettingToggle
from rest_framework import permissions, views
from rest_framework.authentication import SessionAuthentication
from rest_framework.response import Response
@@ -214,20 +215,52 @@ class ToggleStateView(views.APIView):
def _get_settings_state(self):
"""
Returns a dictionary of settings values. Will only return values that are set to true or false.
Return a list of setting-based toggles: Django settings, SettingToggle and SettingDictToggle instances.
SettingToggle and SettingDictToggle override the settings with identical names (if any).
"""
settings_dict = {}
self._add_settings(settings_dict)
self._add_setting_toggles(settings_dict)
self._add_setting_dict_toggles(settings_dict)
return sorted(settings_dict.values(), key=(lambda toggle: toggle['name']))
bool_settings = list()
def _add_settings(self, settings_dict):
"""
Fill the `settings_dict`: will only include values that are set to true or false.
"""
for setting_name, setting_value in vars(settings).items():
if isinstance(setting_value, dict):
for dict_name, dict_value in setting_value.items():
if isinstance(dict_value, bool):
bool_settings.append(
{
'name': "{setting_name}['{dict_name}']".format(setting_name=setting_name, dict_name=dict_name),
'is_active': dict_value,
}
)
name = setting_dict_name(setting_name, dict_name)
toggle_response = self._get_or_create_toggle_response(settings_dict, name)
toggle_response['is_active'] = dict_value
elif isinstance(setting_value, bool):
bool_settings.append({'name': setting_name, 'is_active': setting_value})
return bool_settings
toggle_response = self._get_or_create_toggle_response(settings_dict, setting_name)
toggle_response['is_active'] = setting_value
def _add_setting_toggles(self, settings_dict):
"""
Fill the `settings_dict` with values from the list of SettingToggle instances.
"""
for toggle in SettingToggle.get_instances():
toggle_response = self._get_or_create_toggle_response(settings_dict, toggle.name)
toggle_response["is_active"] = toggle.is_enabled()
self._add_toggle_instance_details(toggle_response, toggle)
def _add_setting_dict_toggles(self, settings_dict):
"""
Fill the `settings_dict` with values from the list of SettingDictToggle instances.
"""
for toggle in SettingDictToggle.get_instances():
name = setting_dict_name(toggle.name, toggle.key)
toggle_response = self._get_or_create_toggle_response(settings_dict, name)
toggle_response["is_active"] = toggle.is_enabled()
self._add_toggle_instance_details(toggle_response, toggle)
def setting_dict_name(dict_name, key):
"""
Return the name associated to a `dict_name[key]` setting.
"""
return "{dict_name}['{key}']".format(dict_name=dict_name, key=key)