Example #1
0
def test_item_path(fconfig: Config, db: SQLAlchemy):
    """
    Tests that the URL converter works for the item endpoints of
    the resources.

    The following test has set an URL converter of type int, and will
    allow only integers using the flask rules.
    """
    DeviceDef, *_ = fconfig.RESOURCE_DEFINITIONS  # type: Tuple[ResourceDef, ...]

    def cannot_find(id):
        assert id == 1
        return Response(status=200)

    DeviceDef.VIEW.one = MagicMock(side_effect=cannot_find)
    app = Teal(config=fconfig, db=db)
    client = app.test_client()  # type: Client
    with populated_db(db, app):
        # All ok, we expect an int and got an int
        client.get(res=DeviceDef.type, item=1)
        DeviceDef.VIEW.one.assert_called_once_with(1)
        # Conversion of 'int' works in here
        client.get(res=DeviceDef.type, item='1')
        assert DeviceDef.VIEW.one.call_count == 2
        # Anything else fails and our function is directly not executed
        client.get(res=DeviceDef.type, item='foo', status=NotFound)
        assert DeviceDef.VIEW.one.call_count == 2
Example #2
0
def test_resource_without_path(config: Config, db: SQLAlchemy):
    """Test resources that don't have url_prefix."""

    class FooDef(Resource):
        VIEW = View
        __type__ = 'Foo'

        def __init__(self, app,
                     import_name=__name__.split('.')[0],
                     static_folder=None,
                     static_url_path=None,
                     template_folder=None,
                     url_prefix='',  # We set url_prefix to empty string
                     subdomain=None,
                     url_defaults=None,
                     root_path=None):
            super().__init__(app, import_name, static_folder, static_url_path, template_folder,
                             url_prefix, subdomain, url_defaults, root_path)

    config.RESOURCE_DEFINITIONS = FooDef,

    app = Teal(config=config, db=db)
    with app.test_request_context():
        assert url_for_resource(FooDef) == '/'
        assert url_for_resource(FooDef, 1) == '/1'
Example #3
0
def test_post(fconfig: Config, db: SQLAlchemy):
    """
    Tests posting resources, going through API (Marshmallow) and DB
    (SQLAlchemy) validation, and retrieving and returning a result.
    """
    DeviceDef, ComponentDef, ComputerDef = fconfig.RESOURCE_DEFINITIONS  # type: Tuple[ResourceDef]
    Computer = ComputerDef.MODEL
    Component = ComponentDef.MODEL
    PC = {
        'id': 1,
        'model': 'foo',
        'components': [{'id': 2, 'type': 'Component'}, {'id': 3, 'type': 'Component'}]
    }

    def post():
        pc = request.get_json()
        pc = Computer(**pc)
        db.session.add(pc)
        db.session.commit()
        return Response(status=201)

    def _one(id):
        pc = Computer.query.filter_by(id=id).first()
        return_pc = {
            'id': pc.id,
            'model': pc.model,
            'type': pc.type,
        }
        # todo convert components to JSON
        return return_pc

    def one(id: int):
        return jsonify(_one(id))

    def find(_):
        return jsonify([_one(1)])

    ComputerDef.VIEW.post = MagicMock(side_effect=post)
    ComputerDef.VIEW.one = MagicMock(side_effect=one)
    ComputerDef.VIEW.find = MagicMock(side_effect=find)

    app = Teal(config=fconfig, db=db)

    client = app.test_client()  # type: Client
    with populated_db(db, app):
        client.post(res=ComputerDef.type, data=PC)
        # Wrong data
        data, _ = client.post(res=ComputerDef.type, data={'id': 'foo'}, status=ValidationError)
        assert data == {
            'code': 422,
            'type': 'ValidationError',
            'message': {'id': ['Not a valid integer.']}
        }
        # Get the first data
        data, _ = client.get(res=ComputerDef.type, item=1)
        assert data == {'id': 1, 'model': 'foo', 'type': 'Computer'}
        # Get all data
        data, _ = client.get(res=ComputerDef.type)
        assert data == [{'id': 1, 'model': 'foo', 'type': 'Computer'}]
Example #4
0
def test_http_exception(fconfig: Config, db: SQLAlchemy):
    """Tests correct handling of HTTP exceptions."""
    DeviceDef, *_ = fconfig.RESOURCE_DEFINITIONS  # type: Tuple[ResourceDef]

    DeviceDef.VIEW.get = MagicMock(side_effect=NotFound)
    client = Teal(config=fconfig, db=db).test_client()  # type: Client
    d, _ = client.get(res=DeviceDef.type, status=NotFound)
    assert d['code'] == 404
Example #5
0
def test_token_auth_view(db: SQLAlchemy):
    """
    Ensures that an authorization endpoint correctly protects against
    wrong credentials (this case tokens), allowing the endpoint
    to only specific cases.
    """

    class TestTokenAuth(TokenAuth):
        authenticate = MagicMock(side_effect=Unauthorized)

    class FooSchema(Schema):
        pass

    class FooView(View):
        get = MagicMock(side_effect=lambda id: jsonify({'did': 'it!'}))

    class Foo(db.Model):
        id = db.Column(db.Integer, primary_key=True)

    class FooDef(Resource):
        SCHEMA = FooSchema
        VIEW = FooView
        MODEL = Foo
        AUTH = True

    class TestConfig(Config):
        RESOURCE_DEFINITIONS = [FooDef]

    app = Teal(config=TestConfig(), Auth=TestTokenAuth, db=db)
    client = app.test_client()

    # No token
    # No auth header sent
    client.get(res=FooSchema.t, status=Unauthorized)
    assert TestTokenAuth.authenticate.call_count == 0

    # Wrong format
    # System couldn't parse Auth header
    client.get(res=FooSchema.t, token='this is wrong', status=Unauthorized)
    assert TestTokenAuth.authenticate.call_count == 0

    # Wrong credentials
    # System can parse credentials but they are incorrect
    client.get(res=FooSchema.t, token=b64encode(b'nok:').decode(), status=Unauthorized)
    # Authenticate method was hit
    assert TestTokenAuth.authenticate.call_count == 1

    # OK
    # Our authenticate method now returns some dummy user instead of
    # raising Unauthorized
    TestTokenAuth.authenticate = MagicMock(return_value={'id': '1'})
    data, _ = client.get(res=FooSchema.t, token=b64encode(b'ok:').decode())
    TestTokenAuth.authenticate.assert_called_once_with('ok', '')
    # The endpoint was hit
    assert data == {'did': 'it!'}
    FooView.get.assert_called_once_with(id=None)
Example #6
0
def test_init_db(db: SQLAlchemy, config: Config):
    """Tests :meth:`teal.resource.Resource.init_db` with one inventory."""

    class Foo(db.Model):
        id = Column(db.Integer, primary_key=True)

    class FooDef(Resource):
        __type__ = 'Foo'

        def init_db(self, db: SQLAlchemy, exclude_schema=None):
            db.session.add(Foo())

    config.RESOURCE_DEFINITIONS = FooDef,
    app = Teal(config=config, db=db)
    with app.app_context():
        app.init_db()
    with app.app_context():
        # If no commit happened in init_db() or anything else
        # this would not exist
        assert Foo.query.filter_by(id=1).one()

    # Test again but executing init-db through the command-line
    runner = app.test_cli_runner()
    runner.invoke('init-db')
    with app.app_context():
        assert Foo.query.filter_by(id=2).one()

    # Test with --erase option
    runner.invoke('init-db', '--erase')
    with app.app_context():
        assert Foo.query.count() == 1
Example #7
0
def test_cors(fconfig: Config, db: SchemaSQLAlchemy):
    DeviceDef, *_ = fconfig.RESOURCE_DEFINITIONS  # type: Tuple[ResourceDef]

    def foo(*args, **kw):
        return Response(status=200)

    DeviceDef.VIEW.get = MagicMock(side_effect=foo)
    client = Teal(config=fconfig, db=db).test_client()  # type: Client
    _, response = client.get('/devices/')
    headers = response.headers.to_list()
    assert ('Access-Control-Expose-Headers', 'Authorization') in headers
    assert ('Access-Control-Allow-Origin', '*') in headers
Example #8
0
def test_nested_on(fconfig: Config, db: SQLAlchemy):
    """Tests the NestedOn marshmallow field."""
    DeviceDef, ComponentDef, ComputerDef = fconfig.RESOURCE_DEFINITIONS

    class GraphicCardSchema(ComponentDef.SCHEMA):
        speed = Integer()

    class GraphicCard(ComponentDef.MODEL):
        speed = db.Column(db.Integer)

    class GraphicCardDef(ComponentDef):
        SCHEMA = GraphicCardSchema
        MODEL = GraphicCard

    fconfig.RESOURCE_DEFINITIONS += (GraphicCardDef,)

    app = Teal(config=fconfig, db=db)

    pc_template = {
        'id': 1,
        'components': [
            {'id': 2, 'type': 'Component'},
            {'id': 3, 'type': 'GraphicCard', 'speed': 4}
        ]
    }
    with app.app_context():
        schema = app.resources['Computer'].SCHEMA()
        result = schema.load(pc_template)
        assert pc_template['id'] == result['id']
        assert isinstance(result['components'][0], ComponentDef.MODEL)
        assert isinstance(result['components'][1], GraphicCardDef.MODEL)
        # Let's add the graphic card's speed field to the component
        with pytest.raises(ValidationError, message={'components': {'speed': ['Unknown field']}}):
            pc = deepcopy(pc_template)
            pc['components'][0]['speed'] = 4
            schema.load(pc)
        # Let's remove the 'type'
        with pytest.raises(ValidationError,
                           message={
                               'components': ['\'Type\' field required to disambiguate resources.']
                           }):
            pc = deepcopy(pc_template)
            del pc['components'][0]['type']
            del pc['components'][1]['type']
            schema.load(pc)
        # Let's set a 'type' that is not a Component
        with pytest.raises(ValidationError,
                           message={'components': ['Computer is not a sub-type of Component']}):
            pc = deepcopy(pc_template)
            pc['components'][0]['type'] = 'Computer'
            schema.load(pc)
Example #9
0
def test_etag_secondary(client: Client, app: Teal):
    """Tests creating, linking and accessing an ETag through
    its secondary (NFC) id."""
    with app.app_context():
        et = ETag(secondary='NFCID')
        db.session.add(et)
        db.session.commit()
    client.get('/', item='NFCID', accept=ANY, status=400)
    with app.app_context():
        tag = ETag.query.filter_by(secondary='NFCID').one()
        tag.devicehub = URL('https://dh.com')
        db.session.commit()
    _, r = client.get('/', item='NFCID', accept=ANY, status=302)
    assert r.location == 'https://dh.com/tags/FO-3MP5M/device'
Example #10
0
def test_tag_export(runner: FlaskCliRunner, app: Teal):
    with app.app_context():
        t = ETag()
        db.session.add(t)
        db.session.commit()
    with NamedTemporaryFile('r+') as f:
        result = runner.invoke(args=('export', f.name), catch_exceptions=False)
        assert result.exit_code == 0
        result = runner.invoke(args=('import', f.name), catch_exceptions=False)
        assert result.exit_code == 0
    with app.app_context():
        t = Tag()
        db.session.add(t)
        db.session.commit()
        assert Tag.decode(t.id) == 2
Example #11
0
def test_http_exception(fconfig: Config, db: SQLAlchemy):
    """Tests correct handling of HTTP exceptions."""
    DeviceDef, *_ = fconfig.RESOURCE_DEFINITIONS  # type: Tuple[ResourceDef]

    DeviceDef.VIEW.get = MagicMock(side_effect=NotFound)
    client = Teal(config=fconfig, db=db).test_client()  # type: Client
    d, _ = client.get(res=DeviceDef.type, status=NotFound)
    assert d == {
        'code':
        404,
        'message':
        '404 Not Found: The requested URL was not found on the server.  '
        'If you entered the URL manually please check your spelling and try again.',
        'type':
        'NotFound'
    }
Example #12
0
def test_json_encoder(app: Teal):
    """
    Ensures that Teal is using the custom JSON Encoder through Flask's
    json.
    """
    with app.app_context():
        # Try to dump a type flask's json encoder cannot handle
        json.dumps({'foo': StrictVersion('1.3')})
Example #13
0
def test_get_not_linked_tag(app: Teal, client: Client):
    """Tests getting a tag that has not been linked yet to a Devicehub."""
    with app.app_context():
        t = Tag()
        db.session.add(t)
        db.session.commit()
        id = t.id
    client.get(res=Tag.t, item=id, status=NoRemoteTag)
Example #14
0
def test_args(fconfig: Config, db: SQLAlchemy):
    """Tests the handling of query arguments in the URL."""
    DeviceDef, *_ = fconfig.RESOURCE_DEFINITIONS  # type: Tuple[ResourceDef]

    class FindArgsFoo(DeviceDef.VIEW.FindArgs):
        foo = Integer()

    DeviceDef.VIEW.FindArgs = FindArgsFoo

    def find(args: dict):
        assert args == {'foo': 25}
        return Response(status=200)

    DeviceDef.VIEW.find = MagicMock(side_effect=find)

    client = Teal(config=fconfig, db=db).test_client()  # type: Client

    # Ok
    client.get(res=DeviceDef.type, query=[('foo', 25)])
    # Extra not needed data
    client.get(res=DeviceDef.type, query=[('foo', 25), ('bar', 'nope')])
    # Wrong data
    r, _ = client.get(res=DeviceDef.type,
                      query=[('foo', 'nope')],
                      status=UnprocessableEntity)
Example #15
0
def test_not_found(app: Teal):
    """
    When not finding a resource, the db should raise a ``NotFound``
    exception instead of the built-in for SQLAlchemy.
    """
    with app.app_context():
        Device = app.resources['Device'].MODEL
        with pytest.raises(NotFound):
            Device.query.one()
Example #16
0
def test_models(app: Teal, db: SQLAlchemy):
    """Checks that the models used in the fixture work."""
    DeviceDef, ComponentDef, ComputerDef = \
        app.config['RESOURCE_DEFINITIONS']  # type: Tuple[ResourceDef]
    Component = ComponentDef.MODEL
    Computer = ComputerDef.MODEL

    component = Component(id=1, model='foo')
    pc = Computer(id=2, model='bar', components=[component])
    with app.app_context():
        db.session.add(pc)
        queried_pc = Computer.query.first()
        assert pc == queried_pc
Example #17
0
def test_validator_is_type(app: Teal):
    """Checks the validator IsType"""
    with app.app_context():
        is_type = IsType()
        is_type('Device')
        is_type('Component')
        with pytest.raises(ValidationError):
            is_type('Foo')

        is_subtype = IsType('Component')
        is_subtype('Component')
        with pytest.raises(ValidationError):
            is_subtype('Computer')
Example #18
0
def app(request):
    class TestConfig(TagsConfig):
        SQLALCHEMY_DATABASE_URI = 'postgresql://*****:*****@localhost/tagtest'
        # SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/tagtest'
        TAG_PROVIDER_ID = 'FO'
        TAG_HASH_SALT = 'So salty'
        SERVER_NAME = 'foo.bar'
        TESTING = True
        DEVICEHUBS = {'soToken': 'https://dh.com'}

    app = Teal(config=TestConfig(), db=db, Auth=Auth)
    db.create_all(app=app)
    # More robust than 'yield'
    request.addfinalizer(lambda *args, **kw: db.drop_all(app=app))
    return app
Example #19
0
def test_tag_creation(app: Teal):
    with app.app_context():
        t = Tag()
        db.session.add(t)
        db.session.commit()
        assert t.id
        assert t.devicehub is None
        assert t.id == '3MP5M'

        et = ETag()
        db.session.add(et)
        db.session.commit()
        assert et.id == 'FO-WNBRM'
        assert et.id.split('-')[0] == 'FO'
        assert len(et.id.split('-')[1]) == 5
        assert et.devicehub is None
Example #20
0
def test_inheritance_access(fconfig: Config, db: SQLAlchemy):
    """
    Tests that the right endpoint is called when accessing sub-resources.
    """
    DeviceDef, ComponentDef, ComputerDef = fconfig.RESOURCE_DEFINITIONS  # type: Tuple[ResourceDef]

    DUMMY_DICT = {'ok': 'yes'}

    def foo(*args, **kw):
        return jsonify(DUMMY_DICT)

    DeviceDef.VIEW.get = MagicMock(side_effect=foo)
    ComponentDef.VIEW.get = MagicMock(side_effect=foo)
    ComputerDef.VIEW.get = MagicMock(side_effect=foo)

    client = Teal(config=fconfig, db=db).test_client()  # type: Client

    # Access any non-defined URI
    client.get(uri='/this-does-not-exist', status=NotFound)
    assert DeviceDef.VIEW.get.call_count == \
           ComponentDef.VIEW.get.call_count == \
           ComputerDef.VIEW.get.call_count == 0

    # Access to a non-defined method for a resource
    client.post(res=DeviceDef.type, status=MethodNotAllowed, data=dict())
    assert DeviceDef.VIEW.get.call_count == \
           ComponentDef.VIEW.get.call_count == \
           ComputerDef.VIEW.get.call_count == 0

    # Get top resource Device
    # Only device endpoint is called
    d, _ = client.get(res=DeviceDef.type)
    assert d == DUMMY_DICT
    DeviceDef.VIEW.get.assert_called_once_with(id=None)
    assert ComponentDef.VIEW.get.call_count == 0
    assert ComputerDef.VIEW.get.call_count == 0

    # Get computer
    # Only component endpoint is called
    d, _ = client.get(res=ComputerDef.type)
    assert d == DUMMY_DICT
    assert DeviceDef.VIEW.get.call_count == 1  # from before
    assert ComponentDef.VIEW.get.call_count == 0
    ComputerDef.VIEW.get.assert_called_once_with(id=None)
Example #21
0
def test_query_sort(app: Teal):
    """Tests sorting params."""
    with app.app_context():
        Device = app.resources['Device'].MODEL

        class Sorting(Sort):
            models = SortField(Device.model)
            ids = SortField(Device.id)

        schema = Sorting()
        sort = tuple(
            str(s) for s in schema.load({
                'models': True,
                'ids': False
            }))
        assert len(sort) == 2
        assert 'device.model ASC' in sort
        assert 'device.id DESC' in sort
Example #22
0
def test_query_join(app: Teal):
    """Checks that nested queries work."""
    with app.app_context():
        Device = app.resources['Device'].MODEL
        Computer = app.resources['Computer'].MODEL

        class Inner(Query):
            foo = ILike(Device.model)

        class Q(Query):
            idq = Between(Device.id, Integer())
            componentq = Join(Device.id == Computer.id, Inner)

        schema = Q()
        query = schema.load({'idq': [1, 4], 'componentq': {'foo': 'bar'}})
        s, params = compiled(Device, query)
        assert 'device.id BETWEEN %(id_1)s AND %(id_2)s' in s
        assert 'device.model ILIKE %(model_1)s' in s
        assert params == {'id_1': 1, 'model_1': 'bar%', 'id_2': 4}
Example #23
0
def test_query(app: Teal):
    with app.app_context():
        Device = app.resources['Device'].MODEL

        class Q(Query):
            idq = Or(Equal(Device.id, Str()))
            modelq = ILike(Device.model)

        schema = Q()
        query = schema.load({'idq': ['a', 'b', 'c'], 'modelq': 'foobar'})
        s, params = compiled(Device, query)
        # Order between query clauses can change
        assert 'device.model ILIKE %(model_1)s' in s
        assert '(device.id = %(id_1)s OR device.id = %(id_2)s OR device.id = %(id_3)s' in s
        assert params == {
            'id_2': 'b',
            'id_3': 'c',
            'id_1': 'a',
            'model_1': 'foobar%'
        }
Example #24
0
def app_context(app: Teal):
    with app.app_context():
        yield
Example #25
0
def app(fconfig: Config, db: SQLAlchemy) -> Teal:
    app = Teal(config=fconfig, db=db)
    with app.app_context():
        app.init_db()
    yield app
Example #26
0
def test_url_for_resource(app: Teal):
    with app.test_request_context():
        # Note we need a test_request_context or flask won't know
        # which base_url to use.
        assert url_for_resource('Computer') == '/computers/'
        assert url_for_resource('Computer', 24) == '/computers/24'
Example #27
0
def client(app: Teal) -> Client:
    return app.test_client()
Example #28
0
def runner(app: Teal) -> FlaskCliRunner:
    return app.test_cli_runner()
Example #29
0
from ereuse_tag.auth import Auth
from ereuse_tag.config import TagsConfig
from ereuse_tag.db import db
from teal.teal import Teal
from teal.auth import TokenAuth


class DeviceTagConf(TagsConfig):
    TAG_PROVIDER_ID = 'DT'
    TAG_HASH_SALT = '$6f/Wspgaswc1xJq5xj'
    SQLALCHEMY_DATABASE_URI = 'postgresql://*****:*****@localhost/tags'
    DEVICEHUBS = {
        '7ad6eb73-d95c-4cdf-bf9f-b33be4e514b3': 'http://localhost:5000/testdb'
    }
    API_DOC_CONFIG_TITLE = 'Tags'
    API_DOC_CONFIG_VERSION = '0.1'
    API_DOC_CONFIG_COMPONENTS = {
        'securitySchemes': {
            'bearerAuth': TokenAuth.API_DOCS
        }
    }
    API_DOC_CLASS_DISCRIMINATOR = 'type'


app = Teal(config=DeviceTagConf(), db=db, Auth=Auth)