mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 17:31:09 -05:00
feat: complete monorepo structure with frontend and shared resources
- Add complete backend/ directory with full Django application - Add frontend/ directory with Vite + TypeScript setup ready for Next.js - Add comprehensive shared/ directory with: - Complete documentation and memory-bank archives - Media files and avatars (letters, park/ride images) - Deployment scripts and automation tools - Shared types and utilities - Add architecture/ directory with migration guides - Configure pnpm workspace for monorepo development - Update .gitignore to exclude .django_tailwind_cli/ build artifacts - Preserve all historical documentation in shared/docs/memory-bank/ - Set up proper structure for full-stack development with shared resources
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.sites.models import Site
|
||||
from django.test import RequestFactory, Client
|
||||
from allauth.account.models import EmailAddress
|
||||
from apps.accounts.adapters import CustomAccountAdapter
|
||||
from django.conf import settings
|
||||
import uuid
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Test all email flows in the application"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.factory = RequestFactory()
|
||||
# Disable CSRF for testing
|
||||
self.client = Client(enforce_csrf_checks=False)
|
||||
self.adapter = CustomAccountAdapter()
|
||||
self.site = Site.objects.get_current()
|
||||
|
||||
# Generate unique test data
|
||||
unique_id = str(uuid.uuid4())[:8]
|
||||
self.test_username = f"testuser_{unique_id}"
|
||||
self.test_email = f"test_{unique_id}@thrillwiki.com"
|
||||
self.test_password = "[PASSWORD-REMOVED]"
|
||||
self.new_password = "[PASSWORD-REMOVED]"
|
||||
|
||||
# Add testserver to ALLOWED_HOSTS
|
||||
if "testserver" not in settings.ALLOWED_HOSTS:
|
||||
settings.ALLOWED_HOSTS.append("testserver")
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.stdout.write("Starting email flow tests...\n")
|
||||
|
||||
# Clean up any existing test users
|
||||
User.objects.filter(email__endswith="@thrillwiki.com").delete()
|
||||
|
||||
# Test registration email
|
||||
self.test_registration()
|
||||
|
||||
# Create a test user for other flows
|
||||
user = User.objects.create_user(
|
||||
username=f"testuser2_{str(uuid.uuid4())[:8]}",
|
||||
email=f"test2_{str(uuid.uuid4())[:8]}@thrillwiki.com",
|
||||
password=self.test_password,
|
||||
)
|
||||
EmailAddress.objects.create(
|
||||
user=user, email=user.email, primary=True, verified=True
|
||||
)
|
||||
|
||||
# Log in the test user
|
||||
self.client.force_login(user)
|
||||
|
||||
# Test other flows
|
||||
self.test_password_change(user)
|
||||
self.test_email_change(user)
|
||||
self.test_password_reset(user)
|
||||
|
||||
# Cleanup
|
||||
User.objects.filter(email__endswith="@thrillwiki.com").delete()
|
||||
self.stdout.write(self.style.SUCCESS("All email flow tests completed!\n"))
|
||||
|
||||
def test_registration(self):
|
||||
"""Test registration email flow"""
|
||||
self.stdout.write("Testing registration email...")
|
||||
try:
|
||||
# Use dj-rest-auth registration endpoint
|
||||
response = self.client.post(
|
||||
"/api/auth/registration/",
|
||||
{
|
||||
"username": self.test_username,
|
||||
"email": self.test_email,
|
||||
"password1": self.test_password,
|
||||
"password2": self.test_password,
|
||||
},
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
if response.status_code in [200, 201, 204]:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS("Registration email test passed!\n")
|
||||
)
|
||||
else:
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
f"Registration returned status {
|
||||
response.status_code}: {
|
||||
response.content.decode()}\n"
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(
|
||||
f"Registration email test failed: {
|
||||
str(e)}\n"
|
||||
)
|
||||
)
|
||||
|
||||
def test_password_change(self, user):
|
||||
"""Test password change using dj-rest-auth"""
|
||||
self.stdout.write("Testing password change email...")
|
||||
try:
|
||||
response = self.client.post(
|
||||
"/api/auth/password/change/",
|
||||
{
|
||||
"old_password": self.test_password,
|
||||
"new_password1": self.new_password,
|
||||
"new_password2": self.new_password,
|
||||
},
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS("Password change email test passed!\n")
|
||||
)
|
||||
else:
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
f"Password change returned status {
|
||||
response.status_code}: {
|
||||
response.content.decode()}\n"
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(
|
||||
f"Password change email test failed: {
|
||||
str(e)}\n"
|
||||
)
|
||||
)
|
||||
|
||||
def test_email_change(self, user):
|
||||
"""Test email change verification"""
|
||||
self.stdout.write("Testing email change verification...")
|
||||
try:
|
||||
new_email = f"newemail_{str(uuid.uuid4())[:8]}@thrillwiki.com"
|
||||
response = self.client.post(
|
||||
"/api/auth/email/",
|
||||
{"email": new_email},
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS("Email change verification test passed!\n")
|
||||
)
|
||||
else:
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
f"Email change returned status {
|
||||
response.status_code}: {
|
||||
response.content.decode()}\n"
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(
|
||||
f"Email change verification test failed: {
|
||||
str(e)}\n"
|
||||
)
|
||||
)
|
||||
|
||||
def test_password_reset(self, user):
|
||||
"""Test password reset using dj-rest-auth"""
|
||||
self.stdout.write("Testing password reset email...")
|
||||
try:
|
||||
# Request password reset
|
||||
response = self.client.post(
|
||||
"/api/auth/password/reset/",
|
||||
{"email": user.email},
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS("Password reset email test passed!\n")
|
||||
)
|
||||
else:
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
f"Password reset returned status {
|
||||
response.status_code}: {
|
||||
response.content.decode()}\n"
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(
|
||||
f"Password reset email test failed: {
|
||||
str(e)}\n"
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,244 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.mail import send_mail
|
||||
from django.conf import settings
|
||||
import requests
|
||||
import os
|
||||
from apps.email_service.models import EmailConfiguration
|
||||
from apps.email_service.services import EmailService
|
||||
from apps.email_service.backends import ForwardEmailBackend
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Test the email service functionality"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--to",
|
||||
type=str,
|
||||
help="Recipient email address (optional, defaults to current user's email)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--api-key",
|
||||
type=str,
|
||||
help="ForwardEmail API key (optional, will use configured value)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--from-email",
|
||||
type=str,
|
||||
help="Sender email address (optional, will use configured value)",
|
||||
)
|
||||
|
||||
def get_config(self):
|
||||
"""Get email configuration from database or environment"""
|
||||
try:
|
||||
site = Site.objects.get(id=settings.SITE_ID)
|
||||
config = EmailConfiguration.objects.get(site=site)
|
||||
return {
|
||||
"api_key": config.api_key,
|
||||
"from_email": config.default_from_email,
|
||||
"site": site,
|
||||
}
|
||||
except (Site.DoesNotExist, EmailConfiguration.DoesNotExist):
|
||||
# Try environment variables
|
||||
api_key = os.environ.get("FORWARD_EMAIL_API_KEY")
|
||||
from_email = os.environ.get("FORWARD_EMAIL_FROM")
|
||||
|
||||
if not api_key or not from_email:
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
"No configuration found in database or environment variables.\n"
|
||||
"Please either:\n"
|
||||
"1. Configure email settings in Django admin, or\n"
|
||||
"2. Set environment variables FORWARD_EMAIL_API_KEY and FORWARD_EMAIL_FROM, or\n"
|
||||
"3. Provide --api-key and --from-email arguments"
|
||||
)
|
||||
)
|
||||
return None
|
||||
|
||||
return {
|
||||
"api_key": api_key,
|
||||
"from_email": from_email,
|
||||
"site": Site.objects.get(id=settings.SITE_ID),
|
||||
}
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.stdout.write(self.style.SUCCESS("Starting email service tests..."))
|
||||
|
||||
# Get configuration
|
||||
config = self.get_config()
|
||||
if not config and not (options["api_key"] and options["from_email"]):
|
||||
self.stdout.write(
|
||||
self.style.ERROR("No email configuration available. Tests aborted.")
|
||||
)
|
||||
return
|
||||
|
||||
# Use provided values or fall back to config
|
||||
api_key = options["api_key"] or config["api_key"]
|
||||
from_email = options["from_email"] or config["from_email"]
|
||||
site = config["site"]
|
||||
|
||||
# If no recipient specified, use the from_email address for testing
|
||||
to_email = options["to"] or "test@thrillwiki.com"
|
||||
|
||||
self.stdout.write(self.style.SUCCESS("Using configuration:"))
|
||||
self.stdout.write(f" From: {from_email}")
|
||||
self.stdout.write(f" To: {to_email}")
|
||||
self.stdout.write(f' API Key: {"*" * len(api_key)}')
|
||||
self.stdout.write(f" Site: {site.domain}")
|
||||
|
||||
try:
|
||||
# 1. Test site configuration
|
||||
config = self.test_site_configuration(api_key, from_email)
|
||||
|
||||
# 2. Test direct service
|
||||
self.test_email_service_directly(to_email, config.site)
|
||||
|
||||
# 3. Test API endpoint
|
||||
self.test_api_endpoint(to_email)
|
||||
|
||||
# 4. Test Django email backend
|
||||
self.test_email_backend(to_email, config.site)
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS("\nAll tests completed successfully! 🎉")
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
self.stdout.write(self.style.ERROR(f"\nTests failed: {str(e)}"))
|
||||
|
||||
def test_site_configuration(self, api_key, from_email):
|
||||
"""Test creating and retrieving site configuration"""
|
||||
self.stdout.write("\nTesting site configuration...")
|
||||
|
||||
try:
|
||||
# Get or create default site
|
||||
site = Site.objects.get_or_create(
|
||||
id=settings.SITE_ID,
|
||||
defaults={"domain": "example.com", "name": "example.com"},
|
||||
)[0]
|
||||
|
||||
# Create or update email configuration
|
||||
config, created = EmailConfiguration.objects.update_or_create(
|
||||
site=site,
|
||||
defaults={
|
||||
"api_key": api_key,
|
||||
"default_from_email": from_email,
|
||||
},
|
||||
)
|
||||
|
||||
action = "Created new" if created else "Updated existing"
|
||||
self.stdout.write(self.style.SUCCESS(f"✓ {action} site configuration"))
|
||||
return config
|
||||
except Exception as e:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(
|
||||
f"✗ Site configuration failed: {
|
||||
str(e)}"
|
||||
)
|
||||
)
|
||||
raise
|
||||
|
||||
def test_api_endpoint(self, to_email):
|
||||
"""Test sending email via the API endpoint"""
|
||||
self.stdout.write("\nTesting API endpoint...")
|
||||
|
||||
try:
|
||||
# Make request to the API endpoint
|
||||
response = requests.post(
|
||||
"http://127.0.0.1:8000/api/email/send-email/",
|
||||
json={
|
||||
"to": to_email,
|
||||
"subject": "Test Email via API",
|
||||
"text": "This is a test email sent via the API endpoint.",
|
||||
},
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
self.stdout.write(self.style.SUCCESS("✓ API endpoint test successful"))
|
||||
else:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(
|
||||
f"✗ API endpoint test failed with status {
|
||||
response.status_code}: {
|
||||
response.text}"
|
||||
)
|
||||
)
|
||||
raise Exception(f"API test failed: {response.text}")
|
||||
except requests.exceptions.ConnectionError:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(
|
||||
"✗ API endpoint test failed: Could not connect to server. "
|
||||
"Make sure the Django development server is running."
|
||||
)
|
||||
)
|
||||
raise Exception("Could not connect to Django server")
|
||||
except Exception as e:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(
|
||||
f"✗ API endpoint test failed: {
|
||||
str(e)}"
|
||||
)
|
||||
)
|
||||
raise
|
||||
|
||||
def test_email_backend(self, to_email, site):
|
||||
"""Test sending email via Django's email backend"""
|
||||
self.stdout.write("\nTesting Django email backend...")
|
||||
|
||||
try:
|
||||
# Create a connection with site context
|
||||
backend = ForwardEmailBackend(fail_silently=False, site=site)
|
||||
|
||||
# Debug output
|
||||
self.stdout.write(
|
||||
f" Debug: Using from_email: {
|
||||
site.email_config.default_from_email}"
|
||||
)
|
||||
self.stdout.write(f" Debug: Using to_email: {to_email}")
|
||||
|
||||
send_mail(
|
||||
subject="Test Email via Backend",
|
||||
message="This is a test email sent via the Django email backend.",
|
||||
from_email=site.email_config.default_from_email, # Explicitly set from_email
|
||||
recipient_list=[to_email],
|
||||
fail_silently=False,
|
||||
connection=backend,
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS("✓ Email backend test successful"))
|
||||
except Exception as e:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(
|
||||
f"✗ Email backend test failed: {
|
||||
str(e)}"
|
||||
)
|
||||
)
|
||||
raise
|
||||
|
||||
def test_email_service_directly(self, to_email, site):
|
||||
"""Test sending email directly via EmailService"""
|
||||
self.stdout.write("\nTesting EmailService directly...")
|
||||
|
||||
try:
|
||||
response = EmailService.send_email(
|
||||
to=to_email,
|
||||
subject="Test Email via Service",
|
||||
text="This is a test email sent directly via the EmailService.",
|
||||
site=site,
|
||||
)
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS("✓ Direct EmailService test successful")
|
||||
)
|
||||
return response
|
||||
except Exception as e:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(
|
||||
f"✗ Direct EmailService test failed: {
|
||||
str(e)}"
|
||||
)
|
||||
)
|
||||
raise
|
||||
Reference in New Issue
Block a user