| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- import { fireEvent, screen } from '@testing-library/react'
- import { mockAnimationsApi } from 'jsdom-testing-mocks'
- import type { MouseEventHandler, ReactNode } from 'react'
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
- import { ProjectNeedsSecuring } from './ProjectNeedsSecuring'
- import { render } from '@/tests/helpers'
- const {
- mockUseFlag,
- mockUseProjectLintsQuery,
- mockUseSelectedProjectQuery,
- mockUseTablesQuery,
- mockUseProjectPostgrestConfigQuery,
- mockUseTablePrivilegesQuery,
- mockUseLocalStorageQuery,
- mockUseRouter,
- mockRouterPush,
- } = vi.hoisted(() => ({
- mockUseFlag: vi.fn(),
- mockUseProjectLintsQuery: vi.fn(),
- mockUseSelectedProjectQuery: vi.fn(),
- mockUseTablesQuery: vi.fn(),
- mockUseProjectPostgrestConfigQuery: vi.fn(),
- mockUseTablePrivilegesQuery: vi.fn(),
- mockUseLocalStorageQuery: vi.fn(),
- mockUseRouter: vi.fn(),
- mockRouterPush: vi.fn(),
- }))
- vi.mock('common', async () => {
- const actual = await vi.importActual<typeof import('common')>('common')
- return {
- ...actual,
- useFlag: mockUseFlag,
- useParams: () => ({ ref: 'project-ref' }),
- }
- })
- vi.mock('next/router', () => ({
- useRouter: () => mockUseRouter(),
- }))
- vi.mock('next/link', () => ({
- default: ({
- href,
- children,
- onClick,
- ...props
- }: {
- href: string
- children: ReactNode
- onClick?: MouseEventHandler<HTMLAnchorElement>
- [key: string]: unknown
- }) => (
- <a href={href} onClick={onClick} {...props}>
- {children}
- </a>
- ),
- }))
- vi.mock('@/data/lint/lint-query', () => ({
- useProjectLintsQuery: mockUseProjectLintsQuery,
- }))
- vi.mock('@/hooks/misc/useSelectedProject', () => ({
- useSelectedProjectQuery: mockUseSelectedProjectQuery,
- }))
- vi.mock('@/data/tables/tables-query', () => ({
- useTablesQuery: mockUseTablesQuery,
- }))
- vi.mock('@/data/config/project-postgrest-config-query', () => ({
- parseDbSchemaString: vi.fn((value: string) => value.split(',').map((schema) => schema.trim())),
- useProjectPostgrestConfigQuery: mockUseProjectPostgrestConfigQuery,
- }))
- vi.mock('@/data/privileges/table-privileges-query', () => ({
- useTablePrivilegesQuery: mockUseTablePrivilegesQuery,
- }))
- vi.mock('@/hooks/misc/useLocalStorage', () => ({
- useLocalStorageQuery: mockUseLocalStorageQuery,
- }))
- vi.mock('sonner', () => ({
- toast: {
- error: vi.fn(),
- },
- }))
- const issueLint = {
- cache_key: 'lint-1',
- name: 'rls_disabled_in_public',
- detail: 'RLS is disabled on public.invoices',
- description: 'RLS disabled',
- level: 'ERROR',
- categories: ['SECURITY'],
- metadata: {
- schema: 'public',
- name: 'invoices',
- },
- }
- const tables = [
- {
- id: 1,
- name: 'invoices',
- schema: 'public',
- rls_enabled: false,
- },
- {
- id: 2,
- name: 'profiles',
- schema: 'public',
- rls_enabled: false,
- },
- {
- id: 3,
- name: 'customers',
- schema: 'public',
- rls_enabled: true,
- },
- ]
- const tablePrivileges = [
- {
- schema: 'public',
- name: 'invoices',
- privileges: [
- {
- grantee: 'anon',
- privilege_type: 'SELECT',
- },
- ],
- },
- ]
- describe('ProjectNeedsSecuring', () => {
- beforeEach(() => {
- mockAnimationsApi()
- mockUseFlag.mockReturnValue(true)
- mockUseRouter.mockReturnValue({ pathname: '/project/[ref]', push: mockRouterPush })
- mockUseSelectedProjectQuery.mockReturnValue({
- data: { connectionString: 'postgresql://example' },
- })
- mockUseProjectLintsQuery.mockReturnValue({
- data: [issueLint],
- isPending: false,
- isError: false,
- })
- mockUseTablesQuery.mockReturnValue({
- data: tables,
- isPending: false,
- isError: false,
- })
- mockUseProjectPostgrestConfigQuery.mockReturnValue({
- data: 'public',
- isPending: false,
- isError: false,
- })
- mockUseTablePrivilegesQuery.mockReturnValue({
- data: tablePrivileges,
- isPending: false,
- isError: false,
- })
- mockUseLocalStorageQuery.mockReturnValue([null, vi.fn(), { isLoading: false }])
- })
- afterEach(() => {
- vi.clearAllMocks()
- window.localStorage.clear()
- })
- it('renders the security gate when an exposed table has RLS disabled and the project has not been dismissed', () => {
- render(
- <ProjectNeedsSecuring>
- <div data-testid="project-children">Project content</div>
- </ProjectNeedsSecuring>
- )
- expect(screen.getByText('Your project needs securing')).toBeInTheDocument()
- expect(screen.getByText('Review and fix')).toBeInTheDocument()
- expect(screen.getByRole('link', { name: 'Open Data API settings' })).toHaveAttribute(
- 'href',
- '/project/project-ref/integrations/data_api/settings'
- )
- expect(screen.queryByRole('columnheader', { name: 'Action' })).not.toBeInTheDocument()
- expect(screen.queryByRole('link', { name: 'View policies' })).not.toBeInTheDocument()
- expect(screen.queryByText('profiles')).not.toBeInTheDocument()
- expect(screen.queryByText('customers')).not.toBeInTheDocument()
- expect(screen.getByText('Skip to home')).toBeInTheDocument()
- expect(screen.queryByTestId('project-children')).not.toBeInTheDocument()
- })
- it('navigates to the table policies page when a table row is clicked', () => {
- render(
- <ProjectNeedsSecuring>
- <div data-testid="project-children">Project content</div>
- </ProjectNeedsSecuring>
- )
- fireEvent.click(screen.getByText('invoices'))
- expect(mockRouterPush).toHaveBeenCalledWith(
- '/project/project-ref/auth/policies?schema=public&search=invoices'
- )
- })
- it('renders the project content when the security gate has been dismissed', () => {
- mockUseLocalStorageQuery.mockReturnValue([
- '2026-04-21T00:00:00.000Z',
- vi.fn(),
- { isLoading: false },
- ])
- render(
- <ProjectNeedsSecuring>
- <div data-testid="project-children">Project content</div>
- </ProjectNeedsSecuring>
- )
- expect(screen.queryByText('Your project needs securing')).not.toBeInTheDocument()
- expect(screen.getByTestId('project-children')).toBeInTheDocument()
- })
- it('renders the project content when there are no RLS issues', () => {
- mockUseProjectLintsQuery.mockReturnValue({
- data: [],
- isPending: false,
- isError: false,
- })
- render(
- <ProjectNeedsSecuring>
- <div data-testid="project-children">Project content</div>
- </ProjectNeedsSecuring>
- )
- expect(screen.queryByText('Your project needs securing')).not.toBeInTheDocument()
- expect(screen.getByTestId('project-children')).toBeInTheDocument()
- })
- it('renders the project content on non-home project routes', () => {
- mockUseRouter.mockReturnValue({ pathname: '/project/[ref]/database/tables' })
- render(
- <ProjectNeedsSecuring>
- <div data-testid="project-children">Project content</div>
- </ProjectNeedsSecuring>
- )
- expect(screen.queryByText('Your project needs securing')).not.toBeInTheDocument()
- expect(screen.getByTestId('project-children')).toBeInTheDocument()
- })
- it('renders the project content when the feature flag is disabled', () => {
- mockUseFlag.mockReturnValue(false)
- render(
- <ProjectNeedsSecuring>
- <div data-testid="project-children">Project content</div>
- </ProjectNeedsSecuring>
- )
- expect(screen.queryByText('Your project needs securing')).not.toBeInTheDocument()
- expect(screen.getByTestId('project-children')).toBeInTheDocument()
- })
- })
|