Ejemplo n.º 1
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'
Ejemplo n.º 2
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
Ejemplo n.º 3
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)
Ejemplo n.º 4
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
Ejemplo n.º 5
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'}]
Ejemplo n.º 6
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
Ejemplo n.º 7
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)
Ejemplo n.º 8
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
Ejemplo n.º 9
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)
Ejemplo n.º 10
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
Ejemplo n.º 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'
    }
Ejemplo n.º 12
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)
Ejemplo n.º 13
0
def app(fconfig: Config, db: SQLAlchemy) -> Teal:
    app = Teal(config=fconfig, db=db)
    with app.app_context():
        app.init_db()
    yield app
Ejemplo n.º 14
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)