Skip to content

Commit b722726

Browse files
feat(studio): scoped pat unit tests (supabase#42646)
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? This is a follow up to supabase#38170 adding unit tests for the functions and e2e tests for the feature. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Added comprehensive test suites covering access token schemas, token utilities (sorting, filtering, mapping, formatting, and expiration logic), and permission utilities (action sorting and resource toggle behavior). These tests exercise validation rules, edge cases, deterministic date handling, immutability, and multiple branch scenarios to improve reliability and coverage. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 1e749f7 commit b722726

3 files changed

Lines changed: 693 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)