test: cover the authz primitives (getAuthMember/requireMember/requireMemberWithRole)
These gate every server action but had no direct tests. Add cases for: no user, no active membership, active member; role allow/deny; the require_approval unverified gate; and late members keeping their role. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
9bd21b185d
commit
ad010edb41
@@ -1,6 +1,44 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { z } from 'zod'
|
||||
import { parseInput } from '@/lib/auth-helpers'
|
||||
import {
|
||||
parseInput,
|
||||
getAuthMember,
|
||||
requireMember,
|
||||
requireMemberWithRole,
|
||||
type ServerSupabase,
|
||||
} from '@/lib/auth-helpers'
|
||||
import { ADMIN_ROLES } from '@/lib/permissions'
|
||||
|
||||
/**
|
||||
* Minimal mock of the query the auth helpers issue:
|
||||
* supabase.auth.getUser()
|
||||
* supabase.from('space_members').select(...).eq(...).in(...).single()
|
||||
*/
|
||||
function mockClient(opts: {
|
||||
user?: { id: string } | null
|
||||
member?: Record<string, unknown> | null
|
||||
}): ServerSupabase {
|
||||
const chain = {
|
||||
select: () => chain,
|
||||
eq: () => chain,
|
||||
in: () => chain,
|
||||
single: () => Promise.resolve({ data: opts.member ?? null }),
|
||||
}
|
||||
return {
|
||||
auth: { getUser: () => Promise.resolve({ data: { user: opts.user ?? null } }) },
|
||||
from: () => chain,
|
||||
} as unknown as ServerSupabase
|
||||
}
|
||||
|
||||
const activeMember = (role: string, status = 'current') => ({
|
||||
id: 'm1',
|
||||
space_id: 'space-A',
|
||||
user_id: 'u1',
|
||||
role,
|
||||
status,
|
||||
display_name: 'M',
|
||||
handle: 'm',
|
||||
})
|
||||
|
||||
describe('parseInput', () => {
|
||||
const schema = z.object({
|
||||
@@ -41,3 +79,66 @@ describe('parseInput', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAuthMember', () => {
|
||||
it('returns null when there is no authenticated user', async () => {
|
||||
expect(await getAuthMember(mockClient({ user: null }))).toBeNull()
|
||||
})
|
||||
|
||||
it('returns null when the user has no active membership', async () => {
|
||||
expect(await getAuthMember(mockClient({ user: { id: 'u1' }, member: null }))).toBeNull()
|
||||
})
|
||||
|
||||
it('returns the active member for an authenticated user', async () => {
|
||||
const m = await getAuthMember(mockClient({ user: { id: 'u1' }, member: activeMember('member') }))
|
||||
expect(m?.space_id).toBe('space-A')
|
||||
expect(m?.role).toBe('member')
|
||||
})
|
||||
})
|
||||
|
||||
describe('requireMember', () => {
|
||||
it('errors when unauthenticated', async () => {
|
||||
const r = await requireMember(mockClient({ user: null }))
|
||||
expect(r.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('ok for an active member', async () => {
|
||||
const r = await requireMember(mockClient({ user: { id: 'u1' }, member: activeMember('member') }))
|
||||
expect(r.ok).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('requireMemberWithRole', () => {
|
||||
it('rejects an unverified member even with an admin role (require_approval gate)', async () => {
|
||||
const r = await requireMemberWithRole(
|
||||
mockClient({ user: { id: 'u1' }, member: activeMember('admin', 'unverified') }),
|
||||
ADMIN_ROLES,
|
||||
)
|
||||
expect(r.ok).toBe(false)
|
||||
if (!r.ok) expect(r.error).toMatch(/pending approval/i)
|
||||
})
|
||||
|
||||
it('rejects a current member whose role is not allowed', async () => {
|
||||
const r = await requireMemberWithRole(
|
||||
mockClient({ user: { id: 'u1' }, member: activeMember('member') }),
|
||||
ADMIN_ROLES,
|
||||
)
|
||||
expect(r.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('allows a current admin', async () => {
|
||||
const r = await requireMemberWithRole(
|
||||
mockClient({ user: { id: 'u1' }, member: activeMember('admin') }),
|
||||
ADMIN_ROLES,
|
||||
)
|
||||
expect(r.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('allows a late member to keep their privileged role (dues lapse is not an authz downgrade)', async () => {
|
||||
const r = await requireMemberWithRole(
|
||||
mockClient({ user: { id: 'u1' }, member: activeMember('board', 'late') }),
|
||||
ADMIN_ROLES,
|
||||
)
|
||||
expect(r.ok).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user