diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aab7ea0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,110 @@ + +# Created by https://www.gitignore.io/api/python +# Edit at https://www.gitignore.io/?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# End of https://www.gitignore.io/api/python diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..e69de29 diff --git a/behave.ini b/behave.ini new file mode 100644 index 0000000..66dd3d5 --- /dev/null +++ b/behave.ini @@ -0,0 +1,5 @@ +[behave] +lang = pt + +[behave.userdata] +base_url = http://localhost:5000 diff --git a/cvapi/__init__.py b/cvapi/__init__.py new file mode 100644 index 0000000..5cc5666 --- /dev/null +++ b/cvapi/__init__.py @@ -0,0 +1,18 @@ +from flask import Flask, jsonify, request +from .schema import ResumeSchema, configure_app +from marshmallow.exceptions import ValidationError + + +def create_app(): + app = Flask(__name__) + configure_app(app) + + @app.route('/validate-json', methods=['POST']) + def validate_json(): + try: + ResumeSchema().load(request.json) + return jsonify({'ok': 'Dados válidos'}), 201 + except ValidationError as m_error: + return jsonify(m_error.normalized_messages()), 400 + + return app diff --git a/cvapi/schema.py b/cvapi/schema.py new file mode 100644 index 0000000..6324497 --- /dev/null +++ b/cvapi/schema.py @@ -0,0 +1,37 @@ +from typing import NoReturn +from flask_marshmallow import Marshmallow, fields +from flask_marshmallow.fields import fields + +ma = Marshmallow() + + +class ResumeSchema(ma.Schema): + name = fields.Str(required=True) + profession = fields.Str(required=True) + phone = fields.Str(required=True) + email = fields.Str(required=True) + address = fields.Str(required=True) + portfolio = fields.Str(required=True) + education = fields.List(fields.List(fields.Raw()), required=True) + experience = fields.List(fields.List(fields.Raw()), required=True) + skills = fields.List(fields.List(fields.Raw()), required=True) + languages = fields.List(fields.List(fields.Raw()), required=True) + + class Meta: + fields = ( + 'name', + 'profession', + 'phone', + 'email', + 'address', + 'portfolio', + 'education', + 'experience', + 'skills', + 'languages', + ) + + +def configure_app(app) -> NoReturn: + """Configurar o app para ter o schema.""" + ma.init_app(app) diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..cb32460 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,485 @@ +[[package]] +category = "dev" +description = "Disable App Nap on OS X 10.9" +marker = "python_version >= \"3.4\" and sys_platform == \"darwin\"" +name = "appnope" +optional = false +python-versions = "*" +version = "0.1.0" + +[[package]] +category = "dev" +description = "Atomic file writes." +name = "atomicwrites" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.3.0" + +[[package]] +category = "dev" +description = "Classes Without Boilerplate" +name = "attrs" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "19.1.0" + +[[package]] +category = "dev" +description = "Specifications for callback functions passed in to an API" +marker = "python_version >= \"3.4\"" +name = "backcall" +optional = false +python-versions = "*" +version = "0.1.0" + +[[package]] +category = "dev" +description = "behave is behaviour-driven development, Python style" +name = "behave" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "1.2.6" + +[package.dependencies] +parse = ">=1.8.2" +parse-type = ">=0.4.2" +six = ">=1.11" + +[[package]] +category = "dev" +description = "Python package for providing Mozilla's CA Bundle." +name = "certifi" +optional = false +python-versions = "*" +version = "2019.9.11" + +[[package]] +category = "dev" +description = "Universal encoding detector for Python 2 and 3" +name = "chardet" +optional = false +python-versions = "*" +version = "3.0.4" + +[[package]] +category = "main" +description = "Composable command line interface toolkit" +name = "click" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "7.0" + +[[package]] +category = "dev" +description = "Cross-platform colored terminal text." +marker = "python_version >= \"3.4\" and sys_platform == \"win32\" or sys_platform == \"win32\"" +name = "colorama" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.4.1" + +[[package]] +category = "dev" +description = "Code coverage measurement for Python" +name = "coverage" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4" +version = "4.5.4" + +[[package]] +category = "dev" +description = "Better living through Python with decorators" +marker = "python_version >= \"3.4\"" +name = "decorator" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*" +version = "4.4.0" + +[[package]] +category = "main" +description = "A simple framework for building complex web applications." +name = "flask" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "1.1.1" + +[package.dependencies] +Jinja2 = ">=2.10.1" +Werkzeug = ">=0.15" +click = ">=5.1" +itsdangerous = ">=0.24" + +[[package]] +category = "main" +description = "Flask + marshmallow for beautiful APIs" +name = "flask-marshmallow" +optional = false +python-versions = "*" +version = "0.10.1" + +[package.dependencies] +Flask = "*" +marshmallow = ">=2.0.0" +six = ">=1.9.0" + +[[package]] +category = "dev" +description = "Internationalized Domain Names in Applications (IDNA)" +name = "idna" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.8" + +[[package]] +category = "dev" +description = "Read metadata from Python packages" +marker = "python_version < \"3.8\"" +name = "importlib-metadata" +optional = false +python-versions = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3" +version = "0.23" + +[package.dependencies] +zipp = ">=0.5" + +[[package]] +category = "dev" +description = "IPython-enabled pdb" +name = "ipdb" +optional = false +python-versions = ">=2.7" +version = "0.12.2" + +[package.dependencies] +setuptools = "*" + +[package.dependencies.ipython] +python = ">=3.4" +version = ">=5.1.0" + +[[package]] +category = "dev" +description = "IPython: Productive Interactive Computing" +marker = "python_version >= \"3.4\"" +name = "ipython" +optional = false +python-versions = ">=3.5" +version = "7.8.0" + +[package.dependencies] +appnope = "*" +backcall = "*" +colorama = "*" +decorator = "*" +jedi = ">=0.10" +pexpect = "*" +pickleshare = "*" +prompt-toolkit = ">=2.0.0,<2.1.0" +pygments = "*" +setuptools = ">=18.5" +traitlets = ">=4.2" + +[[package]] +category = "dev" +description = "Vestigial utilities from IPython" +marker = "python_version >= \"3.4\"" +name = "ipython-genutils" +optional = false +python-versions = "*" +version = "0.2.0" + +[[package]] +category = "main" +description = "Various helpers to pass data to untrusted environments and back." +name = "itsdangerous" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.1.0" + +[[package]] +category = "dev" +description = "An autocompletion tool for Python that can be used for text editors." +marker = "python_version >= \"3.4\"" +name = "jedi" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.15.1" + +[package.dependencies] +parso = ">=0.5.0" + +[[package]] +category = "main" +description = "A small but fast and easy to use stand-alone template engine written in pure python." +name = "jinja2" +optional = false +python-versions = "*" +version = "2.10.1" + +[package.dependencies] +MarkupSafe = ">=0.23" + +[[package]] +category = "main" +description = "Safely add untrusted strings to HTML/XML markup." +name = "markupsafe" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "1.1.1" + +[[package]] +category = "main" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +name = "marshmallow" +optional = false +python-versions = ">=3.5" +version = "3.2.0" + +[[package]] +category = "dev" +description = "More routines for operating on iterables, beyond itertools" +name = "more-itertools" +optional = false +python-versions = ">=3.4" +version = "7.2.0" + +[[package]] +category = "dev" +description = "parse() is the opposite of format()" +name = "parse" +optional = false +python-versions = "*" +version = "1.12.1" + +[[package]] +category = "dev" +description = "Simplifies to build parse types based on the parse module" +name = "parse-type" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*" +version = "0.5.2" + +[package.dependencies] +parse = ">=1.8.4" +six = ">=1.11" + +[[package]] +category = "dev" +description = "A Python Parser" +marker = "python_version >= \"3.4\"" +name = "parso" +optional = false +python-versions = "*" +version = "0.5.1" + +[[package]] +category = "dev" +description = "Pexpect allows easy control of interactive console applications." +marker = "python_version >= \"3.4\" and sys_platform != \"win32\"" +name = "pexpect" +optional = false +python-versions = "*" +version = "4.7.0" + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +category = "dev" +description = "Tiny 'shelve'-like database with concurrency support" +marker = "python_version >= \"3.4\"" +name = "pickleshare" +optional = false +python-versions = "*" +version = "0.7.5" + +[[package]] +category = "dev" +description = "plugin and hook calling mechanisms for python" +name = "pluggy" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.13.0" + +[package.dependencies] +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12" + +[[package]] +category = "dev" +description = "Library for building powerful interactive command lines in Python" +marker = "python_version >= \"3.4\"" +name = "prompt-toolkit" +optional = false +python-versions = "*" +version = "2.0.9" + +[package.dependencies] +six = ">=1.9.0" +wcwidth = "*" + +[[package]] +category = "dev" +description = "Run a subprocess in a pseudo terminal" +marker = "python_version >= \"3.4\" and sys_platform != \"win32\"" +name = "ptyprocess" +optional = false +python-versions = "*" +version = "0.6.0" + +[[package]] +category = "dev" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +name = "py" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.8.0" + +[[package]] +category = "dev" +description = "Pygments is a syntax highlighting package written in Python." +marker = "python_version >= \"3.4\"" +name = "pygments" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.4.2" + +[[package]] +category = "dev" +description = "pytest: simple powerful testing with Python" +name = "pytest" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "3.10.1" + +[package.dependencies] +atomicwrites = ">=1.0" +attrs = ">=17.4.0" +colorama = "*" +more-itertools = ">=4.0.0" +pluggy = ">=0.7" +py = ">=1.5.0" +setuptools = "*" +six = ">=1.10.0" + +[[package]] +category = "dev" +description = "Python HTTP for Humans." +name = "requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.22.0" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<3.1.0" +idna = ">=2.5,<2.9" +urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" + +[[package]] +category = "main" +description = "Python 2 and 3 compatibility utilities" +name = "six" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*" +version = "1.12.0" + +[[package]] +category = "dev" +description = "Traitlets Python config system" +marker = "python_version >= \"3.4\"" +name = "traitlets" +optional = false +python-versions = "*" +version = "4.3.2" + +[package.dependencies] +decorator = "*" +ipython-genutils = "*" +six = "*" + +[[package]] +category = "dev" +description = "HTTP library with thread-safe connection pooling, file post, and more." +name = "urllib3" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" +version = "1.25.6" + +[[package]] +category = "dev" +description = "Measures number of Terminal column cells of wide-character codes" +marker = "python_version >= \"3.4\"" +name = "wcwidth" +optional = false +python-versions = "*" +version = "0.1.7" + +[[package]] +category = "main" +description = "The comprehensive WSGI web application library." +name = "werkzeug" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.16.0" + +[[package]] +category = "dev" +description = "Backport of pathlib-compatible object wrapper for zip files" +marker = "python_version < \"3.8\"" +name = "zipp" +optional = false +python-versions = ">=2.7" +version = "0.6.0" + +[package.dependencies] +more-itertools = "*" + +[metadata] +content-hash = "51971049b72390a11c3b36331b37b7a1b7372f99600efd58c9eaee7617ba938b" +python-versions = "^3.7" + +[metadata.hashes] +appnope = ["5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", "8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"] +atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"] +attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"] +backcall = ["38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", "bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"] +behave = ["b9662327aa53294c1351b0a9c369093ccec1d21026f050c3bd9b3e5cccf81a86", "ebda1a6c9e5bfe95c5f9f0a2794e01c7098b3dde86c10a95d8621c5907ff6f1c"] +certifi = ["e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", "fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"] +chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"] +click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"] +colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"] +coverage = ["08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", "0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", "141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", "19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", "23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", "245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", "331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", "386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", "3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", "60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", "63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", "6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", "6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", "7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", "826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", "93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", "9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", "af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", "bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", "bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", "c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", "dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", "df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", "e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", "e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", "e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", "eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", "eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", "ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", "efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", "fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", "ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"] +decorator = ["86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de", "f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6"] +flask = ["13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52", "45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"] +flask-marshmallow = ["4f507f883838b397638a3a36c7d36ee146b255a49db952f5d9de3f6f4522e8a8", "69e99e3a123393894884a032ae2d11e6bdf4519a505819b66cec7eda32057741"] +idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"] +importlib-metadata = ["aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", "d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"] +ipdb = ["473fdd798a099765f093231a8b1fabfa95b0b682fce12de0c74b61a4b4d8ee57"] +ipython = ["c4ab005921641e40a68e405e286e7a1fcc464497e14d81b6914b4fd95e5dee9b", "dd76831f065f17bddd7eaa5c781f5ea32de5ef217592cf019e34043b56895aa1"] +ipython-genutils = ["72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", "eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"] +itsdangerous = ["321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", "b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"] +jedi = ["786b6c3d80e2f06fd77162a07fed81b8baa22dde5d62896a790a331d6ac21a27", "ba859c74fa3c966a22f2aeebe1b74ee27e2a462f56d3f5f7ca4a59af61bfe42e"] +jinja2 = ["065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"] +markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"] +marshmallow = ["6dca0125320c15795c4f2dfe8f2f5c37a96916e7a09a123eba05ef24d3126b94", "801a7c70f0596b812a086773b9d7ba85b4bbad1becab14cde460ab7798511409"] +more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"] +parse = ["a5fca7000c6588d77bc65c28f3f21bfce03b5e44daa8f9f07c17fe364990d717"] +parse-type = ["089a471b06327103865dfec2dd844230c3c658a4a1b5b4c8b6c16c8f77577f9e", "7f690b18d35048c15438d6d0571f9045cffbec5907e0b1ccf006f889e3a38c0b"] +parso = ["63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc", "666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c"] +pexpect = ["2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1", "9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb"] +pickleshare = ["87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", "9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"] +pluggy = ["0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", "fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"] +prompt-toolkit = ["11adf3389a996a6d45cc277580d0d53e8a5afd281d0c9ec71b28e6f121463780", "2519ad1d8038fd5fc8e770362237ad0364d16a7650fb5724af6997ed5515e3c1", "977c6583ae813a37dc1c2e1b715892461fcbdaa57f6fc62f33a528c4886c8f55"] +ptyprocess = ["923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", "d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"] +py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"] +pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"] +pytest = ["3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec", "e246cf173c01169b9617fc07264b7b1316e78d7a650055235d6d897bc80d9660"] +requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"] +six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"] +traitlets = ["9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", "c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9"] +urllib3 = ["3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", "9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"] +wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"] +werkzeug = ["7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7", "e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"] +zipp = ["3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", "f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9f5f00a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[tool.poetry] +name = "cvapi" +version = "0.1.0" +description = "" +authors = ["dunossauro "] + +[tool.poetry.dependencies] +python = "^3.7" +flask = "^1.1" +marshmallow = "^3.2" +flask-marshmallow = "^0.10.1" + +[tool.poetry.dev-dependencies] +pytest = "^3.0" +behave = "^1.2" +requests = "^2.22" +ipdb = "^0.12.2" +coverage = "^4.5" + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/tests/behavior/features/environment.py b/tests/behavior/features/environment.py new file mode 100644 index 0000000..aeff39f --- /dev/null +++ b/tests/behavior/features/environment.py @@ -0,0 +1,9 @@ +from ipdb import spost_mortem + + +def before_all(context): + context.base_url = context.config.userdata.get('base_url', None) + +def after_step(context, step): + if context.config.userdata.get('debug', None) and step.status == 'failed': + spost_mortem(step.exc_traceback) diff --git a/tests/behavior/features/json_endpoint.feature b/tests/behavior/features/json_endpoint.feature new file mode 100644 index 0000000..e6d7820 --- /dev/null +++ b/tests/behavior/features/json_endpoint.feature @@ -0,0 +1,38 @@ +Funcionalidade: Endpoint de validação do Json + + Cenário: Json inválido + Quando enviar o json + """ + {"Nome": "Eduardo"} + """ + Então a API deve retornar 400 + + Cenário: Json válido + Quando enviar o json + """ + { + "name": "George the Martian", + "profession": "Youtube senior analyst", + "phone": "+55 287298121", + "email": "George@martian.com", + "address": "73 N. Oak Drive - Dyersburg, TN 38024", + "portfolio": "github.com/g_martian", + "education": [ + ["PhD in the streets - Harvard", + "2001", + "phd in doing absolutelly nothing 24/7"] + ], + "experience": [ + ["Garbage collector", + "Jan 2000 - Mai 2003", + "Collecting garbage in other codes"] + ], + "skills": [ + ["Python", 5] + ], + "languages": [ + ["Pirate English", "Native"] + ] + } + """ + Então a API deve retornar 201 diff --git a/tests/behavior/features/steps/endpoint.py b/tests/behavior/features/steps/endpoint.py new file mode 100644 index 0000000..6c89263 --- /dev/null +++ b/tests/behavior/features/steps/endpoint.py @@ -0,0 +1,16 @@ +from behave import when, then +from requests import post +from json import loads + + +@when('enviar o json') +def request_api_with_json(context): + context.api_request = post( + context.base_url + '/validate-json', + json=loads(context.text) + ) + + +@then('a API deve retornar {status_code:d}') +def check_api_response(context, status_code): + assert context.api_request.status_code == status_code diff --git a/tests/test_factory.py b/tests/test_factory.py new file mode 100644 index 0000000..f781465 --- /dev/null +++ b/tests/test_factory.py @@ -0,0 +1,26 @@ +from unittest import TestCase +from flask import Flask +import cvapi + + +class TestFlaskApp(TestCase): + def test_create_app_deve_existir(self): + self.assertEqual( + hasattr(cvapi, 'create_app'), + True, + 'app factory não existe' + ) + + def test_create_app_deve_ser_invocavel(self): + self.assertEqual( + hasattr(cvapi.create_app, '__call__'), + True, + 'create_app não é invocável' + ) + + def test_create_app_deve_retornar_um_flask_app(self): + self.assertIsInstance( + cvapi.create_app(), + Flask, + 'create_app não retornar um app Flask' + ) diff --git a/tests/test_schema.py b/tests/test_schema.py new file mode 100644 index 0000000..011187c --- /dev/null +++ b/tests/test_schema.py @@ -0,0 +1,32 @@ +from unittest import TestCase +from cvapi import schema + + +class TestSchema(TestCase): + def test_deve_ter_configure_app(self): + self.assertEqual( + hasattr(schema, 'configure_app'), + True, + 'schema não tem função de configuração' + ) + + def test_deve_ter_ma(self): + self.assertEqual( + hasattr(schema, 'ma'), + True, + 'schema não tem class Marshmallow definida' + ) + + def test_configure_app_deve_ser_invocavel(self): + self.assertEqual( + hasattr(schema.configure_app, '__call__'), + True, + 'configure_app não é invocável' + ) + + def test_configure_app_deve_receber_app(self): + self.assertEqual( + schema.configure_app.__code__.co_argcount, + 1, + 'configure_app não recebe app' + ) diff --git a/tests/test_validate_json.py b/tests/test_validate_json.py new file mode 100644 index 0000000..b76bf01 --- /dev/null +++ b/tests/test_validate_json.py @@ -0,0 +1,125 @@ +from unittest import TestCase +from typing import List +from flask import url_for, Flask +from cvapi import create_app + +json_valido = { + "name": "George the Martian", + "profession": "Youtube senior analyst", + "phone": "+55 287298121", + "email": "George@martian.com", + "address": "73 N. Oak Drive - Dyersburg, TN 38024", + "portfolio": "github.com/g_martian", + "education": [ + [ + "PhD in the streets - Harvard", + "2001", + "phd in doing absolutelly nothing 24/7" + ] + ], + "experience": [ + [ + "Garbage collector", + "Jan 2000 - Mai 2003", + "Collecting garbage in other codes" + ] + ], + "skills": [ + [ + "Python", + 5 + ] + ], + "languages": [ + [ + "Pirate English", + "Native" + ] + ] +} + +def app_endpoint(app: Flask) -> List[str]: + """Retornar todos os endpoints do `app`.""" + return [e.endpoint for e in app.url_map.iter_rules()] + + +class FlaskBaseTestCase(TestCase): + def setUp(self): + self.app = create_app() + self.app.testing = True + self.app_context = self.app.test_request_context() + self.app_context.push() + self.client = self.app.test_client() + + +class TestRouteValidateJson(FlaskBaseTestCase): + def test_app_deve_conter_a_rota_validate_json(self): + endpoints = app_endpoint(create_app()) + self.assertIn( + 'validate_json', + endpoints, + 'o endpoint validade-json não existe no APP' + ) + + def test_deve_retornar_um_requisicao_valida(self): + response = self.client.post( + url_for('validate_json') + ) + self.assertIn( + response.status_code, + list(range(100, 511)) + ) + + def test_deve_receber_um_objeto_json(self): + response = self.client.post( + url_for('validate_json'), + json={} + ) + + self.assertIsInstance(response.json, dict) + + def test_deve_receber_um_vazio_e_retornar_o_schema(self): + response = self.client.post( + url_for('validate_json'), + json={} + ) + schema_esperado = { + "address": [ + "Missing data for required field." + ], + "education": [ + "Missing data for required field." + ], + "email": [ + "Missing data for required field." + ], + "experience": [ + "Missing data for required field." + ], + "languages": [ + "Missing data for required field." + ], + "name": [ + "Missing data for required field." + ], + "phone": [ + "Missing data for required field." + ], + "portfolio": [ + "Missing data for required field." + ], + "profession": [ + "Missing data for required field." + ], + "skills": [ + "Missing data for required field." + ] + } + self.assertEqual(response.json, schema_esperado) + + def test_deve_retornar_dados_validos_com_json_correto(self): + response = self.client.post( + url_for('validate_json'), + json=json_valido + ) + self.assertEqual(response.json, {'ok': 'Dados válidos'})