From 073523a576ecbc3c5f424371301ca6db2a915fa8 Mon Sep 17 00:00:00 2001 From: Vector Nguyen Date: Sat, 22 Jul 2023 20:18:15 +0700 Subject: [PATCH 01/16] UPDATE Postman --- ...api-rest-template.postman_collection.json} | 0 ...api-rest-template.postman_environment.json | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+) rename tests/postman/{Template REST API.postman_collection.json => flask-api-rest-template.postman_collection.json} (100%) create mode 100644 tests/postman/flask-api-rest-template.postman_environment.json diff --git a/tests/postman/Template REST API.postman_collection.json b/tests/postman/flask-api-rest-template.postman_collection.json similarity index 100% rename from tests/postman/Template REST API.postman_collection.json rename to tests/postman/flask-api-rest-template.postman_collection.json diff --git a/tests/postman/flask-api-rest-template.postman_environment.json b/tests/postman/flask-api-rest-template.postman_environment.json new file mode 100644 index 0000000..aead2c3 --- /dev/null +++ b/tests/postman/flask-api-rest-template.postman_environment.json @@ -0,0 +1,27 @@ +{ + "id": "25c79762-c6a7-41e5-997c-ac2c02ed715a", + "name": "flask-api-rest-template", + "values": [ + { + "key": "HOST", + "value": "http://127.0.0.1:5000", + "type": "default", + "enabled": true + }, + { + "key": "TOKEN", + "value": "", + "type": "default", + "enabled": true + }, + { + "key": "REFRESHTOKEN", + "value": "", + "type": "default", + "enabled": true + } + ], + "_postman_variable_scope": "environment", + "_postman_exported_at": "2023-07-22T13:17:15.403Z", + "_postman_exported_using": "Postman/10.16.0" +} \ No newline at end of file From 3d0c179b55f49720836987fc44cc82e060cdc5a7 Mon Sep 17 00:00:00 2001 From: Vector Nguyen Date: Sun, 23 Jul 2023 13:40:24 +0700 Subject: [PATCH 02/16] UPDATE principal --- app/__init__.py | 6 +++-- app/controllers/permission_controller.py | 9 +++++++ app/controllers/role_controller.py | 10 ++++++++ app/controllers/user_controller.py | 21 ++++++++-------- app/extention.py | 2 ++ app/models/permission_model.py | 2 +- app/models/user_model.py | 2 +- app/services/permission_service.py | 8 +++--- app/services/user_service.py | 5 ++++ app/utils/__init__.py | 0 app/utils/auth.py | 3 +-- app/utils/principal.py | 31 +++++++++++------------- 12 files changed, 61 insertions(+), 38 deletions(-) create mode 100644 app/utils/__init__.py diff --git a/app/__init__.py b/app/__init__.py index d04838d..6aaa34a 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,6 +1,8 @@ import os from flask import Flask -from app.extention import migrate, jwt, cors +from app.extention import migrate, cors +from app.utils.auth import jwt +from app.utils.principal import principal from app.utils.logging import configure_logging from app.db import db from app.blueprint import register_routing @@ -15,6 +17,7 @@ def create_app(settings_module): migrate.init_app(app, db) jwt.init_app(app) cors.init_app(app, supports_credentials='true' ,resources={r"*": { "origins": "*" }}) + principal.init_app(app) manage.init_app(app) # Logging configuration @@ -25,6 +28,5 @@ def create_app(settings_module): return app - settings_module = os.getenv('APP_SETTINGS_MODULE') app = create_app(settings_module) \ No newline at end of file diff --git a/app/controllers/permission_controller.py b/app/controllers/permission_controller.py index 103bc4f..a213d1e 100644 --- a/app/controllers/permission_controller.py +++ b/app/controllers/permission_controller.py @@ -5,17 +5,23 @@ from flask_jwt_extended import jwt_required from app.schemas.user_schema import PermissionSchema, UpdatePermissionRoleSchema +# Define permissions +read_permission = Permission(RoleNeed('read')) +write_permission = Permission(RoleNeed('write')) + blp = Blueprint("Permission", __name__, description="Permission API") @blp.route("/permission") class PermissionList(MethodView): @jwt_required() + @read_permission.require(http_exception=403) @blp.response(200, PermissionSchema(many=True)) def get(self): result = permission_service.get_all_permission() return result @jwt_required() + @write_permission.require(http_exception=403) @blp.arguments(PermissionSchema) def post(self, qa_history_data): result = permission_service.post_permission(qa_history_data) @@ -24,12 +30,14 @@ def post(self, qa_history_data): @blp.route("/permission/") class Permission(MethodView): @jwt_required() + @read_permission.require(http_exception=403) @blp.response(200, PermissionSchema) def get(self, permission_id): result = permission_service.get_permission(permission_id) return result @jwt_required() + @write_permission.require(http_exception=403) @blp.arguments(PermissionSchema) def put(self, permission_data, permission_id): result = permission_service.update_permission(permission_data, permission_id) @@ -38,6 +46,7 @@ def put(self, permission_data, permission_id): @blp.route("/permission-role-update") class PermissionRole(MethodView): @jwt_required() + @write_permission.require(http_exception=403) @blp.arguments(UpdatePermissionRoleSchema) def put(self, permission_data): result = role_permission_service.update_roles_to_permission(permission_data) diff --git a/app/controllers/role_controller.py b/app/controllers/role_controller.py index c314bd3..bd28ca1 100644 --- a/app/controllers/role_controller.py +++ b/app/controllers/role_controller.py @@ -5,17 +5,24 @@ from flask_jwt_extended import jwt_required from app.schemas.user_schema import RoleSchema, UpdateRolePermissionSchema +# Define permissions +read_permission = Permission(RoleNeed('read')) +write_permission = Permission(RoleNeed('write')) +delete_permission = Permission(RoleNeed('delete')) + blp = Blueprint("Role", __name__, description="Role API") @blp.route("/role") class RoleList(MethodView): @jwt_required() + @read_permission.require(http_exception=403) @blp.response(200, RoleSchema(many=True)) def get(self): result = role_service.get_all_role() return result @jwt_required() + @write_permission.require(http_exception=403) @blp.arguments(UpdateRolePermissionSchema) def post(self, qa_history_data): result = role_service.post_role(qa_history_data) @@ -24,18 +31,21 @@ def post(self, qa_history_data): @blp.route("/role/") class Role(MethodView): @jwt_required() + @read_permission.require(http_exception=403) @blp.response(200, RoleSchema) def get(self, role_id): result = role_service.get_role(role_id) return result @jwt_required() + @write_permission.require(http_exception=403) @blp.arguments(UpdateRolePermissionSchema) def put(self, role_data, role_id): result = role_service.update_role(role_data, role_id) return result @jwt_required() + @delete_permission.require(http_exception=403) def delete(self, role_id): result = role_service.delete_role(role_id) return result \ No newline at end of file diff --git a/app/controllers/user_controller.py b/app/controllers/user_controller.py index aa1c99f..b88f80b 100644 --- a/app/controllers/user_controller.py +++ b/app/controllers/user_controller.py @@ -1,19 +1,21 @@ from app.services import user_service -from flask_jwt_extended import jwt_required, get_jwt, get_jwt_identity, create_access_token +from flask_jwt_extended import jwt_required, get_jwt from flask.views import MethodView -from flask_smorest import Blueprint, abort +from flask_smorest import Blueprint from flask_principal import Permission, RoleNeed from app.schemas.user_schema import * -# Define some permissions -# admin_permission = Permission(RoleNeed('user_management')) +# Define permissions +read_permission = Permission(RoleNeed('read')) +write_permission = Permission(RoleNeed('write')) +delete_permission = Permission(RoleNeed('delete')) blp = Blueprint("User", __name__, description="User API") @blp.route("/user") class UserList(MethodView): @jwt_required() - # @admin_permission.require(http_exception=403) + @read_permission.require(http_exception=403) @blp.response(200, UserSchema(many=True)) def get(self): result = user_service.get_all_user() @@ -22,26 +24,23 @@ def get(self): @blp.route("/user/") class User(MethodView): @jwt_required() + @read_permission.require(http_exception=403) @blp.response(200, UserSchema) def get(self, user_id): result = user_service.get_user(user_id) return result @jwt_required() + @write_permission.require(http_exception=403) @blp.arguments(UserUpdateSchema) def put(self, user_data, user_id): result = user_service.update_user(user_data, user_id) return result - - @jwt_required() - def delete(self, user_id): - result = user_service.delete_user(user_id) - return result @blp.route("/block-user/") class BlockUser(MethodView): @jwt_required() - # @admin_permission.require(http_exception=403) + @delete_permission.require(http_exception=403) @blp.arguments(UpdateBlockUserSchema) def put(self, user_data, user_id): result = user_service.update_block_user(user_data, user_id) diff --git a/app/extention.py b/app/extention.py index e6fd943..e524629 100644 --- a/app/extention.py +++ b/app/extention.py @@ -1,7 +1,9 @@ from flask_migrate import Migrate from flask_jwt_extended import JWTManager from flask_cors import CORS +from flask_principal import Principal migrate = Migrate() jwt = JWTManager() cors = CORS() +principal = Principal() diff --git a/app/models/permission_model.py b/app/models/permission_model.py index d714efa..b7dc2ff 100644 --- a/app/models/permission_model.py +++ b/app/models/permission_model.py @@ -5,6 +5,6 @@ class PermissionModel(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(), unique=True, nullable=False) - route = db.Column(db.String(), unique=True, nullable=False) + description = db.Column(db.String()) roles = db.relationship("RoleModel", back_populates="permissions", secondary="role_permission") \ No newline at end of file diff --git a/app/models/user_model.py b/app/models/user_model.py index e5a6196..9f975f7 100644 --- a/app/models/user_model.py +++ b/app/models/user_model.py @@ -4,7 +4,7 @@ class UserModel(db.Model): __tablename__ = "user" - id = db.Column(db.Integer, primary_key = True) + id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False) password = db.Column(db.String(), nullable=False) block = db.Column(db.Boolean, default=False, nullable=False) diff --git a/app/services/permission_service.py b/app/services/permission_service.py index bdfb03b..cfffa23 100644 --- a/app/services/permission_service.py +++ b/app/services/permission_service.py @@ -9,10 +9,10 @@ def get_all_permission(): def post_permission(permission_data): name = permission_data['name'] - route = permission_data['route'] + description = permission_data['description'] try: - new_row = PermissionModel(name=name, route=route) + new_row = PermissionModel(name=name, description=description) db.session.add(new_row) db.session.commit() @@ -36,8 +36,8 @@ def update_permission(permission_data, permission_id): if permission_data['name']: permission.name = permission_data['name'] - if permission_data['route']: - permission.route = permission_data['route'] + if permission_data['description']: + permission.description = permission_data['description'] db.session.commit() except: diff --git a/app/services/user_service.py b/app/services/user_service.py index 7187c65..c60f949 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -50,6 +50,11 @@ def update_user(user_data, user_id): return {"message": "Update successfully!"} def update_block_user(user_data, user_id): + # Only admin can delete user + jwt = get_jwt() + if not jwt.get("is_admin"): + abort(401, message="Admin privilege requierd.") + if user_id == 1: abort(401, message="Can not block Super Admin!") diff --git a/app/utils/__init__.py b/app/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/utils/auth.py b/app/utils/auth.py index 94feae7..e325df0 100644 --- a/app/utils/auth.py +++ b/app/utils/auth.py @@ -1,7 +1,6 @@ from app.extention import jwt -from app.models.user_model import UserModel +from app.models import UserModel, BlocklistModel from flask import jsonify -from models.blocklist_model import BlocklistModel @jwt.token_verification_loader def custom_token_verification_callback(jwt_header, jwt_data): diff --git a/app/utils/principal.py b/app/utils/principal.py index b966e30..107dba4 100644 --- a/app/utils/principal.py +++ b/app/utils/principal.py @@ -1,19 +1,16 @@ -# from app.models.user_model import UserModel -# from flask_principal import Principal, identity_loaded, RoleNeed -# from app import app +from app.models import UserModel +from app.extention import principal +from flask_principal import identity_loaded, RoleNeed -# # Initialize Flask-Principal -# principal = Principal(app) - -# # Define a function to load the user's identity -# @identity_loaded.connect_via(app) -# def on_identity_loaded(sender, identity): -# # Get User -# user = UserModel.query.filter_by(id=identity.id).first() +# Define a function to load the user's identity +@identity_loaded.connect +def on_identity_loaded(sender, identity): + # Get User + user = UserModel.query.filter_by(id=identity.id).first() -# # Get all unique permissions -# for role in user.roles: -# # get permission -# for permission in role.permissions: -# # Add the user's roles to the identity object -# identity.provides.add(RoleNeed(permission.route)) + # Get all unique permissions + for role in user.roles: + # get permission + for permission in role.permissions: + # Add the user's roles to the identity object + identity.provides.add(RoleNeed(permission.name)) \ No newline at end of file From 45772be53ce23cc71130be48f97f386d2c8fcf72 Mon Sep 17 00:00:00 2001 From: Vector Nguyen Date: Sun, 23 Jul 2023 13:41:10 +0700 Subject: [PATCH 03/16] ADD coverage --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7722e1c..d30e594 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ certifi==2023.5.7 click==8.1.3 colorama==0.4.6 comm==0.1.2 +coverage==7.2.7 debugpy==1.6.4 decorator==5.1.1 distlib==0.3.6 @@ -25,7 +26,7 @@ greenlet==2.0.2 gunicorn==20.1.0 importlib-metadata==6.7.0 ipykernel==6.19.2 -ipython==8.10 +ipython==8.10.0 itsdangerous==2.1.2 jedi==0.18.2 Jinja2==3.1.2 From aa6d08c1fc37793c2c0e744280c7d045b4be1997 Mon Sep 17 00:00:00 2001 From: Vector Nguyen Date: Sun, 23 Jul 2023 13:41:42 +0700 Subject: [PATCH 04/16] UPDATE Unittest --- manage.py | 109 +++++++++++++++---- tests/__init__.py | 0 tests/integration/__init__.py | 0 tests/integration/tests_users_integration.py | 46 ++++++++ tests/unit/__init__.py | 0 tests/unit/tests_users_unit.py | 44 ++++++++ 6 files changed, 178 insertions(+), 21 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/integration/__init__.py create mode 100644 tests/integration/tests_users_integration.py create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/tests_users_unit.py diff --git a/manage.py b/manage.py index 4556913..413bc6f 100644 --- a/manage.py +++ b/manage.py @@ -1,7 +1,68 @@ from app import db +import unittest +import click +import coverage from app.models import UserModel, PermissionModel, RoleModel, RolePermissionModel, UserRoleModel from passlib.hash import pbkdf2_sha256 +@click.option("--pattern", default='tests_*.py', help='Test search pattern', required=False) +def cov(pattern): + """ + Run the unit tests with coverage + """ + cov = coverage.coverage( + branch=True, + include='app/*' + ) + cov.start() + tests = unittest.TestLoader().discover('tests', pattern=pattern) + result = unittest.TextTestRunner(verbosity=2).run(tests) + if result.wasSuccessful(): + cov.stop() + cov.save() + print('Coverage Summary:') + cov.report() + cov.erase() + return 0 + return 1 + +@click.option("--pattern", default='tests_*.py', help='Test search pattern', required=False) +def cov_html(pattern): + """ + Run the unit tests with coverage and generate an HTML report. + """ + cov = coverage.coverage( + branch=True, + include='app/*' + ) + cov.start() + + tests = unittest.TestLoader().discover('tests', pattern=pattern) + result = unittest.TextTestRunner(verbosity=2).run(tests) + + if result.wasSuccessful(): + cov.stop() + cov.save() + + print('Coverage Summary:') + cov.report() + cov.html_report(directory='report/htmlcov') + cov.erase() + return 0 + + return 1 + +@click.option("--pattern", default='tests_*.py', help='Test pattern', required=False) +def tests(pattern): + """ + Run the tests without code coverage + """ + tests = unittest.TestLoader().discover('tests', pattern=pattern) + result = unittest.TextTestRunner(verbosity=2).run(tests) + if result.wasSuccessful(): + return 0 + return 1 + def create_db(): """ Create Database. @@ -28,35 +89,43 @@ def drop_db(): def init_db_user(): # Insert Permission - new_perrmission1 = PermissionModel(name='User management', route='user_management') - db.session.add_all([new_perrmission1]) + read_perrmission = PermissionModel(name='read', description='Read data') + write_perrmission = PermissionModel(name='write', description='Write data') + delete_perrmission = PermissionModel(name='delete', description='Delete data') + db.session.add_all([read_perrmission, write_perrmission, delete_perrmission]) db.session.commit() # Insert Role - new_role1 = RoleModel(name='Super Admin', description='Full Permission') - new_role2 = RoleModel(name='Admin', description='Manage user') - new_role3 = RoleModel(name='Member', description='Member') - new_role4 = RoleModel(name='Guest',description='Guest') - db.session.add_all([new_role1, new_role2, new_role3, new_role4]) + admin_role = RoleModel(name='Admin', description='Full Permission') + user_role = RoleModel(name='User', description='Can read, write data') + guest_role = RoleModel(name='Guest',description='Just read data') + db.session.add_all([admin_role, user_role, guest_role]) db.session.commit() # Insert Role_Permission - new_permission1 = RolePermissionModel(role_id=1, permission_id=1) - new_permission2 = RolePermissionModel(role_id=2, permission_id=1) - db.session.add_all([new_permission1, new_permission2]) + role_permission_admin1 = RolePermissionModel(role_id=1, permission_id=1) + role_permission_admin2 = RolePermissionModel(role_id=1, permission_id=2) + role_permission_admin3 = RolePermissionModel(role_id=1, permission_id=3) + role_permission_user1 = RolePermissionModel(role_id=2, permission_id=1) + role_permission_user2 = RolePermissionModel(role_id=2, permission_id=2) + role_permission_guest = RolePermissionModel(role_id=3, permission_id=1) + db.session.add_all([role_permission_admin1, role_permission_admin2, role_permission_admin3, + role_permission_user1, role_permission_user2, role_permission_guest]) db.session.commit() # Insert User password = pbkdf2_sha256.hash("123456") - new_user1 = UserModel(username='admin', password=password) - db.session.add_all([new_user1]) + admin_user = UserModel(username='admin', password=password) + normal_user = UserModel(username='user', password=password) + guest_user = UserModel(username='guest', password=password) + db.session.add_all([admin_user, normal_user, guest_user]) db.session.commit() # Insert UserRole - new_userrole1 = UserRoleModel(user_id=1, role_id = 1) - new_userrole2 = UserRoleModel(user_id=1, role_id = 2) - new_userrole3 = UserRoleModel(user_id=1, role_id = 3) - db.session.add_all([new_userrole1, new_userrole2, new_userrole3]) + user_role1 = UserRoleModel(user_id=1, role_id=1) + user_role2 = UserRoleModel(user_id=2, role_id=2) + user_role3 = UserRoleModel(user_id=3, role_id=3) + db.session.add_all([user_role1, user_role2, user_role3]) db.session.commit() def create_user_admin(username='admin'): @@ -65,9 +134,8 @@ def create_user_admin(username='admin'): """ admin = UserModel.query.filter_by(username=username).first() - if admin is None: - print("user-admin is not created!") + print("user-admin is not created before!") init_db_user() else: print("user-admin is created!") @@ -76,8 +144,7 @@ def init_app(app): if app.config['APP_ENV'] == 'production': commands = [create_db, reset_db, drop_db, create_user_admin] else: - commands = [create_db, reset_db, drop_db, create_user_admin] + commands = [create_db, reset_db, drop_db, create_user_admin, tests, cov_html, cov] for command in commands: - app.cli.add_command(app.cli.command()(command)) - + app.cli.add_command(app.cli.command()(command)) \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/integration/tests_users_integration.py b/tests/integration/tests_users_integration.py new file mode 100644 index 0000000..29f11be --- /dev/null +++ b/tests/integration/tests_users_integration.py @@ -0,0 +1,46 @@ +import os +import unittest +from app.models import UserModel +from app import db, create_app +from passlib.hash import pbkdf2_sha256 + +class UsersUnitTests(unittest.TestCase): + def setUp(self): + """ + This method runs once before any test in this class. + It sets up the application context and creates the necessary database tables. + """ + self.app = create_app(settings_module=os.environ.get('APP_TEST_SETTINGS_MODULE')) + with self.app.app_context(): + db.create_all() + + def tearDown(self): + """ + This method runs once after all tests in this class have been executed. + It removes the database session and drops the database tables. + """ + with self.app.app_context(): + db.session.remove() + db.drop_all() + + def test_create_user(self): + """ + Test case to check if creating a user is successful. + """ + + with self.app.app_context(): + username = 'test_user' + password = "123456" + + user = UserModel(username=username, password=pbkdf2_sha256.hash(password)) + + # Add to database + db.session.add(user) + db.session.commit() + + # Assertions to check if the user object is created correctly + self.assertEqual(username, user.username) + self.assertTrue(pbkdf2_sha256.verify(password, user.password)) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/tests_users_unit.py b/tests/unit/tests_users_unit.py new file mode 100644 index 0000000..feae821 --- /dev/null +++ b/tests/unit/tests_users_unit.py @@ -0,0 +1,44 @@ +import os +import unittest +from app.models import UserModel +from app import db, create_app +from passlib.hash import pbkdf2_sha256 + +class UsersUnitTests(unittest.TestCase): + def setUp(self): + """ + This method runs once before any test in this class. + It sets up the application context and creates the necessary database tables. + """ + self.app = create_app(settings_module=os.environ.get('APP_TEST_SETTINGS_MODULE')) + with self.app.app_context(): + db.create_all() + + def tearDown(self): + """ + This method runs once after all tests in this class have been executed. + It removes the database session and drops the database tables. + """ + with self.app.app_context(): + db.session.remove() + db.drop_all() + + def test_create_user_success(self): + """ + Test case to check if creating a user is successful. + """ + # Given + username = 'test_user' + password = "123456" + + # When + with self.app.app_context(): + user = UserModel(username=username, password=pbkdf2_sha256.hash(password)) + + # Then + # Assertions to check if the user object is created correctly + self.assertEqual(username, user.username) + self.assertTrue(pbkdf2_sha256.verify(password, user.password)) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 50bf46dc343bd12b10b8e7cf976015ae88027201 Mon Sep 17 00:00:00 2001 From: Vector Nguyen Date: Sun, 23 Jul 2023 18:13:19 +0700 Subject: [PATCH 05/16] UPDATE Test ci --- .github/workflows/development_pipeline.yml | 42 +++++++++++++++++++--- entrypoint.sh | 4 +-- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/.github/workflows/development_pipeline.yml b/.github/workflows/development_pipeline.yml index e6e49a0..eccc86d 100644 --- a/.github/workflows/development_pipeline.yml +++ b/.github/workflows/development_pipeline.yml @@ -1,8 +1,40 @@ -name: hello-world -on: push +name: development +on: + pull_request: + branches: develop + + push: + branches: + - develop jobs: - my-job: + build-test: runs-on: ubuntu-latest + services: + postgres: + image: postgres:13.3 + env: + POSTGRES_USER: db_user + POSTGRES_PASSWORD: db_password + POSTGRES_DB: db_test + ports: + - 5432:5432 + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + steps: - - name: my-step - run: echo "Hello World with Github Actions" + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9 + + - name: Install Python dependencies + run: python -m pip install -r requirements.txt + + - name: Unit Tests + env: + DATABASE_TEST_URL: postgresql://db_user:db_password@postgres:5432/db_test + run: python -m unittest discover -p '*_unit.py' + \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh index 132b07f..9ee7f5e 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -29,10 +29,10 @@ if [ "$APP_ENV" = "local" ]; then echo "Done init user-admin" echo "Run app with gunicorn server..." - gunicorn --bind $API_HOST:$API_PORT $API_ENTRYPOINT --timeout 10 --workers 4; + gunicorn --bind $API_HOST:$API_PORT $API_ENTRYPOINT --timeout 10 --workers 1; fi if [ "$APP_ENV" = "production" ]; then echo "Run app with gunicorn server..." - gunicorn --bind $API_HOST:$API_PORT $API_ENTRYPOINT --timeout 10 --workers 4; + gunicorn --bind $API_HOST:$API_PORT $API_ENTRYPOINT --timeout 10 --workers 1; fi \ No newline at end of file From 8ffcc9a275e8944a3813205744101271f68bbf28 Mon Sep 17 00:00:00 2001 From: Vector Nguyen Date: Sun, 23 Jul 2023 18:18:30 +0700 Subject: [PATCH 06/16] UPDATE Test ci --- .github/workflows/development_pipeline.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/development_pipeline.yml b/.github/workflows/development_pipeline.yml index eccc86d..daf0c56 100644 --- a/.github/workflows/development_pipeline.yml +++ b/.github/workflows/development_pipeline.yml @@ -36,5 +36,12 @@ jobs: - name: Unit Tests env: DATABASE_TEST_URL: postgresql://db_user:db_password@postgres:5432/db_test + # APP_NAME: "Flask API Rest Template" + # APP_ENV: "develop" + # FLASK_APP: "app:app" + # FLASK_DEBUG: "true" + APP_TEST_SETTINGS_MODULE: "config.TestingConfig" + # FLASK_RUN_HOST: "0.0.0.0" + # FLASK_RUN_PORT: "5000" run: python -m unittest discover -p '*_unit.py' \ No newline at end of file From 25321e7480f00524b2d7cef03f8fe9ebafc4860e Mon Sep 17 00:00:00 2001 From: Vector Nguyen Date: Sun, 23 Jul 2023 18:21:48 +0700 Subject: [PATCH 07/16] UPDATE Test ci --- .github/workflows/development_pipeline.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/development_pipeline.yml b/.github/workflows/development_pipeline.yml index daf0c56..e0661ca 100644 --- a/.github/workflows/development_pipeline.yml +++ b/.github/workflows/development_pipeline.yml @@ -36,12 +36,12 @@ jobs: - name: Unit Tests env: DATABASE_TEST_URL: postgresql://db_user:db_password@postgres:5432/db_test - # APP_NAME: "Flask API Rest Template" - # APP_ENV: "develop" - # FLASK_APP: "app:app" - # FLASK_DEBUG: "true" - APP_TEST_SETTINGS_MODULE: "config.TestingConfig" - # FLASK_RUN_HOST: "0.0.0.0" - # FLASK_RUN_PORT: "5000" + APP_NAME: "Flask API Rest Template" + APP_ENV: "develop" + FLASK_APP: "app:app" + FLASK_DEBUG: "true" + APP_SETTINGS_MODULE: "config.TestingConfig" + FLASK_RUN_HOST: "0.0.0.0" + FLASK_RUN_PORT: "5000" run: python -m unittest discover -p '*_unit.py' \ No newline at end of file From 0dd7245b77378fcc1a278d600f56c45f0876709c Mon Sep 17 00:00:00 2001 From: Vector Nguyen Date: Sun, 23 Jul 2023 18:28:34 +0700 Subject: [PATCH 08/16] UPDATE Test ci --- .github/workflows/development_pipeline.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/development_pipeline.yml b/.github/workflows/development_pipeline.yml index e0661ca..574ff16 100644 --- a/.github/workflows/development_pipeline.yml +++ b/.github/workflows/development_pipeline.yml @@ -25,6 +25,17 @@ jobs: - name: Checkout code uses: actions/checkout@v2 + - name: Cache Python dependencies + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Copy environment file + run: cp .env $GITHUB_WORKSPACE/.env + - name: Set up Python 3.9 uses: actions/setup-python@v2 with: @@ -36,12 +47,5 @@ jobs: - name: Unit Tests env: DATABASE_TEST_URL: postgresql://db_user:db_password@postgres:5432/db_test - APP_NAME: "Flask API Rest Template" - APP_ENV: "develop" - FLASK_APP: "app:app" - FLASK_DEBUG: "true" - APP_SETTINGS_MODULE: "config.TestingConfig" - FLASK_RUN_HOST: "0.0.0.0" - FLASK_RUN_PORT: "5000" run: python -m unittest discover -p '*_unit.py' \ No newline at end of file From ab2b517ee53f1fb171c83e1e96a090553e4c6850 Mon Sep 17 00:00:00 2001 From: Vector Nguyen Date: Sun, 23 Jul 2023 18:32:41 +0700 Subject: [PATCH 09/16] UPDATE Test ci --- .github/workflows/development_pipeline.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/development_pipeline.yml b/.github/workflows/development_pipeline.yml index 574ff16..9c84f61 100644 --- a/.github/workflows/development_pipeline.yml +++ b/.github/workflows/development_pipeline.yml @@ -33,9 +33,6 @@ jobs: restore-keys: | ${{ runner.os }}-pip- - - name: Copy environment file - run: cp .env $GITHUB_WORKSPACE/.env - - name: Set up Python 3.9 uses: actions/setup-python@v2 with: @@ -47,5 +44,12 @@ jobs: - name: Unit Tests env: DATABASE_TEST_URL: postgresql://db_user:db_password@postgres:5432/db_test + APP_NAME: "Flask API Rest Template" + APP_ENV: "develop" + FLASK_APP: "app:app" + FLASK_DEBUG: "true" + APP_TEST_SETTINGS_MODULE: "config.TestingConfig" + FLASK_RUN_HOST: "0.0.0.0" + FLASK_RUN_PORT: "5000" run: python -m unittest discover -p '*_unit.py' \ No newline at end of file From 594fb7e43b791226265059c2cd8cc54718d77946 Mon Sep 17 00:00:00 2001 From: Vector Nguyen Date: Sun, 23 Jul 2023 18:41:52 +0700 Subject: [PATCH 10/16] UPDATE Test ci --- .github/workflows/development_pipeline.yml | 4 +--- .github/workflows/production_pipeline.yaml | 6 ++++-- .github/workflows/staging_pipeline.yaml | 6 ++++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/development_pipeline.yml b/.github/workflows/development_pipeline.yml index 9c84f61..e562f4e 100644 --- a/.github/workflows/development_pipeline.yml +++ b/.github/workflows/development_pipeline.yml @@ -18,8 +18,6 @@ jobs: POSTGRES_DB: db_test ports: - 5432:5432 - # needed because the postgres container does not provide a healthcheck - options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Checkout code @@ -43,7 +41,7 @@ jobs: - name: Unit Tests env: - DATABASE_TEST_URL: postgresql://db_user:db_password@postgres:5432/db_test + DATABASE_TEST_URL: "postgresql://db_user:db_password@postgres:5432/db_test" APP_NAME: "Flask API Rest Template" APP_ENV: "develop" FLASK_APP: "app:app" diff --git a/.github/workflows/production_pipeline.yaml b/.github/workflows/production_pipeline.yaml index e6e49a0..1f9d663 100644 --- a/.github/workflows/production_pipeline.yaml +++ b/.github/workflows/production_pipeline.yaml @@ -1,5 +1,7 @@ -name: hello-world -on: push +name: production +on: + pull_request: + branches: main jobs: my-job: runs-on: ubuntu-latest diff --git a/.github/workflows/staging_pipeline.yaml b/.github/workflows/staging_pipeline.yaml index e6e49a0..9b510d6 100644 --- a/.github/workflows/staging_pipeline.yaml +++ b/.github/workflows/staging_pipeline.yaml @@ -1,5 +1,7 @@ -name: hello-world -on: push +name: staging +on: + pull_request: + branches: staging jobs: my-job: runs-on: ubuntu-latest From 07b58f7088285aedfb559116b07ceeb91a6acc3d Mon Sep 17 00:00:00 2001 From: Vector Nguyen Date: Sun, 23 Jul 2023 18:44:40 +0700 Subject: [PATCH 11/16] UPDATE Test ci --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index 1d7d56b..7c8de45 100644 --- a/config.py +++ b/config.py @@ -85,7 +85,7 @@ class TestingConfig(DefaultConfig): LOG_FILE_API = f'{basedir}/logs/api_tests.log' # Database configuration - SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_TEST_URL') + SQLALCHEMY_DATABASE_URI = "postgresql://db_user:db_password@postgres:5432/db_test" class LocalConfig(DefaultConfig): From 346250279ee151996ae28e91f7415b5a9fcc2205 Mon Sep 17 00:00:00 2001 From: Vector Nguyen Date: Sun, 23 Jul 2023 18:51:43 +0700 Subject: [PATCH 12/16] UPDATE Test ci --- .github/workflows/development_pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/development_pipeline.yml b/.github/workflows/development_pipeline.yml index e562f4e..7de2c61 100644 --- a/.github/workflows/development_pipeline.yml +++ b/.github/workflows/development_pipeline.yml @@ -49,5 +49,5 @@ jobs: APP_TEST_SETTINGS_MODULE: "config.TestingConfig" FLASK_RUN_HOST: "0.0.0.0" FLASK_RUN_PORT: "5000" - run: python -m unittest discover -p '*_unit.py' + run: python -m flask run \ No newline at end of file From c19fc7d2d9174ad7124878318a1b2666d2bb6cd4 Mon Sep 17 00:00:00 2001 From: Vector Nguyen Date: Sun, 23 Jul 2023 18:51:59 +0700 Subject: [PATCH 13/16] UPDATE Test ci --- .github/workflows/development_pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/development_pipeline.yml b/.github/workflows/development_pipeline.yml index 7de2c61..3e6bb90 100644 --- a/.github/workflows/development_pipeline.yml +++ b/.github/workflows/development_pipeline.yml @@ -49,5 +49,5 @@ jobs: APP_TEST_SETTINGS_MODULE: "config.TestingConfig" FLASK_RUN_HOST: "0.0.0.0" FLASK_RUN_PORT: "5000" - run: python -m flask run + run: python -m flask tests \ No newline at end of file From 4b03faea4ab4aba1d2e4a00404500a558ba65750 Mon Sep 17 00:00:00 2001 From: Vector Nguyen Date: Sun, 23 Jul 2023 18:58:32 +0700 Subject: [PATCH 14/16] UPDATE Test ci --- .github/workflows/development_pipeline.yml | 12 +++--------- config.py | 2 +- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/development_pipeline.yml b/.github/workflows/development_pipeline.yml index 3e6bb90..dbd4190 100644 --- a/.github/workflows/development_pipeline.yml +++ b/.github/workflows/development_pipeline.yml @@ -18,7 +18,8 @@ jobs: POSTGRES_DB: db_test ports: - 5432:5432 - + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Checkout code uses: actions/checkout@v2 @@ -41,13 +42,6 @@ jobs: - name: Unit Tests env: - DATABASE_TEST_URL: "postgresql://db_user:db_password@postgres:5432/db_test" - APP_NAME: "Flask API Rest Template" - APP_ENV: "develop" - FLASK_APP: "app:app" - FLASK_DEBUG: "true" - APP_TEST_SETTINGS_MODULE: "config.TestingConfig" - FLASK_RUN_HOST: "0.0.0.0" - FLASK_RUN_PORT: "5000" + DATABASE_TEST_URL: postgresql://db_user:db_password@postgres:5432/db_test run: python -m flask tests \ No newline at end of file diff --git a/config.py b/config.py index 7c8de45..1d7d56b 100644 --- a/config.py +++ b/config.py @@ -85,7 +85,7 @@ class TestingConfig(DefaultConfig): LOG_FILE_API = f'{basedir}/logs/api_tests.log' # Database configuration - SQLALCHEMY_DATABASE_URI = "postgresql://db_user:db_password@postgres:5432/db_test" + SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_TEST_URL') class LocalConfig(DefaultConfig): From 6e85b6bef307d3ab601a0cc14378a30f9dd12e5c Mon Sep 17 00:00:00 2001 From: Vector Nguyen Date: Sun, 23 Jul 2023 19:01:45 +0700 Subject: [PATCH 15/16] UPDATE Test ci --- .github/workflows/development_pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/development_pipeline.yml b/.github/workflows/development_pipeline.yml index dbd4190..cb573f2 100644 --- a/.github/workflows/development_pipeline.yml +++ b/.github/workflows/development_pipeline.yml @@ -42,6 +42,6 @@ jobs: - name: Unit Tests env: - DATABASE_TEST_URL: postgresql://db_user:db_password@postgres:5432/db_test + DATABASE_TEST_URL: postgresql://db_user:db_password@localhost/db_test run: python -m flask tests \ No newline at end of file From 8c10703bb04953e9a2d927caef3dd1b86bbeb9b6 Mon Sep 17 00:00:00 2001 From: Vector Nguyen Date: Sun, 23 Jul 2023 19:06:53 +0700 Subject: [PATCH 16/16] UPDATE Test ci --- .github/workflows/development_pipeline.yml | 3 +- .github/workflows/staging_pipeline.yaml | 37 ++++++++++++++++++++-- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/.github/workflows/development_pipeline.yml b/.github/workflows/development_pipeline.yml index cb573f2..f3dfd06 100644 --- a/.github/workflows/development_pipeline.yml +++ b/.github/workflows/development_pipeline.yml @@ -18,7 +18,6 @@ jobs: POSTGRES_DB: db_test ports: - 5432:5432 - # needed because the postgres container does not provide a healthcheck options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Checkout code @@ -40,7 +39,7 @@ jobs: - name: Install Python dependencies run: python -m pip install -r requirements.txt - - name: Unit Tests + - name: Unit Tests and Integration Tests env: DATABASE_TEST_URL: postgresql://db_user:db_password@localhost/db_test run: python -m flask tests diff --git a/.github/workflows/staging_pipeline.yaml b/.github/workflows/staging_pipeline.yaml index 9b510d6..05afe6f 100644 --- a/.github/workflows/staging_pipeline.yaml +++ b/.github/workflows/staging_pipeline.yaml @@ -3,8 +3,39 @@ on: pull_request: branches: staging jobs: - my-job: + build-test: runs-on: ubuntu-latest + services: + postgres: + image: postgres:13.3 + env: + POSTGRES_USER: db_user + POSTGRES_PASSWORD: db_password + POSTGRES_DB: db_test + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - - name: my-step - run: echo "Hello World with Github Actions" + - name: Checkout code + uses: actions/checkout@v2 + + - name: Cache Python dependencies + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9 + + - name: Install Python dependencies + run: python -m pip install -r requirements.txt + + - name: Unit Tests and Integration Tests + env: + DATABASE_TEST_URL: postgresql://db_user:db_password@localhost/db_test + run: python -m flask tests \ No newline at end of file