From 38e59557603c680f8651e4527babc87e2448110b Mon Sep 17 00:00:00 2001 From: CodingOnStar Date: Thu, 25 Dec 2025 17:52:45 +0800 Subject: [PATCH] test: improve unit tests for plugin authentication components - Updated tests for the transformFormSchemasSecretInput function to clarify handling of null and numeric values. - Added a data-testid to the AddOAuthButton for more reliable selection in tests. - Refactored tests for AddOAuthButton to use the new data-testid for clicking the settings button. - Enhanced assertions in ApiKeyModal and OAuthClientSettings tests to ensure proper API call handling during validation and state updates. --- .../authorize/add-oauth-button.tsx | 1 + .../authorize/authorize-components.spec.tsx | 70 ++++++++----------- .../plugins/plugin-auth/index.spec.tsx | 4 +- 3 files changed, 32 insertions(+), 43 deletions(-) diff --git a/web/app/components/plugins/plugin-auth/authorize/add-oauth-button.tsx b/web/app/components/plugins/plugin-auth/authorize/add-oauth-button.tsx index caf5a3efaf..6fa7d4d1b6 100644 --- a/web/app/components/plugins/plugin-auth/authorize/add-oauth-button.tsx +++ b/web/app/components/plugins/plugin-auth/authorize/add-oauth-button.tsx @@ -225,6 +225,7 @@ const AddOAuthButton = ({ >
{ render(, { wrapper: createWrapper() }) + // Verify the default button has secondary-accent variant class expect(screen.getByRole('button').className).toContain('btn-secondary-accent') }) }) @@ -613,18 +614,9 @@ describe('AddOAuthButton', () => { render(, { wrapper: createWrapper() }) - // Find and click the settings icon (right side of split button) - const settingsIcon = screen.getByRole('button').querySelector('[class*="shrink-0"][class*="w-8"]') - if (settingsIcon) { - fireEvent.click(settingsIcon) - } - else { - // Alternative: click by finding the RiEqualizer2Line icon's parent - const icons = screen.getByRole('button').querySelectorAll('svg') - const settingsButton = icons[icons.length - 1]?.parentElement - if (settingsButton) - fireEvent.click(settingsButton) - } + // Click the settings icon using data-testid for reliable selection + const settingsButton = screen.getByTestId('oauth-settings-button') + fireEvent.click(settingsButton) await waitFor(() => { expect(screen.getByText('plugin.auth.oauthClientSettings')).toBeInTheDocument() @@ -1138,8 +1130,7 @@ describe('ApiKeyModal', () => { const confirmButton = screen.getByText('common.operation.save') fireEvent.click(confirmButton) - // Wait a bit and verify API was not called - await new Promise(resolve => setTimeout(resolve, 100)) + // Verify API was not called since validation failed synchronously expect(mockAddPluginCredential).not.toHaveBeenCalled() }) @@ -1174,7 +1165,7 @@ describe('ApiKeyModal', () => { ]) // Create a promise that we can control - let resolveFirstCall: (value?: unknown) => void + let resolveFirstCall: (value?: unknown) => void = () => {} let apiCallCount = 0 mockAddPluginCredential.mockImplementation(() => { @@ -1199,20 +1190,19 @@ describe('ApiKeyModal', () => { // First click starts the request fireEvent.click(confirmButton) - // Wait a tick to ensure state updates - await new Promise(resolve => setTimeout(resolve, 10)) + // Wait for the first API call to be made + await waitFor(() => { + expect(apiCallCount).toBe(1) + }) - // Second click while first request is still pending + // Second click while first request is still pending should be ignored fireEvent.click(confirmButton) - // Wait a bit more - await new Promise(resolve => setTimeout(resolve, 10)) - - // Only one API call should have been made + // Verify only one API call was made (no additional calls) expect(apiCallCount).toBe(1) // Clean up by resolving the promise - resolveFirstCall!() + resolveFirstCall() }) it('should call onRemove when extra button is clicked in edit mode', async () => { @@ -1714,7 +1704,7 @@ describe('OAuthClientSettings', () => { it('should return early from handleConfirm if doingActionRef is true', async () => { const pluginPayload = createPluginPayload() - let resolveFirstCall: (value?: unknown) => void + let resolveFirstCall: (value?: unknown) => void = () => {} let apiCallCount = 0 mockSetPluginOAuthCustomClient.mockImplementation(() => { @@ -1740,25 +1730,24 @@ describe('OAuthClientSettings', () => { // First click starts the request fireEvent.click(saveButton) - // Wait a tick to ensure state updates - await new Promise(resolve => setTimeout(resolve, 10)) + // Wait for the first API call to be made + await waitFor(() => { + expect(apiCallCount).toBe(1) + }) - // Second click while first request is pending + // Second click while first request is pending should be ignored fireEvent.click(saveButton) - // Wait a bit more - await new Promise(resolve => setTimeout(resolve, 10)) - - // Only one API call should have been made + // Verify only one API call was made (no additional calls) expect(apiCallCount).toBe(1) // Clean up - resolveFirstCall!() + resolveFirstCall() }) it('should return early from handleRemove if doingActionRef is true', async () => { const pluginPayload = createPluginPayload() - let resolveFirstCall: (value?: unknown) => void + let resolveFirstCall: (value?: unknown) => void = () => {} let deleteCallCount = 0 mockDeletePluginOAuthCustomClient.mockImplementation(() => { @@ -1801,20 +1790,19 @@ describe('OAuthClientSettings', () => { // First click starts the delete request fireEvent.click(removeButton) - // Wait a tick to ensure state updates - await new Promise(resolve => setTimeout(resolve, 10)) + // Wait for the first delete call to be made + await waitFor(() => { + expect(deleteCallCount).toBe(1) + }) - // Second click while first request is pending + // Second click while first request is pending should be ignored fireEvent.click(removeButton) - // Wait a bit more - await new Promise(resolve => setTimeout(resolve, 10)) - - // Only one delete call should have been made + // Verify only one delete call was made (no additional calls) expect(deleteCallCount).toBe(1) // Clean up - resolveFirstCall!() + resolveFirstCall() }) }) diff --git a/web/app/components/plugins/plugin-auth/index.spec.tsx b/web/app/components/plugins/plugin-auth/index.spec.tsx index 97b5094e99..328de71e8d 100644 --- a/web/app/components/plugins/plugin-auth/index.spec.tsx +++ b/web/app/components/plugins/plugin-auth/index.spec.tsx @@ -359,9 +359,9 @@ describe('Utils', () => { const result = transformFormSchemasSecretInput(secretNames, values as Record) - // null is falsy, so it won't be transformed + // null is preserved as-is to represent an explicitly unset secret, not masked as [__HIDDEN__] expect(result.api_key).toBe(null) - // 0 is falsy, so it won't be transformed + // numeric values like 0 are also preserved; only non-empty string secrets are transformed expect(result.null_key).toBe(0) }) })