...
This commit is contained in:
127
.gitignore
vendored
Normal file
127
.gitignore
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Python template
|
||||
# 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
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# 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
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Microservices with Docker, Flask, and React
|
||||
|
||||
[](https://travis-ci.org/zevaverbach/testdriven)
|
||||
34
docker-compose-prod.yml
Normal file
34
docker-compose-prod.yml
Normal file
@@ -0,0 +1,34 @@
|
||||
version: '3.7'
|
||||
|
||||
services:
|
||||
users:
|
||||
build:
|
||||
context: ./services/users
|
||||
dockerfile: Dockerfile-prod
|
||||
expose:
|
||||
- 5000
|
||||
environment:
|
||||
- FLASK_ENV=production
|
||||
- APP_SETTINGS=project.config.ProductionConfig
|
||||
- DATABASE_URL=postgres://postgres:postgres@users-db:5432/users_dev
|
||||
- DATABASE_TEST_URL=postgres://postgres:postgres@users-db:5432/users_test
|
||||
depends_on:
|
||||
- users-db
|
||||
users-db:
|
||||
build:
|
||||
context: ./services/users/project/db
|
||||
dockerfile: Dockerfile
|
||||
expose:
|
||||
- 5432
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
nginx:
|
||||
build:
|
||||
context: ./services/nginx
|
||||
dockerfile: Dockerfile-prod
|
||||
restart: always
|
||||
ports:
|
||||
- "80:80"
|
||||
depends_on:
|
||||
- users
|
||||
37
docker-compose.yml
Normal file
37
docker-compose.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
version: '3.7'
|
||||
|
||||
services:
|
||||
users:
|
||||
build:
|
||||
context: ./services/users
|
||||
dockerfile: Dockerfile
|
||||
volumes:
|
||||
- './services/users:/usr/src/app'
|
||||
ports:
|
||||
- "5001:5000"
|
||||
environment:
|
||||
- FLASK_ENV=development
|
||||
- APP_SETTINGS=project.config.DevelopmentConfig
|
||||
- DATABASE_URL=postgres://postgres:postgres@users-db:5432/users_dev
|
||||
- DATABASE_TEST_URL=postgres://postgres:postgres@users-db:5432/users_test
|
||||
depends_on:
|
||||
- users-db
|
||||
users-db:
|
||||
build:
|
||||
context: ./services/users/project/db
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "5435:5432"
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
nginx:
|
||||
build:
|
||||
context: ./services/nginx
|
||||
dockerfile: Dockerfile
|
||||
restart: always
|
||||
ports:
|
||||
- "80:80"
|
||||
depends_on:
|
||||
- users
|
||||
|
||||
4
services/nginx/Dockerfile
Normal file
4
services/nginx/Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
||||
FROM nginx:1.15.9-alpine
|
||||
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
COPY /dev.conf /etc/nginx/conf.d
|
||||
4
services/nginx/Dockerfile-prod
Normal file
4
services/nginx/Dockerfile-prod
Normal file
@@ -0,0 +1,4 @@
|
||||
FROM nginx:1.15.9-alpine
|
||||
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
COPY /prod.conf /etc/nginx/conf.d
|
||||
0
services/nginx/__init__.py
Normal file
0
services/nginx/__init__.py
Normal file
14
services/nginx/dev.conf
Normal file
14
services/nginx/dev.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
server {
|
||||
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
proxy_pass http://users:5000;
|
||||
proxy_redirect default;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Host $server_name;
|
||||
}
|
||||
|
||||
}
|
||||
14
services/nginx/prod.conf
Normal file
14
services/nginx/prod.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
server {
|
||||
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
proxy_pass http://users:5000;
|
||||
proxy_redirect default;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Host $server_name;
|
||||
}
|
||||
|
||||
}
|
||||
4
services/users/.dockerignore
Normal file
4
services/users/.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
||||
env
|
||||
.dockerignore
|
||||
Dockerfile
|
||||
Dockerfile-prod
|
||||
24
services/users/.travis.yml
Normal file
24
services/users/.travis.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
language: python
|
||||
sudo: required
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
env:
|
||||
- DOCKER_COMPOSE_VERSION=1.23.2
|
||||
|
||||
before_install:
|
||||
- sudo rm /usr/local/bin/docker-compose
|
||||
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
|
||||
- chmod +x docker-compose
|
||||
- sudo mv docker-compose /usr/local/bin
|
||||
|
||||
before_script:
|
||||
- docker-compose up -d --build
|
||||
|
||||
script:
|
||||
- docker-compose exec users python manage.py test
|
||||
- docker-compose exec users flake8 project
|
||||
|
||||
after_script:
|
||||
- docker-compose down
|
||||
17
services/users/Dockerfile
Normal file
17
services/users/Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM python:3.8.0-alpine
|
||||
|
||||
RUN apk update && \
|
||||
apk add --virtual build-deps gcc python-dev musl-dev && \
|
||||
apk add postgresql-dev netcat-openbsd
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY ./requirements.txt /usr/src/app/requirements.txt
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY ./entrypoint.sh /usr/src/app/entrypoint.sh
|
||||
RUN chmod +x /usr/src/app/entrypoint.sh
|
||||
|
||||
COPY . /usr/src/app
|
||||
|
||||
CMD ["/usr/src/app/entrypoint.sh"]
|
||||
17
services/users/Dockerfile-prod
Normal file
17
services/users/Dockerfile-prod
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM python:3.8.0-alpine
|
||||
|
||||
RUN apk update && \
|
||||
apk add --virtual build-deps gcc python-dev musl-dev && \
|
||||
apk add postgresql-dev netcat-openbsd
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY ./requirements.txt /usr/src/app/requirements.txt
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY ./entrypoint-prod.sh /usr/src/app/entrypoint-prod.sh
|
||||
RUN chmod +x /usr/src/app/entrypoint-prod.sh
|
||||
|
||||
COPY . /usr/src/app
|
||||
|
||||
CMD ["/usr/src/app/entrypoint-prod.sh"]
|
||||
18
services/users/Pipfile
Normal file
18
services/users/Pipfile
Normal file
@@ -0,0 +1,18 @@
|
||||
[[source]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[packages]
|
||||
flask = "*"
|
||||
flask-restful = "*"
|
||||
flask-sqlalchemy = "*"
|
||||
psycopg2-binary = "*"
|
||||
flask-testing = "*"
|
||||
coverage = "*"
|
||||
flake8 = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.8"
|
||||
252
services/users/Pipfile.lock
generated
Normal file
252
services/users/Pipfile.lock
generated
Normal file
@@ -0,0 +1,252 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "77ca234305c2fbf685654a912edd73bd100774766adeb44a9cafa01deb77a205"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.8"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"aniso8601": {
|
||||
"hashes": [
|
||||
"sha256:529dcb1f5f26ee0df6c0a1ee84b7b27197c3c50fc3a6321d66c544689237d072",
|
||||
"sha256:c033f63d028b9a58e3ab0c2c7d0532ab4bfa7452bfc788fbfe3ddabd327b181a"
|
||||
],
|
||||
"version": "==8.0.0"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
|
||||
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
|
||||
],
|
||||
"version": "==7.0"
|
||||
},
|
||||
"coverage": {
|
||||
"hashes": [
|
||||
"sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6",
|
||||
"sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650",
|
||||
"sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5",
|
||||
"sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d",
|
||||
"sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351",
|
||||
"sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755",
|
||||
"sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef",
|
||||
"sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca",
|
||||
"sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca",
|
||||
"sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9",
|
||||
"sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc",
|
||||
"sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5",
|
||||
"sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f",
|
||||
"sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe",
|
||||
"sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888",
|
||||
"sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5",
|
||||
"sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce",
|
||||
"sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5",
|
||||
"sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e",
|
||||
"sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e",
|
||||
"sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9",
|
||||
"sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437",
|
||||
"sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1",
|
||||
"sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c",
|
||||
"sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24",
|
||||
"sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47",
|
||||
"sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2",
|
||||
"sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28",
|
||||
"sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c",
|
||||
"sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7",
|
||||
"sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0",
|
||||
"sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.5.4"
|
||||
},
|
||||
"entrypoints": {
|
||||
"hashes": [
|
||||
"sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",
|
||||
"sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"
|
||||
],
|
||||
"version": "==0.3"
|
||||
},
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb",
|
||||
"sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.7.9"
|
||||
},
|
||||
"flask": {
|
||||
"hashes": [
|
||||
"sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52",
|
||||
"sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"flask-restful": {
|
||||
"hashes": [
|
||||
"sha256:ecd620c5cc29f663627f99e04f17d1f16d095c83dc1d618426e2ad68b03092f8",
|
||||
"sha256:f8240ec12349afe8df1db168ea7c336c4e5b0271a36982bff7394f93275f2ca9"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.3.7"
|
||||
},
|
||||
"flask-sqlalchemy": {
|
||||
"hashes": [
|
||||
"sha256:0078d8663330dc05a74bc72b3b6ddc441b9a744e2f56fe60af1a5bfc81334327",
|
||||
"sha256:6974785d913666587949f7c2946f7001e4fa2cb2d19f4e69ead02e4b8f50b33d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.4.1"
|
||||
},
|
||||
"flask-testing": {
|
||||
"hashes": [
|
||||
"sha256:dc076623d7d850653a018cb64f500948334c8aeb6b10a5a842bf1bcfb98122bc"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.7.1"
|
||||
},
|
||||
"itsdangerous": {
|
||||
"hashes": [
|
||||
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
|
||||
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
|
||||
],
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f",
|
||||
"sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"
|
||||
],
|
||||
"version": "==2.10.3"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
|
||||
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
|
||||
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
|
||||
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
|
||||
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
|
||||
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
|
||||
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
|
||||
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
|
||||
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
|
||||
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
|
||||
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
|
||||
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
|
||||
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
|
||||
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
|
||||
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
|
||||
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
|
||||
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
|
||||
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
|
||||
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
|
||||
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
|
||||
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
|
||||
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
|
||||
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
|
||||
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
|
||||
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
|
||||
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
|
||||
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
|
||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"
|
||||
],
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
||||
],
|
||||
"version": "==0.6.1"
|
||||
},
|
||||
"psycopg2-binary": {
|
||||
"hashes": [
|
||||
"sha256:040234f8a4a8dfd692662a8308d78f63f31a97e1c42d2480e5e6810c48966a29",
|
||||
"sha256:086f7e89ec85a6704db51f68f0dcae432eff9300809723a6e8782c41c2f48e03",
|
||||
"sha256:18ca813fdb17bc1db73fe61b196b05dd1ca2165b884dd5ec5568877cabf9b039",
|
||||
"sha256:19dc39616850342a2a6db70559af55b22955f86667b5f652f40c0e99253d9881",
|
||||
"sha256:2166e770cb98f02ed5ee2b0b569d40db26788e0bf2ec3ae1a0d864ea6f1d8309",
|
||||
"sha256:3a2522b1d9178575acee4adf8fd9f979f9c0449b00b4164bb63c3475ea6528ed",
|
||||
"sha256:3aa773580f85a28ffdf6f862e59cb5a3cc7ef6885121f2de3fca8d6ada4dbf3b",
|
||||
"sha256:3b5deaa3ee7180585a296af33e14c9b18c218d148e735c7accf78130765a47e3",
|
||||
"sha256:407af6d7e46593415f216c7f56ba087a9a42bd6dc2ecb86028760aa45b802bd7",
|
||||
"sha256:4c3c09fb674401f630626310bcaf6cd6285daf0d5e4c26d6e55ca26a2734e39b",
|
||||
"sha256:4c6717962247445b4f9e21c962ea61d2e884fc17df5ddf5e35863b016f8a1f03",
|
||||
"sha256:50446fae5681fc99f87e505d4e77c9407e683ab60c555ec302f9ac9bffa61103",
|
||||
"sha256:5057669b6a66aa9ca118a2a860159f0ee3acf837eda937bdd2a64f3431361a2d",
|
||||
"sha256:5dd90c5438b4f935c9d01fcbad3620253da89d19c1f5fca9158646407ed7df35",
|
||||
"sha256:659c815b5b8e2a55193ede2795c1e2349b8011497310bb936da7d4745652823b",
|
||||
"sha256:69b13fdf12878b10dc6003acc8d0abf3ad93e79813fd5f3812497c1c9fb9be49",
|
||||
"sha256:7a1cb80e35e1ccea3e11a48afe65d38744a0e0bde88795cc56a4d05b6e4f9d70",
|
||||
"sha256:7e6e3c52e6732c219c07bd97fff6c088f8df4dae3b79752ee3a817e6f32e177e",
|
||||
"sha256:7f42a8490c4fe854325504ce7a6e4796b207960dabb2cbafe3c3959cb00d1d7e",
|
||||
"sha256:84156313f258eafff716b2961644a4483a9be44a5d43551d554844d15d4d224e",
|
||||
"sha256:8578d6b8192e4c805e85f187bc530d0f52ba86c39172e61cd51f68fddd648103",
|
||||
"sha256:890167d5091279a27e2505ff0e1fb273f8c48c41d35c5b92adbf4af80e6b2ed6",
|
||||
"sha256:98e10634792ac0e9e7a92a76b4991b44c2325d3e7798270a808407355e7bb0a1",
|
||||
"sha256:9aadff9032e967865f9778485571e93908d27dab21d0fdfdec0ca779bb6f8ad9",
|
||||
"sha256:9f24f383a298a0c0f9b3113b982e21751a8ecde6615494a3f1470eb4a9d70e9e",
|
||||
"sha256:a73021b44813b5c84eda4a3af5826dd72356a900bac9bd9dd1f0f81ee1c22c2f",
|
||||
"sha256:afd96845e12638d2c44d213d4810a08f4dc4a563f9a98204b7428e567014b1cd",
|
||||
"sha256:b73ddf033d8cd4cc9dfed6324b1ad2a89ba52c410ef6877998422fcb9c23e3a8",
|
||||
"sha256:b8f490f5fad1767a1331df1259763b3bad7d7af12a75b950c2843ba319b2415f",
|
||||
"sha256:dbc5cd56fff1a6152ca59445178652756f4e509f672e49ccdf3d79c1043113a4",
|
||||
"sha256:eac8a3499754790187bb00574ab980df13e754777d346f85e0ff6df929bcd964",
|
||||
"sha256:eaed1c65f461a959284649e37b5051224f4db6ebdc84e40b5e65f2986f101a08"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.8.4"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
|
||||
"sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
|
||||
],
|
||||
"version": "==2.5.0"
|
||||
},
|
||||
"pyflakes": {
|
||||
"hashes": [
|
||||
"sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
|
||||
"sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
|
||||
],
|
||||
"version": "==2.1.1"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
|
||||
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
|
||||
],
|
||||
"version": "==2019.3"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd",
|
||||
"sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"
|
||||
],
|
||||
"version": "==1.13.0"
|
||||
},
|
||||
"sqlalchemy": {
|
||||
"hashes": [
|
||||
"sha256:afa5541e9dea8ad0014251bc9d56171ca3d8b130c9627c6cb3681cff30be3f8a"
|
||||
],
|
||||
"version": "==1.3.11"
|
||||
},
|
||||
"werkzeug": {
|
||||
"hashes": [
|
||||
"sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7",
|
||||
"sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"
|
||||
],
|
||||
"version": "==0.16.0"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
||||
11
services/users/entrypoint-prod.sh
Executable file
11
services/users/entrypoint-prod.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Waiting for postgres..."
|
||||
|
||||
while ! nc -z users-db 5432; do
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
echo "PostgreSQL started"
|
||||
|
||||
gunicorn -b 0.0.0.0:5000 manage:app
|
||||
11
services/users/entrypoint.sh
Executable file
11
services/users/entrypoint.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Waiting for postgres..."
|
||||
|
||||
while ! nc -z users-db 5432; do
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
echo "PostgreSQL started"
|
||||
|
||||
python manage.py run -h 0.0.0.0
|
||||
66
services/users/manage.py
Normal file
66
services/users/manage.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import coverage
|
||||
from flask.cli import FlaskGroup
|
||||
|
||||
from project import create_app, db
|
||||
from project.api.models import User
|
||||
|
||||
COV = coverage.coverage(
|
||||
branch=True,
|
||||
include='project/*',
|
||||
omit=[
|
||||
'project/tests/*',
|
||||
'project/config.py',
|
||||
]
|
||||
)
|
||||
COV.start()
|
||||
|
||||
app = create_app()
|
||||
cli = FlaskGroup(create_app=create_app)
|
||||
|
||||
|
||||
@cli.command('recreate_db')
|
||||
def recreate_all():
|
||||
db.drop_all()
|
||||
db.create_all()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
@cli.command('seed_db')
|
||||
def seed_db():
|
||||
db.drop_all()
|
||||
db.create_all()
|
||||
db.session.commit()
|
||||
db.session.add(User(username='michael', email='hermanmu@gmail.com'))
|
||||
db.session.add(User(username='michaelherman', email='michael@herman.org'))
|
||||
db.session.commit()
|
||||
|
||||
|
||||
@cli.command()
|
||||
def test():
|
||||
tests = unittest.TestLoader().discover('project/tests', pattern='test*.py')
|
||||
result = unittest.TextTestRunner(verbosity=2).run(tests)
|
||||
if result.wasSuccessful():
|
||||
return 0
|
||||
sys.exit(result)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def cov():
|
||||
tests = unittest.TestLoader().discover('project/tests')
|
||||
result = unittest.TextTestRunner(verbosity=2).run(tests)
|
||||
if result.wasSuccessful():
|
||||
COV.stop()
|
||||
COV.save()
|
||||
print('Coverage Summary:')
|
||||
COV.report()
|
||||
COV.html_report()
|
||||
COV.erase()
|
||||
return 0
|
||||
sys.exit(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
23
services/users/project/__init__.py
Normal file
23
services/users/project/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import os
|
||||
|
||||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
def create_app(script_info=None):
|
||||
app = Flask(__name__)
|
||||
app_settings = os.getenv('APP_SETTINGS')
|
||||
app.config.from_object(app_settings)
|
||||
|
||||
db.init_app(app)
|
||||
|
||||
from project.api.users import users_blueprint
|
||||
app.register_blueprint(users_blueprint)
|
||||
|
||||
@app.shell_context_processor
|
||||
def ctx():
|
||||
return {'app': app, 'db': db}
|
||||
|
||||
return app
|
||||
0
services/users/project/api/__init__.py
Normal file
0
services/users/project/api/__init__.py
Normal file
22
services/users/project/api/models.py
Normal file
22
services/users/project/api/models.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from sqlalchemy import func
|
||||
|
||||
from project import db
|
||||
|
||||
|
||||
class User(db.Model):
|
||||
|
||||
__tablename__ = 'users'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
username = db.Column(db.String, nullable=False)
|
||||
email = db.Column(db.String, nullable=False)
|
||||
active = db.Column(db.Boolean, default=True, nullable=False)
|
||||
created_date = db.Column(db.DateTime, default=func.now(), nullable=False)
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'username': self.username,
|
||||
'email': self.email,
|
||||
'active': self.active,
|
||||
}
|
||||
49
services/users/project/api/templates/index.html
Normal file
49
services/users/project/api/templates/index.html
Normal file
@@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Flask on Docker</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<link href="//cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css"
|
||||
rel="stylesheet"
|
||||
>
|
||||
{% block css %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="column is-one-third">
|
||||
<br>
|
||||
<h1 class="title">All Users</h1>
|
||||
<hr><br>
|
||||
<form action="/" method="POST">
|
||||
<div class="field">
|
||||
<input
|
||||
name="username" class="input" type="text"
|
||||
placeholder="Enter a username" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<input
|
||||
name="email" class="input" type="email"
|
||||
placeholder="Enter an email address" required>
|
||||
</div>
|
||||
<input
|
||||
type="submit" class="button is-primary is-fullwidth" value="Submit">
|
||||
</form>
|
||||
<br>
|
||||
<hr>
|
||||
{% if users %}
|
||||
<ol>
|
||||
{% for user in users %}
|
||||
<li>{{user.username}}</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
{% else %}
|
||||
<p>No users!</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% block js %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
89
services/users/project/api/users.py
Normal file
89
services/users/project/api/users.py
Normal file
@@ -0,0 +1,89 @@
|
||||
from flask import Blueprint, request, render_template
|
||||
from flask_restful import Resource, Api
|
||||
from sqlalchemy import exc
|
||||
|
||||
from project.api.models import User
|
||||
from project import db
|
||||
|
||||
users_blueprint = Blueprint('users', __name__, template_folder='./templates')
|
||||
api = Api(users_blueprint)
|
||||
|
||||
|
||||
class UsersPing(Resource):
|
||||
def get(self):
|
||||
return {
|
||||
'status': 'success',
|
||||
'message': 'pong!'
|
||||
}
|
||||
|
||||
|
||||
class UsersList(Resource):
|
||||
def get(self):
|
||||
response_object = {'status': 'success',
|
||||
'data': {
|
||||
'users': [user.to_json()
|
||||
for user in User.query.all()]
|
||||
}}
|
||||
return response_object, 200
|
||||
|
||||
def post(self):
|
||||
post_data = request.get_json()
|
||||
response_object = {
|
||||
'status': 'fail',
|
||||
'message': 'Invalid payload.',
|
||||
}
|
||||
if not post_data:
|
||||
return response_object, 400
|
||||
username = post_data.get('username')
|
||||
email = post_data.get('email')
|
||||
try:
|
||||
user = User.query.filter_by(email=email).first()
|
||||
if not user:
|
||||
db.session.add(User(username=username, email=email))
|
||||
db.session.commit()
|
||||
response_object['status'] = 'success'
|
||||
response_object['message'] = f'{email} was added!'
|
||||
return response_object, 201
|
||||
else:
|
||||
response_object['message'] = 'Sorry. That email already exists.'
|
||||
return response_object, 400
|
||||
except exc.IntegrityError:
|
||||
db.session.rollback()
|
||||
return response_object, 400
|
||||
|
||||
|
||||
class Users(Resource):
|
||||
def get(self, user_id):
|
||||
response_object = {'status': 'fail',
|
||||
'message': 'User does not exist'}
|
||||
try:
|
||||
user = User.query.filter_by(id=int(user_id)).first()
|
||||
if not user:
|
||||
return response_object, 404
|
||||
else:
|
||||
response_object = {'status': 'success',
|
||||
'data': {
|
||||
'id': user.id,
|
||||
'username': user.username,
|
||||
'email': user.email,
|
||||
'active': user.active,
|
||||
}}
|
||||
return response_object, 200
|
||||
except ValueError:
|
||||
return response_object, 404
|
||||
|
||||
|
||||
@users_blueprint.route('/', methods=['GET', 'POST'])
|
||||
def index():
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
email = request.form['email']
|
||||
db.session.add(User(username=username, email=email))
|
||||
db.session.commit()
|
||||
users = User.query.all()
|
||||
return render_template('index.html', users=users)
|
||||
|
||||
|
||||
api.add_resource(UsersPing, '/users/ping')
|
||||
api.add_resource(UsersList, '/users')
|
||||
api.add_resource(Users, '/users/<user_id>')
|
||||
20
services/users/project/config.py
Normal file
20
services/users/project/config.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import os
|
||||
|
||||
|
||||
class BaseConfig:
|
||||
TESTING = False
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
SECRET_KEY = 'my_precious'
|
||||
|
||||
|
||||
class DevelopmentConfig(BaseConfig):
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
|
||||
|
||||
|
||||
class ProductionConfig(BaseConfig):
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
|
||||
|
||||
|
||||
class TestingConfig(BaseConfig):
|
||||
TESTING = True
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_TEST_URL')
|
||||
3
services/users/project/db/Dockerfile
Normal file
3
services/users/project/db/Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
||||
FROM postgres:11.2-alpine
|
||||
|
||||
ADD create.sql /docker-entrypoint-initdb.d
|
||||
3
services/users/project/db/create.sql
Normal file
3
services/users/project/db/create.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
CREATE DATABASE users_prod;
|
||||
CREATE DATABASE users_dev;
|
||||
CREATE DATABASE users_test;
|
||||
0
services/users/project/tests/__init__.py
Normal file
0
services/users/project/tests/__init__.py
Normal file
19
services/users/project/tests/base.py
Normal file
19
services/users/project/tests/base.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from flask_testing import TestCase
|
||||
|
||||
from project import create_app, db
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
class BaseTestCase(TestCase):
|
||||
def create_app(self):
|
||||
app.config.from_object('project.config.TestingConfig')
|
||||
return app
|
||||
|
||||
def setUp(self):
|
||||
db.create_all()
|
||||
db.session.commit()
|
||||
|
||||
def tearDown(self):
|
||||
db.session.remove()
|
||||
db.drop_all()
|
||||
52
services/users/project/tests/test_config.py
Normal file
52
services/users/project/tests/test_config.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from flask import current_app
|
||||
from flask_testing import TestCase
|
||||
|
||||
from project import create_app
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
class TestDevelopmentConfig(TestCase):
|
||||
def create_app(self):
|
||||
app.config.from_object('project.config.DevelopmentConfig')
|
||||
return app
|
||||
|
||||
def test_app_is_development(self):
|
||||
self.assertTrue(app.config['SECRET_KEY'] == 'my_precious')
|
||||
self.assertFalse(current_app is None)
|
||||
self.assertTrue(
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] ==
|
||||
os.environ.get('DATABASE_URL')
|
||||
)
|
||||
|
||||
|
||||
class TestTestingConfig(TestCase):
|
||||
def create_app(self):
|
||||
app.config.from_object('project.config.TestingConfig')
|
||||
return app
|
||||
|
||||
def test_app_is_testing(self):
|
||||
self.assertTrue(app.config['SECRET_KEY'] == 'my_precious')
|
||||
self.assertTrue(app.config['TESTING'])
|
||||
self.assertFalse(app.config['PRESERVE_CONTEXT_ON_EXCEPTION'])
|
||||
self.assertTrue(
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] ==
|
||||
os.environ.get('DATABASE_TEST_URL')
|
||||
)
|
||||
|
||||
|
||||
class TestProductionConfig(TestCase):
|
||||
def create_app(self):
|
||||
app.config.from_object('project.config.ProductionConfig')
|
||||
return app
|
||||
|
||||
def test_app_is_testing(self):
|
||||
self.assertTrue(app.config['SECRET_KEY'] == 'my_precious')
|
||||
self.assertFalse(app.config['TESTING'])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
148
services/users/project/tests/test_users.py
Normal file
148
services/users/project/tests/test_users.py
Normal file
@@ -0,0 +1,148 @@
|
||||
import json
|
||||
import unittest
|
||||
|
||||
from project import db
|
||||
from project.api.models import User
|
||||
from project.tests.base import BaseTestCase
|
||||
|
||||
|
||||
def add_user(username, email):
|
||||
user = User(username=username, email=email)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return user
|
||||
|
||||
|
||||
class TestUserService(BaseTestCase):
|
||||
|
||||
def test_all_users(self):
|
||||
add_user('michael', 'michael@mherman.org')
|
||||
add_user('fletcher', 'fletcher@notreal.com')
|
||||
with self.client:
|
||||
response = self.client.get(f'/users')
|
||||
data = json.loads(response.data.decode())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(data['data']['users']), 2)
|
||||
self.assertIn('michael', data['data']['users'][0]['username'])
|
||||
self.assertIn('michael@mherman.org', data['data']['users'][0]['email'])
|
||||
self.assertIn('fletcher', data['data']['users'][1]['username'])
|
||||
self.assertIn('fletcher@notreal.com', data['data']['users'][1]['email'])
|
||||
self.assertIn('success', data['status'])
|
||||
|
||||
def test_main_users(self):
|
||||
response = self.client.get('/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(b'All Users', response.data)
|
||||
self.assertIn(b'<p>No users!</p>', response.data)
|
||||
|
||||
def test_main_with_users(self):
|
||||
add_user('michael', 'michael@mherman.org')
|
||||
add_user('fletcher', 'fletcher@notreal.com')
|
||||
with self.client:
|
||||
response = self.client.get('/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(b'All Users', response.data)
|
||||
self.assertNotIn(b'<p>No users!</p>', response.data)
|
||||
self.assertIn(b'michael', response.data)
|
||||
self.assertIn(b'fletcher', response.data)
|
||||
|
||||
def test_main_add_user(self):
|
||||
with self.client:
|
||||
response = self.client.post('/',
|
||||
data={
|
||||
'username': 'michael',
|
||||
'email': 'michael@sonotreal.com',
|
||||
},
|
||||
follow_redirects=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(b'All Users', response.data)
|
||||
self.assertNotIn(b'<p>No users!</p>', response.data)
|
||||
self.assertIn(b'michael', response.data)
|
||||
|
||||
def test_users(self):
|
||||
response = self.client.get('/users/ping')
|
||||
data = json.loads(response.data.decode())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('pong!', data['message'])
|
||||
self.assertIn('success', data['status'])
|
||||
|
||||
def test_single_user(self):
|
||||
user = add_user('michael', 'michael@mherman.org')
|
||||
with self.client:
|
||||
response = self.client.get(f'/users/{user.id}')
|
||||
data = json.loads(response.data.decode())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('michael', data['data']['username'])
|
||||
self.assertIn('michael@mherman.org', data['data']['email'])
|
||||
self.assertIn('success', data['status'])
|
||||
|
||||
def test_single_user_no_id(self):
|
||||
with self.client:
|
||||
response = self.client.get(f'/users/blah')
|
||||
data = json.loads(response.data.decode())
|
||||
self.assertEqual(response.status_code, 404)
|
||||
self.assertIn('User does not exist', data['message'])
|
||||
self.assertIn('fail', data['status'])
|
||||
|
||||
def test_single_user_incorrect_id(self):
|
||||
with self.client:
|
||||
response = self.client.get(f'/users/999')
|
||||
data = json.loads(response.data.decode())
|
||||
self.assertEqual(response.status_code, 404)
|
||||
self.assertIn('User does not exist', data['message'])
|
||||
self.assertIn('fail', data['status'])
|
||||
|
||||
def test_add_user(self):
|
||||
with self.client:
|
||||
response = self.client.post('/users',
|
||||
data=json.dumps({
|
||||
'username': 'michael',
|
||||
'email': 'michael@mherman.org',
|
||||
}),
|
||||
content_type='application/json')
|
||||
data = json.loads(response.data.decode())
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertIn('michael@mherman.org was added!', data['message'])
|
||||
self.assertIn('success', data['status'])
|
||||
|
||||
def test_add_user_invalid_json(self):
|
||||
with self.client:
|
||||
response = self.client.post('/users',
|
||||
data=json.dumps({}),
|
||||
content_type='application/json')
|
||||
data = json.loads(response.data.decode())
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn('Invalid payload.', data['message'])
|
||||
self.assertIn('fail', data['status'])
|
||||
|
||||
def test_add_user_invalid_json_keys(self):
|
||||
with self.client:
|
||||
response = self.client.post('/users',
|
||||
data=json.dumps({'email': 'michael@mherman.org'}),
|
||||
content_type='application/json')
|
||||
data = json.loads(response.data.decode())
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn('Invalid payload.', data['message'])
|
||||
self.assertIn('fail', data['status'])
|
||||
|
||||
def test_add_user_duplicate_email(self):
|
||||
with self.client:
|
||||
self.client.post('/users',
|
||||
data=json.dumps({'username': 'michael',
|
||||
'email': 'michael@mherman.org',
|
||||
}),
|
||||
content_type='application/json')
|
||||
response = self.client.post('/users',
|
||||
data=json.dumps({
|
||||
'username': 'michael',
|
||||
'email': 'michael@mherman.org',
|
||||
}),
|
||||
content_type='application/json')
|
||||
data = json.loads(response.data.decode())
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn('Sorry. That email already exists.', data['message'])
|
||||
self.assertIn('fail', data['status'])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
21
services/users/requirements.txt
Normal file
21
services/users/requirements.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
-i https://pypi.org/simple
|
||||
aniso8601==8.0.0
|
||||
click==7.0
|
||||
coverage==4.5.4
|
||||
entrypoints==0.3
|
||||
flake8==3.7.9
|
||||
flask-restful==0.3.7
|
||||
flask-sqlalchemy==2.4.1
|
||||
flask-testing==0.7.1
|
||||
flask==1.1.1
|
||||
itsdangerous==1.1.0
|
||||
jinja2==2.10.3
|
||||
markupsafe==1.1.1
|
||||
mccabe==0.6.1
|
||||
psycopg2-binary==2.8.4
|
||||
pycodestyle==2.5.0
|
||||
pyflakes==2.1.1
|
||||
pytz==2019.3
|
||||
six==1.13.0
|
||||
sqlalchemy==1.3.11
|
||||
werkzeug==0.16.0
|
||||
2
services/users/setup.cfg
Normal file
2
services/users/setup.cfg
Normal file
@@ -0,0 +1,2 @@
|
||||
[flake8]
|
||||
max-line-length=99
|
||||
Reference in New Issue
Block a user