300 lines
8.0 KiB
TypeScript
300 lines
8.0 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { renderHook } from '@testing-library/react';
|
|
import { usePermissions } from './usePermissions';
|
|
import type { CurrentUser } from '@/shared/types/auth';
|
|
|
|
const mockUser: CurrentUser = {
|
|
id: 'user-1',
|
|
email: 'test@example.com',
|
|
firstName: 'Test',
|
|
lastName: 'User',
|
|
permissions: ['READ_CAMPUSES', 'READ_DASHBOARD', 'READ_FRAME'],
|
|
app_role: { name: 'teacher', globalAccess: false },
|
|
};
|
|
|
|
const mockGlobalUser: CurrentUser = {
|
|
id: 'admin-1',
|
|
email: 'admin@example.com',
|
|
firstName: 'Admin',
|
|
lastName: 'User',
|
|
permissions: [],
|
|
app_role: { name: 'super_admin', globalAccess: true },
|
|
};
|
|
|
|
// Mock the auth context
|
|
vi.mock('@/contexts/useAuth', () => ({
|
|
useAuth: vi.fn(),
|
|
}));
|
|
|
|
import { useAuth } from '@/contexts/useAuth';
|
|
|
|
describe('usePermissions', () => {
|
|
it('returns empty permissions array for null user', () => {
|
|
vi.mocked(useAuth).mockReturnValue({
|
|
user: null,
|
|
profile: null,
|
|
loading: false,
|
|
isAuthenticated: false,
|
|
signIn: vi.fn(),
|
|
signUp: vi.fn(),
|
|
signOut: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
const { result } = renderHook(() => usePermissions());
|
|
|
|
expect(result.current.permissions).toEqual([]);
|
|
});
|
|
|
|
it('returns user permissions array when user exists', () => {
|
|
vi.mocked(useAuth).mockReturnValue({
|
|
user: mockUser,
|
|
profile: null,
|
|
loading: false,
|
|
isAuthenticated: true,
|
|
signIn: vi.fn(),
|
|
signUp: vi.fn(),
|
|
signOut: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
const { result } = renderHook(() => usePermissions());
|
|
|
|
expect(result.current.permissions).toEqual(['READ_CAMPUSES', 'READ_DASHBOARD', 'READ_FRAME']);
|
|
});
|
|
|
|
describe('has()', () => {
|
|
it('returns true for granted permission', () => {
|
|
vi.mocked(useAuth).mockReturnValue({
|
|
user: mockUser,
|
|
profile: null,
|
|
loading: false,
|
|
isAuthenticated: true,
|
|
signIn: vi.fn(),
|
|
signUp: vi.fn(),
|
|
signOut: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
const { result } = renderHook(() => usePermissions());
|
|
|
|
expect(result.current.has('READ_CAMPUSES')).toBe(true);
|
|
});
|
|
|
|
it('returns false for missing permission', () => {
|
|
vi.mocked(useAuth).mockReturnValue({
|
|
user: mockUser,
|
|
profile: null,
|
|
loading: false,
|
|
isAuthenticated: true,
|
|
signIn: vi.fn(),
|
|
signUp: vi.fn(),
|
|
signOut: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
const { result } = renderHook(() => usePermissions());
|
|
|
|
expect(result.current.has('DELETE_ORGANIZATIONS')).toBe(false);
|
|
});
|
|
|
|
it('returns true for any permission when user has globalAccess', () => {
|
|
vi.mocked(useAuth).mockReturnValue({
|
|
user: mockGlobalUser,
|
|
profile: null,
|
|
loading: false,
|
|
isAuthenticated: true,
|
|
signIn: vi.fn(),
|
|
signUp: vi.fn(),
|
|
signOut: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
const { result } = renderHook(() => usePermissions());
|
|
|
|
expect(result.current.has('DELETE_ORGANIZATIONS')).toBe(true);
|
|
expect(result.current.has('UPDATE_USERS')).toBe(true);
|
|
});
|
|
|
|
it('returns false for null user', () => {
|
|
vi.mocked(useAuth).mockReturnValue({
|
|
user: null,
|
|
profile: null,
|
|
loading: false,
|
|
isAuthenticated: false,
|
|
signIn: vi.fn(),
|
|
signUp: vi.fn(),
|
|
signOut: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
const { result } = renderHook(() => usePermissions());
|
|
|
|
expect(result.current.has('READ_CAMPUSES')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('hasAny()', () => {
|
|
it('returns true if any permission matches', () => {
|
|
vi.mocked(useAuth).mockReturnValue({
|
|
user: mockUser,
|
|
profile: null,
|
|
loading: false,
|
|
isAuthenticated: true,
|
|
signIn: vi.fn(),
|
|
signUp: vi.fn(),
|
|
signOut: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
const { result } = renderHook(() => usePermissions());
|
|
|
|
expect(result.current.hasAny(['READ_CAMPUSES', 'DELETE_ORGANIZATIONS'])).toBe(true);
|
|
});
|
|
|
|
it('returns false if no permissions match', () => {
|
|
vi.mocked(useAuth).mockReturnValue({
|
|
user: mockUser,
|
|
profile: null,
|
|
loading: false,
|
|
isAuthenticated: true,
|
|
signIn: vi.fn(),
|
|
signUp: vi.fn(),
|
|
signOut: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
const { result } = renderHook(() => usePermissions());
|
|
|
|
expect(result.current.hasAny(['DELETE_ORGANIZATIONS', 'UPDATE_USERS'])).toBe(false);
|
|
});
|
|
|
|
it('returns true for any permissions when user has globalAccess', () => {
|
|
vi.mocked(useAuth).mockReturnValue({
|
|
user: mockGlobalUser,
|
|
profile: null,
|
|
loading: false,
|
|
isAuthenticated: true,
|
|
signIn: vi.fn(),
|
|
signUp: vi.fn(),
|
|
signOut: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
const { result } = renderHook(() => usePermissions());
|
|
|
|
expect(result.current.hasAny(['DELETE_ORGANIZATIONS', 'UPDATE_USERS'])).toBe(true);
|
|
});
|
|
|
|
it('returns false for null user', () => {
|
|
vi.mocked(useAuth).mockReturnValue({
|
|
user: null,
|
|
profile: null,
|
|
loading: false,
|
|
isAuthenticated: false,
|
|
signIn: vi.fn(),
|
|
signUp: vi.fn(),
|
|
signOut: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
const { result } = renderHook(() => usePermissions());
|
|
|
|
expect(result.current.hasAny(['READ_CAMPUSES'])).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('hasAll()', () => {
|
|
it('returns true if all permissions match', () => {
|
|
vi.mocked(useAuth).mockReturnValue({
|
|
user: mockUser,
|
|
profile: null,
|
|
loading: false,
|
|
isAuthenticated: true,
|
|
signIn: vi.fn(),
|
|
signUp: vi.fn(),
|
|
signOut: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
const { result } = renderHook(() => usePermissions());
|
|
|
|
expect(result.current.hasAll(['READ_CAMPUSES', 'READ_DASHBOARD'])).toBe(true);
|
|
});
|
|
|
|
it('returns false if any permission is missing', () => {
|
|
vi.mocked(useAuth).mockReturnValue({
|
|
user: mockUser,
|
|
profile: null,
|
|
loading: false,
|
|
isAuthenticated: true,
|
|
signIn: vi.fn(),
|
|
signUp: vi.fn(),
|
|
signOut: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
const { result } = renderHook(() => usePermissions());
|
|
|
|
expect(result.current.hasAll(['READ_CAMPUSES', 'DELETE_ORGANIZATIONS'])).toBe(false);
|
|
});
|
|
|
|
it('returns true for all permissions when user has globalAccess', () => {
|
|
vi.mocked(useAuth).mockReturnValue({
|
|
user: mockGlobalUser,
|
|
profile: null,
|
|
loading: false,
|
|
isAuthenticated: true,
|
|
signIn: vi.fn(),
|
|
signUp: vi.fn(),
|
|
signOut: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
const { result } = renderHook(() => usePermissions());
|
|
|
|
expect(result.current.hasAll(['DELETE_ORGANIZATIONS', 'UPDATE_USERS'])).toBe(true);
|
|
});
|
|
|
|
it('returns false for null user', () => {
|
|
vi.mocked(useAuth).mockReturnValue({
|
|
user: null,
|
|
profile: null,
|
|
loading: false,
|
|
isAuthenticated: false,
|
|
signIn: vi.fn(),
|
|
signUp: vi.fn(),
|
|
signOut: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
const { result } = renderHook(() => usePermissions());
|
|
|
|
expect(result.current.hasAll(['READ_CAMPUSES'])).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('memoization', () => {
|
|
it('returns same object reference when user does not change', () => {
|
|
vi.mocked(useAuth).mockReturnValue({
|
|
user: mockUser,
|
|
profile: null,
|
|
loading: false,
|
|
isAuthenticated: true,
|
|
signIn: vi.fn(),
|
|
signUp: vi.fn(),
|
|
signOut: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
const { result, rerender } = renderHook(() => usePermissions());
|
|
|
|
const firstResult = result.current;
|
|
rerender();
|
|
const secondResult = result.current;
|
|
|
|
expect(firstResult).toBe(secondResult);
|
|
});
|
|
});
|
|
});
|