useTestQueryRLS.utils.test.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import { describe, expect, it } from 'vitest'
  2. import type { Policy } from '@/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRow.utils'
  3. import { filterTablePolicies } from '@/components/interfaces/Auth/RLSTester/useTestQueryRLS.utils'
  4. const makePolicy = (overrides: Partial<Policy>): Policy =>
  5. ({
  6. schema: 'public',
  7. table: 'items',
  8. roles: ['anon'],
  9. command: 'SELECT',
  10. ...overrides,
  11. }) as Policy
  12. const base = {
  13. policies: [] as Policy[],
  14. schema: 'public',
  15. table: 'items',
  16. role: 'anon',
  17. operation: 'SELECT' as const,
  18. }
  19. describe('filterTablePolicies', () => {
  20. describe('schema / table matching', () => {
  21. it('excludes policies from a different schema', () => {
  22. const policy = makePolicy({ schema: 'private', table: 'items' })
  23. expect(filterTablePolicies({ ...base, policies: [policy] })).toHaveLength(0)
  24. })
  25. it('excludes policies from a different table', () => {
  26. const policy = makePolicy({ schema: 'public', table: 'other' })
  27. expect(filterTablePolicies({ ...base, policies: [policy] })).toHaveLength(0)
  28. })
  29. it('includes a policy matching schema and table', () => {
  30. const policy = makePolicy({ schema: 'public', table: 'items' })
  31. expect(filterTablePolicies({ ...base, policies: [policy] })).toHaveLength(1)
  32. })
  33. })
  34. describe('role matching', () => {
  35. it('includes policy when role is in the policy roles array', () => {
  36. const policy = makePolicy({ roles: ['anon', 'authenticated'] })
  37. expect(filterTablePolicies({ ...base, role: 'anon', policies: [policy] })).toHaveLength(1)
  38. })
  39. it('excludes policy when role is not in the policy roles array', () => {
  40. const policy = makePolicy({ roles: ['authenticated'] })
  41. expect(filterTablePolicies({ ...base, role: 'anon', policies: [policy] })).toHaveLength(0)
  42. })
  43. it('includes policy when the only role is "public" (applies to all roles)', () => {
  44. const policy = makePolicy({ roles: ['public'] })
  45. expect(filterTablePolicies({ ...base, role: 'anon', policies: [policy] })).toHaveLength(1)
  46. })
  47. it('excludes "public" role shortcut when policy has multiple roles including public', () => {
  48. const policy = makePolicy({ roles: ['public', 'authenticated'] })
  49. expect(filterTablePolicies({ ...base, role: 'anon', policies: [policy] })).toHaveLength(0)
  50. })
  51. it('handles undefined role (service role) — matches nothing unless public', () => {
  52. const rolePolicy = makePolicy({ roles: ['anon'] })
  53. const publicPolicy = makePolicy({ roles: ['public'] })
  54. const result = filterTablePolicies({
  55. ...base,
  56. role: undefined,
  57. policies: [rolePolicy, publicPolicy],
  58. })
  59. expect(result).toHaveLength(1)
  60. expect(result[0]).toBe(publicPolicy)
  61. })
  62. })
  63. describe('command matching', () => {
  64. it('includes policy when command matches the operation', () => {
  65. const policy = makePolicy({ command: 'SELECT' })
  66. expect(
  67. filterTablePolicies({ ...base, operation: 'SELECT', policies: [policy] })
  68. ).toHaveLength(1)
  69. })
  70. it('excludes policy when command does not match the operation', () => {
  71. const policy = makePolicy({ command: 'INSERT' })
  72. expect(
  73. filterTablePolicies({ ...base, operation: 'SELECT', policies: [policy] })
  74. ).toHaveLength(0)
  75. })
  76. it('includes policy with command ALL regardless of operation', () => {
  77. const policy = makePolicy({ command: 'ALL' })
  78. expect(
  79. filterTablePolicies({ ...base, operation: 'SELECT', policies: [policy] })
  80. ).toHaveLength(1)
  81. })
  82. it('includes ALL command policy for non-SELECT operations too', () => {
  83. const policy = makePolicy({ command: 'ALL' })
  84. expect(
  85. filterTablePolicies({ ...base, operation: 'INSERT', policies: [policy] })
  86. ).toHaveLength(1)
  87. })
  88. it('does not include a SELECT-only policy when operation is INSERT', () => {
  89. const policy = makePolicy({ command: 'SELECT' })
  90. expect(
  91. filterTablePolicies({ ...base, operation: 'INSERT', policies: [policy] })
  92. ).toHaveLength(0)
  93. })
  94. })
  95. describe('combined filters', () => {
  96. it('returns only policies that satisfy all conditions', () => {
  97. const match = makePolicy({
  98. schema: 'public',
  99. table: 'items',
  100. roles: ['anon'],
  101. command: 'ALL',
  102. })
  103. const wrongSchema = makePolicy({
  104. schema: 'private',
  105. table: 'items',
  106. roles: ['anon'],
  107. command: 'ALL',
  108. })
  109. const wrongRole = makePolicy({
  110. schema: 'public',
  111. table: 'items',
  112. roles: ['authenticated'],
  113. command: 'ALL',
  114. })
  115. const wrongCommand = makePolicy({
  116. schema: 'public',
  117. table: 'items',
  118. roles: ['anon'],
  119. command: 'INSERT',
  120. })
  121. const result = filterTablePolicies({
  122. ...base,
  123. policies: [match, wrongSchema, wrongRole, wrongCommand],
  124. })
  125. expect(result).toHaveLength(1)
  126. expect(result[0]).toBe(match)
  127. })
  128. })
  129. })