├── .gitignore ├── .travis.yml ├── README.md ├── docker-compose-ci.yml ├── docker-compose.yml ├── docker_build.sh ├── docker_deploy.sh ├── docker_deploy_prod.sh ├── docker_push.sh ├── e2e ├── all-users.test.js ├── exercises.test.js ├── login.test.js ├── message.test.js ├── register.test.js └── status.test.js ├── ecs ├── ecs_client_prod_taskdefinition.json ├── ecs_client_taskdefinition.json ├── ecs_swagger_prod_taskdefinition.json ├── ecs_swagger_taskdefinition.json ├── ecs_users_prod_taskdefinition.json └── ecs_users_taskdefinition.json ├── nginx ├── Dockerfile └── nginx.conf ├── package.json └── test.sh /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | env 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: '7' 3 | 4 | before_install: 5 | - stty cols 80 6 | 7 | dist: trusty 8 | sudo: required 9 | 10 | addons: 11 | apt: 12 | sources: 13 | - google-chrome 14 | packages: 15 | - google-chrome-stable 16 | 17 | services: 18 | - docker 19 | 20 | env: 21 | global: 22 | - DOCKER_COMPOSE_VERSION=1.11.2 23 | - COMMIT=${TRAVIS_COMMIT::8} 24 | - USERS=flask-microservices-users 25 | - USERS_REPO=https://github.com/realpython/$USERS.git 26 | - USERS_DB=flask-microservices-users_db 27 | - USERS_DB_REPO=https://github.com/realpython/$USERS.git#master:project/db 28 | - CLIENT=flask-microservices-client 29 | - CLIENT_REPO=https://github.com/realpython/$CLIENT.git 30 | - SWAGGER=flask-microservices-swagger 31 | - SWAGGER_REPO=https://github.com/realpython/$SWAGGER.git 32 | - EVAL=flask-microservices-eval 33 | - EVAL_REPO=https://github.com/realpython/$EVAL.git 34 | - NGINX=flask-microservices-nginx 35 | - NGINX_REPO=https://github.com/realpython/flask-microservices-main.git#master:nginx 36 | 37 | before_install: 38 | - sudo rm /usr/local/bin/docker-compose 39 | - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose 40 | - chmod +x docker-compose 41 | - sudo mv docker-compose /usr/local/bin 42 | 43 | before_script: 44 | - export TEST_URL=http://127.0.0.1 45 | - export REACT_APP_USERS_SERVICE_URL=http://127.0.0.1 46 | - export REACT_APP_EVAL_SERVICE_URL=http://127.0.0.1 47 | - export REACT_APP_API_GATEWAY_URL=https://c0rue3ifh4.execute-api.us-east-1.amazonaws.com/v1/execute 48 | - export SECRET_KEY=my_precious 49 | - export DISPLAY=:99.0 50 | - sh -e /etc/init.d/xvfb start 51 | - sleep 3 52 | - bash ./docker_build.sh 53 | 54 | script: 55 | - docker-compose -f docker-compose-ci.yml run users-service python manage.py test 56 | - docker-compose -f docker-compose-ci.yml run users-service python manage.py recreate_db 57 | - docker-compose -f docker-compose-ci.yml run eval-service python manage.py test 58 | - docker-compose -f docker-compose-ci.yml run eval-service python manage.py recreate_db 59 | - docker-compose -f docker-compose-ci.yml run eval-service python manage.py seed_db 60 | - testcafe chrome e2e 61 | 62 | after_script: 63 | - docker-compose down 64 | 65 | after_success: 66 | - bash ./docker_push.sh 67 | - bash ./docker_deploy.sh 68 | - bash ./docker_deploy_prod.sh 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask Microservices - Main Service 2 | 3 | [![Build Status](https://travis-ci.org/testdrivenio/flask-microservices-main.svg?branch=master)](https://travis-ci.org/testdrivenio/flask-microservices-main) 4 | 5 | ## Want to learn how to build this project? 6 | 7 | Check out [testdriven.io](http://testdriven.io/). 8 | -------------------------------------------------------------------------------- /docker-compose-ci.yml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | services: 4 | 5 | users-db: 6 | container_name: users-db 7 | build: 8 | context: https://github.com/realpython/flask-microservices-users.git#master:project/db 9 | ports: 10 | - 5435:5432 # expose ports - HOST:CONTAINER 11 | environment: 12 | - POSTGRES_USER=postgres 13 | - POSTGRES_PASSWORD=postgres 14 | healthcheck: 15 | test: exit 0 16 | 17 | users-service: 18 | container_name: users-service 19 | build: 20 | context: https://github.com/realpython/flask-microservices-users.git 21 | expose: 22 | - '5000' 23 | environment: 24 | - APP_SETTINGS=project.config.StagingConfig 25 | - DATABASE_URL=postgres://postgres:postgres@users-db:5432/users_staging 26 | - DATABASE_TEST_URL=postgres://postgres:postgres@users-db:5432/users_test 27 | - SECRET_KEY=${SECRET_KEY} 28 | depends_on: 29 | users-db: 30 | condition: service_healthy 31 | links: 32 | - users-db 33 | command: gunicorn -b 0.0.0.0:5000 manage:app 34 | 35 | nginx: 36 | container_name: nginx 37 | build: ./nginx/ 38 | restart: always 39 | ports: 40 | - 80:80 41 | depends_on: 42 | users-service: 43 | condition: service_started 44 | web-service: 45 | condition: service_started 46 | links: 47 | - users-service 48 | - web-service 49 | 50 | web-service: 51 | container_name: web-service 52 | build: 53 | context: https://github.com/realpython/flask-microservices-client.git 54 | args: 55 | - NODE_ENV=development 56 | - REACT_APP_USERS_SERVICE_URL=${REACT_APP_USERS_SERVICE_URL} 57 | - REACT_APP_EVAL_SERVICE_URL=${REACT_APP_EVAL_SERVICE_URL} 58 | - REACT_APP_API_GATEWAY_URL=${REACT_APP_API_GATEWAY_URL} 59 | ports: 60 | - '9000:9000' # expose ports - HOST:CONTAINER 61 | depends_on: 62 | users-service: 63 | condition: service_started 64 | links: 65 | - users-service 66 | 67 | swagger: 68 | container_name: swagger 69 | build: 70 | context: https://github.com/realpython/flask-microservices-swagger.git 71 | ports: 72 | - '8080:8080' # expose ports - HOST:CONTAINER 73 | environment: 74 | - API_URL=https://raw.githubusercontent.com/realpython/flask-microservices-swagger/master/swagger.json 75 | depends_on: 76 | users-service: 77 | condition: service_started 78 | 79 | eval-service: 80 | container_name: eval-service 81 | build: 82 | context: https://github.com/realpython/flask-microservices-eval.git 83 | ports: 84 | - 5002:5000 # expose ports - HOST:CONTAINER 85 | environment: 86 | - APP_SETTINGS=project.config.StagingConfig 87 | - DATABASE_URL=postgres://postgres:postgres@eval-db:5432/eval_staging 88 | - DATABASE_TEST_URL=postgres://postgres:postgres@eval-db:5432/eval_test 89 | - SECRET_KEY=${SECRET_KEY} 90 | depends_on: 91 | users-service: 92 | condition: service_started 93 | eval-db: 94 | condition: service_healthy 95 | links: 96 | - users-service 97 | - eval-db 98 | command: gunicorn -b 0.0.0.0:5000 manage:app 99 | 100 | eval-db: 101 | container_name: eval-db 102 | build: 103 | context: https://github.com/realpython/flask-microservices-eval.git#master:project/db 104 | ports: 105 | - 5436:5432 # expose ports - HOST:CONTAINER 106 | environment: 107 | - POSTGRES_USER=postgres 108 | - POSTGRES_PASSWORD=postgres 109 | healthcheck: 110 | test: exit 0 111 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | services: 4 | 5 | users-db: 6 | container_name: users-db 7 | build: 8 | context: ../flask-microservices-users/project/db 9 | ports: 10 | - 5435:5432 # expose ports - HOST:CONTAINER 11 | environment: 12 | - POSTGRES_USER=postgres 13 | - POSTGRES_PASSWORD=postgres 14 | healthcheck: 15 | test: exit 0 16 | 17 | users-service: 18 | container_name: users-service 19 | build: 20 | context: ../flask-microservices-users 21 | dockerfile: Dockerfile-local 22 | volumes: 23 | - '../flask-microservices-users:/usr/src/app' 24 | ports: 25 | - 5001:5000 # expose ports - HOST:CONTAINER 26 | environment: 27 | - APP_SETTINGS=project.config.DevelopmentConfig 28 | - DATABASE_URL=postgres://postgres:postgres@users-db:5432/users_dev 29 | - DATABASE_TEST_URL=postgres://postgres:postgres@users-db:5432/users_test 30 | - SECRET_KEY=my_precious 31 | depends_on: 32 | users-db: 33 | condition: service_healthy 34 | links: 35 | - users-db 36 | 37 | nginx: 38 | container_name: nginx 39 | build: ./nginx/ 40 | restart: always 41 | ports: 42 | - 80:80 43 | depends_on: 44 | users-service: 45 | condition: service_started 46 | web-service: 47 | condition: service_started 48 | links: 49 | - users-service 50 | - web-service 51 | 52 | web-service: 53 | container_name: web-service 54 | build: 55 | context: ../flask-microservices-client 56 | dockerfile: Dockerfile-local 57 | volumes: 58 | - '../flask-microservices-client:/usr/src/app' 59 | - '/usr/src/app/node_modules' 60 | ports: 61 | - '9000:9000' # expose ports - HOST:CONTAINER 62 | environment: 63 | - NODE_ENV=development 64 | - REACT_APP_USERS_SERVICE_URL=${REACT_APP_USERS_SERVICE_URL} 65 | - REACT_APP_EVAL_SERVICE_URL=${REACT_APP_EVAL_SERVICE_URL} 66 | - REACT_APP_API_GATEWAY_URL=${REACT_APP_API_GATEWAY_URL} 67 | depends_on: 68 | users-service: 69 | condition: service_started 70 | links: 71 | - users-service 72 | 73 | swagger: 74 | container_name: swagger 75 | build: 76 | context: ../flask-microservices-swagger 77 | ports: 78 | - '8080:8080' # expose ports - HOST:CONTAINER 79 | environment: 80 | - API_URL=https://raw.githubusercontent.com/realpython/flask-microservices-swagger/master/swagger.json 81 | depends_on: 82 | users-service: 83 | condition: service_started 84 | 85 | eval-service: 86 | container_name: eval-service 87 | build: 88 | context: ../flask-microservices-eval 89 | dockerfile: Dockerfile-local 90 | volumes: 91 | - '../flask-microservices-eval:/usr/src/app' 92 | ports: 93 | - 5002:5000 # expose ports - HOST:CONTAINER 94 | environment: 95 | - APP_SETTINGS=project.config.DevelopmentConfig 96 | - USERS_SERVICE_URL=http://users-service:5000 97 | - DATABASE_URL=postgres://postgres:postgres@eval-db:5432/eval_dev 98 | - DATABASE_TEST_URL=postgres://postgres:postgres@eval-db:5432/eval_test 99 | depends_on: 100 | users-service: 101 | condition: service_started 102 | eval-db: 103 | condition: service_healthy 104 | links: 105 | - users-service 106 | - eval-db 107 | 108 | eval-db: 109 | container_name: eval-db 110 | build: 111 | context: ../flask-microservices-eval/project/db 112 | ports: 113 | - 5436:5432 # expose ports - HOST:CONTAINER 114 | environment: 115 | - POSTGRES_USER=postgres 116 | - POSTGRES_PASSWORD=postgres 117 | healthcheck: 118 | test: exit 0 119 | -------------------------------------------------------------------------------- /docker_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$TRAVIS_BRANCH" == "development" ]; then 4 | docker login -e $DOCKER_EMAIL -u $DOCKER_ID -p $DOCKER_PASSWORD 5 | docker pull $DOCKER_ID/$USERS 6 | docker pull $DOCKER_ID/$USERS_DB 7 | docker pull $DOCKER_ID/$CLIENT 8 | docker pull $DOCKER_ID/$SWAGGER 9 | docker pull $DOCKER_ID/$NGINX 10 | fi 11 | 12 | docker-compose -f docker-compose-ci.yml up -d --build 13 | -------------------------------------------------------------------------------- /docker_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z "$TRAVIS_PULL_REQUEST" ] || [ "$TRAVIS_PULL_REQUEST" == "false" ] 4 | then 5 | 6 | if [ "$TRAVIS_BRANCH" == "staging" ] 7 | then 8 | 9 | JQ="jq --raw-output --exit-status" 10 | 11 | configure_aws_cli() { 12 | aws --version 13 | aws configure set default.region us-east-1 14 | aws configure set default.output json 15 | echo "AWS Configured!" 16 | } 17 | 18 | register_definition() { 19 | if revision=$(aws ecs register-task-definition --cli-input-json "$task_def" --family $family | $JQ '.taskDefinition.taskDefinitionArn'); then 20 | echo "Revision: $revision" 21 | else 22 | echo "Failed to register task definition" 23 | return 1 24 | fi 25 | } 26 | 27 | update_service() { 28 | if [[ $(aws ecs update-service --cluster $cluster --service $service --task-definition $revision | $JQ '.service.taskDefinition') != $revision ]]; then 29 | echo "Error updating service." 30 | return 1 31 | fi 32 | } 33 | 34 | deploy_cluster() { 35 | 36 | cluster="flask-microservices-staging-cluster" 37 | 38 | # users 39 | family="flask-microservices-users-td" 40 | service="flask-microservices-users" 41 | template="ecs_users_taskdefinition.json" 42 | task_template=$(cat "ecs/$template") 43 | task_def=$(printf "$task_template" $AWS_ACCOUNT_ID $AWS_ACCOUNT_ID) 44 | echo "$task_def" 45 | register_definition 46 | update_service 47 | 48 | # client 49 | family="flask-microservices-client-td" 50 | service="flask-microservices-client" 51 | template="ecs_client_taskdefinition.json" 52 | task_template=$(cat "ecs/$template") 53 | task_def=$(printf "$task_template" $AWS_ACCOUNT_ID) 54 | echo "$task_def" 55 | register_definition 56 | update_service 57 | 58 | # swagger 59 | family="flask-microservices-swagger-td" 60 | service="flask-microservices-swagger" 61 | template="ecs_swagger_taskdefinition.json" 62 | task_template=$(cat "ecs/$template") 63 | task_def=$(printf "$task_template" $AWS_ACCOUNT_ID) 64 | echo "$task_def" 65 | register_definition 66 | update_service 67 | 68 | } 69 | 70 | configure_aws_cli 71 | deploy_cluster 72 | 73 | fi 74 | 75 | fi 76 | -------------------------------------------------------------------------------- /docker_deploy_prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z "$TRAVIS_PULL_REQUEST" ] || [ "$TRAVIS_PULL_REQUEST" == "false" ] 4 | then 5 | 6 | if [ "$TRAVIS_BRANCH" == "production" ] 7 | then 8 | 9 | JQ="jq --raw-output --exit-status" 10 | 11 | configure_aws_cli() { 12 | aws --version 13 | aws configure set default.region us-east-1 14 | aws configure set default.output json 15 | echo "AWS Configured!" 16 | } 17 | 18 | register_definition() { 19 | if revision=$(aws ecs register-task-definition --cli-input-json "$task_def" --family $family | $JQ '.taskDefinition.taskDefinitionArn'); then 20 | echo "Revision: $revision" 21 | else 22 | echo "Failed to register task definition" 23 | return 1 24 | fi 25 | } 26 | 27 | update_service() { 28 | if [[ $(aws ecs update-service --cluster $cluster --service $service --task-definition $revision | $JQ '.service.taskDefinition') != $revision ]]; then 29 | echo "Error updating service." 30 | return 1 31 | fi 32 | } 33 | 34 | deploy_cluster() { 35 | 36 | cluster="flask-microservices-prod-cluster" 37 | 38 | # users 39 | family="flask-microservices-users-prod-td" 40 | service="flask-microservices-prod-users" 41 | template="ecs_users_prod_taskdefinition.json" 42 | task_template=$(cat "ecs/$template") 43 | task_def=$(printf "$task_template" $AWS_ACCOUNT_ID $AWS_RDS_URI $PRODUCTION_SECRET_KEY) 44 | echo "$task_def" 45 | register_definition 46 | update_service 47 | 48 | # client 49 | family="flask-microservices-client-prod-td" 50 | service="flask-microservices-prod-client" 51 | template="ecs_client_prod_taskdefinition.json" 52 | task_template=$(cat "ecs/$template") 53 | task_def=$(printf "$task_template" $AWS_ACCOUNT_ID) 54 | echo "$task_def" 55 | register_definition 56 | update_service 57 | 58 | # swagger 59 | family="flask-microservices-swagger-prod-td" 60 | service="flask-microservices-prod-swagger" 61 | template="ecs_swagger_prod_taskdefinition.json" 62 | task_template=$(cat "ecs/$template") 63 | task_def=$(printf "$task_template" $AWS_ACCOUNT_ID) 64 | echo "$task_def" 65 | register_definition 66 | update_service 67 | 68 | } 69 | 70 | configure_aws_cli 71 | deploy_cluster 72 | 73 | fi 74 | 75 | fi 76 | -------------------------------------------------------------------------------- /docker_push.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z "$TRAVIS_PULL_REQUEST" ] || [ "$TRAVIS_PULL_REQUEST" == "false" ] 4 | then 5 | 6 | if [ "$TRAVIS_BRANCH" == "development" ] 7 | then 8 | docker login -e $DOCKER_EMAIL -u $DOCKER_ID -p $DOCKER_PASSWORD 9 | export TAG=$TRAVIS_BRANCH 10 | export REPO=$DOCKER_ID 11 | fi 12 | 13 | if [ "$TRAVIS_BRANCH" == "staging" ] || \ 14 | [ "$TRAVIS_BRANCH" == "production" ] 15 | then 16 | curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "awscli-bundle.zip" 17 | unzip awscli-bundle.zip 18 | ./awscli-bundle/install -b ~/bin/aws 19 | export PATH=~/bin:$PATH 20 | # add AWS_ACCOUNT_ID, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY environment vars 21 | eval $(aws ecr get-login --region us-east-1) 22 | export TAG=$TRAVIS_BRANCH 23 | export REPO=$AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com 24 | fi 25 | 26 | if [ "$TRAVIS_BRANCH" == "staging" ] 27 | then 28 | export REACT_APP_USERS_SERVICE_URL="http://flask-microservices-staging-alb-1366920567.us-east-1.elb.amazonaws.com" 29 | export SECRET_KEY="my_precious" 30 | fi 31 | 32 | if [ "$TRAVIS_BRANCH" == "production" ] 33 | then 34 | export REACT_APP_USERS_SERVICE_URL="http://flask-microservices-prod-alb-814316018.us-east-1.elb.amazonaws.com" 35 | export DATABASE_URL="$AWS_RDS_URI" 36 | export SECRET_KEY="$PRODUCTION_SECRET_KEY" 37 | fi 38 | 39 | if [ "$TRAVIS_BRANCH" == "development" ] || \ 40 | [ "$TRAVIS_BRANCH" == "staging" ] || \ 41 | [ "$TRAVIS_BRANCH" == "production" ] 42 | then 43 | # users 44 | if [ "$TRAVIS_BRANCH" == "production" ] 45 | then 46 | docker build $USERS_REPO -t $USERS:$COMMIT -f Dockerfile-prod 47 | else 48 | docker build $USERS_REPO -t $USERS:$COMMIT 49 | fi 50 | docker tag $USERS:$COMMIT $REPO/$USERS:$TAG 51 | docker push $REPO/$USERS:$TAG 52 | # users db 53 | docker build $USERS_DB_REPO -t $USERS_DB:$COMMIT 54 | docker tag $USERS_DB:$COMMIT $REPO/$USERS_DB:$TAG 55 | docker push $REPO/$USERS_DB:$TAG 56 | # client 57 | docker build $CLIENT_REPO -t $CLIENT:$COMMIT 58 | docker tag $CLIENT:$COMMIT $REPO/$CLIENT:$TAG 59 | docker push $REPO/$CLIENT:$TAG 60 | # swagger 61 | docker build $SWAGGER_REPO -t $SWAGGER:$COMMIT 62 | docker tag $SWAGGER:$COMMIT $REPO/$SWAGGER:$TAG 63 | docker push $REPO/$SWAGGER:$TAG 64 | # nginx 65 | docker build $NGINX_REPO -t $NGINX:$COMMIT 66 | docker tag $NGINX:$COMMIT $REPO/$NGINX:$TAG 67 | docker push $REPO/$NGINX:$TAG 68 | fi 69 | 70 | fi 71 | -------------------------------------------------------------------------------- /e2e/all-users.test.js: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe'; 2 | 3 | const TEST_URL = process.env.TEST_URL; 4 | 5 | fixture('/all-users').page(`${TEST_URL}/all-users`); 6 | 7 | test(`should display the page correctly if a user is not logged in`, async (t) => { 8 | await t 9 | .navigateTo(`${TEST_URL}/all-users`) 10 | .expect(Selector('H1').withText('All Users').exists).ok() 11 | .expect(Selector('a').withText('User Status').exists).notOk() 12 | .expect(Selector('a').withText('Log Out').exists).notOk() 13 | .expect(Selector('a').withText('Register').exists).ok() 14 | .expect(Selector('a').withText('Log In').exists).ok() 15 | .expect(Selector('.alert').exists).notOk() 16 | }); 17 | -------------------------------------------------------------------------------- /e2e/exercises.test.js: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe'; 2 | 3 | const randomstring = require('randomstring'); 4 | 5 | const username = randomstring.generate(); 6 | const email = `${username}@test.com`; 7 | const password = 'greaterthanten'; 8 | 9 | const TEST_URL = process.env.TEST_URL; 10 | 11 | 12 | fixture('/').page(`${TEST_URL}/`); 13 | 14 | test(`should display the exercises correctly if a user is not logged in`, async (t) => { 15 | await t 16 | .navigateTo(`${TEST_URL}/`) 17 | .expect(Selector('H1').withText('Exercises').exists).ok() 18 | .expect(Selector('.alert-warning').withText('Please log in to submit an exercise.').exists).ok() 19 | .expect(Selector('button').withText('Run Code').exists).notOk() 20 | }); 21 | 22 | test(`should allow a user to submit an exercise if logged in`, async (t) => { 23 | await t 24 | .navigateTo(`${TEST_URL}/register`) 25 | .typeText('input[name="username"]', username) 26 | .typeText('input[name="email"]', email) 27 | .typeText('input[name="password"]', password) 28 | .click(Selector('input[type="submit"]')) 29 | await t 30 | .navigateTo(`${TEST_URL}/`) 31 | .expect(Selector('H1').withText('Exercises').exists).ok() 32 | .expect(Selector('.alert-warning').withText('Please log in to submit an exercise.').exists).notOk() 33 | .expect(Selector('button').withText('Run Code').exists).ok() 34 | .click(Selector('button').withText('Run Code')) 35 | .expect(Selector('h4').withText('Incorrect!').exists).ok() 36 | }); 37 | -------------------------------------------------------------------------------- /e2e/login.test.js: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe'; 2 | 3 | const randomstring = require('randomstring'); 4 | 5 | const username = randomstring.generate(); 6 | const email = `${username}@test.com`; 7 | const password = 'greaterthanten'; 8 | 9 | const TEST_URL = process.env.TEST_URL; 10 | 11 | fixture('/login').page(`${TEST_URL}/login`); 12 | 13 | test(`should display the sign in form`, async (t) => { 14 | await t 15 | .navigateTo(`${TEST_URL}/login`) 16 | .expect(Selector('H1').withText('Login').exists).ok() 17 | .expect(Selector('form').exists).ok() 18 | .expect(Selector('input[disabled]').exists).ok() 19 | .expect(Selector('.validation-list').exists).ok() 20 | .expect(Selector('.validation-list > .error').nth(0).withText( 21 | 'Email is required.').exists).ok() 22 | }); 23 | 24 | test(`should validate the password field`, async (t) => { 25 | await t 26 | .navigateTo(`${TEST_URL}/login`) 27 | .expect(Selector('H1').withText('Login').exists).ok() 28 | .expect(Selector('form').exists).ok() 29 | .expect(Selector('input[disabled]').exists).ok() 30 | .expect(Selector('.validation-list > .error').nth(1).withText( 31 | 'Password is required.').exists).ok() 32 | .typeText('input[name="password"]', 'something') 33 | .expect(Selector('.validation-list').exists).ok() 34 | .expect(Selector('.validation-list > .error').nth(1).withText( 35 | 'Password is required.').exists).notOk() 36 | .expect(Selector('.validation-list > .success').nth(0).withText( 37 | 'Password is required.').exists).ok() 38 | }); 39 | 40 | test(`should allow a user to sign in`, async (t) => { 41 | 42 | // register user 43 | await t 44 | .navigateTo(`${TEST_URL}/register`) 45 | .typeText('input[name="username"]', username) 46 | .typeText('input[name="email"]', email) 47 | .typeText('input[name="password"]', password) 48 | .click(Selector('input[type="submit"]')) 49 | 50 | // log a user out 51 | await t 52 | .click(Selector('a').withText('Log Out')) 53 | 54 | // log a user in 55 | await t 56 | .navigateTo(`${TEST_URL}/login`) 57 | .typeText('input[name="email"]', email) 58 | .typeText('input[name="password"]', password) 59 | .click(Selector('input[type="submit"]')) 60 | 61 | // assert user is redirected to '/' 62 | // assert '/all-users' is displayed properly 63 | const tableRow = Selector('td').withText(username).parent(); 64 | await t 65 | .expect(Selector('H1').withText('Exercises').exists).ok() 66 | .expect(Selector('.alert-success').withText('Welcome!').exists).ok() 67 | .navigateTo(`${TEST_URL}/all-users`) 68 | .expect(Selector('H1').withText('All Users').exists).ok() 69 | .expect(tableRow.child().withText(username).exists).ok() 70 | .expect(tableRow.child().withText(email).exists).ok() 71 | .expect(Selector('a').withText('User Status').exists).ok() 72 | .expect(Selector('a').withText('Log Out').exists).ok() 73 | .expect(Selector('a').withText('Register').exists).notOk() 74 | .expect(Selector('a').withText('Log In').exists).notOk() 75 | 76 | // log a user out 77 | await t 78 | .click(Selector('a').withText('Log Out')) 79 | 80 | // assert '/logout' is displayed properly 81 | await t 82 | .expect(Selector('p').withText('You are now logged out').exists).ok() 83 | .expect(Selector('a').withText('User Status').exists).notOk() 84 | .expect(Selector('a').withText('Log Out').exists).notOk() 85 | .expect(Selector('a').withText('Register').exists).ok() 86 | .expect(Selector('a').withText('Log In').exists).ok() 87 | 88 | }); 89 | 90 | test(`should throw an error if the credentials are incorrect`, async (t) => { 91 | 92 | // attempt to log in 93 | await t 94 | .navigateTo(`${TEST_URL}/login`) 95 | .typeText('input[name="email"]', 'incorrect@email.com') 96 | .typeText('input[name="password"]', password) 97 | .click(Selector('input[type="submit"]')) 98 | 99 | // assert user login failed 100 | await t 101 | .expect(Selector('H1').withText('Login').exists).ok() 102 | .expect(Selector('a').withText('User Status').exists).notOk() 103 | .expect(Selector('a').withText('Log Out').exists).notOk() 104 | .expect(Selector('a').withText('Register').exists).ok() 105 | .expect(Selector('a').withText('Log In').exists).ok() 106 | .expect(Selector('.alert-success').exists).notOk() 107 | .expect(Selector('.alert-danger').withText( 108 | 'User does not exist.').exists).ok() 109 | 110 | // attempt to log in 111 | await t 112 | .navigateTo(`${TEST_URL}/login`) 113 | .typeText('input[name="email"]', email) 114 | .typeText('input[name="password"]', 'incorrectpassword') 115 | .click(Selector('input[type="submit"]')) 116 | 117 | // assert user login failed 118 | await t 119 | .expect(Selector('H1').withText('Login').exists).ok() 120 | .expect(Selector('a').withText('User Status').exists).notOk() 121 | .expect(Selector('a').withText('Log Out').exists).notOk() 122 | .expect(Selector('a').withText('Register').exists).ok() 123 | .expect(Selector('a').withText('Log In').exists).ok() 124 | .expect(Selector('.alert-success').exists).notOk() 125 | .expect(Selector('.alert-danger').withText( 126 | 'User does not exist.').exists).ok() 127 | 128 | }); 129 | -------------------------------------------------------------------------------- /e2e/message.test.js: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe'; 2 | 3 | const randomstring = require('randomstring'); 4 | 5 | const username = randomstring.generate(); 6 | const email = `${username}@test.com`; 7 | const password = 'greaterthanten'; 8 | 9 | const TEST_URL = process.env.TEST_URL; 10 | 11 | fixture('/register').page(`${TEST_URL}/register`); 12 | 13 | test(`should display flash messages correctly`, async (t) => { 14 | 15 | // register user 16 | await t 17 | .navigateTo(`${TEST_URL}/register`) 18 | .typeText('input[name="username"]', username) 19 | .typeText('input[name="email"]', email) 20 | .typeText('input[name="password"]', password) 21 | .click(Selector('input[type="submit"]')) 22 | 23 | // assert flash messages are removed when user clicks the 'x' 24 | await t 25 | .expect(Selector('.alert-success').withText('Welcome!').exists).ok() 26 | .click(Selector('.alert > button')) 27 | .expect(Selector('.alert-success').withText('Welcome!').exists).notOk() 28 | 29 | // log a user out 30 | await t 31 | .click(Selector('a').withText('Log Out')) 32 | 33 | // attempt to log in 34 | await t 35 | .navigateTo(`${TEST_URL}/login`) 36 | .typeText('input[name="email"]', 'incorrect@email.com') 37 | .typeText('input[name="password"]', password) 38 | .click(Selector('input[type="submit"]')) 39 | 40 | // assert correct message is flashed 41 | await t 42 | .expect(Selector('.alert-success').exists).notOk() 43 | .expect(Selector('.alert-danger').withText( 44 | 'User does not exist.').exists).ok() 45 | 46 | // log a user in 47 | await t 48 | .navigateTo(`${TEST_URL}/login`) 49 | .typeText('input[name="email"]', email) 50 | .typeText('input[name="password"]', password) 51 | .click(Selector('input[type="submit"]')) 52 | 53 | // assert flash message is removed when a new message is flashed 54 | await t 55 | .expect(Selector('.alert-success').withText('Welcome!').exists).ok() 56 | .expect(Selector('.alert-danger').withText( 57 | 'User does not exist.').exists).notOk() 58 | 59 | // log a user out 60 | await t 61 | .click(Selector('a').withText('Log Out')) 62 | 63 | // log a user in 64 | await t 65 | .navigateTo(`${TEST_URL}/login`) 66 | .typeText('input[name="email"]', email) 67 | .typeText('input[name="password"]', password) 68 | .click(Selector('input[type="submit"]')) 69 | 70 | // assert flash message is removed after three seconds 71 | await t 72 | .expect(Selector('.alert-success').withText('Welcome!').exists).ok() 73 | .wait(4000) 74 | .expect(Selector('.alert-success').withText('Welcome!').exists).notOk() 75 | 76 | }); 77 | -------------------------------------------------------------------------------- /e2e/register.test.js: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe'; 2 | 3 | const randomstring = require('randomstring'); 4 | 5 | const username = randomstring.generate(); 6 | const email = `${username}@test.com`; 7 | const password = 'greaterthanten'; 8 | 9 | const TEST_URL = process.env.TEST_URL; 10 | 11 | 12 | fixture('/register').page(`${TEST_URL}/register`); 13 | 14 | test(`should display the registration form`, async (t) => { 15 | await t 16 | .navigateTo(`${TEST_URL}/register`) 17 | .expect(Selector('H1').withText('Register').exists).ok() 18 | .expect(Selector('form').exists).ok() 19 | .expect(Selector('input[disabled]').exists).ok() 20 | .expect(Selector('.validation-list').exists).ok() 21 | .expect(Selector('.validation-list > .error').nth(0).withText( 22 | 'Username must be greater than 5 characters.').exists).ok() 23 | }); 24 | 25 | test(`should allow a user to register`, async (t) => { 26 | 27 | // register user 28 | await t 29 | .navigateTo(`${TEST_URL}/register`) 30 | .typeText('input[name="username"]', username) 31 | .typeText('input[name="email"]', email) 32 | .typeText('input[name="password"]', password) 33 | .click(Selector('input[type="submit"]')) 34 | 35 | // assert user is redirected to '/' 36 | // assert '/all-users' is displayed properly 37 | const tableRow = Selector('td').withText(username).parent(); 38 | await t 39 | .expect(Selector('H1').withText('Exercises').exists).ok() 40 | .navigateTo(`${TEST_URL}/all-users`) 41 | .expect(Selector('H1').withText('All Users').exists).ok() 42 | .expect(tableRow.child().withText(username).exists).ok() 43 | .expect(tableRow.child().withText(email).exists).ok() 44 | .expect(Selector('a').withText('User Status').exists).ok() 45 | .expect(Selector('a').withText('Log Out').exists).ok() 46 | .expect(Selector('a').withText('Register').exists).notOk() 47 | .expect(Selector('a').withText('Log In').exists).notOk() 48 | 49 | }); 50 | 51 | test(`should throw an error if the username is taken`, async (t) => { 52 | 53 | // register user with duplicate user name 54 | await t 55 | .navigateTo(`${TEST_URL}/register`) 56 | .typeText('input[name="username"]', username) 57 | .typeText('input[name="email"]', `${email}unique`) 58 | .typeText('input[name="password"]', password) 59 | .click(Selector('input[type="submit"]')) 60 | 61 | // assert user registration failed 62 | await t 63 | .expect(Selector('H1').withText('Register').exists).ok() 64 | .expect(Selector('a').withText('User Status').exists).notOk() 65 | .expect(Selector('a').withText('Log Out').exists).notOk() 66 | .expect(Selector('a').withText('Register').exists).ok() 67 | .expect(Selector('a').withText('Log In').exists).ok() 68 | .expect(Selector('.alert-success').exists).notOk() 69 | .expect(Selector('.alert-danger').withText( 70 | 'That user already exists.').exists).ok() 71 | 72 | }); 73 | 74 | test(`should throw an error if the email is taken`, async (t) => { 75 | 76 | // register user with duplicate email 77 | await t 78 | .navigateTo(`${TEST_URL}/register`) 79 | .typeText('input[name="username"]', `${username}unique`) 80 | .typeText('input[name="email"]', email) 81 | .typeText('input[name="password"]', password) 82 | .click(Selector('input[type="submit"]')) 83 | 84 | // assert user registration failed 85 | await t 86 | .expect(Selector('H1').withText('Register').exists).ok() 87 | .expect(Selector('a').withText('User Status').exists).notOk() 88 | .expect(Selector('a').withText('Log Out').exists).notOk() 89 | .expect(Selector('a').withText('Register').exists).ok() 90 | .expect(Selector('a').withText('Log In').exists).ok() 91 | .expect(Selector('.alert-success').exists).notOk() 92 | .expect(Selector('.alert-danger').withText( 93 | 'That user already exists.').exists).ok() 94 | 95 | }); 96 | -------------------------------------------------------------------------------- /e2e/status.test.js: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe'; 2 | 3 | const randomstring = require('randomstring'); 4 | 5 | const username = randomstring.generate(); 6 | const email = `${username}@test.com`; 7 | const password = 'greaterthanten'; 8 | 9 | const TEST_URL = process.env.TEST_URL; 10 | 11 | fixture('/status').page(`${TEST_URL}/status`); 12 | 13 | test(`should display the page if user is not logged in`, async (t) => { 14 | await t 15 | .navigateTo(`${TEST_URL}/status`) 16 | .expect(Selector('p').withText( 17 | 'You must be logged in to view this.').exists).ok() 18 | .expect(Selector('a').withText('User Status').exists).notOk() 19 | .expect(Selector('a').withText('Log Out').exists).notOk() 20 | .expect(Selector('a').withText('Register').exists).ok() 21 | .expect(Selector('a').withText('Log In').exists).ok() 22 | }); 23 | 24 | test(`should display user info if user is logged in`, async (t) => { 25 | 26 | // register user 27 | await t 28 | .navigateTo(`${TEST_URL}/register`) 29 | .typeText('input[name="username"]', username) 30 | .typeText('input[name="email"]', email) 31 | .typeText('input[name="password"]', password) 32 | .click(Selector('input[type="submit"]')) 33 | 34 | // assert '/status' is displayed properly 35 | await t 36 | .navigateTo(`${TEST_URL}/status`) 37 | .expect(Selector('li > strong').withText('User ID:').exists).ok() 38 | .expect(Selector('li > strong').withText('Email:').exists).ok() 39 | .expect(Selector('li').withText(email).exists).ok() 40 | .expect(Selector('li > strong').withText('Username:').exists).ok() 41 | .expect(Selector('li').withText(username).exists).ok() 42 | .expect(Selector('a').withText('User Status').exists).ok() 43 | .expect(Selector('a').withText('Log Out').exists).ok() 44 | .expect(Selector('a').withText('Register').exists).notOk() 45 | .expect(Selector('a').withText('Log In').exists).notOk() 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /ecs/ecs_client_prod_taskdefinition.json: -------------------------------------------------------------------------------- 1 | { 2 | "containerDefinitions": [ 3 | { 4 | "name": "client-prod", 5 | "image": "%s.dkr.ecr.us-east-1.amazonaws.com\/flask-microservices-client:production", 6 | "essential": true, 7 | "memoryReservation": 300, 8 | "cpu": 300, 9 | "portMappings": [ 10 | { 11 | "containerPort": 9000, 12 | "hostPort": 0, 13 | "protocol": "tcp" 14 | } 15 | ], 16 | "environment": [ 17 | { 18 | "name": "NODE_ENV", 19 | "value": "production" 20 | }, 21 | { 22 | "name": "REACT_APP_USERS_SERVICE_URL", 23 | "value": "http://flask-microservices-prod-alb-814316018.us-east-1.elb.amazonaws.com" 24 | } 25 | ], 26 | "logConfiguration": { 27 | "logDriver": "awslogs", 28 | "options": { 29 | "awslogs-group": "flask-microservices-production", 30 | "awslogs-region": "us-east-1" 31 | } 32 | } 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /ecs/ecs_client_taskdefinition.json: -------------------------------------------------------------------------------- 1 | { 2 | "containerDefinitions": [ 3 | { 4 | "name": "client", 5 | "image": "%s.dkr.ecr.us-east-1.amazonaws.com\/flask-microservices-client:staging", 6 | "essential": true, 7 | "memoryReservation": 300, 8 | "cpu": 300, 9 | "portMappings": [ 10 | { 11 | "containerPort": 9000, 12 | "hostPort": 0, 13 | "protocol": "tcp" 14 | } 15 | ], 16 | "environment": [ 17 | { 18 | "name": "NODE_ENV", 19 | "value": "staging" 20 | }, 21 | { 22 | "name": "REACT_APP_USERS_SERVICE_URL", 23 | "value": "http://flask-microservices-staging-alb-1366920567.us-east-1.elb.amazonaws.com" 24 | } 25 | ], 26 | "logConfiguration": { 27 | "logDriver": "awslogs", 28 | "options": { 29 | "awslogs-group": "flask-microservices-staging", 30 | "awslogs-region": "us-east-1" 31 | } 32 | } 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /ecs/ecs_swagger_prod_taskdefinition.json: -------------------------------------------------------------------------------- 1 | { 2 | "containerDefinitions": [ 3 | { 4 | "name": "swagger-prod", 5 | "image": "%s.dkr.ecr.us-east-1.amazonaws.com\/flask-microservices-swagger:production", 6 | "essential": true, 7 | "memoryReservation": 300, 8 | "cpu": 300, 9 | "portMappings": [ 10 | { 11 | "containerPort": 8080, 12 | "hostPort": 0, 13 | "protocol": "tcp" 14 | } 15 | ], 16 | "environment": [ 17 | { 18 | "name": "API_URL", 19 | "value": "https://raw.githubusercontent.com/realpython/flask-microservices-swagger/master/swagger-prod.json" 20 | } 21 | ], 22 | "logConfiguration": { 23 | "logDriver": "awslogs", 24 | "options": { 25 | "awslogs-group": "flask-microservices-production", 26 | "awslogs-region": "us-east-1" 27 | } 28 | } 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /ecs/ecs_swagger_taskdefinition.json: -------------------------------------------------------------------------------- 1 | { 2 | "containerDefinitions": [ 3 | { 4 | "name": "swagger", 5 | "image": "%s.dkr.ecr.us-east-1.amazonaws.com\/flask-microservices-swagger:staging", 6 | "essential": true, 7 | "memoryReservation": 300, 8 | "cpu": 300, 9 | "portMappings": [ 10 | { 11 | "containerPort": 8080, 12 | "hostPort": 0, 13 | "protocol": "tcp" 14 | } 15 | ], 16 | "environment": [ 17 | { 18 | "name": "API_URL", 19 | "value": "https://raw.githubusercontent.com/realpython/flask-microservices-swagger/master/swagger.json" 20 | } 21 | ], 22 | "logConfiguration": { 23 | "logDriver": "awslogs", 24 | "options": { 25 | "awslogs-group": "flask-microservices-staging", 26 | "awslogs-region": "us-east-1" 27 | } 28 | } 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /ecs/ecs_users_prod_taskdefinition.json: -------------------------------------------------------------------------------- 1 | { 2 | "containerDefinitions": [ 3 | { 4 | "name": "users-service-prod", 5 | "image": "%s.dkr.ecr.us-east-1.amazonaws.com\/flask-microservices-users:production", 6 | "essential": true, 7 | "memoryReservation": 300, 8 | "cpu": 300, 9 | "portMappings": [ 10 | { 11 | "containerPort": 5000, 12 | "hostPort": 0, 13 | "protocol": "tcp" 14 | } 15 | ], 16 | "environment": [ 17 | { 18 | "name": "APP_SETTINGS", 19 | "value": "project.config.ProductionConfig" 20 | }, 21 | { 22 | "name": "DATABASE_URL", 23 | "value": "%s" 24 | }, 25 | { 26 | "name": "SECRET_KEY", 27 | "value": "%s" 28 | } 29 | ], 30 | "logConfiguration": { 31 | "logDriver": "awslogs", 32 | "options": { 33 | "awslogs-group": "flask-microservices-production", 34 | "awslogs-region": "us-east-1" 35 | } 36 | } 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /ecs/ecs_users_taskdefinition.json: -------------------------------------------------------------------------------- 1 | { 2 | "containerDefinitions": [ 3 | { 4 | "name": "users-service", 5 | "image": "%s.dkr.ecr.us-east-1.amazonaws.com\/flask-microservices-users:staging", 6 | "essential": true, 7 | "memoryReservation": 300, 8 | "cpu": 300, 9 | "portMappings": [ 10 | { 11 | "containerPort": 5000, 12 | "hostPort": 0, 13 | "protocol": "tcp" 14 | } 15 | ], 16 | "environment": [ 17 | { 18 | "name": "APP_SETTINGS", 19 | "value": "project.config.StagingConfig" 20 | }, 21 | { 22 | "name": "DATABASE_TEST_URL", 23 | "value": "postgres://postgres:postgres@users-db:5432/users_test" 24 | }, 25 | { 26 | "name": "DATABASE_URL", 27 | "value": "postgres://postgres:postgres@users-db:5432/users_staging" 28 | }, 29 | { 30 | "name": "SECRET_KEY", 31 | "value": "my_precious" 32 | } 33 | ], 34 | "links": [ 35 | "users-db" 36 | ], 37 | "logConfiguration": { 38 | "logDriver": "awslogs", 39 | "options": { 40 | "awslogs-group": "flask-microservices-staging", 41 | "awslogs-region": "us-east-1" 42 | } 43 | } 44 | }, 45 | { 46 | "name": "users-db", 47 | "image": "%s.dkr.ecr.us-east-1.amazonaws.com\/flask-microservices-users_db:staging", 48 | "essential": true, 49 | "memoryReservation": 300, 50 | "cpu": 300, 51 | "portMappings": [ 52 | { 53 | "containerPort": 5432 54 | } 55 | ], 56 | "environment": [ 57 | { 58 | "name": "POSTGRES_PASSWORD", 59 | "value": "postgres" 60 | }, 61 | { 62 | "name": "POSTGRES_USER", 63 | "value": "postgres" 64 | } 65 | ], 66 | "logConfiguration": { 67 | "logDriver": "awslogs", 68 | "options": { 69 | "awslogs-group": "flask-microservices-staging", 70 | "awslogs-region": "us-east-1" 71 | } 72 | } 73 | } 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.13.0 2 | 3 | RUN rm /etc/nginx/conf.d/default.conf 4 | ADD /nginx.conf /etc/nginx/conf.d 5 | -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | 3 | listen 80; 4 | 5 | location / { 6 | proxy_pass http://web-service:9000; 7 | proxy_http_version 1.1; 8 | proxy_set_header Upgrade $http_upgrade; 9 | proxy_set_header Connection "upgrade"; 10 | proxy_set_header Host $host; 11 | proxy_set_header X-Real-IP $remote_addr; 12 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 13 | } 14 | 15 | location /users { 16 | proxy_pass http://users-service:5000; 17 | proxy_http_version 1.1; 18 | proxy_set_header Upgrade $http_upgrade; 19 | proxy_set_header Connection "upgrade"; 20 | proxy_set_header Host $host; 21 | proxy_set_header X-Real-IP $remote_addr; 22 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 23 | } 24 | 25 | location /ping { 26 | proxy_pass http://users-service:5000; 27 | proxy_http_version 1.1; 28 | proxy_set_header Upgrade $http_upgrade; 29 | proxy_set_header Connection "upgrade"; 30 | proxy_set_header Host $host; 31 | proxy_set_header X-Real-IP $remote_addr; 32 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 33 | } 34 | 35 | location /auth { 36 | proxy_pass http://users-service:5000; 37 | proxy_http_version 1.1; 38 | proxy_set_header Upgrade $http_upgrade; 39 | proxy_set_header Connection "upgrade"; 40 | proxy_set_header Host $host; 41 | proxy_set_header X-Real-IP $remote_addr; 42 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 43 | } 44 | 45 | location /scores { 46 | proxy_pass http://eval-service:5000; 47 | proxy_http_version 1.1; 48 | proxy_set_header Upgrade $http_upgrade; 49 | proxy_set_header Connection "upgrade"; 50 | proxy_set_header Host $host; 51 | proxy_set_header X-Real-IP $remote_addr; 52 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 53 | } 54 | 55 | location /exercises { 56 | proxy_pass http://eval-service:5000; 57 | proxy_http_version 1.1; 58 | proxy_set_header Upgrade $http_upgrade; 59 | proxy_set_header Connection "upgrade"; 60 | proxy_set_header Host $host; 61 | proxy_set_header X-Real-IP $remote_addr; 62 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flask-microservices-main", 3 | "dependencies": { 4 | "randomstring": "^1.1.5", 5 | "testcafe": "^0.16.1" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | fails='' 4 | 5 | inspect() { 6 | if [ $1 -ne 0 ]; then 7 | fails="${fails} $2" 8 | fi 9 | } 10 | 11 | docker-compose run users-service python manage.py test 12 | inspect $? users-service 13 | 14 | docker-compose run eval-service python manage.py test 15 | inspect $? eval-service 16 | 17 | testcafe chrome e2e 18 | inspect $? e2e 19 | 20 | if [ -n "${fails}" ]; 21 | then 22 | echo "Tests failed: ${fails}" 23 | exit 1 24 | else 25 | echo "Tests passed!" 26 | exit 0 27 | fi 28 | --------------------------------------------------------------------------------