Files
supabase/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccess.utils.test.ts
Etienne Stalmans 85743d7215 feat: branching support for temporary access (#45411)
## 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?

feature


## Additional context

Needs API deployment, adds a toggle to allow roles to only be available
on branch projects


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a "Branches only" option for JIT database access grants;
included when grants are submitted.

* **UI**
* Configuration UI shows an informational notice and hides
temporary-access controls when preview branches are managed from the
main branch.
* Feature preview label changed to "Temporary access"; badge text now
reads "Preview".

* **Tests**
  * Unit test updated to cover branches-only serialization.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45411?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Danny White <3104761+dnywh@users.noreply.github.com>
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-05-18 09:47:59 +02:00

153 lines
4.2 KiB
TypeScript

import dayjs from 'dayjs'
import { describe, expect, it } from 'vitest'
import type { JitUserRuleDraft } from './JitDbAccess.types'
import {
computeStatusFromGrants,
createEmptyGrant,
getInvalidIpRangeRows,
getJitMemberOptions,
getRelativeDatetimeByMode,
serializeDraftRolesForGrantMutation,
} from './JitDbAccess.utils'
import type { OrganizationMembersData } from '@/data/organizations/organization-members-query'
describe('jitDbAccess.utils', () => {
it('returns empty expiry string for never/custom-only fallback modes', () => {
expect(getRelativeDatetimeByMode('never')).toBe('')
expect(getRelativeDatetimeByMode('custom')).toBe('')
})
it('creates future datetimes for preset expiry modes', () => {
const inOneHour = dayjs(getRelativeDatetimeByMode('1h'))
expect(inOneHour.isValid()).toBe(true)
expect(inOneHour.isAfter(dayjs())).toBe(true)
})
it('computes active and expired status counts including IP counts', () => {
const activeGrant = {
...createEmptyGrant('role_active'),
enabled: true,
hasExpiry: true,
expiry: dayjs().add(1, 'day').toISOString(),
ipRanges: [{ value: '192.0.2.0/24' }],
}
const expiredGrant = {
...createEmptyGrant('role_expired'),
enabled: true,
hasExpiry: true,
expiry: dayjs().subtract(1, 'day').toISOString(),
ipRanges: [{ value: '203.0.113.0/24' }],
}
const perpetualGrant = {
...createEmptyGrant('role_never'),
enabled: true,
hasExpiry: false,
expiryMode: 'never' as const,
expiry: '',
}
expect(computeStatusFromGrants([activeGrant, expiredGrant, perpetualGrant])).toEqual({
active: 2,
expired: 1,
activeIp: 1,
expiredIp: 1,
})
})
it('returns invalid CIDRs from repeated input rows', () => {
expect(
getInvalidIpRangeRows([
{ value: '192.0.2.0/24' },
{ value: 'not-a-cidr' },
{ value: '10.0.0.1/33' },
{ value: '2001:db8::/64' },
{ value: '2001:db8::/129' },
])
).toEqual(['not-a-cidr', '10.0.0.1/33', '2001:db8::/129'])
})
})
describe('serializeDraftRolesForGrantMutation', () => {
it('serializes role expiry and IP restrictions for grant mutation payload', () => {
const expiry = '2026-06-01T12:00:00.000Z'
const draft: JitUserRuleDraft = {
memberId: 'user-1',
grants: [
{
...createEmptyGrant('postgres'),
enabled: true,
branchesOnly: true,
hasExpiry: true,
expiryMode: 'custom',
expiry,
ipRanges: [{ value: '192.0.2.0/24' }, { value: ' ' }, { value: '2001:db8::/64' }],
},
{
...createEmptyGrant('supabase_read_only_user'),
enabled: true,
hasExpiry: false,
expiryMode: 'never',
expiry: '',
},
{
...createEmptyGrant('ignored_disabled'),
enabled: false,
},
],
}
expect(serializeDraftRolesForGrantMutation(draft)).toEqual([
{
role: 'postgres',
branches_only: true,
expires_at: dayjs(expiry).unix(),
allowed_networks: {
allowed_cidrs: [{ cidr: '192.0.2.0/24' }],
allowed_cidrs_v6: [{ cidr: '2001:db8::/64' }],
},
},
{
role: 'supabase_read_only_user',
},
])
})
})
describe('getJitMemberOptions', () => {
it('excludes invited org members without gotrue IDs from selectable options', () => {
const organizationMembers: OrganizationMembersData = [
{
gotrue_id: 'de305d54-75b4-431b-adb2-eb6b9e546014',
primary_email: 'active@example.com',
username: 'Active User',
is_sso_user: false,
mfa_enabled: false,
metadata: {},
role_ids: [],
},
{
gotrue_id: '',
invited_id: 123,
invited_at: '2026-03-01T00:00:00.000Z',
primary_email: 'expired-invite@example.com',
username: 'e',
is_sso_user: false,
mfa_enabled: false,
metadata: {},
role_ids: [],
},
]
expect(getJitMemberOptions(organizationMembers, [])).toEqual([
{
id: 'de305d54-75b4-431b-adb2-eb6b9e546014',
email: 'active@example.com',
name: 'Active User',
},
])
})
})