Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions routes/connectors-home/default-connectors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ function ApiKeyConnector( {
} );
const isExternallyConfigured =
keySource === 'env' || keySource === 'constant';
// A stored key is only available masked, so it can't be edited, even when
// it can't be verified. It has to be removed before entering a new one.
const isReadOnly =
isConnected || isExternallyConfigured || !! currentApiKey;
const showUnavailableBadge =
( pluginStatus === 'not-installed' && canInstallPlugins === false ) ||
( pluginStatus === 'inactive' && canActivatePlugins === false );
Expand Down Expand Up @@ -248,14 +252,14 @@ function ApiKeyConnector( {
pluginStatus === 'active' &&
hasResolvedSettings && (
<DefaultConnectorSettings
key={ isConnected ? 'connected' : 'setup' }
key={ isReadOnly ? 'read-only' : 'setup' }
initialValue={
isExternallyConfigured
? '••••••••••••••••'
: currentApiKey
}
helpUrl={ helpUrl }
readOnly={ isConnected || isExternallyConfigured }
readOnly={ isReadOnly }
keySource={ keySource }
onRemove={
isExternallyConfigured
Expand Down
133 changes: 133 additions & 0 deletions routes/connectors-home/test/default-connectors.jsdom.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { render, screen } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
privateApis as connectorsPrivateApis,
type ConnectorConfig,
} from '@wordpress/connectors';
import { select } from '@wordpress/data';
import { unlock } from '@wordpress/routes-lock-unlock';
import { registerDefaultConnectors } from '../default-connectors';
import { useConnectorPlugin } from '../use-connector-plugin';

vi.mock(
import( '../use-connector-plugin' ),
() =>
( {
useConnectorPlugin: vi.fn(),
} ) as unknown as typeof import( '../use-connector-plugin' )
);

const { store: connectorsStore } = unlock( connectorsPrivateApis );
const mockedUseConnectorPlugin = vi.mocked( useConnectorPlugin );

const MASKED_API_KEY = '•'.repeat( 16 ) + 'fj39';

function mockConnectorPlugin( {
isConnected,
currentApiKey,
}: {
isConnected: boolean;
currentApiKey: string;
} ) {
mockedUseConnectorPlugin.mockReturnValue( {
pluginStatus: 'active',
canInstallPlugins: true,
canActivatePlugins: true,
isExpanded: true,
setIsExpanded: vi.fn(),
isBusy: false,
isConnected,
currentApiKey,
hasResolvedSettings: true,
keySource: currentApiKey ? 'database' : 'none',
handleButtonClick: vi.fn(),
getButtonLabel: () => ( isConnected ? 'Edit' : 'Set up' ),
saveApiKey: vi.fn(),
removeApiKey: vi.fn(),
} as unknown as ReturnType< typeof useConnectorPlugin > );
}

function renderConnector( connector: ConnectorConfig ) {
if ( ! connector.render ) {
throw new Error( 'The connector has no render.' );
}

return (
<connector.render
slug={ connector.slug }
name={ connector.name }
description={ connector.description }
type={ connector.type }
logo={ connector.logo }
authentication={ connector.authentication }
plugin={ connector.plugin }
/>
);
}

describe( 'API key connector', () => {
let connector: ConnectorConfig;

beforeEach( () => {
document.body.innerHTML = `<script type="application/json" id="wp-script-module-data-options-connectors-wp-admin">${ JSON.stringify(
{
connectors: {
openai: {
name: 'OpenAI',
description: 'Text and image generation.',
type: 'ai_provider',
authentication: {
method: 'api_key',
settingName: 'connectors_ai_openai_api_key',
keySource: 'database',
isConnected: false,
},
},
},
}
) }</script>`;

registerDefaultConnectors();
connector = unlock( select( connectorsStore ) ).getConnector(
'openai'
);
} );

it( 'shows a stored key as read-only when it cannot be verified', () => {
mockConnectorPlugin( {
isConnected: false,
currentApiKey: MASKED_API_KEY,
} );

render( renderConnector( connector ) );

const input = screen.getByRole( 'textbox', { name: 'API Key' } );
expect( input ).toHaveValue( MASKED_API_KEY );
expect( input ).toBeDisabled();
expect(
screen.getByRole( 'button', { name: 'Remove and replace' } )
).toBeVisible();
expect(
screen.queryByRole( 'button', { name: 'Save' } )
).not.toBeInTheDocument();
} );

it( 'shows an empty editable field once the stored key is removed', () => {
mockConnectorPlugin( {
isConnected: false,
currentApiKey: MASKED_API_KEY,
} );

const { rerender } = render( renderConnector( connector ) );

mockConnectorPlugin( { isConnected: false, currentApiKey: '' } );
rerender( renderConnector( connector ) );

const input = screen.getByRole( 'textbox', { name: 'API Key' } );
expect( input ).toHaveValue( '' );
expect( input ).toBeEnabled();
expect(
screen.getByRole( 'button', { name: 'Save' } )
).toBeInTheDocument();
} );
} );
Loading