Add unit tests for LoginButton component with comprehensive coverage
All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 46s

This commit is contained in:
2026-02-05 16:37:10 -05:00
parent fd70eca0c9
commit 47541afbbf
47 changed files with 1179 additions and 824 deletions

View File

@@ -3,9 +3,9 @@ import secrets, jwt
from datetime import datetime, timedelta, timezone
from models.user import User
from flask import Blueprint, request, jsonify, current_app
from utils.email_instance import email_sender
from tinydb import Query
import os
import utils.email_sender as email_sender
from api.utils import sanitize_email
from config.paths import get_user_image_dir

View File

@@ -101,7 +101,10 @@ def request_image(id):
if image.user_id is not None and image.user_id != user_id:
return jsonify({'error': 'Forbidden: image does not belong to user', 'code': 'FORBIDDEN'}), 403
filename = f"{image.id}{image.extension}"
filepath = os.path.abspath(os.path.join(get_user_image_dir(image.user_id or user_id), filename))
if image.user_id is None:
filepath = os.path.abspath(os.path.join(get_user_image_dir("default"), filename))
else:
filepath = os.path.abspath(os.path.join(get_user_image_dir(image.user_id), filename))
if not os.path.exists(filepath):
return jsonify({'error': 'File not found'}), 404
return send_file(filepath)

View File

@@ -5,8 +5,7 @@ from db.db import users_db
import jwt
import random
import string
import smtplib
from backend.utils.email_instance import email_sender
import utils.email_sender as email_sender
from datetime import datetime, timedelta
from api.utils import get_validated_user_id

View File

@@ -2,6 +2,8 @@
# File: db/debug.py
from tinydb import Query
import os
import shutil
from api.image_api import IMAGE_TYPE_ICON, IMAGE_TYPE_PROFILE
from db.db import task_db, reward_db, image_db
@@ -119,7 +121,22 @@ def createDefaultRewards():
reward_db.insert(reward.to_dict())
def initializeImages():
"""Initialize the image database with default images if empty."""
"""Initialize the image database with default images if empty, and copy images to data/images/default."""
# Step 1: Create data/images/default directory if it doesn't exist
default_img_dir = os.path.join(os.path.dirname(__file__), '../data/images/default')
os.makedirs(default_img_dir, exist_ok=True)
# Step 2: Copy all image files from resources/images/ to data/images/default
src_img_dir = os.path.join(os.path.dirname(__file__), '../resources/images')
if os.path.exists(src_img_dir):
for fname in os.listdir(src_img_dir):
src_path = os.path.join(src_img_dir, fname)
dst_path = os.path.join(default_img_dir, fname)
if os.path.isfile(src_path):
shutil.copy2(src_path, dst_path)
# Original DB initialization logic
if len(image_db.all()) == 0:
image_defs = [
('boy01', IMAGE_TYPE_PROFILE, '.png', True),

View File

@@ -12,7 +12,6 @@ from api.task_api import task_api
from api.user_api import user_api
from config.version import get_full_version
from backend.utils.email_instance import email_sender
from db.default import initializeImages, createDefaultTasks, createDefaultRewards
from events.broadcaster import Broadcaster
from events.sse import sse_response_for_user, send_to_user
@@ -49,8 +48,6 @@ app.config.update(
CORS(app)
email_sender.init_app(app)
@app.route("/version")
def api_version():
return jsonify({"version": get_full_version()})

View File

@@ -1,3 +0,0 @@
from utils.email_sender import EmailSender
email_sender = EmailSender()

View File

@@ -1,65 +1,56 @@
import os
from flask import current_app
from flask_mail import Mail, Message
import smtplib
from email.mime.text import MIMEText
class EmailSender:
def __init__(self, app=None):
self.mail = None
if app is not None:
self.init_app(app)
def send_verification_email(to_email: str, token: str) -> None:
verify_url = f"{current_app.config['FRONTEND_URL']}/auth/verify?token={token}"
html_body = f'Click <a href="{verify_url}">here</a> to verify your account.'
msg = Message(
subject="Verify your account",
recipients=[to_email],
html=html_body,
sender=current_app.config.get('MAIL_DEFAULT_SENDER', 'no-reply@reward-app.local')
)
try:
Mail(current_app).send(msg)
print(f"[EMAIL to {to_email}] Verification: {verify_url}")
except Exception:
print(f"Failed to send email to {to_email}. Verification link: {verify_url}")
def init_app(self, app):
self.mail = Mail(app)
def send_reset_password_email(to_email: str, token: str) -> None:
reset_url = f"{current_app.config['FRONTEND_URL']}/auth/reset-password?token={token}"
html_body = f'Click <a href="{reset_url}">here</a> to reset your password.'
msg = Message(
subject="Reset your password",
recipients=[to_email],
html=html_body,
sender=current_app.config.get('MAIL_DEFAULT_SENDER', 'no-reply@reward-app.local')
)
try:
Mail(current_app).send(msg)
print(f"[EMAIL to {to_email}] Reset password: {reset_url}")
except Exception:
print(f"Failed to send email to {to_email}. Reset link: {reset_url}")
def send_verification_email(self, to_email, token):
verify_url = f"{current_app.config['FRONTEND_URL']}/auth/verify?token={token}"
html_body = f'Click <a href="{verify_url}">here</a> to verify your account.'
msg = Message(
subject="Verify your account",
recipients=[to_email],
html=html_body,
sender=current_app.config.get('MAIL_DEFAULT_SENDER', 'no-reply@reward-app.local')
)
if self.mail:
self.mail.send(msg)
else:
print(f"[EMAIL to {to_email}] Verification: {verify_url}")
def send_reset_password_email(self, to_email, token):
reset_url = f"{current_app.config['FRONTEND_URL']}/auth/reset-password?token={token}"
html_body = f'Click <a href="{reset_url}">here</a> to reset your password.'
msg = Message(
subject="Reset your password",
recipients=[to_email],
html=html_body,
sender=current_app.config.get('MAIL_DEFAULT_SENDER', 'no-reply@reward-app.local')
)
if self.mail:
self.mail.send(msg)
else:
print(f"[EMAIL to {to_email}] Reset password: {reset_url}")
def send_pin_setup_email(self, to_email, code):
html_body = f"""
<div style='font-family:sans-serif;'>
<h2>Set up your Parent PIN</h2>
<p>To set your Parent PIN, enter the following code in the app:</p>
<div style='font-size:2rem; font-weight:bold; letter-spacing:0.2em; margin:1.5rem 0;'>{code}</div>
<p>This code is valid for 10 minutes.</p>
<p>If you did not request this, you can ignore this email.</p>
<hr>
<div style='color:#888;font-size:0.95rem;'>Reward App</div>
</div>
"""
msg = Message(
subject="Set up your Parent PIN",
recipients=[to_email],
html=html_body,
sender=current_app.config.get('MAIL_DEFAULT_SENDER', 'no-reply@reward-app.local')
)
if self.mail:
self.mail.send(msg)
else:
print(f"[EMAIL to {to_email}] Parent PIN setup code: {code}")
def send_pin_setup_email(to_email: str, code: str) -> None:
html_body = f"""
<div style='font-family:sans-serif;'>
<h2>Set up your Parent PIN</h2>
<p>To set your Parent PIN, enter the following code in the app:</p>
<div style='font-size:2rem; font-weight:bold; letter-spacing:0.2em; margin:1.5rem 0;'>{code}</div>
<p>This code is valid for 10 minutes.</p>
<p>If you did not request this, you can ignore this email.</p>
<hr>
<div style='color:#888;font-size:0.95rem;'>Reward App</div>
</div>
"""
msg = Message(
subject="Set up your Parent PIN",
recipients=[to_email],
html=html_body,
sender=current_app.config.get('MAIL_DEFAULT_SENDER', 'no-reply@reward-app.local')
)
try:
Mail(current_app).send(msg)
print(f"[EMAIL to {to_email}] Parent PIN setup code: {code}")
except Exception:
print(f"Failed to send email to {to_email}. Parent PIN setup code: {code}")