PolicyTableRow.utils.test.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import { describe, expect, it } from 'vitest'
  2. import { getTableAdmonitionMessage, getTableDataApiStatus } from './PolicyTableRow.utils'
  3. import type { TableApiAccessData } from '@/data/privileges/table-api-access-query'
  4. import type { ApiPrivilegesByRole } from '@/lib/data-api-types'
  5. const FULL_PRIVILEGES: ApiPrivilegesByRole = {
  6. anon: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
  7. authenticated: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
  8. service_role: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
  9. }
  10. const PARTIAL_PRIVILEGES: ApiPrivilegesByRole = {
  11. anon: ['SELECT'],
  12. authenticated: [],
  13. service_role: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'],
  14. }
  15. const grantedAccess: TableApiAccessData = {
  16. apiAccessType: 'access',
  17. grantStatus: 'granted',
  18. privileges: FULL_PRIVILEGES,
  19. }
  20. const customAccess: TableApiAccessData = {
  21. apiAccessType: 'access',
  22. grantStatus: 'custom',
  23. privileges: PARTIAL_PRIVILEGES,
  24. }
  25. const noGrants: TableApiAccessData = { apiAccessType: 'exposed-schema-no-grants' }
  26. const schemaNotExposedData: TableApiAccessData = { apiAccessType: 'none' }
  27. describe('getTableDataApiStatus', () => {
  28. it('returns schema-not-exposed when the schema is not in the exposed list', () => {
  29. const status = getTableDataApiStatus({
  30. isSchemaExposed: false,
  31. apiAccessData: grantedAccess,
  32. isRLSEnabled: true,
  33. policiesCount: 1,
  34. })
  35. expect(status).toBe('schema-not-exposed')
  36. })
  37. it('returns no-grants when schema is exposed but no API roles have privileges', () => {
  38. const status = getTableDataApiStatus({
  39. isSchemaExposed: true,
  40. apiAccessData: noGrants,
  41. isRLSEnabled: true,
  42. policiesCount: 0,
  43. })
  44. expect(status).toBe('no-grants')
  45. })
  46. it('returns custom-grants for partial/non-standard grants — even if RLS is off', () => {
  47. const status = getTableDataApiStatus({
  48. isSchemaExposed: true,
  49. apiAccessData: customAccess,
  50. isRLSEnabled: false,
  51. policiesCount: 0,
  52. })
  53. expect(status).toBe('custom-grants')
  54. })
  55. it('returns publicly-readable when fully granted and RLS is off', () => {
  56. const status = getTableDataApiStatus({
  57. isSchemaExposed: true,
  58. apiAccessData: grantedAccess,
  59. isRLSEnabled: false,
  60. policiesCount: 3,
  61. })
  62. expect(status).toBe('publicly-readable')
  63. })
  64. it('returns locked-by-rls when fully granted + RLS on + no policies', () => {
  65. const status = getTableDataApiStatus({
  66. isSchemaExposed: true,
  67. apiAccessData: grantedAccess,
  68. isRLSEnabled: true,
  69. policiesCount: 0,
  70. })
  71. expect(status).toBe('locked-by-rls')
  72. })
  73. it('returns secured when fully granted + RLS on + policies exist', () => {
  74. const status = getTableDataApiStatus({
  75. isSchemaExposed: true,
  76. apiAccessData: grantedAccess,
  77. isRLSEnabled: true,
  78. policiesCount: 2,
  79. })
  80. expect(status).toBe('secured')
  81. })
  82. it('returns unknown when apiAccessData is still loading or errored', () => {
  83. // apiAccessData is undefined during loading AND on query error (isPending flips false
  84. // but data stays undefined). We must not fall through to 'schema-not-exposed' — that
  85. // would tell the user to reconfigure API settings for a schema that is in fact exposed.
  86. const status = getTableDataApiStatus({
  87. isSchemaExposed: true,
  88. apiAccessData: undefined,
  89. isRLSEnabled: true,
  90. policiesCount: 0,
  91. })
  92. expect(status).toBe('unknown')
  93. })
  94. it('returns unknown when apiAccessData reports apiAccessType=none on an exposed schema', () => {
  95. // Defensive: the query shouldn't emit apiAccessType=none when schema is exposed,
  96. // but if it does we still don't want the false "schema not exposed" admonition.
  97. const status = getTableDataApiStatus({
  98. isSchemaExposed: true,
  99. apiAccessData: schemaNotExposedData,
  100. isRLSEnabled: true,
  101. policiesCount: 0,
  102. })
  103. expect(status).toBe('unknown')
  104. })
  105. it('isSchemaExposed=false wins over any apiAccessData value', () => {
  106. const status = getTableDataApiStatus({
  107. isSchemaExposed: false,
  108. apiAccessData: noGrants,
  109. isRLSEnabled: true,
  110. policiesCount: 0,
  111. })
  112. expect(status).toBe('schema-not-exposed')
  113. })
  114. it('custom-grants wins over RLS state — we never claim public-readable for partial grants', () => {
  115. const rlsOff = getTableDataApiStatus({
  116. isSchemaExposed: true,
  117. apiAccessData: customAccess,
  118. isRLSEnabled: false,
  119. policiesCount: 0,
  120. })
  121. const rlsOnNoPolicies = getTableDataApiStatus({
  122. isSchemaExposed: true,
  123. apiAccessData: customAccess,
  124. isRLSEnabled: true,
  125. policiesCount: 0,
  126. })
  127. expect(rlsOff).toBe('custom-grants')
  128. expect(rlsOnNoPolicies).toBe('custom-grants')
  129. })
  130. })
  131. describe('getTableAdmonitionMessage', () => {
  132. it('returns the custom-grants copy', () => {
  133. expect(getTableAdmonitionMessage('custom-grants')).toBe(
  134. 'This table has custom Data API permissions — access may be restricted for some roles or operations.'
  135. )
  136. })
  137. it('returns the no-grants copy', () => {
  138. expect(getTableAdmonitionMessage('no-grants')).toBe(
  139. 'This table cannot be accessed via the Data API. Enable access in your project’s Data API settings.'
  140. )
  141. })
  142. it('returns the publicly-readable copy', () => {
  143. expect(getTableAdmonitionMessage('publicly-readable')).toBe(
  144. 'This table can be accessed by anyone via the Data API as RLS is disabled.'
  145. )
  146. })
  147. it('returns the locked-by-rls copy', () => {
  148. expect(getTableAdmonitionMessage('locked-by-rls')).toBe(
  149. 'No data will be returned via the Data API as no RLS policies exist on this table.'
  150. )
  151. })
  152. it('returns null for secured — no admonition needed', () => {
  153. expect(getTableAdmonitionMessage('secured')).toBeNull()
  154. })
  155. it('returns null for schema-not-exposed — handled by a separate admonition with a link', () => {
  156. expect(getTableAdmonitionMessage('schema-not-exposed')).toBeNull()
  157. })
  158. it('returns null for unknown — caller should stay silent during loading/errored state', () => {
  159. expect(getTableAdmonitionMessage('unknown')).toBeNull()
  160. })
  161. })