{
expect(screen.getByText('Service Unavailable')).toBeInTheDocument()
})
+ it('should describe rows as temporarily unavailable, not unregistered, when the catalog fails', async () => {
+ mockedInitializersApi.listRegistered.mockRejectedValue(new Error('Service Unavailable'))
+
+ renderInitializers()
+
+ const row = await screen.findByTestId('baseline-initializer-row-target')
+ // A metadata outage must not be presented as a configuration problem.
+ expect(within(row).getByText(/temporarily unavailable/i)).toBeInTheDocument()
+ expect(within(row).queryByText(/no longer registered/i)).not.toBeInTheDocument()
+
+ mockedInitializersApi.listRegistered.mockResolvedValue({
+ items: [targetInitializer],
+ pagination: { limit: 200, has_more: false },
+ })
+ fireEvent.click(screen.getByRole('button', { name: 'Refresh' }))
+
+ // Refresh re-runs the load effect (and remounts rows via the loading
+ // state), so assert on a freshly queried node.
+ await waitFor(async () => {
+ expect(screen.getByTestId('baseline-initializer-row-target')).toHaveTextContent(
+ 'Registers targets.',
+ )
+ })
+ })
+
it('should remove an additional initializer and show success feedback', async () => {
const user = userEvent.setup()
renderInitializers()
diff --git a/frontend/src/components/Initializers/Initializers.tsx b/frontend/src/components/Initializers/Initializers.tsx
index 55c7547d7f..39af8feaa6 100644
--- a/frontend/src/components/Initializers/Initializers.tsx
+++ b/frontend/src/components/Initializers/Initializers.tsx
@@ -26,6 +26,10 @@ export default function Initializers() {
const styles = useInitializersStyles()
const [settings, setSettings] = useState
(EMPTY_SETTINGS)
const [registeredInitializers, setRegisteredInitializers] = useState([])
+ // A failed catalog request leaves registration state unknown; rows must
+ // then say "temporarily unavailable" instead of "no longer registered",
+ // which would present a metadata outage as a configuration problem.
+ const [catalogAvailable, setCatalogAvailable] = useState(true)
const [loading, setLoading] = useState(true)
const [statusMessage, setStatusMessage] = useState(null)
const [refetchCount, setRefetchCount] = useState(0)
@@ -55,7 +59,9 @@ export default function Initializers() {
if (registeredResult.status === 'fulfilled') {
setRegisteredInitializers(registeredResult.value.items)
+ setCatalogAvailable(true)
} else {
+ setCatalogAvailable(false)
const catalogError = toApiError(registeredResult.reason).detail
setStatusMessage((current: StatusMessage | null) =>
current
@@ -207,10 +213,12 @@ export default function Initializers() {
{
expect(result.initializer_type).toBe('UnknownInitializer')
})
})
+
+describe('resolveRegisteredInitializer with catalog availability', () => {
+ it('reports temporary unavailability instead of unregistered when the catalog request failed', () => {
+ const result = resolveRegisteredInitializer('target', [], false)
+
+ expect(result).toEqual({
+ initializer_name: 'target',
+ initializer_type: 'UnverifiedInitializer',
+ description: expect.stringContaining('temporarily unavailable'),
+ required_env_vars: [],
+ supported_parameters: [],
+ })
+ expect(result.description).not.toContain('no longer registered')
+ })
+
+ it('still resolves matches from stale-but-loaded catalog data when available flag is true', () => {
+ const result = resolveRegisteredInitializer('target', registered, true)
+
+ expect(result).toBe(registered[0])
+ })
+})
diff --git a/frontend/src/components/Initializers/initializerLookup.ts b/frontend/src/components/Initializers/initializerLookup.ts
index 73b3d889b1..20cb305f5b 100644
--- a/frontend/src/components/Initializers/initializerLookup.ts
+++ b/frontend/src/components/Initializers/initializerLookup.ts
@@ -1,16 +1,36 @@
import type { RegisteredInitializer } from '@/types'
+export const UNREGISTERED_INITIALIZER_DESCRIPTION = 'Initializer is no longer registered.'
+export const CATALOG_UNAVAILABLE_DESCRIPTION =
+ 'Initializer catalog is temporarily unavailable; registration state cannot be confirmed.'
+
/**
* Resolve a settings entry's `initializer_name` to its catalog definition.
*
* Settings reference an initializer by name; the catalog (from `listRegistered`)
* is the single source of truth for display metadata. When a persisted name is no
* longer registered, return a placeholder so the row still renders.
+ *
+ * `catalogAvailable` distinguishes the two ways a name can fail to resolve:
+ * a successful catalog response that lacks the name means the entry is truly
+ * unregistered, while a failed catalog request only means registration state
+ * is unknown and must not be reported as a configuration problem.
*/
export function resolveRegisteredInitializer(
initializerName: string,
registeredInitializers: RegisteredInitializer[],
+ catalogAvailable: boolean = true,
): RegisteredInitializer {
+ if (!catalogAvailable) {
+ return {
+ initializer_name: initializerName,
+ initializer_type: 'UnverifiedInitializer',
+ description: CATALOG_UNAVAILABLE_DESCRIPTION,
+ required_env_vars: [],
+ supported_parameters: [],
+ }
+ }
+
const match = registeredInitializers.find((item) => item.initializer_name === initializerName)
if (match) {
return match
@@ -19,7 +39,7 @@ export function resolveRegisteredInitializer(
return {
initializer_name: initializerName,
initializer_type: 'UnknownInitializer',
- description: 'Initializer is no longer registered.',
+ description: UNREGISTERED_INITIALIZER_DESCRIPTION,
required_env_vars: [],
supported_parameters: [],
}