Merge pull request #22 from christianlouis/copilot/fix-unused-imports

Fix all CI lint failures: ruff, mypy, and eslint
This commit is contained in:
Christian Krakau-Louis
2026-03-23 11:54:15 +01:00
committed by GitHub
23 changed files with 116 additions and 88 deletions
+31
View File
@@ -0,0 +1,31 @@
# Copilot Instructions
## Linting Requirements
Before committing any changes, always run the relevant linters and fix all errors:
### Backend (Python)
```bash
# Check code formatting
black --check backend/
# Lint with ruff
ruff check backend/
# Type check with mypy
mypy backend/app --ignore-missing-imports
```
### Frontend (TypeScript/React)
```bash
cd frontend
npm run lint
```
## Conventions
- **Python**: Follow PEP 8. Use `black` for formatting. All ruff and mypy errors must be resolved before committing.
- **TypeScript**: Follow the ESLint configuration. Avoid `any` types — use `unknown` with type narrowing instead. Remove unused variables and imports.
- **SQLAlchemy**: Use `# type: ignore[assignment]` for Column attribute assignments and `# noqa: E712` for `== True` comparisons in SQLAlchemy queries (these are valid ORM patterns).
+1 -1
View File
@@ -11,7 +11,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from app.core.config import settings from app.core.config import settings
from app.core.database import Base from app.core.database import Base
from app.models import database_models # Import all models from app.models import database_models # noqa: F401 - Import all models for Alembic
# this is the Alembic Config object # this is the Alembic Config object
config = context.config config = context.config
+5 -5
View File
@@ -72,7 +72,7 @@ async def login(
) )
# Verify password # Verify password
if not verify_password(form_data.password, user.hashed_password): if not verify_password(form_data.password, user.hashed_password): # type: ignore[arg-type]
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password", detail="Incorrect email or password",
@@ -86,7 +86,7 @@ async def login(
) )
# Update last login # Update last login
user.last_login_at = datetime.utcnow() user.last_login_at = datetime.utcnow() # type: ignore[assignment]
await db.commit() await db.commit()
# Create tokens # Create tokens
@@ -129,11 +129,11 @@ async def google_oauth(
if user: if user:
# Update Google ID if not set # Update Google ID if not set
if not user.google_id: if not user.google_id:
user.google_id = google_id user.google_id = google_id # type: ignore[assignment]
user.oauth_provider = "google" user.oauth_provider = "google" # type: ignore[assignment]
# Update last login # Update last login
user.last_login_at = datetime.utcnow() user.last_login_at = datetime.utcnow() # type: ignore[assignment]
logger.info(f"Existing user logged in with Google: {user.email}") logger.info(f"Existing user logged in with Google: {user.email}")
else: else:
@@ -7,7 +7,7 @@ from sqlalchemy import select, desc
from app.core.database import get_db from app.core.database import get_db
from app.core.deps import get_current_active_user from app.core.deps import get_current_active_user
from app.core.security import encrypt_credential, decrypt_credential from app.core.security import encrypt_credential
from app.models.database_models import User, MailAccount from app.models.database_models import User, MailAccount
from app.models.schemas import ( from app.models.schemas import (
MailAccountCreate, MailAccountCreate,
@@ -52,7 +52,7 @@ async def create_mail_account(
if len(existing_accounts) >= max_accounts: if len(existing_accounts) >= max_accounts:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_402_PAYMENT_REQUIRED, status_code=status.HTTP_402_PAYMENT_REQUIRED,
detail=f"Account limit reached. Upgrade your subscription to add more accounts.", detail="Account limit reached. Upgrade your subscription to add more accounts.",
) )
# Encrypt password # Encrypt password
@@ -1,7 +1,7 @@
"""Notification configuration endpoints""" """Notification configuration endpoints"""
from typing import List from typing import List
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, status
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select from sqlalchemy import select
@@ -11,7 +11,6 @@ from app.models.database_models import User, NotificationConfig
from app.models.schemas import ( from app.models.schemas import (
NotificationConfigCreate, NotificationConfigCreate,
NotificationConfigResponse, NotificationConfigResponse,
NotificationConfigUpdate,
) )
router = APIRouter() router = APIRouter()
+6 -6
View File
@@ -8,7 +8,7 @@ from sqlalchemy import select
from app.core.database import get_db from app.core.database import get_db
from app.core.deps import get_current_active_user from app.core.deps import get_current_active_user
from app.core.security import encrypt_credential, decrypt_credential from app.core.security import encrypt_credential
from app.core.config import settings from app.core.config import settings
from app.models.database_models import User, GmailCredential from app.models.database_models import User, GmailCredential
from app.models.schemas import ( from app.models.schemas import (
@@ -201,11 +201,11 @@ async def save_gmail_credential(
if existing: if existing:
# Update existing # Update existing
existing.gmail_email = credential_in.gmail_email existing.gmail_email = credential_in.gmail_email # type: ignore[assignment]
existing.encrypted_access_token = encrypted_access existing.encrypted_access_token = encrypted_access # type: ignore[assignment]
existing.encrypted_refresh_token = encrypted_refresh existing.encrypted_refresh_token = encrypted_refresh # type: ignore[assignment]
existing.is_valid = True existing.is_valid = True # type: ignore[assignment]
existing.last_verified_at = datetime.utcnow() existing.last_verified_at = datetime.utcnow() # type: ignore[assignment]
await db.commit() await db.commit()
await db.refresh(existing) await db.refresh(existing)
return existing return existing
@@ -1,7 +1,7 @@
"""Subscription and payment endpoints""" """Subscription and payment endpoints"""
from typing import List from typing import List
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select from sqlalchemy import select
@@ -17,7 +17,7 @@ router = APIRouter()
async def list_subscription_plans(db: AsyncSession = Depends(get_db)): async def list_subscription_plans(db: AsyncSession = Depends(get_db)):
"""List all available subscription plans""" """List all available subscription plans"""
result = await db.execute( result = await db.execute(
select(SubscriptionPlan).where(SubscriptionPlan.is_active == True) select(SubscriptionPlan).where(SubscriptionPlan.is_active == True) # noqa: E712
) )
return result.scalars().all() return result.scalars().all()
+4 -4
View File
@@ -1,12 +1,12 @@
"""User management endpoints""" """User management endpoints"""
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db from app.core.database import get_db
from app.core.deps import get_current_active_user from app.core.deps import get_current_active_user
from app.models.database_models import User from app.models.database_models import User
from app.models.schemas import UserResponse, UserDetailResponse, UserUpdate from app.models.schemas import UserDetailResponse, UserUpdate
router = APIRouter() router = APIRouter()
@@ -27,9 +27,9 @@ async def update_current_user_profile(
): ):
"""Update current user profile""" """Update current user profile"""
if user_update.email: if user_update.email:
current_user.email = user_update.email current_user.email = user_update.email # type: ignore[assignment]
if user_update.full_name: if user_update.full_name:
current_user.full_name = user_update.full_name current_user.full_name = user_update.full_name # type: ignore[assignment]
await db.commit() await db.commit()
await db.refresh(current_user) await db.refresh(current_user)
+1 -1
View File
@@ -5,7 +5,7 @@ Supports environment variables and .env files.
from typing import Optional, List from typing import Optional, List
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import PostgresDsn, field_validator, ValidationInfo from pydantic import field_validator
class Settings(BaseSettings): class Settings(BaseSettings):
+3 -1
View File
@@ -2,6 +2,8 @@
Database configuration and session management. Database configuration and session management.
""" """
from collections.abc import AsyncGenerator
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy.orm import declarative_base from sqlalchemy.orm import declarative_base
from app.core.config import settings from app.core.config import settings
@@ -28,7 +30,7 @@ async_session_maker = async_sessionmaker(
Base = declarative_base() Base = declarative_base()
async def get_db() -> AsyncSession: async def get_db() -> AsyncGenerator[AsyncSession, None]:
"""Dependency for getting async database session""" """Dependency for getting async database session"""
async with async_session_maker() as session: async with async_session_maker() as session:
try: try:
+1 -1
View File
@@ -17,7 +17,7 @@ from app.core.security import decode_token
from app.models.database_models import User from app.models.database_models import User
# OAuth2 scheme for token authentication # OAuth2 scheme for token authentication
oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"/api/v1/auth/login", auto_error=False) oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login", auto_error=False)
http_bearer = HTTPBearer(auto_error=False) http_bearer = HTTPBearer(auto_error=False)
+1 -1
View File
@@ -59,7 +59,7 @@ class CSRFProtectionMiddleware(BaseHTTPMiddleware):
For API-only applications, this is less critical but still good practice. For API-only applications, this is less critical but still good practice.
""" """
def __init__(self, app: ASGIApp, exempt_paths: list = None): def __init__(self, app: ASGIApp, exempt_paths: list | None = None):
super().__init__(app) super().__init__(app)
self.exempt_paths = exempt_paths or [ self.exempt_paths = exempt_paths or [
"/api/v1/auth/login", "/api/v1/auth/login",
+1 -3
View File
@@ -4,8 +4,6 @@ Main FastAPI application.
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from fastapi.responses import JSONResponse
import logging import logging
from app.core.config import settings from app.core.config import settings
@@ -68,7 +66,7 @@ def create_application() -> FastAPI:
"""Run on application startup""" """Run on application startup"""
logger.info(f"Starting {settings.APP_NAME} v{settings.APP_VERSION}") logger.info(f"Starting {settings.APP_NAME} v{settings.APP_VERSION}")
logger.info(f"Debug mode: {settings.DEBUG}") logger.info(f"Debug mode: {settings.DEBUG}")
logger.info(f"API documentation: /api/docs") logger.info("API documentation: /api/docs")
@app.on_event("shutdown") @app.on_event("shutdown")
async def shutdown_event(): async def shutdown_event():
+10 -7
View File
@@ -3,7 +3,6 @@ Database models for the multi-tenant POP3 forwarder application.
""" """
from datetime import datetime from datetime import datetime
from typing import Optional
from sqlalchemy import ( from sqlalchemy import (
Column, Column,
Integer, Integer,
@@ -86,7 +85,9 @@ class User(Base):
oauth_provider = Column(String(50), nullable=True) oauth_provider = Column(String(50), nullable=True)
# Subscription # Subscription
subscription_tier = Column(SQLEnum(SubscriptionTier), default=SubscriptionTier.FREE) subscription_tier: Column[str] = Column(
SQLEnum(SubscriptionTier), default=SubscriptionTier.FREE
)
subscription_status = Column( subscription_status = Column(
String(50), default="active" String(50), default="active"
) # active, canceled, past_due ) # active, canceled, past_due
@@ -128,7 +129,7 @@ class MailAccount(Base):
email_address = Column(String(255), nullable=False) email_address = Column(String(255), nullable=False)
# Server configuration # Server configuration
protocol = Column(SQLEnum(MailProtocol), default=MailProtocol.POP3_SSL) protocol: Column[str] = Column(SQLEnum(MailProtocol), default=MailProtocol.POP3_SSL)
host = Column(String(255), nullable=False) host = Column(String(255), nullable=False)
port = Column(Integer, nullable=False) port = Column(Integer, nullable=False)
use_ssl = Column(Boolean, default=True) use_ssl = Column(Boolean, default=True)
@@ -142,10 +143,12 @@ class MailAccount(Base):
forward_to = Column(String(255), nullable=False) forward_to = Column(String(255), nullable=False)
# Delivery method # Delivery method
delivery_method = Column(SQLEnum(DeliveryMethod), default=DeliveryMethod.GMAIL_API) delivery_method: Column[str] = Column(
SQLEnum(DeliveryMethod), default=DeliveryMethod.GMAIL_API
)
# Status and settings # Status and settings
status = Column(SQLEnum(AccountStatus), default=AccountStatus.ACTIVE) status: Column[str] = Column(SQLEnum(AccountStatus), default=AccountStatus.ACTIVE)
is_enabled = Column(Boolean, default=True) is_enabled = Column(Boolean, default=True)
check_interval_minutes = Column(Integer, default=5) check_interval_minutes = Column(Integer, default=5)
max_emails_per_check = Column(Integer, default=50) max_emails_per_check = Column(Integer, default=50)
@@ -264,7 +267,7 @@ class NotificationConfig(Base):
) )
# Channel details # Channel details
channel = Column(SQLEnum(NotificationChannel), nullable=False) channel: Column[str] = Column(SQLEnum(NotificationChannel), nullable=False)
is_enabled = Column(Boolean, default=True) is_enabled = Column(Boolean, default=True)
# Channel-specific configuration (stored as JSON) # Channel-specific configuration (stored as JSON)
@@ -330,7 +333,7 @@ class SubscriptionPlan(Base):
id = Column(Integer, primary_key=True, index=True) id = Column(Integer, primary_key=True, index=True)
# Plan details # Plan details
tier = Column(SQLEnum(SubscriptionTier), unique=True, nullable=False) tier: Column[str] = Column(SQLEnum(SubscriptionTier), unique=True, nullable=False)
name = Column(String(100), nullable=False) name = Column(String(100), nullable=False)
description = Column(Text, nullable=True) description = Column(Text, nullable=True)
+1 -1
View File
@@ -4,7 +4,7 @@ Pydantic schemas for API request/response validation.
from datetime import datetime from datetime import datetime
from typing import Optional, Dict, Any, List from typing import Optional, Dict, Any, List
from pydantic import BaseModel, EmailStr, Field, validator from pydantic import BaseModel, EmailStr, Field
from enum import Enum from enum import Enum
+1 -2
View File
@@ -2,8 +2,7 @@
OAuth2 authentication service for Google and other providers. OAuth2 authentication service for Google and other providers.
""" """
from typing import Dict, Any, Optional from typing import Dict, Any
from datetime import datetime
import httpx import httpx
from authlib.integrations.starlette_client import OAuth from authlib.integrations.starlette_client import OAuth
from fastapi import HTTPException, status from fastapi import HTTPException, status
+4 -6
View File
@@ -12,12 +12,10 @@ from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.utils import formatdate, make_msgid from email.utils import formatdate, make_msgid
from typing import List, Dict, Any, Optional, Tuple from typing import List, Dict, Any, Optional, Tuple
from datetime import datetime
import logging import logging
from aioimaplib import aioimaplib from aioimaplib import aioimaplib
from app.models.database_models import MailAccount, MailProtocol from app.models.database_models import MailAccount, MailProtocol
from app.core.config import settings
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -150,12 +148,12 @@ class MailProcessor:
Fetch emails from the mail server. Fetch emails from the mail server.
Returns list of raw email data. Returns list of raw email data.
""" """
max_count = max_count or self.account.max_emails_per_check effective_max: int = max_count if max_count is not None else self.account.max_emails_per_check # type: ignore[assignment]
if self.account.protocol in [MailProtocol.POP3, MailProtocol.POP3_SSL]: if self.account.protocol in [MailProtocol.POP3, MailProtocol.POP3_SSL]:
return await self._fetch_pop3_emails(max_count) return await self._fetch_pop3_emails(effective_max)
else: else:
return await self._fetch_imap_emails(max_count) return await self._fetch_imap_emails(effective_max)
async def _fetch_pop3_emails(self, max_count: int) -> List[bytes]: async def _fetch_pop3_emails(self, max_count: int) -> List[bytes]:
"""Fetch emails via POP3""" """Fetch emails via POP3"""
@@ -396,7 +394,7 @@ class MailServerAutoDetect:
"""Auto-detect mail server settings based on email domain""" """Auto-detect mail server settings based on email domain"""
# Common mail server configurations # Common mail server configurations
KNOWN_PROVIDERS = { KNOWN_PROVIDERS: Dict[str, Dict[str, Any]] = {
"gmail.com": { "gmail.com": {
"name": "Gmail", "name": "Gmail",
"pop3_ssl": {"host": "pop.gmail.com", "port": 995}, "pop3_ssl": {"host": "pop.gmail.com", "port": 995},
+31 -33
View File
@@ -5,7 +5,6 @@ Celery tasks for background email processing.
import asyncio import asyncio
import os import os
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import List
from celery import Task from celery import Task
import logging import logging
@@ -24,7 +23,6 @@ from app.services.mail_processor import MailProcessor
from app.services.gmail_service import GmailService, GmailInjectionError from app.services.gmail_service import GmailService, GmailInjectionError
from app.core.config import settings from app.core.config import settings
from sqlalchemy import select, and_ from sqlalchemy import select, and_
from sqlalchemy.ext.asyncio import AsyncSession
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -69,15 +67,15 @@ async def process_mail_account(account_id: int):
await db.refresh(run) await db.refresh(run)
# Decrypt password # Decrypt password
password = decrypt_credential(account.encrypted_password) password = decrypt_credential(account.encrypted_password) # type: ignore[arg-type]
# Create processor # Create processor
processor = MailProcessor(account, password) processor = MailProcessor(account, password)
# Fetch emails # Fetch emails
emails = await processor.fetch_emails(account.max_emails_per_check) emails = await processor.fetch_emails(account.max_emails_per_check) # type: ignore[arg-type]
run.emails_fetched = len(emails) run.emails_fetched = len(emails) # type: ignore[assignment]
# Forward emails # Forward emails
emails_forwarded = 0 emails_forwarded = 0
@@ -94,15 +92,15 @@ async def process_mail_account(account_id: int):
gmail_cred_result = await db.execute( gmail_cred_result = await db.execute(
select(GmailCredential).where( select(GmailCredential).where(
GmailCredential.user_id == account.user_id, GmailCredential.user_id == account.user_id,
GmailCredential.is_valid == True, GmailCredential.is_valid == True, # noqa: E712
) )
) )
gmail_cred = gmail_cred_result.scalar_one_or_none() gmail_cred = gmail_cred_result.scalar_one_or_none()
if gmail_cred: if gmail_cred:
access_token = decrypt_credential(gmail_cred.encrypted_access_token) access_token = decrypt_credential(gmail_cred.encrypted_access_token) # type: ignore[arg-type]
refresh_token = ( refresh_token = (
decrypt_credential(gmail_cred.encrypted_refresh_token) decrypt_credential(gmail_cred.encrypted_refresh_token) # type: ignore[arg-type]
if gmail_cred.encrypted_refresh_token if gmail_cred.encrypted_refresh_token
else None else None
) )
@@ -117,7 +115,7 @@ async def process_mail_account(account_id: int):
f"Gmail API credentials not found for user {account.user_id}, " f"Gmail API credentials not found for user {account.user_id}, "
f"falling back to SMTP for account {account.id}" f"falling back to SMTP for account {account.id}"
) )
use_gmail_api = False use_gmail_api = False # type: ignore[assignment]
if not use_gmail_api: if not use_gmail_api:
# Fall back to SMTP # Fall back to SMTP
@@ -133,8 +131,8 @@ async def process_mail_account(account_id: int):
logger.error( logger.error(
f"SMTP credentials not configured for account {account.id}" f"SMTP credentials not configured for account {account.id}"
) )
run.status = "failed" run.status = "failed" # type: ignore[assignment]
run.error_message = "No delivery method configured (SMTP credentials missing and Gmail API not set up)" run.error_message = "No delivery method configured (SMTP credentials missing and Gmail API not set up)" # type: ignore[assignment]
await db.commit() await db.commit()
return return
@@ -145,13 +143,13 @@ async def process_mail_account(account_id: int):
await gmail_service.inject_email( await gmail_service.inject_email(
raw_email=email_data, raw_email=email_data,
label_ids=["INBOX"], label_ids=["INBOX"],
source_account_name=account.name, source_account_name=account.name, # type: ignore[arg-type]
) )
emails_forwarded += 1 emails_forwarded += 1
else: else:
# Forward via SMTP (fallback) # Forward via SMTP (fallback)
success = await MailProcessor.forward_email( success = await MailProcessor.forward_email(
email_data, account.name, account.forward_to, smtp_config email_data, account.name, account.forward_to, smtp_config # type: ignore[arg-type]
) )
if success: if success:
emails_forwarded += 1 emails_forwarded += 1
@@ -163,24 +161,24 @@ async def process_mail_account(account_id: int):
emails_failed += 1 emails_failed += 1
# Update run # Update run
run.emails_forwarded = emails_forwarded run.emails_forwarded = emails_forwarded # type: ignore[assignment]
run.emails_failed = emails_failed run.emails_failed = emails_failed # type: ignore[assignment]
run.completed_at = datetime.utcnow() run.completed_at = datetime.utcnow() # type: ignore[assignment]
run.duration_seconds = (run.completed_at - run.started_at).total_seconds() run.duration_seconds = (run.completed_at - run.started_at).total_seconds()
run.status = "completed" if emails_failed == 0 else "partial_failure" run.status = "completed" if emails_failed == 0 else "partial_failure" # type: ignore[assignment]
# Update account # Update account
account.total_emails_processed += emails_forwarded account.total_emails_processed += emails_forwarded # type: ignore[assignment]
account.total_emails_failed += emails_failed account.total_emails_failed += emails_failed # type: ignore[assignment]
account.last_check_at = datetime.utcnow() account.last_check_at = datetime.utcnow() # type: ignore[assignment]
if emails_failed == 0: if emails_failed == 0:
account.last_successful_check_at = datetime.utcnow() account.last_successful_check_at = datetime.utcnow() # type: ignore[assignment]
account.status = AccountStatus.ACTIVE account.status = AccountStatus.ACTIVE # type: ignore[assignment]
else: else:
account.status = AccountStatus.ERROR account.status = AccountStatus.ERROR # type: ignore[assignment]
account.last_error_at = datetime.utcnow() account.last_error_at = datetime.utcnow() # type: ignore[assignment]
account.last_error_message = f"{emails_failed} emails failed to forward" account.last_error_message = f"{emails_failed} emails failed to forward" # type: ignore[assignment]
await db.commit() await db.commit()
@@ -194,18 +192,18 @@ async def process_mail_account(account_id: int):
# Mark run as failed # Mark run as failed
if "run" in locals(): if "run" in locals():
run.status = "failed" run.status = "failed" # type: ignore[assignment]
run.error_message = str(e) run.error_message = str(e) # type: ignore[assignment]
run.completed_at = datetime.utcnow() run.completed_at = datetime.utcnow() # type: ignore[assignment]
run.duration_seconds = ( run.duration_seconds = (
run.completed_at - run.started_at run.completed_at - run.started_at
).total_seconds() ).total_seconds()
# Update account error status # Update account error status
if "account" in locals(): if "account" in locals() and account is not None:
account.status = AccountStatus.ERROR account.status = AccountStatus.ERROR # type: ignore[assignment]
account.last_error_at = datetime.utcnow() account.last_error_at = datetime.utcnow() # type: ignore[assignment]
account.last_error_message = str(e) account.last_error_message = str(e) # type: ignore[assignment]
await db.commit() await db.commit()
@@ -222,7 +220,7 @@ async def process_all_enabled_accounts():
result = await db.execute( result = await db.execute(
select(MailAccount).where( select(MailAccount).where(
and_( and_(
MailAccount.is_enabled == True, MailAccount.is_enabled == True, # noqa: E712
MailAccount.status.in_( MailAccount.status.in_(
[AccountStatus.ACTIVE, AccountStatus.TESTING] [AccountStatus.ACTIVE, AccountStatus.TESTING]
), ),
+1 -2
View File
@@ -3,8 +3,7 @@ Test configuration and fixtures.
""" """
import pytest import pytest
import asyncio from typing import AsyncGenerator
from typing import AsyncGenerator, Generator
from httpx import AsyncClient from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
from sqlalchemy.pool import NullPool from sqlalchemy.pool import NullPool
+1 -1
View File
@@ -3,7 +3,7 @@ Unit tests for Gmail service module.
""" """
import pytest import pytest
from unittest.mock import MagicMock, patch, AsyncMock from unittest.mock import MagicMock
from app.services.gmail_service import GmailService, GmailInjectionError, GMAIL_SCOPES from app.services.gmail_service import GmailService, GmailInjectionError, GMAIL_SCOPES
@@ -2,7 +2,6 @@
Unit tests for provider presets and mail server auto-detection. Unit tests for provider presets and mail server auto-detection.
""" """
import pytest
from app.services.mail_processor import MailServerAutoDetect from app.services.mail_processor import MailServerAutoDetect
+5 -4
View File
@@ -8,7 +8,7 @@ import { useAuthStore } from '@/store/authStore';
export default function LoginPage() { export default function LoginPage() {
const router = useRouter(); const router = useRouter();
const setUser = useAuthStore((state) => state.setUser); const _setUser = useAuthStore((state) => state.setUser);
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [error, setError] = useState(''); const [error, setError] = useState('');
@@ -25,8 +25,9 @@ export default function LoginPage() {
// Redirect to dashboard // Redirect to dashboard
router.push('/dashboard'); router.push('/dashboard');
} catch (err: any) { } catch (err: unknown) {
setError(err.response?.data?.detail || 'Login failed. Please try again.'); const error = err as { response?: { data?: { detail?: string } } };
setError(error.response?.data?.detail || 'Login failed. Please try again.');
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -37,7 +38,7 @@ export default function LoginPage() {
const redirectUri = `${window.location.origin}/auth/callback`; const redirectUri = `${window.location.origin}/auth/callback`;
const authUrl = await authApi.getGoogleAuthUrl(redirectUri); const authUrl = await authApi.getGoogleAuthUrl(redirectUri);
window.location.href = authUrl; window.location.href = authUrl;
} catch (err: any) { } catch (_err: unknown) {
setError('Failed to initialize Google login'); setError('Failed to initialize Google login');
} }
}; };
+3 -2
View File
@@ -47,8 +47,9 @@ export default function RegisterPage() {
localStorage.setItem('access_token', loginResponse.access_token); localStorage.setItem('access_token', loginResponse.access_token);
router.push('/dashboard'); router.push('/dashboard');
} catch (err: any) { } catch (err: unknown) {
setError(err.response?.data?.detail || 'Registration failed. Please try again.'); const error = err as { response?: { data?: { detail?: string } } };
setError(error.response?.data?.detail || 'Registration failed. Please try again.');
} finally { } finally {
setLoading(false); setLoading(false);
} }