def test_perform_action(self): print("\nTesting perform_action from metagov\n") # 1) Create Policy that performs a metagov action policy = PlatformPolicy() policy.community = self.community policy.filter = "return True" policy.initialize = "debug('help!')" policy.check = """parameters = {"low": 4, "high": 5} response = metagov.perform_action('randomness.random-int', parameters) if response and response.get('value') == 4: return PASSED return FAILED""" policy.notify = "pass" policy.success = "pass" policy.fail = "pass" policy.description = "test" policy.name = "test policy" policy.save() # 2) Save an action to trigger the policy execution action = SlackPinMessage() action.initiator = self.user action.community = self.community action.save() self.assertEqual(action.proposal.status, "passed") # Check that evaluation debug log was generated from django_db_logger.models import EvaluationLog self.assertEqual( EvaluationLog.objects.filter(community=policy.community, msg__contains="help!").count(), 1)
def test_non_community_origin_actions(self): """Actions that didnt originate on the community platform should executed on 'pass'""" first_policy = Policy.objects.create( **all_actions_pass_policy, kind=Policy.PLATFORM, community=self.slack_community, name="policy that passes", ) # engine should call "execute" for non-community actions on pass action = SlackPinMessage(initiator=self.user, community=self.slack_community, community_origin=False) # mock the execute function action.execute = lambda: action.data.set("was_executed", True) action.save() self.assertEqual(action.proposal.status, "passed") self.assertEqual(action.data.get("was_executed"), True) # engine should not call "execute" for community actions on pass action = SlackPinMessage(initiator=self.user, community=self.slack_community, community_origin=True) # mock the execute function action.execute = lambda: action.data.set("was_executed", True) action.save() self.assertEqual(action.proposal.status, "passed") self.assertEqual(action.data.get("was_executed"), None)
def test_close_process(self): """Integration-test metagov process with randomness plugin""" # 1) Create Policy and PlatformAction policy_code = { **all_actions_pass_policy, "initialize": """ metagov.start_process("randomness.delayed-stochastic-vote", {"options": ["one", "two", "three"], "delay": 1}) """, "check": """ result = metagov.close_process() action.data.set('status', result.status) action.data.set('outcome', result.outcome) if result is None: return #still processing if result.errors: return FAILED if result.outcome: return PASSED if result.outcome.get('winner') else FAILED return FAILED """, } policy = Policy( **policy_code, kind=Policy.PLATFORM, community=self.slack_community, description="test", name="test policy", ) policy.save() action = SlackPinMessage() action.initiator = self.user action.community = self.slack_community action.community_origin = True # 2) Save action to trigger policy execution action.save() process = MetagovProcess.objects.filter(action=action, policy=policy).first() self.assertIsNotNone(process) self.assertEqual(action.proposal.status, "passed") self.assertEqual(action.data.get("status"), "completed") self.assertIsNotNone(action.data.get("outcome").get("winner"))
def test_close_process(self): """Integration-test metagov process with randomness plugin. Process is closed from within the policy.""" # 1) Create Policy and GovernableAction policy_code = { **TestUtils.ALL_ACTIONS_PASS, "initialize": """ metagov.start_process("randomness.delayed-stochastic-vote", options=["one", "two", "three"], delay=1) """, "check": """ result = metagov.close_process() proposal.data.set('reached_close', True) if result is None: return #still processing if result.errors: return FAILED if result.outcome: return PASSED if result.outcome.get('winner') else FAILED return FAILED """, } policy = Policy( **policy_code, kind=Policy.PLATFORM, community=self.community, ) policy.save() action = SlackPinMessage() action.initiator = self.user action.community = self.slack_community action.community_origin = True # 2) Save action to trigger policy execution action.save() proposal = Proposal.objects.get(action=action, policy=policy) self.assertEqual(proposal.status, Proposal.PASSED) self.assertTrue(proposal.is_vote_closed) self.assertEqual(proposal.data.get("reached_close"), True) # assert that the process outcome was saved metagov = Metagov(proposal) process_data = metagov.get_process() self.assertEqual(process_data.status, "completed") self.assertIsNotNone(process_data.outcome.get("winner"))
def test_perform_action(self): """Integration-test metagov.perform_action function with randomness plugin""" # 1) Create Policy that performs a metagov action policy = Policy(kind=Policy.PLATFORM) policy.community = self.community policy.filter = "return True" policy.initialize = "logger.debug('help!')" policy.check = """ response = metagov.perform_action('randomness.random-int', low=4, high=5) if response and response.get('value') == 4: return PASSED return FAILED""" policy.notify = "pass" policy.success = "pass" policy.fail = "pass" policy.description = "test" policy.name = "test policy" policy.save() # 2) Save an action to trigger the policy execution action = SlackPinMessage() action.initiator = self.user action.community = self.slack_community action.community_origin = True action.revert = lambda: None action.execute = lambda: None action.save() proposal = Proposal.objects.get(action=action, policy=policy) self.assertEqual(proposal.status, Proposal.PASSED) # Check that proposal debug log was generated from django_db_logger.models import EvaluationLog self.assertEqual( EvaluationLog.objects.filter(proposal=proposal, msg__contains="help!").count(), 1)
def test_loomio_vote(self): print("\nTesting external process\n") # 1) Create Policy and PlatformAction policy = PlatformPolicy() policy.community = self.community policy.filter = "return True" policy.initialize = """ result = metagov.start_process("loomio.poll", {"title": "[test] policykit poll", "options": ["one", "two", "three"], "closing_at": "2021-05-11"}) poll_url = result.outcome.get('poll_url') action.data.set('poll_url', poll_url) """ policy.notify = "pass" policy.check = """ result = metagov.get_process() if result.status != "completed": return None #still processing if result.errors: return FAILED if result.outcome: return PASSED if result.outcome.get('value') == 27 else FAILED return FAILED """ policy.success = "pass" policy.fail = "pass" policy.description = "test" policy.name = "test policy" policy.save() action = SlackPinMessage() action.initiator = self.user action.community = self.community # 2) Save action to trigger execution of check() and notify() action.save() process = MetagovProcess.objects.filter(action=action, policy=policy).first() self.assertIsNotNone(process) self.assertEqual(process.data.status, "pending") self.assertTrue("https://www.loomio.org/p/" in process.data.outcome.get("poll_url")) self.assertEqual(action.proposal.status, "proposed") self.assertTrue( "https://www.loomio.org/p/" in action.data.get("poll_url"))
class ExecCodeTests(TestCase): def setUp(self): self.slack_community, self.user = TestUtils.create_slack_community_and_user() self.policy = Policy.objects.create( **TestUtils.ALL_ACTIONS_PROPOSED, kind=Policy.PLATFORM, community=self.slack_community.community, ) self.action = SlackPinMessage(initiator=self.user, community=self.slack_community, community_origin=True) self.action.revert = lambda: None self.action.execute = lambda: None self.action.save() self.proposal = Proposal.objects.create(action=self.action, policy=self.policy) def test_return_status(self): """Test the ability to return status""" ctx = EvaluationContext(self.proposal) self.assertEqual(exec_code_block("return PASSED", ctx), "passed") self.assertEqual(exec_code_block("return FAILED", ctx), "failed") self.assertEqual(exec_code_block("return PROPOSED", ctx), "proposed") def test_scope(self): """Test that all the EvaluationContext attributes are in scope""" ctx = EvaluationContext(self.proposal) self.assertRaises(PolicyCodeError, exec_code_block, "return variable_doesnt_exist", ctx) self.assertRaises(PolicyCodeError, exec_code_block, "return discord.team_id", ctx) self.assertRaises(PolicyCodeError, exec_code_block, "import os", ctx) self.assertEqual(exec_code_block("return slack.team_id", ctx), "ABC") # these shouldn't raise any exceptions exec_code_block("return action.id", ctx) exec_code_block("return policy.id", ctx) exec_code_block("return proposal.proposal_time", ctx) exec_code_block("return proposal.vote_url", ctx) exec_code_block("return datetime.datetime.now()", ctx) self.assertEqual(exec_code_block("return math.ceil(0.9)", ctx), 1) def test_syntax_errors(self): """syntax errors and other errors show correct line numbers""" ctx = EvaluationContext(self.proposal) with self.assertRaises(PolicyCodeError) as cm: exec_code_block("return variable_doesnt_exist", ctx) self.assertTrue("NameError at line 1" in str(cm.exception)) with self.assertRaises(PolicyCodeError) as cm: exec_code_block("foo = 10\nimport something\nreturn True", ctx) self.assertTrue("SyntaxError at line 2" in str(cm.exception)) def test_logger(self): """Test evaluation logger""" ctx = EvaluationContext(self.proposal) exec_code_block("logger.debug('hello')", ctx) self.assertEqual(EvaluationLog.objects.filter(proposal=self.proposal, msg__contains="hello").count(), 1) exec_code_block("logger.error('world')", ctx) self.assertEqual(EvaluationLog.objects.filter(proposal=self.proposal, msg__contains="world").count(), 1)
def action(request): json_data = json.loads(request.body) logger.info('RECEIVED ACTION') logger.info(json_data) action_type = json_data.get('type') if action_type == "url_verification": challenge = json_data.get('challenge') return HttpResponse(challenge) elif action_type == "event_callback": event = json_data.get('event') team_id = json_data.get('team_id') community = SlackCommunity.objects.get(team_id=team_id) admin_user = SlackUser.objects.filter(is_community_admin=True)[0] new_api_action = None policy_kit_action = False if event.get('type') == "channel_rename": if not is_policykit_action(community, event['channel']['name'], 'name', SlackRenameConversation.ACTION): new_api_action = SlackRenameConversation() new_api_action.community = community new_api_action.name = event['channel']['name'] new_api_action.channel = event['channel']['id'] u,_ = SlackUser.objects.get_or_create(username=event['user'], community=community) new_api_action.initiator = u prev_names = new_api_action.get_channel_info() new_api_action.prev_name = prev_names[0] elif event.get('type') == 'message' and event.get('subtype') == None: if not is_policykit_action(community, event['text'], 'text', SlackPostMessage.ACTION): new_api_action = SlackPostMessage() new_api_action.community = community new_api_action.text = event['text'] new_api_action.channel = event['channel'] new_api_action.time_stamp = event['ts'] u,_ = SlackUser.objects.get_or_create(username=event['user'], community=community) new_api_action.initiator = u elif event.get('type') == "member_joined_channel": if not is_policykit_action(community, event['channel'], 'channel', SlackJoinConversation.ACTION): new_api_action = SlackJoinConversation() new_api_action.community = community if event.get('inviter'): u,_ = SlackUser.objects.get_or_create(username=event['inviter'], community=community) new_api_action.initiator = u else: u,_ = SlackUser.objects.get_or_create(username=event['user'], community=community) new_api_action.initiator = u new_api_action.users = event.get('user') new_api_action.channel = event['channel'] elif event.get('type') == 'pin_added': if not is_policykit_action(community, event['channel_id'], 'channel', SlackPinMessage.ACTION): new_api_action = SlackPinMessage() new_api_action.community = community u,_ = SlackUser.objects.get_or_create(username=event['user'], community=community) new_api_action.initiator = u new_api_action.channel = event['channel_id'] new_api_action.timestamp = event['item']['message']['ts'] if new_api_action.initiator.has_perm('slackintegration.add_' + new_api_action.action_codename): if new_api_action and not policy_kit_action: #if they have execute permission, skip all policies if new_api_action.initiator.has_perm('slackintegration.can_execute_' + new_api_action.action_codename): new_api_action.execute() else: for policy in PlatformPolicy.objects.filter(community=new_api_action.community): if filter_policy(policy, new_api_action): if not new_api_action.pk: new_api_action.community_origin = True new_api_action.is_bundled = False new_api_action.save() initialize_policy(policy, new_api_action) cond_result = check_policy(policy, new_api_action) if cond_result == Proposal.PROPOSED or cond_result == Proposal.FAILED: new_api_action.revert() else: p = Proposal.objects.create(status=Proposal.FAILED, author=new_api_action.initiator) new_api_action.proposal = p if event.get('type') == 'reaction_added': ts = event['item']['ts'] action = None action_res = PlatformAction.objects.filter(community_post=ts) if action_res.exists(): action = action_res[0] if event['reaction'] == '+1' or event['reaction'] == '-1': if event['reaction'] == '+1': value = True elif event['reaction'] == '-1': value = False user,_ = SlackUser.objects.get_or_create(username=event['user'], community=action.community) uv = BooleanVote.objects.filter(proposal=action.proposal, user=user) if uv.exists(): uv = uv[0] uv.boolean_value = value uv.save() else: uv = BooleanVote.objects.create(proposal=action.proposal, user=user, boolean_value=value) if action == None: action_res = PlatformActionBundle.objects.filter(community_post=ts) if action_res.exists(): action = action_res[0] bundled_actions = list(action.bundled_actions.all()) if event['reaction'] in NUMBERS_TEXT.keys(): num = NUMBERS_TEXT[event['reaction']] voted_action = bundled_actions[num] user,_ = SlackUser.objects.get_or_create(username=event['user'], community=voted_action.community) uv = NumberVote.objects.filter(proposal=voted_action.proposal, user=user) if uv.exists(): uv = uv[0] uv.number_value = 1 uv.save() else: uv = NumberVote.objects.create(proposal=voted_action.proposal, user=user, number_value=1) return HttpResponse("")
def slack_event_to_platform_action(community, event_type, data, initiator): new_api_action = None initiator = initiator.get("user_id") if not initiator: # logger.debug(f"{event_type} event does not have an initiating user ID, skipping") return from integrations.slack.models import ( SlackJoinConversation, SlackPinMessage, SlackPostMessage, SlackRenameConversation, SlackUser, ) event = data if event_type == "message" and event.get("subtype") == "channel_name": if not is_policykit_action(community, event["name"], "name", SlackRenameConversation.ACTION): new_api_action = SlackRenameConversation( community=community, name=event["name"], channel=event["channel"], previous_name=event["old_name"]) u, _ = SlackUser.objects.get_or_create(username=initiator, community=community) new_api_action.initiator = u elif event_type == "message" and event.get("subtype") == None: if not is_policykit_action(community, event["text"], "text", SlackPostMessage.ACTION): new_api_action = SlackPostMessage() new_api_action.community = community new_api_action.text = event["text"] new_api_action.channel = event["channel"] new_api_action.timestamp = event["ts"] u, _ = SlackUser.objects.get_or_create(username=initiator, community=community) new_api_action.initiator = u elif event_type == "member_joined_channel": if not is_policykit_action(community, event["channel"], "channel", SlackJoinConversation.ACTION): new_api_action = SlackJoinConversation() new_api_action.community = community if event.get("inviter"): u, _ = SlackUser.objects.get_or_create( username=event["inviter"], community=community) new_api_action.initiator = u else: u, _ = SlackUser.objects.get_or_create(username=initiator, community=community) new_api_action.initiator = u new_api_action.users = initiator new_api_action.channel = event["channel"] elif event_type == "pin_added": if not is_policykit_action(community, event["channel_id"], "channel", SlackPinMessage.ACTION): new_api_action = SlackPinMessage() new_api_action.community = community u, _ = SlackUser.objects.get_or_create(username=initiator, community=community) new_api_action.initiator = u new_api_action.channel = event["channel_id"] new_api_action.timestamp = event["item"]["message"]["ts"] return new_api_action
def new_slackpinmessage(self, initiator=None, community_origin=False): """helper for creating a new platform action""" return SlackPinMessage(initiator=initiator or self.user, community=self.slack_community, community_origin=community_origin)
def test_outcome_endpoint(self): """Integration-test metagov process is updated via the outcome receiver endpoint in PolicyKit""" # 1) Create Policy and GovernableAction policy_code = { **TestUtils.ALL_ACTIONS_PASS, "initialize": """ metagov.start_process("randomness.delayed-stochastic-vote", options=["one", "two", "three"], delay=1) """, "check": """ result = metagov.get_process() proposal.data.set('process_status', result.status) proposal.data.set('is_vote_closed', proposal.is_vote_closed) if result is None or result.status == "pending": return None #still processing if result.errors: return FAILED if result.outcome: return PASSED if result.outcome.get('winner') else FAILED return FAILED """, } policy = Policy( **policy_code, kind=Policy.PLATFORM, community=self.community, ) policy.save() action = SlackPinMessage() action.initiator = self.user action.community = self.slack_community action.community_origin = True action.revert = lambda: None action.execute = lambda: None # 2) Save action to trigger policy execution action.save() proposal = Proposal.objects.get(action=action, policy=policy) self.assertEqual(proposal.status, Proposal.PROPOSED) self.assertEqual(proposal.data.get("process_status"), "pending") self.assertFalse(proposal.data.get("is_vote_closed")) self.assertFalse(proposal.is_vote_closed) # 2) Close the process, which should emit a signal for PolicyKit to handle proposal.governance_process.update() proposal.governance_process.proxy.close() # re-run proposal using celery task function consider_proposed_actions() proposal.refresh_from_db() self.assertEqual(proposal.status, Proposal.PASSED) metagov = Metagov(proposal) process_data = metagov.get_process() self.assertEqual(process_data.status, "completed") self.assertIsNotNone(process_data.outcome.get("winner"))