Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USERNAME=your_username_here
SMTP_PASSWORD=your_password_here
SMTP_FROM=no-reply@example.com
SMTP_TO=recipient@example.com
SMTP_USE_TLS=false
SMTP_USE_SSL=false
15 changes: 15 additions & 0 deletions core/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import os


def _env_bool(name: str, default: str = "false") -> bool:
return os.getenv(name, default).strip().lower() in {"1", "true", "yes", "on"}


SMTP_HOST = os.getenv("SMTP_HOST", "localhost")
SMTP_PORT = int(os.getenv("SMTP_PORT", "25"))
SMTP_USERNAME = os.getenv("SMTP_USERNAME", "")
SMTP_PASSWORD = os.getenv("SMTP_PASSWORD", "")
SMTP_FROM = os.getenv("SMTP_FROM", "")
SMTP_TO = os.getenv("SMTP_TO", "")
SMTP_USE_TLS = _env_bool("SMTP_USE_TLS")
SMTP_USE_SSL = _env_bool("SMTP_USE_SSL")
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
pythonpath = .
13 changes: 13 additions & 0 deletions routers/submit.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from models.dto import ExecAppFormDTO
from models.request import ExecFormRequest
from models.response import ResponseDTO
from services.smtp_email_sender import send_application_email

router = APIRouter(prefix="/api/submit", tags=["submit"])

Expand Down Expand Up @@ -106,6 +107,17 @@ async def receive_exec_form(form_dto: ExecAppFormDTO):
processed_questions,
)

send_application_email(
full_name=exec_form_request.fullName,
sfu_email=exec_form_request.email,
student_id=exec_form_request.studentId,
year_of_study=exec_form_request.yearOfStudy,
faculty=exec_form_request.faculty,
major=exec_form_request.major,
co_op=exec_form_request.isCoop,
team_type=team_type_str,
)

return ResponseDTO(
message="Submission received successfully",
content=exec_form_request.model_dump(mode="json"),
Expand All @@ -115,3 +127,4 @@ async def receive_exec_form(form_dto: ExecAppFormDTO):
@router.post("/project-proposal")
async def receive_project_proposal_form(payload: Dict[str, Any]):
return JSONResponse(status_code=400, content="Under Construction")

Empty file added services/__init__.py
Empty file.
72 changes: 72 additions & 0 deletions services/smtp_email_sender.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from email.message import EmailMessage
import smtplib
from core.config import SMTP_FROM, SMTP_TO
from core.config import SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_USE_TLS, SMTP_USE_SSL


def build_email_message(
full_name: str,
sfu_email: str,
student_id: int,
year_of_study: int,
faculty: str,
major: str,
co_op: bool,
team_type: str,
) -> EmailMessage:
msg = EmailMessage()
msg["Subject"] = f"New Executive Application: {full_name} - {team_type}"
msg["From"] = SMTP_FROM
msg["To"] = SMTP_TO
msg["Reply-To"] = sfu_email

msg.set_content(f"""\
Full Name: {full_name}
SFU Email: {sfu_email}
Student ID: {student_id}
Year of Study: {year_of_study}
Major: {major}
Faculty: {faculty}
Co-op: {"Yes" if co_op else "No"}
Team Type: {team_type}
""")

return msg

def send_email_message(msg: EmailMessage) -> None:
if SMTP_USE_SSL:
with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT) as server:
if SMTP_USERNAME and SMTP_PASSWORD:
server.login(SMTP_USERNAME, SMTP_PASSWORD)
server.send_message(msg)
else:
with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
if SMTP_USE_TLS:
server.starttls()
if SMTP_USERNAME and SMTP_PASSWORD:
server.login(SMTP_USERNAME, SMTP_PASSWORD)
server.send_message(msg)

def send_application_email(
full_name: str,
sfu_email: str,
student_id: int,
year_of_study: int,
faculty: str,
major: str,
co_op: bool,
team_type: str,
) -> None:
msg = build_email_message(
full_name=full_name,
sfu_email=sfu_email,
student_id=student_id,
year_of_study=year_of_study,
faculty=faculty,
major=major,
co_op=co_op,
team_type=team_type
)
send_email_message(msg)


37 changes: 29 additions & 8 deletions tests/test_submit.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# import pytest
from unittest.mock import patch

from fastapi.testclient import TestClient

from main import app

client = TestClient(app)


def test_submit_exec_form_success():
@patch("routers.submit.send_application_email")
def test_submit_exec_form_success(mock_send_application_email):
# Test data for a successful submission
test_data = {
"fullName": "John Doe",
Expand All @@ -28,9 +30,20 @@ def test_submit_exec_form_success():
assert "message" in data
assert data["message"] == "Submission received successfully"
assert "content" in data


def test_submit_exec_form_invalid_student_id():
mock_send_application_email.assert_called_once_with(
full_name="John Doe",
sfu_email="john.doe@example.com",
student_id=12345678,
year_of_study=3,
faculty="Engineering",
major="Computer Science",
co_op=False,
team_type="EVENT",
)


@patch("routers.submit.send_application_email")
def test_submit_exec_form_invalid_student_id(mock_send_application_email):
# Test with invalid student ID
test_data = {
"fullName": "Jane Doe",
Expand All @@ -50,9 +63,11 @@ def test_submit_exec_form_invalid_student_id():
data = response.json()
assert "message" in data
assert "Invalid number format" in data["message"]
mock_send_application_email.assert_not_called()


def test_submit_exec_form_coop_term():
@patch("routers.submit.send_application_email")
def test_submit_exec_form_coop_term(mock_send_application_email):
# Test with coop term yes
test_data = {
"fullName": "Alice Smith",
Expand All @@ -72,9 +87,11 @@ def test_submit_exec_form_coop_term():
data = response.json()
assert "message" in data
assert "co-op term" in data["message"]
mock_send_application_email.assert_not_called()


def test_submit_exec_form_invalid_team():
@patch("routers.submit.send_application_email")
def test_submit_exec_form_invalid_team(mock_send_application_email):
# Test with invalid team type
test_data = {
"fullName": "Bob Johnson",
Expand All @@ -94,8 +111,11 @@ def test_submit_exec_form_invalid_team():
data = response.json()
assert "message" in data
assert "Invalid team type" in data["message"]
mock_send_application_email.assert_not_called()


def test_submit_exec_form_missing_fields():
@patch("routers.submit.send_application_email")
def test_submit_exec_form_missing_fields(mock_send_application_email):
# Test for when one component is missing
test_data = {
"fullName": "Charlie Brown",
Expand All @@ -114,3 +134,4 @@ def test_submit_exec_form_missing_fields():
data = response.json()
assert "message" in data
assert "Missing required fields" in data["message"]
mock_send_application_email.assert_not_called()
Loading