Skip to content

Commit

Permalink
add tests for modals and middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenkilbourn committed Jan 3, 2025
1 parent 333d2fb commit 871dc3a
Show file tree
Hide file tree
Showing 6 changed files with 353 additions and 6 deletions.
87 changes: 87 additions & 0 deletions components/ErrorModal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react';
import { cleanup, render, screen } from '@testing-library/react';
import { describe, it, expect, vi, afterEach } from 'vitest';
import ErrorModal from './ErrorModal';

// Mock StyledModal
vi.mock('@/components/StyledModal', () => ({
__esModule: true,
default: vi.fn(({ title, children }) => (
<div data-testid="styled-modal">
<h1>{title}</h1>
<div>{children}</div>
</div>
)),
}));

describe('ErrorModal Component', () => {
afterEach(() => {
cleanup();
});
it('renders "Collection Name Exists" modal when API error contains "Reference already exists"', () => {
const props = {
collectionName: 'Test Collection',
apiErrorMessage: 'Reference already exists',
};

render(<ErrorModal {...props} />);

// Check modal content
const modal = screen.getByTestId('styled-modal');
expect(modal).toBeInTheDocument();
expect(screen.getByText('Collection Name Exists')).toBeInTheDocument();
expect(
screen.getByText(
/A branch with the collection name/i
)
).toBeInTheDocument();
expect(
screen.getByText(
/Test Collection/i
)
).toBeInTheDocument();
expect(
screen.getByText(
/already exists\./i
)
).toBeInTheDocument();
expect(
screen.getByText(/Please try another collection name or delete the feature branch\./i)
).toBeInTheDocument();
});

it('renders generic error modal when API error message does not include "Reference already exists"', () => {
const props = {
collectionName: 'Test Collection',
apiErrorMessage: 'Unexpected error',
};

render(<ErrorModal {...props} />);

// Check modal content
const modal = screen.getByTestId('styled-modal');
expect(modal).toBeInTheDocument();
expect(screen.getByText('Something Went Wrong')).toBeInTheDocument();
expect(
screen.getByText(/Something went wrong with updating Test Collection\./i)
).toBeInTheDocument();
expect(screen.getByText(/Please try again\./i)).toBeInTheDocument();
});

it('renders generic error modal when no API error message is provided', () => {
const props = {
collectionName: 'Test Collection',
};

render(<ErrorModal {...props} />);

// Check modal content
const modal = screen.getByTestId('styled-modal');
expect(modal).toBeInTheDocument();
expect(screen.getByText('Something Went Wrong')).toBeInTheDocument();
expect(
screen.getByText(/Something went wrong with updating Test Collection\./i)
).toBeInTheDocument();
expect(screen.getByText(/Please try again\./i)).toBeInTheDocument();
});
});
80 changes: 80 additions & 0 deletions components/SuccessModal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'react';
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
import { describe, it, vi, expect, afterEach } from 'vitest';
import SuccessModal from './SuccessModal';


// Mock StyledModal
vi.mock('./StyledModal', () => ({
__esModule: true,
default: vi.fn(({ children, okText, onOk }) => (
<div data-testid="styled-modal">
{children}
<button onClick={onOk}>{okText}</button>
</div>
)),
}));

describe('SuccessModal Component', () => {

afterEach(() => {
cleanup();
});

it('renders correctly for "create" type', () => {
const mockSetStatus = vi.fn();
const props = {
type: 'create' as const,
collectionName: 'Test Collection',
pullRequestUrl: 'https://github.com/test/pr',
setStatus: mockSetStatus,
};

render(<SuccessModal {...props} />);

// Verify modal content
expect(screen.getByTestId('styled-modal')).toBeInTheDocument();
expect(
screen.getByText(/collection has been submitted\./i)
).toBeInTheDocument();
expect(screen.getByRole('link', { name: /Github/i })).toHaveAttribute(
'href',
'https://github.com/test/pr'
);
});

it('renders correctly for "edit" type', () => {
const mockSetStatus = vi.fn();
const props = {
type: 'edit' as const,
collectionName: 'Edited Collection',
setStatus: mockSetStatus,
};

render(<SuccessModal {...props} />);

// Verify modal content
expect(screen.getByTestId('styled-modal')).toBeInTheDocument();
expect(
screen.getByText(/The update to/i)
).toBeInTheDocument();
});

it('calls setStatus with "idle" when OK is clicked', () => {
const mockSetStatus = vi.fn();
const props = {
type: 'edit' as const,
collectionName: 'Edited Collection',
setStatus: mockSetStatus,
};

render(<SuccessModal {...props} />);

// Simulate clicking the OK button
const okButton = screen.getByText('OK');
fireEvent.click(okButton);

// Verify setStatus is called
expect(mockSetStatus).toHaveBeenCalledWith('idle');
});
});
72 changes: 72 additions & 0 deletions middleware.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { describe, it, expect, vi } from 'vitest';
import { NextRequest } from 'next/server';
import { middleware } from './middleware';
import { runWithAmplifyServerContext } from '@/utils/amplify-server-util';
import { fetchAuthSession } from 'aws-amplify/auth/server';

// Mock dependencies
vi.mock('@/utils/amplify-server-util', () => ({
runWithAmplifyServerContext: vi.fn(),
}));

vi.mock('aws-amplify/auth/server', () => ({
fetchAuthSession: vi.fn(),
}));

// JWT Mock
const mockJWT = {
payload: { sub: 'user123', exp: Math.floor(Date.now() / 1000) + 3600 }, // Example payload
toString: () => 'mockJWT',
};

// List of protected routes
const protectedRoutes = [
'/list-ingests',
'/retrieve-ingest',
'/api/create-ingest',
'/api/edit-ingest',
];

describe('Middleware - Protected Routes', () => {
protectedRoutes.forEach((route) => {
it(`allows authenticated users to proceed for ${route}`, async () => {
// Mock authenticated behavior
vi.mocked(runWithAmplifyServerContext).mockImplementation(async ({ operation }) => {
return operation({
token: { value: Symbol('mockToken') },
});
});

vi.mocked(fetchAuthSession).mockResolvedValue({
tokens: { accessToken: mockJWT },
});

// Mock request and invoke middleware
const mockRequest = new NextRequest(`http://localhost${route}`);
const response = await middleware(mockRequest);

expect(response.status).toBe(200); // Status 200 implies `NextResponse.next()`
});

it(`rejects unauthenticated users for ${route}`, async () => {
// Mock unauthenticated behavior
vi.mocked(runWithAmplifyServerContext).mockImplementation(async ({ operation }) => {
return operation({
token: { value: Symbol('mockToken') },
});
});

vi.mocked(fetchAuthSession).mockResolvedValue({
tokens: undefined, // Simulate missing tokens for unauthenticated users
});

// Mock request and invoke middleware
const mockRequest = new NextRequest(`http://localhost${route}`);
const response = await middleware(mockRequest);
const jsonResponse = await response.json();

expect(response.status).toBe(401);
expect(jsonResponse).toEqual({ message: 'Not Authenticated' });
});
});
});
7 changes: 6 additions & 1 deletion middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,15 @@ export async function middleware(request: NextRequest) {

// user is not authenticated
return NextResponse.json({ message: 'Not Authenticated' }, { status: 401 });

}

// This config will match all routes accept /login, /api, _next/static, /_next/image
// favicon.ico
export const config = {
matcher: ['/api/create-ingest'],
matcher: [
'/api/list-ingests',
'/api/retrieve-ingest',
'/api/create-ingest',
],
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"lodash": "^4.17.21",
"next": "15.0.3",
"react": "19.0.0-rc-66855b96-20241106",
"react-dom": "19.0.0-rc-66855b96-20241106"
"react-dom": "19.0.0-rc-66855b96-20241106",
"vitest-tsconfig-paths": "^3.4.1"
},
"devDependencies": {
"@octokit/types": "^13.6.2",
Expand Down
Loading

0 comments on commit 871dc3a

Please sign in to comment.