AccessToken.schemas.test.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import { describe, expect, it } from 'vitest'
  2. import { PermissionRowSchema, TokenSchema } from './AccessToken.schemas'
  3. const validTokenData = {
  4. tokenName: 'My Token',
  5. expiresAt: 'day',
  6. resourceAccess: 'all-orgs' as const,
  7. permissionRows: [{ resource: 'organization:billing', actions: ['read'] }],
  8. }
  9. // --- PermissionRowSchema ---
  10. describe('PermissionRowSchema', () => {
  11. it('should pass for a valid permission row', () => {
  12. const result = PermissionRowSchema.safeParse({
  13. resource: 'organization:billing',
  14. actions: ['read'],
  15. })
  16. expect(result.success).toBe(true)
  17. })
  18. it('should fail when resource is empty', () => {
  19. const result = PermissionRowSchema.safeParse({
  20. resource: '',
  21. actions: ['read'],
  22. })
  23. expect(result.success).toBe(false)
  24. if (!result.success) {
  25. expect(result.error.issues[0].message).toBe('Please select a resource')
  26. }
  27. })
  28. it('should fail when actions is empty', () => {
  29. const result = PermissionRowSchema.safeParse({
  30. resource: 'organization:billing',
  31. actions: [],
  32. })
  33. expect(result.success).toBe(false)
  34. if (!result.success) {
  35. expect(result.error.issues[0].message).toBe('Please select at least one action')
  36. }
  37. })
  38. it('should fail when resource is missing', () => {
  39. const result = PermissionRowSchema.safeParse({ actions: ['read'] })
  40. expect(result.success).toBe(false)
  41. })
  42. it('should fail when actions is missing', () => {
  43. const result = PermissionRowSchema.safeParse({ resource: 'organization:billing' })
  44. expect(result.success).toBe(false)
  45. })
  46. })
  47. // --- TokenSchema ---
  48. describe('TokenSchema', () => {
  49. it('should pass for valid token data', () => {
  50. const result = TokenSchema.safeParse(validTokenData)
  51. expect(result.success).toBe(true)
  52. })
  53. it('should fail when tokenName is empty', () => {
  54. const result = TokenSchema.safeParse({ ...validTokenData, tokenName: '' })
  55. expect(result.success).toBe(false)
  56. if (!result.success) {
  57. const nameError = result.error.issues.find((i) => i.path.includes('tokenName'))
  58. expect(nameError?.message).toBe('Please enter a name for the token')
  59. }
  60. })
  61. it('should fail when tokenName is missing', () => {
  62. const { tokenName, ...rest } = validTokenData
  63. const result = TokenSchema.safeParse(rest)
  64. expect(result.success).toBe(false)
  65. })
  66. it('should fail when permissionRows is empty', () => {
  67. const result = TokenSchema.safeParse({ ...validTokenData, permissionRows: [] })
  68. expect(result.success).toBe(false)
  69. if (!result.success) {
  70. const permError = result.error.issues.find((i) => i.path.includes('permissionRows'))
  71. expect(permError?.message).toBe('Please configure at least one permission')
  72. }
  73. })
  74. it('should fail when resourceAccess is not a valid enum value', () => {
  75. const result = TokenSchema.safeParse({ ...validTokenData, resourceAccess: 'invalid' })
  76. expect(result.success).toBe(false)
  77. })
  78. it('should accept all valid resourceAccess enum values', () => {
  79. for (const value of ['all-orgs', 'selected-orgs', 'selected-projects'] as const) {
  80. const result = TokenSchema.safeParse({ ...validTokenData, resourceAccess: value })
  81. expect(result.success).toBe(true)
  82. }
  83. })
  84. describe('expiresAt preprocessing', () => {
  85. it('should convert "never" to undefined', () => {
  86. const result = TokenSchema.safeParse({ ...validTokenData, expiresAt: 'never' })
  87. expect(result.success).toBe(true)
  88. if (result.success) {
  89. expect(result.data.expiresAt).toBeUndefined()
  90. }
  91. })
  92. it('should pass through other string values', () => {
  93. const result = TokenSchema.safeParse({ ...validTokenData, expiresAt: 'day' })
  94. expect(result.success).toBe(true)
  95. if (result.success) {
  96. expect(result.data.expiresAt).toBe('day')
  97. }
  98. })
  99. it('should allow expiresAt to be omitted', () => {
  100. const { expiresAt, ...rest } = validTokenData
  101. const result = TokenSchema.safeParse(rest)
  102. expect(result.success).toBe(true)
  103. })
  104. })
  105. describe('custom expiry refinement', () => {
  106. it('should fail when expiresAt is "custom" and customExpiryDate is not provided', () => {
  107. const result = TokenSchema.safeParse({
  108. ...validTokenData,
  109. expiresAt: 'custom',
  110. customExpiryDate: undefined,
  111. })
  112. expect(result.success).toBe(false)
  113. if (!result.success) {
  114. const customError = result.error.issues.find((i) => i.path.includes('expiresAt'))
  115. expect(customError?.message).toBe('Please select a custom expiry date')
  116. }
  117. })
  118. it('should fail when expiresAt is "custom" and customExpiryDate is empty string', () => {
  119. const result = TokenSchema.safeParse({
  120. ...validTokenData,
  121. expiresAt: 'custom',
  122. customExpiryDate: '',
  123. })
  124. expect(result.success).toBe(false)
  125. })
  126. it('should pass when expiresAt is "custom" and customExpiryDate is provided', () => {
  127. const result = TokenSchema.safeParse({
  128. ...validTokenData,
  129. expiresAt: 'custom',
  130. customExpiryDate: '2026-12-31T00:00:00Z',
  131. })
  132. expect(result.success).toBe(true)
  133. })
  134. it('should pass when expiresAt is not "custom" even without customExpiryDate', () => {
  135. const result = TokenSchema.safeParse({
  136. ...validTokenData,
  137. expiresAt: 'day',
  138. })
  139. expect(result.success).toBe(true)
  140. })
  141. })
  142. describe('nested permissionRows validation', () => {
  143. it('should fail when a permission row has an empty resource', () => {
  144. const result = TokenSchema.safeParse({
  145. ...validTokenData,
  146. permissionRows: [{ resource: '', actions: ['read'] }],
  147. })
  148. expect(result.success).toBe(false)
  149. })
  150. it('should fail when a permission row has empty actions', () => {
  151. const result = TokenSchema.safeParse({
  152. ...validTokenData,
  153. permissionRows: [{ resource: 'organization:billing', actions: [] }],
  154. })
  155. expect(result.success).toBe(false)
  156. })
  157. it('should pass with multiple valid permission rows', () => {
  158. const result = TokenSchema.safeParse({
  159. ...validTokenData,
  160. permissionRows: [
  161. { resource: 'organization:billing', actions: ['read'] },
  162. { resource: 'organization:members', actions: ['read', 'write'] },
  163. ],
  164. })
  165. expect(result.success).toBe(true)
  166. })
  167. })
  168. })