updates
This commit is contained in:
@@ -12,6 +12,7 @@ from padelnomics.billing.routes import (
|
||||
get_subscription_by_provider_id,
|
||||
is_within_limits,
|
||||
update_subscription_status,
|
||||
upsert_billing_customer,
|
||||
upsert_subscription,
|
||||
)
|
||||
from padelnomics.core import config
|
||||
@@ -40,11 +41,11 @@ class TestGetSubscription:
|
||||
|
||||
class TestUpsertSubscription:
|
||||
async def test_insert_new_subscription(self, db, test_user):
|
||||
await upsert_billing_customer(test_user["id"], "cust_abc")
|
||||
sub_id = await upsert_subscription(
|
||||
user_id=test_user["id"],
|
||||
plan="pro",
|
||||
status="active",
|
||||
provider_customer_id="cust_abc",
|
||||
provider_subscription_id="sub_xyz",
|
||||
current_period_end="2025-06-01T00:00:00Z",
|
||||
)
|
||||
@@ -52,33 +53,30 @@ class TestUpsertSubscription:
|
||||
row = await get_subscription(test_user["id"])
|
||||
assert row["plan"] == "pro"
|
||||
assert row["status"] == "active"
|
||||
assert row["paddle_customer_id"] == "cust_abc"
|
||||
assert row["paddle_subscription_id"] == "sub_xyz"
|
||||
assert row["provider_subscription_id"] == "sub_xyz"
|
||||
assert row["current_period_end"] == "2025-06-01T00:00:00Z"
|
||||
|
||||
async def test_update_existing_subscription(self, db, test_user, create_subscription):
|
||||
original_id = await create_subscription(
|
||||
test_user["id"], plan="starter", status="active",
|
||||
paddle_subscription_id="sub_old",
|
||||
provider_subscription_id="sub_old",
|
||||
)
|
||||
returned_id = await upsert_subscription(
|
||||
user_id=test_user["id"],
|
||||
plan="pro",
|
||||
status="active",
|
||||
provider_customer_id="cust_new",
|
||||
provider_subscription_id="sub_new",
|
||||
provider_subscription_id="sub_old",
|
||||
)
|
||||
assert returned_id == original_id
|
||||
row = await get_subscription(test_user["id"])
|
||||
assert row["plan"] == "pro"
|
||||
assert row["paddle_subscription_id"] == "sub_new"
|
||||
assert row["provider_subscription_id"] == "sub_old"
|
||||
|
||||
async def test_upsert_with_none_period_end(self, db, test_user):
|
||||
await upsert_subscription(
|
||||
user_id=test_user["id"],
|
||||
plan="pro",
|
||||
status="active",
|
||||
provider_customer_id="cust_1",
|
||||
provider_subscription_id="sub_1",
|
||||
current_period_end=None,
|
||||
)
|
||||
@@ -95,8 +93,8 @@ class TestGetSubscriptionByProviderId:
|
||||
result = await get_subscription_by_provider_id("nonexistent")
|
||||
assert result is None
|
||||
|
||||
async def test_finds_by_paddle_subscription_id(self, db, test_user, create_subscription):
|
||||
await create_subscription(test_user["id"], paddle_subscription_id="sub_findme")
|
||||
async def test_finds_by_provider_subscription_id(self, db, test_user, create_subscription):
|
||||
await create_subscription(test_user["id"], provider_subscription_id="sub_findme")
|
||||
result = await get_subscription_by_provider_id("sub_findme")
|
||||
assert result is not None
|
||||
assert result["user_id"] == test_user["id"]
|
||||
@@ -108,14 +106,14 @@ class TestGetSubscriptionByProviderId:
|
||||
|
||||
class TestUpdateSubscriptionStatus:
|
||||
async def test_updates_status(self, db, test_user, create_subscription):
|
||||
await create_subscription(test_user["id"], status="active", paddle_subscription_id="sub_upd")
|
||||
await create_subscription(test_user["id"], status="active", provider_subscription_id="sub_upd")
|
||||
await update_subscription_status("sub_upd", status="cancelled")
|
||||
row = await get_subscription(test_user["id"])
|
||||
assert row["status"] == "cancelled"
|
||||
assert row["updated_at"] is not None
|
||||
|
||||
async def test_updates_extra_fields(self, db, test_user, create_subscription):
|
||||
await create_subscription(test_user["id"], paddle_subscription_id="sub_extra")
|
||||
await create_subscription(test_user["id"], provider_subscription_id="sub_extra")
|
||||
await update_subscription_status(
|
||||
"sub_extra",
|
||||
status="active",
|
||||
@@ -128,7 +126,7 @@ class TestUpdateSubscriptionStatus:
|
||||
assert row["current_period_end"] == "2026-01-01T00:00:00Z"
|
||||
|
||||
async def test_noop_for_unknown_provider_id(self, db, test_user, create_subscription):
|
||||
await create_subscription(test_user["id"], paddle_subscription_id="sub_known", status="active")
|
||||
await create_subscription(test_user["id"], provider_subscription_id="sub_known", status="active")
|
||||
await update_subscription_status("sub_unknown", status="expired")
|
||||
row = await get_subscription(test_user["id"])
|
||||
assert row["status"] == "active" # unchanged
|
||||
@@ -301,7 +299,7 @@ class TestLimitsHypothesis:
|
||||
# Use upsert to avoid duplicate inserts across Hypothesis examples
|
||||
await upsert_subscription(
|
||||
user_id=test_user["id"], plan="pro", status="active",
|
||||
provider_customer_id="cust_hyp", provider_subscription_id="sub_hyp",
|
||||
provider_subscription_id="sub_hyp",
|
||||
)
|
||||
result = await is_within_limits(test_user["id"], "items", count)
|
||||
assert result is True
|
||||
|
||||
@@ -82,7 +82,7 @@ class TestManageRoute:
|
||||
assert response.status_code in (302, 303, 307)
|
||||
|
||||
async def test_redirects_to_portal(self, auth_client, db, test_user, create_subscription):
|
||||
await create_subscription(test_user["id"], paddle_subscription_id="sub_test")
|
||||
await create_subscription(test_user["id"], provider_subscription_id="sub_test")
|
||||
|
||||
mock_sub = MagicMock()
|
||||
mock_sub.management_urls.update_payment_method = "https://paddle.com/manage/test_123"
|
||||
@@ -108,7 +108,7 @@ class TestCancelRoute:
|
||||
assert response.status_code in (302, 303, 307)
|
||||
|
||||
async def test_cancels_subscription(self, auth_client, db, test_user, create_subscription):
|
||||
await create_subscription(test_user["id"], paddle_subscription_id="sub_test")
|
||||
await create_subscription(test_user["id"], provider_subscription_id="sub_test")
|
||||
|
||||
mock_client = MagicMock()
|
||||
with patch("padelnomics.billing.routes._paddle_client", return_value=mock_client):
|
||||
@@ -123,7 +123,7 @@ class TestCancelRoute:
|
||||
|
||||
from quart import Blueprint # noqa: E402
|
||||
|
||||
from padelnomics.billing.routes import subscription_required # noqa: E402
|
||||
from padelnomics.auth.routes import subscription_required # noqa: E402
|
||||
|
||||
test_bp = Blueprint("test", __name__)
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ class TestWebhookSubscriptionActivated:
|
||||
|
||||
class TestWebhookSubscriptionUpdated:
|
||||
async def test_updates_subscription_status(self, client, db, test_user, create_subscription):
|
||||
await create_subscription(test_user["id"], status="active", paddle_subscription_id="sub_test456")
|
||||
await create_subscription(test_user["id"], status="active", provider_subscription_id="sub_test456")
|
||||
|
||||
payload = make_webhook_payload(
|
||||
"subscription.updated",
|
||||
@@ -197,7 +197,7 @@ class TestWebhookSubscriptionUpdated:
|
||||
|
||||
class TestWebhookSubscriptionCanceled:
|
||||
async def test_marks_subscription_cancelled(self, client, db, test_user, create_subscription):
|
||||
await create_subscription(test_user["id"], status="active", paddle_subscription_id="sub_test456")
|
||||
await create_subscription(test_user["id"], status="active", provider_subscription_id="sub_test456")
|
||||
|
||||
payload = make_webhook_payload(
|
||||
"subscription.canceled",
|
||||
@@ -219,7 +219,7 @@ class TestWebhookSubscriptionCanceled:
|
||||
|
||||
class TestWebhookSubscriptionPastDue:
|
||||
async def test_marks_subscription_past_due(self, client, db, test_user, create_subscription):
|
||||
await create_subscription(test_user["id"], status="active", paddle_subscription_id="sub_test456")
|
||||
await create_subscription(test_user["id"], status="active", provider_subscription_id="sub_test456")
|
||||
|
||||
payload = make_webhook_payload(
|
||||
"subscription.past_due",
|
||||
@@ -251,7 +251,7 @@ class TestWebhookSubscriptionPastDue:
|
||||
])
|
||||
async def test_event_status_transitions(client, db, test_user, create_subscription, event_type, expected_status):
|
||||
if event_type != "subscription.activated":
|
||||
await create_subscription(test_user["id"], paddle_subscription_id="sub_test456")
|
||||
await create_subscription(test_user["id"], provider_subscription_id="sub_test456")
|
||||
|
||||
payload = make_webhook_payload(event_type, user_id=str(test_user["id"]))
|
||||
payload_bytes = json.dumps(payload).encode()
|
||||
|
||||
@@ -744,11 +744,23 @@ class TestRouteRegistration:
|
||||
# ════════════════════════════════════════════════════════════
|
||||
|
||||
@pytest.fixture
|
||||
async def admin_client(app):
|
||||
"""Test client with admin session."""
|
||||
async def admin_client(app, db):
|
||||
"""Test client with admin user (has admin role)."""
|
||||
from datetime import datetime
|
||||
now = datetime.utcnow().isoformat()
|
||||
async with db.execute(
|
||||
"INSERT INTO users (email, name, created_at) VALUES (?, ?, ?)",
|
||||
("admin@test.com", "Admin", now),
|
||||
) as cursor:
|
||||
admin_id = cursor.lastrowid
|
||||
await db.execute(
|
||||
"INSERT INTO user_roles (user_id, role) VALUES (?, 'admin')", (admin_id,)
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
async with app.test_client() as c:
|
||||
async with c.session_transaction() as sess:
|
||||
sess["is_admin"] = True
|
||||
sess["user_id"] = admin_id
|
||||
yield c
|
||||
|
||||
|
||||
|
||||
@@ -314,6 +314,7 @@ class TestQuoteRequest:
|
||||
"stakeholder_type": "entrepreneur",
|
||||
"contact_name": "Test User",
|
||||
"contact_email": "test@example.com",
|
||||
"contact_phone": "+491234567890",
|
||||
"csrf_token": csrf,
|
||||
},
|
||||
)
|
||||
@@ -345,6 +346,7 @@ class TestQuoteRequest:
|
||||
"stakeholder_type": "entrepreneur",
|
||||
"contact_name": "Guest",
|
||||
"contact_email": "guest@example.com",
|
||||
"contact_phone": "+491234567890",
|
||||
"csrf_token": csrf,
|
||||
},
|
||||
)
|
||||
@@ -374,6 +376,7 @@ class TestQuoteRequest:
|
||||
"stakeholder_type": "entrepreneur",
|
||||
"contact_name": "Auth User",
|
||||
"contact_email": "test@example.com", # matches test_user email
|
||||
"contact_phone": "+491234567890",
|
||||
"csrf_token": csrf,
|
||||
},
|
||||
)
|
||||
@@ -404,6 +407,7 @@ class TestQuoteRequest:
|
||||
"stakeholder_type": "developer",
|
||||
"contact_name": "Venue Search",
|
||||
"contact_email": "venue@example.com",
|
||||
"contact_phone": "+491234567890",
|
||||
"csrf_token": csrf,
|
||||
},
|
||||
)
|
||||
@@ -432,6 +436,7 @@ class TestQuoteRequest:
|
||||
"stakeholder_type": "tennis_club",
|
||||
"contact_name": "Club Owner",
|
||||
"contact_email": "club@example.com",
|
||||
"contact_phone": "+491234567890",
|
||||
"csrf_token": csrf,
|
||||
},
|
||||
)
|
||||
@@ -460,6 +465,7 @@ class TestQuoteRequest:
|
||||
"stakeholder_type": "entrepreneur",
|
||||
"contact_name": "Context Test",
|
||||
"contact_email": "ctx@example.com",
|
||||
"contact_phone": "+491234567890",
|
||||
"csrf_token": csrf,
|
||||
},
|
||||
)
|
||||
@@ -487,7 +493,7 @@ class TestQuoteRequest:
|
||||
assert resp.status_code == 422
|
||||
data = await resp.get_json()
|
||||
assert data["ok"] is False
|
||||
assert len(data["errors"]) >= 3 # country, timeline, stakeholder_type + name + email
|
||||
assert len(data["errors"]) >= 3 # country, timeline, stakeholder_type + name + email + phone
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
@@ -505,6 +511,7 @@ class TestQuoteVerification:
|
||||
"stakeholder_type": "entrepreneur",
|
||||
"contact_name": "Verify Test",
|
||||
"contact_email": "verify@example.com",
|
||||
"contact_phone": "+491234567890",
|
||||
}
|
||||
|
||||
async def _submit_guest_quote(self, client, db, email="verify@example.com"):
|
||||
|
||||
Reference in New Issue
Block a user