Spaces:
Sleeping
Sleeping
refactor
Browse files- app.py +2 -2
- auth_utils.py +0 -89
- database.py β core/database.py +0 -0
- models.py β core/models.py +2 -2
- schemas.py β core/schemas.py +0 -0
- core/security.py +25 -0
- dependencies.py +3 -3
- routers/auth.py +6 -5
- routers/blink.py +3 -3
- drive_service.py β services/drive_service.py +0 -0
- gmail_service.py β services/email_service.py +58 -4
- encryption.py β services/encryption_service.py +0 -0
- tests/conftest.py +1 -1
- test_email_env.py β tests/debug_email_env.py +2 -2
- test_gmail_service.py β tests/test_gmail_service.py +3 -3
- tests/test_integration.py +2 -2
app.py
CHANGED
|
@@ -11,9 +11,9 @@ from fastapi import FastAPI, Request
|
|
| 11 |
from fastapi.middleware.cors import CORSMiddleware
|
| 12 |
from fastapi.responses import JSONResponse
|
| 13 |
|
| 14 |
-
from database import init_db
|
| 15 |
from routers import auth, blink, general
|
| 16 |
-
from drive_service import DriveService
|
| 17 |
|
| 18 |
# Configure logging
|
| 19 |
logging.basicConfig(
|
|
|
|
| 11 |
from fastapi.middleware.cors import CORSMiddleware
|
| 12 |
from fastapi.responses import JSONResponse
|
| 13 |
|
| 14 |
+
from core.database import init_db
|
| 15 |
from routers import auth, blink, general
|
| 16 |
+
from services.drive_service import DriveService
|
| 17 |
|
| 18 |
# Configure logging
|
| 19 |
logging.basicConfig(
|
auth_utils.py
DELETED
|
@@ -1,89 +0,0 @@
|
|
| 1 |
-
import secrets
|
| 2 |
-
import smtplib
|
| 3 |
-
import ssl
|
| 4 |
-
from email.mime.text import MIMEText
|
| 5 |
-
from email.mime.multipart import MIMEMultipart
|
| 6 |
-
import bcrypt
|
| 7 |
-
import logging
|
| 8 |
-
import os
|
| 9 |
-
|
| 10 |
-
# Configure logging
|
| 11 |
-
logger = logging.getLogger(__name__)
|
| 12 |
-
|
| 13 |
-
# Email configuration
|
| 14 |
-
# Email configuration
|
| 15 |
-
SMTP_SERVER = os.getenv("SMTP_SERVER", "127.0.0.1")
|
| 16 |
-
SMTP_PORT = int(os.getenv("SMTP_PORT", "1025"))
|
| 17 |
-
# Prioritize EMAIL_ID and EMAIL_PASSWORD if available
|
| 18 |
-
SMTP_USERNAME = os.getenv("EMAIL_ID") or os.getenv("SMTP_USERNAME", "sender@domain.com")
|
| 19 |
-
SMTP_PASSWORD = os.getenv("EMAIL_PASSWORD") or os.getenv("SMTP_PASSWORD", "yourpassword")
|
| 20 |
-
|
| 21 |
-
# Auto-configure for Gmail if using defaults and gmail address
|
| 22 |
-
if SMTP_SERVER == "127.0.0.1" and "gmail.com" in SMTP_USERNAME:
|
| 23 |
-
SMTP_SERVER = "smtp.gmail.com"
|
| 24 |
-
SMTP_PORT = 465
|
| 25 |
-
|
| 26 |
-
SMTP_SENDER = os.getenv("SMTP_SENDER", SMTP_USERNAME)
|
| 27 |
-
|
| 28 |
-
from gmail_service import GmailService
|
| 29 |
-
|
| 30 |
-
# Initialize Gmail Service
|
| 31 |
-
gmail_service = GmailService()
|
| 32 |
-
|
| 33 |
-
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
| 34 |
-
"""
|
| 35 |
-
Verify a password against a hash.
|
| 36 |
-
"""
|
| 37 |
-
if isinstance(hashed_password, str):
|
| 38 |
-
hashed_password = hashed_password.encode('utf-8')
|
| 39 |
-
return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password)
|
| 40 |
-
|
| 41 |
-
def get_password_hash(password: str) -> str:
|
| 42 |
-
"""
|
| 43 |
-
Hash a password using bcrypt.
|
| 44 |
-
"""
|
| 45 |
-
# rounds=12 as per spec
|
| 46 |
-
salt = bcrypt.gensalt(rounds=12)
|
| 47 |
-
hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
|
| 48 |
-
return hashed.decode('utf-8')
|
| 49 |
-
|
| 50 |
-
def generate_secret_key() -> str:
|
| 51 |
-
"""
|
| 52 |
-
Generate a secure secret key starting with 'sk_'.
|
| 53 |
-
"""
|
| 54 |
-
return "sk_" + secrets.token_urlsafe(32)
|
| 55 |
-
|
| 56 |
-
def send_email(to_email: str, subject: str, body: str):
|
| 57 |
-
"""
|
| 58 |
-
Send an email using Gmail API.
|
| 59 |
-
This function is blocking and should be run in a background task.
|
| 60 |
-
"""
|
| 61 |
-
# Try Gmail API first
|
| 62 |
-
if gmail_service.authenticate():
|
| 63 |
-
return gmail_service.send_email(to_email, subject, body)
|
| 64 |
-
|
| 65 |
-
logger.warning("Gmail API credentials not found or invalid. Falling back to SMTP (may fail on some platforms).")
|
| 66 |
-
|
| 67 |
-
# Fallback to SMTP (Original Implementation)
|
| 68 |
-
try:
|
| 69 |
-
message = MIMEMultipart()
|
| 70 |
-
message["From"] = SMTP_SENDER
|
| 71 |
-
message["To"] = to_email
|
| 72 |
-
message["Subject"] = subject
|
| 73 |
-
|
| 74 |
-
message.attach(MIMEText(body, "plain"))
|
| 75 |
-
|
| 76 |
-
# Create a secure SSL context
|
| 77 |
-
context = ssl.create_default_context()
|
| 78 |
-
context.check_hostname = False
|
| 79 |
-
context.verify_mode = ssl.CERT_NONE
|
| 80 |
-
|
| 81 |
-
with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, context=context) as server:
|
| 82 |
-
server.login(SMTP_USERNAME, SMTP_PASSWORD)
|
| 83 |
-
server.sendmail(SMTP_SENDER, to_email, message.as_string())
|
| 84 |
-
|
| 85 |
-
logger.info(f"Email sent successfully to {to_email} via SMTP")
|
| 86 |
-
return True
|
| 87 |
-
except Exception as e:
|
| 88 |
-
logger.error(f"Failed to send email to {to_email}: {e}")
|
| 89 |
-
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
database.py β core/database.py
RENAMED
|
File without changes
|
models.py β core/models.py
RENAMED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
"""
|
| 2 |
-
SQLAlchemy models for the
|
| 3 |
"""
|
| 4 |
from sqlalchemy import Column, Integer, String, Text, DateTime, JSON, Boolean
|
| 5 |
from sqlalchemy.sql import func
|
| 6 |
-
from database import Base
|
| 7 |
|
| 8 |
|
| 9 |
class BlinkData(Base):
|
|
|
|
| 1 |
"""
|
| 2 |
+
SQLAlchemy models for the APIGateway application.
|
| 3 |
"""
|
| 4 |
from sqlalchemy import Column, Integer, String, Text, DateTime, JSON, Boolean
|
| 5 |
from sqlalchemy.sql import func
|
| 6 |
+
from core.database import Base
|
| 7 |
|
| 8 |
|
| 9 |
class BlinkData(Base):
|
schemas.py β core/schemas.py
RENAMED
|
File without changes
|
core/security.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import secrets
|
| 2 |
+
import bcrypt
|
| 3 |
+
|
| 4 |
+
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
| 5 |
+
"""
|
| 6 |
+
Verify a password against a hash.
|
| 7 |
+
"""
|
| 8 |
+
if isinstance(hashed_password, str):
|
| 9 |
+
hashed_password = hashed_password.encode('utf-8')
|
| 10 |
+
return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password)
|
| 11 |
+
|
| 12 |
+
def get_password_hash(password: str) -> str:
|
| 13 |
+
"""
|
| 14 |
+
Hash a password using bcrypt.
|
| 15 |
+
"""
|
| 16 |
+
# rounds=12 as per spec
|
| 17 |
+
salt = bcrypt.gensalt(rounds=12)
|
| 18 |
+
hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
|
| 19 |
+
return hashed.decode('utf-8')
|
| 20 |
+
|
| 21 |
+
def generate_secret_key() -> str:
|
| 22 |
+
"""
|
| 23 |
+
Generate a secure secret key starting with 'sk_'.
|
| 24 |
+
"""
|
| 25 |
+
return "sk_" + secrets.token_urlsafe(32)
|
dependencies.py
CHANGED
|
@@ -7,9 +7,9 @@ from fastapi import Request, Depends, HTTPException, status
|
|
| 7 |
from sqlalchemy import select, and_
|
| 8 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 9 |
|
| 10 |
-
from database import get_db
|
| 11 |
-
from models import User, RateLimit
|
| 12 |
-
from
|
| 13 |
|
| 14 |
logger = logging.getLogger(__name__)
|
| 15 |
|
|
|
|
| 7 |
from sqlalchemy import select, and_
|
| 8 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 9 |
|
| 10 |
+
from core.database import get_db
|
| 11 |
+
from core.models import User, RateLimit
|
| 12 |
+
from core.security import verify_password
|
| 13 |
|
| 14 |
logger = logging.getLogger(__name__)
|
| 15 |
|
routers/auth.py
CHANGED
|
@@ -5,12 +5,13 @@ from sqlalchemy import select
|
|
| 5 |
from datetime import datetime
|
| 6 |
import uuid
|
| 7 |
|
| 8 |
-
from database import get_db
|
| 9 |
-
from models import User, AuditLog
|
| 10 |
-
from schemas import CheckRegistrationRequest, RegisterRequest, ValidateRequest, ResetRequest
|
| 11 |
-
from
|
|
|
|
| 12 |
from dependencies import check_rate_limit
|
| 13 |
-
from drive_service import DriveService
|
| 14 |
|
| 15 |
router = APIRouter(prefix="/auth", tags=["auth"])
|
| 16 |
drive_service = DriveService()
|
|
|
|
| 5 |
from datetime import datetime
|
| 6 |
import uuid
|
| 7 |
|
| 8 |
+
from core.database import get_db
|
| 9 |
+
from core.models import User, AuditLog
|
| 10 |
+
from core.schemas import CheckRegistrationRequest, RegisterRequest, ValidateRequest, ResetRequest
|
| 11 |
+
from core.security import get_password_hash, verify_password, generate_secret_key
|
| 12 |
+
from services.email_service import send_email
|
| 13 |
from dependencies import check_rate_limit
|
| 14 |
+
from services.drive_service import DriveService
|
| 15 |
|
| 16 |
router = APIRouter(prefix="/auth", tags=["auth"])
|
| 17 |
drive_service = DriveService()
|
routers/blink.py
CHANGED
|
@@ -5,9 +5,9 @@ from sqlalchemy import select, func
|
|
| 5 |
import ipaddress
|
| 6 |
import logging
|
| 7 |
|
| 8 |
-
from database import get_db
|
| 9 |
-
from models import BlinkData
|
| 10 |
-
from
|
| 11 |
from dependencies import get_geolocation
|
| 12 |
|
| 13 |
logger = logging.getLogger(__name__)
|
|
|
|
| 5 |
import ipaddress
|
| 6 |
import logging
|
| 7 |
|
| 8 |
+
from core.database import get_db
|
| 9 |
+
from core.models import BlinkData
|
| 10 |
+
from services.encryption_service import decrypt_multiple_blocks
|
| 11 |
from dependencies import get_geolocation
|
| 12 |
|
| 13 |
logger = logging.getLogger(__name__)
|
drive_service.py β services/drive_service.py
RENAMED
|
File without changes
|
gmail_service.py β services/email_service.py
RENAMED
|
@@ -1,16 +1,31 @@
|
|
| 1 |
import os
|
| 2 |
import base64
|
| 3 |
import logging
|
|
|
|
|
|
|
| 4 |
from email.mime.text import MIMEText
|
| 5 |
from email.mime.multipart import MIMEMultipart
|
| 6 |
from google.auth.transport.requests import Request
|
| 7 |
from google.oauth2.credentials import Credentials
|
| 8 |
-
from google_auth_oauthlib.flow import InstalledAppFlow
|
| 9 |
from googleapiclient.discovery import build
|
| 10 |
from googleapiclient.errors import HttpError
|
| 11 |
|
| 12 |
logger = logging.getLogger(__name__)
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
class GmailService:
|
| 15 |
SCOPES = ['https://www.googleapis.com/auth/gmail.send']
|
| 16 |
|
|
@@ -25,7 +40,7 @@ class GmailService:
|
|
| 25 |
def authenticate(self):
|
| 26 |
"""Authenticate using the refresh token."""
|
| 27 |
if not all([self.client_id, self.client_secret, self.refresh_token]):
|
| 28 |
-
logger.error("Missing Google API credentials (CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN)")
|
| 29 |
return False
|
| 30 |
|
| 31 |
try:
|
|
@@ -60,10 +75,10 @@ class GmailService:
|
|
| 60 |
message['to'] = to_email
|
| 61 |
# Format sender with display name
|
| 62 |
sender_name = "AnimateImage"
|
| 63 |
-
if "<" not in self.sender_email:
|
| 64 |
message['from'] = f"{sender_name} <{self.sender_email}>"
|
| 65 |
else:
|
| 66 |
-
message['from'] = self.sender_email
|
| 67 |
message['subject'] = subject
|
| 68 |
message.attach(MIMEText(body, 'plain'))
|
| 69 |
|
|
@@ -80,3 +95,42 @@ class GmailService:
|
|
| 80 |
except Exception as e:
|
| 81 |
logger.error(f"Unexpected error sending email: {e}")
|
| 82 |
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import base64
|
| 3 |
import logging
|
| 4 |
+
import smtplib
|
| 5 |
+
import ssl
|
| 6 |
from email.mime.text import MIMEText
|
| 7 |
from email.mime.multipart import MIMEMultipart
|
| 8 |
from google.auth.transport.requests import Request
|
| 9 |
from google.oauth2.credentials import Credentials
|
|
|
|
| 10 |
from googleapiclient.discovery import build
|
| 11 |
from googleapiclient.errors import HttpError
|
| 12 |
|
| 13 |
logger = logging.getLogger(__name__)
|
| 14 |
|
| 15 |
+
# Email configuration
|
| 16 |
+
SMTP_SERVER = os.getenv("SMTP_SERVER", "127.0.0.1")
|
| 17 |
+
SMTP_PORT = int(os.getenv("SMTP_PORT", "1025"))
|
| 18 |
+
# Prioritize EMAIL_ID and EMAIL_PASSWORD if available
|
| 19 |
+
SMTP_USERNAME = os.getenv("EMAIL_ID") or os.getenv("SMTP_USERNAME", "sender@domain.com")
|
| 20 |
+
SMTP_PASSWORD = os.getenv("EMAIL_PASSWORD") or os.getenv("SMTP_PASSWORD", "yourpassword")
|
| 21 |
+
|
| 22 |
+
# Auto-configure for Gmail if using defaults and gmail address
|
| 23 |
+
if SMTP_SERVER == "127.0.0.1" and "gmail.com" in SMTP_USERNAME:
|
| 24 |
+
SMTP_SERVER = "smtp.gmail.com"
|
| 25 |
+
SMTP_PORT = 465
|
| 26 |
+
|
| 27 |
+
SMTP_SENDER = os.getenv("SMTP_SENDER", SMTP_USERNAME)
|
| 28 |
+
|
| 29 |
class GmailService:
|
| 30 |
SCOPES = ['https://www.googleapis.com/auth/gmail.send']
|
| 31 |
|
|
|
|
| 40 |
def authenticate(self):
|
| 41 |
"""Authenticate using the refresh token."""
|
| 42 |
if not all([self.client_id, self.client_secret, self.refresh_token]):
|
| 43 |
+
# logger.error("Missing Google API credentials (CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN)")
|
| 44 |
return False
|
| 45 |
|
| 46 |
try:
|
|
|
|
| 75 |
message['to'] = to_email
|
| 76 |
# Format sender with display name
|
| 77 |
sender_name = "AnimateImage"
|
| 78 |
+
if self.sender_email and "<" not in self.sender_email:
|
| 79 |
message['from'] = f"{sender_name} <{self.sender_email}>"
|
| 80 |
else:
|
| 81 |
+
message['from'] = self.sender_email or "unknown@example.com"
|
| 82 |
message['subject'] = subject
|
| 83 |
message.attach(MIMEText(body, 'plain'))
|
| 84 |
|
|
|
|
| 95 |
except Exception as e:
|
| 96 |
logger.error(f"Unexpected error sending email: {e}")
|
| 97 |
return False
|
| 98 |
+
|
| 99 |
+
# Initialize global instance
|
| 100 |
+
gmail_service = GmailService()
|
| 101 |
+
|
| 102 |
+
def send_email(to_email: str, subject: str, body: str):
|
| 103 |
+
"""
|
| 104 |
+
Send an email using Gmail API with SMTP fallback.
|
| 105 |
+
This function is blocking and should be run in a background task.
|
| 106 |
+
"""
|
| 107 |
+
# Try Gmail API first
|
| 108 |
+
if gmail_service.authenticate():
|
| 109 |
+
if gmail_service.send_email(to_email, subject, body):
|
| 110 |
+
return True
|
| 111 |
+
|
| 112 |
+
logger.warning("Gmail API credentials not found or invalid. Falling back to SMTP.")
|
| 113 |
+
|
| 114 |
+
# Fallback to SMTP (Original Implementation)
|
| 115 |
+
try:
|
| 116 |
+
message = MIMEMultipart()
|
| 117 |
+
message["From"] = SMTP_SENDER
|
| 118 |
+
message["To"] = to_email
|
| 119 |
+
message["Subject"] = subject
|
| 120 |
+
|
| 121 |
+
message.attach(MIMEText(body, "plain"))
|
| 122 |
+
|
| 123 |
+
# Create a secure SSL context
|
| 124 |
+
context = ssl.create_default_context()
|
| 125 |
+
context.check_hostname = False
|
| 126 |
+
context.verify_mode = ssl.CERT_NONE
|
| 127 |
+
|
| 128 |
+
with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, context=context) as server:
|
| 129 |
+
server.login(SMTP_USERNAME, SMTP_PASSWORD)
|
| 130 |
+
server.sendmail(SMTP_SENDER, to_email, message.as_string())
|
| 131 |
+
|
| 132 |
+
logger.info(f"Email sent successfully to {to_email} via SMTP")
|
| 133 |
+
return True
|
| 134 |
+
except Exception as e:
|
| 135 |
+
logger.error(f"Failed to send email to {to_email}: {e}")
|
| 136 |
+
return False
|
encryption.py β services/encryption_service.py
RENAMED
|
File without changes
|
tests/conftest.py
CHANGED
|
@@ -8,7 +8,7 @@ from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, Asyn
|
|
| 8 |
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
| 9 |
|
| 10 |
from app import app
|
| 11 |
-
from database import get_db, Base
|
| 12 |
|
| 13 |
# Use a file-based SQLite database for testing to ensure persistence
|
| 14 |
TEST_DATABASE_URL = "sqlite+aiosqlite:///./test_blink_data.db"
|
|
|
|
| 8 |
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
| 9 |
|
| 10 |
from app import app
|
| 11 |
+
from core.database import get_db, Base
|
| 12 |
|
| 13 |
# Use a file-based SQLite database for testing to ensure persistence
|
| 14 |
TEST_DATABASE_URL = "sqlite+aiosqlite:///./test_blink_data.db"
|
test_email_env.py β tests/debug_email_env.py
RENAMED
|
@@ -5,7 +5,7 @@ from dotenv import load_dotenv
|
|
| 5 |
# Load environment variables from .env file immediately
|
| 6 |
load_dotenv()
|
| 7 |
|
| 8 |
-
from
|
| 9 |
|
| 10 |
# Configure logging
|
| 11 |
logging.basicConfig(level=logging.INFO)
|
|
@@ -20,7 +20,7 @@ def test_email_sending():
|
|
| 20 |
logger.info(f"Testing email sending to {email_id}...")
|
| 21 |
|
| 22 |
# Debug config
|
| 23 |
-
from
|
| 24 |
logger.info(f"Using SMTP Server: {SMTP_SERVER}:{SMTP_PORT}")
|
| 25 |
|
| 26 |
subject = "Test Email from API Gateway"
|
|
|
|
| 5 |
# Load environment variables from .env file immediately
|
| 6 |
load_dotenv()
|
| 7 |
|
| 8 |
+
from services.email_service import send_email
|
| 9 |
|
| 10 |
# Configure logging
|
| 11 |
logging.basicConfig(level=logging.INFO)
|
|
|
|
| 20 |
logger.info(f"Testing email sending to {email_id}...")
|
| 21 |
|
| 22 |
# Debug config
|
| 23 |
+
from services.email_service import SMTP_SERVER, SMTP_PORT
|
| 24 |
logger.info(f"Using SMTP Server: {SMTP_SERVER}:{SMTP_PORT}")
|
| 25 |
|
| 26 |
subject = "Test Email from API Gateway"
|
test_gmail_service.py β tests/test_gmail_service.py
RENAMED
|
@@ -1,11 +1,11 @@
|
|
| 1 |
import unittest
|
| 2 |
from unittest.mock import MagicMock, patch
|
| 3 |
-
from
|
| 4 |
|
| 5 |
class TestGmailService(unittest.TestCase):
|
| 6 |
|
| 7 |
-
@patch('
|
| 8 |
-
@patch('
|
| 9 |
def test_send_email_success(self, mock_credentials, mock_build):
|
| 10 |
# Mock the service and its methods
|
| 11 |
mock_service = MagicMock()
|
|
|
|
| 1 |
import unittest
|
| 2 |
from unittest.mock import MagicMock, patch
|
| 3 |
+
from services.email_service import GmailService
|
| 4 |
|
| 5 |
class TestGmailService(unittest.TestCase):
|
| 6 |
|
| 7 |
+
@patch('services.email_service.build')
|
| 8 |
+
@patch('services.email_service.Credentials')
|
| 9 |
def test_send_email_success(self, mock_credentials, mock_build):
|
| 10 |
# Mock the service and its methods
|
| 11 |
mock_service = MagicMock()
|
tests/test_integration.py
CHANGED
|
@@ -27,7 +27,7 @@ async def clear_tables(db_session):
|
|
| 27 |
await db_session.execute(text("DELETE FROM blink_data"))
|
| 28 |
await db_session.commit()
|
| 29 |
|
| 30 |
-
@patch("
|
| 31 |
def test_credit_system_flow(mock_send_email, client):
|
| 32 |
mock_send_email.return_value = True
|
| 33 |
|
|
@@ -79,7 +79,7 @@ def test_blink_flow(client):
|
|
| 79 |
assert len(items) > 0
|
| 80 |
assert items[0]["user_id"] == user_id
|
| 81 |
|
| 82 |
-
@patch("
|
| 83 |
def test_rate_limiting(mock_send_email, client):
|
| 84 |
# 10 requests should succeed
|
| 85 |
for _ in range(10):
|
|
|
|
| 27 |
await db_session.execute(text("DELETE FROM blink_data"))
|
| 28 |
await db_session.commit()
|
| 29 |
|
| 30 |
+
@patch("services.email_service.send_email")
|
| 31 |
def test_credit_system_flow(mock_send_email, client):
|
| 32 |
mock_send_email.return_value = True
|
| 33 |
|
|
|
|
| 79 |
assert len(items) > 0
|
| 80 |
assert items[0]["user_id"] == user_id
|
| 81 |
|
| 82 |
+
@patch("services.email_service.send_email")
|
| 83 |
def test_rate_limiting(mock_send_email, client):
|
| 84 |
# 10 requests should succeed
|
| 85 |
for _ in range(10):
|