From be436894947cfaa9bf5fac216badd5fed2ef975f Mon Sep 17 00:00:00 2001 From: MishoBankata Date: Thu, 29 Jan 2026 23:36:59 +0200 Subject: [PATCH 01/17] Added the feature User Registration Endpoint --- backend/routes/auth_routes.py | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/backend/routes/auth_routes.py b/backend/routes/auth_routes.py index e69de29..636f95c 100644 --- a/backend/routes/auth_routes.py +++ b/backend/routes/auth_routes.py @@ -0,0 +1,39 @@ +from flask import Blueprint, jsonify, request +from models/user import User +from extensions import db + +# Auth Blueprint +auth_bp = Blueprint('auth', __name__) + +@auth_bp.route('/register', methods=['POST']) +def register(): + data = request.get_json() + username = data.get('username') + email = data.get('email') + password = data.get('password') + + # Check if fields are empty + if not username or not email or not password: + return jsonify({"msg": "Missing required fields"}), 400 + + # Check if username already exists + existing_user = User.query.filter_by(username=data['username']).first() + if existing_user: + return jsonify({'error': 'Username already taken'}), 409 + + # Check if email already exists + existing_email = User.query.filter_by(email=data['email']).first() + if existing_email: + return jsonify({'error': 'Email already registered'}), 409 + + #Validate and hash password + User.validate_password(password) + User.set_password(password) + + + new_user = User(email=email, password=password, username=username) + + db.session.add(new_user) + db.session.commit() + + return jsonify({"msg": "User registered successfully"}), 201 From a5da8da71ed0716732bee989056197d28ac494d4 Mon Sep 17 00:00:00 2001 From: MishoBankata Date: Fri, 30 Jan 2026 01:23:01 +0200 Subject: [PATCH 02/17] Fixed synthax errors in auth_routes.py; Added blueprint for registration feature to app.py; Created .env file with placeholder values --- backend/app.py | 8 ++++++++ backend/routes/auth_routes.py | 7 ++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/backend/app.py b/backend/app.py index c1e5b34..4f829c2 100644 --- a/backend/app.py +++ b/backend/app.py @@ -11,6 +11,14 @@ def create_app(): jwt.init_app(app) migrate.init_app(app, db) + from routes.auth_routes import auth_bp + app.register_blueprint(auth_bp) + + with app.app_context(): + from models.user import User + from models.user_stats import UserStats + db.create_all() + return app diff --git a/backend/routes/auth_routes.py b/backend/routes/auth_routes.py index 636f95c..22b7526 100644 --- a/backend/routes/auth_routes.py +++ b/backend/routes/auth_routes.py @@ -1,6 +1,7 @@ from flask import Blueprint, jsonify, request -from models/user import User +from models.user import User from extensions import db +from werkzeug.security import generate_password_hash, check_password_hash # Auth Blueprint auth_bp = Blueprint('auth', __name__) @@ -28,10 +29,10 @@ def register(): #Validate and hash password User.validate_password(password) - User.set_password(password) + hashed_password = generate_password_hash(password, method='pbkdf2:sha256') - new_user = User(email=email, password=password, username=username) + new_user = User(email=email, password=hashed_password, username=username) db.session.add(new_user) db.session.commit() From 464bbf640ec294cdaa5f6996fe546eac76c2a0d2 Mon Sep 17 00:00:00 2001 From: MishoBankata Date: Fri, 30 Jan 2026 01:26:40 +0200 Subject: [PATCH 03/17] modified .gitignore file to allow the .env file to be pushed --- .gitignore | 2 +- backend/.env | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 backend/.env diff --git a/.gitignore b/.gitignore index 7d23db4..b9dfc04 100644 --- a/.gitignore +++ b/.gitignore @@ -135,7 +135,7 @@ celerybeat.pid *.sage.py # Environments -.env +#.env .envrc .venv env/ diff --git a/backend/.env b/backend/.env new file mode 100644 index 0000000..5b128ed --- /dev/null +++ b/backend/.env @@ -0,0 +1,10 @@ +#Postgres SQL Envs +SKILLFORGE_POSTGRES_USER=skillforge_admin +SKILLFORGE_POSTGRES_PASSWORD=misho_e_gotin +SKILLFORGE_POSTGRES_DBNAME=skillforge_db +SKILLFORGE_POSTGRES_PORT=5450 +SKILLFORGE_TIMEZONE=Europe/Sofia + + +#SQLAlchemy database URI +SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://${SKILLFORGE_POSTGRES_USER}:${SKILLFORGE_POSTGRES_PASSWORD}@localhost:5450/${SKILLFORGE_POSTGRES_DBNAME}" \ No newline at end of file From 13272ac54c2ef2b4c6afcd7f45bd2277a6da64ac Mon Sep 17 00:00:00 2001 From: Aleksandar Karastoyanov Date: Fri, 30 Jan 2026 09:38:48 +0200 Subject: [PATCH 04/17] Update grep pattern to match print statements --- .github/workflows/check-python-prints.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-python-prints.yml b/.github/workflows/check-python-prints.yml index 0bbe327..6cbd25f 100644 --- a/.github/workflows/check-python-prints.yml +++ b/.github/workflows/check-python-prints.yml @@ -31,7 +31,7 @@ jobs: echo "$changed_files" # Search for 'print(' in changed files - found=$(grep -nH "print(" $changed_files || true) + found=$(grep -nH -E '(^|[^a-zA-Z0-9_])print\s*\(' $changed_files || true) if [ -n "$found" ]; then echo "❌ Found print statements:" From 3747ad13a7abdb785fb13d344286bf4e9019db8f Mon Sep 17 00:00:00 2001 From: MishoBankata Date: Fri, 30 Jan 2026 12:41:21 +0200 Subject: [PATCH 05/17] Removed unneccessary check_password_hash from auth_routes.py; Changed single quotes to double quotes in json responses in auth_routes.py; removed my .env from remote repo and altered gitignore so it won't get pushed again; Removed one unnecessary import from app.py and moved the other one to the top of the file --- .gitignore | 2 +- backend/.env | 10 ---------- backend/app.py | 4 +--- backend/routes/auth_routes.py | 6 +++--- 4 files changed, 5 insertions(+), 17 deletions(-) delete mode 100644 backend/.env diff --git a/.gitignore b/.gitignore index b9dfc04..7d23db4 100644 --- a/.gitignore +++ b/.gitignore @@ -135,7 +135,7 @@ celerybeat.pid *.sage.py # Environments -#.env +.env .envrc .venv env/ diff --git a/backend/.env b/backend/.env deleted file mode 100644 index 5b128ed..0000000 --- a/backend/.env +++ /dev/null @@ -1,10 +0,0 @@ -#Postgres SQL Envs -SKILLFORGE_POSTGRES_USER=skillforge_admin -SKILLFORGE_POSTGRES_PASSWORD=misho_e_gotin -SKILLFORGE_POSTGRES_DBNAME=skillforge_db -SKILLFORGE_POSTGRES_PORT=5450 -SKILLFORGE_TIMEZONE=Europe/Sofia - - -#SQLAlchemy database URI -SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://${SKILLFORGE_POSTGRES_USER}:${SKILLFORGE_POSTGRES_PASSWORD}@localhost:5450/${SKILLFORGE_POSTGRES_DBNAME}" \ No newline at end of file diff --git a/backend/app.py b/backend/app.py index 4f829c2..19f7c5a 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,7 +1,7 @@ from flask import Flask from config import Config from extensions import db, jwt, migrate - +from models.user_stats import UserStats def create_app(): app = Flask(__name__) @@ -15,8 +15,6 @@ def create_app(): app.register_blueprint(auth_bp) with app.app_context(): - from models.user import User - from models.user_stats import UserStats db.create_all() return app diff --git a/backend/routes/auth_routes.py b/backend/routes/auth_routes.py index 22b7526..3facc64 100644 --- a/backend/routes/auth_routes.py +++ b/backend/routes/auth_routes.py @@ -1,7 +1,7 @@ from flask import Blueprint, jsonify, request from models.user import User from extensions import db -from werkzeug.security import generate_password_hash, check_password_hash +from werkzeug.security import generate_password_hash # Auth Blueprint auth_bp = Blueprint('auth', __name__) @@ -20,12 +20,12 @@ def register(): # Check if username already exists existing_user = User.query.filter_by(username=data['username']).first() if existing_user: - return jsonify({'error': 'Username already taken'}), 409 + return jsonify({"error": "Username already taken"}), 409 # Check if email already exists existing_email = User.query.filter_by(email=data['email']).first() if existing_email: - return jsonify({'error': 'Email already registered'}), 409 + return jsonify({"error": "Email already registered"}), 409 #Validate and hash password User.validate_password(password) From 8fb66b08bb02db2b2031949a87fc502ab2257a65 Mon Sep 17 00:00:00 2001 From: Aleksandar Karastoyanov Date: Fri, 30 Jan 2026 13:15:57 +0200 Subject: [PATCH 06/17] Fixed python lint errors Signed-off-by: Aleksandar Karastoyanov --- backend/app.py | 2 ++ backend/requirements.txt | Bin 490 -> 463 bytes backend/routes/auth_routes.py | 22 +++++++++++----------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/backend/app.py b/backend/app.py index 19f7c5a..26fd155 100644 --- a/backend/app.py +++ b/backend/app.py @@ -3,6 +3,7 @@ from extensions import db, jwt, migrate from models.user_stats import UserStats + def create_app(): app = Flask(__name__) app.config.from_object(Config) @@ -12,6 +13,7 @@ def create_app(): migrate.init_app(app, db) from routes.auth_routes import auth_bp + app.register_blueprint(auth_bp) with app.app_context(): diff --git a/backend/requirements.txt b/backend/requirements.txt index 505530767e521e5af288926494d52ac32b0d6fe1..72de1d8fbbf7da4fff1e8606f211601e235f65a6 100644 GIT binary patch literal 463 zcmY+A%}&EG5QOi3msoL{gmUnK0~{(Ufr`{)wN2dA#D8*JMEdlM6Ap0mc(mWnEE#3b zx|33H(ej3!4RxcGRl|kgC8TzvirIdI?2OLtmJ7aRoi{#`B`Hf@^On6BnqY2VR;ws> z$B*LqJ1N(zUdoz3aPjNx^$;nk{F(PGv&G^4^~rQsW#<(mn6|;kWc2xDRid5}wrv-T zPE${AP|@Ee??I_r5#8PUa55TpoSM?Nnx;x9;WW_mCn@B15ebL@xDQ!QLRVL&u|%sb(x!ytJP}J zd%ahqlw7*mqene z=6b@#tv)&zIdi#BS$3DdPX15LNTKws&AEB9Uh@=Fs>!a!Lm-EeZ4xa#H%q7051$fA AO#lD@ diff --git a/backend/routes/auth_routes.py b/backend/routes/auth_routes.py index 3facc64..dc64c16 100644 --- a/backend/routes/auth_routes.py +++ b/backend/routes/auth_routes.py @@ -4,33 +4,33 @@ from werkzeug.security import generate_password_hash # Auth Blueprint -auth_bp = Blueprint('auth', __name__) +auth_bp = Blueprint("auth", __name__) -@auth_bp.route('/register', methods=['POST']) + +@auth_bp.route("/register", methods=["POST"]) def register(): data = request.get_json() - username = data.get('username') - email = data.get('email') - password = data.get('password') + username = data.get("username") + email = data.get("email") + password = data.get("password") # Check if fields are empty if not username or not email or not password: return jsonify({"msg": "Missing required fields"}), 400 # Check if username already exists - existing_user = User.query.filter_by(username=data['username']).first() + existing_user = User.query.filter_by(username=data["username"]).first() if existing_user: return jsonify({"error": "Username already taken"}), 409 - + # Check if email already exists - existing_email = User.query.filter_by(email=data['email']).first() + existing_email = User.query.filter_by(email=data["email"]).first() if existing_email: return jsonify({"error": "Email already registered"}), 409 - #Validate and hash password + # Validate and hash password User.validate_password(password) - hashed_password = generate_password_hash(password, method='pbkdf2:sha256') - + hashed_password = generate_password_hash(password, method="pbkdf2:sha256") new_user = User(email=email, password=hashed_password, username=username) From 34c69197d41c67801319722f57ab4d4551e4d6a9 Mon Sep 17 00:00:00 2001 From: Aleksandar Karastoyanov Date: Fri, 30 Jan 2026 13:17:11 +0200 Subject: [PATCH 07/17] Remove unnecessary imports Signed-off-by: Aleksandar Karastoyanov --- backend/app.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/app.py b/backend/app.py index 26fd155..a32ca96 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,7 +1,6 @@ -from flask import Flask from config import Config from extensions import db, jwt, migrate -from models.user_stats import UserStats +from flask import Flask def create_app(): From 99a5d60e0e5e2a8b2e38d90f10a2207d95ae25c6 Mon Sep 17 00:00:00 2001 From: Aleksandar Karastoyanov Date: Fri, 30 Jan 2026 13:44:03 +0200 Subject: [PATCH 08/17] fix: Fixed /register route: - Improved errors handling - Fixed UsersStats class and relations Signed-off-by: Aleksandar Karastoyanov --- backend/.env.template | 4 +++- backend/app.py | 1 + backend/models/__init__.py | 2 ++ backend/requirements.txt | 1 + backend/routes/auth_routes.py | 22 +++++++++++++++++++--- 5 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 backend/models/__init__.py diff --git a/backend/.env.template b/backend/.env.template index 8852813..490783f 100644 --- a/backend/.env.template +++ b/backend/.env.template @@ -5,6 +5,8 @@ SKILLFORGE_POSTGRES_DBNAME="your-db-name" SKILLFORGE_POSTGRES_PORT="your-host-port" SKILLFORGE_TIMEZONE="Europe/Sofia" +# Flask Secret Key +SECRET_KEY="your-super-secret-key" # Use https://www.terrific.tools/online/flask-secret-key-generator to generate one for yourself #SQLAlchemy database URI -SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://your-username:your-password@localhost:your-host-port/your-db-name" \ No newline at end of file +SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://your-username:your-password@localhost:your-host-port/your-db-name" diff --git a/backend/app.py b/backend/app.py index a32ca96..d4567e1 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,6 +1,7 @@ from config import Config from extensions import db, jwt, migrate from flask import Flask +from models import * def create_app(): diff --git a/backend/models/__init__.py b/backend/models/__init__.py new file mode 100644 index 0000000..c3c580c --- /dev/null +++ b/backend/models/__init__.py @@ -0,0 +1,2 @@ +from models.user import User +from models.user_stats import UserStats diff --git a/backend/requirements.txt b/backend/requirements.txt index 72de1d8..1056346 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -24,3 +24,4 @@ tomli==2.4.0 typing_extensions==4.15.0 Werkzeug==3.1.5 zipp==3.23.0 +psycopg2-binary diff --git a/backend/routes/auth_routes.py b/backend/routes/auth_routes.py index dc64c16..93ef065 100644 --- a/backend/routes/auth_routes.py +++ b/backend/routes/auth_routes.py @@ -1,11 +1,15 @@ +import logging +from extensions import db from flask import Blueprint, jsonify, request from models.user import User -from extensions import db +from models.user_stats import UserStats from werkzeug.security import generate_password_hash # Auth Blueprint auth_bp = Blueprint("auth", __name__) +logger = logging.getLogger(__name__) + @auth_bp.route("/register", methods=["POST"]) def register(): @@ -29,10 +33,22 @@ def register(): return jsonify({"error": "Email already registered"}), 409 # Validate and hash password - User.validate_password(password) + try: + User.validate_password(password) + except ValueError as ve: + logger.error("Password validation error: %s", ve) + return jsonify({"error": "Password does not meet criteria!"}), 400 + hashed_password = generate_password_hash(password, method="pbkdf2:sha256") - new_user = User(email=email, password=hashed_password, username=username) + # Create new user and user stats + try: + new_user = User(email=email, password=hashed_password, username=username) + new_user.stats = UserStats() + except Exception as e: + db.session.rollback() + logger.error("Error creating new user: %s", e) + return jsonify({"error": "Error creating newuser"}), 500 db.session.add(new_user) db.session.commit() From 2f6faea8948db07bb191735981b2c80bad45da0c Mon Sep 17 00:00:00 2001 From: Aleksandar Karastoyanov Date: Fri, 30 Jan 2026 13:46:45 +0200 Subject: [PATCH 09/17] lint: Fixed Python lint errors Signed-off-by: Aleksandar Karastoyanov --- backend/models/__init__.py | 2 -- backend/routes/auth_routes.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 backend/models/__init__.py diff --git a/backend/models/__init__.py b/backend/models/__init__.py deleted file mode 100644 index c3c580c..0000000 --- a/backend/models/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from models.user import User -from models.user_stats import UserStats diff --git a/backend/routes/auth_routes.py b/backend/routes/auth_routes.py index 93ef065..6c98fb2 100644 --- a/backend/routes/auth_routes.py +++ b/backend/routes/auth_routes.py @@ -38,7 +38,7 @@ def register(): except ValueError as ve: logger.error("Password validation error: %s", ve) return jsonify({"error": "Password does not meet criteria!"}), 400 - + hashed_password = generate_password_hash(password, method="pbkdf2:sha256") # Create new user and user stats From f32f5cee902fabe2df3f0f6f3d7158485b51900c Mon Sep 17 00:00:00 2001 From: Aleksandar Karastoyanov Date: Fri, 30 Jan 2026 13:49:27 +0200 Subject: [PATCH 10/17] Remove unnecessary imports Signed-off-by: Aleksandar Karastoyanov --- backend/app.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/app.py b/backend/app.py index d4567e1..6c682ee 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,8 +1,6 @@ from config import Config from extensions import db, jwt, migrate from flask import Flask -from models import * - def create_app(): app = Flask(__name__) From 769e758c7c016b7c56ae6098d54fa0e8383e4729 Mon Sep 17 00:00:00 2001 From: Aleksandar Karastoyanov Date: Fri, 30 Jan 2026 13:50:20 +0200 Subject: [PATCH 11/17] lint: Fix Python lint errors Signed-off-by: Aleksandar Karastoyanov --- backend/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/app.py b/backend/app.py index 6c682ee..a32ca96 100644 --- a/backend/app.py +++ b/backend/app.py @@ -2,6 +2,7 @@ from extensions import db, jwt, migrate from flask import Flask + def create_app(): app = Flask(__name__) app.config.from_object(Config) From 0815342e4a46746cdced38ae2c3f77ca99b8f5c3 Mon Sep 17 00:00:00 2001 From: Aleksandar Karastoyanov Date: Fri, 30 Jan 2026 14:20:32 +0200 Subject: [PATCH 12/17] test: Implement Flask Pytest - modified dir structure - add pytest and flask-pytest libs - introduce backend test with dummy data Signed-off-by: Aleksandar Karastoyanov --- backend/__init__.py | 0 backend/app.py | 6 ++--- backend/models/user.py | 2 +- backend/models/user_stats.py | 2 +- backend/requirements.txt | 8 +++++- backend/routes/auth_routes.py | 6 ++--- backend/tests/__init__.py | 0 backend/tests/conftest.py | 28 ++++++++++++++++++++ backend/tests/data/users.json | 17 ++++++++++++ backend/tests/test_auth_register.py | 40 +++++++++++++++++++++++++++++ 10 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 backend/__init__.py create mode 100644 backend/tests/__init__.py create mode 100644 backend/tests/conftest.py create mode 100644 backend/tests/data/users.json create mode 100644 backend/tests/test_auth_register.py diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app.py b/backend/app.py index a32ca96..3b68f5a 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,5 +1,5 @@ -from config import Config -from extensions import db, jwt, migrate +from backend.config import Config +from backend.extensions import db, jwt, migrate from flask import Flask @@ -11,7 +11,7 @@ def create_app(): jwt.init_app(app) migrate.init_app(app, db) - from routes.auth_routes import auth_bp + from backend.routes.auth_routes import auth_bp app.register_blueprint(auth_bp) diff --git a/backend/models/user.py b/backend/models/user.py index cb96997..014f866 100644 --- a/backend/models/user.py +++ b/backend/models/user.py @@ -1,7 +1,7 @@ import string import uuid -from extensions import db +from backend.extensions import db from werkzeug.security import check_password_hash, generate_password_hash diff --git a/backend/models/user_stats.py b/backend/models/user_stats.py index 9391406..80a7cf3 100644 --- a/backend/models/user_stats.py +++ b/backend/models/user_stats.py @@ -1,4 +1,4 @@ -from extensions import db +from backend.extensions import db class UserStats(db.Model): diff --git a/backend/requirements.txt b/backend/requirements.txt index 1056346..3e91890 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -3,11 +3,13 @@ black==25.11.0 blinker==1.9.0 click==8.1.8 colorama==0.4.6 +exceptiongroup==1.3.1 Flask==3.1.2 Flask-JWT-Extended==4.7.1 Flask-Migrate==4.1.0 Flask-SQLAlchemy==3.1.1 importlib_metadata==8.7.1 +iniconfig==2.1.0 itsdangerous==2.2.0 Jinja2==3.1.6 Mako==1.3.10 @@ -16,7 +18,12 @@ mypy_extensions==1.1.0 packaging==26.0 pathspec==1.0.4 platformdirs==4.4.0 +pluggy==1.6.0 +psycopg2-binary==2.9.11 +Pygments==2.19.2 PyJWT==2.10.1 +pytest==8.4.2 +pytest-flask==1.3.0 python-dotenv==1.2.1 pytokens==0.4.1 SQLAlchemy==2.0.46 @@ -24,4 +31,3 @@ tomli==2.4.0 typing_extensions==4.15.0 Werkzeug==3.1.5 zipp==3.23.0 -psycopg2-binary diff --git a/backend/routes/auth_routes.py b/backend/routes/auth_routes.py index 6c98fb2..a41ecd4 100644 --- a/backend/routes/auth_routes.py +++ b/backend/routes/auth_routes.py @@ -1,8 +1,8 @@ import logging -from extensions import db +from backend.extensions import db from flask import Blueprint, jsonify, request -from models.user import User -from models.user_stats import UserStats +from backend.models.user import User +from backend.models.user_stats import UserStats from werkzeug.security import generate_password_hash # Auth Blueprint diff --git a/backend/tests/__init__.py b/backend/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py new file mode 100644 index 0000000..c682645 --- /dev/null +++ b/backend/tests/conftest.py @@ -0,0 +1,28 @@ +import json +import pytest +from backend.app import create_app +from backend.extensions import db + +@pytest.fixture(scope="session") +def test_data(): + with open("tests/data/users.json") as f: + return json.load(f) + +@pytest.fixture +def app(): + app = create_app() + app.config.update( + TESTING=True, + SQLALCHEMY_DATABASE_URI="sqlite:///:memory:", + SQLALCHEMY_TRACK_MODIFICATIONS=False, + ) + + with app.app_context(): + db.create_all() + yield app + db.session.remove() + db.drop_all() + +@pytest.fixture +def client(app): + return app.test_client() \ No newline at end of file diff --git a/backend/tests/data/users.json b/backend/tests/data/users.json new file mode 100644 index 0000000..10f9760 --- /dev/null +++ b/backend/tests/data/users.json @@ -0,0 +1,17 @@ +{ + "valid_user": { + "username": "testuser", + "email": "testuser@example.com", + "password": "StrongP@ssw0rd" + }, + "weak_password": { + "username": "weakuser", + "email": "weak@example.com", + "password": "weak" + }, + "missing_fields": { + "username": "", + "email": "missing@example.com", + "password": "" + } + } \ No newline at end of file diff --git a/backend/tests/test_auth_register.py b/backend/tests/test_auth_register.py new file mode 100644 index 0000000..6fe4655 --- /dev/null +++ b/backend/tests/test_auth_register.py @@ -0,0 +1,40 @@ +def test_register_success(client, test_data): + res = client.post( + "/register", + json=test_data["valid_user"] + ) + + assert res.status_code == 201 + data = res.get_json() + assert "User registered successfully" in data["msg"] + + +def test_register_weak_password(client, test_data): + res = client.post( + "/register", + json=test_data["weak_password"] + ) + + assert res.status_code == 400 + data = res.get_json() + assert "password" in data["error"].lower() + + +def test_register_missing_fields(client, test_data): + res = client.post( + "/register", + json=test_data["missing_fields"] + ) + + assert res.status_code == 400 + + +def test_register_duplicate_username(client, test_data): + client.post("/register", json=test_data["valid_user"]) + + res = client.post("/register", json={ + **test_data["valid_user"], + "email": "another@example.com" + }) + + assert res.status_code == 409 \ No newline at end of file From 3c7663789f9c11fe406102aaa14fc6bda1d47517 Mon Sep 17 00:00:00 2001 From: Aleksandar Karastoyanov Date: Fri, 30 Jan 2026 14:23:24 +0200 Subject: [PATCH 13/17] test: Added GitHub workflow for Pytest Signed-off-by: Aleksandar Karastoyanov --- .github/workflows/run-tests.yml | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/run-tests.yml diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..555ce89 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,34 @@ +name: Run Flask Backend Tests + +# Trigger on PRs to master +on: + pull_request: + branches: + - master + +jobs: + tests: + runs-on: ubuntu-latest + + steps: + # Step 1: Checkout repo + - name: Checkout code + uses: actions/checkout@v4 + + # Step 2: Set up Python + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + # Step 3: Install dependencies + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r backend/requirements.txt + + # Step 4: Run tests + - name: Run pytest + working-directory: backend + run: | + pytest -v \ No newline at end of file From 4ae73b66cc79739d36098fc2d97f79f52d99909a Mon Sep 17 00:00:00 2001 From: Aleksandar Karastoyanov Date: Fri, 30 Jan 2026 14:34:45 +0200 Subject: [PATCH 14/17] fix: Fix requirements.txt file Signed-off-by: Aleksandar Karastoyanov --- backend/requirements.txt | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/backend/requirements.txt b/backend/requirements.txt index e69de29..3e91890 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -0,0 +1,33 @@ +alembic==1.16.5 +black==25.11.0 +blinker==1.9.0 +click==8.1.8 +colorama==0.4.6 +exceptiongroup==1.3.1 +Flask==3.1.2 +Flask-JWT-Extended==4.7.1 +Flask-Migrate==4.1.0 +Flask-SQLAlchemy==3.1.1 +importlib_metadata==8.7.1 +iniconfig==2.1.0 +itsdangerous==2.2.0 +Jinja2==3.1.6 +Mako==1.3.10 +MarkupSafe==3.0.3 +mypy_extensions==1.1.0 +packaging==26.0 +pathspec==1.0.4 +platformdirs==4.4.0 +pluggy==1.6.0 +psycopg2-binary==2.9.11 +Pygments==2.19.2 +PyJWT==2.10.1 +pytest==8.4.2 +pytest-flask==1.3.0 +python-dotenv==1.2.1 +pytokens==0.4.1 +SQLAlchemy==2.0.46 +tomli==2.4.0 +typing_extensions==4.15.0 +Werkzeug==3.1.5 +zipp==3.23.0 From 327d59352f3b197059310608972f5992726532f9 Mon Sep 17 00:00:00 2001 From: Aleksandar Karastoyanov Date: Fri, 30 Jan 2026 14:36:50 +0200 Subject: [PATCH 15/17] lint: Fix Python Lint errors Signed-off-by: Aleksandar Karastoyanov --- backend/tests/conftest.py | 5 ++++- backend/tests/test_auth_register.py | 24 +++++++----------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index c682645..3de2bd4 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -3,11 +3,13 @@ from backend.app import create_app from backend.extensions import db + @pytest.fixture(scope="session") def test_data(): with open("tests/data/users.json") as f: return json.load(f) + @pytest.fixture def app(): app = create_app() @@ -23,6 +25,7 @@ def app(): db.session.remove() db.drop_all() + @pytest.fixture def client(app): - return app.test_client() \ No newline at end of file + return app.test_client() diff --git a/backend/tests/test_auth_register.py b/backend/tests/test_auth_register.py index 6fe4655..d8108ae 100644 --- a/backend/tests/test_auth_register.py +++ b/backend/tests/test_auth_register.py @@ -1,8 +1,5 @@ def test_register_success(client, test_data): - res = client.post( - "/register", - json=test_data["valid_user"] - ) + res = client.post("/register", json=test_data["valid_user"]) assert res.status_code == 201 data = res.get_json() @@ -10,10 +7,7 @@ def test_register_success(client, test_data): def test_register_weak_password(client, test_data): - res = client.post( - "/register", - json=test_data["weak_password"] - ) + res = client.post("/register", json=test_data["weak_password"]) assert res.status_code == 400 data = res.get_json() @@ -21,10 +15,7 @@ def test_register_weak_password(client, test_data): def test_register_missing_fields(client, test_data): - res = client.post( - "/register", - json=test_data["missing_fields"] - ) + res = client.post("/register", json=test_data["missing_fields"]) assert res.status_code == 400 @@ -32,9 +23,8 @@ def test_register_missing_fields(client, test_data): def test_register_duplicate_username(client, test_data): client.post("/register", json=test_data["valid_user"]) - res = client.post("/register", json={ - **test_data["valid_user"], - "email": "another@example.com" - }) + res = client.post( + "/register", json={**test_data["valid_user"], "email": "another@example.com"} + ) - assert res.status_code == 409 \ No newline at end of file + assert res.status_code == 409 From 2f52bcd7ee81099208deb01b2fab9c5e4ec2068c Mon Sep 17 00:00:00 2001 From: Aleksandar Karastoyanov Date: Fri, 30 Jan 2026 14:41:18 +0200 Subject: [PATCH 16/17] fix: Fix Pytest configs in app.py Signed-off-by: Aleksandar Karastoyanov --- backend/app.py | 9 ++++++++- backend/tests/conftest.py | 11 +++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/backend/app.py b/backend/app.py index 3b68f5a..1136f86 100644 --- a/backend/app.py +++ b/backend/app.py @@ -3,9 +3,16 @@ from flask import Flask -def create_app(): +def create_app(config_object=None): app = Flask(__name__) + + # Default config app.config.from_object(Config) + + # Override config (for tests) + if config_object: + app.config.update(config_object) + db.init_app(app) jwt.init_app(app) diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 3de2bd4..a3b5f05 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -12,12 +12,11 @@ def test_data(): @pytest.fixture def app(): - app = create_app() - app.config.update( - TESTING=True, - SQLALCHEMY_DATABASE_URI="sqlite:///:memory:", - SQLALCHEMY_TRACK_MODIFICATIONS=False, - ) + app = create_app({ + "TESTING": True, + "SQLALCHEMY_DATABASE_URI": "sqlite:///:memory:", + "SQLALCHEMY_TRACK_MODIFICATIONS": False, + }) with app.app_context(): db.create_all() From e99505e228f92adf46f597542a7b81097302c236 Mon Sep 17 00:00:00 2001 From: Aleksandar Karastoyanov Date: Fri, 30 Jan 2026 14:42:58 +0200 Subject: [PATCH 17/17] lint: Fixed Python lint errors Signed-off-by: Aleksandar Karastoyanov --- backend/app.py | 5 ++--- backend/tests/conftest.py | 12 +++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/backend/app.py b/backend/app.py index 1136f86..6a2ef72 100644 --- a/backend/app.py +++ b/backend/app.py @@ -5,15 +5,14 @@ def create_app(config_object=None): app = Flask(__name__) - + # Default config app.config.from_object(Config) - + # Override config (for tests) if config_object: app.config.update(config_object) - db.init_app(app) jwt.init_app(app) migrate.init_app(app, db) diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index a3b5f05..ab5c239 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -12,11 +12,13 @@ def test_data(): @pytest.fixture def app(): - app = create_app({ - "TESTING": True, - "SQLALCHEMY_DATABASE_URI": "sqlite:///:memory:", - "SQLALCHEMY_TRACK_MODIFICATIONS": False, - }) + app = create_app( + { + "TESTING": True, + "SQLALCHEMY_DATABASE_URI": "sqlite:///:memory:", + "SQLALCHEMY_TRACK_MODIFICATIONS": False, + } + ) with app.app_context(): db.create_all()