mirror of
https://github.com/supabase/supabase.git
synced 2026-09-06 09:59:03 +08:00
Just a tiny nit i came across - realised that if the schema is not exposed via the API, the warning that we show on the policies page RE data not being selectable is repeated for each table which imo seems unnecessary. Opting for a single admonition at the top instead ## Before <img width="1085" height="744" alt="image" src="https://github.com/user-attachments/assets/7bd8cf7b-f5a8-47d6-b41f-13fc4782ed8b" /> ### After <img width="1080" height="673" alt="image" src="https://github.com/user-attachments/assets/c839f502-42ff-4184-ad07-0ff2fd2d2676" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added an on-screen warning for tables whose schemas aren’t exposed via the project Data APIs, including a link to Data API settings. * **UI Improvements** * Refined the “filter entity types” control’s tooltip behavior and updated the popover header text. * **Bug Fixes** * Improved Data API/RLS status messaging by removing the prior “schema not exposed” outcome and showing “unknown” when access can’t be determined. * Consolidated policy warning rendering to avoid duplicated or inconsistent messages. * **Tests** * Updated policy/admonition helper tests to match the revised status and message behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
167 lines
5.7 KiB
TypeScript
167 lines
5.7 KiB
TypeScript
import { render } from '@testing-library/react'
|
||
import type { ReactElement } from 'react'
|
||
import { describe, expect, it } from 'vitest'
|
||
|
||
import { getTableAdmonitionMessage, getTableDataApiStatus } from './PolicyTableRow.utils'
|
||
import type { TableDataApiStatus } from './PolicyTableRow.utils'
|
||
import type { TableApiAccessData } from '@/data/privileges/table-api-access-query'
|
||
import type { ApiPrivilegesByRole } from '@/lib/data-api-types'
|
||
|
||
const FULL_PRIVILEGES: ApiPrivilegesByRole = {
|
||
anon: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
|
||
authenticated: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
|
||
service_role: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
|
||
}
|
||
|
||
const PARTIAL_PRIVILEGES: ApiPrivilegesByRole = {
|
||
anon: ['SELECT'],
|
||
authenticated: [],
|
||
service_role: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
|
||
}
|
||
|
||
const grantedAccess: TableApiAccessData = {
|
||
apiAccessType: 'access',
|
||
grantStatus: 'granted',
|
||
privileges: FULL_PRIVILEGES,
|
||
}
|
||
|
||
const customAccess: TableApiAccessData = {
|
||
apiAccessType: 'access',
|
||
grantStatus: 'custom',
|
||
privileges: PARTIAL_PRIVILEGES,
|
||
}
|
||
|
||
const noGrants: TableApiAccessData = { apiAccessType: 'exposed-schema-no-grants' }
|
||
const schemaNotExposedData: TableApiAccessData = { apiAccessType: 'none' }
|
||
|
||
describe('getTableDataApiStatus', () => {
|
||
it('returns no-grants when schema is exposed but no API roles have privileges', () => {
|
||
const status = getTableDataApiStatus({
|
||
apiAccessData: noGrants,
|
||
isRLSEnabled: true,
|
||
policiesCount: 0,
|
||
})
|
||
expect(status).toBe('no-grants')
|
||
})
|
||
|
||
it('returns custom-grants for partial/non-standard grants — even if RLS is off', () => {
|
||
const status = getTableDataApiStatus({
|
||
apiAccessData: customAccess,
|
||
isRLSEnabled: false,
|
||
policiesCount: 0,
|
||
})
|
||
expect(status).toBe('custom-grants')
|
||
})
|
||
|
||
it('returns publicly-readable when fully granted and RLS is off', () => {
|
||
const status = getTableDataApiStatus({
|
||
apiAccessData: grantedAccess,
|
||
isRLSEnabled: false,
|
||
policiesCount: 3,
|
||
})
|
||
expect(status).toBe('publicly-readable')
|
||
})
|
||
|
||
it('returns locked-by-rls when fully granted + RLS on + no policies', () => {
|
||
const status = getTableDataApiStatus({
|
||
apiAccessData: grantedAccess,
|
||
isRLSEnabled: true,
|
||
policiesCount: 0,
|
||
})
|
||
expect(status).toBe('locked-by-rls')
|
||
})
|
||
|
||
it('returns secured when fully granted + RLS on + policies exist', () => {
|
||
const status = getTableDataApiStatus({
|
||
apiAccessData: grantedAccess,
|
||
isRLSEnabled: true,
|
||
policiesCount: 2,
|
||
})
|
||
expect(status).toBe('secured')
|
||
})
|
||
|
||
it('returns unknown when apiAccessData is still loading or errored', () => {
|
||
// apiAccessData is undefined during loading AND on query error (isPending flips false
|
||
// but data stays undefined). We must not fall through to 'schema-not-exposed' — that
|
||
// would tell the user to reconfigure API settings for a schema that is in fact exposed.
|
||
const status = getTableDataApiStatus({
|
||
apiAccessData: undefined,
|
||
isRLSEnabled: true,
|
||
policiesCount: 0,
|
||
})
|
||
expect(status).toBe('unknown')
|
||
})
|
||
|
||
it('returns unknown when apiAccessData reports apiAccessType=none on an exposed schema', () => {
|
||
// Defensive: the query shouldn't emit apiAccessType=none when schema is exposed,
|
||
// but if it does we still don't want the false "schema not exposed" admonition.
|
||
const status = getTableDataApiStatus({
|
||
apiAccessData: schemaNotExposedData,
|
||
isRLSEnabled: true,
|
||
policiesCount: 0,
|
||
})
|
||
expect(status).toBe('unknown')
|
||
})
|
||
|
||
it('custom-grants wins over RLS state — we never claim public-readable for partial grants', () => {
|
||
const rlsOff = getTableDataApiStatus({
|
||
apiAccessData: customAccess,
|
||
isRLSEnabled: false,
|
||
policiesCount: 0,
|
||
})
|
||
const rlsOnNoPolicies = getTableDataApiStatus({
|
||
apiAccessData: customAccess,
|
||
isRLSEnabled: true,
|
||
policiesCount: 0,
|
||
})
|
||
expect(rlsOff).toBe('custom-grants')
|
||
expect(rlsOnNoPolicies).toBe('custom-grants')
|
||
})
|
||
})
|
||
|
||
const renderAdmonitionText = (status: TableDataApiStatus) => {
|
||
const message = getTableAdmonitionMessage({ status })
|
||
return render(message as ReactElement).container.textContent
|
||
}
|
||
|
||
describe('getTableAdmonitionMessage', () => {
|
||
it('returns the custom-grants copy', () => {
|
||
expect(renderAdmonitionText('custom-grants')).toBe(
|
||
'This table has custom Data API permissions — access may be restricted for some roles or operations.'
|
||
)
|
||
})
|
||
|
||
it('returns the no-grants copy, linking to the Data API settings for the given project ref', () => {
|
||
const message = getTableAdmonitionMessage({ status: 'no-grants', ref: 'my-project' })
|
||
const { container, getByRole } = render(message as ReactElement)
|
||
|
||
expect(container.textContent).toBe(
|
||
'This table cannot be accessed via the Data API. Enable access in your project’s Data API settings.'
|
||
)
|
||
expect(getByRole('link', { name: 'Data API settings' })).toHaveAttribute(
|
||
'href',
|
||
'/project/my-project/integrations/data_api/settings'
|
||
)
|
||
})
|
||
|
||
it('returns the publicly-readable copy', () => {
|
||
expect(renderAdmonitionText('publicly-readable')).toBe(
|
||
'This table can be accessed by anyone via the Data API as RLS is disabled.'
|
||
)
|
||
})
|
||
|
||
it('returns the locked-by-rls copy', () => {
|
||
expect(renderAdmonitionText('locked-by-rls')).toBe(
|
||
'No data will be returned via the Data API as no RLS policies exist on this table.'
|
||
)
|
||
})
|
||
|
||
it('returns null for secured — no admonition needed', () => {
|
||
expect(getTableAdmonitionMessage({ status: 'secured' })).toBeNull()
|
||
})
|
||
|
||
it('returns null for unknown — caller should stay silent during loading/errored state', () => {
|
||
expect(getTableAdmonitionMessage({ status: 'unknown' })).toBeNull()
|
||
})
|
||
})
|