def test_redaction_private_user_level(self): team_id = self.response_params['team_id'] user = User.create(email='*****@*****.**') own_params = dict( self.response_params, type=Response.USER_LEVEL_SYMBOL, private=True, user_id=user.uid, ) own_private_response = Response.create(**own_params) own_private_response.put() other_params = dict( self.response_params, type=Response.USER_LEVEL_SYMBOL, private=True, user_id='User_other', ) other_private_response = Response.create(**other_params) other_private_response.put() responses = Response.get_for_teams(user, [team_id]) self.assertEqual(len(responses), 2) own_fetched = next(r for r in responses if r.user_id == user.uid) other_fetched = next(r for r in responses if r.user_id != user.uid) # Own response is not redacted, despite being private. self.assertGreater(len(own_fetched.body), 0) # Other's private response is redacted. self.assertEqual(len(other_fetched.body), 0)
def test_task_creates_backup_for_user(self): user_response_params = dict( self.response_params, type=Response.USER_LEVEL_SYMBOL, private=True, user_id='User_foo', ) r = Response.create(**user_response_params) r.put() payload = json.dumps( r.to_dict(), default=util.json_dumps_default, ) self.testapp.post( '/task/backup_response', payload, headers={'Content-Type': 'application/json'}, ) with mysql_connection.connect() as sql: rows = sql.select_star_where(ResponseBackup.table) backup = rows[0] backup['body'] = json.loads(backup['body']) expected = dict(r.to_dict(), backup_id=1), self.assertEqual(len(rows), 1) self.assertEqual( rows[0], dict(r.to_dict(), backup_id=1), )
def create_for_dashboard(self, org, x=0): x_label = str(x).rjust(2, '0') team = Team.create( name='Team {}'.format(x_label), captain_id='User_captain_{}'.format(x_label), organization_ids=[org.uid], program_id=self.program.uid, ) user = User.create(name='User {}'.format(x_label), email='foo.{}@bar.com'.format(x_label), owned_teams=[team.uid]) cycle = Cycle.create( team_id=team.uid, ordinal=1, start_date=datetime.date.today() - datetime.timedelta(days=1), end_date=datetime.date.today() + datetime.timedelta(days=1), ) response = Response.create( type=Response.USER_LEVEL_SYMBOL, user_id=user.uid, team_id=team.uid, parent_id=cycle.uid, module_label='DemoModule', progress=50, page=1, body={ 'question1': { 'modified': '2019-01-01T00:00:00Z', 'value': 'foo', }, }, ) return (team, user, cycle, response)
def team_module_response(self, cycle, module_label, progress=100): return Response.create( type=Response.TEAM_LEVEL_SYMBOL, user_id="", team_id=cycle.team_id, parent_id=cycle.uid, module_label=module_label, progress=progress, body={}, )
def test_redaction_public_team_level(self): user = User.create(email='*****@*****.**') team_params = dict( self.response_params, type=Response.TEAM_LEVEL_SYMBOL, private=False, ) r = Response.create(**team_params) r.put() responses = Response.get_for_teams(user, [team_params['team_id']]) # Not redacted. self.assertGreater(len(responses[0].body), 0)
def test_redaction_public_user_level(self): user = User.create(email='*****@*****.**') user_params = dict( self.response_params, type=Response.USER_LEVEL_SYMBOL, private=False, user_id='User_foo', ) r = Response.create(**user_params) r.put() responses = Response.get_for_teams(user, [user_params['team_id']]) # Not redacted, even though user doesn't own the response. self.assertGreater(len(responses[0].body), 0)
def test_update_creates_task(self): # **Don't** use .put() here, because responses are saved with a # custom transaction via insert_or_conflict() and update_or_conflict(). user_response_params = dict( self.response_params, type=Response.USER_LEVEL_SYMBOL, private=True, user_id='User_foo', ) r = Response.create(**user_response_params) r.put() # Creates one task. # Modifying should create a (second) task. new_params = dict(user_response_params, progress = 100) updated = Response.update_or_conflict(r.uid, new_params, False) expected_payload = json.dumps( updated.to_dict(), default=util.json_dumps_default, ) tasks = self.taskqueue_stub.get_filtered_tasks() self.assertEqual(len(tasks), 2) self.assertEqual(tasks[1].payload, expected_payload)
def test_atomic_updates(self): """Not exactly a test; more of a proof of concept that mysql row locking works.""" r = Response.create( user_id='User_foo', team_id='Team_foo', parent_id='Cycle_foo', module_label='ModuleFoo', body={'foo': 'bar'}, progress=0, ) r.put() table = 'response' with mysql_connection.connect(retry_on_error=False) as sql: # This simulates one user clicking "submit" to a module. sql.select_row_for_update(table, 'uid', r.uid) # locks with self.assertRaises(MySQLdb.OperationalError): with mysql_connection.connect(retry_on_error=False) as sql: # This default to 50, which is too long to wait. sql.query('SET innodb_lock_wait_timeout = 1', tuple()) # This simulates another user clicking submit on their # client at the exact same time, which if it weren't for the # lock would be a race condition. Here it should just wait, # and then reach the timeout and raise. sql.update_row(table, 'uid', r.uid, progress=1) # Unfortunately exiting here will close _both_ connections, # so we'll have to let that happen and open a third. # If lock succeeded, the data should be unchanged. fetched = Response.get_by_id(r.uid) self.assertEqual(fetched.progress, 0)
def create(self): org = Organization.create(name='Foo Community', program_id=self.program.uid) org.put() team = Team.create( name='foo', captain_id='User_captain', program_id=self.program.uid, organization_ids=[org.uid], ) teammate = User.create(name='teammate', email='*****@*****.**', owned_teams=[team.uid]) other = User.create(name='other', email='*****@*****.**') User.put_multi((other, teammate)) team.put() cycles = ( Cycle.create( team_id=team.uid, ordinal=1, start_date=datetime.date(2000, 1, 1), end_date=datetime.date(2000, 2, 1), ), Cycle.create( team_id=team.uid, ordinal=2, start_date=datetime.date.today(), end_date=datetime.date.today() + datetime.timedelta(weeks=4), ), ) Cycle.put_multi(cycles) responses = { # User-level, not related to our team or user, inaccessible. 'user_other_other': Response.create( user_id=other.uid, team_id='Team_other', parent_id='Cycle_other', module_label='ModuleFoo', body=self.default_body(), ), # Related to our user but not our team. 'user_other_user': Response.create( user_id=teammate.uid, team_id='Team_other', parent_id='Cycle_isolated', module_label='ModuleFoo', body=self.default_body(), ), # Related to both our team and user; two different cycles. 'user_team_user1': Response.create( user_id=teammate.uid, team_id=team.uid, parent_id=cycles[0].uid, module_label='ModuleFoo', body=self.default_body(), ), 'user_team_user2': Response.create( user_id=teammate.uid, team_id=team.uid, parent_id=cycles[1].uid, module_label='ModuleFoo', body=self.default_body(), ), # Related to our team but not our user; body should stay secret 'user_team_other': Response.create( user_id='User_other-teammate', team_id=team.uid, parent_id=cycles[0].uid, module_label='ModuleFoo', body=self.default_body(), ), # Team-level response, readable for all team members 'team_team': Response.create( type='Team', user_id='', team_id=team.uid, parent_id='launch-step', module_label='ModuleFoo', body=self.default_body(), ), # Team-level, but for a different team. 'team_other': Response.create( type='Team', user_id='', team_id='Team_other', parent_id='launch-step', module_label='ModuleFoo', body=self.default_body(), ), } Response.put_multi(responses.values()) return (other, teammate, team, cycles, responses)