def test_rserve_skips_existing(self): program = Program.create( name="The Engagement Project", label="ep19", preview_url='foo.com', ) week = util.datelike_to_iso_string(datetime.date.today()) org = Organization.create(name="Organization", captain_id="User_cap", program_id=program.uid) org_to_skip = Organization.create(name="Organization", captain_id="User_cap", program_id=program.uid) Organization.put_multi([org, org_to_skip]) team = Team.create(name="Team", captain_id="User_cap", program_id=program.uid) team_to_skip = Team.create(name="Team", captain_id="User_cap", program_id=program.uid) Team.put_multi([team, team_to_skip]) cl = Classroom.create(name="Classroom", team_id=team.uid, code="foo", contact_id="User_contact") cl_to_skip = Classroom.create(name="Classroom", team_id=team.uid, code="foo", contact_id="User_contact") Classroom.put_multi([cl, cl_to_skip]) Report.put_multi([ Report.create(parent_id=org_to_skip.uid, filename="foo", issue_date=week), Report.create(parent_id=team_to_skip.uid, filename="foo", issue_date=week), Report.create(parent_id=cl_to_skip.uid, filename="foo", issue_date=week), ]) # Skips all the parents who have reports already this week. orgs, teams, classes = cron_rserve.get_report_parents( program, week, False) self.assertEqual(len(orgs), 1) self.assertEqual(len(teams), 1) self.assertEqual(len(classes), 1) # ...unless you force it, then they're all there. orgs, teams, classes = cron_rserve.get_report_parents( program, week, True) self.assertEqual(len(orgs), 2) self.assertEqual(len(teams), 2) self.assertEqual(len(classes), 2)
def create_for_paging(self, n): # Pad numeric names so they sort alphabetically. orgs = [ Organization.create(name=str(x).rjust(2, '0'), program_id=self.program.uid) for x in range(n) ] Organization.put_multi(orgs) super_admin = User.create(name='super', email='*****@*****.**', user_type='super_admin') super_admin.put() return orgs, super_admin
def test_organization_name_update(self): """Updating an organization name should update associated project""" org_name_1 = 'Foo College' org_name_2 = 'Bar College' organization = Organization.create(name=org_name_1) organization.put() project = Project.create(program_label='demo-program', organization_id=organization.uid) project.put() organization = Organization.get_by_id(organization.uid) organization.name = org_name_2 organization.put() project_dict = project.get_by_id(project.uid).to_client_dict() self.assertEqual(project_dict['organization_name'], organization.name)
def test_dashboard_forbidden(self): org = Organization.create( name='Org Foo', program_id=self.program.uid, ) org.put() bad_admin = User.create( name='Bad Admin', email='*****@*****.**', ) bad_admin.put() # 404 self.testapp.get( '/api/organization_dashboards/{}'.format('Organization_dne'), headers=self.login_headers(bad_admin), status=404, ) # 403 self.testapp.get( '/api/organization_dashboards/{}'.format(org.uid), headers=self.login_headers(bad_admin), status=403, )
def joined_cohort(user, project_cohort): """Notify program and super admins.""" # This always happens along with creating a program. pc = project_cohort program_admins = User.get(owned_programs=pc.program_label) super_admins = User.get(user_type='super_admin') organization = Organization.get_by_id(pc.organization_id) program_config = Program.get_config(pc.program_label) cohort_name = program_config['cohorts'][pc.cohort_label]['name'] notes = [] for admin in program_admins + super_admins: note = Notification.create( parent=admin, context_id=pc.uid, subject=u"{org} joined a cohort".format(org=organization.name), body=( u"{org} joined {cohort} in {program}. The organization is " "currently {status}." ).format( org=organization.name, cohort=cohort_name, program=program_config['name'], status=organization.status, ), link='/organizations/{}'.format(organization.short_uid), autodismiss=True, ) notes.append(note) ndb.put_multi(notes)
def post(self, project_id, slug): """A project has been identified as new. Send them a welcome.""" project = Project.get_by_id(project_id) program = Program.get_config(project.program_label) org = Organization.get_by_id(project.organization_id) # The Org liaison is preferred, but is not guaranteed to be set (users # choose their org liaison explicitly as one of the org tasks). Default # to the Project liaison, which is set on creation in # add_program.controller.js@joinProgram org_liaison = User.get_by_id(org.liaison_id) project_liaison = User.get_by_id(project.liaison_id) liaison = org_liaison or project_liaison email = Email.create( to_address=liaison.email, mandrill_template=slug, mandrill_template_content={ 'program_name': program['name'], 'organization_name': org.name, 'liaison_name': liaison.name, 'join_date': util.datelike_to_iso_string(project.created), }, ) email.put()
def test_get_all_projects(self): user = User.create(email="*****@*****.**", user_type='super_admin') user.put() org = Organization.create(name="Org Foo") org.put() project1 = Project.create(organization_id=org.uid, program_label='demo-program') project2 = Project.create(organization_id=org.uid, program_label='demo-program') project1.put() project2.put() query = ''' query GetAllProjects { projects { uid } } ''' response = self.testapp.post_json( '/api/graphql', {'query': query}, headers=login_headers(user.uid), ) received = json.loads(response.body) # No particular order. self.assertIn({'uid': project1.uid}, received['projects']) self.assertIn({'uid': project2.uid}, received['projects'])
def test_remove_fellow_from_organization_success(self): """Admins can remove each other.""" org = Organization.create(name='foo', program_id=self.program.uid) org.put() user = User.create(name='Admin', email='*****@*****.**', user_type='user', owned_organizations=['Organization_foo']) req = User.create(name='Valid Requestor', email='*****@*****.**', user_type='user', owned_organizations=['Organization_foo']) user.put() req.put() # Successful removal. response = self.testapp.put_json( '/api/users/{}'.format(user.uid), {'owned_organizations': []}, headers=self.login_headers(req), ) self.assertEqual(json.loads(response.body)['owned_organizations'], []) # Changed in the db. fetched_user = User.get_by_id(user.uid) self.assertEqual(fetched_user.owned_organizations, []) self.assertEqual(user.user_type, fetched_user.user_type)
def test_update_task(self): """Org admins can't change some tasks.""" org = Organization.create(name='org') org.put() user = User.create(email='*****@*****.**', user_type='user', owned_organizations=[org.uid]) user.put() orig_config = organization_tasks.tasklist_template organization_tasks.tasklist_template = [{ 'tasks': [{ 'label': 'task_foo', 'non_admin_may_edit': False, }] }] task = Task.create('task_foo', 1, 'checkpoint_foo', parent=org) task.put() # Org admin shouldn't be able to mark the task as complete, even though # they own the task. task.status = 'complete' response = self.testapp.put_json('/api/tasks/{}'.format(task.uid), task.to_client_dict(), headers=login_headers(user.uid), status=403) organization_tasks.tasklist_template = orig_config
def test_super_admin_changes_organization_task(self): """Org admins get notifications about activity on their org tasks.""" org = Organization.create(name='Foo Org') admin1 = User.create(email='*****@*****.**', user_type='user', owned_organizations=[org.uid]) admin2 = User.create(email='*****@*****.**', user_type='user', owned_organizations=[org.uid]) admin3 = User.create(email='*****@*****.**', user_type='user') admin1.put() admin2.put() admin3.put() task = org.tasklist.tasks[0] sup = User.create(email='*****@*****.**', user_type='super_admin', name="Susan Super") notifier.changed_organization_task(sup, org, task) # Each related org admin should get a notification. self.assertEqual(len(admin1.notifications()), 1) self.assertEqual(len(admin2.notifications()), 1) self.assertEqual(len(admin3.notifications()), 0)
def test_update_checkpoint_waiting(self): user = User.create(email='*****@*****.**', user_type='super_admin') user.put() org = Organization.create(name='Foo Org') org.put() # Work with the first checkpoint (generally there's only one anyway). c = org.tasklist.checkpoints[0] # Our org tasklist doesn't change much; break down its structure. tasks = [t for t in org.tasklist.tasks if t.checkpoint_id == c.uid] invite_task, liaison_task, approve_task = tasks # Marking the first two complete should change the checkpoint to # waiting, since the last is for super admins only. for task in (invite_task, liaison_task): task.status = 'complete' self.testapp.put_json( '/api/tasks/{}'.format(task.uid), task.to_client_dict(), headers=login_headers(user.uid), ) fetched_c = Checkpoint.get_by_id(c.uid) self.assertEqual(fetched_c.status, 'waiting')
def test_program_admin_changes_project_task_with_liaison(self): """Liaison notified.""" org = Organization.create(name='Foo Org') admin1 = User.create(email='*****@*****.**', user_type='user', owned_organizations=[org.uid]) admin2 = User.create(email='*****@*****.**', user_type='user', owned_organizations=[org.uid]) admin1.put() admin2.put() task = org.tasklist.tasks[0] prog = User.create(email='*****@*****.**', user_type='program_admin', name='Petrarch Prog') project = Project.create(program_label='demo-program', organization_id=org.uid, liaison_id=admin1.uid) notifier.changed_project_task(prog, project, task) # Each related org admin should get a notification. self.assertEqual(len(admin1.notifications()), 1) self.assertEqual(len(admin2.notifications()), 0)
def create_org_tasks(self, user, cohort_date=datetime.datetime.today()): org = Organization.create(name='Org Foo') org.put() org.tasklist.open(user) return (org, Task.get(ancestor=org, order='ordinal'))
def test_user_changes_project_task(self): """All program admins are notified.""" prog1 = User.create(email='*****@*****.**', user_type='program_admin', owned_programs=['demo-program']) prog2 = User.create(email='*****@*****.**', user_type='super_admin', owned_programs=['demo-program']) prog3 = User.create(email='*****@*****.**', user_type='program_admin', owned_programs=[]) prog1.put() prog2.put() prog3.put() org = Organization.create(name='Foo Org') org.put() admin = User.create(email='*****@*****.**', user_type='user', name='Addi Admin') project = Project.create(program_label='demo-program', organization_id=org.uid) task = project.tasklist.tasks[0] notifier.changed_project_task(admin, project, task) # Each related program admin should get a notification. self.assertEqual(len(prog1.notifications()), 1) self.assertEqual(len(prog2.notifications()), 1) self.assertEqual(len(prog3.notifications()), 0)
def test_user_changes_project_task_with_account_manager(self): """Account manager notified.""" acct_mgr = User.create(email='*****@*****.**', user_type='program_admin', owned_programs=['demo-program']) other_prog = User.create(email='*****@*****.**', user_type='program_admin', owned_programs=['demo-program']) acct_mgr.put() other_prog.put() org = Organization.create(name='Foo Org') org.put() admin = User.create(email='*****@*****.**', user_type='user', name='Addi Admin') project = Project.create(program_label='demo-program', organization_id=org.uid, account_manager_id=acct_mgr.uid) task = project.tasklist.tasks[0] notifier.changed_project_task(admin, project, task) # Each account manager gets a notification, other related program # admins don't. self.assertEqual(len(acct_mgr.notifications()), 1) self.assertEqual(len(other_prog.notifications()), 0)
def create_for_permission(self): org = Organization.create( name="Foo Org", program_id=self.ep_program.uid) org.put() team = Team.create(name="Foo Team", program_id=self.ep_program.uid, captain_id="User_cap", organization_ids=[org.uid]) team.put() network = Network.create( name="Foo Network", program_id=self.ep_program.uid, association_ids=[org.uid] ) meta_network = Network.create( name="Foo Network", program_id=self.ep_program.uid, association_ids=[network.uid] ) Network.put_multi([network, meta_network]) net_admin = User.create(email="*****@*****.**", owned_networks=[network.uid]) meta_admin = User.create(email="*****@*****.**", owned_networks=[meta_network.uid]) User.put_multi([net_admin, meta_admin]) return (meta_admin, net_admin, meta_network, network, org, team)
def test_create_project(self): """All program owners notified about new projects.""" prog1 = User.create(email='*****@*****.**', user_type='program_admin', owned_programs=['demo-program']) prog2 = User.create(email='*****@*****.**', user_type='super_admin', owned_programs=['demo-program']) prog3 = User.create(email='*****@*****.**', user_type='program_admin', owned_programs=[]) prog1.put() prog2.put() prog3.put() org = Organization.create(name='Foo Org') org.put() admin = User.create(email='*****@*****.**', user_type='user', name='Addi Admin') project = Project.create(program_label='demo-program', organization_id=org.uid) notifier.created_project(admin, project) # Each super admin should get a notification. self.assertEqual(len(prog1.notifications()), 1) self.assertEqual(len(prog2.notifications()), 1) self.assertEqual(len(prog3.notifications()), 0)
def get(self, program_id_or_label): user = self.get_current_user() if not user.super_admin: return self.http_forbidden() program = Program.get_by_id(program_id_or_label) if not program: program = Program.get_by_label(program_id_or_label) if not program: return self.http_not_found() search_str = self.get_param('q', unicode, None) if not search_str: return self.write([]) if search_str.startswith('user:'******'t have team r orgs = Organization.query_by_name(search_str, program.uid) teams = Team.query_by_name(search_str, program.uid) classrooms = Classroom.query_by_name(search_str, program.uid) users = User.query_by_name_or_email(search_str) self.write({ 'organizations': [e.to_client_dict() for e in orgs], 'teams': [e.to_client_dict() for e in teams], 'classrooms': [e.to_client_dict() for e in classrooms], 'users': [e.to_client_dict() for e in users], })
def test_redundant(self): """Multiple undismissed updates about the same task are ignored.""" org = Organization.create(name='Foo Org') admin1 = User.create(email='*****@*****.**', user_type='user', owned_organizations=[org.uid]) admin1.put() task = org.tasklist.tasks[0] sup = User.create(email='*****@*****.**', user_type='super_admin', name="Susan Super") # Second notification should not be saved. notifier.changed_organization_task(sup, org, task) notifier.changed_organization_task(sup, org, task) notes = admin1.notifications() self.assertEqual(len(notes), 1) # Two will arrive if one is dismissed. notes[0].dismissed = True notes[0].put() notifier.changed_organization_task(sup, org, task) self.assertEqual(len(admin1.notifications()), 2)
def test_add_org_not_found(self): captain = User.create(name='captain', email='*****@*****.**') team = Team.create(name='Team Foo', captain_id=captain.uid, program_id=self.demo_program.uid) org = Organization.create(name='Org Bar', program_id='Program_other') captain.owned_teams = [team.uid] team.put() org.put() captain.put() # Forbidden b/c org doesn't exist response = self.testapp.post_json( '/api/teams/{}/organizations'.format(team.uid), {'organization_code': 'dne'}, headers=self.login_headers(captain), status=400, ) # Forbidden b/c org isn't on the same program. response = self.testapp.post_json( '/api/teams/{}/organizations'.format(team.uid), {'organization_code': org.code}, headers=self.login_headers(captain), status=400, )
def set_up(self): # Let ConsistencyTestCase set up the datastore testing stub. super(TestApiInvitations, self).set_up() application = webapp2.WSGIApplication(api_routes, config={ 'webapp2_extras.sessions': { 'secret_key': self.cookie_key } }, debug=True) self.testapp = webtest.TestApp(application) with mysql_connection.connect() as sql: sql.reset({ 'organization': Organization.get_table_definition(), 'program': Program.get_table_definition(), 'team': Team.get_table_definition(), 'user': User.get_table_definition(), }) self.program = Program.create( name="Engagement Project", label='ep18', min_cycles=3, active=True, preview_url='foo.com', ) self.program.put()
def test_remove_from_organization_forbidden(self): """Normally you can't modify someone else's membership.""" org = Organization.create(name='foo', program_id=self.program.uid) org.put() user = User.create(name='Admin', email='*****@*****.**', user_type='user', owned_organizations=['Organization_foo']) req = User.create(name='Invalid Requestor', email='*****@*****.**', user_type='user') user.put() req.put() response = self.testapp.put_json( '/api/users/{}'.format(user.uid), {'owned_organizations': []}, headers=self.login_headers(req), status=403, ) # Not changed in the db. fetched_user = User.get_by_id(user.uid) self.assertEqual(user.user_type, fetched_user.user_type) self.assertEqual(user.owned_organizations, fetched_user.owned_organizations)
def test_close_tasklist(self): """Should delete all associated task reminders.""" user1 = User.create(email='*****@*****.**', user_type='user') user2 = User.create(email='*****@*****.**', user_type='user') org = Organization.create(name="Foo Org", liaison_id=user1.uid) user1.owned_organizations = [org.uid] user2.owned_organizations = [org.uid] user1.put() user2.put() org.put() response = self.testapp.post_json( '/api/projects', { 'organization_id': org.uid, 'program_label': 'demo-program', 'liaison_id': user1.uid }, headers=login_headers(user1.uid), ) project_dict = json.loads(response.body) project = Project.get_by_id(project_dict['uid']) # Simulate time passing and the datastore reaching consistency. trs1 = TaskReminder.get(ancestor=user1) trs2 = TaskReminder.get(ancestor=user2) Tasklist(project).close() self.assertEqual(len(TaskReminder.get(ancestor=user1)), 0) self.assertEqual(len(TaskReminder.get(ancestor=user2)), 0)
def test_create_project(self): """Creating a project through the api makes a TR for all org owners.""" user1 = User.create(email='*****@*****.**', user_type='user') user2 = User.create(email='*****@*****.**', user_type='user') org = Organization.create(name="Foo Org", liaison_id=user1.uid) user1.owned_organizations = [org.uid] user2.owned_organizations = [org.uid] user1.put() user2.put() org.put() # Bring org 2 into consistency, assuming they've been part of the org # for some time. Don't bring org 1 into consistency, to simulate # joining an org and creating a project within a short timespan, which # we expect. user2.key.get() response = self.testapp.post_json( '/api/projects', { 'organization_id': org.uid, 'program_label': 'demo-program', 'liaison_id': user1.uid }, headers=login_headers(user1.uid), ) project_dict = json.loads(response.body) trs1 = TaskReminder.get(ancestor=user1) trs2 = TaskReminder.get(ancestor=user2) self.assertEqual(len(trs1), 1) self.assertEqual(len(trs2), 1) self.assertEqual(trs1[0].context_id, project_dict['uid'])
def test_get_single_project(self): user = User.create(email="*****@*****.**", user_type='super_admin') user.put() org = Organization.create(name="Org Foo") org.put() project = Project.create( organization_id=org.uid, program_label='demo-program', account_manager_id='User_001', liaison_id='User_002', priority=True, deidentification_method='total', loa_notes="Some stuff happened.", last_active=datetime.datetime.now(), ) project.put() query = ''' query GetSingleProject($uid: String!) { project(uid: $uid) { account_manager_id created deidentification_method deleted last_active liaison_id loa_notes modified organization_id organization_name organization_status priority program_description program_label program_name short_uid uid } } ''' response = self.testapp.post_json( '/api/graphql', # See http://graphql.org/learn/serving-over-http/#post-request { 'query': query, 'variables': { 'uid': project.uid }, }, headers=login_headers(user.uid), ) self.assertEqual( response.body, json.dumps({'project': project.to_client_dict()}), )
def test_join_org(self): """Joining an org makes task reminders for all contained tasklists.""" program_label = 'demo-program' cohort_label = 'demo-cohort' # Guarantee the dates will work by mocking the cohort config. cohort_config = { 'label': cohort_label, 'name': 'Demo Cohort', 'open_date': str(datetime.date.today()), 'close_date': str(datetime.date.today()), } Program.mock_program_config( program_label, {'cohorts': { cohort_label: cohort_config }}, ) program = Program.get_config(program_label) tasklist_template = program['surveys'][0]['survey_tasklist_template'] owner = User.create(email='*****@*****.**', user_type='user') org = Organization.create(name="Foo Org", liaison_id=owner.uid) owner.owned_organizations = [org.uid] project = Project.create(program_label=program_label, organization_id=org.uid) survey = Survey.create(tasklist_template, project_id=project.uid, organization_id=org.uid, program_label=program_label, ordinal=1, cohort_label=cohort_label) owner.put() org.put() project.put() survey.put() # The assumption here is the org and its contents are long-standing, # so force consistency with all. org.key.get() project.key.get() survey.key.get() joiner = User.create(email='*****@*****.**', user_type='user') joiner.put() self.testapp.post_json( '/api/users/{}/organizations'.format(joiner.uid), org.to_client_dict(), headers=login_headers(owner.uid), ) # One TaskReminder for each of: org tasklist, project tasklist, survey # tasklist. self.assertEqual(len(TaskReminder.get(ancestor=joiner)), 3) return (org, project, survey, owner, joiner)
def test_organization_name(self): """Updating a project should update organization name""" organization = Organization.create(name='Foo College') organization.put() project = Project.create(program_label='demo-program', organization_id=organization.uid) project.put() self.assertEqual(project.to_client_dict()['organization_name'], organization.name)
def test_post_empty_report(self): """RServe posts "empty" reports to note why it hasn't produced a visible report. Test that the API accepts these for each kind of parent. """ rserve_user = User.create( id='rserve', email='*****@*****.**', user_type='super_admin', ) rserve_user.put() org = Organization.create(name='Organization', captain_id='User_cap', program_id=self.program.uid) org.put() team = Team.create( name='Team Foo', captain_id='User_cap', organization_ids=[org.uid], program_id=self.program.uid, ) team.put() classroom = Classroom.create(name='Class foo', team_id=team.uid, code='trout viper', contact_id='User_contact') classroom.put() url = '/api/reports' report_date = datetime.date.today().strftime('%Y-%m-%d') self.testapp.post_json( url, dict(self.empty_report_params(report_date, org.uid), organization_id=org.uid), headers=self.login_headers(rserve_user), ) self.testapp.post_json( url, dict(self.empty_report_params(report_date, team.uid), team_id=team.uid), headers=self.login_headers(rserve_user), ) self.testapp.post_json( url, dict(self.empty_report_params(report_date, classroom.uid), team_id=team.uid, classroom_id=classroom.uid), headers=self.login_headers(rserve_user), ) reports = Report.get() self.assertEqual(len(reports), 3) self.assertEqual(all(r.template == 'empty' for r in reports), True)
def test_creating_user_caches_rel_count(self): org = Organization.create(name="Organization", captain_id="User_cap", program_id=self.ep_program.uid) user = User.create(email="*****@*****.**", owned_organizations=[org.uid]) org.put() # Populate memcache with the org's original value of 0 users. cached = Organization.update_cached_properties(org.uid) # This should trigger a memcache update. user.put() cached = memcache.get(util.cached_properties_key(org.uid)) self.assertEqual(cached['num_users'], 1) # Should see the same count on object. self.assertEqual(org.to_client_dict()['num_users'], 1) return (org, user)
def test_update_checkpoint(self): """Can't change a checkpoint to an invalid status, based on tasks.""" # One checkpoint with one tasks. orig_config = organization_tasks.tasklist_template tasklist_tmpl = [ { 'name': "Checkpoint Foo", 'label': 'checkpoint label foo', 'tasks': [{ 'label': 'foo', 'non_admin_may_edit': True }, { 'label': 'bar', 'non_admin_may_edit': True }] }, ] organization_tasks.tasklist_template = tasklist_tmpl user = User.create(email='*****@*****.**', user_type='user') org = Organization.create(name='org', tasklist_template=tasklist_tmpl, liaison_id=user.uid) user.owned_organizations = [org.uid] org.put() user.put() # Make one task complete, the other stays incomplete by default. org.tasklist.tasks[0].status = 'complete' org.tasklist.tasks[0].put() # This is an unsaved, in-memory dict, and thus doesn't contain defaults # set in the table definition. ckpt = org.tasklist.checkpoints[0] # Fetch the checkpoint by id to pick up those defaults. ckpt = Checkpoint.get_by_id(ckpt.uid) # The derived checkpoint status is 'incomplete'. Setting it this way # should work. response = self.testapp.put_json( '/api/checkpoints/{}'.format(ckpt.uid), ckpt.to_client_dict(), headers=login_headers(user.uid), ) # Shouldn't be able to set the checkpoint to any other value. ckpt.status = 'foo' response = self.testapp.put_json( '/api/checkpoints/{}'.format(ckpt.uid), ckpt.to_client_dict(), headers=login_headers(user.uid), status=500, ) organization_tasks.tasklist_template = orig_config