feat(loyalty): Google Wallet production readiness — 10 hardening items
Some checks failed
Some checks failed
- Fix rate limiter to extract real client IP and handle sync/async endpoints - Rate-limit public enrollment (10/min) and program info (30/min) endpoints - Add 409 Conflict to non-retryable status codes in retry decorator - Cache private key in get_save_url() to avoid re-reading JSON per call - Make update_class() return bool success status with error-level logging - Move Google Wallet config from core to loyalty module config - Document time.sleep() safety in retry decorator (threadpool execution) - Add per-card retry (1 retry, 2s delay) to wallet sync task - Add logo URL reachability check (HEAD request) to validate_config() - Add 26 comprehensive unit tests for GoogleWalletService Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -224,15 +224,15 @@ class TestCreateWalletObjectsApple:
|
||||
class TestGoogleWalletCreateObject:
|
||||
"""Tests that GoogleWalletService.create_object populates DB fields correctly."""
|
||||
|
||||
@patch("app.modules.loyalty.services.google_wallet_service.settings")
|
||||
def test_create_object_sets_google_object_id(self, mock_settings, db, wt_card, wt_program):
|
||||
@patch("app.modules.loyalty.services.google_wallet_service.config")
|
||||
def test_create_object_sets_google_object_id(self, mock_config, db, wt_card, wt_program):
|
||||
"""create_object sets card.google_object_id in the database."""
|
||||
from app.modules.loyalty.services.google_wallet_service import (
|
||||
GoogleWalletService,
|
||||
)
|
||||
|
||||
mock_settings.loyalty_google_issuer_id = "1234567890"
|
||||
mock_settings.loyalty_google_service_account_json = "/fake/path.json"
|
||||
mock_config.google_issuer_id = "1234567890"
|
||||
mock_config.google_service_account_json = "/fake/path.json"
|
||||
|
||||
# Pre-set class_id on program so create_class isn't called
|
||||
wt_program.google_class_id = "1234567890.loyalty_program_1"
|
||||
@@ -255,15 +255,15 @@ class TestGoogleWalletCreateObject:
|
||||
db.refresh(wt_card)
|
||||
assert wt_card.google_object_id == expected_object_id
|
||||
|
||||
@patch("app.modules.loyalty.services.google_wallet_service.settings")
|
||||
def test_create_object_handles_409_conflict(self, mock_settings, db, wt_card, wt_program):
|
||||
@patch("app.modules.loyalty.services.google_wallet_service.config")
|
||||
def test_create_object_handles_409_conflict(self, mock_config, db, wt_card, wt_program):
|
||||
"""create_object handles 409 (already exists) by still setting the object_id."""
|
||||
from app.modules.loyalty.services.google_wallet_service import (
|
||||
GoogleWalletService,
|
||||
)
|
||||
|
||||
mock_settings.loyalty_google_issuer_id = "1234567890"
|
||||
mock_settings.loyalty_google_service_account_json = "/fake/path.json"
|
||||
mock_config.google_issuer_id = "1234567890"
|
||||
mock_config.google_service_account_json = "/fake/path.json"
|
||||
|
||||
wt_program.google_class_id = "1234567890.loyalty_program_1"
|
||||
db.commit()
|
||||
@@ -282,15 +282,15 @@ class TestGoogleWalletCreateObject:
|
||||
db.refresh(wt_card)
|
||||
assert wt_card.google_object_id == f"1234567890.loyalty_card_{wt_card.id}"
|
||||
|
||||
@patch("app.modules.loyalty.services.google_wallet_service.settings")
|
||||
def test_create_class_sets_google_class_id(self, mock_settings, db, wt_program):
|
||||
@patch("app.modules.loyalty.services.google_wallet_service.config")
|
||||
def test_create_class_sets_google_class_id(self, mock_config, db, wt_program):
|
||||
"""create_class sets program.google_class_id in the database."""
|
||||
from app.modules.loyalty.services.google_wallet_service import (
|
||||
GoogleWalletService,
|
||||
)
|
||||
|
||||
mock_settings.loyalty_google_issuer_id = "1234567890"
|
||||
mock_settings.loyalty_google_service_account_json = "/fake/path.json"
|
||||
mock_config.google_issuer_id = "1234567890"
|
||||
mock_config.google_service_account_json = "/fake/path.json"
|
||||
|
||||
assert wt_program.google_class_id is None
|
||||
|
||||
@@ -322,17 +322,17 @@ class TestGoogleWalletCreateObject:
|
||||
class TestEnrollmentWalletCreation:
|
||||
"""Tests that enrollment triggers wallet object creation and populates DB fields."""
|
||||
|
||||
@patch("app.modules.loyalty.services.google_wallet_service.settings")
|
||||
@patch("app.modules.loyalty.services.google_wallet_service.config")
|
||||
def test_enrollment_creates_google_wallet_object(
|
||||
self, mock_settings, db, wt_program, test_merchant, test_customer
|
||||
self, mock_config, db, wt_program, test_merchant, test_customer
|
||||
):
|
||||
"""Full enrollment flow creates Google Wallet class + object in DB."""
|
||||
from app.modules.loyalty.services.google_wallet_service import (
|
||||
google_wallet_service,
|
||||
)
|
||||
|
||||
mock_settings.loyalty_google_issuer_id = "1234567890"
|
||||
mock_settings.loyalty_google_service_account_json = "/fake/path.json"
|
||||
mock_config.google_issuer_id = "1234567890"
|
||||
mock_config.google_service_account_json = "/fake/path.json"
|
||||
|
||||
# Mock the HTTP client to simulate Google API success
|
||||
mock_http = MagicMock()
|
||||
@@ -373,13 +373,13 @@ class TestEnrollmentWalletCreation:
|
||||
# Wallet fields should be None since not configured
|
||||
assert card.google_object_id is None
|
||||
|
||||
@patch("app.modules.loyalty.services.google_wallet_service.settings")
|
||||
@patch("app.modules.loyalty.services.google_wallet_service.config")
|
||||
def test_enrollment_with_apple_wallet_sets_serial(
|
||||
self, mock_settings, db, wt_program_with_apple, test_customer
|
||||
self, mock_config, db, wt_program_with_apple, test_customer
|
||||
):
|
||||
"""Enrollment sets apple_serial_number when program has apple_pass_type_id."""
|
||||
mock_settings.loyalty_google_issuer_id = None
|
||||
mock_settings.loyalty_google_service_account_json = None
|
||||
mock_config.google_issuer_id = None
|
||||
mock_config.google_service_account_json = None
|
||||
|
||||
from app.modules.loyalty.services.card_service import card_service
|
||||
card = card_service.enroll_customer(
|
||||
|
||||
Reference in New Issue
Block a user