ProjectNeedsSecuring.utils.test.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { describe, expect, it } from 'vitest'
  2. import type { ProjectSecurityTable } from './ProjectNeedsSecuring.types'
  3. import {
  4. buildSecurityPromptMarkdown,
  5. formatRlsDescription,
  6. getTableKey,
  7. getTablePoliciesHref,
  8. sortTables,
  9. } from './ProjectNeedsSecuring.utils'
  10. const table = (overrides: Partial<ProjectSecurityTable>): ProjectSecurityTable => ({
  11. id: 1,
  12. name: 't',
  13. schema: 'public',
  14. rlsEnabled: false,
  15. hasRlsIssue: false,
  16. dataApiAccessible: false,
  17. ...overrides,
  18. })
  19. describe('ProjectNeedsSecuring.utils: getTableKey', () => {
  20. it('joins schema and name with a dot', () => {
  21. expect(getTableKey({ schema: 'public', name: 'users' })).toBe('public.users')
  22. })
  23. })
  24. describe('ProjectNeedsSecuring.utils: formatRlsDescription', () => {
  25. it('returns the singular form when count is 1', () => {
  26. expect(formatRlsDescription(1)).toContain('1 table has RLS disabled')
  27. expect(formatRlsDescription(1)).toContain('its data')
  28. })
  29. it('returns the plural form for any other count', () => {
  30. expect(formatRlsDescription(3)).toContain('3 tables have RLS disabled')
  31. expect(formatRlsDescription(3)).toContain('their data')
  32. })
  33. })
  34. describe('ProjectNeedsSecuring.utils: sortTables', () => {
  35. it('puts tables with active RLS issues first', () => {
  36. const tables = [
  37. table({ id: 1, name: 'a', hasRlsIssue: false, rlsEnabled: true }),
  38. table({ id: 2, name: 'b', hasRlsIssue: true, rlsEnabled: false }),
  39. table({ id: 3, name: 'c', hasRlsIssue: false, rlsEnabled: false }),
  40. ]
  41. const sorted = sortTables(tables)
  42. expect(sorted.map((t) => t.name)).toEqual(['b', 'c', 'a'])
  43. })
  44. })
  45. describe('ProjectNeedsSecuring.utils: buildSecurityPromptMarkdown', () => {
  46. it('builds a markdown report with a header row and one row per table', () => {
  47. const markdown = buildSecurityPromptMarkdown(1, [
  48. table({ name: 'invoices', schema: 'public', dataApiAccessible: true, rlsEnabled: false }),
  49. ])
  50. expect(markdown).toContain('## Project security review')
  51. expect(markdown).toContain('1 table has RLS disabled')
  52. expect(markdown).toContain('| invoices | public | Yes | Disabled |')
  53. })
  54. })
  55. describe('ProjectNeedsSecuring.utils: getTablePoliciesHref', () => {
  56. it('builds the policies href with plain values', () => {
  57. expect(getTablePoliciesHref('abc', 'public', 'invoices')).toBe(
  58. '/project/abc/auth/policies?schema=public&search=invoices'
  59. )
  60. })
  61. it('preserves special characters in the table name', () => {
  62. const href = getTablePoliciesHref('abc', 'public', 'user_data&secret=1')
  63. const parsed = new URL(href, 'http://example.com')
  64. expect(parsed.searchParams.get('search')).toBe('user_data&secret=1')
  65. expect(parsed.searchParams.get('schema')).toBe('public')
  66. })
  67. it('preserves special characters in the schema', () => {
  68. const href = getTablePoliciesHref('abc', 'my schema+x', 'users')
  69. const parsed = new URL(href, 'http://example.com')
  70. expect(parsed.searchParams.get('schema')).toBe('my schema+x')
  71. expect(parsed.searchParams.get('search')).toBe('users')
  72. })
  73. it('encodes both values together', () => {
  74. const href = getTablePoliciesHref('abc', 'a&b=c', 'd e+f')
  75. const parsed = new URL(href, 'http://example.com')
  76. expect(parsed.searchParams.get('schema')).toBe('a&b=c')
  77. expect(parsed.searchParams.get('search')).toBe('d e+f')
  78. })
  79. it('falls back to empty strings for undefined inputs', () => {
  80. expect(getTablePoliciesHref(undefined, undefined, undefined)).toBe(
  81. '/project//auth/policies?schema=&search='
  82. )
  83. })
  84. })