265 lines
9.9 KiB
Python
265 lines
9.9 KiB
Python
|
|
import pytest
|
||
|
|
import asyncio
|
||
|
|
from playwright.async_api import expect
|
||
|
|
from datetime import datetime, date
|
||
|
|
from rbox.billing.models import UsageAggregate, Invoice
|
||
|
|
from rbox.models import User
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
class TestBillingIntegrationFlow:
|
||
|
|
|
||
|
|
async def test_01_complete_user_journey(self, page, base_url):
|
||
|
|
await page.goto(f"{base_url}/")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
await page.fill('input[name="username"]', 'testuser')
|
||
|
|
await page.fill('input[name="password"]', 'testpassword123')
|
||
|
|
await page.click('button[type="submit"]')
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
await page.click('text=Files')
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
await page.wait_for_timeout(1000)
|
||
|
|
|
||
|
|
await page.click('text=Billing')
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
await expect(page.locator('.billing-dashboard')).to_be_visible()
|
||
|
|
await page.wait_for_timeout(2000)
|
||
|
|
|
||
|
|
async def test_02_verify_usage_tracking_after_operations(self, page, base_url):
|
||
|
|
await page.goto(f"{base_url}/billing")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
initial_storage = await page.locator('.usage-value').first.text_content()
|
||
|
|
|
||
|
|
await page.goto(f"{base_url}/files")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
await page.wait_for_timeout(1000)
|
||
|
|
|
||
|
|
await page.goto(f"{base_url}/billing")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
current_storage = await page.locator('.usage-value').first.text_content()
|
||
|
|
assert current_storage is not None
|
||
|
|
|
||
|
|
async def test_03_verify_cost_calculation_updates(self, page, base_url):
|
||
|
|
await page.goto(f"{base_url}/billing")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
await expect(page.locator('.estimated-cost')).to_be_visible()
|
||
|
|
|
||
|
|
cost_text = await page.locator('.estimated-cost').text_content()
|
||
|
|
assert '$' in cost_text
|
||
|
|
|
||
|
|
await page.reload()
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
new_cost_text = await page.locator('.estimated-cost').text_content()
|
||
|
|
assert '$' in new_cost_text
|
||
|
|
|
||
|
|
async def test_04_verify_subscription_consistency(self, page, base_url):
|
||
|
|
await page.goto(f"{base_url}/billing")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
subscription_badge = page.locator('.subscription-badge')
|
||
|
|
await expect(subscription_badge).to_be_visible()
|
||
|
|
|
||
|
|
badge_classes = await subscription_badge.get_attribute('class')
|
||
|
|
assert 'active' in badge_classes or 'inactive' in badge_classes
|
||
|
|
|
||
|
|
async def test_05_verify_pricing_consistency_across_pages(self, page, base_url):
|
||
|
|
await page.goto(f"{base_url}/billing")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
storage_price_user = await page.locator('.pricing-item:has-text("Storage")').last.text_content()
|
||
|
|
|
||
|
|
await page.goto(f"{base_url}/admin/billing")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
config_value = await page.locator('.pricing-table tbody tr').first.locator('.config-value').text_content()
|
||
|
|
|
||
|
|
assert config_value is not None
|
||
|
|
assert storage_price_user is not None
|
||
|
|
|
||
|
|
async def test_06_admin_changes_reflect_in_user_view(self, page, base_url):
|
||
|
|
await page.goto(f"{base_url}/admin/billing")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
await expect(page.locator('.pricing-table')).to_be_visible()
|
||
|
|
|
||
|
|
await page.goto(f"{base_url}/billing")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
await expect(page.locator('.pricing-card')).to_be_visible()
|
||
|
|
|
||
|
|
async def test_07_navigation_flow_consistency(self, page, base_url):
|
||
|
|
await page.goto(f"{base_url}/")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
await page.click('text=Billing')
|
||
|
|
await page.wait_for_url("**/billing")
|
||
|
|
|
||
|
|
await page.click('text=Dashboard')
|
||
|
|
await page.wait_for_url("**/dashboard")
|
||
|
|
|
||
|
|
await page.click('text=Billing')
|
||
|
|
await page.wait_for_url("**/billing")
|
||
|
|
|
||
|
|
await expect(page.locator('.billing-dashboard')).to_be_visible()
|
||
|
|
|
||
|
|
async def test_08_refresh_maintains_state(self, page, base_url):
|
||
|
|
await page.goto(f"{base_url}/billing")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
usage_before = await page.locator('.usage-value').first.text_content()
|
||
|
|
|
||
|
|
await page.reload()
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
usage_after = await page.locator('.usage-value').first.text_content()
|
||
|
|
|
||
|
|
assert usage_before is not None
|
||
|
|
assert usage_after is not None
|
||
|
|
|
||
|
|
async def test_09_multiple_tabs_data_consistency(self, context, base_url):
|
||
|
|
page1 = await context.new_page()
|
||
|
|
page2 = await context.new_page()
|
||
|
|
|
||
|
|
await page1.goto(f"{base_url}/billing")
|
||
|
|
await page1.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
await page2.goto(f"{base_url}/billing")
|
||
|
|
await page2.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
usage1 = await page1.locator('.usage-value').first.text_content()
|
||
|
|
usage2 = await page2.locator('.usage-value').first.text_content()
|
||
|
|
|
||
|
|
assert usage1 is not None
|
||
|
|
assert usage2 is not None
|
||
|
|
|
||
|
|
await page1.close()
|
||
|
|
await page2.close()
|
||
|
|
|
||
|
|
async def test_10_api_and_ui_data_consistency(self, page, base_url):
|
||
|
|
token = await self._login_and_get_token(page, base_url, 'testuser', 'testpassword123')
|
||
|
|
|
||
|
|
api_response = await page.request.get(
|
||
|
|
f"{base_url}/api/billing/usage/current",
|
||
|
|
headers={"Authorization": f"Bearer {token}"}
|
||
|
|
)
|
||
|
|
api_data = await api_response.json()
|
||
|
|
|
||
|
|
await page.goto(f"{base_url}/billing")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
await expect(page.locator('.usage-card')).to_be_visible()
|
||
|
|
|
||
|
|
assert api_data['storage_gb'] >= 0
|
||
|
|
|
||
|
|
async def test_11_error_handling_invalid_invoice_id(self, page, base_url):
|
||
|
|
token = await self._login_and_get_token(page, base_url, 'testuser', 'testpassword123')
|
||
|
|
|
||
|
|
response = await page.request.get(
|
||
|
|
f"{base_url}/api/billing/invoices/99999",
|
||
|
|
headers={"Authorization": f"Bearer {token}"}
|
||
|
|
)
|
||
|
|
|
||
|
|
assert response.status == 404
|
||
|
|
|
||
|
|
async def test_12_verify_responsive_design_desktop(self, page, base_url):
|
||
|
|
await page.set_viewport_size({"width": 1920, "height": 1080})
|
||
|
|
await page.goto(f"{base_url}/billing")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
await expect(page.locator('.billing-cards')).to_be_visible()
|
||
|
|
|
||
|
|
cards = page.locator('.billing-card')
|
||
|
|
count = await cards.count()
|
||
|
|
assert count >= 3
|
||
|
|
|
||
|
|
async def test_13_verify_responsive_design_tablet(self, page, base_url):
|
||
|
|
await page.set_viewport_size({"width": 768, "height": 1024})
|
||
|
|
await page.goto(f"{base_url}/billing")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
await expect(page.locator('.billing-dashboard')).to_be_visible()
|
||
|
|
|
||
|
|
async def test_14_verify_responsive_design_mobile(self, page, base_url):
|
||
|
|
await page.set_viewport_size({"width": 375, "height": 667})
|
||
|
|
await page.goto(f"{base_url}/billing")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
await expect(page.locator('.billing-dashboard')).to_be_visible()
|
||
|
|
|
||
|
|
async def test_15_performance_page_load_time(self, page, base_url):
|
||
|
|
start_time = asyncio.get_event_loop().time()
|
||
|
|
|
||
|
|
await page.goto(f"{base_url}/billing")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
end_time = asyncio.get_event_loop().time()
|
||
|
|
load_time = end_time - start_time
|
||
|
|
|
||
|
|
assert load_time < 5.0
|
||
|
|
|
||
|
|
async def test_16_verify_no_console_errors(self, page, base_url):
|
||
|
|
errors = []
|
||
|
|
|
||
|
|
page.on("console", lambda msg: errors.append(msg) if msg.type == "error" else None)
|
||
|
|
|
||
|
|
await page.goto(f"{base_url}/billing")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
await page.wait_for_timeout(2000)
|
||
|
|
|
||
|
|
critical_errors = [e for e in errors if 'billing' in str(e).lower()]
|
||
|
|
assert len(critical_errors) == 0
|
||
|
|
|
||
|
|
async def test_17_complete_admin_workflow(self, page, base_url):
|
||
|
|
await page.goto(f"{base_url}/")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
await page.fill('input[name="username"]', 'adminuser')
|
||
|
|
await page.fill('input[name="password"]', 'adminpassword123')
|
||
|
|
await page.click('button[type="submit"]')
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
await page.click('text=Admin')
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
await page.click('text=Billing')
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
await expect(page.locator('.admin-billing')).to_be_visible()
|
||
|
|
await expect(page.locator('.stats-cards')).to_be_visible()
|
||
|
|
await expect(page.locator('.pricing-config-section')).to_be_visible()
|
||
|
|
await expect(page.locator('.invoice-generation-section')).to_be_visible()
|
||
|
|
|
||
|
|
await page.wait_for_timeout(2000)
|
||
|
|
|
||
|
|
async def test_18_end_to_end_billing_lifecycle(self, page, base_url):
|
||
|
|
await page.goto(f"{base_url}/billing")
|
||
|
|
await page.wait_for_load_state("networkidle")
|
||
|
|
|
||
|
|
await expect(page.locator('.billing-dashboard')).to_be_visible()
|
||
|
|
|
||
|
|
await expect(page.locator('.usage-card')).to_be_visible()
|
||
|
|
await expect(page.locator('.cost-card')).to_be_visible()
|
||
|
|
await expect(page.locator('.pricing-card')).to_be_visible()
|
||
|
|
await expect(page.locator('.invoices-section')).to_be_visible()
|
||
|
|
await expect(page.locator('.payment-methods-section')).to_be_visible()
|
||
|
|
|
||
|
|
await page.wait_for_timeout(3000)
|
||
|
|
|
||
|
|
async def _login_and_get_token(self, page, base_url, username, password):
|
||
|
|
response = await page.request.post(
|
||
|
|
f"{base_url}/api/auth/login",
|
||
|
|
data={"username": username, "password": password}
|
||
|
|
)
|
||
|
|
|
||
|
|
if response.ok:
|
||
|
|
data = await response.json()
|
||
|
|
return data.get('access_token', '')
|
||
|
|
|
||
|
|
return ''
|