Пример #1
0
class SchemaTest(unittest.TestCase):
    def setUp(self):
        client = MongoClient()
        client.drop_database('metaphor2_test_db')
        self.db = client.metaphor2_test_db
        self.schema = Schema(self.db)
        self.maxDiff = None

    def test_load_basic_spec(self):
        self.db.metaphor_schema.insert_one({
            "specs": {
                "employee": {
                    "fields": {
                        "name": {
                            "type": "str"
                        },
                        "age": {
                            "type": "int"
                        }
                    },
                },
            }
        })
        self.schema.load_schema()
        self.assertEquals(1, len(self.schema.specs))
        self.assertEquals(
            "str", self.schema.specs['employee'].fields['name'].field_type)
        self.assertEquals(
            "int", self.schema.specs['employee'].fields['age'].field_type)

    def test_load_basic_link_with_reverse_link(self):
        self.db.metaphor_schema.insert_one({
            "specs": {
                "employee": {
                    "fields": {
                        "name": {
                            "type": "str"
                        },
                    },
                },
                "department": {
                    "fields": {
                        "manager": {
                            "type": "link",
                            "target_spec_name": "employee",
                        },
                    },
                },
            }
        })
        self.schema.load_schema()
        self.assertEquals(2, len(self.schema.specs))
        self.assertEquals(
            "str", self.schema.specs['employee'].fields['name'].field_type)
        self.assertEquals(
            "link",
            self.schema.specs['department'].fields['manager'].field_type)
        self.assertEquals(
            "employee",
            self.schema.specs['department'].fields['manager'].target_spec_name)

        self.assertEquals(
            "reverse_link", self.schema.specs['employee'].
            fields['link_department_manager'].field_type)
        self.assertEquals(
            "department", self.schema.specs['employee'].
            fields['link_department_manager'].target_spec_name)

    def test_load_collection_with_parent_link(self):
        self.db.metaphor_schema.insert_one({
            "specs": {
                "employee": {
                    "fields": {
                        "name": {
                            "type": "str"
                        },
                    },
                },
                "department": {
                    "fields": {
                        "employees": {
                            "type": "collection",
                            "target_spec_name": "employee",
                        },
                    },
                },
            },
        })
        self.schema.load_schema()
        self.assertEquals(2, len(self.schema.specs))
        self.assertEquals(
            "str", self.schema.specs['employee'].fields['name'].field_type)
        self.assertEquals(
            "collection",
            self.schema.specs['department'].fields['employees'].field_type)
        self.assertEquals(
            "employee", self.schema.specs['department'].fields['employees'].
            target_spec_name)

        self.assertEquals(
            "parent_collection", self.schema.specs['employee'].
            fields['parent_department_employees'].field_type)
        self.assertEquals(
            "department", self.schema.specs['employee'].
            fields['parent_department_employees'].target_spec_name)

    def test_load_link_collection_with_reverse_link(self):
        self.db.metaphor_schema.insert_one({
            "specs": {
                "employee": {
                    "fields": {
                        "name": {
                            "type": "str"
                        },
                    },
                },
                "department": {
                    "fields": {
                        "parttimers": {
                            "type": "linkcollection",
                            "target_spec_name": "employee",
                        },
                    },
                },
            }
        })
        self.schema.load_schema()
        self.assertEquals(2, len(self.schema.specs))
        self.assertEquals(
            "str", self.schema.specs['employee'].fields['name'].field_type)
        self.assertEquals(
            "linkcollection",
            self.schema.specs['department'].fields['parttimers'].field_type)
        self.assertEquals(
            "employee", self.schema.specs['department'].fields['parttimers'].
            target_spec_name)

        self.assertEquals(
            "reverse_link_collection", self.schema.specs['employee'].
            fields['link_department_parttimers'].field_type)
        self.assertEquals(
            "department", self.schema.specs['employee'].
            fields['link_department_parttimers'].target_spec_name)

    def test_save_resource_encode_id(self):
        self.db.metaphor_schema.insert_one({
            "specs": {
                "employee": {
                    "fields": {
                        "name": {
                            "type": "str"
                        },
                    },
                },
            }
        })
        self.schema.load_schema()
        employee_id = self.schema.insert_resource('employee', {'name': 'Bob'},
                                                  'employees')
        new_resource = self.db.resource_employee.find_one()
        self.assertEquals(ObjectId(employee_id[2:]),
                          self.schema.decodeid(employee_id))
        self.assertEquals(
            {
                '_id': self.schema.decodeid(employee_id),
                '_grants': [],
                '_canonical_url': '/employees/%s' % employee_id,
                'name': 'Bob',
                '_parent_canonical_url': '/',
                '_parent_field_name': 'employees',
                '_parent_id': None,
                '_parent_type': 'root',
            }, new_resource)
        self.assertEquals('ID%s' % (new_resource['_id'], ), employee_id)

    def test_update_field(self):
        self.db.metaphor_schema.insert_one({
            "specs": {
                "employee": {
                    "fields": {
                        "name": {
                            "type": "str"
                        },
                    },
                },
            }
        })
        self.schema.load_schema()
        employee_id = self.schema.insert_resource('employee', {'name': 'Bob'},
                                                  'employees')
        self.schema.update_resource_fields('employee', employee_id,
                                           {'name': 'Ned'})
        reload_employee = self.db.resource_employee.find_one(
            {'_id': self.schema.decodeid(employee_id)})
        self.assertEquals('Ned', reload_employee['name'])

    def test_roots(self):
        self.db.metaphor_schema.insert_one({
            "specs": {
                "employee": {
                    "fields": {
                        "name": {
                            "type": "str"
                        },
                    },
                },
                "department": {
                    "fields": {
                        "manager": {
                            "type": "link",
                            "target_spec_name": "employee",
                        },
                    },
                },
            },
            "root": {
                "employees": {
                    "type": "collection",
                    "target_spec_name": "employee",
                },
                "departments": {
                    "type": "collection",
                    "target_spec_name": "department",
                }
            },
        })
        self.schema.load_schema()
        self.assertEquals(2, len(self.schema.specs))
        self.assertEquals(2, len(self.schema.root.fields))
        self.assertEquals('collection',
                          self.schema.root.fields['employees'].field_type)
        self.assertEquals(
            'employee', self.schema.root.fields['employees'].target_spec_name)
        self.assertEquals('collection',
                          self.schema.root.fields['departments'].field_type)
        self.assertEquals(
            'department',
            self.schema.root.fields['departments'].target_spec_name)

    def test_canonical_url(self):
        self.db.metaphor_schema.insert_one({
            "specs": {
                "employee": {
                    "fields": {
                        "name": {
                            "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",
                        }
                    },
                },
                "section": {
                    "fields": {
                        "name": {
                            "type": "str",
                        },
                    },
                },
            },
            "root": {
                "employees": {
                    "type": "collection",
                    "target_spec_name": "employee",
                },
                "divisions": {
                    "type": "collection",
                    "target_spec_name": "division",
                }
            },
        })
        self.schema.load_schema()

        division_id_1 = self.schema.insert_resource('division', {
            'name': 'sales',
            'yearly_sales': 100
        }, 'divisions')

        self.assertEquals(
            {
                '_id': self.schema.decodeid(division_id_1),
                '_grants': [],
                '_canonical_url': '/divisions/%s' % division_id_1,
                '_parent_id': None,
                '_parent_type': 'root',
                '_parent_field_name': 'divisions',
                '_parent_canonical_url': '/',
                'name': 'sales',
                'yearly_sales': 100,
            }, self.db['resource_division'].find_one(
                {'_id': self.schema.decodeid(division_id_1)}))

        section_id_1 = self.schema.insert_resource(
            'section', {'name': 'appropriation'},
            parent_type='division',
            parent_id=division_id_1,
            parent_field_name='sections')

        self.assertEquals(
            {
                '_id': self.schema.decodeid(division_id_1),
                '_grants': [],
                '_canonical_url': '/divisions/%s' % division_id_1,
                '_parent_id': None,
                '_parent_type': 'root',
                '_parent_field_name': 'divisions',
                '_parent_canonical_url': '/',
                'name': 'sales',
                'yearly_sales': 100,
            }, self.db['resource_division'].find_one(
                {'_id': self.schema.decodeid(division_id_1)}))

        self.assertEquals(
            {
                '_id':
                self.schema.decodeid(section_id_1),
                '_grants': [],
                '_canonical_url':
                '/divisions/%s/sections/%s' % (division_id_1, section_id_1),
                '_parent_id':
                self.schema.decodeid(division_id_1),
                '_parent_type':
                'division',
                '_parent_field_name':
                'sections',
                '_parent_canonical_url':
                '/divisions/%s' % division_id_1,
                'name':
                'appropriation',
            }, self.db['resource_section'].find_one(
                {'_id': self.schema.decodeid(section_id_1)}))

    def test_calc_infer_type(self):
        spec = self.schema.add_spec('employees')
        self.schema.add_field(spec, 'name', 'str')
        calc_field = self.schema.add_calc(spec, 'current_name', 'self.name')

        self.assertEquals('str', calc_field.infer_type().field_type)
        self.assertTrue(calc_field.is_primitive())
        self.assertFalse(calc_field.is_collection())

    def test_calc_infer_type_collection(self):
        spec = self.schema.add_spec('employees')
        buddy_spec = self.schema.add_spec('buddy')
        self.schema.add_field(spec, 'buddies', 'collection', 'buddy')
        calc_field = self.schema.add_calc(spec, 'all_buddies', 'self.buddies')

        self.assertEquals(buddy_spec, calc_field.infer_type())
        self.assertFalse(calc_field.is_primitive())
        self.assertTrue(calc_field.is_collection())

    def test_delete_linkcollection_entry(self):
        pass

    def test_parse_fields_test(self):
        # add parsing and validation for field types
        pass

    def test_initialize_schema(self):
        self.schema.create_initial_schema()

        schema = self.db.metaphor_schema.find_one()
        self.assertEqual(
            {
                "groups": {
                    "target_spec_name": "group",
                    "type": "collection"
                },
                "users": {
                    "target_spec_name": "user",
                    "type": "collection"
                }
            }, schema['root'])
        self.assertEqual(
            {
                'grant': {
                    'fields': {
                        'type': {
                            'type': 'str'
                        },
                        'url': {
                            'type': 'str'
                        }
                    }
                },
                'group': {
                    'fields': {
                        'grants': {
                            'target_spec_name': 'grant',
                            'type': 'collection'
                        },
                        'name': {
                            'type': 'str'
                        }
                    }
                },
                'user': {
                    'fields': {
                        'admin': {
                            'type': 'bool'
                        },
                        'create_grants': {
                            'calc_str':
                            "self.groups.grants[type='create'].url",
                            'deps': [
                                'grant.type', 'grant.url', 'group.grants',
                                'user.groups'
                            ],
                            'type':
                            'calc'
                        },
                        'delete_grants': {
                            'calc_str':
                            "self.groups.grants[type='delete'].url",
                            'deps': [
                                'grant.type', 'grant.url', 'group.grants',
                                'user.groups'
                            ],
                            'type':
                            'calc'
                        },
                        'groups': {
                            'target_spec_name': 'group',
                            'type': 'linkcollection'
                        },
                        'password': {
                            'type': 'str'
                        },
                        'put_grants': {
                            'calc_str':
                            "self.groups.grants[type='put'].url",
                            'deps': [
                                'grant.type', 'grant.url', 'group.grants',
                                'user.groups'
                            ],
                            'type':
                            'calc'
                        },
                        'read_grants': {
                            'calc_str':
                            "self.groups.grants[type='read'].url",
                            'deps': [
                                'grant.type', 'grant.url', 'group.grants',
                                'user.groups'
                            ],
                            'type':
                            'calc'
                        },
                        'update_grants': {
                            'calc_str':
                            "self.groups.grants[type='update'].url",
                            'deps': [
                                'grant.type', 'grant.url', 'group.grants',
                                'user.groups'
                            ],
                            'type':
                            'calc'
                        },
                        'username': {
                            'type': 'str'
                        }
                    }
                }
            }, schema['specs'])

    def test_load_calcs_by_dependency(self):
        self.schema.create_spec('employee')
        self.schema.create_field('employee', 'name', 'str')

        self.schema.create_spec('branch')
        self.schema.create_field('branch', 'income', 'int')
        self.schema.create_field('branch', 'employees', 'collection',
                                 'employee')

        self.schema.create_spec('section')
        self.schema.create_field('section', 'branch', 'link', 'branch')

        self.schema.load_schema()

        self.schema.create_field(
            'employee', 'average_section_income', 'calc', None,
            'average(self.parent_branch_employees.income)')
        self.schema.create_field('branch', 'section', 'calc', None,
                                 'self.link_section_branch')
        self.schema.create_field('section', 'employees', 'calc', None,
                                 'self.branch.employees')

        expected = {
            "employee": {
                "fields": {
                    "name": {
                        "type": "str"
                    },
                    "average_section_income": {
                        "type": "calc",
                        "calc_str":
                        "average(self.parent_branch_employees.income)",
                        "deps": ["branch.income"],
                    },
                },
            },
            "branch": {
                "fields": {
                    "income": {
                        "type": "int",
                    },
                    "employees": {
                        "type": "collection",
                        "target_spec_name": "employee",
                    },
                    "section": {
                        "type": "calc",
                        "calc_str": "self.link_section_branch",
                        "deps": ["section.branch"],
                    },
                },
            },
            "section": {
                "fields": {
                    "branch": {
                        "type": "link",
                        "target_spec_name": "branch",
                    },
                    "employees": {
                        "type": "calc",
                        "calc_str": "self.branch.employees",
                        "deps": ["branch.employees", "section.branch"],
                    },
                },
            },
        }

        self.assertEqual(expected, self.db.metaphor_schema.find_one()['specs'])

        self.schema.load_schema()
        self.assertEquals(3, len(self.schema.specs))

    def test_load_calcs_by_dependency_almost_circular(self):
        self.schema.create_spec('primary')
        self.schema.create_field('primary', 'name', 'str')
        self.schema.create_field('primary',
                                 'calced_name',
                                 'calc',
                                 calc_str="self.name + 'a'")

        self.schema.create_spec('secondary')
        self.schema.create_field('secondary', 'name', 'str')
        self.schema.create_field('secondary',
                                 'calced_name',
                                 'calc',
                                 calc_str="self.name + 'b'")

        self.schema.create_field('primary', 'secondary', 'link', 'secondary')
        self.schema.create_field('secondary', 'primary', 'link', 'primary')

        self.schema.create_field('primary',
                                 'secondary_name',
                                 'calc',
                                 calc_str="self.secondary.calced_name")
        self.schema.create_field('secondary',
                                 'primary_name',
                                 'calc',
                                 calc_str="self.primary.calced_name")

        self.schema.load_schema()
        self.assertEquals(2, len(self.schema.specs))
Пример #2
0
class ApiTest(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.employee_spec = self.schema.create_spec('employee')
        self.schema.create_field('employee', 'name', 'str')
        self.schema.create_field('employee', 'age', 'int')
        self.schema.create_field('employee', 'created', 'datetime')

        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('division', 'parttimers', 'linkcollection', 'employee')
        self.schema.create_field('division', 'contractors', 'linkcollection', 'employee')

        self.schema.create_field('root', 'employees', 'collection', 'employee')
        self.schema.create_field('root', 'former_employees', 'collection', 'employee')
        self.schema.create_field('root', 'divisions', 'collection', 'division')

        self.calcs_spec = self.schema.create_spec('calcs')
        self.schema.create_field('calcs', 'total_employees', 'calc', calc_str='sum(employees.age)')
        self.schema.create_field('calcs', 'total_former_employees', 'calc', calc_str='sum(former_employees.age)')
        #self.schema.create_field('calcs', 'total_division_parttimers', 'calc', calc_str='sum(divisions.parttimers.age)')
        #self.schema.create_field('calcs', 'total_division_contractors', 'calc', calc_str='sum(divisions.contractors.age)')

        self.schema.create_field('root', 'calcs', 'collection', 'calcs')

        self.api = Api(self.schema)

    def test_move_resource(self):
        employee_id_1 = self.api.post('/employees', {'name': 'bob', 'age': 1})

        # check calc
        self.calcs_id = self.api.post('/calcs', {})
        self.assertEqual(1, self.api.get('/calcs/%s' % self.calcs_id)['total_employees'])

        self.api.put('/former_employees', {'_from': '/employees/%s' % employee_id_1})

        former_employees = self.api.get('/former_employees')
        self.assertEqual(1, former_employees['count'])

        self.assertEqual('bob', former_employees['results'][0]['name'])

        # no longer at original collection
        self.assertEqual(0, self.api.get('/employees')['count'])

        # assert calc update
        # apparently sum of empty list gives null
        self.assertEqual(None, self.api.get('/calcs/%s' % self.calcs_id)['total_employees'])
        self.assertEqual(1, self.api.get('/calcs/%s' % self.calcs_id)['total_former_employees'])

    def test_move_collection(self):
        employee_id_1 = self.api.post('/employees', {'name': 'bob', 'age': 1})
        employee_id_2 = self.api.post('/employees', {'name': 'ned', 'age': 1})

        self.api.put('/former_employees', {'_from': '/employees'})

        former_employees = self.api.get('/former_employees')
        self.assertEqual(2, former_employees['count'])

        self.assertEqual('bob', former_employees['results'][0]['name'])
        self.assertEqual('ned', former_employees['results'][1]['name'])

        # no longer at original collection
        self.assertEqual(0, self.api.get('/employees')['count'])

    def test_move_between_nested_collections(self):
        division_id_1 = self.api.post('/divisions', {'name': 'sales'})
        division_id_2 = self.api.post('/divisions', {'name': 'marketting'})

        employee_id_1 = self.api.post('/divisions/%s/employees' % division_id_1, {'name': 'bob'})
        employee_id_2 = self.api.post('/divisions/%s/employees' % division_id_2, {'name': 'ned'})

        self.api.put('/divisions/%s/employees' % division_id_2, {'_from': '/divisions/%s/employees/%s' % (division_id_1, employee_id_1)})

        self.assertEqual(0, self.api.get('/divisions/%s/employees' % division_id_1)['count'])
        self.assertEqual(2, self.api.get('/divisions/%s/employees' % division_id_2)['count'])

        self.assertEqual('bob', self.api.get('/divisions/%s/employees/%s' % (division_id_2, employee_id_1))['name'])
        self.assertEqual('ned', self.api.get('/divisions/%s/employees/%s' % (division_id_2, employee_id_2))['name'])

    def test_basic_update(self):
        employee_id_1 = self.api.post('/employees', {'name': 'bob', 'age': 1})

        # check calc
        self.calcs_id = self.api.post('/calcs', {})
        self.assertEqual(1, self.api.get('/calcs/%s' % self.calcs_id)['total_employees'])
Пример #3
0
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'])