import { test, expect } from '@playwright/test'; test.describe('API Tokens and API Access', () => { test.describe('Personal API Token Management', () => { test('can access API tokens section', async ({ page }) => { await page.goto('/users/settings'); if ((await page.url()).includes('sudo-verify')) { return; } // Look for API tokens section const apiSection = page.locator(':has-text("API"), :has-text("Token")').first(); if (await apiSection.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(apiSection).toBeVisible(); } }); test('shows existing API tokens', async ({ page }) => { await page.goto('/users/settings'); if ((await page.url()).includes('sudo-verify')) { return; } // Look for token list const tokenList = page.locator('table, ul, [data-tokens]').first(); if (await tokenList.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(tokenList).toBeVisible(); } }); test('can generate new API token', async ({ page }) => { await page.goto('/users/settings'); if ((await page.url()).includes('sudo-verify')) { return; } const generateButton = page.locator('button:has-text("Generate"), button:has-text("Create Token")').first(); if (await generateButton.isVisible({ timeout: 2000 }).catch(() => false)) { await generateButton.click(); await page.waitForTimeout(500); // Token creation form or dialog const tokenForm = page.locator('[role="dialog"], form').first(); if (await tokenForm.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(tokenForm).toBeVisible(); } } }); test('new token is displayed only once', async ({ page }) => { await page.goto('/users/settings'); if ((await page.url()).includes('sudo-verify')) { return; } // After generating token, should show warning about copying const warning = page.locator(':has-text("copy"), :has-text("only shown once"), :has-text("save")').first(); if (await warning.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(warning).toBeVisible(); } }); test('can copy token to clipboard', async ({ page }) => { await page.goto('/users/settings'); if ((await page.url()).includes('sudo-verify')) { return; } const copyButton = page.locator('button:has-text("Copy"), [data-copy]').first(); if (await copyButton.isVisible({ timeout: 2000 }).catch(() => false)) { await copyButton.click(); await page.waitForTimeout(300); // Should show copied confirmation const confirmation = page.locator(':has-text("Copied"), .success').first(); if (await confirmation.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(confirmation).toBeVisible(); } } }); test('token name can be set', async ({ page }) => { await page.goto('/users/settings'); if ((await page.url()).includes('sudo-verify')) { return; } const generateButton = page.locator('button:has-text("Generate")').first(); if (await generateButton.isVisible({ timeout: 2000 }).catch(() => false)) { await generateButton.click(); await page.waitForTimeout(300); const nameField = page.locator('input[name*="name"], input[placeholder*="name"]').first(); if (await nameField.isVisible({ timeout: 2000 }).catch(() => false)) { await nameField.fill('E2E Test Token'); await expect(nameField).toBeVisible(); } } }); }); test.describe('Token Permissions', () => { test('can set token scopes', async ({ page }) => { await page.goto('/users/settings'); if ((await page.url()).includes('sudo-verify')) { return; } const generateButton = page.locator('button:has-text("Generate")').first(); if (await generateButton.isVisible({ timeout: 2000 }).catch(() => false)) { await generateButton.click(); await page.waitForTimeout(300); // Look for permission checkboxes const permissionCheckbox = page.locator('input[type="checkbox"], [role="checkbox"]').first(); if (await permissionCheckbox.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(permissionCheckbox).toBeVisible(); } } }); test('shows available scopes', async ({ page }) => { await page.goto('/users/settings'); if ((await page.url()).includes('sudo-verify')) { return; } const generateButton = page.locator('button:has-text("Generate")').first(); if (await generateButton.isVisible({ timeout: 2000 }).catch(() => false)) { await generateButton.click(); await page.waitForTimeout(300); // Look for scope labels const scopeLabel = page.locator(':has-text("read"), :has-text("write"), :has-text("admin")').first(); if (await scopeLabel.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(scopeLabel).toBeVisible(); } } }); }); test.describe('Token Revocation', () => { test('can revoke API token', async ({ page }) => { await page.goto('/users/settings'); if ((await page.url()).includes('sudo-verify')) { return; } const revokeButton = page.locator('button:has-text("Revoke"), button:has-text("Delete")').first(); if (await revokeButton.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(revokeButton).toBeVisible(); } }); test('shows confirmation before revoking', async ({ page }) => { await page.goto('/users/settings'); if ((await page.url()).includes('sudo-verify')) { return; } const revokeButton = page.locator('button:has-text("Revoke")').first(); if (await revokeButton.isVisible({ timeout: 2000 }).catch(() => false)) { await revokeButton.click(); await page.waitForTimeout(300); const confirmation = page.locator(':has-text("confirm"), :has-text("sure"), [role="dialog"]').first(); if (await confirmation.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(confirmation).toBeVisible(); } } }); test('revoked token is removed from list', async ({ page }) => { await page.goto('/users/settings'); if ((await page.url()).includes('sudo-verify')) { return; } const tokenCount = await page.locator('button:has-text("Revoke")').count(); const revokeButton = page.locator('button:has-text("Revoke")').first(); if (await revokeButton.isVisible({ timeout: 2000 }).catch(() => false)) { // Accept confirmation page.once('dialog', dialog => dialog.accept()); await revokeButton.click(); await page.waitForTimeout(1000); // Count should decrease or stay same const newCount = await page.locator('button:has-text("Revoke")').count(); expect(newCount).toBeLessThanOrEqual(tokenCount); } }); }); test.describe('Token Usage Information', () => { test('shows last used timestamp', async ({ page }) => { await page.goto('/users/settings'); if ((await page.url()).includes('sudo-verify')) { return; } const timestamp = page.locator('time, :has-text("Last used"), :has-text("ago")').first(); if (await timestamp.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(timestamp).toBeVisible(); } }); test('shows creation date', async ({ page }) => { await page.goto('/users/settings'); if ((await page.url()).includes('sudo-verify')) { return; } const createdDate = page.locator(':has-text("Created"), time').first(); if (await createdDate.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(createdDate).toBeVisible(); } }); test('shows token expiry if applicable', async ({ page }) => { await page.goto('/users/settings'); if ((await page.url()).includes('sudo-verify')) { return; } const expiry = page.locator(':has-text("Expires"), :has-text("Expiration")').first(); if (await expiry.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(expiry).toBeVisible(); } }); }); test.describe('API Documentation Access', () => { test('can access API documentation', async ({ page }) => { await page.goto('/docs/api'); await expect(page).toHaveURL(/\/docs\/api/); await expect(page.locator('body')).toBeVisible(); }); test('API docs show authentication instructions', async ({ page }) => { await page.goto('/docs/api'); const authSection = page.locator(':has-text("Authentication"), :has-text("Bearer"), :has-text("Authorization")').first(); if (await authSection.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(authSection).toBeVisible(); } }); test('API docs show example requests', async ({ page }) => { await page.goto('/docs/api'); const codeExample = page.locator('code, pre').first(); if (await codeExample.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(codeExample).toBeVisible(); } }); test('API docs list available endpoints', async ({ page }) => { await page.goto('/docs/api'); const endpoint = page.locator(':has-text("/api/"), code:has-text("GET"), code:has-text("POST")').first(); if (await endpoint.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(endpoint).toBeVisible(); } }); test('can try API in interactive docs', async ({ page }) => { await page.goto('/docs/api'); const tryButton = page.locator('button:has-text("Try"), button:has-text("Test")').first(); if (await tryButton.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(tryButton).toBeVisible(); } }); }); test.describe('Rate Limiting', () => { test('shows rate limit information', async ({ page }) => { await page.goto('/docs/api'); const rateLimitInfo = page.locator(':has-text("rate limit"), :has-text("requests per")').first(); if (await rateLimitInfo.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(rateLimitInfo).toBeVisible(); } }); }); test.describe('Token Security', () => { test('warns about token security', async ({ page }) => { await page.goto('/users/settings'); if ((await page.url()).includes('sudo-verify')) { return; } const generateButton = page.locator('button:has-text("Generate")').first(); if (await generateButton.isVisible({ timeout: 2000 }).catch(() => false)) { await generateButton.click(); await page.waitForTimeout(300); // Look for security warning const warning = page.locator(':has-text("secure"), :has-text("secret"), :has-text("keep safe")').first(); if (await warning.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(warning).toBeVisible(); } } }); test('token is masked in list', async ({ page }) => { await page.goto('/users/settings'); if ((await page.url()).includes('sudo-verify')) { return; } // Tokens should be masked (not showing full value) const maskedToken = page.locator(':has-text("***"), code:has-text("...")').first(); if (await maskedToken.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(maskedToken).toBeVisible(); } }); }); });