From f27783643dca39dff67ba44bc1b957634c9d11ff Mon Sep 17 00:00:00 2001 From: Shauna Gordon-McKeon Date: Mon, 11 May 2020 15:48:09 -0400 Subject: [PATCH 1/4] Add www.kybern.org to allowed_hosts --- kybern/mysite/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kybern/mysite/settings.py b/kybern/mysite/settings.py index a61a062..b1f55af 100644 --- a/kybern/mysite/settings.py +++ b/kybern/mysite/settings.py @@ -16,7 +16,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -ALLOWED_HOSTS = ['127.0.0.1', 'kybern.herokuapp.com'] +ALLOWED_HOSTS = ['127.0.0.1', 'kybern.herokuapp.com', 'www.kybern.org'] # Application definition From c79e1e9f7605d11e39afea71a268bbf20cf20c64 Mon Sep 17 00:00:00 2001 From: Shauna Gordon-McKeon Date: Mon, 11 May 2020 19:28:15 -0400 Subject: [PATCH 2/4] Custom 500 and 404 error pages --- .../templates/accounts/error_404.html | 21 ++++++++++++++++++ .../templates/accounts/error_500.html | 22 +++++++++++++++++++ kybern/mysite/urls.py | 6 +++++ kybern/mysite/views.py | 11 ++++++++++ 4 files changed, 60 insertions(+) create mode 100644 kybern/accounts/templates/accounts/error_404.html create mode 100644 kybern/accounts/templates/accounts/error_500.html create mode 100644 kybern/mysite/views.py diff --git a/kybern/accounts/templates/accounts/error_404.html b/kybern/accounts/templates/accounts/error_404.html new file mode 100644 index 0000000..27ed3bf --- /dev/null +++ b/kybern/accounts/templates/accounts/error_404.html @@ -0,0 +1,21 @@ +{% extends 'accounts/base.html' %} + +{% block content %} + +
+
+ +
The page you requested could not be found
+ +
+ +

+ We're sorry, the page you requested could not be found. If you think it really ought to have + been found, email us and let us know. +

+ +
+ + + +{% endblock %} diff --git a/kybern/accounts/templates/accounts/error_500.html b/kybern/accounts/templates/accounts/error_500.html new file mode 100644 index 0000000..71b9d47 --- /dev/null +++ b/kybern/accounts/templates/accounts/error_500.html @@ -0,0 +1,22 @@ +{% extends 'accounts/base.html' %} + +{% block content %} + +
+
+ +
There was a problem with our server
+ + + +

+ We're sorry, something happened on our end. If you're willing, please + email us and let us know what you + were doing just now, so we can try to figure out what went wrong. +

+ +
+ + + +{% endblock %} diff --git a/kybern/mysite/urls.py b/kybern/mysite/urls.py index b70d917..acea755 100644 --- a/kybern/mysite/urls.py +++ b/kybern/mysite/urls.py @@ -15,6 +15,9 @@ """ from django.contrib import admin from django.urls import path, include +from django.conf.urls import handler404, handler500 +from .views import error_404, error_500 + urlpatterns = [ path('', include('accounts.urls')), @@ -23,3 +26,6 @@ path('groups/', include('groups.urls')), path('admin/', admin.site.urls), ] + +handler404 = error_404 +handler500 = error_500 \ No newline at end of file diff --git a/kybern/mysite/views.py b/kybern/mysite/views.py new file mode 100644 index 0000000..7d091cc --- /dev/null +++ b/kybern/mysite/views.py @@ -0,0 +1,11 @@ +from django.shortcuts import render + + +def error_404(request, exception): + data = {} + return render(request,'accounts/error_404.html', data) + + +def error_500(request): + data = {} + return render(request,'accounts/error_500.html', data) \ No newline at end of file From 114f9f6406479f4914a0472c4f3d81f13c4a84b1 Mon Sep 17 00:00:00 2001 From: Shauna Gordon-McKeon Date: Tue, 12 May 2020 16:43:24 -0400 Subject: [PATCH 3/4] Update tests, fix some synch bugs with conditionals --- .../templates/accounts/base_vue_include.html | 24 +- .../templates/groups/add_role_include.html | 2 +- .../groups/approve_condition_include.html | 36 ++- .../groups/configuration_fields_include.html | 4 +- .../templates/groups/edit_role_include.html | 5 +- .../groups/manage_condition_include.html | 7 +- .../groups/vote_condition_include.html | 32 +- kybern/groups/views.py | 23 +- kybern/tests.py | 287 +++++++++++++++++- 9 files changed, 373 insertions(+), 47 deletions(-) diff --git a/kybern/accounts/templates/accounts/base_vue_include.html b/kybern/accounts/templates/accounts/base_vue_include.html index 0614a7a..7dffa3d 100644 --- a/kybern/accounts/templates/accounts/base_vue_include.html +++ b/kybern/accounts/templates/accounts/base_vue_include.html @@ -14,6 +14,8 @@ } update_actions = function (response, dispatch) { + + console.log("Updating actions given response data: ", response.data) if (response.data.multiple_actions) { for (index in response.data.actions) { if (response.data.actions[index].action_pk) { @@ -24,6 +26,7 @@ } } else { if (response.data.action_pk ) { + console.log("Adding or updating an action: ", response.data.action_pk ) dispatch('addOrUpdateAction', { action_pk: response.data.action_pk }) } else { console.log("Warning: no action_pk for single!") @@ -40,10 +43,12 @@ if (response.data.action_status == "implemented") { implementationCallback(response) } else { - throw Error(response.data.action_log) + console.log("Not implemented due to: ", response.data.action_developer_log) + throw response.data.action_log } - }).catch(error => { console.log("Got error: ", error); throw error }) + }).catch(error => { + console.log("Got error: ", error); throw error }) } const store = new Vuex.Store({ @@ -388,23 +393,25 @@ state.governor_condition_pk = null }, REPLACE_ACTIONS_FOR_ITEM (state, data) { - Vue.set(state.actions, data.item_key, data.action_data) + Vue.set(state.actions, data.item_key.toLowerCase(), data.action_data) }, ADD_OR_UPDATE_ACTION (state, data) { // Takes in action data and the item_key for the item the action targets - if (!state.actions[data.item_key]) { - Vue.set(state.actions, data.item_key, []) + item_key = data.item_key.toLowerCase() + + if (!state.actions[item_key]) { + Vue.set(state.actions, item_key, []) } for (index in state.actions[item_key]) { - if (state.actions[data.item_key][index].action_pk == data.action_data.action_pk) { - state.actions[data.item_key].splice(index, 1, data.action_data) + if (state.actions[item_key][index].action_pk == data.action_data.action_pk) { + state.actions[item_key].splice(index, 1, data.action_data) return } } - state.actions[data.item_key].push(data.action_data) // If we get here, we're adding the action + state.actions[item_key].push(data.action_data) // If we get here, we're adding the action }, ADD_OR_UPDATE_FORUM (state, data) { for (index in state.forums) { @@ -667,6 +674,7 @@ return standard_get_api_call(url, params, implementationCallback, dispatch) }, addOrUpdateAction ({ commit, state, dispatch}, payload) { + console.log("Action called!") url = "{{ base_url }}/groups/get_action_data/"; params = { action_pk : payload.action_pk } implementationCallback = (response) => { diff --git a/kybern/groups/templates/groups/add_role_include.html b/kybern/groups/templates/groups/add_role_include.html index c385f52..869543e 100644 --- a/kybern/groups/templates/groups/add_role_include.html +++ b/kybern/groups/templates/groups/add_role_include.html @@ -10,7 +10,7 @@ aria-describedby="role_name_prompt" v-model="new_role_name" name="role_name"> -
+
[[ error_message ]]
diff --git a/kybern/groups/templates/groups/approve_condition_include.html b/kybern/groups/templates/groups/approve_condition_include.html index 0ea8bdf..2c3f6b8 100644 --- a/kybern/groups/templates/groups/approve_condition_include.html +++ b/kybern/groups/templates/groups/approve_condition_include.html @@ -11,7 +11,7 @@ - Save + Save @@ -35,7 +35,7 @@ - [[ error_message ]] + [[ error_message ]] @@ -78,6 +78,7 @@ }, condition_status: function() { if (this.condition_details) { + console.log("New status is: ", this.condition_details.status) return this.condition_details.status } else { return null @@ -90,10 +91,10 @@ axios = this.prep_axios() url = "{{ base_url }}/groups/get_conditional_data/" params = { condition_pk: this.condition_pk, condition_type: this.condition_type } - axios.post(url, params).then(response => { + return axios.post(url, params).then(response => { this.permission_details = response.data.permission_details this.condition_details = response.data.condition_details - }).catch(error => { console.log("ERROR in approval dontion get_conditional_data") }) + }).catch(error => { console.log(error) }) }, update_conditional() { @@ -106,17 +107,26 @@ params = { condition_pk: this.condition_pk, action_to_take: this.button_selected } axios.post(url, params).then(response => { + new_action_pk = response.data.action_pk + // update condition data - this.get_conditional_data() this.user_has_taken_action = true - - // update action this was a condition on - this.addOrUpdateAction({ action_pk: this.action_details["action_pk"] }) - - // also call vuex to record this as an action (need to do this for all actions) - this.addOrUpdateAction({ action_pk: response.data.action_pk }) - - }) + this.get_conditional_data().then(response => { + + if (this.condition_status == "waiting") { + // In a limited set of circumstances, there may be a condition on a conditional action + // (for instance, if someone is using a governing or owning authority to act) + this.error_message = "Before your decision to " + this.button_selected + " can be implemented, a condition must be satisfied." + } else { + // update action this was a condition on + this.addOrUpdateAction({ action_pk: this.action_details["action_pk"] }) + // also call vuex to record this as an action (need to do this for all actions) + this.addOrUpdateAction({ action_pk: new_action_pk }) + } + + }).catch(error => { console.log("Error refreshing condition data:", error); this.error_message = error }) + + }).catch(error => { console.log("Error updating condition: ", error); this.error_message = error }) } } diff --git a/kybern/groups/templates/groups/configuration_fields_include.html b/kybern/groups/templates/groups/configuration_fields_include.html index 8cdfb50..dec5da2 100644 --- a/kybern/groups/templates/groups/configuration_fields_include.html +++ b/kybern/groups/templates/groups/configuration_fields_include.html @@ -23,7 +23,7 @@
-
+
[[ field.display ]] @@ -40,7 +40,7 @@
-
+
[[ field.display ]] diff --git a/kybern/groups/templates/groups/edit_role_include.html b/kybern/groups/templates/groups/edit_role_include.html index 8317804..63f3d28 100644 --- a/kybern/groups/templates/groups/edit_role_include.html +++ b/kybern/groups/templates/groups/edit_role_include.html @@ -5,7 +5,7 @@ People with this role can... - +
[[ pair.permission.display ]] @@ -95,6 +95,9 @@ this.permission_to_edit = permission_id this.permission_mode_from_parent = 'update' }, + pair_element_id: function(pk) { + return "permission_element_" + pk + }, add_condition(permission_id) { this.clearState() // clear previous state this.permission_to_condition = permission_id diff --git a/kybern/groups/templates/groups/manage_condition_include.html b/kybern/groups/templates/groups/manage_condition_include.html index fc31f17..b7b4236 100644 --- a/kybern/groups/templates/groups/manage_condition_include.html +++ b/kybern/groups/templates/groups/manage_condition_include.html @@ -29,7 +29,8 @@ Select condition to add @@ -43,9 +44,9 @@ Save condition + class="mt-3" @click="add_condition()" id="save_condition_button">Save condition Save changes + class="mt-3" @click="update_condition()" id="save_condition_button">Save changes diff --git a/kybern/groups/templates/groups/vote_condition_include.html b/kybern/groups/templates/groups/vote_condition_include.html index 8eb7ae3..c13ef14 100644 --- a/kybern/groups/templates/groups/vote_condition_include.html +++ b/kybern/groups/templates/groups/vote_condition_include.html @@ -28,7 +28,7 @@ - Submit + Submit @@ -124,7 +124,7 @@ axios = this.prep_axios() url = "{{ base_url }}/groups/get_conditional_data/" params = { condition_pk: this.condition_pk, condition_type: this.condition_type } - axios.post(url, params).then(response => { + return axios.post(url, params).then(response => { this.condition_details = response.data.condition_details this.permission_details = response.data.permission_details for (field in this.condition_details.fields) { @@ -145,17 +145,31 @@ params = { condition_pk: this.condition_pk, action_to_take: this.button_selected } axios.post(url, params).then(response => { + new_action_pk = response.data.action_pk + // update condition data - this.get_conditional_data() this.user_has_taken_action = true + this.get_conditional_data().then(response => { + + if (this.condition_details.status == "waiting") { + + // In a vote, *usually* the condition is still waiting after a person's taken action, + // because the condition is only resolved after the voting period ends. So we'll need + // a separate way to let people know if their vote isn't cast due to a condition. + + } else { + + // update action this was a condition on + this.addOrUpdateAction({ action_pk: this.action_details["action_pk"] }) + + // also call vuex to record this as an action (need to do this for all actions) + this.addOrUpdateAction({ action_pk: new_action_pk }) - // update action this was a condition on - this.addOrUpdateAction({ action_pk: this.action_details["action_pk"] }) - - // also call vuex to record this as an action (need to do this for all actions) - this.addOrUpdateAction({ action_pk: response.data.action_pk }) + } - }) + }).catch(error => { console.log("Error refreshing condition data:", error); this.error_message = error }) + + }).catch(error => { console.log("Error updating condition: ", error); this.error_message = error }) } } diff --git a/kybern/groups/views.py b/kybern/groups/views.py index ed74bf2..a64b3bc 100644 --- a/kybern/groups/views.py +++ b/kybern/groups/views.py @@ -41,6 +41,21 @@ def get_model(model_name): pass +readable_log_dict = { + "action did not meet any permission criteria": "You do not have permission to take this action." +} + + +def make_action_errors_readable(action): + """If needed, gets or creates both a developer-friendly (detailed) log and a user-friendly log.""" + + if action.resolution.status in ["accepted", "implemented"]: + return action.resolution.log, action.resolution.log # unlikely to be displayed/accessed + if action.resolution.status == "waiting": + return "This action cannot be completed until a condition is passed.", action.resolution.log + return readable_log_dict.get(action.resolution.log, "We're sorry, there was an error"), action.resolution.log + + def process_action(action): if action.resolution.status == "implemented": @@ -81,14 +96,13 @@ def process_action(action): def get_action_dict(action): - action_log = action.resolution.log - if (not action_log and action.resolution.status == "waiting"): - action_log = "waiting on condition" + display_log, developer_log = make_action_errors_readable(action) return { "action_created": True if action.resolution.status in ["implemented", "approved", "waiting", "rejected"] else False, "action_status": action.resolution.status, - "action_log": action_log, "action_pk": action.pk, + "action_log": display_log, + "action_developer_log": developer_log } @@ -472,7 +486,6 @@ def delete_post(request, target): action_dict = get_action_dict(action) action_dict["deleted_post_pk"] = pk - print(action_dict) return JsonResponse(action_dict) diff --git a/kybern/tests.py b/kybern/tests.py index 7f7a992..83c30b3 100644 --- a/kybern/tests.py +++ b/kybern/tests.py @@ -1,4 +1,4 @@ -import time +import time, json from django.contrib.staticfiles.testing import StaticLiveServerTestCase from splinter import Browser from django.conf import settings @@ -6,6 +6,9 @@ from django.contrib.auth.models import User from concord.communities.client import CommunityClient +from concord.permission_resources.client import PermissionResourceClient +from concord.conditionals.client import PermissionConditionalClient +from concord.actions.state_changes import Changes from groups.models import Group @@ -56,11 +59,12 @@ def delete_selected_in_multiselect(self, username): return True return False - def select_from_multiselect(self, selection, element_css=".multiselect__element"): + def select_from_multiselect(self, selection, element_css=".multiselect__element", search_within=None): """Helper method to select options given the custom interface vue-multiselect provides.""" - self.browser.find_by_css(".multiselect__select").first.click() + base = search_within if search_within else self.browser + base.find_by_css(".multiselect__select").first.click() time.sleep(.25) - for item in self.browser.find_by_css(element_css): + for item in base.find_by_css(element_css): if selection in item.text: item.click() return True @@ -224,6 +228,279 @@ def test_adding_permission_changes_site_behavior(self): self.delete_selected_in_multiselect("crystaldunn") self.browser.find_by_id('save_member_changes').first.click() time.sleep(.5) - self.assertTrue(self.browser.is_text_present('action did not meet any permission criteria')) + self.assertTrue(self.browser.is_text_present('You do not have permission to take this action.')) self.browser.find_by_css(".close").first.click() # close modal self.assertEquals(self.browser.find_by_id('members_member_count').text, "7 people") + + +class ActionsTestCase(BaseTestCase): + + def setUp(self): + self.create_users() + self.actor = User.objects.first() + self.client = CommunityClient(actor=self.actor) + self.client.community_model = Group + self.community = self.client.create_community(name="USWNT") + self.client.set_target(target=self.community) + self.client.add_members(member_pk_list=[user.pk for user in User.objects.all()]) + + def test_taking_action_generates_action(self): + + # Add role + self.login_user("meganrapinoe", "badlands2020") + self.go_to_group("USWNT") + self.browser.find_by_css(".role_name_display")[0].scroll_to() + self.browser.find_by_id('add_role_button').first.click() + self.browser.fill('role_name', 'forwards') + self.browser.find_by_id('save_role_button').first.click() + self.browser.find_by_css(".close").first.click() # close modal + + # Check for action in action history + self.browser.find_by_css("#action_history > span > button")[0].scroll_to() + self.browser.find_by_css("#action_history > span > button").first.click() + self.assertTrue(self.browser.is_text_present('meganrapinoe added role forwards to USWNT')) + + +class ActionConditionsTestCase(BaseTestCase): + + def setUp(self): + + # Basic setup + self.create_users() + self.actor = User.objects.first() + self.client = CommunityClient(actor=self.actor) + self.client.community_model = Group + self.community = self.client.create_community(name="USWNT") + self.client.set_target(target=self.community) + self.client.add_members(member_pk_list=[user.pk for user in User.objects.all()]) + self.client.add_role(role_name="forwards") + pinoe = User.objects.get(username="meganrapinoe") + press = User.objects.get(username="christenpress") + heath = User.objects.get(username="tobinheath") + self.client.add_people_to_role(role_name="forwards", people_to_add=[pinoe.pk, press.pk, heath.pk]) + + # Permission setup + self.permissionClient = PermissionResourceClient(actor=self.actor, target=self.community) + action, self.permission = self.permissionClient.add_permission( + permission_type = Changes.Communities.AddRole, permission_roles=["forwards"]) + + def test_adding_condition_to_permission_generates_condition(self): + + # Pinoe adds condition to permission + self.login_user("meganrapinoe", "badlands2020") + self.go_to_group("USWNT") + self.browser.find_by_id('forwards_editrole')[0].scroll_to() + self.browser.find_by_id('forwards_editrole').first.click() + permissions = [item.text for item in self.browser.find_by_css(".permission-display")] + self.assertEquals(permissions, ["add role to community"]) + css_selector = "#permission_element_" + str(self.permission.pk) + " > div > button.btn.btn-secondary" + self.browser.find_by_css(css_selector).first.click() + self.browser.select("condition_select", "VoteCondition") + # TODO: look up - is there really no way to better identify vue-multiselect items? + element_containing_role_dropdown = self.browser.find_by_css(".permissionrolefield")[0] + self.select_from_multiselect("forwards", search_within=element_containing_role_dropdown) + self.browser.find_by_id('save_condition_button').first.click() + time.sleep(.25) + self.browser.find_by_css(".close").first.click() # close modal + + # Someone with the permission tries to take action (use asserts to check for condition error text) + self.login_user("christenpress", "badlands2020") + self.go_to_group("USWNT") + self.browser.find_by_id('add_role_button').first.click() + self.browser.fill('role_name', 'midfielders') + self.browser.find_by_id('save_role_button').first.click() + time.sleep(.25) + self.assertTrue(self.browser.is_text_present('This action cannot be completed until a condition is passed.')) + self.browser.find_by_css(".close").first.click() # close modal + + # Go to action history and the condition link is there in the has_condition column + self.browser.find_by_css("#action_history > span > button")[0].scroll_to() + self.browser.find_by_css("#action_history > span > button").first.click() + self.assertTrue(self.browser.is_text_present('christenpress asked to add role midfielders to USWNT')) + self.browser.find_by_xpath('//*[@id="action_history_table_element"]/tbody/tr[1]/td[7]/button').first.click() + self.assertTrue(self.browser.is_text_present('Please cast your vote')) + + +class ApprovalConditionsTestCase(BaseTestCase): + + def setUp(self): + + # create group, add members, add roles, add members to role + self.create_users() + self.actor = User.objects.first() + self.client = CommunityClient(actor=self.actor) + self.client.community_model = Group + self.community = self.client.create_community(name="USWNT") + self.client.set_target(target=self.community) + self.client.add_members(member_pk_list=[user.pk for user in User.objects.all()]) + self.client.add_role(role_name="forwards") + pinoe = User.objects.get(username="meganrapinoe") + press = User.objects.get(username="christenpress") + heath = User.objects.get(username="tobinheath") + self.client.add_people_to_role(role_name="forwards", people_to_add=[pinoe.pk, press.pk, heath.pk]) + + # add permission & condition to permission + self.permissionClient = PermissionResourceClient(actor=self.actor, target=self.community) + action, self.permission = self.permissionClient.add_permission( + permission_type = Changes.Communities.AddRole, permission_roles=["forwards"]) + self.conditionClient = PermissionConditionalClient(actor=self.actor, target=self.permission) + action, self.condition = self.conditionClient.add_condition(condition_type="approvalcondition", + permission_data=json.dumps({ "approve_roles": ["forwards"], "reject_roles": ["forwards"] })) + + # have person take action that triggers permission/condition + self.client.set_actor(heath) + self.client.add_role(role_name="midfielders") + + def test_approve_implements_action(self): + + # User navigates to action history and approves action + self.login_user("christenpress", "badlands2020") + self.go_to_group("USWNT") + self.browser.find_by_css("#action_history > span > button")[0].scroll_to() + self.browser.find_by_css("#action_history > span > button").first.click() + self.assertTrue(self.browser.is_text_present('tobinheath asked to add role midfielders to USWNT')) + self.browser.find_by_xpath('//*[@id="action_history_table_element"]/tbody/tr[1]/td[7]/button').first.click() + self.assertTrue(self.browser.is_text_present('Please approve or reject this action.')) + self.browser.find_by_css("#btn-radios-1 > label:nth-child(1) > span").first.click() + time.sleep(.25) + self.browser.find_by_id('save_approve_choice').first.click() + time.sleep(.25) + self.assertTrue(self.browser.is_text_present("You have approved tobinheath's action. Nothing further is needed from you.")) + + # Navigate back to action history and check action is implemented + xpath_string = '//*[@id="action_history_modal_' + str(self.community.pk) + '_group___BV_modal_footer_"]/button[2]' + self.browser.find_by_xpath(xpath_string).first.click() + element = self.browser.find_by_css("#action_history_table_element > tbody > tr:nth-child(1) > td:nth-child(4)")[0] + self.assertTrue(element.text, "implemented") + + def test_reject_rejects_action(self): + + # User navigates to action history and approves action + self.login_user("christenpress", "badlands2020") + self.go_to_group("USWNT") + self.browser.find_by_css("#action_history > span > button")[0].scroll_to() + self.browser.find_by_css("#action_history > span > button").first.click() + self.assertTrue(self.browser.is_text_present('tobinheath asked to add role midfielders to USWNT')) + self.browser.find_by_xpath('//*[@id="action_history_table_element"]/tbody/tr[1]/td[7]/button').first.click() + self.assertTrue(self.browser.is_text_present('Please approve or reject this action.')) + self.browser.find_by_css("#btn-radios-1 > label:nth-child(2) > span").first.click() + self.browser.find_by_id('save_approve_choice').first.click() + time.sleep(.25) + self.assertTrue(self.browser.is_text_present("You have rejected tobinheath's action. Nothing further is needed from you.")) + + # Navigate back to action history and check action is implemented + xpath_string = '//*[@id="action_history_modal_' + str(self.community.pk) + '_group___BV_modal_footer_"]/button[2]' + self.browser.find_by_xpath(xpath_string).first.click() + element = self.browser.find_by_css("#action_history_table_element > tbody > tr:nth-child(1) > td:nth-child(4)")[0] + self.assertTrue(element.text, "rejected") + + def test_person_without_permission_to_approve_cant_approve(self): + self.login_user("emilysonnett", "badlands2020") + self.go_to_group("USWNT") + self.browser.find_by_css("#action_history > span > button")[0].scroll_to() + self.browser.find_by_css("#action_history > span > button").first.click() + self.assertTrue(self.browser.is_text_present('tobinheath asked to add role midfielders to USWNT')) + self.browser.find_by_xpath('//*[@id="action_history_table_element"]/tbody/tr[1]/td[7]/button').first.click() + self.assertTrue(self.browser.is_text_present('You do not have permission to approve or reject this action.')) + + +class VotingConditionTestCase(BaseTestCase): + + def setUp(self): + + # create group, add members, add roles, add members to role + self.create_users() + self.actor = User.objects.first() + self.client = CommunityClient(actor=self.actor) + self.client.community_model = Group + self.community = self.client.create_community(name="USWNT") + self.client.set_target(target=self.community) + self.client.add_members(member_pk_list=[user.pk for user in User.objects.all()]) + self.client.add_role(role_name="forwards") + pinoe = User.objects.get(username="meganrapinoe") + press = User.objects.get(username="christenpress") + heath = User.objects.get(username="tobinheath") + self.client.add_people_to_role(role_name="forwards", people_to_add=[pinoe.pk, press.pk, heath.pk]) + + # add permission & condition to permission + self.permissionClient = PermissionResourceClient(actor=self.actor, target=self.community) + action, self.permission = self.permissionClient.add_permission( + permission_type = Changes.Communities.AddRole, permission_roles=["forwards"]) + self.conditionClient = PermissionConditionalClient(actor=self.actor, target=self.permission) + action, self.condition = self.conditionClient.add_condition(condition_type="votecondition", + permission_data=json.dumps({ "vote_roles": ["forwards"] })) + + # have person take action that triggers permission/condition + self.client.set_actor(heath) + self.client.add_role(role_name="midfielders") + + def test_yea_updates_vote_results(self): + + # User navigates to action history and votes yea + self.login_user("christenpress", "badlands2020") + self.go_to_group("USWNT") + self.browser.find_by_css("#action_history > span > button")[0].scroll_to() + self.browser.find_by_css("#action_history > span > button").first.click() + self.assertTrue(self.browser.is_text_present('tobinheath asked to add role midfielders to USWNT')) + self.browser.find_by_xpath('//*[@id="action_history_table_element"]/tbody/tr[1]/td[7]/button').first.click() + self.assertTrue(self.browser.is_text_present('The results so far are 0 yeas and 0 nays with 0 abstentions.')) + self.assertTrue(self.browser.is_text_present('Please cast your vote')) + self.browser.find_by_css("#btn-radios-1 > label:nth-child(1) > span").first.click() + time.sleep(.25) + self.browser.find_by_id('save_vote_choice').first.click() + time.sleep(.25) + time.sleep(2) + self.assertTrue(self.browser.is_text_present('The results so far are 1 yeas and 0 nays with 0 abstentions.')) + self.assertTrue(self.browser.is_text_present("Thank you for voting! No further action from you is needed.")) + + def test_nay_updates_vote_results(self): + + # User navigates to action history and votes nay + self.login_user("christenpress", "badlands2020") + self.go_to_group("USWNT") + self.browser.find_by_css("#action_history > span > button")[0].scroll_to() + self.browser.find_by_css("#action_history > span > button").first.click() + self.assertTrue(self.browser.is_text_present('tobinheath asked to add role midfielders to USWNT')) + self.browser.find_by_xpath('//*[@id="action_history_table_element"]/tbody/tr[1]/td[7]/button').first.click() + self.assertTrue(self.browser.is_text_present('The results so far are 0 yeas and 0 nays with 0 abstentions.')) + self.assertTrue(self.browser.is_text_present('Please cast your vote')) + self.browser.find_by_css("#btn-radios-1 > label:nth-child(2) > span").first.click() + time.sleep(.25) + self.browser.find_by_id('save_vote_choice').first.click() + time.sleep(.25) + self.assertTrue(self.browser.is_text_present('The results so far are 0 yeas and 1 nays with 0 abstentions.')) + self.assertTrue(self.browser.is_text_present("Thank you for voting! No further action from you is needed.")) + + def test_abstain_updates_vote_results(self): + + # User navigates to action history and votes nay + self.login_user("christenpress", "badlands2020") + self.go_to_group("USWNT") + self.browser.find_by_css("#action_history > span > button")[0].scroll_to() + self.browser.find_by_css("#action_history > span > button").first.click() + self.assertTrue(self.browser.is_text_present('tobinheath asked to add role midfielders to USWNT')) + self.browser.find_by_xpath('//*[@id="action_history_table_element"]/tbody/tr[1]/td[7]/button').first.click() + self.assertTrue(self.browser.is_text_present('The results so far are 0 yeas and 0 nays with 0 abstentions.')) + self.assertTrue(self.browser.is_text_present('Please cast your vote')) + self.browser.find_by_css("#btn-radios-1 > label:nth-child(3) > span").first.click() + time.sleep(.25) + self.browser.find_by_id('save_vote_choice').first.click() + time.sleep(.25) + self.assertTrue(self.browser.is_text_present('The results so far are 0 yeas and 0 nays with 1 abstentions.')) + self.assertTrue(self.browser.is_text_present("Thank you for voting! No further action from you is needed.")) + + def test_person_without_permission_to_approve_cant_vote(self): + + self.login_user("emilysonnett", "badlands2020") + self.go_to_group("USWNT") + self.browser.find_by_css("#action_history > span > button")[0].scroll_to() + self.browser.find_by_css("#action_history > span > button").first.click() + self.assertTrue(self.browser.is_text_present('tobinheath asked to add role midfielders to USWNT')) + self.browser.find_by_xpath('//*[@id="action_history_table_element"]/tbody/tr[1]/td[7]/button').first.click() + self.assertTrue(self.browser.is_text_present('You are not eligible to vote.')) + + + # def test_vote_has_passed(self): + # # FIXME: not sure how to do this, given the one hour vote minimum? + # # maybe the vote needs a: [close when X people voted option] + # ... From 753d6e61a9105ffecb647a0f9221fe02dad0ea36 Mon Sep 17 00:00:00 2001 From: Shauna Gordon-McKeon Date: Tue, 12 May 2020 17:31:39 -0400 Subject: [PATCH 4/4] Replace Github Actions test of 3.6 with 3.6.10 it's closer to prod's 3.6.9, and not failing mysteriously --- .github/workflows/django.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index 738832a..d4beece 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -13,7 +13,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [3.6, 3.7, 3.8] + python-version: [3.6.10, 3.7, 3.8] services: postgres: