This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import React from 'react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import AppRoutes from './AppRoutes';
|
||||
|
||||
const mockUseAuth = vi.fn();
|
||||
|
||||
vi.mock('./contexts/AuthContext', () => ({
|
||||
useAuth: () => mockUseAuth(),
|
||||
}));
|
||||
|
||||
vi.mock('./contexts/UserContext', () => ({
|
||||
useUser: () => ({ isInitialized: true }),
|
||||
}));
|
||||
|
||||
vi.mock('./debugConfig', () => ({
|
||||
logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
||||
}));
|
||||
|
||||
vi.mock('./pages/Layout', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
vi.mock('./pages/auth/PlatformAdminPage', () => ({ default: () => <div>Platform Admin Page</div> }));
|
||||
vi.mock('./pages/auth/adminPage', () => ({ default: () => <div>Legacy Admin</div> }));
|
||||
vi.mock('./pages/auth/loginPage', () => ({ default: () => <div>Login Page</div> }));
|
||||
vi.mock('./pages/auth/signupPage', () => ({ default: () => <div>Signup Page</div> }));
|
||||
vi.mock('./pages/user/dashboardPage', () => ({ default: () => <div>Dashboard Page</div> }));
|
||||
vi.mock('./pages/user/NotFound', () => ({ default: () => <div>Private Not Found</div> }));
|
||||
vi.mock('./pages/NotFoundPublic', () => ({ default: () => <div>Public Not Found</div> }));
|
||||
vi.mock('./pages/tldraw/TLDrawCanvas', () => ({ default: () => <div>Public Home</div> }));
|
||||
vi.mock('./pages/tldraw/singlePlayerPage', () => ({ default: () => <div>Single Player</div> }));
|
||||
vi.mock('./pages/tldraw/multiplayerUser', () => ({ default: () => <div>Multiplayer</div> }));
|
||||
vi.mock('./pages/tldraw/CCExamMarker/CCExamMarker', () => ({ CCExamMarker: () => <div>Exam Marker</div> }));
|
||||
vi.mock('./pages/user/calendarPage', () => ({ default: () => <div>Calendar</div> }));
|
||||
vi.mock('./pages/user/settingsPage', () => ({ default: () => <div>Settings</div> }));
|
||||
vi.mock('./pages/tldraw/devPlayerPage', () => ({ default: () => <div>TLDraw Dev</div> }));
|
||||
vi.mock('./pages/tldraw/devPage', () => ({ default: () => <div>Dev</div> }));
|
||||
vi.mock('./pages/react-flow/teacherPlanner', () => ({ default: () => <div>Teacher Planner</div> }));
|
||||
vi.mock('./pages/morphicPage', () => ({ default: () => <div>Morphic</div> }));
|
||||
vi.mock('./pages/tldraw/ShareHandler', () => ({ default: () => <div>Share</div> }));
|
||||
vi.mock('./pages/searxngPage', () => ({ default: () => <div>Search</div> }));
|
||||
vi.mock('./pages/dev/SimpleUploadTest', () => ({ default: () => <div>Upload Test</div> }));
|
||||
vi.mock('./pages/tldraw/CCDocumentIntelligence/CCDocumentIntelligence', () => ({
|
||||
CCDocumentIntelligence: () => <div>Doc Intelligence</div>,
|
||||
}));
|
||||
vi.mock('./pages/timetable', () => ({
|
||||
TimetablePage: () => <div>Timetable</div>,
|
||||
ClassesPage: () => <div>Classes</div>,
|
||||
LessonPage: () => <div>Lesson</div>,
|
||||
TaughtLessonsPage: () => <div>Taught Lessons</div>,
|
||||
MyClassesPage: () => <div>My Classes</div>,
|
||||
EnrollmentRequestsPage: () => <div>Enrollment Requests</div>,
|
||||
StaffManagerPage: () => <div>Staff Manager</div>,
|
||||
StudentManagerPage: () => <div>Student Manager</div>,
|
||||
SchoolSettingsPage: () => <div>School Settings</div>,
|
||||
ClassDetailPage: () => <div>Class Detail</div>,
|
||||
StudentLessonsPage: () => <div>Student Lessons</div>,
|
||||
LessonPlansPage: () => <div>Lesson Plans</div>,
|
||||
LessonPlanDetailPage: () => <div>Lesson Plan Detail</div>,
|
||||
}));
|
||||
|
||||
function renderAt(path: string) {
|
||||
return render(
|
||||
<MemoryRouter initialEntries={[path]}>
|
||||
<AppRoutes />
|
||||
</MemoryRouter>
|
||||
);
|
||||
}
|
||||
|
||||
function authState(overrides: Record<string, unknown>) {
|
||||
return {
|
||||
user: null,
|
||||
user_role: null,
|
||||
accessToken: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
signIn: vi.fn(),
|
||||
signOut: vi.fn(),
|
||||
clearError: vi.fn(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('/admin route authorization', () => {
|
||||
beforeEach(() => {
|
||||
mockUseAuth.mockReset();
|
||||
});
|
||||
|
||||
it('redirects anonymous users away from /admin', () => {
|
||||
mockUseAuth.mockReturnValue(authState({ user: null, user_role: null }));
|
||||
|
||||
renderAt('/admin');
|
||||
|
||||
expect(screen.queryByText('Platform Admin Page')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Login Page')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('redirects authenticated non-admin users away from /admin', () => {
|
||||
mockUseAuth.mockReturnValue(authState({
|
||||
user: { id: 'user-1', email: '[email protected]', user_type: 'email_teacher' },
|
||||
user_role: 'email_teacher',
|
||||
accessToken: 'token',
|
||||
}));
|
||||
|
||||
renderAt('/admin');
|
||||
|
||||
expect(screen.queryByText('Platform Admin Page')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Dashboard Page')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('allows super_admin users to access /admin', () => {
|
||||
mockUseAuth.mockReturnValue(authState({
|
||||
user: { id: 'admin-1', email: '[email protected]', user_type: 'super_admin' },
|
||||
user_role: 'super_admin',
|
||||
accessToken: 'token',
|
||||
}));
|
||||
|
||||
renderAt('/admin');
|
||||
|
||||
expect(screen.getByText('Platform Admin Page')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+10
-2
@@ -69,8 +69,10 @@ const FullContextRoutes: React.FC = () => {
|
||||
};
|
||||
|
||||
const AppRoutes: React.FC = () => {
|
||||
const { user, loading: isAuthLoading } = useAuth();
|
||||
const { user, user_role, loading: isAuthLoading } = useAuth();
|
||||
const location = useLocation();
|
||||
const platformAdminRoles = ['super_admin', 'cc_admin', 'cc_developer'];
|
||||
const isPlatformAdmin = !!user && platformAdminRoles.includes(user_role ?? '');
|
||||
|
||||
// Debug log for routing
|
||||
logger.debug('routing', '🔄 Rendering routes', {
|
||||
@@ -123,7 +125,13 @@ const AppRoutes: React.FC = () => {
|
||||
<Route
|
||||
path="/admin"
|
||||
element={
|
||||
<PlatformAdminPage />
|
||||
!user ? (
|
||||
<Navigate to="/login" replace state={{ from: location }} />
|
||||
) : isPlatformAdmin ? (
|
||||
<PlatformAdminPage />
|
||||
) : (
|
||||
<Navigate to="/dashboard" replace />
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user