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.api = Api(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', 'collection',
                              'employee')
        self.schema.add_field(self.division_spec, 'managers', 'linkcollection',
                              'employee')
        self.schema.add_calc(self.division_spec, 'older_managers',
                             'self.managers[age>30]')

        self.company_spec = self.schema.add_spec('company')
        self.schema.add_field(self.company_spec, 'name', 'str')
        self.schema.add_field(self.company_spec, 'division', 'link',
                              'division')
        self.schema.add_calc(self.company_spec, 'max_age',
                             'max(self.division.older_managers.age)')

        self.schema.add_field(self.schema.root, 'companies', 'collection',
                              'company')
        self.schema.add_field(self.schema.root, 'divisions', 'collection',
                              'division')
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
def create_app(db):
    schema = Schema(db)
    schema.load_schema()

    api = Api(schema)

    app = Flask(__name__)
    app.secret_key = 'keepitsecretkeepitsafe'
    app.config['api'] = api
    app.config['admin_api'] = AdminApi(schema)
    app.register_blueprint(api_bp)
    app.register_blueprint(client_bp)
    app.register_blueprint(admin_bp)
    app.register_blueprint(search_bp)
    app.register_blueprint(login_bp)

    init_login(app)

    @app.route("/")
    def index():
        return redirect(url_for('client.client_root'))

    integration_runner = IntegrationRunner(api, db)
    integration_runner.start()

    atexit.register(integration_runner.stop)

    return app
Example #5
0
    def setUp(self):
        self.maxDiff = None
        client = MongoClient()
        client.drop_database('metaphor2_test_db')
        #client.drop_database('mataphor2_test_db_integration')
        self.db = client.metaphor2_test_db
        #self.source_db = client.metaphor2_test_db_integration

        self.schema = Schema(self.db)
        self.schema.create_initial_schema()

        self.api = Api(self.schema)

        self.events = [
            {
                "operationType": "insert"
            },
        ]

        watched_collection = mock.Mock()
        watched_collection.watch.return_value = Stream(self.events)

        self.source_db = {'external_collection': watched_collection}

        self.integration = MongoChangeStreamIntegration(
            self.source_db, 'external_collection', [], self.api,
            self._process_change_func)

        self._call_log = []
Example #6
0
class MongoTest(unittest.TestCase):
    def setUp(self):
        self.maxDiff = None
        client = MongoClient()
        client.drop_database('metaphor2_test_db')
        #client.drop_database('mataphor2_test_db_integration')
        self.db = client.metaphor2_test_db
        #self.source_db = client.metaphor2_test_db_integration

        self.schema = Schema(self.db)
        self.schema.create_initial_schema()

        self.api = Api(self.schema)

        self.events = [
            {
                "operationType": "insert"
            },
        ]

        watched_collection = mock.Mock()
        watched_collection.watch.return_value = Stream(self.events)

        self.source_db = {'external_collection': watched_collection}

        self.integration = MongoChangeStreamIntegration(
            self.source_db, 'external_collection', [], self.api,
            self._process_change_func)

        self._call_log = []

    def _process_change_func(self, change_doc, source_db, api):
        self._call_log.append((change_doc, source_db, api))

    def test_run_integration(self):
        self.source_db['external_collection'].insert({'a': 1})
        self.integration.process()

        self.assertEqual(1, len(self._call_log))

        self.assertEqual((self.events[0], self.source_db, self.api),
                         self._call_log[0])

    def test_multiple_events(self):
        self.events.append({'operationType': 'update'})

        self.source_db['external_collection'].insert({'a': 1})
        self.integration.process()

        self.assertEqual(2, len(self._call_log))

        self.assertEqual((self.events[0], self.source_db, self.api),
                         self._call_log[0])
        self.assertEqual((self.events[1], self.source_db, self.api),
                         self._call_log[1])
Example #7
0
    def setUp(self):
        self.maxDiff = None
        client = MongoClient()
        client.drop_database('metaphor2_test_db')
        self.db = client.metaphor2_test_db

        Schema(self.db).create_initial_schema()

        self.app = create_app(self.db)
        self.api = self.app.config['api']
        self.schema = self.api.schema

        self.client = self.app.test_client()

        self.user_id = self.api.post('/users', {'username': '******', 'password': '******'})
        self.group_id = self.api.post('/groups', {'name': 'manager'})
        self.api.post('/groups/%s/grants' % self.group_id, {'type': 'read', 'url': '/employees'})
        self.api.post('/groups/%s/grants' % self.group_id, {'type': 'create', 'url': '/employees'})
        self.api.post('/users/%s/groups' % self.user_id, {'id': self.group_id})

        # add test data
        employee_spec = self.schema.add_spec('employee')
        self.schema.add_field(employee_spec, 'name', 'str')

        self.schema.add_field(self.schema.root, 'employees', 'collection', 'employee')

        for i in range(12):
            self.api.post('/employees', {'name': 'fred %s' % i})

        # login
        self.client.post('/login', data={
            "username": "******",
            "password": "******",
        }, follow_redirects=True)
Example #8
0
    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.schema.add_field(self.schema.root, 'current_employees', 'collection', 'employee')
        self.schema.add_field(self.schema.root, 'former_employees', 'collection', 'employee')

        self.calcs_spec = self.schema.add_spec('calcs')
Example #9
0
    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')
Example #10
0
    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.schema.root, 'employees', 'collection',
                              'employee')

        self.api = Api(self.schema)

        self.db.delete_calc.create_index([
            ('update_id', pymongo.ASCENDING),
            ('resource_id', pymongo.ASCENDING),
        ],
                                         unique=True)
Example #11
0
    def setUp(self):
        self.maxDiff = None
        client = MongoClient()
        client.drop_database('metaphor2_test_db')
        self.db = client.metaphor2_test_db

        Schema(self.db).create_initial_schema()

        self.app = create_app(self.db)
        self.api = self.app.config['api']
        self.schema = self.api.schema

        self.client = self.app.test_client()

        self.user_id = self.api.post('/users', {'username': '******', 'password': '******'})
        self.group_id = self.api.post('/groups', {'name': 'manager'})
        self.grant_id_1 = self.api.post('/groups/%s/grants' % self.group_id, {'type': 'read', 'url': '/employees'})
        self.api.post('/users/%s/groups' % self.user_id, {'id': self.group_id})

        self.client.post('/login', data={
            "username": "******",
            "password": "******",
        }, follow_redirects=True)
Example #12
0
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)
Example #13
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'])
Example #14
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.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', 'collection', 'employee')

        self.schema.add_field(self.schema.root, 'divisions', 'collection', 'division')

    def test_update_simple_field(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': [],
            'name': 'sales',
            '_canonical_url': '/divisions/%s' % division_id_1,
            '_parent_canonical_url': '/',
            '_parent_field_name': 'divisions',
            '_parent_id': None,
            '_parent_type': 'root',
            'older_employees': [ObjectId(employee_id_1[2:])],
        }, division_data)

        self.updater.update_fields('employee', employee_id_1, {"age": 20})

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

    def test_update_containing_collection(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')

        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': [],
        }, division_data)

        employee_id_1 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, {
            'name': 'Bob',
            'age': 41,
        })

        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_data = self.db.resource_employee.find_one()
        self.assertEquals({
            '_id': self.schema.decodeid(employee_id_1),
            '_grants': [],
            '_canonical_url': '/divisions/%s/employees/%s' % (division_id_1, employee_id_1),
            'name': 'Bob',
            'age': 41,
            '_parent_canonical_url': '/divisions/%s' % division_id_1,
            '_parent_field_name': 'employees',
            '_parent_id': ObjectId(division_id_1[2:]),
            '_parent_type': 'division',
        }, employee_data)

    def test_update_link_collection(self):
        self.schema.add_field(self.division_spec, 'managers', 'linkcollection', 'employee')
        self.schema.add_calc(self.division_spec, 'older_managers', 'self.managers[age>30]')
        self.schema.add_calc(self.division_spec, 'older_non_retired_managers', 'self.older_managers[age<65]')
        log.debug("start")

        division_id_1 = self.schema.insert_resource(
            'division', {'name': 'sales'}, 'divisions')
        log.debug("inserted")

        employee_id_1 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, {
            'name': 'Bob',
            'age': 41
        })
        log.debug("created 1")
        employee_id_2 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, {
            'name': 'Ned',
            'age': 70
        })
        log.debug("created 2")
        employee_id_3 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, {
            'name': 'Fred',
            'age': 25
        })
        log.debug("created 3")

        self.updater.create_linkcollection_entry('division', division_id_1, 'managers', employee_id_1)
        log.debug("created entry 1")
        self.updater.create_linkcollection_entry('division', division_id_1, 'managers', employee_id_2)
        log.debug("created entry 2")
        self.updater.create_linkcollection_entry('division', division_id_1, 'managers', employee_id_3)
        log.debug("created entry 3")

        division_data = self.db.resource_division.find_one()
        self.assertEquals("sales", division_data['name'])
        self.assertEquals(3, len(division_data['managers']))
        self.assertTrue({"_id" : self.schema.decodeid(employee_id_1)} in division_data['managers'])
        self.assertTrue({"_id" : self.schema.decodeid(employee_id_2)} in division_data['managers'])
        self.assertTrue({"_id" : self.schema.decodeid(employee_id_3)} in division_data['managers'])
        self.assertEquals(sorted([
            self.schema.decodeid(employee_id_1),
            self.schema.decodeid(employee_id_2),
        ]), sorted(division_data['older_managers']))
        self.assertEquals([
            self.schema.decodeid(employee_id_1)],
            division_data['older_non_retired_managers'])
        self.assertEquals({
            "_id" : self.schema.decodeid(division_id_1),
            '_grants': [],
            '_canonical_url': '/divisions/%s' % division_id_1,
            "_parent_field_name" : "divisions",
            "_parent_id" : None,
            "_parent_type" : "root",
            "_parent_canonical_url" : '/',
            "name" : "sales",
            "managers" : [
                    {
                            "_id" : self.schema.decodeid(employee_id_1)
                    },
                    {
                            "_id" : self.schema.decodeid(employee_id_2)
                    },
                    {
                            "_id" : self.schema.decodeid(employee_id_3)
                    }
            ],
            "older_managers" : [
                    self.schema.decodeid(employee_id_1),
                    self.schema.decodeid(employee_id_2),
            ],
            "older_non_retired_managers" : [
                    self.schema.decodeid(employee_id_1),
            ]
        }, division_data)

    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.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})
        employee_id_3 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, {
            'name': 'fred', 'age': 41})
        employee_id_4 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, {
            'name': 'mike', 'age': 51})

        # add manager
        calc_spec = self.schema.calc_trees[('employee', 'all_my_subordinates')]
        self.assertEquals({'division.managers', 'division.employees'}, calc_spec.get_resource_dependencies())
        self.updater.create_linkcollection_entry('division', division_id_1, 'managers', employee_id_1)

        employee_data = self.db.resource_employee.find_one()
        self.assertEquals({
            "_id" : self.schema.decodeid(employee_id_1),
            '_grants': [],
            '_canonical_url': '/divisions/%s/employees/%s' % (division_id_1, employee_id_1),
            "_parent_field_name" : "employees",
            "_parent_id" : self.schema.decodeid(division_id_1),
            "_parent_type" : "division",
            "_parent_canonical_url" : "/divisions/%s" % division_id_1,
            "name" : "bob",
            "age": 21,
            "all_my_subordinates" : [
                self.schema.decodeid(employee_id_1),
                self.schema.decodeid(employee_id_2),
                self.schema.decodeid(employee_id_3),
                self.schema.decodeid(employee_id_4),
            ]}, employee_data)

    def test_resource_deps_for_field(self):
        self.schema.add_calc(self.employee_spec, 'all_ages', 'divisions.employees.age + 10')

        # add manager
        calc_spec = self.schema.calc_trees[('employee', 'all_ages')]
        self.assertEquals({'division.employees', 'root.divisions', 'employee.age'}, calc_spec.get_resource_dependencies())
Example #15
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))
Example #16
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.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.schema.root, 'employees', 'collection',
                              'employee')

        self.api = Api(self.schema)

        self.db.delete_calc.create_index([
            ('update_id', pymongo.ASCENDING),
            ('resource_id', pymongo.ASCENDING),
        ],
                                         unique=True)

    def test_aggregation_merge(self):
        employee_id_1 = self.api.post('employees', {'name': 'ned'})
        employee_id_2 = self.api.post('employees', {'name': 'bob'})

        cursor = self.db.resource_employee.aggregate([
            {
                "$set": {
                    "update_id": 1
                }
            },
            {
                "$project": {
                    "resource_id": "$_id",
                    "update_id": True,
                    "_id": False
                }
            },
            {
                "$merge": {
                    "into": "delete_calc",
                    "on": ["update_id", "resource_id"],
                    "whenNotMatched": "insert"
                }
            },
        ])

        cursor = self.db.resource_employee.aggregate([
            {
                "$set": {
                    "update_id": 2
                }
            },
            {
                "$project": {
                    "resource_id": "$_id",
                    "update_id": True,
                    "_id": False
                }
            },
            {
                "$merge": {
                    "into": "delete_calc",
                    "on": ["update_id", "resource_id"],
                    "whenNotMatched": "insert"
                }
            },
        ])

        employee_id_3 = self.api.post('employees', {'name': 'fred'})

        cursor = self.db.resource_employee.aggregate([
            {
                "$set": {
                    "update_id": 2
                }
            },
            {
                "$project": {
                    "resource_id": "$_id",
                    "update_id": True,
                    "_id": False
                }
            },
            {
                "$merge": {
                    "into": "delete_calc",
                    "on": ["update_id", "resource_id"],
                    "whenNotMatched": "insert"
                }
            },
        ])

        self.assertEqual([], list(cursor))
Example #17
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.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 UpdaterBackgroundTest(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.api = Api(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', 'collection',
                              'employee')
        self.schema.add_field(self.division_spec, 'managers', 'linkcollection',
                              'employee')
        self.schema.add_calc(self.division_spec, 'older_managers',
                             'self.managers[age>30]')

        self.company_spec = self.schema.add_spec('company')
        self.schema.add_field(self.company_spec, 'name', 'str')
        self.schema.add_field(self.company_spec, 'division', 'link',
                              'division')
        self.schema.add_calc(self.company_spec, 'max_age',
                             'max(self.division.older_managers.age)')

        self.schema.add_field(self.schema.root, 'companies', 'collection',
                              'company')
        self.schema.add_field(self.schema.root, 'divisions', 'collection',
                              'division')

    def test_update(self):
        company_1_id = self.api.post('/companies', {'name': 'Bobs Burgers'})
        division_1_id = self.api.post('/divisions', {'name': 'Kitchen'})

        employee_1_id = self.api.post(
            '/divisions/%s/employees' % division_1_id, {
                'name': 'Bob',
                'age': 38
            })
        employee_2_id = self.api.post(
            '/divisions/%s/employees' % division_1_id, {
                'name': 'Linda',
                'age': 36
            })

        self.api.post('/divisions/%s/managers' % division_1_id,
                      {'id': employee_1_id})
        self.api.post('/divisions/%s/managers' % division_1_id,
                      {'id': employee_2_id})

        self.assertEquals(
            None,
            self.api.get('/companies/%s' % company_1_id)['max_age'])

        self.api.patch('/companies/%s' % company_1_id,
                       {'division': division_1_id})

        self.assertEquals(
            38,
            self.api.get('/companies/%s' % company_1_id)['max_age'])
Example #19
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_int_type(self):
        self.db.metaphor_schema.insert_one(
            {"specs": {
                "employee": {
                    "fields": {
                        "age": {
                            "type": "int"
                        }
                    },
                },
            }})
        self.schema.load_schema()
        self.assertEquals(1, len(self.schema.specs))
        self.assertEquals(
            "int", self.schema.specs['employee'].fields['age'].field_type)

        self.assertEquals([],
                          self.schema.validate_spec('employee', {'age': 12}))

        self.assertEquals([{
            'error': "Nonexistant field: 'name'"
        }], self.schema.validate_spec('employee', {'name': "12"}))

        self.assertEquals([{
            'error':
            "Invalid type: str for field 'age' of 'employee' (expected 'int')"
        }], self.schema.validate_spec('employee', {'age': "12"}))

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

        self.assertEquals([],
                          self.schema.validate_spec('employee',
                                                    {'name': 'Bob'}))

        self.assertEquals([{
            'error': "Nonexistant field: 'age'"
        }], self.schema.validate_spec('employee', {'age': "12"}))

        self.assertEquals([{
            'error':
            "Invalid type: int for field 'name' of 'employee' (expected 'str')"
        }], self.schema.validate_spec('employee', {'name': 12}))

    def test_bool_type(self):
        self.db.metaphor_schema.insert_one({
            "specs": {
                "employee": {
                    "fields": {
                        "admin": {
                            "type": "bool"
                        }
                    },
                },
            }
        })
        self.schema.load_schema()
        self.assertEquals(1, len(self.schema.specs))
        self.assertEquals(
            "bool", self.schema.specs['employee'].fields['admin'].field_type)

        self.assertEquals([],
                          self.schema.validate_spec('employee',
                                                    {'admin': True}))

        self.assertEquals([{
            'error': "Nonexistant field: 'age'"
        }], self.schema.validate_spec('employee', {'age': "12"}))

        self.assertEquals([{
            'error':
            "Invalid type: int for field 'admin' of 'employee' (expected 'bool')"
        }], self.schema.validate_spec('employee', {'admin': 12}))

    def test_float_type(self):
        self.db.metaphor_schema.insert_one({
            "specs": {
                "employee": {
                    "fields": {
                        "salary": {
                            "type": "float"
                        }
                    },
                },
            }
        })
        self.schema.load_schema()
        self.assertEquals(1, len(self.schema.specs))
        self.assertEquals(
            "float", self.schema.specs['employee'].fields['salary'].field_type)

        self.assertEquals([],
                          self.schema.validate_spec('employee',
                                                    {'salary': 22.33}))
        self.assertEquals([],
                          self.schema.validate_spec('employee',
                                                    {'salary': 22}))

        self.assertEquals([{
            'error': "Nonexistant field: 'age'"
        }], self.schema.validate_spec('employee', {'age': "12"}))

    def test_datetime_type(self):
        self.db.metaphor_schema.insert_one({
            "specs": {
                "employee": {
                    "fields": {
                        "created": {
                            "type": "datetime"
                        }
                    },
                },
            },
            "root": {
                "employees": {
                    "type": "collection",
                    "target_spec_name": "employee",
                },
            },
        })
        self.schema.load_schema()
        self.assertEquals(1, len(self.schema.specs))
        self.assertEquals(
            "datetime",
            self.schema.specs['employee'].fields['created'].field_type)

        self.assertEquals([],
                          self.schema.validate_spec(
                              'employee', {'created': "2021-12-21T12:30:40"}))

        self.assertEqual(
            [{
                'error': "Invalid type for field 'created' (expected 'str')"
            }], self.schema.validate_spec('employee', {'created': 12}))

        self.assertEquals([{
            'error':
            "Invalid date string for field 'created' (expected ISO format)"
        }], self.schema.validate_spec('employee', {'created': "March 12"}))

        self.schema.insert_resource('employee',
                                    {'created': "2021-12-31T12:30:20"},
                                    'employees')

        inserted = self.db.resource_employee.find_one()
        self.assertEqual(datetime(2021, 12, 31, 12, 30, 20),
                         inserted['created'])
Example #20
0
class MoveResourceTest(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.schema.add_field(self.schema.root, 'current_employees', 'collection', 'employee')
        self.schema.add_field(self.schema.root, 'former_employees', 'collection', 'employee')

        self.calcs_spec = self.schema.add_spec('calcs')

    def test_move_from_root(self):
        self.schema.add_calc(self.calcs_spec, 'sum_employee_age', 'sum(current_employees.age)')
        self.schema.add_field(self.schema.root, 'calcs', 'collection', 'calcs')

        # add root resources
        employee_id_1 = self.schema.insert_resource('employee', {'name': 'Bob', 'age': 10}, 'current_employees')
        employee_id_2 = self.schema.insert_resource('employee', {'name': 'Ned', 'age': 14}, 'current_employees')

        calcs_id_1 = self.schema.insert_resource('calcs', {}, 'calcs')

        # create move update
        self.move_resource = MoveResourceUpdate(
            self.updater,
            self.schema,
            None,
            'root',
            'root',
            'former_employees',
            'current_employees')

        from_path_agg, from_path_spec, from_path_is_coll = self.move_resource.from_path_agg()

        self.assertEqual([
            {'$match': {
                '$and': [
                    {'_parent_field_name': 'current_employees'},
                    {'_parent_canonical_url': '/'}
                ]
            }},
        ], from_path_agg)
        self.assertEqual(self.schema.specs['employee'], from_path_spec)
        self.assertTrue(from_path_is_coll)

        affected_ids_agg_before = self.move_resource.affected_aggs()
        self.assertEqual([
            ('calcs', 'sum_employee_age', [
            {'$lookup': {'as': '_field_sum_employee_age',
               'from': 'resource_calcs',
               'pipeline': []}},
            {'$group': {'_id': '$_field_sum_employee_age'}},
            {'$unwind': '$_id'},
            {'$replaceRoot': {'newRoot': '$_id'}}])
        ], affected_ids_agg_before)

        # check affected agg to path
        affected_ids_agg_after = self.move_resource.affected_aggs_to_path()
        self.assertEqual([
        ], affected_ids_agg_after)

        # check affected ids
        self.assertEqual(set([
            ('calcs', 'sum_employee_age', self.schema.decodeid(calcs_id_1)),
        ]), self.move_resource.affected_ids())

        # check affected ids for to path
        self.assertEqual(set([
        ]), self.move_resource.affected_ids_to_path())

    def test_move_from_root_after_aggs(self):
        self.schema.add_calc(self.calcs_spec, 'sum_employee_age', 'sum(former_employees.age)')
        self.schema.add_field(self.schema.root, 'calcs', 'collection', 'calcs')

        # add root resources
        employee_id_1 = self.schema.insert_resource('employee', {'name': 'Bob', 'age': 10}, 'current_employees')
        employee_id_2 = self.schema.insert_resource('employee', {'name': 'Ned', 'age': 14}, 'current_employees')

        calcs_id_1 = self.schema.insert_resource('calcs', {}, 'calcs')

        # create move update
        self.move_resource = MoveResourceUpdate(
            self.updater,
            self.schema,
            None,
            'root',
            'former_employees',
            'former_employees',
            'current_employees')

        from_path_agg, from_path_spec, from_path_is_coll = self.move_resource.from_path_agg()

        self.assertEqual([
            {'$match': {
                '$and': [
                    {'_parent_field_name': 'current_employees'},
                    {'_parent_canonical_url': '/'}
                ]
            }},
        ], from_path_agg)
        self.assertEqual(self.schema.specs['employee'], from_path_spec)
        self.assertTrue(from_path_is_coll)

        affected_ids_agg_before = self.move_resource.affected_aggs()
        self.assertEqual([
        ], affected_ids_agg_before)

        # check affected agg to path
        affected_ids_agg_after = self.move_resource.affected_aggs_to_path()
        self.assertEqual([
            ('calcs', 'sum_employee_age',
            [{'$lookup': {'as': '_field_sum_employee_age',
                            'from': 'resource_calcs',
                            'pipeline': []}},
            {'$group': {'_id': '$_field_sum_employee_age'}},
            {'$unwind': '$_id'},
            {'$replaceRoot': {'newRoot': '$_id'}}])], affected_ids_agg_after)

        # check affected ids
        self.assertEqual(set([
        ]), self.move_resource.affected_ids())

        # need to perform the move before the after ids will show up
        self.move_resource.perform_move()

        # check affected ids for to path
        self.assertEqual(set([
            ('calcs', 'sum_employee_age', self.schema.decodeid(calcs_id_1)),
        ]), self.move_resource.affected_ids_to_path())

        # check canonical_url
        employee = self.db['resource_employee'].find_one({'_id': self.schema.decodeid(employee_id_1 )})
        self.assertEqual('/', employee['_parent_canonical_url'])
        self.assertEqual('former_employees', employee['_parent_field_name'])

    def test_move_from_root_more_resources(self):
        self.schema.add_calc(self.calcs_spec, 'sum_employee_age', 'sum(current_employees.age)')
        self.schema.add_field(self.schema.root, 'calcs', 'collection', 'calcs')

        # add root resources
        employee_id_1 = self.schema.insert_resource('employee', {'name': 'Bob', 'age': 10}, 'current_employees')
        employee_id_2 = self.schema.insert_resource('employee', {'name': 'Ned', 'age': 14}, 'current_employees')

        calcs_id_1 = self.schema.insert_resource('calcs', {}, 'calcs')
        calcs_id_2 = self.schema.insert_resource('calcs', {}, 'calcs')

        # create move update
        self.move_resource = MoveResourceUpdate(
            self.updater,
            self.schema,
            None,
            'root',
            'root',
            'former_employees',
            'current_employees')

        # check affected ids
        self.assertEqual(set([
            ('calcs', 'sum_employee_age', self.schema.decodeid(calcs_id_1)),
            ('calcs', 'sum_employee_age', self.schema.decodeid(calcs_id_2)),
        ]), self.move_resource.affected_ids())

    def test_move_from_child_collection(self):
        self.division_spec = self.schema.add_spec('division')
        self.schema.add_field(self.division_spec, 'name', 'str')

        self.schema.add_field(self.schema.root, 'divisions', 'collection', 'division')
        self.schema.add_field(self.schema.root, 'calcs', 'collection', 'calcs')

        self.schema.add_field(self.division_spec, 'employees', 'collection', 'employee')

        self.schema.add_field(self.calcs_spec, 'division', 'link', 'division')
        self.schema.add_calc(self.calcs_spec, 'sum_division_employee_age', 'sum(self.division.employees.age)')

        # add root resources
        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': 10}, 'employees', 'division', division_id_1)
        employee_id_2 = self.schema.insert_resource('employee', {'name': 'Ned', 'age': 14}, 'employees', 'division', division_id_1)
        employee_id_3 = self.schema.insert_resource('employee', {'name': 'Fred', 'age': 16}, 'employees', 'division', division_id_2)

        calcs_id_1 = self.schema.insert_resource('calcs', {"division": division_id_1}, 'calcs')
        calcs_id_2 = self.schema.insert_resource('calcs', {"division": division_id_2}, 'calcs')

        # create move update
        self.move_resource = MoveResourceUpdate(
            self.updater,
            self.schema,
            division_id_2,
            'division',
            'employees',
            'divisions/%s/employees' % division_id_2,
            'divisions/%s/employees' % division_id_1)

        # check from agg
        from_path_agg, from_path_spec, from_path_is_coll = self.move_resource.from_path_agg()
        self.assertEqual([
            {'$match': {'$and': [{'_parent_field_name': 'divisions'},
                                 {'_parent_canonical_url': '/'}]}},
            {'$match': {'_id': self.schema.decodeid(division_id_1)}},
            {'$lookup': {'as': '_field_employees',
                        'foreignField': '_parent_id',
                        'from': 'resource_employee',
                        'localField': '_id'}},
            {'$group': {'_id': '$_field_employees'}},
            {'$unwind': '$_id'},
            {'$replaceRoot': {'newRoot': '$_id'}}], from_path_agg)
        self.assertEqual(self.schema.specs['employee'], from_path_spec)
        self.assertTrue(from_path_is_coll)

        # check affected ids agg
        affected_ids_agg_before = self.move_resource.affected_aggs()
        self.assertEqual([
            ('calcs', 'sum_division_employee_age', [
            # lookup to division
            {'$lookup': {'as': '_field_employees',
                        'foreignField': '_id',
                        'from': 'resource_division',
                        'localField': '_parent_id'}},
            {'$group': {'_id': '$_field_employees'}},
            {'$unwind': '$_id'},
            {'$replaceRoot': {'newRoot': '$_id'}},
            # lookup to calcs
            {'$lookup': {'as': '_field_division',
                        'foreignField': 'division',
                        'from': 'resource_calcs',
                        'localField': '_id'}},
            {'$group': {'_id': '$_field_division'}},
            {'$unwind': '$_id'},
            {'$replaceRoot': {'newRoot': '$_id'}}])], affected_ids_agg_before)

        # check affected ids
        self.assertEqual(set([
            ('calcs', 'sum_division_employee_age', self.schema.decodeid(calcs_id_1)),
        ]), self.move_resource.affected_ids())
Example #21
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.updater = Updater(self.schema)

        self.schema.create_initial_schema()


        self.company_spec = self.schema.add_spec('company')
        self.employee_spec = self.schema.add_spec('employee')
        self.division_spec = self.schema.add_spec('division')

        self.schema.add_field(self.company_spec, 'divisions', 'collection', 'division')

        self.schema.add_field(self.employee_spec, 'name', 'str')

        self.schema.add_field(self.division_spec, 'employees', 'collection', 'employee')

        self.schema.add_field(self.schema.root, 'companies', 'collection', 'company')


    def test_delete_group(self):
        self.admin_group_id = self.updater.create_resource('group', 'root', 'groups', None, {'name': 'admin'}, self.schema.read_root_grants('groups'))

        self.grant_id = self.updater.create_resource('grant', 'group', 'grants', self.admin_group_id, {'type': 'read', 'url': '/companies'})

        self.user_id = self.updater.create_resource('user', 'root', 'users', None, {'username': '******', 'password': '******', 'admin': True}, self.schema.read_root_grants('users'))

        self.updater.create_linkcollection_entry('user', self.user_id, 'groups', self.admin_group_id)


        user_db = self.db['resource_user'].find_one()
        self.assertEqual(1, len(user_db['read_grants']))

        self.updater.delete_linkcollection_entry('user', self.schema.decodeid(self.user_id), 'groups', self.admin_group_id)

        user_db = self.db['resource_user'].find_one()
        self.assertEqual(0, len(user_db['read_grants']))

    def test_root_grants(self):
        group_id = self.updater.create_resource('group', 'root', 'groups', None, {'name': 'readall'}, self.schema.read_root_grants('groups'))
        grant_id = self.updater.create_resource('grant', 'group', 'grants', group_id, {'type': 'read', 'url': '/'})

        company_id = self.updater.create_resource('company', 'root', 'companies', None, {}, self.schema.read_root_grants('companies'))

        company_data = self.db['resource_company'].find_one({})
        self.assertEqual([self.schema.decodeid(grant_id)], company_data['_grants'])

    def test_nested_grants(self):
        group_id = self.updater.create_resource('group', 'root', 'groups', None, {'name': 'readall'}, self.schema.read_root_grants('groups'))
        grant_id = self.updater.create_resource('grant', 'group', 'grants', group_id, {'type': 'read', 'url': '/'})

        company_id = self.updater.create_resource('company', 'root', 'companies', None, {}, self.schema.read_root_grants('companies'))

        company_path = "companies/%s" % company_id
        division_id_1 = self.updater.create_resource('division', 'company', 'divisions', company_id, {}, self.schema.read_root_grants(company_path))
        division_id_2 = self.updater.create_resource('division', 'company', 'divisions', company_id, {}, self.schema.read_root_grants(company_path))

        division_1_path = "companies/%s/divisions/%s" % (company_id, division_id_1)
        employee_id_1 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, {}, self.schema.read_root_grants(division_1_path))
        employee_id_2 = self.updater.create_resource('employee', 'division', 'employees', division_id_1, {}, self.schema.read_root_grants(division_1_path))

        division_2_path = "companies/%s/divisions/%s" % (company_id, division_id_2)
        employee_id_3 = self.updater.create_resource('employee', 'division', 'employees', division_id_2, {}, self.schema.read_root_grants(division_2_path))
        employee_id_4 = self.updater.create_resource('employee', 'division', 'employees', division_id_2, {}, self.schema.read_root_grants(division_2_path))

        # check grants
        company_data = self.db['resource_company'].find_one({})
        self.assertEqual([self.schema.decodeid(grant_id)], company_data['_grants'])

        division_1_data = self.db['resource_division'].find_one({"_id": self.schema.decodeid(division_id_1)})
        self.assertEqual([self.schema.decodeid(grant_id)], division_1_data['_grants'])

        division_2_data = self.db['resource_division'].find_one({"_id": self.schema.decodeid(division_id_2)})
        self.assertEqual([self.schema.decodeid(grant_id)], division_2_data['_grants'])

        employee_1_data = self.db['resource_employee'].find_one({"_id": self.schema.decodeid(employee_id_1)})
        self.assertEqual([self.schema.decodeid(grant_id)], employee_1_data['_grants'])

        employee_2_data = self.db['resource_employee'].find_one({"_id": self.schema.decodeid(employee_id_2)})
        self.assertEqual([self.schema.decodeid(grant_id)], employee_2_data['_grants'])

        employee_3_data = self.db['resource_employee'].find_one({"_id": self.schema.decodeid(employee_id_3)})
        self.assertEqual([self.schema.decodeid(grant_id)], employee_3_data['_grants'])

        employee_4_data = self.db['resource_employee'].find_one({"_id": self.schema.decodeid(employee_id_4)})
        self.assertEqual([self.schema.decodeid(grant_id)], employee_4_data['_grants'])

    def test_deleting_grant_removes_grant_id(self):
        group_id = self.updater.create_resource('group', 'root', 'groups', None, {'name': 'readall'}, self.schema.read_root_grants('groups'))
        grant_id = self.updater.create_resource('grant', 'group', 'grants', group_id, {'type': 'read', 'url': '/'})

        company_id = self.updater.create_resource('company', 'root', 'companies', None, {}, self.schema.read_root_grants('companies'))

        self.updater.delete_resource('grant', grant_id, 'group', 'grants')

        company_data = self.db['resource_company'].find_one({})
        self.assertEqual([], company_data['_grants'])
Example #22
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.db.metaphor_schema.insert_one({
            "specs" : {
                "employee" : {
                    "fields" : {
                        "name" : {
                            "type" : "str"
                        },
                        "age": {
                            "type": "int"
                        },
                        "division": {
                            "type": "link",
                            "target_spec_name": "division",
                        },
                        "division_link": {
                            "type": "calc",
                            "calc_str": "self.division",
                        },
                        "parttime_division_name": {
                            "type": "calc",
                            "calc_str": "self.name + (self.parent_section_parttimers.parent_division_sections.name)",
                        },
                    },
                },
                "division": {
                    "fields": {
                        "name": {
                            "type": "str",
                        },
                        "yearly_sales": {
                            "type": "int",
                        },
                        "sections": {
                            "type": "orderedcollection",
                            "target_spec_name": "section",
                        },
                        "primary_sections": {
                            "type": "calc",
                            "calc_str": "self.sections[name='primary']",
                        },
                        "average_section_total": {
                            "type": "calc",
                            "calc_str": "average(self.sections.section_total)",
                        },
                        "average_bracket_calc": {
                            "type": "calc",
                            "calc_str": "average(self.sections.section_total) + ((10 + sum(self.sections.section_total)) / 5)",
                        },
                        "older_employees": {
                            "type": "calc",
                            "calc_str": "self.link_employee_division[age>40]",
                        },
                    },
                },
                "section": {
                    "fields": {
                        "name": {
                            "type": "str",
                        },
                        "section_total": {
                            "type": "int",
                        },
                        "division_name": {
                            "type": "calc",
                            "calc_str": "self.parent_division_sections.name",
                        },
                        "distance_from_average": {
                            "type": "calc",
                            "calc_str": "self.section_total - average(self.parent_division_sections.sections.section_total)",
                        },
                        "parttimers": {
                            "type": "orderedcollection",
                            "target_spec_name": "employee",
                        },
                    },
                },
            },
            "root": {
                "employees": {
                    "type": "collection",
                    "target_spec_name": "employee",
                },
                "divisions": {
                    "type": "collection",
                    "target_spec_name": "division",
                }
            },
        })
        self.schema.load_schema()

        self.api = Api(self.schema)

    def test_calc_results(self):
        employee_id_1 = self.api.post('employees', {'name': 'ned', 'age': 41})
        employee_id_2 = self.api.post('employees', {'name': 'bob', 'age': 31})
        employee_id_3 = self.api.post('employees', {'name': 'fred', 'age': 21})

        division_id_1 = self.api.post('divisions', {'name': 'sales', 'yearly_sales': 100})

        self.api.patch('employees/%s' % employee_id_1, {'division': division_id_1})
        self.api.patch('employees/%s' % employee_id_2, {'division': division_id_1})

        section_id_1 = self.api.post('/divisions/%s/sections' % division_id_1, {'name': 'primary', 'section_total': 120})
        section_id_2 = self.api.post('/divisions/%s/sections' % division_id_1, {'name': 'secondary', 'section_total': 90})

        # test simple type
        section_1 = self.api.get('/divisions/%s/sections/%s' % (division_id_1, section_id_1))

        self.assertEquals('sales', section_1['division_name'])

        # test resource type
        division_1 = self.api.get('/divisions/%s' % division_id_1)
        self.assertEquals({
            '_meta': {'is_collection': False, 'spec': {'name': 'division'}},
            'id': division_id_1,
            'link_employee_division': '/divisions/%s/link_employee_division' % division_id_1,
            'name': 'sales',
            'average_section_total': 105.0,
            'average_bracket_calc': 149.0,
            'older_employees': '/divisions/%s/older_employees' % division_id_1,
            'primary_sections': '/divisions/%s/primary_sections' % division_id_1,
            'sections': '/divisions/%s/sections' % division_id_1,
            'self': '/divisions/%s' % division_id_1,
            'yearly_sales': 100}, division_1)

        # test calculated resource collection endpoint
        older_employees = self.api.get('/divisions/%s/older_employees' % division_id_1)['results']
        self.assertEquals(1, len(older_employees))

    def test_calc_link_1(self):
        employee_id_1 = self.api.post('employees', {'name': 'ned', 'age': 41})

    def test_calc_link(self):
        division_id_1 = self.api.post('divisions', {'name': 'sales', 'yearly_sales': 100})
        employee_id_1 = self.api.post('employees', {'name': 'ned', 'age': 41, 'division': division_id_1})

        self.assertEqual({
            '_meta': {'is_collection': False, 'spec': {'name': 'employee'}},
            'age': 41,
            'division': '/divisions/%s' % division_id_1,
            'division_link': '/divisions/%s' % division_id_1,
            'id': employee_id_1,
            'name': 'ned',
            'parent_section_parttimers': None,
            'parttime_division_name': None,
            'self': '/employees/%s' % employee_id_1}
            , self.api.get('/employees/%s' % employee_id_1))

    def test_calc_parent_links(self):
        division_id_1 = self.api.post('divisions', {'name': 'sales', 'yearly_sales': 100})
        section_1 = self.api.post('/divisions/%s/sections' % (division_id_1,), {'name': 'hr'})
        parttimer_1 = self.api.post('/divisions/%s/sections/%s/parttimers' % (division_id_1, section_1), {'name': 'bob'})

        parttimer = self.api.get('/divisions/%s/sections/%s/parttimers/%s' % (division_id_1, section_1, parttimer_1))

        self.assertEqual('bobsales', parttimer['parttime_division_name'])
Example #23
0
    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"
                        },
                        "age": {
                            "type": "int"
                        },
                        "division": {
                            "type": "link",
                            "target_spec_name": "division",
                        },
                        "division_link": {
                            "type": "calc",
                            "calc_str": "self.division",
                        },
                        "parttime_division_name": {
                            "type": "calc",
                            "calc_str": "self.name + (self.parent_section_parttimers.parent_division_sections.name)",
                        },
                    },
                },
                "division": {
                    "fields": {
                        "name": {
                            "type": "str",
                        },
                        "yearly_sales": {
                            "type": "int",
                        },
                        "sections": {
                            "type": "orderedcollection",
                            "target_spec_name": "section",
                        },
                        "primary_sections": {
                            "type": "calc",
                            "calc_str": "self.sections[name='primary']",
                        },
                        "average_section_total": {
                            "type": "calc",
                            "calc_str": "average(self.sections.section_total)",
                        },
                        "average_bracket_calc": {
                            "type": "calc",
                            "calc_str": "average(self.sections.section_total) + ((10 + sum(self.sections.section_total)) / 5)",
                        },
                        "older_employees": {
                            "type": "calc",
                            "calc_str": "self.link_employee_division[age>40]",
                        },
                    },
                },
                "section": {
                    "fields": {
                        "name": {
                            "type": "str",
                        },
                        "section_total": {
                            "type": "int",
                        },
                        "division_name": {
                            "type": "calc",
                            "calc_str": "self.parent_division_sections.name",
                        },
                        "distance_from_average": {
                            "type": "calc",
                            "calc_str": "self.section_total - average(self.parent_division_sections.sections.section_total)",
                        },
                        "parttimers": {
                            "type": "orderedcollection",
                            "target_spec_name": "employee",
                        },
                    },
                },
            },
            "root": {
                "employees": {
                    "type": "collection",
                    "target_spec_name": "employee",
                },
                "divisions": {
                    "type": "collection",
                    "target_spec_name": "division",
                }
            },
        })
        self.schema.load_schema()

        self.api = Api(self.schema)
Example #24
0
 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
Example #25
0
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)
Example #26
0
    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()
Example #27
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'])