diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index de9032d..0000000 --- a/.pylintrc +++ /dev/null @@ -1,426 +0,0 @@ -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist=lxml - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS, manage.py - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. -jobs=1 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -# disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call -disable=R,too-many-function-args,no-self-use,redefined-builtin,invalid-name,too-few-public-methods,broad-except,unexpected-keyword-arg,eval-used,arguments-differ,protected-access,bad-classmethod-argument,duplicate-code - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable= - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio).You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[BASIC] - -# Naming hint for argument names -argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct argument names -argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Naming hint for attribute names -attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct attribute names -attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression matching correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming hint for function names -function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct function names -function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_,id,Base,faker,cv - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=yes - -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Naming hint for method names -method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct method names -method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty - -# Naming hint for variable names -variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct variable names -variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module -max-module-lines=1500 - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=XXX,TODO - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members=fake.* - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local,Profile - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,future.builtins - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=15 - -# Maximum number of attributes for a class (see R0902). -max-attributes=20 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - -# Maximum number of branch for function / method body -max-branches=20 - -# Maximum number of locals for function / method body -max-locals=35 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of statements in function / method body -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index fc946af..899f45b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: python +dist: xenial python: - - '3.6' + - '3.7' install: - pip install -r requirements/test.txt - python setup.py install diff --git a/MANIFEST.in b/MANIFEST.in index 1ee26d5..385ec10 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,7 +7,6 @@ include .bumpversion.cfg include .coveragerc include .cookiecutterrc include .editorconfig -include .pylintrc include AUTHORS.rst include CHANGELOG.rst diff --git a/README.rst b/README.rst index f188819..cc3ca89 100644 --- a/README.rst +++ b/README.rst @@ -59,7 +59,7 @@ Development :: - pyenv virtualenv -p python3.6 3.6.5 protean-flask-dev + pyenv virtualenv -p python3.7 3.7.2 protean-flask-dev To run the all tests run:: diff --git a/requirements.txt b/requirements.txt index b573128..5eaadb8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1 @@ -click==7.0 -protean==0.0.9 -authentic==0.0.9 -protean-flask==0.0.9 --r requirements/dev.txt +-r requirements/prod.txt diff --git a/requirements/dev.txt b/requirements/dev.txt index 48e722f..11c8378 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,7 +1,5 @@ -check-manifest==0.37 -coverage==4.5.1 +-r prod.txt + +check-manifest==0.38 +coverage==4.5.3 docutils==0.14 -flake8==3.5.0 -pylint==2.0.0 -tox==3.1.2 -twine==1.11.0 diff --git a/requirements/prod.txt b/requirements/prod.txt new file mode 100644 index 0000000..fd37020 --- /dev/null +++ b/requirements/prod.txt @@ -0,0 +1,4 @@ +click==7.0 +protean==0.0.11 +authentic==0.0.11 +protean-flask==0.0.11 diff --git a/requirements/test.txt b/requirements/test.txt index 81068cc..c694e2d 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,8 +1,8 @@ -r dev.txt mock==2.0.0 -pytest==3.6.3 -pytest-cov==2.5.1 -pytest-travis-fold==1.3.0 +pluggy==0.9.0 +pytest-cov==2.6.1 pytest-flake8==1.0.4 -pluggy==0.6.0 +pytest-travis-fold==1.3.0 +pytest==4.4.1 diff --git a/setup.py b/setup.py index 101379f..25d49ae 100644 --- a/setup.py +++ b/setup.py @@ -54,7 +54,7 @@ def read(*names, **kwargs): 'Operating System :: POSIX :: Linux', 'Operating System :: OS/2', 'Programming Language :: Python', - 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3 :: Only', 'Topic :: Database', 'Topic :: Software Development :: Libraries', @@ -66,9 +66,9 @@ def read(*names, **kwargs): ], install_requires=[ 'click==7.0', - 'protean==0.0.9', - 'authentic==0.0.9', - 'protean-flask==0.0.9' + 'protean==0.0.11', + 'authentic==0.0.11', + 'protean-flask==0.0.11' ], extras_require={ # eg: diff --git a/tests/conftest.py b/tests/conftest.py index ae6a5d4..1f774d3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,57 @@ """Module to setup Factories and other required artifacts for tests""" import os +import pytest + os.environ['PROTEAN_CONFIG'] = 'tests.support.sample_config' + + +def pytest_addoption(parser): + """Additional options for running tests with pytest""" + parser.addoption( + "--slow", action="store_true", default=False, help="run slow tests" + ) + + +def pytest_collection_modifyitems(config, items): + """Configure special markers on tests, so as to control execution""" + if config.getoption("--slow"): + # --slow given in cli: do not skip slow tests + return + skip_slow = pytest.mark.skip(reason="need --slow option to run") + for item in items: + if "slow" in item.keywords: + item.add_marker(skip_slow) + + +@pytest.fixture(scope="session", autouse=True) +def register_models(): + """Register Test Models with Dict Repo + + Run only once for the entire test suite + """ + from protean.core.repository import repo_factory + from authentic.utils import get_account_entity + + from .support.sample_app.entities import Human + + Account = get_account_entity() + + repo_factory.register(Account) + repo_factory.register(Human) + + +@pytest.fixture(autouse=True) +def run_around_tests(): + """Cleanup Database after each test run""" + from protean.core.repository import repo_factory + from authentic.utils import get_account_entity + from .support.sample_app.entities import Human + + Account = get_account_entity() + + # A test function will be run at this point + yield + + repo_factory.get_repository(Account).delete_all() + repo_factory.get_repository(Human).delete_all() diff --git a/tests/support/sample_config.py b/tests/support/sample_config.py index 0bb9c09..f673ddd 100644 --- a/tests/support/sample_config.py +++ b/tests/support/sample_config.py @@ -16,9 +16,9 @@ TESTING = True # Define the repositories -REPOSITORIES = { +DATABASES = { 'default': { - 'PROVIDER': 'protean.impl.repository.dict_repo' + 'PROVIDER': 'protean.impl.repository.dict_repo.DictProvider' } } diff --git a/tests/test_account_views.py b/tests/test_account_views.py index e09d860..c1d18e1 100644 --- a/tests/test_account_views.py +++ b/tests/test_account_views.py @@ -2,9 +2,9 @@ import base64 import json +import pytest from authentic.utils import get_account_entity from passlib.hash import pbkdf2_sha256 -from protean.core.repository import repo_factory from .support.sample_app import app @@ -14,32 +14,34 @@ class TestViews: """Class to test all the account blueprint views""" - @classmethod - def setup_class(cls): - """ Setup for this test case""" + @pytest.fixture(scope="function", autouse=True) + def client(self): + """ Setup client for test cases """ + yield app.test_client() - # Create the test client - cls.client = app.test_client() - cls.auth_header = { + @pytest.fixture(scope="function", autouse=True) + def auth_header(self): + """ Setup auth header for test cases """ + header = { 'Authorization': b'Basic ' + base64.b64encode(b'janedoe:duMmy@123') } + yield header - # Create a test account - cls.account = Account.create({ + @pytest.fixture(scope="function", autouse=True) + def account(self): + """ Setup dummy account for test cases """ + + new_account = Account.create({ 'email': 'janedoe@domain.com', 'username': 'janedoe', 'name': 'Jane Doe', 'password': pbkdf2_sha256.hash('duMmy@123'), 'phone': '90080000800', }) + yield new_account - @classmethod - def teardown_class(cls): - """ Teardown for this test case """ - repo_factory.Account.delete_all() - - def test_create(self): + def test_create(self, client, auth_header): """ Test creating an account """ # Create an account object @@ -50,9 +52,9 @@ def test_create(self): 'password': 'heLLo@123', 'confirm_password': 'heLLo@123', } - rv = self.client.post( + rv = client.post( '/auth/accounts', data=json.dumps(account_info), - headers=self.auth_header, content_type='application/json') + headers=auth_header, content_type='application/json') assert rv.status_code == 201 expected_resp = { @@ -69,20 +71,20 @@ def test_create(self): } assert rv.json == expected_resp - def test_update(self): + def test_update(self, client, auth_header, account): """ Test updating an existing account """ # update an account object update_info = { 'phone': '9007007007', } - rv = self.client.put( - f'/auth/accounts/{self.account.id}', data=json.dumps(update_info), - headers=self.auth_header, content_type='application/json') + rv = client.put( + f'/auth/accounts/{account.id}', data=json.dumps(update_info), + headers=auth_header, content_type='application/json') assert rv.status_code == 200 expected_resp = { 'account': {'email': 'janedoe@domain.com', - 'id': self.account.id, + 'id': account.id, 'is_active': True, 'is_locked': False, 'is_verified': False, @@ -94,35 +96,48 @@ def test_update(self): } assert rv.json == expected_resp - def test_show(self): + def test_show(self, client, auth_header, account): """ Test showing an existing account """ - rv = self.client.get( - f'/auth/accounts/{self.account.id}', headers=self.auth_header) + rv = client.get( + f'/auth/accounts/{account.id}', headers=auth_header) assert rv.status_code == 200 expected_resp = { 'account': {'email': 'janedoe@domain.com', - 'id': self.account.id, + 'id': account.id, 'is_active': True, 'is_locked': False, 'is_verified': False, 'name': 'Jane Doe', - 'phone': '9007007007', + 'phone': '90080000800', 'timezone': None, 'title': None, 'username': 'janedoe'} } assert rv.json == expected_resp - def test_login(self): + def test_login(self, client, auth_header): """ Test logging in using account credentials """ + # Create an account object + account_info = { + 'username': 'johnny', + 'email': 'johnny@domain.com', + 'name': 'John Doe', + 'password': 'heLLo@123', + 'confirm_password': 'heLLo@123', + } + rv = client.post( + '/auth/accounts', data=json.dumps(account_info), + headers=auth_header, content_type='application/json') + assert rv.status_code == 201 + # Send the login request credentials = { 'username_or_email': 'johnny', 'password': 'heLLo@79', } - rv = self.client.post( + rv = client.post( f'/auth/login', data=json.dumps(credentials), content_type='application/json') assert rv.status_code == 422 @@ -131,24 +146,24 @@ def test_login(self): # Try using the right credentials credentials['password'] = 'heLLo@123' - rv = self.client.post( + rv = client.post( f'/auth/login', data=json.dumps(credentials), content_type='application/json') assert rv.status_code == 200 assert rv.json['username'] == 'johnny' - def test_logout(self): + def test_logout(self, client, auth_header): """ Test logging out of the application """ # Send the logout request - rv = self.client.post( - '/auth/logout', data=json.dumps({}), headers=self.auth_header, + rv = client.post( + '/auth/logout', data=json.dumps({}), headers=auth_header, content_type='application/json') assert rv.status_code == 200 assert rv.json == {'message': 'success'} - def test_update_password(self): + def test_update_password(self, client, auth_header): """ Test updating password of an account """ # update an account object @@ -157,19 +172,19 @@ def test_update_password(self): 'new_password': 'duMmy@456', 'confirm_password': 'duMmy@456', } - rv = self.client.post( + rv = client.post( '/auth/accounts/change-password', data=json.dumps(password_update), - headers=self.auth_header, content_type='application/json') + headers=auth_header, content_type='application/json') assert rv.status_code == 200 expected_resp = {'message': 'Success'} assert rv.json == expected_resp - def test_reset_password(self): + def test_reset_password(self, client, account): """ Test resetting the password for an account """ # Send a reset password request - rv = self.client.post( + rv = client.post( '/auth/accounts/reset-password', data=json.dumps({'email': 'janedoe@domain.com'}), content_type='application/json') @@ -179,7 +194,7 @@ def test_reset_password(self): assert rv.json == expected_resp # Get the verification token for the account - account = Account.get(self.account.id) + account = Account.get(account.id) assert account.verification_token is not None # Send the reset password request @@ -187,7 +202,7 @@ def test_reset_password(self): 'new_password': 'duMmy@789', 'confirm_password': 'duMmy@789', } - rv = self.client.post( + rv = client.post( f'/auth/accounts/reset-password/{account.verification_token}', data=json.dumps(password_update), content_type='application/json') @@ -196,12 +211,12 @@ def test_reset_password(self): expected_resp = {'message': 'Success'} assert rv.json == expected_resp - def test_delete(self): + def test_delete(self, client, account): """ Test deleting an existing account """ auth_header = { 'Authorization': b'Basic ' + - base64.b64encode(b'janedoe:duMmy@789') + base64.b64encode(b'janedoe:duMmy@123') } - rv = self.client.delete( - f'/auth/accounts/{self.account.id}', headers=auth_header) + rv = client.delete( + f'/auth/accounts/{account.id}', headers=auth_header) assert rv.status_code == 204 diff --git a/tests/test_authentication.py b/tests/test_authentication.py index a234d25..1a14646 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -1,9 +1,9 @@ """ Test the authentication mechanism of authentic flask """ import base64 +import pytest from authentic.utils import get_account_entity from passlib.hash import pbkdf2_sha256 -from protean.core.repository import repo_factory from tests.support.sample_app import app Account = get_account_entity() @@ -12,14 +12,16 @@ class TestAuthentication: """Class to test authentication mechanism""" - @classmethod - def setup_class(cls): - """ Setup for this test case""" - # Create the test client - cls.client = app.test_client() + @pytest.fixture(scope="function", autouse=True) + def client(self): + """ Setup client for test cases """ + yield app.test_client() - # Create a test account - cls.account = Account.create({ + @pytest.fixture(scope="function", autouse=True) + def account(self): + """ Setup dummy account for test cases """ + + new_account = Account.create({ 'email': 'johndoe@domain.com', 'username': 'johndoe', 'name': 'John Doe', @@ -27,15 +29,11 @@ def setup_class(cls): 'phone': '90080000800', 'roles': ['ADMIN'] }) + yield new_account - @classmethod - def teardown_class(cls): - """ Teardown for this test case """ - repo_factory.Account.delete_all() - - def test_authenticated_class_view(self): + def test_authenticated_class_view(self, client, account): """ Test the authenticated class based view """ - rv = self.client.get(f'/accounts/{self.account.id}') + rv = client.get(f'/accounts/{account.id}') assert rv.status_code == 401 assert rv.json == { 'code': 401, @@ -45,7 +43,7 @@ def test_authenticated_class_view(self): headers = { 'Authorization': b'Basic ' + base64.b64encode(b'johndoe:dummy@123'), } - rv = self.client.get(f'/accounts/{self.account.id}', headers=headers) + rv = client.get(f'/accounts/{account.id}', headers=headers) assert rv.status_code == 401 assert rv.json == { 'code': 401, 'message': {'_entity': 'Authentication Failed'}} @@ -54,11 +52,11 @@ def test_authenticated_class_view(self): headers = { 'Authorization': b'Basic ' + base64.b64encode(b'johndoe:duMmy@123'), } - rv = self.client.get(f'/accounts/{self.account.id}', headers=headers) + rv = client.get(f'/accounts/{account.id}', headers=headers) assert rv.status_code == 200 assert rv.json == { 'account': {'email': 'johndoe@domain.com', - 'id': self.account.id, + 'id': account.id, 'is_active': True, 'is_locked': False, 'is_verified': False, @@ -69,17 +67,17 @@ def test_authenticated_class_view(self): 'username': 'johndoe'} } - def test_authenticated_func_view(self): + def test_authenticated_func_view(self, client): """ Test the authenticated function based view """ # Test with correct credentials now headers = { 'Authorization': b'Basic ' + base64.b64encode(b'johndoe:duMmy@123'), } - rv = self.client.get('/accounts/current', headers=headers) + rv = client.get('/accounts/current', headers=headers) assert rv.status_code == 200 assert rv.json == {'account': 'johndoe'} # Test with no credentials - rv = self.client.get('/accounts/current') + rv = client.get('/accounts/current') assert rv.status_code == 200 assert rv.json == {'account': 'anonymous'} diff --git a/tests/test_protected_viewset.py b/tests/test_protected_viewset.py index 8723aeb..051da91 100644 --- a/tests/test_protected_viewset.py +++ b/tests/test_protected_viewset.py @@ -2,9 +2,9 @@ import base64 import json +import pytest from authentic.utils import get_account_entity from passlib.hash import pbkdf2_sha256 -from protean.core.repository import repo_factory from .support.sample_app import app from .support.sample_app.entities import Human @@ -15,38 +15,40 @@ class TestGenericAPIResourceSet: """Class to test GenericAPIResourceSet functionality and methods""" - @classmethod - def setup_class(cls): - """ Setup for this test case""" + @pytest.fixture(scope="function", autouse=True) + def client(self): + """ Setup client for test cases """ + yield app.test_client() - # Create the test client - cls.client = app.test_client() - cls.auth_header = { + @pytest.fixture(scope="function", autouse=True) + def auth_header(self): + """ Setup auth header for test cases """ + header = { 'Authorization': b'Basic ' + base64.b64encode(b'janedoe:duMmy@123') } + yield header - # Create a test account - cls.account = Account.create({ + @pytest.fixture(scope="function", autouse=True) + def account(self): + """ Setup dummy account for test cases """ + + new_account = Account.create({ 'email': 'janedoe@domain.com', 'username': 'janedoe', 'name': 'Jane Doe', 'password': pbkdf2_sha256.hash('duMmy@123'), 'phone': '90080000800', }) + yield new_account - @classmethod - def teardown_class(cls): - """ Teardown for this test case """ - repo_factory.Account.delete_all() - - def test_set_show(self): + def test_set_show(self, client, auth_header, account): """ Test retrieving an entity using the resource set""" # Create a human object human = Human.create(id=1, name='John') # Fetch this human by ID - rv = self.client.get('/humans/1') + rv = client.get('/humans/1') assert rv.status_code == 200 expected_resp = { 'human': {'contact': None, 'id': 1, 'name': 'John', @@ -55,56 +57,51 @@ def test_set_show(self): assert rv.json == expected_resp # Fetch again with authentication - rv = self.client.get('/humans/1', headers=self.auth_header) + rv = client.get('/humans/1', headers=auth_header) assert rv.status_code == 200 expected_resp = { 'human': {'contact': None, 'id': 1, 'name': 'John', - 'current_account': self.account.id} + 'current_account': account.id} } assert rv.json == expected_resp # Delete the human now human.delete() - def test_set_list(self): + def test_set_list(self, client, auth_header, account): """ Test listing an entity using the resource set""" # Create Human objects Human.create(id=2, name='Jane') Human.create(id=3, name='Mary') # Get the list of humans - rv = self.client.get('/humans?order_by[]=id') + rv = client.get('/humans?order_by[]=id') assert rv.status_code == 200 assert rv.json['total'] == 2 assert rv.json['humans'][0] == {'id': 2, 'name': 'Jane', 'contact': None, 'current_account': None} # Fetch again with authentication - rv = self.client.get('/humans?order_by[]=id', headers=self.auth_header) + rv = client.get('/humans?order_by[]=id', headers=auth_header) assert rv.status_code == 200 assert rv.json['total'] == 2 assert rv.json['humans'][0] == {'id': 2, 'name': 'Jane', - 'contact': None, 'current_account': self.account.id} + 'contact': None, 'current_account': account.id} - def test_set_create(self): + def test_set_create(self, client, auth_header, account): """ Test creating an entity using the resource set """ # Create a human object - rv = self.client.post('/humans', - data=json.dumps( - dict(id=1, name='John')), - content_type='application/json') + rv = client.post('/humans', data=json.dumps(dict(id=1, name='John')), + content_type='application/json') assert rv.status_code == 401 # Send again with authentication - rv = self.client.post('/humans', - headers=self.auth_header, - data=json.dumps( - dict(id=1, name='John')), - content_type='application/json') + rv = client.post('/humans', headers=auth_header, data=json.dumps(dict(id=1, name='John')), + content_type='application/json') assert rv.status_code == 201 expected_resp = { 'human': {'contact': None, 'id': 1, 'name': 'John', - 'current_account': self.account.id} + 'current_account': account.id} } assert rv.json == expected_resp @@ -112,43 +109,41 @@ def test_set_create(self): human = Human.get(1) human.delete() - def test_set_update(self): + def test_set_update(self, client, auth_header, account): """ Test updating an entity using the resource set """ # Create a human object human = Human.create(id=1, name='John') # Update the human object - rv = self.client.put('/humans/1', - data=json.dumps(dict(contact='9000900090')), - content_type='application/json') + rv = client.put('/humans/1', data=json.dumps(dict(contact='9000900090')), + content_type='application/json') assert rv.status_code == 401 # Send again with authentication - rv = self.client.put('/humans/1', - headers=self.auth_header, - data=json.dumps(dict(contact='9000900090')), - content_type='application/json') + rv = client.put('/humans/1', headers=auth_header, + data=json.dumps(dict(contact='9000900090')), + content_type='application/json') expected_resp = { 'human': {'contact': '9000900090', 'id': 1, 'name': 'John', - 'current_account': self.account.id} + 'current_account': account.id} } assert rv.json == expected_resp # Delete the human now human.delete() - def test_set_delete(self): + def test_set_delete(self, client, auth_header, account): """ Test deleting an entity using the resource set """ # Create a human object Human.create(id=1, name='John') # Delete the dog object - rv = self.client.delete('/humans/1') + rv = client.delete('/humans/1') assert rv.status_code == 401 # Send again with authentication - rv = self.client.delete('/humans/1', headers=self.auth_header) + rv = client.delete('/humans/1', headers=auth_header) assert rv.status_code == 204 assert rv.data == b'' diff --git a/tox.ini b/tox.ini index 56823b7..ca47064 100644 --- a/tox.ini +++ b/tox.ini @@ -4,13 +4,13 @@ envlist = clean, check, - {py36}, + {py37}, report, docs [testenv] basepython = - {py36,docs,spell}: {env:TOXPYTHON:python3.6} + {py37,docs,spell}: {env:TOXPYTHON:python3.7} {bootstrap,clean,check,report,coveralls,codecov}: {env:TOXPYTHON:python3} setenv = PYTHONPATH={toxinidir}/tests