def test_multi_default_1(init_world, get_client): ''' friends - always childred 1 & 2 ''' init_world(family.dm) client = get_client() # 1. Add best_friend field. child = dm().object('child') child.add_field(Rel('friends', stores=child, multi=True, default=[1, 2])) # If this is PG storage, we need to update database schema if issubclass(type(world().storage), PGStorage): conn = world().storage._conn conn.cursor().execute('ALTER TABLE child ADD COLUMN friends integer[]') conn.commit() # 2. Post a child without friends data, status, headers = client.post('child', {'name': 'c'}) assert status == 201 assert data['friends'] == [1, 2] # 3. Post a child with friends data, status, headers = client.post('child', { 'name': 'c', 'friends': [2, 3] }) assert status == 201 assert data['friends'] == [2, 3]
def delete(self, propagated_from=None): ''' Delete SELF: * make other instances forget (by updating relationship fields) * make world forget PROPAGATED_FROM - other instance, connected to SELF via a Rel field with cascade=True, that was deleted - and this way initialized deletion of SELF. None if SELF.delete() was called in a different context. ''' # Not usable -> exception if not self.usable: raise exceptions.ProgrammingError("This instance is no longer usable") # Delete started self.deleted = True for field, val in self.field_values(): if field.rel and val is not None: field.propagate_delete(self) # And the world will forget world().remove_instance(self) # Instance should not be used any longer self.usable = False
def test_one_dir_1(init_world, get_client): # 1. Init world init_cookies_with_shelf_1(init_world) client = get_client() # 2. Save current world state, for further comparisions world_data = world().data() # 3. Create a shelf with both jars data, status, headers = client.put('shelf', 1, {'jars': [1, 2]}) assert status == 201 # 4. Check if world data didn't change (except shelf) new_world_data = world().data() assert new_world_data['cookie'] == world_data['cookie'] assert new_world_data['jar'] == world_data['jar'] # 5. Check if GET works as expected jar_1, status_1, headers = client.get('jar', 1, depth=1) jar_2, status_2, headers = client.get('jar', 2, depth=1) shelf, status_3, headers = client.get('shelf', 1, depth=2) assert status_1 == 200 assert status_2 == 200 assert status_3 == 200 assert shelf['jars'] == [jar_1, jar_2] # 6. Test 404 shelf, status, headers = client.put('shelf', 2, {'jars': [3]}) assert status == 404
def test_single_default_1(init_world, get_client): ''' Best Friend - always child 1 ''' init_world(family.dm) client = get_client() # 1. Add best_friend field. child = dm().object('child') child.add_field(Rel('best_friend', stores=child, multi=False, default=1)) # If this is PG storage, we need to update database schema if issubclass(type(world().storage), PGStorage): conn = world().storage._conn conn.cursor().execute( 'ALTER TABLE child ADD COLUMN best_friend integer REFERENCES child(id) DEFERRABLE' ) conn.commit() # 2. Post a child without best friend data, status, headers = client.post('child', {'name': 'c'}) assert status == 201 assert data['best_friend'] == 1 # 3. Post a child with best friend data, status, headers = client.post('child', { 'name': 'c', 'best_friend': 2 }) assert status == 201 assert data['best_friend'] == 2
def test_3(init_world): init_world(cookies.dm, get_instance_class=get_instance_class) world().begin() cookie = world().get_instance('cookie', 1) cookie.update({'jar': 2}) assert type(cookie) is FirstJarCookieInstance assert cookie.repr(1)['jar'] == 1
def add_field_closed(dm): jar = dm.object('jar') jar.add_field(Scalar('closed', default=False)) # Add database column if PGStorage if issubclass(type(engine.world().storage), engine.PGStorage): engine.world().storage._conn.cursor().execute(''' ALTER TABLE jar ADD COLUMN closed boolean; ''')
def test_usable_4(init_world): '''attempt to update not usable instance should raise an exception''' init_world(family.dm) world().begin() child = world().get_instance('child', 1) world().write() with pytest.raises(exceptions.ProgrammingError): child.update(dict(name='aaa'))
def test_usable_5(init_world): '''attempt to delete not usable instance should raise an exception''' init_world(family.dm) world().begin() child = world().get_instance('child', 1) world().write() with pytest.raises(Exception): child.delete()
def test_usable_3(init_world): init_world(family.dm) world().begin() child = world().new_instance('child') # fresh instance is fine ... assert child.usable # ... until written world().write() assert not child.usable
def test_delete_3(init_world): init_world(cookies.dm) cookie_id = 2 world().begin() cookie = world().get_instance('cookie', cookie_id) jar = related(cookie, 'jar') cookie.delete() assert cookie not in related(jar, 'cookies') assert cookie_id not in world().data()['cookie']
def test_delete_1(init_world): init_world(family.dm) child_id = 1 world().begin() child = world().get_instance('child', child_id) father = related(child, 'father') mother = related(child, 'mother') child.delete() assert child not in related(father, 'children') assert child not in related(mother, 'children') assert child_id not in world().data()['child']
def test_delete_4(init_world): init_world(cookies.dm) jar_id = 1 world().begin() jar = world().get_instance('jar', jar_id) jar_1_cookies = related(jar, 'cookies') jar.delete() for cookie in jar_1_cookies: assert related(cookie, 'jar') is None assert jar_id not in world().data()['jar']
def test_delete_2(init_world): init_world(family.dm) male_id = 1 world().begin() male = world().get_instance('male', male_id) wife = related(male, 'wife') children = related(male, 'children') male.delete() assert related(wife, 'husband') is None for child in children: assert related(child, 'father') is None assert male_id not in world().data()['male']
def propagate_delete(self, deleted_instance): ''' DELETED_INSTANCE is being deleted. If SELF.other is not None, we need to remove all connections. ''' other = self.other if other is None: return ids = deleted_instance.get_val(self).ids() for id_ in ids: this_id = deleted_instance.id() # Other instance could have already been deleted, if we have multiple # cascade fields - we just ignore it here try: other_instance = world().get_instance(self.stores.name, id_) except exceptions.e404: continue other_instance_ids = other_instance.get_val(other).ids() if this_id in other_instance_ids: # If other.cascade, instance should also be deleted, # if not - connection should be removed # # Note: if other.cascade is True, other.multi must be False (-> check Rel.init()) # so this is the only connection. # # We also check instance.deleted to avoid recursion on recursive cascades if other.cascade and not other_instance.deleted: other_instance.delete(propagated_from=deleted_instance) else: new_ids = other_instance_ids new_ids.remove(this_id) other_instance.set_value(other, other.val(new_ids))
def init_cookies_with_shelf_1(init_world): '''Add shelf to cookies datamodel. Shelf knows it's jars, jar has no idea about shelf ''' dm = deepcopy(cookies.dm) shelf = dm.create_object('shelf') shelf.add_field(Scalar('id', pkey=True, type_=int)) shelf.add_field(Rel('jars', stores=dm.object('jar'), multi=True)) init_world(dm) # If this is PG storage, we need to update database schema if issubclass(type(world().storage), PGStorage): conn = world().storage._conn conn.cursor().execute('CREATE TABLE shelf (id serial PRIMARY KEY, jars integer[])') conn.commit()
def test_implicit_fields_post_put(get_client, method, args): ''' Check if fields set in a implicit way after POST/PUT (i.e. database defaults) are returned ''' # INIT init_pg_world(cookies.dm) client = get_client() # Add default column value world().storage._conn.cursor().execute(''' ALTER TABLE cookie ALTER COLUMN jar SET DEFAULT 1; ''') data, status, headers = getattr(client, method)(*args) assert status == 201 assert data['jar'] == 1
def init_cookies_with_shelf_2(init_world): '''Add shelf to cookies datamodel. Jar knows it's shelf, shelf has no idea about jars. ''' dm = deepcopy(cookies.dm) shelf = dm.create_object('shelf') shelf.add_field(Scalar('id', pkey=True, type_=int)) jar = dm.object('jar') jar.add_field(Rel('shelf', stores=shelf, multi=False)) init_world(dm) # If this is PG storage, we need to update database schema if issubclass(type(world().storage), PGStorage): conn = world().storage._conn conn.cursor().execute('CREATE TABLE shelf (id serial PRIMARY KEY)') conn.cursor().execute('ALTER TABLE jar ADD COLUMN shelf integer REFERENCES shelf(id)') conn.commit()
def world_data(): ''' Returns expected data for current world, based on * world's datamodel name * example.*.world_data() function ''' d = eval(engine.dm().name) return d.world_data(type(engine.world().storage).__name__)
def test_rollback_delete_1(init_world, finalize, equal, operation): init_world(cookies.dm) data_before = deepcopy(world().data()) world().begin() operation(world()) world().write() getattr(world(), finalize)() assert (data_before == world().data()) is equal
def test_world_commit_3(init_world): '''Test remove_instance()''' init_world(cookies.dm) w = world() w.begin() i = w.get_instance('cookie', 1) w.write() w.commit() with pytest.raises(exceptions.ProgrammingError): w.remove_instance(i)
def test_not_null_put(get_client, status, args): init_pg_world(cookies.dm) client = get_client() # from now on, we no longer accept cookies of unknown type conn = world().storage._conn conn.cursor().execute('ALTER TABLE cookie ALTER COLUMN type SET NOT NULL') conn.commit() assert client.put('cookie', 4, args)[1] == status
def _get_not_null_msg(): '''Ugly fix to make the tests work on both tested versions of PostgreSQL''' init_pg_world(cookies.dm) cursor = world().storage._conn.cursor() cursor.execute('SELECT version()') version = cursor.fetchone()[0] if 'PostgreSQL 9' in version: return 'null value in column "type" violates not-null constraint' else: return 'null value in column "type" of relation "cookie" violates not-null constraint'
def test_api_delete_cookies(init_world, get_client, deletions, data_id): init_world(cookies.dm) client = get_client() for name, id_ in deletions: client.delete(name, id_) expected_data = expected_delete_data(data_id) assert world().data() == expected_data
def test_implicit_fields_patch(get_client): ''' Check if fields set in a implicit way after PATCH (i.e. by database triggers) are returned ''' # INIT init_pg_world(cookies.dm) client = get_client() # Add trigger changing type after update world().storage._conn.cursor().execute(''' CREATE FUNCTION pg_temp.new_cookie_type() RETURNS trigger AS $new_cookie_type$ BEGIN NEW.type = 'type_set_by_trigger'; RETURN NEW; END; $new_cookie_type$ LANGUAGE plpgsql; CREATE TRIGGER change_cookie_type BEFORE UPDATE ON pg_temp.cookie FOR EACH ROW EXECUTE PROCEDURE pg_temp.new_cookie_type(); ''') # Create fresh cookie data, status, headers = client.put('cookie', 4, {'type': 'donut'}) assert status == 201 assert data['type'] == 'donut' # Make sure it's still a donut data, status, headers = client.get('cookie', 4) assert status == 200 assert data['type'] == 'donut' # Patch it with some data and check if we got triggered type data, status, headers = client.patch('cookie', 4, {'type': 'doesnt matter'}) assert status == 200 assert data['type'] == 'type_set_by_trigger' # Make sure it's still a triggered type data, status, headers = client.get('cookie', 4) assert status == 200 assert data['type'] == 'type_set_by_trigger'
def _extract_ids(self, values): if not self.multi and len(values) > 1: raise exceptions.ProgrammingError('More than one instance on a Single related field') ids = [] for val in values: type_ = type(val) if val is None: raise exceptions.ProgrammingError("None is not accepted for {}".format(type(self))) elif type_ in (int, float, str): ids.append(val) elif issubclass(type_, world().get_instance_class(self.name)): ids.append(val.id()) elif issubclass(type_, dict): inst = world().new_instance(self.name) inst.update(val) ids.append(inst.id()) else: raise exceptions.ProgrammingError('could not create {}'.format(type(self))) return sorted(ids)
def _int_update(self, this_instance, value, ext): old_val = this_instance.get_val(self) new_val = self.val(value) if ext: # If ext is True, we need to validate if new value objects really exist, # the most straightforward way is to create instances stored in new_val. # This would work well, but slow - we might end up creating many redundant instances. # Instead here we extract new ids and validate only those. new_ids = set(new_val.ids()) - set(old_val.ids()) new_ids_val = self.val(list(new_ids)) new_ids_val.inst() this_instance.set_value(self, new_val) other = self.other if other: this_id = this_instance.id() added = set(new_val.ids()) - set(old_val.ids()) removed = set(old_val.ids()) - set(new_val.ids()) for added_id in added: other_instance = world().get_instance(self.stores.name, added_id) other_instance_ids = other_instance.get_val(other).ids() if this_id not in other_instance_ids: if other.multi: new_ids = sorted(other_instance_ids + [this_id]) else: new_ids = [this_id] if other_instance_ids: disconnected_instance = world().get_instance(other.stores.name, other_instance_ids[0]) disconnected_instance.set_value(self, self.val(self.default)) other_instance.set_value(other, other.val(new_ids)) for removed_id in removed: other_instance = world().get_instance(self.stores.name, removed_id) other_instance_ids = other_instance.get_val(other).ids() if this_id in other_instance_ids: new_ids = other_instance_ids new_ids.remove(this_id) other_instance.set_value(other, other.val(new_ids))
def test_ext_name_storage(init_world, get_client): ''' ext_name should have no influence on stored data ''' # 1. Init init_world(cookies.dm) client = get_client() # 2. Post data, status, headers = client.post('cookie', {'type': 'tasty'}) id_ = data['id'] stored_before = world().data() # 4. Set an ext_name dm().object('cookie').field('type').ext_name = 'Cookie Type' # 5. Put the same data, but using ext_name data, status, headers = client.put('cookie', id_, {'Cookie Type': 'tasty'}) # 6. Check assert stored_before == world().data()
def test_single_multi_2(init_world, get_client): ''' Best Friend - new female named BLARGH (note: new anonymous child creates infinite recursion) ''' init_world(family.dm) client = get_client() # 1. Add best_friend field. New child's best friend is always child no 1. child = dm().object('child') child.add_field( Rel('best_friend', stores=dm().object('female'), multi=False, default={'name': 'BLARGH'})) # If this is PG storage, we need to update database schema if issubclass(type(world().storage), PGStorage): conn = world().storage._conn conn.cursor().execute( 'ALTER TABLE child ADD COLUMN best_friend integer REFERENCES child(id) DEFERRABLE' ) conn.commit() # 2. Post a child without best friend data, status, headers = client.post('child', {'name': 'c'}) assert status == 201 assert data['best_friend'] == 3 # 3. Check if name was saved data, status, headers = client.get('female', 3) assert status == 200 assert data['name'] == 'BLARGH' # 4. Post a child with best friend data, status, headers = client.post('child', { 'name': 'c', 'best_friend': 2 }) assert status == 201 assert data['best_friend'] == 2
def cleanup(): yield # Drop schema conn = engine.world().storage._conn conn.close() conn = get_connection() conn.cursor().execute( 'DROP SCHEMA IF EXISTS {} CASCADE'.format(schema_name)) conn.commit() # Reload PGStorage - remove code changes importlib.reload(pg_storage)
def test_not_null_patch(get_client, expected_status, cookie_id, args, expected_data): init_pg_world(cookies.dm) client = get_client() # from now on, we no longer accept cookies of unknown type conn = world().storage._conn conn.cursor().execute('ALTER TABLE cookie ALTER COLUMN type SET NOT NULL') conn.commit() data, status, headers = client.patch('cookie', cookie_id, args) assert status == expected_status print(data) if expected_data is not None: assert data == expected_data