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'
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
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)
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
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'}]
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
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)
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
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)
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
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' }
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)
def app(fconfig: Config, db: SQLAlchemy) -> Teal: app = Teal(config=fconfig, db=db) with app.app_context(): app.init_db() yield app
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)