"""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 # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- 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, ) # Force-close any onboarding overlay that might intercept clicks. 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; }") # --------------------------------------------------------------------------- # Tests # --------------------------------------------------------------------------- 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) # Click "+ Create Key" and fill in the label. 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") # Newly created key modal appears with the plaintext key value. 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}" # Close the modal and verify the key now appears in the list. page.locator("#new-key-modal >> text=Done").click() key_rows = page.locator(".api-key-row") expect(key_rows.first).to_be_visible(timeout=5000) # At least one row should show our label. 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) # Create a key first. 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() # Find the row and click "Revoke". rows = page.locator(".api-key-row") expect(rows.first).to_be_visible(timeout=5000) initial_count = rows.count() assert initial_count >= 1 # Get the first active key ID from the DOM, then invoke revokeApiKey() directly. 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})") # Wait for the API call and list re-render. page.wait_for_timeout(3000) list_text = page.locator("#api-keys-list").inner_text() dev_empty_visible = page.locator("#dev-empty").is_visible() # Success conditions: # 1. The list is now empty (dev-empty showing), OR # 2. The row is still visible but marked as "Revoked". assert dev_empty_visible or "revok" in list_text.lower(), ( f"Key still appears active after revocation. Current list: {list_text!r}" )