feat: add SQL query tool, platform debug, loyalty settings, and multi-module improvements
Some checks failed
Some checks failed
- Add admin SQL query tool with saved queries, schema explorer presets, and collapsible category sections (dev_tools module) - Add platform debug tool for admin diagnostics - Add loyalty settings page with owner-only access control - Fix loyalty settings owner check (use currentUser instead of window.__userData) - Replace HTTPException with AuthorizationException in loyalty routes - Expand loyalty module with PIN service, Apple Wallet, program management - Improve store login with platform detection and multi-platform support - Update billing feature gates and subscription services - Add store platform sync improvements and remove is_primary column - Add unit tests for loyalty (PIN, points, stamps, program services) - Update i18n translations across dev_tools locales Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -352,3 +352,98 @@ class TestDeleteProgram:
|
||||
"""Deleting non-existent program raises exception."""
|
||||
with pytest.raises(LoyaltyProgramNotFoundException):
|
||||
self.service.delete_program(db, 999999)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Stats
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.loyalty
|
||||
class TestGetProgramStats:
|
||||
"""Tests for get_program_stats."""
|
||||
|
||||
def setup_method(self):
|
||||
self.service = ProgramService()
|
||||
|
||||
def test_stats_returns_all_fields(self, db, ps_program):
|
||||
"""Stats response includes all required fields."""
|
||||
stats = self.service.get_program_stats(db, ps_program.id)
|
||||
|
||||
assert "total_cards" in stats
|
||||
assert "active_cards" in stats
|
||||
assert "new_this_month" in stats
|
||||
assert "total_points_balance" in stats
|
||||
assert "avg_points_per_member" in stats
|
||||
assert "transactions_30d" in stats
|
||||
assert "points_issued_30d" in stats
|
||||
assert "points_redeemed_30d" in stats
|
||||
assert "points_this_month" in stats
|
||||
assert "points_redeemed_this_month" in stats
|
||||
assert "estimated_liability_cents" in stats
|
||||
|
||||
def test_stats_empty_program(self, db, ps_program):
|
||||
"""Stats for program with no cards."""
|
||||
stats = self.service.get_program_stats(db, ps_program.id)
|
||||
|
||||
assert stats["total_cards"] == 0
|
||||
assert stats["active_cards"] == 0
|
||||
assert stats["new_this_month"] == 0
|
||||
assert stats["total_points_balance"] == 0
|
||||
assert stats["avg_points_per_member"] == 0
|
||||
|
||||
def test_stats_with_cards(self, db, ps_program, ps_merchant):
|
||||
"""Stats reflect actual card data."""
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from app.modules.customers.models.customer import Customer
|
||||
from app.modules.loyalty.models import LoyaltyCard
|
||||
from app.modules.tenancy.models import Store
|
||||
|
||||
uid_store = uuid.uuid4().hex[:8]
|
||||
store = Store(
|
||||
merchant_id=ps_merchant.id,
|
||||
store_code=f"STAT_{uid_store.upper()}",
|
||||
subdomain=f"stat{uid_store}",
|
||||
name=f"Stats Store {uid_store}",
|
||||
is_active=True,
|
||||
is_verified=True,
|
||||
)
|
||||
db.add(store)
|
||||
db.flush()
|
||||
|
||||
# Create cards with customers
|
||||
for i in range(3):
|
||||
uid = uuid.uuid4().hex[:8]
|
||||
customer = Customer(
|
||||
email=f"stat_{uid}@test.com",
|
||||
first_name="Stat",
|
||||
last_name=f"Customer{i}",
|
||||
hashed_password="!unused!", # noqa: SEC001
|
||||
customer_number=f"SC-{uid.upper()}",
|
||||
store_id=store.id,
|
||||
is_active=True,
|
||||
)
|
||||
db.add(customer)
|
||||
db.flush()
|
||||
|
||||
card = LoyaltyCard(
|
||||
merchant_id=ps_merchant.id,
|
||||
program_id=ps_program.id,
|
||||
customer_id=customer.id,
|
||||
card_number=f"STAT-{i}-{uuid.uuid4().hex[:6]}",
|
||||
points_balance=100 * (i + 1),
|
||||
total_points_earned=100 * (i + 1),
|
||||
is_active=True,
|
||||
last_activity_at=datetime.now(UTC),
|
||||
)
|
||||
db.add(card)
|
||||
db.commit()
|
||||
|
||||
stats = self.service.get_program_stats(db, ps_program.id)
|
||||
|
||||
assert stats["total_cards"] == 3
|
||||
assert stats["active_cards"] == 3
|
||||
assert stats["total_points_balance"] == 600 # 100+200+300
|
||||
assert stats["avg_points_per_member"] == 200.0 # 600/3
|
||||
|
||||
Reference in New Issue
Block a user