Files
westfarn f3e34f5099 Add shopper accounts, reviews, tracking, and seed_demo (#10)
## Summary
- Closes #9. Port shop-gated buyer accounts (register/login/profile, password reset, order history), purchase-gated reviews, and Stripe customer ids. Cards stay on Stripe.
- Slim public contact form to email + interest + message. Shipping tracking status, scan events, paste-in numbers, and EasyPost webhook when FEATURE_SHIPPING is on.
- Add template-neutral `seed_demo` (generic SKUs, demo shoppers, guest orders, tracking stubs). Prod refused. `--reset` deletes tagged demo rows after invoices.

## Test plan
- [ ] `cd site && uv run python manage.py test` (173 tests passed locally)
- [ ] Shop on: `/account/register`, login, profile, password reset, claim guest orders by email
- [ ] Shop off: `/account/` URLs absent; Sign in / Account links hidden
- [ ] Checkout as logged-in buyer stores `Order.user` and Stripe customer id; product review only after paid purchase
- [ ] Shipping on: portal paste-in tracking + EasyPost webhook updates status; shipping off: orders still work without tracking UI
- [ ] `seed_demo` refused in prod; `--reset` rebuilds tagged catalog/shoppers without deleting staff invoices

Reviewed-on: #10
2026-09-21 13:29:36 -07:00

129 lines
4.6 KiB
Python

from django.contrib.auth import get_user_model
from django.contrib.auth.tokens import default_token_generator
from django.core import mail
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from accounts.models import CustomerProfile
from shop.models import Order
User = get_user_model()
class CustomerAccountTests(TestCase):
def test_register_login_and_profile(self):
client = Client()
response = client.post(
reverse("account:register"),
{
"email": "buyer@example.com",
"first_name": "Pat",
"last_name": "Lee",
"password1": "s3cure-pass-123",
"password2": "s3cure-pass-123",
},
)
self.assertEqual(response.status_code, 302)
user = User.objects.get(email="buyer@example.com")
self.assertEqual(user.username, "buyer@example.com")
self.assertFalse(user.is_staff)
self.assertTrue(CustomerProfile.objects.filter(user=user).exists())
client.logout()
login = client.post(
reverse("account:login"),
{"username": "buyer@example.com", "password": "s3cure-pass-123"},
)
self.assertEqual(login.status_code, 302)
save = client.post(
reverse("account:profile"),
{
"first_name": "Patricia",
"last_name": "Lee",
"phone": "6305550100",
"address_line1": "10 Main St",
"address_city": "Aurora",
"address_state": "IL",
"address_zip": "60505",
},
)
self.assertEqual(save.status_code, 302)
user.refresh_from_db()
profile = user.customer_profile
self.assertEqual(user.first_name, "Patricia")
self.assertEqual(profile.phone, "6305550100")
self.assertEqual(profile.shipping_address.get("line1"), "10 Main St")
def test_register_claims_guest_orders(self):
order = Order.objects.create(
number="ORD-CLAIM-001",
email="buyer@example.com",
status=Order.Status.PAID,
amount="18.00",
)
client = Client()
client.post(
reverse("account:register"),
{
"email": "buyer@example.com",
"password1": "s3cure-pass-123",
"password2": "s3cure-pass-123",
},
)
order.refresh_from_db()
self.assertEqual(order.user.email, "buyer@example.com")
history = client.get(reverse("account:orders"))
self.assertEqual(history.status_code, 200)
self.assertContains(history, "ORD-CLAIM-001")
def test_customer_cannot_open_portal(self):
User.objects.create_user(
username="buyer@example.com",
email="buyer@example.com",
password="s3cure-pass-123",
)
client = Client()
client.login(username="buyer@example.com", password="s3cure-pass-123")
response = client.get(reverse("dashboard:home"))
self.assertEqual(response.status_code, 302)
self.assertEqual(response["Location"], reverse("account:home"))
def test_password_reset_sends_mail_and_sets_new_password(self):
user = User.objects.create_user(
username="buyer@example.com",
email="buyer@example.com",
password="s3cure-pass-123",
)
client = Client()
login_page = client.get(reverse("account:login"))
self.assertContains(login_page, reverse("account:password_reset"))
posted = client.post(
reverse("account:password_reset"),
{"email": "buyer@example.com"},
)
self.assertEqual(posted.status_code, 302)
self.assertEqual(len(mail.outbox), 1)
self.assertIn("password-reset", mail.outbox[0].body)
uid = urlsafe_base64_encode(force_bytes(user.pk))
token = default_token_generator.make_token(user)
confirm_url = reverse(
"account:password_reset_confirm",
kwargs={"uidb64": uid, "token": token},
)
bounced = client.get(confirm_url)
self.assertEqual(bounced.status_code, 302)
set_url = bounced["Location"]
saved = client.post(
set_url,
{
"new_password1": "n3wer-pass-456",
"new_password2": "n3wer-pass-456",
},
)
self.assertEqual(saved.status_code, 302)
user.refresh_from_db()
self.assertTrue(user.check_password("n3wer-pass-456"))