From 91a8d59e43157b16e06f8f9cf80c8c445967f3de Mon Sep 17 00:00:00 2001 From: Ali Waseem Date: Wed, 1 Apr 2026 11:52:30 -0600 Subject: [PATCH] chore: add unit test for regression of webhooks (#44409) ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? We had some bugs where webhooks were not able to be turned on for projects. This is to ensure that doesn't happen again ## Summary by CodeRabbit * **Tests** * Added UI tests for integration overview and installation flows: verify action enablement/disablement (aria-disabled and visual opacity) depending on required extensions, ensure the "Install integration" button is disabled when no installation command or required extensions are missing, and confirm the installation command is invoked (without executing SQL) when appropriate. --------- Co-authored-by: Joshen Lim --- .../IntegrationOverviewTab.test.tsx | 85 ++++++++++++ .../InstallIntegrationSheet.test.tsx | 123 ++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.test.tsx create mode 100644 apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/InstallIntegrationSheet.test.tsx diff --git a/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.test.tsx b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.test.tsx new file mode 100644 index 00000000000..7ec19bcf924 --- /dev/null +++ b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.test.tsx @@ -0,0 +1,85 @@ +import { screen } from '@testing-library/dom' +import { mockAnimationsApi } from 'jsdom-testing-mocks' +import { customRender } from 'tests/lib/custom-render' +import { routerMock } from 'tests/lib/route-mock' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { IntegrationOverviewTab } from './IntegrationOverviewTab' + +mockAnimationsApi() + +vi.mock('../Landing/Integrations.constants', () => ({ + INTEGRATIONS: [ + { + id: 'test-integration', + name: 'Test Integration', + requiredExtensions: ['pg_net'], + }, + ], +})) + +vi.mock('framer-motion', async (importOriginal) => { + const actual = (await importOriginal()) as any + return { + ...actual, + motion: { + ...actual.motion, + div: ({ children, ...props }: any) =>
{children}
, + }, + } +}) + +const mockExtensions = vi.fn() + +vi.mock('data/database-extensions/database-extensions-query', () => ({ + useDatabaseExtensionsQuery: () => ({ data: mockExtensions() }), +})) + +vi.mock('@/hooks/misc/useSelectedProject', () => ({ + useSelectedProjectQuery: () => ({ + data: { ref: 'default', connectionString: 'postgres://localhost' }, + }), + useIsOrioleDb: () => false, +})) + +vi.mock('common', async (importOriginal) => { + const actual = (await importOriginal()) as any + return { + ...actual, + useParams: () => ({ id: 'test-integration', ref: 'default' }), + } +}) + +vi.mock('./MarkdownContent', () => ({ + MarkdownContent: () => null, +})) + +describe('IntegrationOverviewTab', () => { + beforeEach(() => { + routerMock.setCurrentUrl('/project/default/integrations/test-integration/overview') + mockExtensions.mockReturnValue([ + { name: 'pg_net', installed_version: null, default_version: '0.6.0' }, + ]) + }) + + it('does not disable actions when hideRequiredExtensionsSection is true and extensions are uninstalled', () => { + customRender( + Enable webhooks} + /> + ) + + const actionsArea = screen.getByText('Enable webhooks').closest('[aria-disabled]') + expect(actionsArea).toHaveAttribute('aria-disabled', 'false') + expect(actionsArea).not.toHaveClass('opacity-25') + }) + + it('disables actions when extensions are uninstalled and hideRequiredExtensionsSection is false', () => { + customRender(Enable integration} />) + + const actionsArea = screen.getByText('Enable integration').closest('[aria-disabled]') + expect(actionsArea).toHaveAttribute('aria-disabled', 'true') + expect(actionsArea).toHaveClass('opacity-25') + }) +}) diff --git a/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/InstallIntegrationSheet.test.tsx b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/InstallIntegrationSheet.test.tsx new file mode 100644 index 00000000000..9406d17efb2 --- /dev/null +++ b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/InstallIntegrationSheet.test.tsx @@ -0,0 +1,123 @@ +import { screen, waitFor } from '@testing-library/dom' +import userEvent from '@testing-library/user-event' +import { mockAnimationsApi } from 'jsdom-testing-mocks' +import { customRender } from 'tests/lib/custom-render' +import { routerMock } from 'tests/lib/route-mock' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { IntegrationDefinition } from '../../Landing/Integrations.constants' +import { InstallIntegrationSheet } from './InstallIntegrationSheet' + +mockAnimationsApi() + +vi.mock('@/hooks/misc/useSelectedProject', () => ({ + useSelectedProjectQuery: () => ({ + data: { ref: 'default', connectionString: 'postgres://localhost' }, + }), +})) + +vi.mock('@/hooks/useProtectedSchemas', () => ({ + useProtectedSchemas: () => ({ data: [] }), +})) + +const mockExtensions = vi.fn() +vi.mock('@/data/database-extensions/database-extensions-query', () => ({ + useDatabaseExtensionsQuery: () => ({ data: mockExtensions(), isSuccess: true }), +})) + +vi.mock('@/data/database/schemas-query', () => ({ + useSchemasQuery: () => ({ data: [{ id: 1, name: 'public' }] }), +})) + +const mockExecuteSql = vi.fn() +vi.mock('@/data/sql/execute-sql-mutation', () => ({ + useExecuteSqlMutation: () => ({ mutateAsync: mockExecuteSql }), +})) + +vi.mock('@/data/database-extensions/database-extension-enable-mutation', () => ({ + useDatabaseExtensionEnableMutation: () => ({ mutateAsync: vi.fn() }), +})) + +vi.mock('@/components/interfaces/Database/Extensions/Extensions.constants', () => ({ + extensionsWithRecommendedSchemas: {}, +})) + +vi.mock('./IntegrationOverviewTabV2.utils', () => ({ + getEnableExtensionsSQL: () => 'CREATE EXTENSION IF NOT EXISTS pg_net;', + getExtensionDefaultSchema: () => 'extensions', +})) + +const createIntegration = (overrides: Partial = {}): IntegrationDefinition => + ({ + id: 'test-integration', + type: 'postgres_extension', + name: 'Test Integration', + requiredExtensions: ['pg_net'], + icon: () => null, + description: 'Test description', + docsUrl: null, + author: { name: 'Test', websiteUrl: 'https://test.com' }, + navigate: () => null, + ...overrides, + }) as unknown as any + +const getInstallButton = () => { + const buttons = screen.getAllByRole('button', { name: 'Install integration' }) + return buttons[buttons.length - 1] +} + +describe('InstallIntegrationSheet', () => { + beforeEach(() => { + routerMock.setCurrentUrl('/project/default/integrations/test-integration/overview') + mockExecuteSql.mockReset() + }) + + it('install button is disabled when extensions are missing even if installationCommand exists', async () => { + mockExtensions.mockReturnValue([]) + + customRender( + + ) + + await userEvent.click(screen.getByRole('button', { name: 'Install integration' })) + expect(getInstallButton()).toBeDisabled() + }) + + it('install button is disabled when extensions are missing and no installationCommand', async () => { + mockExtensions.mockReturnValue([]) + + customRender( + + ) + + await userEvent.click(screen.getByRole('button', { name: 'Install integration' })) + expect(getInstallButton()).toBeDisabled() + }) + + it('uses installationCommand instead of SQL when provided', async () => { + mockExtensions.mockReturnValue([ + { name: 'pg_net', installed_version: null, default_version: '0.6.0' }, + ]) + + const mockCommand = vi.fn().mockResolvedValue(undefined) + customRender( + + ) + + await userEvent.click(screen.getByRole('button', { name: 'Install integration' })) + await userEvent.click(getInstallButton()) + + await waitFor(() => { + expect(mockCommand).toHaveBeenCalledWith({ ref: 'default' }) + }) + expect(mockExecuteSql).not.toHaveBeenCalled() + }) +})