def test_merge(historian: mincepy.Historian): with testing.temporary_historian( testing.create_archive_uri(db_name='test_historian')) as remote: local = historian # remote = clean_test_historian remote_skoda = testing.Car(make='skoda', colour='green') skoda_id = remote.save(remote_skoda) assert remote_skoda._historian is remote result = local.merge(remote.objects.find(obj_id=skoda_id)) assert remote.get_snapshot_id(remote_skoda) in result.merged assert local.find(obj_id=skoda_id).count() == 1 # Now, let's update and see if we can merge remote_skoda.colour = 'yellow' remote.save(remote_skoda) result = local.merge(remote.objects.find(obj_id=skoda_id)) assert remote.get_snapshot_id(remote_skoda) in result.merged assert local.snapshots.find(obj_id=skoda_id).count() == 2 assert local.find(obj_id=skoda_id).one().colour == 'yellow' # Now, change both to the same thing local_skoda = local.load(skoda_id) assert local_skoda._historian is local assert local_skoda is not remote_skoda local_skoda.colour = 'blue' local_skoda.save() remote_skoda.colour = 'blue' remote_skoda.save() result = local.merge(remote.objects.find(obj_id=skoda_id)) assert not result.merged # None should have been transferred # Now check that conflicts are correctly handled remote_skoda.colour = 'brown' remote_skoda.save() local_skoda.colour = 'grey' local_skoda.save() with pytest.raises(mincepy.MergeError): local.merge(remote.objects.find(obj_id=skoda_id))
def test_ref_load_save_load(historian: mincepy.Historian): """This is here to catch a bug that manifested when a reference was saved, loaded and then re-saved without being dereferenced in-between. This would result in the second saved state being that of a null reference. This can only be tested if the reference is stored by value as otherwise the historian will not re-save a reference that has not been mutated.""" ref_list = mincepy.List((mincepy.ObjRef(testing.Car()), )) assert isinstance(ref_list[0](), testing.Car) list_id = ref_list.save() # pylint: disable=no-member del ref_list loaded = historian.load(list_id) # Re-save loaded.save() del loaded # Re-load reloaded = historian.load(list_id) # Should still be our car but because of a bug this was a None reference assert isinstance(reloaded[0](), testing.Car)
def test_delete(historian: mincepy.Historian): """Test deleting and then attempting to load an object""" car = testing.Car('lada') car_id = historian.save(car) historian.delete(car) with pytest.raises(mincepy.NotFound): historian.load(car_id) records = historian.history(car_id, as_objects=False) assert len( records) == 2, 'There should be two record, the initial and the delete' assert records[-1].is_deleted_record() # Check imperative deleting with pytest.raises(mincepy.NotFound): historian.delete(car_id) # This, should not raise: result = historian.delete(car_id, imperative=False) assert not result.deleted assert car_id in result.not_found
def test_migrating_live_object(historian: mincepy.Historian): """Test that a migration including a live object works fine""" class V1(mincepy.ConvenientSavable): TYPE_ID = uuid.UUID('8b1620f6-dd6d-4d39-b8b1-4433dc2a54df') ref = mincepy.field() def __init__(self, obj): super().__init__() self.ref = obj car = testing.Car() car.save() class V2(mincepy.ConvenientSavable): TYPE_ID = uuid.UUID('8b1620f6-dd6d-4d39-b8b1-4433dc2a54df') ref = mincepy.field(ref=True) class V1toV2(mincepy.ObjectMigration): VERSION = 1 @classmethod def upgrade(cls, saved_state, loader: 'mincepy.Loader'): # Create a reference to the live car object saved_state['ref'] = mincepy.ObjRef(car) return saved_state LATEST_MIGRATION = V1toV2 martin = testing.Person('martin', 35) my_obj = V1(martin) my_obj_id = my_obj.save() del my_obj # Now change my mind historian.register_type(V2) assert len(historian.migrations.migrate_all()) == 1 migrated = historian.load(my_obj_id) assert migrated.ref is car
def test_migrate_with_saved(historian: mincepy.Historian): """Test migrating an object that has saved references""" class V3(mincepy.ConvenientSavable): TYPE_ID = StoreByRef.TYPE_ID ref = mincepy.field(ref=True) description = mincepy.field() class Migration(mincepy.ObjectMigration): VERSION = 2 PREVIOUS = StoreByRef.ToRefMigration @classmethod def upgrade(cls, saved_state, loader: 'mincepy.Loader'): saved_state['description'] = None return saved_state LATEST_MIGRATION = Migration def __init__(self, ref): super().__init__() self.ref = ref self.description = None obj = StoreByRef(testing.Car()) obj_id = obj.save() del obj gc.collect() historian.register_type(V3) migrated = historian.migrations.migrate_all() assert len(migrated) == 1 assert migrated[0].obj_id == obj_id obj = historian.load(obj_id) assert isinstance(obj, V3) assert hasattr(obj, 'description') assert obj.description is None
def test_migrate_to_reference(historian: mincepy.Historian, caplog): caplog.set_level(logging.DEBUG) car = testing.Car() by_val = StoreByValue(car) oid = historian.save(by_val) del by_val # Now, change my mind historian.register_type(StoreByRef) # Migrate migrated = historian.migrations.migrate_all() assert len(migrated) == 1 by_ref = historian.load(oid) assert isinstance(by_ref.ref, testing.Car) assert by_ref.ref is not car loaded_car = by_ref.ref del by_ref # Reload and check that ref points to the same car loaded = historian.load(oid) assert loaded.ref is loaded_car
def test_objects_collection(historian: mincepy.Historian): ferrari = testing.Car(colour='red', make='ferrari') ferrari_id = ferrari.save() records = list(historian.objects.records.find()) assert len(records) == 1 objects = list(historian.objects.find()) assert len(objects) == 1 assert objects[0] is ferrari ferrari.colour = 'brown' ferrari.save() records = list(historian.objects.records.find()) assert len(records) == 1 objects = list(historian.objects.find()) assert len(objects) == 1 assert set(car.colour for car in objects) == {'brown'} assert historian.objects.records.find(Car.colour == 'brown', obj_id=ferrari_id).one().version == 1
def execute(self): return mincepy.builtins.RefList([testing.Car()])
def execute(self): # pylint: disable=no-self-use return mincepy.builtins.RefList([testing.Car()])
def test_delete(): car = testing.Car() car_id = car.save() mincepy.delete(car) assert not list(mincepy.find(obj_id=car_id))
def test_find(): car = testing.Car() car_id = car.save() assert list(mincepy.find(obj_id=car_id))[0] is car
def test_save_load(): car = testing.Car() car_id = mincepy.save(car) del car # Now try loading mincepy.load(car_id)
def test_find_from_class(historian): """Test that we can use the class to search for all objects of that type""" car = testing.Car() car.save() assert list(historian.find(testing.Car)) == [car]