class UpdaterTest(unittest.TestCase): def setUp(self): self.maxDiff = None client = MongoClient() client.drop_database('metaphor2_test_db') self.db = client.metaphor2_test_db self.schema = Schema(self.db) self.updater = Updater(self.schema) self.employee_spec = self.schema.add_spec('employee') self.schema.add_field(self.employee_spec, 'name', 'str') self.schema.add_field(self.employee_spec, 'age', 'int') self.division_spec = self.schema.add_spec('division') self.schema.add_field(self.division_spec, 'name', 'str') self.schema.add_field(self.division_spec, 'employees', 'linkcollection', 'employee') self.schema.add_calc(self.division_spec, 'older_employees', 'self.employees[age>30]') self.schema.add_field(self.schema.root, 'divisions', 'collection', 'division') self.schema.add_field(self.schema.root, 'employees', 'collection', 'employee') def test_update_only_linked_resources(self): employee_id_1 = self.schema.insert_resource('employee', { 'name': 'ned', 'age': 10 }, 'employees') employee_id_2 = self.schema.insert_resource('employee', { 'name': 'bob', 'age': 10 }, 'employees') division_id_1 = self.schema.insert_resource('division', {'name': 'sales'}, 'divisions') division_id_2 = self.schema.insert_resource('division', {'name': 'marketting'}, 'divisions') self.schema.create_linkcollection_entry('division', division_id_1, 'employees', employee_id_1) self.schema.create_linkcollection_entry('division', division_id_2, 'employees', employee_id_1) self.schema.create_linkcollection_entry('division', division_id_2, 'employees', employee_id_2) self.assertEqual([ self.schema.decodeid(division_id_1), self.schema.decodeid(division_id_2) ], self.updater.get_affected_ids_for_resource( 'division', 'older_employees', self.employee_spec, employee_id_1)) self.assertEqual([self.schema.decodeid(division_id_2)], self.updater.get_affected_ids_for_resource( 'division', 'older_employees', self.employee_spec, employee_id_2))
class AggregatorTest(unittest.TestCase): def setUp(self): self.maxDiff = None client = MongoClient() client.drop_database('metaphor2_test_db') self.db = client.metaphor2_test_db self.schema = Schema(self.db) self.updater = Updater(self.schema) self.employee_spec = self.schema.add_spec('employee') self.schema.add_field(self.employee_spec, 'name', 'str') self.schema.add_field(self.employee_spec, 'age', 'int') self.section_spec = self.schema.add_spec('section') self.schema.add_field(self.section_spec, 'name', 'str') self.schema.add_field(self.section_spec, 'members', 'linkcollection', 'employee') self.division_spec = self.schema.add_spec('division') self.schema.add_field(self.division_spec, 'name', 'str') self.schema.add_field(self.division_spec, 'employees', 'collection', 'employee') self.schema.add_field(self.division_spec, 'sections', 'collection', 'section') self.schema.add_field(self.schema.root, 'divisions', 'collection', 'division') self.aggregator = ReverseAggregator(self.schema) def test_simple(self): tree = parse('self.employees.age', self.schema.specs['division']) employee_id = ObjectId() aggregations = self.aggregator.get_for_resource( tree, 'employee', employee_id, 'division', 'all_employees_age') self.assertEqual([[{ '$match': { '_id': employee_id } }, { '$lookup': { 'as': '_field_employees', 'foreignField': '_id', 'from': 'resource_division', 'localField': '_parent_id' } }, { '$group': { '_id': '$_field_employees' } }, { '$unwind': '$_id' }, { '$replaceRoot': { 'newRoot': '$_id' } }]], aggregations) def test_middle_of_calc(self): tree = parse('divisions.sections.members.age', self.schema.specs['division']) section_id = ObjectId() aggregations = self.aggregator.get_for_resource( tree, 'section', section_id, 'division', 'all_ages') self.assertEqual([[{ '$match': { '_id': section_id } }, { '$lookup': { 'as': '_field_sections', 'foreignField': '_id', 'from': 'resource_division', 'localField': '_parent_id' } }, { '$group': { '_id': '$_field_sections' } }, { '$unwind': '$_id' }, { '$replaceRoot': { 'newRoot': '$_id' } }], [{ '$lookup': { 'as': '_field_all_ages', 'from': 'resource_division', 'pipeline': [] } }, { '$group': { '_id': '$_field_all_ages' } }, { '$unwind': '$_id' }, { '$replaceRoot': { 'newRoot': '$_id' } }]], aggregations) def test_double(self): tree = parse( 'self.employees.parent_division_employees.sections.members.age', self.schema.specs['division']) employee_id = ObjectId() aggregations = self.aggregator.get_for_resource( tree, 'employee', employee_id) self.assertEqual([ [ { '$match': { '_id': employee_id } }, { '$lookup': { 'as': '_field_members', 'foreignField': 'members._id', 'from': 'resource_section', 'localField': '_id' } }, { '$group': { '_id': '$_field_members' } }, { '$unwind': '$_id' }, { '$replaceRoot': { 'newRoot': '$_id' } }, { '$lookup': { 'as': '_field_sections', 'foreignField': '_id', 'from': 'resource_division', 'localField': '_parent_id' } }, { '$group': { '_id': '$_field_sections' } }, { '$unwind': '$_id' }, { '$replaceRoot': { 'newRoot': '$_id' } }, { '$lookup': { 'as': '_field_parent_division_employees', 'foreignField': '_parent_id', 'from': 'resource_employee', 'localField': '_id' } }, { '$group': { '_id': '$_field_parent_division_employees' } }, { '$unwind': '$_id' }, { '$replaceRoot': { 'newRoot': '$_id' } }, { '$lookup': { 'as': '_field_employees', 'foreignField': '_id', 'from': 'resource_division', 'localField': '_parent_id' } }, { '$group': { '_id': '$_field_employees' } }, { '$unwind': '$_id' }, { '$replaceRoot': { 'newRoot': '$_id' } }, ], [ { '$match': { '_id': employee_id } }, { '$lookup': { 'as': '_field_employees', 'foreignField': '_id', 'from': 'resource_division', 'localField': '_parent_id' } }, { '$group': { '_id': '$_field_employees' } }, { '$unwind': '$_id' }, { '$replaceRoot': { 'newRoot': '$_id' } }, ], ], aggregations) def test_calc(self): tree = parse( 'max(self.employees.age) + min(divisions.employees.age) + 10', self.schema.specs['division']) employee_id = ObjectId() aggregations = self.aggregator.get_for_resource( tree, 'employee', employee_id, 'section', 'age_calc') self.assertEqual( [ [ { '$match': { '_id': employee_id } }, { '$lookup': { 'as': '_field_employees', 'foreignField': '_id', 'from': 'resource_division', 'localField': '_parent_id' } }, { '$group': { '_id': '$_field_employees' } }, { '$unwind': '$_id' }, { '$replaceRoot': { 'newRoot': '$_id' } }, ], # TODO: if two of the aggregations are the same, remove the second one: [ { '$match': { '_id': employee_id } }, { '$lookup': { 'as': '_field_employees', 'foreignField': '_id', 'from': 'resource_division', 'localField': '_parent_id' } }, { '$group': { '_id': '$_field_employees' } }, { '$unwind': '$_id' }, { '$replaceRoot': { 'newRoot': '$_id' } }, ], [ { '$lookup': { 'as': '_field_age_calc', 'from': 'resource_section', 'pipeline': [] } }, { '$group': { '_id': '$_field_age_calc' } }, { '$unwind': '$_id' }, { '$replaceRoot': { 'newRoot': '$_id' } }, ] ], aggregations) def test_double_aggregate(self): division_id_1 = self.schema.insert_resource('division', {'name': 'Sales'}, 'divisions') section_id_1 = self.schema.insert_resource('section', {'name': 'Sales'}, 'sections', 'division', division_id_1) employee_id_1 = self.schema.insert_resource('employee', { 'name': 'ned', 'age': 10 }, 'employees', 'division', division_id_1) employee_id_2 = self.schema.insert_resource('employee', { 'name': 'bob', 'age': 10 }, 'employees', 'division', division_id_1) self.schema.create_linkcollection_entry('section', section_id_1, 'members', employee_id_1) tree = parse( 'self.employees.parent_division_employees.sections.members.age', self.schema.specs['division']) aggregations = self.aggregator.get_for_resource( tree, 'employee', self.schema.decodeid(employee_id_1)) result = self.schema.db['resource_employee'].aggregate(aggregations[0]) self.assertEqual( { '_canonical_url': '/divisions/%s' % division_id_1, '_grants': [], '_id': self.schema.decodeid(division_id_1), '_parent_canonical_url': '/', '_parent_field_name': 'divisions', '_parent_id': None, '_parent_type': 'root', 'name': 'Sales' }, next(result)) def test_ternary_aggregate(self): division_id_1 = self.schema.insert_resource('division', {'name': 'Sales'}, 'divisions') section_id_1 = self.schema.insert_resource('section', {'name': 'Sales'}, 'sections', 'division', division_id_1) employee_id_1 = self.schema.insert_resource('employee', { 'name': 'ned', 'age': 10 }, 'employees', 'division', division_id_1) employee_id_2 = self.schema.insert_resource('employee', { 'name': 'bob', 'age': 10 }, 'employees', 'division', division_id_1) self.schema.create_linkcollection_entry('section', section_id_1, 'members', employee_id_1) tree = parse( 'max(self.employees.age) > 20 -> max(self.employees.age) : min(divisions.sections.members.age)', self.schema.specs['division']) aggregations = self.aggregator.get_for_resource( tree, 'employee', self.schema.decodeid(employee_id_1)) result = self.schema.db['resource_employee'].aggregate(aggregations[0]) self.assertEqual( { '_canonical_url': '/divisions/%s' % division_id_1, '_grants': [], '_id': self.schema.decodeid(division_id_1), '_parent_canonical_url': '/', '_parent_field_name': 'divisions', '_parent_id': None, '_parent_type': 'root', 'name': 'Sales' }, next(result)) def test_simple_root(self): # uncertain what case this is testing exactly - section has no link to division division_id_1 = self.schema.insert_resource('division', {'name': 'Sales'}, 'divisions') tree = parse('max(divisions.name)', self.schema.root) aggregations = self.aggregator.get_for_resource( tree, 'division', self.schema.decodeid(division_id_1), 'division', 'max_divisions_name') self.assertEqual([[{ '$lookup': { 'as': '_field_max_divisions_name', 'from': 'resource_division', 'pipeline': [] } }, { '$group': { '_id': '$_field_max_divisions_name' } }, { '$unwind': '$_id' }, { '$replaceRoot': { 'newRoot': '$_id' } }]], aggregations)
class LRParseTest(unittest.TestCase): def setUp(self): self.maxDiff = None client = MongoClient() client.drop_database('metaphor2_test_db') self.db = client.metaphor2_test_db self.schema = Schema(self.db) self.db.metaphor_schema.insert_one({ "specs" : { "employee" : { "fields" : { "name" : { "type" : "str" }, "pseudoname" : { "type" : "str" }, "age": { "type": "int" }, "division": { "type": "link", "target_spec_name": "division", }, }, }, "division": { "fields": { "name": { "type": "str", }, "yearly_sales": { "type": "int", }, "sections": { "type": "collection", "target_spec_name": "section", }, "parttimers": { "type": "linkcollection", "target_spec_name": "employee", }, "partners": { "type": "collection", "target_spec_name": "employee", }, }, }, "section": { "fields": { "name": { "type": "str", }, }, }, }, "root": { "employees": { "type": "collection", "target_spec_name": "employee", }, "divisions": { "type": "collection", "target_spec_name": "division", } }, }) self.schema.load_schema() def test_basic(self): employee_id = self.schema.insert_resource('employee', {'name': 'sailor', 'age': 41}, 'employees') division_id = self.schema.insert_resource('division', {'name': 'sales', 'yearly_sales': 10}, 'divisions') self.schema.update_resource_fields('employee', employee_id, {'division': division_id}) tree = parse("self.division.yearly_sales", self.schema.specs['employee']) self.assertEquals(self.schema.specs['division'].fields['yearly_sales'], tree.infer_type()) self.assertFalse(tree.is_collection()) self.assertEquals(10, tree.calculate(employee_id)) def test_even_basicer(self): employee_id = self.schema.insert_resource('employee', {'name': 'sailor', 'age': 41}, 'employees') tree = parse("self.age", self.schema.specs['employee']) self.assertEquals(self.schema.specs['employee'].fields['age'], tree.infer_type()) self.assertFalse(tree.is_collection()) self.assertEquals(41, tree.calculate(employee_id)) def test_basic_link_follow(self): employee_id = self.schema.insert_resource('employee', {'name': 'sailor', 'age': 41}, 'employees') division_id = self.schema.insert_resource('division', {'name': 'sales', 'yearly_sales': 10}, 'divisions') self.schema.update_resource_fields('employee', employee_id, {'division': division_id}) tree = parse("self.division", self.schema.specs['employee']) self.assertEquals(self.schema.specs['division'], tree.infer_type()) self.assertFalse(tree.is_collection()) calculated = tree.calculate(employee_id) self.assertEquals({ '_id': self.schema.decodeid(division_id), '_grants': [], '_canonical_url': '/divisions/%s' % division_id, 'name': 'sales', 'yearly_sales': 10, '_parent_canonical_url': '/', '_parent_field_name': 'divisions', '_parent_id': None, '_parent_type': 'root', }, calculated) def test_aggregate_filtered(self): tree = parse("sum(employees.division[name='sales'].yearly_sales)", self.schema.specs['employee']) employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'age': 41}, 'employees') employee_id_2 = self.schema.insert_resource('employee', {'name': 'bob', 'age': 31}, 'employees') employee_id_3 = self.schema.insert_resource('employee', {'name': 'fred', 'age': 21}, 'employees') division_id_1 = self.schema.insert_resource('division', {'name': 'sales', 'yearly_sales': 100}, 'divisions') division_id_2 = self.schema.insert_resource('division', {'name': 'marketting', 'yearly_sales': 20}, 'divisions') self.schema.update_resource_fields('employee', employee_id_1, {'division': division_id_1}) self.schema.update_resource_fields('employee', employee_id_2, {'division': division_id_1}) self.schema.update_resource_fields('employee', employee_id_3, {'division': division_id_2}) result = tree.calculate(employee_id_1) self.assertEquals(100, result) def test_list(self): employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'age': 41}, 'employees') employee_id_2 = self.schema.insert_resource('employee', {'name': 'bob', 'age': 31}, 'employees') employee_id_3 = self.schema.insert_resource('employee', {'name': 'fred', 'age': 21}, 'employees') division_id_1 = self.schema.insert_resource('division', {'name': 'sales', 'yearly_sales': 100}, 'divisions') division_id_2 = self.schema.insert_resource('division', {'name': 'marketting', 'yearly_sales': 20}, 'divisions') self.schema.update_resource_fields('employee', employee_id_1, {'division': division_id_1}) self.schema.update_resource_fields('employee', employee_id_2, {'division': division_id_1}) self.schema.update_resource_fields('employee', employee_id_3, {'division': division_id_2}) tree = parse("employees.division", self.schema.specs['employee']) # size and offset to be added result = tree.calculate(employee_id_1) # just making up for a lack of ordering division_1 = [r for r in result if r['name'] == 'sales'][0] division_2 = [r for r in result if r['name'] == 'marketting'][0] self.assertEquals('sales', division_1['name']) self.assertEquals('marketting', division_2['name']) self.assertEquals(division_id_1, self.schema.encodeid(division_1['_id'])) self.assertEquals(division_id_2, self.schema.encodeid(division_2['_id'])) def test_reverse_list(self): employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'age': 41}, 'employees') employee_id_2 = self.schema.insert_resource('employee', {'name': 'bob', 'age': 31}, 'employees') employee_id_3 = self.schema.insert_resource('employee', {'name': 'fred', 'age': 21}, 'employees') division_id_1 = self.schema.insert_resource('division', {'name': 'sales', 'yearly_sales': 100}, 'divisions') division_id_2 = self.schema.insert_resource('division', {'name': 'marketting', 'yearly_sales': 20}, 'divisions') self.schema.update_resource_fields('employee', employee_id_1, {'division': division_id_1}) self.schema.update_resource_fields('employee', employee_id_2, {'division': division_id_1}) self.schema.update_resource_fields('employee', employee_id_3, {'division': division_id_2}) tree = parse("self.division.link_employee_division", self.schema.specs['employee']) result = tree.calculate(employee_id_1) self.assertEquals("ned", result[0]['name']) self.assertEquals("bob", result[1]['name']) self.assertEquals(self.schema.decodeid(employee_id_1), result[0]['_id']) self.assertEquals(self.schema.decodeid(employee_id_2), result[1]['_id']) def test_spec_hier_error(self): employee_id = self.schema.insert_resource('employee', {'name': 'sailor'}, 'employees') division_id = self.schema.insert_resource('division', {'name': 'sales', 'yearly_sales': 10}, 'divisions') self.schema.update_resource_fields('employee', employee_id, {'division': division_id}) tree = parse("self.name", self.schema.specs['employee']) aggregation, spec, is_aggregate = tree.aggregation(employee_id) # unsure how this guy fits in exactly self.assertEquals([ {'$match': {'_id': self.schema.decodeid(employee_id)}}, {'$project': {'name': True}} ], aggregation) def test_nonexistant_field_in_calc(self): try: tree = parse("self.nonexistant", self.schema.specs['employee']) self.fail("should have thrown") except SyntaxError as e: self.assertEquals("No such field nonexistant in employee", str(e)) def test_validate_condition_nofield(self): try: tree = parse("employees[total_nonexistant>100]", self.schema.specs['employee']) tree.validate() self.fail("should have thrown") except SyntaxError as e: self.assertEquals("Resource employee has no field total_nonexistant", str(e)) def test_validate_const_type(self): try: tree = parse("employees[age>'str']", self.schema.specs['employee']) tree.validate() self.fail("should have thrown") except SyntaxError as e: self.assertEquals('Cannot compare "int > str"', str(e)) def test_validate_collection(self): try: tree = parse("employees.nonexistant", self.schema.specs['employee']) self.fail("should have thrown") except SyntaxError as e: self.assertEquals('No such field nonexistant in employee', str(e)) def test_validate_filtered_collection(self): try: tree = parse("employees[age>21].nonexistant", self.schema.specs['employee']) self.fail("should have thrown") except Exception as e: self.assertEquals('No such field nonexistant in employee', str(e)) def test_validate_ternary(self): try: tree = parse("max(employees.age) < 50 -> 'young' : 14", self.schema.specs['employee']) tree.validate() self.fail("should have thrown") except SyntaxError as e: self.assertEquals('Both sides of ternary must return same type', str(e)) def test_validate_resource_ternary(self): try: tree = parse("max(employees.age) < 50 -> employees.age : divisions.sections", self.schema.specs['employee']) tree.validate() self.fail("should have thrown") except SyntaxError as e: self.assertEquals('Both sides of ternary must return same type (int != section)', str(e)) def test_nonexistant_root_collection(self): try: parse("nonexistant", self.schema.specs['employee']) self.fail("should have thrown") except SyntaxError as e: self.assertEquals("Cannot parse expression: nonexistant", str(e)) def test_aggregation(self): employee_id = self.schema.insert_resource('employee', {'name': 'sailor', 'age': 41}, 'employees') division_id = self.schema.insert_resource('division', {'name': 'sales', 'yearly_sales': 10}, 'divisions') self.schema.update_resource_fields('employee', employee_id, {'division': division_id}) tree = parse("employees[age>40].division[name='sales'].yearly_sales", self.schema.specs['employee']) aggregation, spec, is_aggregate = tree.aggregation(employee_id) self.assertEquals([ {'$match': {'$and': [{'_parent_field_name': 'employees'}, {'_parent_canonical_url': '/'}]}}, {'$match': {'age': {'$gt': 40}}}, {'$lookup': {'as': '_field_division', 'foreignField': '_id', 'from': 'resource_division', 'localField': 'division'}}, {'$group': {'_id': '$_field_division'}}, {'$unwind': '$_id'}, {'$replaceRoot': {'newRoot': '$_id'}}, {'$match': {'name': {'$eq': 'sales'}}}, {'$project': {'yearly_sales': True}}], aggregation) self.assertEquals(self.schema.specs['division'].fields['yearly_sales'], spec) def test_conditions_multiple(self): employee_spec = self.schema.specs['employee'] self.schema.add_field(employee_spec, 'salary', 'int') employee_id = self.schema.insert_resource('employee', {'name': 'sailor', 'age': 41, 'salary': 100}, 'employees') tree = parse("employees[age>40 & salary>99].name", self.schema.specs['employee']) aggregation, spec, is_aggregate = tree.aggregation(employee_id) self.assertEquals([ {'$match': {'$and': [{'_parent_field_name': 'employees'}, {'_parent_canonical_url': '/'}]}}, {'$match': {'$and' : [ {'age': {'$gt': 40}}, {'salary': {'$gt': 99}}]}}, {'$project': {'name': True}}], aggregation) self.assertEquals(self.schema.specs['employee'].fields['name'], spec) def test_conditions_multiple_or(self): employee_spec = self.schema.specs['employee'] self.schema.add_field(employee_spec, 'salary', 'int') employee_id = self.schema.insert_resource('employee', {'name': 'sailor', 'age': 41, 'salary': 100}, 'employees') tree = parse("employees[age>40 | salary>99].name", self.schema.specs['employee']) aggregation, spec, is_aggregate = tree.aggregation(employee_id) self.assertEquals([ {'$match': {'$and': [{'_parent_field_name': 'employees'}, {'_parent_canonical_url': '/'}]}}, {'$match': {'$or' : [ {'age': {'$gt': 40}}, {'salary': {'$gt': 99}}]}}, {'$project': {'name': True}}], aggregation) self.assertEquals(self.schema.specs['employee'].fields['name'], spec) def test_aggregation_self(self): employee_id = self.schema.insert_resource('employee', {'name': 'sailor', 'age': 41}, 'employees') division_id = self.schema.insert_resource('division', {'name': 'sales', 'yearly_sales': 10}, 'divisions') tree = parse("self.division[name='sales'].yearly_sales", self.schema.specs['employee']) aggregation, spec, is_aggregate = tree.aggregation(employee_id) self.assertEquals([ {'$match': {'_id': self.schema.decodeid(employee_id)}}, {'$lookup': {'as': '_field_division', 'foreignField': '_id', 'from': 'resource_division', 'localField': 'division'}}, {'$group': {'_id': '$_field_division'}}, {'$unwind': '$_id'}, {'$replaceRoot': {'newRoot': '$_id'}}, {'$match': {'name': {'$eq': 'sales'}}}, {'$project': {'yearly_sales': True}}], aggregation) self.assertEquals(self.schema.specs['division'].fields['yearly_sales'], spec) def test_aggregates(self): # entities[name=self.other[resolve='me',first=True]] # parents[name='ned'].entities[averagePay>average(self.children[self.type='boss'].pay)] pass def test_calc_operators(self): employee_spec = self.schema.specs['employee'] employee_spec.fields["salary"] = Field("salary", "int") employee_spec.fields["tax"] = Field("tax", "int") employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'salary': 10, 'tax': 2}, 'employees') tree = parse("self.salary + self.tax", employee_spec) self.assertEquals(12, tree.calculate(employee_id_1)) tree = parse("self.salary - self.tax", employee_spec) self.assertEquals(8, tree.calculate(employee_id_1)) tree = parse("self.salary * self.tax", employee_spec) self.assertEquals(20, tree.calculate(employee_id_1)) tree = parse("self.salary / self.tax", employee_spec) self.assertEquals(5, tree.calculate(employee_id_1)) tree = parse("self.salary < self.tax", employee_spec) self.assertTrue(False is tree.calculate(employee_id_1)) tree = parse("self.salary > self.tax", employee_spec) self.assertTrue(True is tree.calculate(employee_id_1)) tree = parse("self.salary = self.tax", employee_spec) self.assertTrue(False is tree.calculate(employee_id_1)) tree = parse("self.salary <= self.tax", employee_spec) self.assertTrue(False is tree.calculate(employee_id_1)) tree = parse("self.salary >= self.tax", employee_spec) self.assertTrue(True is tree.calculate(employee_id_1)) def test_calc_nones(self): employee_spec = self.schema.specs['employee'] employee_spec.fields["salary"] = Field("salary", "int") employee_spec.fields["tax"] = Field("tax", "int") employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'salary': 10, 'tax': None}, 'employees') tree = parse("self.salary + self.tax", employee_spec) self.assertEquals(10, tree.calculate(employee_id_1)) tree = parse("self.salary - self.tax", employee_spec) self.assertEquals(10, tree.calculate(employee_id_1)) tree = parse("self.salary * self.tax", employee_spec) self.assertEquals(None, tree.calculate(employee_id_1)) # Going with None instead of NaN for now tree = parse("self.salary / self.tax", employee_spec) self.assertEquals(None, tree.calculate(employee_id_1)) def test_function_call_param_list(self): employee_spec = self.schema.specs['employee'] employee_spec.fields["salary"] = Field("salary", "float") employee_spec.fields["tax"] = Field("tax", "float") employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'salary': 10.6, 'tax': 2.4}, 'employees') tree = parse("round(self.salary + self.tax, 2)", employee_spec) self.assertEquals(13, tree.calculate(employee_id_1)) def test_function_basic(self): employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned'}, 'employees') employee_spec = self.schema.specs['employee'] tree = parse("round(14.14)", employee_spec) self.assertEquals(14, tree.calculate(employee_id_1)) def test_function_call_param_list_multiple_calcs(self): employee_spec = self.schema.specs['employee'] employee_spec.fields["salary"] = Field("salary", "float") employee_spec.fields["tax"] = Field("tax", "float") employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'salary': 10.6, 'tax': 2.4}, 'employees') tree = parse("round(self.salary) + round(self.tax)", employee_spec) self.assertEquals(13, tree.calculate(employee_id_1)) def test_function_within_a_function(self): employee_spec = self.schema.specs['employee'] employee_spec.fields["salary"] = Field("salary", "float") employee_spec.fields["tax"] = Field("tax", "float") employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'salary': 10.12345}, 'employees') tree = parse("round(round(self.salary, 4), 3)", employee_spec) self.assertEquals(10.123, tree.calculate(employee_id_1)) def test_ternary_condition(self): employee_spec = self.schema.specs['employee'] employee_spec.fields["salary"] = Field("salary", "float") employee_spec.fields["tax"] = Field("tax", "float") employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'salary': 10.6, 'tax': 2.4}, 'employees') tree = parse("self.salary < 2 -> 5 : 10", employee_spec) self.assertEquals(10, tree.calculate(employee_id_1)) tree = parse("self.salary > 10 -> 5 : 10", employee_spec) self.assertEquals(5, tree.calculate(employee_id_1)) tree = parse("(self.salary + 5) > 15 -> 5 : 10", employee_spec) self.assertEquals(5, tree.calculate(employee_id_1)) def test_ternary_condition_rhs(self): employee_spec = self.schema.specs['employee'] employee_spec.fields["salary"] = Field("salary", "float") employee_spec.fields["tax"] = Field("tax", "float") employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'salary': 10.6, 'tax': 2.4}, 'employees') tree = parse("self.salary > self.tax -> 'greater' : 'wrong'", employee_spec) self.assertEquals('greater', tree.calculate(employee_id_1)) tree = parse("self.salary < (self.tax + 10) -> 'less' : 'wrong'", employee_spec) self.assertEquals('less', tree.calculate(employee_id_1)) def test_ternary_condition_resource(self): employee_spec = self.schema.specs['employee'] employee_spec.fields["salary"] = Field("salary", "float") employee_spec.fields["tax"] = Field("tax", "float") employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'salary': 10.6, 'tax': 2.4}, 'employees') tree = parse("self.salary > 10 -> self.salary : 0", employee_spec) self.assertEquals(10.6, tree.calculate(employee_id_1)) tree = parse("self.salary < 10 -> 0 : self.tax", employee_spec) self.assertEquals(2.4, tree.calculate(employee_id_1)) def test_ternary_resource_plus_const(self): employee_spec = self.schema.specs['employee'] employee_spec.fields["salary"] = Field("salary", "float") employee_spec.fields["tax"] = Field("tax", "float") employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'salary': 10.6, 'tax': 2.4}, 'employees') tree = parse("self.salary > 10 -> 11 : self.salary", employee_spec) self.assertEquals(11, tree.calculate(employee_id_1)) tree = parse("self.salary < 10 -> self.tax: 12", employee_spec) self.assertEquals(12, tree.calculate(employee_id_1)) def test_switch(self): employee_spec = self.schema.specs['employee'] employee_spec.fields["salary_range"] = Field("salary_range", "str") employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'salary_range': 'upper'}, 'employees') employee_id_2 = self.schema.insert_resource('employee', {'name': 'bob', 'salary_range': 'lower'}, 'employees') tree = parse("self.salary_range -> ('upper': 20.0, 'lower': 40.0)", employee_spec) self.assertEquals(20.0, tree.calculate(employee_id_1)) self.assertEquals(40.0, tree.calculate(employee_id_2)) def test_switch_longer_list(self): employee_spec = self.schema.specs['employee'] employee_spec.fields["salary_range"] = Field("salary_range", "str") employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'salary_range': 'upper'}, 'employees') employee_id_2 = self.schema.insert_resource('employee', {'name': 'bob', 'salary_range': 'lower'}, 'employees') tree = parse("self.salary_range -> ('upper': 20.0, 'lower': 40.0, 'middling': 30.0)", employee_spec) self.assertEquals(20.0, tree.calculate(employee_id_1)) self.assertEquals(40.0, tree.calculate(employee_id_2)) def test_switch_basic(self): employee_spec = self.schema.specs['employee'] employee_spec.fields["salary_range"] = Field("salary_range", "str") employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'salary_range': 'upper'}, 'employees') employee_id_2 = self.schema.insert_resource('employee', {'name': 'bob', 'salary_range': 'lower'}, 'employees') tree = parse("self.salary_range -> ('upper': 20.0)", employee_spec) self.assertEquals(20.0, tree.calculate(employee_id_1)) self.assertEquals(None, tree.calculate(employee_id_2)) def test_switch_field_refs(self): employee_spec = self.schema.specs['employee'] employee_spec.fields["upper_salary"] = Field("salary", "float") employee_spec.fields["lower_salary"] = Field("salary", "float") employee_spec.fields["salary_range"] = Field("salary_range", "str") employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'salary_range': 'upper', 'upper_salary': 50000, 'lower_salary': 40000}, 'employees') employee_id_2 = self.schema.insert_resource('employee', {'name': 'bob', 'salary_range': 'lower', 'upper_salary': 30000, 'lower_salary': 20000}, 'employees') tree = parse("self.salary_range -> ('upper': self.upper_salary, 'lower': self.lower_salary)", employee_spec) self.assertEquals(50000, tree.calculate(employee_id_1)) self.assertEquals(20000, tree.calculate(employee_id_2)) def test_switch_calcs(self): employee_spec = self.schema.specs['employee'] employee_spec.fields["upper_salary_level"] = Field("salary", "float") employee_spec.fields["lower_salary_level"] = Field("salary", "float") employee_spec.fields["salary"] = Field("salary", "float") employee_spec.fields["salary_range"] = Field("salary_range", "str") employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'salary_range': 'upper', 'salary': 50000, 'upper_salary_level': 0.4, 'lower_salary_level': 0.2}, 'employees') employee_id_2 = self.schema.insert_resource('employee', {'name': 'bob', 'salary_range': 'lower', 'salary': 30000, 'upper_salary_level': 0.5, 'lower_salary_level': 0.3}, 'employees') tree = parse("self.salary_range -> ('upper': (self.salary * self.upper_salary_level), 'lower': (self.salary * self.lower_salary_level))", employee_spec) self.assertEquals(20000, tree.calculate(employee_id_1)) self.assertEquals(9000, tree.calculate(employee_id_2)) def test_math_functions(self): employee_spec = self.schema.specs['employee'] employee_spec.fields["salary"] = Field("salary", "float") employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'salary': 20}, 'employees') self.schema.insert_resource('employee', {'name': 'bob', 'salary': 10}, 'employees') self.schema.insert_resource('employee', {'name': 'bil', 'salary': 30}, 'employees') # max tree = parse("max(employees.salary)", employee_spec) self.assertEquals(30, tree.calculate(employee_id_1)) # min tree = parse("min(employees.salary)", employee_spec) self.assertEquals(10, tree.calculate(employee_id_1)) # avg tree = parse("average(employees.salary)", employee_spec) self.assertEquals(20, tree.calculate(employee_id_1)) # sum tree = parse("sum(employees.salary)", employee_spec) self.assertEquals(60, tree.calculate(employee_id_1)) def test_extra_math(self): employee_spec = self.schema.specs['employee'] employee_spec.fields["salary"] = Field("salary", "float") employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'salary': 20.1234}, 'employees') self.schema.insert_resource('employee', {'name': 'ned', 'salary': 10.5678}, 'employees') self.schema.insert_resource('employee', {'name': 'bil', 'salary': 30.7777}, 'employees') tree = parse("round(sum(employees.salary), 2) + round(max(employees.salary))", employee_spec) self.assertEquals(92.47, tree.calculate(employee_id_1)) tree = parse("round(sum(employees[name='ned'].salary), 2) + round(max(employees.salary))", employee_spec) self.assertEquals(61.69, tree.calculate(employee_id_1)) tree = parse("round(sum(employees[name='ned'].salary), 2) + round(max(employees[name='ned'].salary))", employee_spec) self.assertEquals(50.69, tree.calculate(employee_id_1)) # filter nones # filter generic aggregates (filter(aggregate, name='paul', age>20)) # space out range # cap (ceil, floor) aggregates # min max range def test_return_type(self): employee_spec = self.schema.specs['employee'] division_spec = self.schema.specs['division'] tree = parse("employees[age>40].division[name='sales'].yearly_sales", employee_spec) self.assertEquals(division_spec.fields['yearly_sales'], tree.infer_type()) self.assertTrue(tree.is_collection()) tree = parse("employees[age>40].division[name='sales']", employee_spec) # it's a link spec self.assertEquals(division_spec, tree.infer_type()) self.assertTrue(tree.is_collection()) tree = parse("self.division", employee_spec) self.assertEquals(division_spec, tree.infer_type()) self.assertFalse(tree.is_collection()) def test_root_collection_aggregates(self): tree = parse("employees.division", self.schema.specs['division']) employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'age': 41}, 'employees') employee_id_2 = self.schema.insert_resource('employee', {'name': 'bob', 'age': 31}, 'employees') employee_id_3 = self.schema.insert_resource('employee', {'name': 'fred', 'age': 21}, 'employees') division_id_1 = self.schema.insert_resource('division', {'name': 'sales', 'yearly_sales': 100}, 'divisions') division_id_2 = self.schema.insert_resource('division', {'name': 'marketting', 'yearly_sales': 20}, 'divisions') self.schema.update_resource_fields('employee', employee_id_1, {'division': division_id_1}) self.schema.update_resource_fields('employee', employee_id_2, {'division': division_id_1}) self.schema.update_resource_fields('employee', employee_id_3, {'division': division_id_2}) result = tree.calculate(division_id_1) self.assertEquals(2, len(result)) def test_calculate_toplevel_rootresourceref(self): tree = parse("employees[name='bob']", self.schema.specs['division']) division_id_1 = self.schema.insert_resource('division', {'name': 'sales', 'yearly_sales': 100}, 'divisions') employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'age': 41}, 'employees') employee_id_2 = self.schema.insert_resource('employee', {'name': 'bob', 'age': 31}, 'employees') employee_id_3 = self.schema.insert_resource('employee', {'name': 'fred', 'age': 21}, 'employees') result = tree.calculate(division_id_1) self.assertEquals(1, len(result)) self.assertEquals('bob', result[0]['name']) # work with either resource type as starting point result = tree.calculate(employee_id_1) self.assertEquals(1, len(result)) self.assertEquals('bob', result[0]['name']) def test_parent_link(self): tree = parse("self.parent_division_sections", self.schema.specs['section']) division_id_1 = self.schema.insert_resource('division', {'name': 'sales', 'yearly_sales': 100}, 'divisions') division_id_2 = self.schema.insert_resource('division', {'name': 'marketting', 'yearly_sales': 20}, 'divisions') section_id_1 = self.schema.insert_resource('section', {'name': 'appropriation'}, parent_type='division', parent_id=division_id_1, parent_field_name='sections') result = tree.calculate(section_id_1) self.assertEquals('sales', result['name']) def test_parse_url(self): tree = parse("employees", self.schema.root) employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'age': 41}, 'employees') employee_id_2 = self.schema.insert_resource('employee', {'name': 'bob', 'age': 31}, 'employees') employee_id_3 = self.schema.insert_resource('employee', {'name': 'fred', 'age': 21}, 'employees') division_id_1 = self.schema.insert_resource('division', {'name': 'sales', 'yearly_sales': 100}, 'divisions') division_id_2 = self.schema.insert_resource('division', {'name': 'marketting', 'yearly_sales': 20}, 'divisions') self.schema.update_resource_fields('employee', employee_id_1, {'division': division_id_1}) self.schema.update_resource_fields('employee', employee_id_2, {'division': division_id_1}) self.schema.update_resource_fields('employee', employee_id_3, {'division': division_id_2}) result = tree.calculate(employee_id_1) self.assertEquals(3, len(result)) def test_linkcollection(self): employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'age': 41}, 'employees') employee_id_2 = self.schema.insert_resource('employee', {'name': 'bob', 'age': 31}, 'employees') employee_id_3 = self.schema.insert_resource('employee', {'name': 'fred', 'age': 21}, 'employees') division_id_1 = self.schema.insert_resource('division', {'name': 'sales', 'yearly_sales': 100}, 'divisions') division_id_2 = self.schema.insert_resource('division', {'name': 'marketting', 'yearly_sales': 20}, 'divisions') tree = parse('self.parttimers', self.schema.specs['division']) self.assertEquals([], tree.calculate(division_id_1)) self.schema.create_linkcollection_entry('division', division_id_1, 'parttimers', employee_id_1) self.assertEquals([{ '_id': self.schema.decodeid(employee_id_1), '_grants': [], '_canonical_url': '/employees/%s' % employee_id_1, '_parent_canonical_url': '/', '_parent_field_name': 'employees', '_parent_id': None, '_parent_type': 'root', 'age': 41, 'name': 'ned'}], tree.calculate(division_id_1)) def test_linkcollection_filter(self): employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'age': 41}, 'employees') employee_id_2 = self.schema.insert_resource('employee', {'name': 'bob', 'age': 31}, 'employees') employee_id_3 = self.schema.insert_resource('employee', {'name': 'fred', 'age': 21}, 'employees') division_id_1 = self.schema.insert_resource('division', {'name': 'sales', 'yearly_sales': 100}, 'divisions') self.schema.create_linkcollection_entry('division', division_id_1, 'parttimers', employee_id_1) self.schema.create_linkcollection_entry('division', division_id_1, 'parttimers', employee_id_2) self.schema.create_linkcollection_entry('division', division_id_1, 'parttimers', employee_id_3) tree = parse('self.parttimers[age>30]', self.schema.specs['division']) self.assertEquals([ {'_id': self.schema.decodeid(employee_id_1), '_grants': [], '_canonical_url': '/employees/%s' % employee_id_1, '_parent_canonical_url': '/', '_parent_field_name': 'employees', '_parent_id': None, '_parent_type': 'root', 'age': 41, 'name': 'ned'}, {'_id': self.schema.decodeid(employee_id_2), '_grants': [], '_canonical_url': '/employees/%s' % employee_id_2, '_parent_canonical_url': '/', '_parent_field_name': 'employees', '_parent_id': None, '_parent_type': 'root', 'age': 31, 'name': 'bob'}, ], tree.calculate(division_id_1)) def test_linkcollection_reverse_aggregation(self): employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'age': 41}, 'employees') employee_id_2 = self.schema.insert_resource('employee', {'name': 'bob', 'age': 31}, 'employees') employee_id_3 = self.schema.insert_resource('employee', {'name': 'fred', 'age': 21}, 'employees') division_id_1 = self.schema.insert_resource('division', {'name': 'sales', 'yearly_sales': 100}, 'divisions') self.schema.create_linkcollection_entry('division', division_id_1, 'parttimers', employee_id_1) self.schema.create_linkcollection_entry('division', division_id_1, 'parttimers', employee_id_2) self.schema.create_linkcollection_entry('division', division_id_1, 'parttimers', employee_id_3) tree = parse('self.parttimers.age', self.schema.specs['division']) # testing reverse aggregation when a resource in the "middle" of the calc is added/removed/updated # Note: the first aggregation is the "tracker" aggregation self.assertEqual([ [ {'$lookup': {'as': '_field_parttimers', 'foreignField': 'parttimers._id', 'from': 'resource_division', 'localField': '_id'}}, {'$group': {'_id': '$_field_parttimers'}}, {'$unwind': '$_id'}, {'$replaceRoot': {'newRoot': '$_id'}}], [ {'$match': {'_id': self.schema.decodeid(employee_id_2)}}, {'$lookup': {'as': '_field_parttimers', 'foreignField': 'parttimers._id', 'from': 'resource_division', 'localField': '_id'}}, {'$group': {'_id': '$_field_parttimers'}}, {'$unwind': '$_id'}, {'$replaceRoot': {'newRoot': '$_id'}} ], ], tree.build_reverse_aggregations(self.schema.specs['employee'], employee_id_2, 'division', 'parttimers_age')) def test_dependencies(self): employee_spec = self.schema.specs['employee'] division_spec = self.schema.specs['division'] tree = parse("employees[age>40].division[name='sales'].yearly_sales", employee_spec) self.assertEquals( tree.get_resource_dependencies(), { 'root.employees', 'employee.division', 'employee.age', 'division.name', 'division.yearly_sales', }) tree = parse("self.age", employee_spec) self.assertEquals( tree.get_resource_dependencies(), { 'employee.age', }) tree = parse("self.parttimers", division_spec) self.assertEquals( tree.get_resource_dependencies(), { 'division.parttimers', }) tree = parse("self.division.parttimers", employee_spec) self.assertEquals( tree.get_resource_dependencies(), { 'employee.division', 'division.parttimers', }) # referenced calc self.schema.add_calc(employee_spec, 'my_division', 'self.division.link_employee_division') tree = parse("self.my_division", employee_spec) self.assertEquals( tree.get_resource_dependencies(), { 'employee.my_division', }) # reverse link tree = parse("self.division.link_employee_division.name", employee_spec) self.assertEquals( tree.get_resource_dependencies(), { 'employee.division', 'employee.name', }) def test_gte(self): employee_spec = self.schema.specs['employee'] division_spec = self.schema.specs['division'] tree = parse("employees[age>=40]", employee_spec) employee_id = self.schema.insert_resource('employee', {'name': 'sailor', 'age': 40}, 'employees') calculated = tree.calculate(employee_id) self.assertEquals([{ '_id': self.schema.decodeid(employee_id), '_grants': [], '_canonical_url': '/employees/%s' % employee_id, 'name': 'sailor', 'age': 40, '_parent_canonical_url': '/', '_parent_field_name': 'employees', '_parent_id': None, '_parent_type': 'root', }], calculated) def test_lte(self): employee_spec = self.schema.specs['employee'] division_spec = self.schema.specs['division'] tree = parse("employees[age<=40]", employee_spec) employee_id = self.schema.insert_resource('employee', {'name': 'sailor', 'age': 40}, 'employees') calculated = tree.calculate(employee_id) self.assertEquals([{ '_id': self.schema.decodeid(employee_id), '_grants': [], '_canonical_url': '/employees/%s' % employee_id, 'name': 'sailor', 'age': 40, '_parent_canonical_url': '/', '_parent_field_name': 'employees', '_parent_id': None, '_parent_type': 'root', }], calculated) def test_search_filter(self): employee_spec = self.schema.specs['employee'] tree = parse_filter("age<=40", employee_spec) employee_id = self.schema.insert_resource('employee', {'name': 'sailor', 'age': 40}, 'employees') self.assertEqual({'age': {'$lte': 40}}, tree.condition_aggregation(employee_spec, employee_id)) def test_search_filter_multiple(self): employee_spec = self.schema.specs['employee'] tree = parse_filter("age<=40 & name='sailor'", employee_spec) employee_id = self.schema.insert_resource( 'employee', {'name': 'sailor', 'age': 40}, 'employees') self.assertEqual( {'$and': [{'age': {'$lte': 40}}, {'name': {'$eq': 'sailor'}}]}, tree.condition_aggregation(employee_spec, employee_id)) def test_search_filter_commas_are_nice(self): employee_spec = self.schema.specs['employee'] tree = parse_filter("age<=40, name='sailor'", employee_spec) employee_id = self.schema.insert_resource( 'employee', {'name': 'sailor', 'age': 40}, 'employees') self.assertEqual( {'$or': [{'age': {'$lte': 40}}, {'name': {'$eq': 'sailor'}}]}, tree.condition_aggregation(employee_spec, employee_id)) def test_search_filter_like_string(self): employee_spec = self.schema.specs['employee'] tree = parse_filter("name~'sam'", employee_spec) self.assertEqual( {'name': {'$options': 'i', '$regex': 'sam'}}, tree.condition_aggregation(employee_spec, None)) def test_search_filter_like_string_or(self): employee_spec = self.schema.specs['employee'] tree = parse_filter("name~'sam',name~'bob'", employee_spec) self.assertEqual( {'$or': [{'name': {'$options': 'i', '$regex': 'sam'}}, {'name': {'$options': 'i', '$regex': 'bob'}}]}, tree.condition_aggregation(employee_spec, None)) def test_search_filter_commas_and_or(self): employee_spec = self.schema.specs['employee'] tree = parse_filter("age<=40, name='sailor' | name='weaver'", employee_spec) employee_id = self.schema.insert_resource( 'employee', {'name': 'sailor', 'age': 40}, 'employees') self.assertEqual( {'$or': [{'$or': [{'age': {'$lte': 40}}, {'name': {'$eq': 'sailor'}}]}, {'name': {'$eq': 'weaver'}}]}, tree.condition_aggregation(employee_spec, employee_id)) def test_search_filter_resource_ref(self): employee_spec = self.schema.specs['employee'] tree = parse("name=self.pseudoname", employee_spec) employee_id = self.schema.insert_resource( 'employee', {'name': 'sailor', 'pseudoname': 'bob'}, 'employees') self.assertEqual( {'name': {'$eq': 'bob'}}, tree.condition_aggregation(employee_spec, employee_id)) def test_resources_in_different_collections(self): tree = parse("employees", self.schema.root) employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'age': 41}, 'employees') employee_id_2 = self.schema.insert_resource('employee', {'name': 'bob', 'age': 31}, 'employees') employee_id_3 = self.schema.insert_resource('employee', {'name': 'fred', 'age': 21}, 'employees') division_id_1 = self.schema.insert_resource('division', {'name': 'sales', 'yearly_sales': 100}, 'divisions') employee_id_4 = self.schema.insert_resource('employee', {'name': 'pete', 'age': 60}, 'partners', 'division', division_id_1) result = tree.calculate(employee_id_1) self.assertEquals(3, len(result)) employee_spec = self.schema.specs['employee'] tree = parse("max(employees.age)", employee_spec) self.assertEquals(41, tree.calculate(employee_id_1)) def test_validate_root(self): try: tree = parse("schmemployees", self.schema.root) tree.validate() self.fail("should have thrown") except SyntaxError as se: self.assertEqual("Cannot parse expression: schmemployees", str(se)) def test_validate_condition(self): try: tree = parse("employees[nope>21]", self.schema.root) tree.validate() self.fail("should have thrown") except SyntaxError as se: self.assertEqual("Resource employee has no field nope", str(se)) def test_calc_result(self): employee_spec = self.schema.specs['employee'] division_spec = self.schema.specs['division'] tree = parse("max(employees[age>=40].age) + 15", employee_spec) employee_id = self.schema.insert_resource('employee', {'name': 'sailor', 'age': 40}, 'employees') calculated = tree.calculate(employee_id) self.assertEqual(55, calculated) def test_basic_calc_result(self): employee_spec = self.schema.specs['employee'] division_spec = self.schema.specs['division'] tree = parse("10 + (15 / 3)", employee_spec) employee_id = self.schema.insert_resource('employee', {'name': 'sailor', 'age': 40}, 'employees') calculated = tree.calculate(employee_id) self.assertEqual(15, calculated) def test_calc_datetime(self): employee_spec = self.schema.specs['employee'] division_spec = self.schema.specs['division'] employee_spec.fields["created"] = Field("created", "datetime") employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'created': "2021-12-01"}, 'employees') employee_id_2 = self.schema.insert_resource('employee', {'name': 'bob', 'created': "2021-12-01"}, 'employees') employee_id_3 = self.schema.insert_resource('employee', {'name': 'bil', 'created': "2021-12-01"}, 'employees') tree = parse("self.created - days(2)", employee_spec) calculated = tree.calculate(employee_id_1) self.assertEqual(datetime(2021, 11, 29), calculated) tree = parse("self.created - hours(2)", employee_spec) calculated = tree.calculate(employee_id_1) self.assertEqual(datetime(2021, 11, 30, 22), calculated) tree = parse("self.created - minutes(2)", employee_spec) calculated = tree.calculate(employee_id_1) self.assertEqual(datetime(2021, 11, 30, 23, 58), calculated) def test_calc_datetime_comparison(self): employee_spec = self.schema.specs['employee'] division_spec = self.schema.specs['division'] employee_spec.fields["created"] = Field("created", "datetime") division_spec.fields["cutoff"] = Field("cutoff", "datetime") division_id_1 = self.schema.insert_resource('division', {'name': 'sales', 'cutoff': "2021-12-12"}, 'divisions') employee_id_1 = self.schema.insert_resource('employee', {'name': 'ned', 'created': "2021-12-01", "division": division_id_1}, 'employees') employee_id_2 = self.schema.insert_resource('employee', {'name': 'bob', 'created': "2021-12-14", "division": division_id_1}, 'employees') employee_id_3 = self.schema.insert_resource('employee', {'name': 'bil', 'created': "2021-12-24", "division": division_id_1}, 'employees') tree = parse("self.link_employee_division[created>self.cutoff]", division_spec) calculated = tree.calculate(division_id_1) self.assertEqual(['bob', 'bil'], [e['name'] for e in calculated]) def test_add_reverse_links(self): section_spec = self.schema.specs['section'] tree = parse('self.name + (self.parent_division_sections.name)', section_spec) def test_replace_whitespace_with_spaces(self): section_spec = self.schema.specs['section'] tree = parse('self.name + \t(\nself.parent_division_sections.name\n)', section_spec)
class UpdaterTest(unittest.TestCase): def setUp(self): self.maxDiff = None client = MongoClient() client.drop_database('metaphor2_test_db') self.db = client.metaphor2_test_db self.schema = Schema(self.db) self.schema.create_initial_schema() self.updater = Updater(self.schema) self.employee_spec = self.schema.create_spec('employee') self.schema.create_field('employee', 'name', 'str') self.schema.create_field('employee', 'age', 'int') self.division_spec = self.schema.create_spec('division') self.schema.create_field('division', 'name', 'str') self.schema.create_field('division', 'employees', 'collection', 'employee') self.schema.create_field('root', 'divisions', 'collection', 'division') def test_updater(self): self.schema.add_calc(self.division_spec, 'older_employees', 'self.employees[age>30]') division_id_1 = self.schema.insert_resource('division', {'name': 'sales'}, 'divisions') employee_id_1 = self.schema.insert_resource('employee', { 'name': 'bob', 'age': 31 }, 'employees', 'division', division_id_1) self.updater.update_calc('division', 'older_employees', division_id_1) division_data = self.db.resource_division.find_one() self.assertEquals( { '_id': self.schema.decodeid(division_id_1), '_grants': [], '_canonical_url': '/divisions/%s' % division_id_1, 'name': 'sales', '_parent_canonical_url': '/', '_parent_field_name': 'divisions', '_parent_id': None, '_parent_type': 'root', 'older_employees': [ObjectId(employee_id_1[2:])], }, division_data) employee_id_2 = self.schema.insert_resource('employee', { 'name': 'Ned', 'age': 41 }, 'employees', 'division', division_id_1) # check again self.updater.update_calc('division', 'older_employees', division_id_1) division_data = self.db.resource_division.find_one() self.assertEquals( { '_id': self.schema.decodeid(division_id_1), '_grants': [], '_canonical_url': '/divisions/%s' % division_id_1, 'name': 'sales', '_parent_canonical_url': '/', '_parent_field_name': 'divisions', '_parent_id': None, '_parent_type': 'root', 'older_employees': [ObjectId(employee_id_1[2:]), ObjectId(employee_id_2[2:])], }, division_data) def test_reverse_aggregation(self): self.schema.add_calc(self.division_spec, 'older_employees', 'self.employees[age>30]') self.schema.add_calc(self.division_spec, 'average_age', 'average(self.employees.age)') division_id_1 = self.schema.insert_resource('division', {'name': 'sales'}, 'divisions') employee_id_1 = self.schema.insert_resource('employee', { 'name': 'bob', 'age': 31 }, 'employees', 'division', division_id_1) division_id_2 = self.schema.insert_resource('division', {'name': 'sales'}, 'divisions') employee_id_2 = self.schema.insert_resource('employee', { 'name': 'bob', 'age': 31 }, 'employees', 'division', division_id_2) average_agg = self.updater.build_reverse_aggregations_to_calc( 'division', 'average_age', self.employee_spec, employee_id_1) self.assertEquals([[ { "$match": { "_id": self.schema.decodeid(employee_id_1) } }, { "$lookup": { "from": "resource_division", "localField": "_parent_id", "foreignField": "_id", "as": "_field_employees", } }, { '$group': { '_id': '$_field_employees' } }, { "$unwind": "$_id" }, { "$replaceRoot": { "newRoot": "$_id" } }, ]], average_agg) affected_ids = self.updater.get_affected_ids_for_resource( 'division', 'average_age', self.employee_spec, employee_id_1) self.assertEquals([self.schema.decodeid(division_id_1)], list(affected_ids)) # check another collection employee_id_3 = self.schema.insert_resource('employee', { 'name': 'bob', 'age': 31 }, 'employees', 'division', division_id_2) affected_ids = self.updater.get_affected_ids_for_resource( 'division', 'average_age', self.employee_spec, employee_id_3) self.assertEquals([self.schema.decodeid(division_id_2)], list(affected_ids)) # different calc older_agg = self.updater.build_reverse_aggregations_to_calc( 'division', 'older_employees', self.employee_spec, employee_id_1) self.assertEquals([[ { "$match": { "_id": self.schema.decodeid(employee_id_1) } }, { "$lookup": { "from": "resource_division", "localField": "_parent_id", "foreignField": "_id", "as": "_field_employees", } }, { '$group': { '_id': '$_field_employees' } }, { "$unwind": "$_id" }, { "$replaceRoot": { "newRoot": "$_id" } }, ]], average_agg) def test_reverse_aggregation_link(self): self.schema.add_field(self.division_spec, 'manager', 'link', 'employee') self.schema.add_calc(self.division_spec, 'manager_age', 'self.manager.age') division_id_1 = self.schema.insert_resource('division', {'name': 'sales'}, 'divisions') employee_id_1 = self.schema.insert_resource('employee', { 'name': 'bob', 'age': 31 }, 'employees', 'division', division_id_1) self.schema.update_resource_fields('division', division_id_1, {'manager': employee_id_1}) agg = self.updater.build_reverse_aggregations_to_calc( 'division', 'manager_age', self.employee_spec, employee_id_1) self.assertEquals([[ { "$match": { "_id": self.schema.decodeid(employee_id_1) } }, { "$lookup": { "from": "resource_division", "localField": "_id", "foreignField": "manager", "as": "_field_manager", } }, { '$group': { '_id': '$_field_manager' } }, { "$unwind": "$_id" }, { "$replaceRoot": { "newRoot": "$_id" } }, ]], agg) # check affected ids affected_ids = self.updater.get_affected_ids_for_resource( 'division', 'manager_age', self.employee_spec, employee_id_1) self.assertEquals([self.schema.decodeid(division_id_1)], list(affected_ids)) # check having two links division_id_2 = self.schema.insert_resource('division', {'name': 'sales'}, 'divisions') self.schema.update_resource_fields('division', division_id_2, {'manager': employee_id_1}) affected_ids = self.updater.get_affected_ids_for_resource( 'division', 'manager_age', self.employee_spec, employee_id_1) self.assertEquals([ self.schema.decodeid(division_id_1), self.schema.decodeid(division_id_2) ], list(affected_ids)) def test_reverse_aggregation_link_collection(self): self.schema.add_field(self.division_spec, 'managers', 'linkcollection', 'employee') self.schema.add_calc(self.division_spec, 'average_manager_age', 'average(self.managers.age)') division_id_1 = self.schema.insert_resource('division', {'name': 'sales'}, 'divisions') employee_id_1 = self.schema.insert_resource('employee', { 'name': 'bob', 'age': 31 }, 'employees', 'division', division_id_1) self.schema.create_linkcollection_entry('division', division_id_1, 'managers', employee_id_1) agg = self.updater.build_reverse_aggregations_to_calc( 'division', 'average_manager_age', self.employee_spec, employee_id_1) self.assertEquals([[ { "$match": { "_id": self.schema.decodeid(employee_id_1) } }, { "$lookup": { "from": "resource_division", "foreignField": "managers._id", "localField": "_id", "as": "_field_managers", } }, { '$group': { '_id': '$_field_managers' } }, { "$unwind": "$_id" }, { "$replaceRoot": { "newRoot": "$_id" } }, ]], agg) # check affected ids affected_ids = self.updater.get_affected_ids_for_resource( 'division', 'average_manager_age', self.employee_spec, employee_id_1) self.assertEquals([self.schema.decodeid(division_id_1)], list(affected_ids)) division_id_2 = self.schema.insert_resource('division', {'name': 'marketting'}, 'divisions') self.schema.create_linkcollection_entry('division', division_id_2, 'managers', employee_id_1) affected_ids = self.updater.get_affected_ids_for_resource( 'division', 'average_manager_age', self.employee_spec, employee_id_1) self.assertEquals([ self.schema.decodeid(division_id_1), self.schema.decodeid(division_id_2) ], list(affected_ids)) def test_reverse_aggregation_calc_through_calc(self): self.schema.add_calc(self.division_spec, 'older_employees', 'self.employees[age>30]') self.schema.add_calc(self.division_spec, 'older_employees_called_ned', 'self.older_employees[name="ned"]') division_id_1 = self.schema.insert_resource('division', {'name': 'sales'}, 'divisions') division_id_2 = self.schema.insert_resource('division', {'name': 'marketting'}, 'divisions') employee_id_1 = self.schema.insert_resource('employee', { 'name': 'bob', 'age': 21 }, 'employees', 'division', division_id_1) employee_id_2 = self.schema.insert_resource('employee', { 'name': 'ned', 'age': 31 }, 'employees', 'division', division_id_1) employee_id_3 = self.schema.insert_resource('employee', { 'name': 'fred', 'age': 41 }, 'employees', 'division', division_id_1) employee_id_4 = self.schema.insert_resource('employee', { 'name': 'sam', 'age': 25 }, 'employees', 'division', division_id_2) employee_id_5 = self.schema.insert_resource('employee', { 'name': 'ned', 'age': 35 }, 'employees', 'division', division_id_2) self.updater.update_calc('division', 'older_employees', division_id_1) self.updater.update_calc('division', 'older_employees_called_ned', division_id_1) self.updater.update_calc('division', 'older_employees', division_id_2) self.updater.update_calc('division', 'older_employees_called_ned', division_id_2) agg = self.updater.build_reverse_aggregations_to_calc( 'division', 'older_employees_called_ned', self.employee_spec, employee_id_2) self.assertEquals([[ { "$match": { "_id": self.schema.decodeid(employee_id_2) } }, { "$lookup": { "from": "resource_division", "foreignField": "older_employees", "localField": "_id", "as": "_field_older_employees", } }, { '$group': { '_id': '$_field_older_employees' } }, { "$unwind": "$_id" }, { "$replaceRoot": { "newRoot": "$_id" } }, ]], agg) # check affected ids affected_ids = self.updater.get_affected_ids_for_resource( 'division', 'older_employees_called_ned', self.employee_spec, employee_id_2) self.assertEquals([self.schema.decodeid(division_id_1)], list(affected_ids)) def test_reverse_aggregation_parent_link(self): self.schema.add_calc(self.employee_spec, 'division_name', 'self.parent_division_employees.name') division_id_1 = self.schema.insert_resource('division', {'name': 'sales'}, 'divisions') employee_id_1 = self.schema.insert_resource('employee', { 'name': 'bob', 'age': 31 }, 'employees', 'division', division_id_1) self.updater.update_calc('employee', 'division_name', employee_id_1) agg = self.updater.build_reverse_aggregations_to_calc( 'employee', 'division_name', self.division_spec, division_id_1) self.assertEquals([[ { "$match": { "_id": self.schema.decodeid(division_id_1) } }, { "$lookup": { "from": "resource_employee", "foreignField": "_parent_id", "localField": "_id", "as": "_field_parent_division_employees", } }, { '$group': { '_id': '$_field_parent_division_employees' } }, { "$unwind": "$_id" }, { "$replaceRoot": { "newRoot": "$_id" } }, ]], agg) # check affected ids affected_ids = self.updater.get_affected_ids_for_resource( 'employee', 'division_name', self.division_spec, division_id_1) self.assertEquals([self.schema.decodeid(employee_id_1)], list(affected_ids)) def test_reverse_aggregation_reverse_link(self): self.schema.add_field(self.division_spec, 'manager', 'link', 'employee') self.schema.add_calc(self.employee_spec, 'divisions_i_manage', 'self.link_division_manager') division_id_1 = self.schema.insert_resource('division', {'name': 'sales'}, 'divisions') division_id_2 = self.schema.insert_resource('division', {'name': 'marketting'}, 'divisions') employee_id_1 = self.schema.insert_resource('employee', { 'name': 'bob', 'age': 31 }, 'employees', 'division', division_id_1) self.schema.update_resource_fields('division', division_id_1, {'manager': employee_id_1}) self.schema.update_resource_fields('division', division_id_2, {'manager': employee_id_1}) self.updater.update_calc('employee', 'divisions_i_manage', employee_id_1) agg = self.updater.build_reverse_aggregations_to_calc( 'employee', 'divisions_i_manage', self.division_spec, division_id_1) self.assertEquals([[ { "$match": { "_id": self.schema.decodeid(division_id_1) } }, { "$lookup": { "from": "resource_employee", "foreignField": "_id", "localField": "manager", "as": "_field_link_division_manager", } }, { '$group': { '_id': '$_field_link_division_manager' } }, { "$unwind": "$_id" }, { "$replaceRoot": { "newRoot": "$_id" } }, ]], agg) division_id_2 = self.schema.insert_resource('division', {'name': 'marketting'}, 'divisions') self.schema.create_linkcollection_entry('division', division_id_2, 'managers', employee_id_1) def test_reverse_aggregation_loopback(self): self.schema.add_field(self.division_spec, 'managers', 'linkcollection', 'employee') self.schema.add_calc(self.employee_spec, 'all_my_subordinates', 'self.link_division_managers.employees') division_id_1 = self.schema.insert_resource('division', {'name': 'sales'}, 'divisions') employee_id_1 = self.schema.insert_resource('employee', { 'name': 'bob', 'age': 21 }, 'employees', 'division', division_id_1) employee_id_2 = self.schema.insert_resource('employee', { 'name': 'ned', 'age': 31 }, 'employees', 'division', division_id_1) employee_id_3 = self.schema.insert_resource('employee', { 'name': 'fred', 'age': 41 }, 'employees', 'division', division_id_1) employee_id_4 = self.schema.insert_resource('employee', { 'name': 'mike', 'age': 51 }, 'employees', 'division', division_id_1) # add manager self.updater.create_linkcollection_entry('division', division_id_1, 'managers', employee_id_1) # bobs addition alters bobs calc self.assertEquals([self.schema.decodeid(employee_id_1)], list( self.updater.get_affected_ids_for_resource( 'employee', 'all_my_subordinates', self.employee_spec, employee_id_1))) # a little unsure of this agg = self.updater.build_reverse_aggregations_to_calc( 'employee', 'all_my_subordinates', self.division_spec, division_id_1) self.assertEquals([[ { "$match": { "_id": self.schema.decodeid(division_id_1) } }, { '$lookup': { 'as': '_field_link_division_managers', 'foreignField': '_id', 'from': 'resource_employee', 'localField': 'managers._id' } }, { '$group': { '_id': '$_field_link_division_managers' } }, { '$unwind': '$_id' }, { "$replaceRoot": { "newRoot": "$_id" } }, ]], agg) def test_reverse_aggregation_simple_collection(self): self.schema.add_calc(self.division_spec, 'all_employees', 'self.employees') division_id_1 = self.schema.insert_resource('division', {'name': 'sales'}, 'divisions') employee_id_1 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, { 'name': 'bob', 'age': 21 }) employee_id_2 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, { 'name': 'ned', 'age': 31 }) self.assertEquals([self.schema.decodeid(division_id_1)], list( self.updater.get_affected_ids_for_resource( 'division', 'all_employees', self.employee_spec, employee_id_1))) def test_reverse_aggregation_switch(self): self.schema.add_calc( self.division_spec, 'all_employees', 'self.name -> ("sales": (self.employees[age > 25]), "marketting": self.employees)' ) division_id_1 = self.schema.insert_resource('division', {'name': 'sales'}, 'divisions') employee_id_1 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, { 'name': 'bob', 'age': 21 }) employee_id_2 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, { 'name': 'ned', 'age': 31 }) self.assertEquals({self.schema.decodeid(division_id_1)}, set( self.updater.get_affected_ids_for_resource( 'division', 'all_employees', self.employee_spec, employee_id_1))) def test_reverse_aggregation_ternary(self): self.schema.add_calc( self.division_spec, 'all_employees', 'self.name = "sales" -> (self.employees[age > 25]) : self.employees' ) division_id_1 = self.schema.insert_resource('division', {'name': 'sales'}, 'divisions') employee_id_1 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, { 'name': 'bob', 'age': 21 }) employee_id_2 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, { 'name': 'ned', 'age': 31 }) self.assertEquals({self.schema.decodeid(division_id_1)}, set( self.updater.get_affected_ids_for_resource( 'division', 'all_employees', self.employee_spec, employee_id_1))) def test_delete_resource_deletes_children(self): division_id_1 = self.schema.insert_resource('division', {'name': 'sales'}, 'divisions') employee_id_1 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, { 'name': 'bob', 'age': 21 }) employee_id_2 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, { 'name': 'ned', 'age': 31 }) self.assertEqual(1, self.db['resource_division'].count()) self.assertEqual(2, self.db['resource_employee'].count()) self.updater.delete_resource('division', division_id_1, None, 'divisions') self.assertEqual(0, self.db['resource_division'].count()) self.assertEqual(0, self.db['resource_employee'].count()) def test_delete_resource_deletes_links_to_resource(self): self.schema.add_field(self.division_spec, 'employees', 'linkcollection', 'employee') self.schema.add_field(self.division_spec, 'manager', 'link', 'employee') division_id_1 = self.schema.insert_resource('division', {'name': 'sales'}, 'divisions') employee_id_1 = self.schema.insert_resource('employee', {'name': 'Fred'}, 'employees') self.schema.create_linkcollection_entry('division', division_id_1, 'employees', employee_id_1) self.schema.update_resource_fields('division', division_id_1, {'manager': employee_id_1}) self.updater.delete_resource('employee', employee_id_1, 'root', 'employees') self.assertEqual( { '_canonical_url': '/divisions/%s' % division_id_1, '_canonical_url_manager': '/employees/%s' % employee_id_1, '_grants': [], '_id': self.schema.decodeid(division_id_1), '_parent_canonical_url': '/', '_parent_field_name': 'divisions', '_parent_id': None, '_parent_type': 'root', 'employees': [], 'manager': None, 'name': 'sales' }, self.db['resource_division'].find_one()) def test_updates_calc_linked_to_calc(self): self.schema.create_field('root', 'parttimers', 'collection', 'employee') self.schema.create_field('employee', 'income', 'int') self.schema.create_field('employee', 'vat', 'int') self.schema.create_field('employee', 'income_after_vat', 'calc', calc_str='self.income - self.vat') self.schema.create_field('division', 'parttimers', 'linkcollection', 'employee') self.schema.create_field( 'division', 'employee_total', 'calc', calc_str="sum(self.employees.income_after_vat)") self.schema.create_field( 'division', 'parttime_total', 'calc', calc_str="sum(self.parttimers.income_after_vat)") division_id_1 = self.updater.create_resource('division', 'root', 'divisions', None, {'name': 'sales'}) employee_id_1 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, { 'name': 'Fred', 'income': 10000, 'vat': 2000 }) employee_id_2 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, { 'name': 'Ned', 'income': 20000, 'vat': 4000 }) employee_id_3 = self.updater.create_resource('employee', 'root', 'parttimers', None, { 'name': 'Bob', 'income': 40000, 'vat': 8000 }) self.updater.create_linkcollection_entry('division', division_id_1, 'parttimers', employee_id_3) self.assertEqual( 24000, self.db['resource_division'].find_one()['employee_total']) self.assertEqual( 32000, self.db['resource_division'].find_one()['parttime_total']) # assert calc change propagates self.updater.update_fields('employee', employee_id_3, {'vat': 9000}) self.assertEqual( 24000, self.db['resource_division'].find_one()['employee_total']) self.assertEqual( 31000, self.db['resource_division'].find_one()['parttime_total']) def test_update_adjacent_calc_after_update(self): self.schema.create_field( 'employee', 'division_name', 'calc', calc_str='self.parent_division_employees.name') self.schema.create_field('employee', 'both_names', 'calc', calc_str='self.name + self.division_name') division_id_1 = self.updater.create_resource('division', 'root', 'divisions', None, {'name': 'sales'}) employee_id_1 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, {'name': 'Fred'}) self.assertEqual('Fredsales', self.db['resource_employee'].find_one()['both_names']) self.updater.update_fields('division', division_id_1, {'name': 'marketting'}) self.assertEqual('Fredmarketting', self.db['resource_employee'].find_one()['both_names'])