Compare commits

...

15 Commits

Author SHA1 Message Date
Matt Miller
1f699d8738 Merge remote-tracking branch 'origin/main' into matt/be-2783-type-cloudsecrets-mocks-ingest-types
# Conflicts:
#	browser_tests/tests/cloudSecrets.spec.ts
2026-07-09 14:39:36 -07:00
Matt Miller
5c3dc4cee6 test: type cloudSecrets E2E mocks with ingest-types and return 201 on create 2026-07-09 14:32:18 -07:00
Matt Miller
d935c5bb26 Merge branch 'main' into matt/be-2593-f14-fe-add-list-delete-e2e 2026-07-09 15:02:34 -04:00
Matt Miller
99e820ac8a test: address secret form review feedback
- Revert providerHint copy: provider is required by the form validation,
  so the 'Optional' wording contradicted the enforced behavior.
- Drop the inert input/attribute DOM scan in the secrets e2e; the form is
  unmounted by then so it can never fail. Keep the request-contract and
  list-text assertions that actually guard the write-only value.
- Assert the non-entitled provider dropdown has zero options so a
  fetch-failure fallback to the default providers can't pass vacuously.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 11:56:15 -07:00
Matt Miller
1e491ba204 Merge branch 'main' into matt/be-2593-f14-fe-add-list-delete-e2e 2026-07-09 14:36:58 -04:00
Matt Miller
6702c6006e test: address review feedback on secret form
- Mark decorative provider logo alt as empty to avoid duplicate SR announcement
- Rename watch callback param to avoid shadowing composable options arg
2026-07-08 21:05:59 -07:00
Matt Miller
0f9cfab430 Merge remote-tracking branch 'origin/main' into matt/be-2593-f14-fe-add-list-delete-e2e
# Conflicts:
#	src/locales/en/main.json
2026-07-08 21:00:21 -07:00
Matt Miller
98799c61e3 test: narrow secrets route mock to real API paths so the lazy panel mounts 2026-07-08 23:26:28 -04:00
Matt Miller
9807132934 fix(dialog): keep Settings open when a nested dialog closes on focus loss
Deleting a Secrets key closed the entire Settings dialog: confirming the
delete removes the focused list row, so focus falls to an ordinary app
element. As a non-modal Reka dialog, Settings read that programmatic focus
shift as an outside interaction and dismissed itself, failing the add/list/
delete e2e at the empty-state assertion.

Add a `dismissOnFocusOutside` dialog prop (default true) and set it false on
Settings so it no longer dismisses on focus-outside; escape, the close
button, and outside-pointer dismissal are unaffected.
2026-07-08 14:40:08 -07:00
Matt Miller
32759ca672 test: harden cloud secrets e2e assertions
- Anchor the non-entitled test on the opened listbox so the gated-provider
  absence checks can't pass vacuously against a dropdown that never opened.
- Also assert the secret is not smuggled into any input value or attribute,
  not just visible text nodes.
2026-07-08 16:41:27 -04:00
Matt Miller
5bf0708719 test: suppress new-user template modal in cloud secrets e2e
The workflow template selector (z-1700) auto-opens on boot when
Comfy.TutorialCompleted is falsy and intercepts pointer events on the
settings dialog, so clicking the Secrets nav timed out. Mock settings
with TutorialCompleted=true, matching the existing cloud boot pattern.

Also drop the unused `export` on SECRET_PROVIDERS (internal-only) so
knip passes.
2026-07-08 16:41:27 -04:00
Matt Miller
130feb3fc1 test: E2E for BYOK secret add / list / delete flow 2026-07-08 16:41:27 -04:00
Matt Miller
275d8c02b7 fix(secrets): unexport SECRET_PROVIDERS and address review nits
- Drop unused SECRET_PROVIDERS export (knip) now that lookups go through
  findProvider internally
- Reconcile the selected provider once the server allowlist resolves so an
  unlisted fallback cannot be submitted
- Dedupe server-returned provider ids; replace unsafe as never cast with a
  type-safe membership check
- Guard existingProviders against null with != null
2026-07-08 16:40:06 -04:00
GitHub Action
42eea78cc8 [automated] Apply ESLint and Oxfmt fixes 2026-07-08 16:33:01 -04:00
Matt Miller
bce8075bec feat(secrets): render provider surface (labels/logos/help) from server data
The secrets provider dropdown and saved-keys list now render whichever
providers the server lists via GET /secrets/providers, mapping each id to
its display label, logo, and optional help text. Previously the dropdown
intersected the server list against a hardcoded provider array, so a
newly-listed provider (e.g. runway/gemini) could never appear.

- Drive create-mode options from the server list; unknown ids fall back to
  the raw id with no logo, so adding a provider server-side needs no FE change
- Add label/logo/help registry entries and placeholder logo assets for the
  BYOK providers
- Surface provider-specific help text under the picker, falling back to the
  generic hint
- Extend composable + add registry unit tests
2026-07-08 16:33:01 -04:00

View File

@@ -1,3 +1,9 @@
import type {
CreateSecretRequest,
SecretListResponse,
SecretProvidersResponse,
SecretResponse
} from '@comfyorg/ingest-types'
import { expect } from '@playwright/test'
import type { Page, Route } from '@playwright/test'
@@ -35,26 +41,11 @@ const BOOT_SETTINGS = { 'Comfy.TutorialCompleted': true }
// back by the API or rendered anywhere in the UI.
const RUNWAY_KEY_VALUE = 'sk-runway-do-not-echo-0xDEADBEEF'
interface SecretRecord {
id: string
name: string
provider?: string
created_at: string
updated_at: string
last_used_at?: string
}
interface CreateCapture {
name?: string
provider?: string
secret_value?: string
}
interface SecretsBackend {
/** Bodies received by POST /secrets, in order — for asserting what was sent. */
createRequests: CreateCapture[]
createRequests: CreateSecretRequest[]
/** Current server-side store — for asserting delete actually removed a row. */
store: SecretRecord[]
store: SecretResponse[]
}
/**
@@ -73,7 +64,9 @@ async function mockSecretsBackend(
let idSeq = 0
const respondList = (route: Route) =>
route.fulfill(jsonRoute({ data: backend.store }))
route.fulfill(
jsonRoute({ data: backend.store } satisfies SecretListResponse)
)
await page.route('**/api/secrets**', async (route) => {
const request = route.request()
@@ -93,7 +86,9 @@ async function mockSecretsBackend(
// GET /secrets/providers — the entitlement-gated provider allowlist.
if (pathname.endsWith('/secrets/providers')) {
return route.fulfill(
jsonRoute({ data: providerIds.map((id) => ({ id })) })
jsonRoute({
data: providerIds.map((id) => ({ id }))
} satisfies SecretProvidersResponse)
)
}
@@ -110,19 +105,23 @@ async function mockSecretsBackend(
// /secrets — collection routes.
if (method === 'POST') {
const body = (request.postDataJSON() ?? {}) as CreateCapture
const body = (request.postDataJSON() ?? {}) as CreateSecretRequest
backend.createRequests.push(body)
idSeq += 1
const created: SecretRecord = {
const created: SecretResponse = {
id: `00000000-0000-4000-8000-${String(idSeq).padStart(12, '0')}`,
name: body.name ?? '',
name: body.name,
provider: body.provider,
created_at: '2026-07-08T00:00:00Z',
updated_at: '2026-07-08T00:00:00Z'
}
backend.store.push(created)
// Response echoes metadata ONLY — the schema has no secret_value field.
return route.fulfill(jsonRoute(created))
// 201 Created, echoing metadata ONLY — the schema has no secret_value field.
return route.fulfill({
status: 201,
contentType: 'application/json',
body: JSON.stringify(created)
})
}
// GET /secrets (list).