| """E2E tests for API-key management on the Developer tab.""" |
| from __future__ import annotations |
|
|
| import pytest |
| from playwright.sync_api import Page, expect |
|
|
|
|
| pytestmark = pytest.mark.e2e |
|
|
|
|
| |
| |
| |
|
|
| def _register_user(page: Page, base_url: str, email: str, |
| password: str = "E2E_Test_P@55") -> None: |
| page.goto(base_url, timeout=10000) |
| expect(page.locator("#navbar")).to_be_visible(timeout=5000) |
| page.locator("#nav-auth-logged-out button", has_text="Get Started").first.click() |
| page.wait_for_selector("#register-modal.open", timeout=5000) |
| page.fill("#register-email", email) |
| page.fill("#register-password", password) |
| with page.expect_response("**/api/auth/register", timeout=15000) as resp_info: |
| page.click("#register-submit") |
| assert resp_info.value.status == 201, f"Register failed: {resp_info.value.text()}" |
| page.wait_for_function( |
| "() => typeof state !== 'undefined' && !!state.user", |
| timeout=10000, |
| ) |
| |
| page.wait_for_timeout(600) |
| page.evaluate( |
| "() => { const m = document.getElementById('onboarding-modal');" |
| " if (m) { m.classList.remove('open'); m.style.display = 'none'; } }" |
| ) |
|
|
|
|
| def _open_developer_tab(page: Page) -> None: |
| """Navigate to the Developer tab from the user dropdown (API Keys link).""" |
| tab_btn = page.locator("#tab-developer-btn") |
| expect(tab_btn).to_be_visible(timeout=5000) |
| tab_btn.click() |
| expect(page.locator("#tab-developer")).to_be_visible() |
|
|
|
|
| def _accept_dialogs(page: Page) -> None: |
| """Stub window.confirm to always return true — avoids native dialog timing issues.""" |
| page.evaluate("() => { window.confirm = () => true; }") |
|
|
|
|
| |
| |
| |
|
|
| def test_create_api_key(page: Page, base_url: str, unique_email: str) -> None: |
| """Create an API key via the Developer tab and verify it's listed.""" |
| _register_user(page, base_url, unique_email) |
| _open_developer_tab(page) |
|
|
| |
| page.locator("#tab-developer >> text=+ Create Key").click() |
| page.wait_for_selector("#create-key-modal.open", timeout=5000) |
|
|
| page.fill("#key-label", "E2E Test Key") |
| page.click("#key-submit") |
|
|
| |
| page.wait_for_selector("#new-key-modal.open", timeout=10000) |
| key_value = page.locator("#new-key-value").inner_text().strip() |
| assert key_value.startswith("msk_"), f"Expected key prefix msk_, got: {key_value!r}" |
|
|
| |
| page.locator("#new-key-modal >> text=Done").click() |
|
|
| key_rows = page.locator(".api-key-row") |
| expect(key_rows.first).to_be_visible(timeout=5000) |
| |
| assert "E2E Test Key" in page.locator("#api-keys-list").inner_text() |
|
|
|
|
| def test_revoke_api_key(page: Page, base_url: str, unique_email: str) -> None: |
| """Create a key, revoke it, and verify the row flips to revoked state.""" |
| _register_user(page, base_url, unique_email) |
| _open_developer_tab(page) |
| _accept_dialogs(page) |
|
|
| |
| page.locator("#tab-developer >> text=+ Create Key").click() |
| page.wait_for_selector("#create-key-modal.open", timeout=5000) |
| page.fill("#key-label", "Revoke Me") |
| page.click("#key-submit") |
| page.wait_for_selector("#new-key-modal.open", timeout=10000) |
| page.locator("#new-key-modal >> text=Done").click() |
|
|
| |
| rows = page.locator(".api-key-row") |
| expect(rows.first).to_be_visible(timeout=5000) |
| initial_count = rows.count() |
| assert initial_count >= 1 |
|
|
| |
| key_id = page.evaluate(""" |
| () => { |
| const btn = document.querySelector( |
| '.api-key-row button[onclick^="revokeApiKey"]' |
| ); |
| if (!btn) return null; |
| const m = btn.getAttribute('onclick').match(/revokeApiKey\((\d+)\)/); |
| return m ? parseInt(m[1]) : null; |
| } |
| """) |
| assert key_id is not None, "No revokable API key button found in DOM" |
| page.evaluate(f"() => revokeApiKey({key_id})") |
|
|
| |
| page.wait_for_timeout(3000) |
|
|
| list_text = page.locator("#api-keys-list").inner_text() |
| dev_empty_visible = page.locator("#dev-empty").is_visible() |
|
|
| |
| |
| |
| assert dev_empty_visible or "revok" in list_text.lower(), ( |
| f"Key still appears active after revocation. Current list: {list_text!r}" |
| ) |
|
|