Skip to content

Commit

Permalink
feat: Implement first test in a module
Browse files Browse the repository at this point in the history
  • Loading branch information
drorganvidez committed Mar 25, 2024
1 parent 809fdc6 commit 4850bc9
Show file tree
Hide file tree
Showing 14 changed files with 216 additions and 34 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ jobs:
export MYSQL_DATABASE=fmlibdb
export MYSQL_USER=fmlibuser
export MYSQL_PASSWORD=fmlibpass
pytest app/tests/units.py
pytest app/blueprints/
9 changes: 6 additions & 3 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ FROM python:3.11-alpine
# Set this environment variable to suppress the "Running as root" warning from pip
ENV PIP_ROOT_USER_ACTION=ignore

# Install the MySQL client to be able to use it in the standby script.
RUN apk add --no-cache mysql-client
# Install the MariaDB client to be able to use it in the standby script.
RUN apk add --no-cache mariadb-client

# Set the working directory in the container to /app
WORKDIR /app
Expand All @@ -19,6 +19,9 @@ COPY requirements.txt .
# Copy the wait-for-db.sh script and set execution permissions
COPY --chmod=+x scripts/wait-for-db.sh ./scripts/

# Copy the init-db.sh script and set execution permissions
COPY --chmod=+x scripts/init-db.sh ./scripts/

# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

Expand All @@ -32,4 +35,4 @@ RUN pip install --no-cache-dir --upgrade pip
EXPOSE 5000

# Sets the CMD command to correctly execute the wait-for-db.sh script
CMD sh ./scripts/wait-for-db.sh && flask db upgrade && flask run --host=0.0.0.0 --port=5000 --reload --debug
CMD sh ./scripts/wait-for-db.sh && sh ./scripts/init-db.sh && flask db upgrade && flask run --host=0.0.0.0 --port=5000 --reload --debug
5 changes: 5 additions & 0 deletions Dockerfile.mariadb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM mariadb:latest

RUN apt-get update && \
apt-get install -y mariadb-client && \
rm -rf /var/lib/apt/lists/*
52 changes: 39 additions & 13 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,61 @@
migrate = Migrate()


def create_app(config_name=None):
app = Flask(__name__)
app.secret_key = secrets.token_bytes()

# Database configuration
app.config['SQLALCHEMY_DATABASE_URI'] = (
class Config:
SECRET_KEY = os.getenv('SECRET_KEY', secrets.token_bytes())
SQLALCHEMY_DATABASE_URI = (
f"mysql+pymysql://{os.getenv('MARIADB_USER', 'default_user')}:"
f"{os.getenv('MARIADB_PASSWORD', 'default_password')}@"
f"{os.getenv('MARIADB_HOSTNAME', 'localhost')}:"
f"{os.getenv('MARIADB_PORT', '3306')}/"
f"{os.getenv('MARIADB_DATABASE', 'default_db')}"
)
SQLALCHEMY_TRACK_MODIFICATIONS = False
TIMEZONE = 'Europe/Madrid'
TEMPLATES_AUTO_RELOAD = True


class DevelopmentConfig(Config):
DEBUG = True


class TestingConfig(Config):
TESTING = True
SECRET_KEY = os.getenv('SECRET_KEY', 'secret_test_key')
SQLALCHEMY_DATABASE_URI = (
f"mysql+pymysql://{os.getenv('MARIADB_USER', 'default_user')}:"
f"{os.getenv('MARIADB_PASSWORD', 'default_password')}@"
f"{os.getenv('MARIADB_HOSTNAME', 'localhost')}:"
f"{os.getenv('MARIADB_PORT', '3306')}/"
f"{os.getenv('MARIADB_TEST_DATABASE', 'default_db')}"
)
UPLOAD_FOLDER = 'uploads'
WTF_CSRF_ENABLED = False

# Timezone
app.config['TIMEZONE'] = 'Europe/Madrid'

# Templates configuration
app.config['TEMPLATES_AUTO_RELOAD'] = True
class ProductionConfig(Config):
pass


def create_app(config_name='development'):
app = Flask(__name__)

# Uploads feature models configuration
app.config['UPLOAD_FOLDER'] = os.path.join(app.root_path, 'uploads')
# Load configuration
if config_name == 'testing':
app.config.from_object(TestingConfig)
elif config_name == 'production':
app.config.from_object(ProductionConfig)
else:
app.config.from_object(DevelopmentConfig)

# Initialize SQLAlchemy and Migrate with the app
db.init_app(app)
migrate.init_app(app, db)

# Register blueprints
register_blueprints(app)
print_registered_blueprints(app)
if config_name == 'development':
print_registered_blueprints(app)

from flask_login import LoginManager
login_manager = LoginManager()
Expand Down
Empty file added app/blueprints/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions app/blueprints/auth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ class User(db.Model, UserMixin):
data_sets = db.relationship('DataSet', backref='user', lazy=True)
profile = db.relationship('UserProfile', backref='user', uselist=False)

def __init__(self, **kwargs):
super(User, self).__init__(**kwargs)
if 'password' in kwargs:
self.set_password(kwargs['password'])

def __repr__(self):
return f'<User {self.email}>'

Expand Down
22 changes: 11 additions & 11 deletions app/blueprints/auth/routes.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from flask import (render_template, redirect, url_for)
from flask import (render_template, redirect, url_for, flash, request)
from flask_login import current_user, login_user, logout_user

from app.blueprints.auth import auth_bp
from app.blueprints.auth.forms import SignupForm, LoginForm
from app.blueprints.auth.services import AuthenticationService

from app.blueprints.profile.models import UserProfile

Expand Down Expand Up @@ -45,16 +46,15 @@ def login():
if current_user.is_authenticated:
return redirect(url_for('public.index'))
form = LoginForm()
if form.validate_on_submit():
from app.blueprints.auth.models import User
user = User.get_by_email(form.email.data)

if user is not None and user.check_password(form.password.data):
login_user(user, remember=form.remember_me.data)
return redirect(url_for('public.index'))
else:
error = 'Invalid credentials'
return render_template("auth/login_form.html", form=form, error=error)
if request.method == 'POST':
if form.validate_on_submit():
email = form.email.data
password = form.password.data
if AuthenticationService.login(email, password):
return redirect(url_for('public.index'))
else:
error = 'Invalid credentials'
return render_template("auth/login_form.html", form=form, error=error)

return render_template('auth/login_form.html', form=form)

Expand Down
14 changes: 14 additions & 0 deletions app/blueprints/auth/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from flask_login import login_user

from app.blueprints.auth.models import User


class AuthenticationService:

@staticmethod
def login(email, password, remember=True):
user = User.get_by_email(email)
if user is not None and user.check_password(password):
login_user(user, remember=remember)
return True
return False
Empty file.
102 changes: 102 additions & 0 deletions app/blueprints/profile/tests/test_unit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import pytest
from flask import url_for
from app import create_app, db
from app.blueprints.auth.models import User


@pytest.fixture(scope='module')
def test_client():
flask_app = create_app('testing')

with flask_app.test_client() as testing_client:
with flask_app.app_context():
db.create_all()

user_test = User(email='[email protected]')
user_test.set_password('test1234')
db.session.add(user_test)
db.session.commit()

yield testing_client

db.session.remove()
db.drop_all()


def login(test_client, email, password):
"""
Authenticates the user with the credentials provided.
Args:
test_client: Flask test client.
email (str): User's email address.
password (str): User's password.
Returns:
response: POST login request response.
"""
response = test_client.post('/login', data=dict(
email=email,
password=password
), follow_redirects=True)
return response


def logout(test_client):
"""
Logs out the user.
Args:
test_client: Flask test client.
Returns:
response: Response to GET request to log out.
"""
return test_client.get('/logout', follow_redirects=True)


def test_login_success(test_client):
response = test_client.post('/login', data=dict(
email='[email protected]',
password='test1234'
), follow_redirects=True)

assert response.request.path != url_for('auth.login'), "Login was unsuccessful"

test_client.get('/logout', follow_redirects=True)


def test_login_unsuccessful_bad_email(test_client):
response = test_client.post('/login', data=dict(
email='[email protected]',
password='test1234'
), follow_redirects=True)

assert response.request.path == url_for('auth.login'), "Login was unsuccessful"

test_client.get('/logout', follow_redirects=True)


def test_login_unsuccessful_bad_password(test_client):
response = test_client.post('/login', data=dict(
email='[email protected]',
password='basspassword'
), follow_redirects=True)

assert response.request.path == url_for('auth.login'), "Login was unsuccessful"

test_client.get('/logout', follow_redirects=True)


def test_edit_profile_page_get(test_client):
"""
Tests access to the profile editing page via a GET request.
"""
login_response = login(test_client, '[email protected]', 'test1234')
assert login_response.status_code == 200, "Login was unsuccessful."

response = test_client.get('/profile/edit')
assert response.status_code == 200, "The profile editing page could not be accessed."
assert b"Edit profile" in response.data, "The expected content is not present on the page"

logout(test_client)
9 changes: 9 additions & 0 deletions app/blueprints/public/routes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import logging

from flask_login import login_required

import app

from flask import render_template
Expand All @@ -23,3 +26,9 @@ def index():
datasets=latest_datasets,
datasets_counter=datasets_counter,
feature_models_counter=feature_models_counter)


@public_bp.route('/secret')
@login_required
def secret():
return "Esto es secreto!"
16 changes: 12 additions & 4 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,28 @@ services:
context: .
dockerfile: Dockerfile.dev
volumes:
- .:/app
- type: bind
source: .
target: /app
expose:
- "5000"
environment:
FLASK_ENV: development
MARIADB_HOSTNAME: ${MARIADB_HOSTNAME}
MARIADB_DATABASE: ${MARIADB_DATABASE}
MARIADB_TEST_DATABASE: ${MARIADB_TEST_DATABASE}
MARIADB_PORT: ${MARIADB_PORT}
MARIADB_USER: ${MARIADB_USER}
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
depends_on:
- db

db:
container_name: mariadb_container
image: mariadb:latest
command: --default-authentication-plugin=mysql_native_password
build:
context: ./
dockerfile: Dockerfile.mariadb
restart: always
environment:
MARIADB_DATABASE: ${MARIADB_DATABASE}
Expand All @@ -39,7 +45,9 @@ services:
container_name: nginx_web_server
image: nginx:latest
volumes:
- ./nginx/nginx.dev.conf:/etc/nginx/nginx.conf
- type: bind
source: ./nginx/nginx.dev.conf
target: /etc/nginx/nginx.conf
ports:
- "80:80"
depends_on:
Expand Down
11 changes: 11 additions & 0 deletions scripts/init-db.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

echo "(testing) Hostname: $MARIADB_HOSTNAME, Port: $MARIADB_PORT, User: $MARIADB_USER, Test DB: $MARIADB_TEST_DATABASE"

echo "MariaDB is up - creating test database if it doesn't exist"

# Create the test database if it does not exist
mariadb -h "$MARIADB_HOSTNAME" -P "$MARIADB_PORT" -u root -p"$MARIADB_ROOT_PASSWORD" -e "CREATE DATABASE IF NOT EXISTS \`${MARIADB_TEST_DATABASE}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; GRANT ALL PRIVILEGES ON \`${MARIADB_TEST_DATABASE}\`.* TO '$MARIADB_USER'@'%'; FLUSH PRIVILEGES;"


echo "Test database created and privileges granted"
3 changes: 1 addition & 2 deletions scripts/wait-for-db.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

echo "Hostname: $MARIADB_HOSTNAME, Port: $MARIADB_PORT, User: $MARIADB_USER"

until mysql -h "$MARIADB_HOSTNAME" -P "$MARIADB_PORT" -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" -e 'SELECT 1' &> /dev/null
until mariadb -h "$MARIADB_HOSTNAME" -P "$MARIADB_PORT" -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" -e 'SELECT 1' &> /dev/null
do
echo "MariaDB is unavailable - sleeping"
sleep 1
done

echo "MariaDB is up - executing command"
exec "$@"

0 comments on commit 4850bc9

Please sign in to comment.