From c776b5748a9f14862d8273f5c5cfbea665b38ca0 Mon Sep 17 00:00:00 2001 From: Trezy Date: Wed, 24 Jun 2026 06:57:35 -0500 Subject: [PATCH] test: fix broken tests Signed-off-by: Trezy Signed-off-by: Trezy --- src/auth/middleware.rs | 2 +- src/service_identity.rs | 2 +- src/setup.rs | 3 +- tests/e2e_admin_service_identity.rs | 7 ++-- tests/e2e_service_identity.rs | 25 ++++++++++--- web/src/app/dashboard/lexicons/page.tsx | 3 +- .../app/dashboard/settings/scripts/page.tsx | 2 +- .../components/setup/setup-attach-auth.tsx | 2 +- web/tests/e2e/lexicon-services.spec.ts | 17 +++++++-- web/tests/e2e/record-delete.spec.ts | 5 +-- web/tests/e2e/script-delete.spec.ts | 5 ++- .../e2e/service-identity-settings.spec.ts | 36 +++++++++++++++---- web/tests/e2e/setup-attach-account.spec.ts | 2 +- web/tests/e2e/setup-didplc.spec.ts | 19 ++++++---- web/tests/e2e/setup-features.spec.ts | 6 ++-- web/tests/e2e/setup-wizard.spec.ts | 2 +- 16 files changed, 98 insertions(+), 40 deletions(-) diff --git a/src/auth/middleware.rs b/src/auth/middleware.rs index 59a0667..1ce3612 100644 --- a/src/auth/middleware.rs +++ b/src/auth/middleware.rs @@ -342,7 +342,7 @@ async fn try_parse_service_auth( let instance_did = match &identity.mode { crate::service_identity::IdentityMode::DidWeb => { let h = host.ok_or_else(|| AppError::Auth("missing Host header for did:web".into()))?; - format!("did:web:{h}") + format!("did:web:{}", h.replace(':', "%3A")) } _ => identity .did diff --git a/src/service_identity.rs b/src/service_identity.rs index 15d645e..342de75 100644 --- a/src/service_identity.rs +++ b/src/service_identity.rs @@ -189,7 +189,7 @@ pub fn generate_did_document( return None; } - let did = format!("did:web:{host}"); + let did = format!("did:web:{}", host.replace(':', "%3A")); let verification_method = serde_json::json!([{ "id": format!("{did}#atproto"), diff --git a/src/setup.rs b/src/setup.rs index 11269b3..3a83dbf 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -13,6 +13,7 @@ use serde::Deserialize; use crate::admin::auth::UserAuth; use crate::auth::COOKIE_NAME; +use crate::auth::middleware::Claims; use crate::event_log::{EventLog, Severity, log_event}; use crate::service_identity::{self, IdentityMode}; use crate::{AppState, error::AppError}; @@ -39,7 +40,7 @@ pub fn routes() -> Router { } async fn status( - _auth: UserAuth, + _auth: Claims, State(state): State, ) -> Result, AppError> { let status = service_identity::get_setup_status(&state.db, state.db_backend).await?; diff --git a/tests/e2e_admin_service_identity.rs b/tests/e2e_admin_service_identity.rs index 314a748..f7a319a 100644 --- a/tests/e2e_admin_service_identity.rs +++ b/tests/e2e_admin_service_identity.rs @@ -48,7 +48,7 @@ async fn get_identity_returns_null_when_not_configured() { async fn get_identity_returns_identity_after_setup() { common::require_db!(); let mut app = TestApp::new().await; - let did = app.setup_did_web().await; + app.setup_did_web().await; let cookie = app.admin_cookie(); let resp = app @@ -67,7 +67,10 @@ async fn get_identity_returns_identity_after_setup() { assert_eq!(resp.status(), StatusCode::OK); let body = json_body(resp).await; assert_eq!(body["mode"], "did_web"); - assert_eq!(body["did"], did); + assert!( + body["did"].is_null(), + "did:web derives DID from host, not stored" + ); assert_eq!(body["setup_complete"], true); } diff --git a/tests/e2e_service_identity.rs b/tests/e2e_service_identity.rs index 59eb175..68af8d6 100644 --- a/tests/e2e_service_identity.rs +++ b/tests/e2e_service_identity.rs @@ -226,7 +226,7 @@ async fn did_doc_empty_services() { .oneshot( Request::builder() .uri("/.well-known/did.json") - .header("host", "127.0.0.1") + .header("host", "127.0.0.1:0") .body(Body::empty()) .unwrap(), ) @@ -260,7 +260,7 @@ async fn did_doc_with_entries() { .oneshot( Request::builder() .uri("/.well-known/did.json") - .header("host", "127.0.0.1") + .header("host", "127.0.0.1:0") .body(Body::empty()) .unwrap(), ) @@ -284,7 +284,7 @@ async fn did_doc_with_entries() { .oneshot( Request::builder() .uri("/.well-known/did.json") - .header("host", "127.0.0.1") + .header("host", "127.0.0.1:0") .body(Body::empty()) .unwrap(), ) @@ -325,6 +325,7 @@ async fn service_auth_query_allowed() { Request::builder() .uri("/xrpc/games.gamesgamesgamesgames.listGames") .header("authorization", &auth) + .header("host", "127.0.0.1:0") .body(Body::empty()) .unwrap(), ) @@ -358,6 +359,7 @@ async fn service_auth_query_denied() { Request::builder() .uri("/xrpc/games.gamesgamesgamesgames.listGames") .header("authorization", &auth) + .header("host", "127.0.0.1:0") .body(Body::empty()) .unwrap(), ) @@ -396,6 +398,7 @@ async fn service_auth_specific_xrpc_allowed() { Request::builder() .uri("/xrpc/games.gamesgamesgamesgames.listGames") .header("authorization", &auth) + .header("host", "127.0.0.1:0") .body(Body::empty()) .unwrap(), ) @@ -438,6 +441,7 @@ async fn service_auth_procedure_allowed() { .method("POST") .uri("/xrpc/games.gamesgamesgamesgames.createGame") .header("authorization", &auth) + .header("host", "127.0.0.1:0") .header("content-type", "application/json") .body(Body::from( serde_json::to_vec(&json!({"title": "test"})).unwrap(), @@ -476,6 +480,7 @@ async fn service_auth_procedure_denied() { .method("POST") .uri("/xrpc/games.gamesgamesgamesgames.createGame") .header("authorization", &auth) + .header("host", "127.0.0.1:0") .header("content-type", "application/json") .body(Body::from( serde_json::to_vec(&json!({"title": "test"})).unwrap(), @@ -523,6 +528,7 @@ async fn token_scope_enforcement() { .method("POST") .uri("/xrpc/games.gamesgamesgamesgames.createGame") .header("authorization", &auth) + .header("host", "127.0.0.1:0") .header("content-type", "application/json") .body(Body::from( serde_json::to_vec(&json!({"title": "test"})).unwrap(), @@ -707,6 +713,7 @@ async fn nonexistent_fragment_denies_access() { Request::builder() .uri("/xrpc/games.gamesgamesgamesgames.listGames") .header("authorization", &auth) + .header("host", "127.0.0.1:0") .body(Body::empty()) .unwrap(), ) @@ -769,6 +776,7 @@ async fn multiple_entries_matched_by_fragment() { Request::builder() .uri("/xrpc/games.gamesgamesgamesgames.listGames") .header("authorization", &auth_chess) + .header("host", "127.0.0.1:0") .body(Body::empty()) .unwrap(), ) @@ -788,6 +796,7 @@ async fn multiple_entries_matched_by_fragment() { Request::builder() .uri("/xrpc/games.gamesgamesgamesgames.listGames") .header("authorization", &auth_checkers) + .header("host", "127.0.0.1:0") .body(Body::empty()) .unwrap(), ) @@ -826,6 +835,7 @@ async fn scope_check_applies_with_access_mode_all() { .method("POST") .uri("/xrpc/games.gamesgamesgamesgames.createGame") .header("authorization", &auth) + .header("host", "127.0.0.1:0") .header("content-type", "application/json") .body(Body::from( serde_json::to_vec(&json!({"title": "test"})).unwrap(), @@ -1319,7 +1329,7 @@ async fn setup_http_flow_did_web_produces_valid_did_doc() { .oneshot( Request::builder() .uri("/.well-known/did.json") - .header("host", "127.0.0.1") + .header("host", "127.0.0.1:0") .body(Body::empty()) .unwrap(), ) @@ -1328,7 +1338,7 @@ async fn setup_http_flow_did_web_produces_valid_did_doc() { assert_eq!(resp.status(), StatusCode::OK); let doc = json_body(resp).await; - assert_eq!(doc["id"], "did:web:127.0.0.1"); + assert_eq!(doc["id"], "did:web:127.0.0.1%3A0"); assert!(!doc["verificationMethod"].as_array().unwrap().is_empty()); // Step 4: Verify status shows complete @@ -1460,6 +1470,7 @@ async fn did_web_issuer_resolved_via_https() { Request::builder() .uri("/xrpc/games.gamesgamesgamesgames.listGames") .header("authorization", &auth) + .header("host", "127.0.0.1:0") .body(Body::empty()) .unwrap(), ) @@ -1498,6 +1509,7 @@ async fn service_auth_rejected_when_no_entries_exist() { Request::builder() .uri("/xrpc/games.gamesgamesgamesgames.listGames") .header("authorization", &auth) + .header("host", "127.0.0.1:0") .body(Body::empty()) .unwrap(), ) @@ -1627,6 +1639,7 @@ async fn service_auth_es256k_query_allowed() { Request::builder() .uri("/xrpc/games.gamesgamesgamesgames.listGames") .header("authorization", &auth) + .header("host", "127.0.0.1:0") .body(Body::empty()) .unwrap(), ) @@ -1978,6 +1991,7 @@ async fn post_to_query_endpoint_rejected() { .method("POST") .uri("/xrpc/games.gamesgamesgamesgames.listGames") .header("authorization", &auth) + .header("host", "127.0.0.1:0") .header("content-type", "application/json") .body(Body::from( serde_json::to_vec(&json!({"test": true})).unwrap(), @@ -2176,6 +2190,7 @@ async fn jwt_with_allowed_typ_accepted() { Request::builder() .uri("/xrpc/games.gamesgamesgamesgames.listGames") .header("authorization", &auth) + .header("host", "127.0.0.1:0") .body(Body::empty()) .unwrap(), ) diff --git a/web/src/app/dashboard/lexicons/page.tsx b/web/src/app/dashboard/lexicons/page.tsx index 732197b..59e1161 100644 --- a/web/src/app/dashboard/lexicons/page.tsx +++ b/web/src/app/dashboard/lexicons/page.tsx @@ -232,11 +232,12 @@ export default function LexiconsPage() { title="Delete lexicon" aria-label="Delete lexicon" onClick={(e) => e.stopPropagation()} + onPointerDown={(e) => e.stopPropagation()} > - + e.stopPropagation()}> Delete lexicon? diff --git a/web/src/app/dashboard/settings/scripts/page.tsx b/web/src/app/dashboard/settings/scripts/page.tsx index 2bc127e..03272a0 100644 --- a/web/src/app/dashboard/settings/scripts/page.tsx +++ b/web/src/app/dashboard/settings/scripts/page.tsx @@ -249,7 +249,7 @@ export default function ScriptsPage() { - + e.stopPropagation()}> Delete script? diff --git a/web/src/components/setup/setup-attach-auth.tsx b/web/src/components/setup/setup-attach-auth.tsx index 91929cd..ce9fb6d 100644 --- a/web/src/components/setup/setup-attach-auth.tsx +++ b/web/src/components/setup/setup-attach-auth.tsx @@ -97,7 +97,7 @@ export function SetupAttachAuth({ localStorage.setItem(ATTACH_AUTH_STORAGE_KEY, JSON.stringify(payload)); const handle = attachedHandle ?? attachedDid; - return fetch(`/auth/login?handle=${encodeURIComponent(handle)}&scope=${encodeURIComponent("atproto identity:*")}`, { + return fetch(`/auth/login?handle=${encodeURIComponent(handle)}&scope=${encodeURIComponent("atproto identity:*")}&redirect_uri=${encodeURIComponent("/setup")}`, { credentials: "same-origin", }); }) diff --git a/web/tests/e2e/lexicon-services.spec.ts b/web/tests/e2e/lexicon-services.spec.ts index 5df8e55..08cf5bc 100644 --- a/web/tests/e2e/lexicon-services.spec.ts +++ b/web/tests/e2e/lexicon-services.spec.ts @@ -78,14 +78,22 @@ test.describe("Lexicon Services", () => { // Create a service entry via the settings page await page.goto("/dashboard/settings/service-identity") - const fragmentInput = page.getByLabel(/fragment/i) + // Open the add sheet + const addEntryButton = page.getByRole("button", { name: "Add Service Entry" }).first() + await expect(addEntryButton).toBeVisible({ timeout: 5000 }) + await addEntryButton.click() + + const addSheet = page.locator("[data-slot='sheet-content']") + await expect(addSheet).toBeVisible({ timeout: 3000 }) + + const fragmentInput = addSheet.getByRole("textbox", { name: /fragment/i }) await expect(fragmentInput).toBeVisible({ timeout: 5000 }) await fragmentInput.fill("#lextest") - const typeInput = page.getByLabel(/service type/i) + const typeInput = addSheet.getByRole("textbox", { name: /service type/i }) await typeInput.fill("TestView") - const addButton = page.getByRole("button", { name: "Add" }) + const addButton = addSheet.getByRole("button", { name: "Add" }) await expect(addButton).toBeEnabled({ timeout: 3000 }) await addButton.click() @@ -128,6 +136,9 @@ test.describe("Lexicon Services", () => { const deleteButton = page.getByRole("button", { name: /delete #lextest/i }) if (await deleteButton.isVisible({ timeout: 3000 }).catch(() => false)) { await deleteButton.click() + const dialog = page.getByRole("alertdialog") + await expect(dialog).toBeVisible({ timeout: 3000 }) + await dialog.getByRole("button", { name: "Delete" }).click() await expect(page.getByText("#lextest")).not.toBeVisible({ timeout: 5000 }) } }) diff --git a/web/tests/e2e/record-delete.spec.ts b/web/tests/e2e/record-delete.spec.ts index bd5598b..5e81aec 100644 --- a/web/tests/e2e/record-delete.spec.ts +++ b/web/tests/e2e/record-delete.spec.ts @@ -42,8 +42,8 @@ async function seedRecords( const uri = `at://did:plc:e2e-test-admin/${COLLECTION}/record-${i}` const now = new Date().toISOString() await client.query( - `INSERT INTO records (uri, did, collection, rkey, record, cid, indexed_at) - VALUES ($1, $2, $3, $4, $5, $6, $7) + `INSERT INTO records (uri, did, collection, rkey, record, cid, indexed_at, created_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (uri) DO NOTHING`, [ uri, @@ -53,6 +53,7 @@ async function seedRecords( JSON.stringify({ title: `Test Record ${i}` }), `cid-e2e-${i}`, now, + now, ], ) } diff --git a/web/tests/e2e/script-delete.spec.ts b/web/tests/e2e/script-delete.spec.ts index 642588f..039d868 100644 --- a/web/tests/e2e/script-delete.spec.ts +++ b/web/tests/e2e/script-delete.spec.ts @@ -19,7 +19,7 @@ const TEST_LEXICON = { } const TEST_SCRIPT_SUFFIX = "test.e2e.scriptdelete.item" -const TEST_SCRIPT_ID = `before_create:${TEST_SCRIPT_SUFFIX}` +const TEST_SCRIPT_ID = `record.create:${TEST_SCRIPT_SUFFIX}` async function seedScript( request: import("@playwright/test").APIRequestContext, @@ -37,8 +37,7 @@ async function seedScript( const resp = await request.post("/admin/scripts", { data: { id: TEST_SCRIPT_ID, - code: 'return record', - language: "lua", + body: 'function handle(record)\n return record\nend', }, }) if (!resp.ok()) { diff --git a/web/tests/e2e/service-identity-settings.spec.ts b/web/tests/e2e/service-identity-settings.spec.ts index 961eeb2..0a5a1a3 100644 --- a/web/tests/e2e/service-identity-settings.spec.ts +++ b/web/tests/e2e/service-identity-settings.spec.ts @@ -8,15 +8,22 @@ test.describe("Service Identity Settings", () => { }) test("manage service entry access mode and xrpcs", async ({ page }) => { + // Open the add sheet + const addEntryButton = page.getByRole("button", { name: "Add Service Entry" }).first() + await expect(addEntryButton).toBeVisible({ timeout: 5000 }) + await addEntryButton.click() + // Create an entry to work with - const fragmentInput = page.getByLabel(/fragment/i) - const typeInput = page.getByLabel(/service type/i) + const addSheet = page.locator("[data-slot='sheet-content']") + await expect(addSheet).toBeVisible({ timeout: 3000 }) + const fragmentInput = addSheet.getByRole("textbox", { name: /fragment/i }) + const typeInput = addSheet.getByRole("textbox", { name: /service type/i }) await expect(fragmentInput).toBeVisible({ timeout: 5000 }) await fragmentInput.fill("#e2esheet") await typeInput.fill("TestView") - const mainAddButton = page.getByRole("button", { name: "Add" }) + const mainAddButton = addSheet.getByRole("button", { name: "Add" }) await expect(mainAddButton).toBeEnabled({ timeout: 3000 }) await mainAddButton.click() @@ -67,20 +74,31 @@ test.describe("Service Identity Settings", () => { const deleteButton = sheetAfterReload.getByRole("button", { name: /delete service/i }) await deleteButton.click() + const deleteDialog = page.getByRole("alertdialog") + await expect(deleteDialog).toBeVisible({ timeout: 3000 }) + await deleteDialog.getByRole("button", { name: "Delete" }).click() + // Wait for sheet to close, then verify entry is removed from the table await expect(sheetAfterReload).not.toBeVisible({ timeout: 5000 }) await expect(page.getByRole("button", { name: /delete #e2esheet/i })).not.toBeVisible({ timeout: 5000 }) }) test("add and remove a service entry", async ({ page }) => { - const fragmentInput = page.getByLabel(/fragment/i) - const typeInput = page.getByLabel(/service type/i) + // Open the add sheet + const addEntryButton = page.getByRole("button", { name: "Add Service Entry" }).first() + await expect(addEntryButton).toBeVisible({ timeout: 5000 }) + await addEntryButton.click() + + const addSheet = page.locator("[data-slot='sheet-content']") + await expect(addSheet).toBeVisible({ timeout: 3000 }) + const fragmentInput = addSheet.getByRole("textbox", { name: /fragment/i }) + const typeInput = addSheet.getByRole("textbox", { name: /service type/i }) await expect(fragmentInput).toBeVisible({ timeout: 5000 }) await fragmentInput.fill("#testentry") await typeInput.fill("TestAppView") - const addButton = page.getByRole("button", { name: "Add" }) + const addButton = addSheet.getByRole("button", { name: "Add" }) await expect(addButton).toBeEnabled({ timeout: 3000 }) await addButton.click() @@ -90,7 +108,11 @@ test.describe("Service Identity Settings", () => { await expect(deleteButton).toBeVisible({ timeout: 3000 }) await deleteButton.click() - await expect(page.getByText("#testentry")).not.toBeVisible({ timeout: 5000 }) + const dialog = page.getByRole("alertdialog") + await expect(dialog).toBeVisible({ timeout: 3000 }) + await dialog.getByRole("button", { name: "Delete" }).click() + + await expect(page.getByText("#testentry", { exact: true })).not.toBeVisible({ timeout: 5000 }) }) test("change mode redirects to setup", async ({ page }) => { diff --git a/web/tests/e2e/setup-attach-account.spec.ts b/web/tests/e2e/setup-attach-account.spec.ts index 1ae0f5f..00beefd 100644 --- a/web/tests/e2e/setup-attach-account.spec.ts +++ b/web/tests/e2e/setup-attach-account.spec.ts @@ -163,7 +163,7 @@ test.describe("Setup - Attach Account", () => { await skipCard.click() await page.getByRole("button", { name: /continue/i }).click() await expect( - page.getByText("Your AppView is ready"), + page.getByText("Your AppView is ready", { exact: true }), ).toBeVisible({ timeout: 5000 }) } } finally { diff --git a/web/tests/e2e/setup-didplc.spec.ts b/web/tests/e2e/setup-didplc.spec.ts index 3af4dd7..c196f2f 100644 --- a/web/tests/e2e/setup-didplc.spec.ts +++ b/web/tests/e2e/setup-didplc.spec.ts @@ -40,16 +40,21 @@ test.describe("Setup - did:plc", () => { return } - // Verify "Download Rotation Key" button is visible - await expect( - page.getByRole("button", { name: /download rotation key/i }), - ).toBeVisible() + // Download the rotation key (required before Continue is enabled) + const downloadButton = page.getByRole("button", { name: /download rotation key/i }) + await expect(downloadButton).toBeVisible() + const [_download] = await Promise.all([ + page.waitForEvent("download"), + downloadButton.click(), + ]) // Click Continue to complete setup - await page.getByRole("button", { name: /continue/i }).click() + const continueButton = page.getByRole("button", { name: /continue/i }) + await expect(continueButton).toBeEnabled({ timeout: 5000 }) + await continueButton.click() // Verify setup completes - await expect(page.getByText("Your AppView is ready")).toBeVisible({ timeout: 5000 }) + await expect(page.getByText("Your AppView is ready", { exact: true })).toBeVisible({ timeout: 5000 }) }) // Restore setup state for subsequent tests @@ -68,7 +73,7 @@ test.describe("Setup - did:plc", () => { await skipCard.click() await page.getByRole("button", { name: /continue/i }).click() await expect( - page.getByText("Your AppView is ready"), + page.getByText("Your AppView is ready", { exact: true }), ).toBeVisible({ timeout: 5000 }) } } finally { diff --git a/web/tests/e2e/setup-features.spec.ts b/web/tests/e2e/setup-features.spec.ts index e27cb08..add6045 100644 --- a/web/tests/e2e/setup-features.spec.ts +++ b/web/tests/e2e/setup-features.spec.ts @@ -14,7 +14,7 @@ test.describe("Setup - Features", () => { await page.getByText(/skip for now/i).click() await page.getByRole("button", { name: /continue/i }).click() - await expect(page.getByText("Your AppView is ready")).toBeVisible({ timeout: 5000 }) + await expect(page.getByText("Your AppView is ready", { exact: true })).toBeVisible({ timeout: 5000 }) await expect(page.getByText(/skipped/i)).toBeVisible() }) @@ -29,7 +29,7 @@ test.describe("Setup - Features", () => { await expect(page.getByText("Review your domain identity")).toBeVisible({ timeout: 10000 }) // Click the Identity stepper step to go back and reset - await page.getByText("Identity").click() + await page.getByRole("tab", { name: "Identity" }).click() // Should be back at mode selection with state reset await expect(page.getByText(/set up your service identity/i)).toBeVisible({ timeout: 5000 }) @@ -96,7 +96,7 @@ test.describe("Setup - Features", () => { await skipCard.click() await page.getByRole("button", { name: /continue/i }).click() await expect( - page.getByText("Your AppView is ready"), + page.getByText("Your AppView is ready", { exact: true }), ).toBeVisible({ timeout: 5000 }) } } finally { diff --git a/web/tests/e2e/setup-wizard.spec.ts b/web/tests/e2e/setup-wizard.spec.ts index 11e05f7..0689238 100644 --- a/web/tests/e2e/setup-wizard.spec.ts +++ b/web/tests/e2e/setup-wizard.spec.ts @@ -23,7 +23,7 @@ test.describe("Setup Wizard", () => { await expect(completeButton).toBeVisible({ timeout: 5000 }) await completeButton.click() - await expect(page.getByText("Your AppView is ready")).toBeVisible({ timeout: 5000 }) + await expect(page.getByText("Your AppView is ready", { exact: true })).toBeVisible({ timeout: 5000 }) }) test("setup page redirects to dashboard after completion", async ({ page }) => { -- 2.51.2