Merge pull request #26058 from regisb/regisb/toggle-state-report-unittests

[BD-21] Improve test coverage of toggle state view
This commit is contained in:
Robert Raposa
2021-02-01 14:53:31 -05:00
committed by GitHub
2 changed files with 242 additions and 212 deletions

View File

@@ -12,14 +12,14 @@ from waffle.testutils import override_switch
from common.djangoapps.student.tests.factories import UserFactory
from .. import WaffleFlag, WaffleFlagNamespace
from ..views import ToggleStateView
from .. import models
from .. import views as toggle_state_views
TEST_WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace("test")
TEST_WAFFLE_FLAG = WaffleFlag(TEST_WAFFLE_FLAG_NAMESPACE, "flag", __name__)
# TODO: Missing coverage for:
# - course overrides
# - computed_status
class ToggleStateViewTests(TestCase):
@@ -144,11 +144,26 @@ class ToggleStateViewTests(TestCase):
][0]
self.assertNotIn("code_owner", result)
def test_course_overrides(self):
models.WaffleFlagCourseOverrideModel.objects.create(waffle_flag="my.flag", enabled=True)
course_overrides = {}
# pylint: disable=protected-access
toggle_state_views._add_waffle_flag_course_override_state(course_overrides)
toggle_state_views._add_waffle_flag_computed_status(course_overrides)
self.assertIn("my.flag", course_overrides)
self.assertIn("course_overrides", course_overrides["my.flag"])
self.assertEqual(1, len(course_overrides["my.flag"]["course_overrides"]))
self.assertEqual("None", course_overrides["my.flag"]["course_overrides"][0]["course_id"])
self.assertEqual("on", course_overrides["my.flag"]["course_overrides"][0]["force"])
self.assertEqual("both", course_overrides["my.flag"]["computed_status"])
def _get_toggle_state_response(self, is_staff=True):
request = APIRequestFactory().get('/api/toggles/state/')
user = UserFactory()
user.is_staff = is_staff
request.user = user
view = ToggleStateView.as_view()
view = toggle_state_views.ToggleStateView.as_view()
response = view(request)
return response

View File

@@ -26,235 +26,250 @@ class ToggleStateView(views.APIView):
def get(self, request):
response = OrderedDict()
response['waffle_flags'] = self._get_all_waffle_flags()
response['waffle_switches'] = self._get_all_waffle_switches()
response['django_settings'] = self._get_settings_state()
response['waffle_flags'] = _get_all_waffle_flags()
response['waffle_switches'] = _get_all_waffle_switches()
response['django_settings'] = _get_settings_state()
return Response(response)
def _get_all_waffle_switches(self):
"""
Gets all waffle switches and their state.
"""
switches_dict = {}
self._add_waffle_switch_instances(switches_dict)
self._add_waffle_switch_state(switches_dict)
self._add_waffle_switch_computed_status(switches_dict)
switch_list = list(switches_dict.values())
switch_list.sort(key=lambda toggle: toggle['name'])
return switch_list
def _add_waffle_switch_instances(self, switches_dict):
"""
Add details from waffle switch instances, like code_owner.
"""
waffle_switch_instances = WaffleSwitch.get_instances()
for switch_instance in waffle_switch_instances:
switch = self._get_or_create_toggle_response(switches_dict, switch_instance.name)
self._add_toggle_instance_details(switch, switch_instance)
def _get_all_waffle_switches():
"""
Gets all waffle switches and their state.
"""
switches_dict = {}
_add_waffle_switch_instances(switches_dict)
_add_waffle_switch_state(switches_dict)
_add_waffle_switch_computed_status(switches_dict)
switch_list = list(switches_dict.values())
switch_list.sort(key=lambda toggle: toggle['name'])
return switch_list
def _add_waffle_switch_state(self, switches_dict):
"""
Add waffle switch state from the waffle Switch model.
"""
waffle_switches = Switch.objects.all()
for switch_data in waffle_switches:
switch = self._get_or_create_toggle_response(switches_dict, switch_data.name)
switch['is_active'] = 'true' if switch_data.active else 'false'
if switch_data.note:
switch['note'] = switch_data.note
switch['created'] = str(switch_data.created)
switch['modified'] = str(switch_data.modified)
def _add_waffle_switch_computed_status(self, switch_dict):
"""
Add computed status to each waffle switch.
"""
for switch in switch_dict.values():
computed_status = 'off'
if 'is_active' in switch:
if switch['is_active'] == 'true':
computed_status = 'on'
else:
computed_status = 'off'
switch['computed_status'] = computed_status
def _add_waffle_switch_instances(switches_dict):
"""
Add details from waffle switch instances, like code_owner.
"""
waffle_switch_instances = WaffleSwitch.get_instances()
for switch_instance in waffle_switch_instances:
switch = _get_or_create_toggle_response(switches_dict, switch_instance.name)
_add_toggle_instance_details(switch, switch_instance)
def _get_all_waffle_flags(self):
"""
Gets all waffle flags and their state.
"""
flags_dict = {}
self._add_waffle_flag_instances(flags_dict)
self._add_waffle_flag_state(flags_dict)
self._add_waffle_flag_course_override_state(flags_dict)
self._add_waffle_flag_computed_status(flags_dict)
flag_list = list(flags_dict.values())
flag_list.sort(key=lambda toggle: toggle['name'])
return flag_list
def _add_waffle_flag_instances(self, flags_dict):
"""
Add details from waffle flag instances, like code_owner.
"""
waffle_flag_instances = WaffleFlag.get_instances()
for flag_instance in waffle_flag_instances:
flag = self._get_or_create_toggle_response(flags_dict, flag_instance.name)
self._add_toggle_instance_details(flag, flag_instance)
def _add_waffle_switch_state(switches_dict):
"""
Add waffle switch state from the waffle Switch model.
"""
waffle_switches = Switch.objects.all()
for switch_data in waffle_switches:
switch = _get_or_create_toggle_response(switches_dict, switch_data.name)
switch['is_active'] = 'true' if switch_data.active else 'false'
if switch_data.note:
switch['note'] = switch_data.note
switch['created'] = str(switch_data.created)
switch['modified'] = str(switch_data.modified)
def _add_toggle_instance_details(self, toggle, toggle_instance):
"""
Add details (class, module, code_owner) from a specific toggle instance.
"""
toggle['class'] = toggle_instance.__class__.__name__
toggle['module'] = toggle_instance.module_name
if toggle_instance.module_name:
code_owner = get_code_owner_from_module(toggle_instance.module_name)
if code_owner:
toggle['code_owner'] = code_owner
def _add_waffle_flag_state(self, flags_dict):
"""
Add waffle flag state from the waffle Flag model.
"""
waffle_flags = Flag.objects.all()
for flag_data in waffle_flags:
flag = self._get_or_create_toggle_response(flags_dict, flag_data.name)
if flag_data.everyone is True:
everyone = 'yes'
elif flag_data.everyone is False:
everyone = 'no'
def _add_waffle_switch_computed_status(switch_dict):
"""
Add computed status to each waffle switch.
"""
for switch in switch_dict.values():
computed_status = 'off'
if 'is_active' in switch:
if switch['is_active'] == 'true':
computed_status = 'on'
else:
everyone = 'unknown'
flag['everyone'] = everyone
if flag_data.note:
flag['note'] = flag_data.note
flag['created'] = str(flag_data.created)
flag['modified'] = str(flag_data.modified)
computed_status = 'off'
switch['computed_status'] = computed_status
def _add_waffle_flag_course_override_state(self, flags_dict):
"""
Add waffle flag course override state from the WaffleFlagCourseOverrideModel model.
"""
# This dict is keyed by flag name, and contains dicts keyed by course_id, the contains
# the final dict of metadata for a single course override that will be returned.
flag_course_overrides = OrderedDict()
# Note: We can't just get enabled records, because if a historical record is enabled but
# the current record is disabled, we would not know this. We get all records, and mark
# some overrides as disabled, and then later filter the disabled records.
course_overrides_data = WaffleFlagCourseOverrideModel.objects.all()
course_overrides_data = course_overrides_data.order_by('waffle_flag', 'course_id', '-change_date')
for course_override_data in course_overrides_data:
flag_name = course_override_data.waffle_flag
course_id = str(course_override_data.course_id)
if flag_name not in flag_course_overrides:
flag_course_overrides[flag_name] = OrderedDict()
course_overrides = flag_course_overrides[flag_name]
if course_id not in course_overrides:
course_overrides[course_id] = OrderedDict()
course_override = course_overrides[course_id]
# data is reverse ordered by date, so the first record is the current record
if 'course_id' not in course_override:
course_override['course_id'] = course_id
if not course_override_data.enabled:
# The current record may be disabled, but later history might be enabled.
# We'll filter these disabled records below.
course_override['disabled'] = True
else:
course_override['force'] = course_override_data.override_choice
course_override['modified'] = str(course_override_data.change_date)
# data is reverse ordered by date, so the last record is the oldest record
course_override['created'] = str(course_override_data.change_date)
for flag_name, course_overrides_dict in flag_course_overrides.items():
course_overrides = [
course_override for course_override in course_overrides_dict.values()
if 'disabled' not in course_override
]
if course_overrides:
flag = self._get_or_create_toggle_response(flags_dict, flag_name)
flag['course_overrides'] = course_overrides
def _get_all_waffle_flags():
"""
Gets all waffle flags and their state.
"""
flags_dict = {}
_add_waffle_flag_instances(flags_dict)
_add_waffle_flag_state(flags_dict)
_add_waffle_flag_course_override_state(flags_dict)
_add_waffle_flag_computed_status(flags_dict)
flag_list = list(flags_dict.values())
flag_list.sort(key=lambda toggle: toggle['name'])
return flag_list
def _add_waffle_flag_computed_status(self, flags_dict):
"""
Add computed status to each waffle flag.
"""
for flag in flags_dict.values():
computed_status = 'off'
if 'everyone' in flag:
if flag['everyone'] == 'yes':
computed_status = 'on'
elif flag['everyone'] == 'unknown':
computed_status = 'both'
# check course overrides only if computed_status is not already 'both'
if computed_status != 'both' and 'course_overrides' in flag:
has_force_on = any(override['force'] == 'on' for override in flag['course_overrides'])
has_force_off = any(override['force'] == 'off' for override in flag['course_overrides'])
if has_force_on and has_force_off:
computed_status = 'both'
elif has_force_on:
computed_status = 'on' if computed_status == 'on' else 'both'
elif has_force_off:
computed_status = 'off' if computed_status == 'off' else 'both'
flag['computed_status'] = computed_status
def _get_or_create_toggle_response(self, toggles_dict, toggle_name):
"""
Gets or creates a toggle response dict and adds it to the toggles_dict.
def _add_waffle_flag_instances(flags_dict):
"""
Add details from waffle flag instances, like code_owner.
"""
waffle_flag_instances = WaffleFlag.get_instances()
for flag_instance in waffle_flag_instances:
flag = _get_or_create_toggle_response(flags_dict, flag_instance.name)
_add_toggle_instance_details(flag, flag_instance)
Returns:
Either the pre-existing toggle response, or a new toggle dict with its name set.
"""
if toggle_name in toggles_dict:
return toggles_dict[toggle_name]
toggle = OrderedDict()
toggle['name'] = toggle_name
toggles_dict[toggle_name] = toggle
return toggle
def _add_waffle_flag_state(flags_dict):
"""
Add waffle flag state from the waffle Flag model.
"""
waffle_flags = Flag.objects.all()
for flag_data in waffle_flags:
flag = _get_or_create_toggle_response(flags_dict, flag_data.name)
if flag_data.everyone is True:
everyone = 'yes'
elif flag_data.everyone is False:
everyone = 'no'
else:
everyone = 'unknown'
flag['everyone'] = everyone
if flag_data.note:
flag['note'] = flag_data.note
flag['created'] = str(flag_data.created)
flag['modified'] = str(flag_data.modified)
def _get_settings_state(self):
"""
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']))
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):
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):
toggle_response = self._get_or_create_toggle_response(settings_dict, setting_name)
toggle_response['is_active'] = setting_value
def _add_waffle_flag_course_override_state(flags_dict):
"""
Add waffle flag course override state from the WaffleFlagCourseOverrideModel model.
"""
# This dict is keyed by flag name, and contains dicts keyed by course_id, the contains
# the final dict of metadata for a single course override that will be returned.
flag_course_overrides = OrderedDict()
# Note: We can't just get enabled records, because if a historical record is enabled but
# the current record is disabled, we would not know this. We get all records, and mark
# some overrides as disabled, and then later filter the disabled records.
course_overrides_data = WaffleFlagCourseOverrideModel.objects.all()
course_overrides_data = course_overrides_data.order_by('waffle_flag', 'course_id', '-change_date')
for course_override_data in course_overrides_data:
flag_name = course_override_data.waffle_flag
course_id = str(course_override_data.course_id)
if flag_name not in flag_course_overrides:
flag_course_overrides[flag_name] = OrderedDict()
course_overrides = flag_course_overrides[flag_name]
if course_id not in course_overrides:
course_overrides[course_id] = OrderedDict()
course_override = course_overrides[course_id]
# data is reverse ordered by date, so the first record is the current record
if 'course_id' not in course_override:
course_override['course_id'] = course_id
if not course_override_data.enabled:
# The current record may be disabled, but later history might be enabled.
# We'll filter these disabled records below.
course_override['disabled'] = True
else:
course_override['force'] = course_override_data.override_choice
course_override['modified'] = str(course_override_data.change_date)
# data is reverse ordered by date, so the last record is the oldest record
course_override['created'] = str(course_override_data.change_date)
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)
for flag_name, course_overrides_dict in flag_course_overrides.items():
course_overrides = [
course_override for course_override in course_overrides_dict.values()
if 'disabled' not in course_override
]
if course_overrides:
flag = _get_or_create_toggle_response(flags_dict, flag_name)
flag['course_overrides'] = course_overrides
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 _add_waffle_flag_computed_status(flags_dict):
"""
Add computed status to each waffle flag.
"""
for flag in flags_dict.values():
computed_status = 'off'
if 'everyone' in flag:
if flag['everyone'] == 'yes':
computed_status = 'on'
elif flag['everyone'] == 'unknown':
computed_status = 'both'
# check course overrides only if computed_status is not already 'both'
if computed_status != 'both' and 'course_overrides' in flag:
has_force_on = any(override['force'] == 'on' for override in flag['course_overrides'])
has_force_off = any(override['force'] == 'off' for override in flag['course_overrides'])
if has_force_on and has_force_off:
computed_status = 'both'
elif has_force_on:
computed_status = 'on' if computed_status == 'on' else 'both'
elif has_force_off:
computed_status = 'off' if computed_status == 'off' else 'both'
flag['computed_status'] = computed_status
def _get_settings_state():
"""
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 = {}
_add_settings(settings_dict)
_add_setting_toggles(settings_dict)
_add_setting_dict_toggles(settings_dict)
return sorted(settings_dict.values(), key=(lambda toggle: toggle['name']))
def _add_settings(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):
name = setting_dict_name(setting_name, dict_name)
toggle_response = _get_or_create_toggle_response(settings_dict, name)
toggle_response['is_active'] = dict_value
elif isinstance(setting_value, bool):
toggle_response = _get_or_create_toggle_response(settings_dict, setting_name)
toggle_response['is_active'] = setting_value
def _add_setting_toggles(settings_dict):
"""
Fill the `settings_dict` with values from the list of SettingToggle instances.
"""
for toggle in SettingToggle.get_instances():
toggle_response = _get_or_create_toggle_response(settings_dict, toggle.name)
toggle_response["is_active"] = toggle.is_enabled()
_add_toggle_instance_details(toggle_response, toggle)
def _add_toggle_instance_details(toggle, toggle_instance):
"""
Add details (class, module, code_owner) from a specific toggle instance.
"""
toggle['class'] = toggle_instance.__class__.__name__
toggle['module'] = toggle_instance.module_name
if toggle_instance.module_name:
code_owner = get_code_owner_from_module(toggle_instance.module_name)
if code_owner:
toggle['code_owner'] = code_owner
def _add_setting_dict_toggles(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 = _get_or_create_toggle_response(settings_dict, name)
toggle_response["is_active"] = toggle.is_enabled()
_add_toggle_instance_details(toggle_response, toggle)
def _get_or_create_toggle_response(toggles_dict, toggle_name):
"""
Gets or creates a toggle response dict and adds it to the toggles_dict.
Returns:
Either the pre-existing toggle response, or a new toggle dict with its name set.
"""
if toggle_name in toggles_dict:
return toggles_dict[toggle_name]
toggle = OrderedDict()
toggle['name'] = toggle_name
toggles_dict[toggle_name] = toggle
return toggle
def setting_dict_name(dict_name, key):