All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 19m58s
553 lines
22 KiB
TypeScript
553 lines
22 KiB
TypeScript
// src/pages/admin/components/SystemCheck.test.tsx
|
|
import React from 'react';
|
|
import { screen, waitFor, cleanup, fireEvent, act } from '@testing-library/react';
|
|
import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from 'vitest';
|
|
import { SystemCheck } from './SystemCheck';
|
|
import * as apiClient from '../../../services/apiClient';
|
|
import toast from 'react-hot-toast';
|
|
import { createMockUser } from '../../../tests/utils/mockFactories';
|
|
import { renderWithProviders } from '../../../tests/utils/renderWithProviders';
|
|
|
|
// The apiClient is mocked globally in `src/tests/setup/globalApiMock.ts`.
|
|
// We can get a type-safe mocked version of the module to override functions for specific tests.
|
|
const mockedApiClient = vi.mocked(apiClient);
|
|
|
|
// The logger and react-hot-toast are mocked globally.
|
|
|
|
describe('SystemCheck', () => {
|
|
// Store original env variable
|
|
const originalGeminiApiKey = import.meta.env.GEMINI_API_KEY;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
// Use `mockImplementation` to create a new Response object for every call.
|
|
// This prevents "Body has already been read" errors and memory leaks.
|
|
mockedApiClient.pingBackend.mockImplementation(() => Promise.resolve(new Response('pong')));
|
|
mockedApiClient.checkStorage.mockImplementation(() =>
|
|
Promise.resolve(new Response(JSON.stringify({ success: true, message: 'Storage OK' }))),
|
|
);
|
|
mockedApiClient.checkDbPoolHealth.mockImplementation(() =>
|
|
Promise.resolve(new Response(JSON.stringify({ success: true, message: 'DB Pool OK' }))),
|
|
);
|
|
mockedApiClient.checkPm2Status.mockImplementation(() =>
|
|
Promise.resolve(new Response(JSON.stringify({ success: true, message: 'PM2 OK' }))),
|
|
);
|
|
mockedApiClient.checkRedisHealth.mockImplementation(() =>
|
|
Promise.resolve(new Response(JSON.stringify({ success: true, message: 'Redis OK' }))),
|
|
);
|
|
mockedApiClient.checkDbSchema.mockImplementation(() =>
|
|
Promise.resolve(new Response(JSON.stringify({ success: true, message: 'Schema OK' }))),
|
|
);
|
|
mockedApiClient.loginUser.mockImplementation(() =>
|
|
Promise.resolve(
|
|
new Response(JSON.stringify({ user: createMockUser(), token: '' }), { status: 200 }),
|
|
),
|
|
);
|
|
mockedApiClient.triggerFailingJob.mockImplementation(() =>
|
|
Promise.resolve(new Response(JSON.stringify({ message: 'Job triggered!' }))),
|
|
);
|
|
mockedApiClient.clearGeocodeCache.mockImplementation(() =>
|
|
Promise.resolve(new Response(JSON.stringify({ message: 'Cache cleared!' }))),
|
|
);
|
|
|
|
// Reset GEMINI_API_KEY for each test to its original value.
|
|
setGeminiApiKey(originalGeminiApiKey);
|
|
});
|
|
|
|
// Restore all mocks after each test to ensure test isolation.
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
cleanup();
|
|
});
|
|
|
|
// Helper to set GEMINI_API_KEY for specific tests.
|
|
const setGeminiApiKey = (value: string | undefined) => {
|
|
if (value === undefined) {
|
|
delete (import.meta.env as Record<string, string | undefined>).GEMINI_API_KEY;
|
|
} else {
|
|
(import.meta.env as Record<string, string | undefined>).GEMINI_API_KEY = value;
|
|
}
|
|
};
|
|
|
|
it('should render initial idle state and then run checks automatically on mount', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
// Initially, all checks should be in 'running' state due to auto-run
|
|
// However, the API key check is synchronous and resolves immediately.
|
|
// All 8 checks now run asynchronously.
|
|
expect(screen.getAllByText('Checking...')).toHaveLength(8);
|
|
|
|
// Wait for all checks to complete
|
|
await waitFor(() => {
|
|
expect(screen.getByText('GEMINI_API_KEY is set.')).toBeInTheDocument();
|
|
expect(screen.getByText('Backend server is running and reachable.')).toBeInTheDocument();
|
|
expect(screen.getByText('Redis OK')).toBeInTheDocument();
|
|
expect(screen.getByText('PM2 OK')).toBeInTheDocument();
|
|
expect(screen.getByText('DB Pool OK')).toBeInTheDocument();
|
|
expect(screen.getByText('Schema OK')).toBeInTheDocument();
|
|
expect(screen.getByText('Default admin user login was successful.')).toBeInTheDocument();
|
|
expect(screen.getByText('Storage OK')).toBeInTheDocument();
|
|
});
|
|
|
|
// Check that the re-run button is enabled and elapsed time is shown
|
|
expect(screen.getByRole('button', { name: /re-run checks/i })).toBeEnabled();
|
|
expect(screen.getByText(/finished in \d+\.\d{2} seconds\./i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should show API key as failed if GEMINI_API_KEY is not set', async () => {
|
|
setGeminiApiKey(undefined);
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
// Wait for the specific error message to appear.
|
|
expect(
|
|
await screen.findByText('GEMINI_API_KEY is missing. AI features will not work.'),
|
|
).toBeInTheDocument();
|
|
// Crucially, other checks should still pass.
|
|
expect(await screen.findByText('Backend server is running and reachable.')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should show backend connection as failed if pingBackend fails', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
(mockedApiClient.pingBackend as Mock).mockRejectedValueOnce(new Error('Network error'));
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Network error')).toBeInTheDocument();
|
|
// Multiple checks will be skipped, so we use getAllByText.
|
|
const skippedMessages = screen.getAllByText('Skipped: Backend server is not reachable.'); // Redis is now also skipped
|
|
expect(skippedMessages.length).toBe(6); // PM2, DB Pool, Redis, Schema, Seed, Storage
|
|
});
|
|
|
|
// Dependent checks should be skipped/failed
|
|
expect(mockedApiClient.checkDbPoolHealth).not.toHaveBeenCalled();
|
|
expect(mockedApiClient.checkDbSchema).not.toHaveBeenCalled();
|
|
expect(mockedApiClient.loginUser).not.toHaveBeenCalled();
|
|
expect(mockedApiClient.checkStorage).not.toHaveBeenCalled();
|
|
expect(mockedApiClient.checkPm2Status).not.toHaveBeenCalled();
|
|
expect(mockedApiClient.checkRedisHealth).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should show PM2 status as failed if checkPm2Status returns success: false', async () => {
|
|
setGeminiApiKey('mock-api-key'); // This was missing
|
|
mockedApiClient.checkPm2Status.mockImplementationOnce(() =>
|
|
Promise.resolve(
|
|
new Response(JSON.stringify({ success: false, message: 'PM2 process not found' })),
|
|
),
|
|
);
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('PM2 process not found')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should show database pool check as failed if checkDbPoolHealth fails', async () => {
|
|
setGeminiApiKey('mock-api-key'); // This was missing
|
|
mockedApiClient.checkDbPoolHealth.mockRejectedValueOnce(new Error('DB connection refused'));
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('DB connection refused')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should show Redis check as failed if checkRedisHealth fails', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
mockedApiClient.checkRedisHealth.mockRejectedValueOnce(new Error('Redis connection refused'));
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Redis connection refused')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should skip schema and seed checks if DB pool check fails', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
// Mock the DB pool check to fail
|
|
mockedApiClient.checkDbPoolHealth.mockImplementationOnce(() =>
|
|
Promise.reject(new Error('DB connection refused')),
|
|
);
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
// Verify the specific "skipped" messages for DB-dependent checks
|
|
expect(screen.getAllByText('Skipped: Database connection pool is unhealthy.')).toHaveLength(
|
|
2,
|
|
);
|
|
// Verify other parallel checks still run and pass
|
|
expect(screen.getByText('Storage OK')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should show database schema check as failed if checkDbSchema fails', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
mockedApiClient.checkDbSchema.mockImplementationOnce(() =>
|
|
Promise.resolve(new Response(JSON.stringify({ success: false, message: 'Schema mismatch' }))),
|
|
);
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Schema mismatch')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should show seeded user check as failed if loginUser fails', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
mockedApiClient.loginUser.mockRejectedValueOnce(new Error('Incorrect email or password'));
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByText('Login failed. Ensure the default admin user is seeded in your database.'),
|
|
).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should show a generic failure message for other login errors', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
mockedApiClient.loginUser.mockRejectedValueOnce(new Error('Server is on fire'));
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Failed: Server is on fire')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should show storage directory check as failed if checkStorage fails', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
mockedApiClient.checkStorage.mockRejectedValueOnce(new Error('Storage not writable'));
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Storage not writable')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should display a loading spinner and disable button while checks are running', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
// Create a promise we can resolve manually to control the loading state
|
|
let resolvePromise: (value: Response) => void;
|
|
const mockPromise = new Promise<Response>((resolve) => {
|
|
resolvePromise = resolve;
|
|
});
|
|
mockedApiClient.pingBackend.mockImplementation(() => mockPromise);
|
|
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
// The button text changes to "Running Checks..."
|
|
const runningButton = screen.getByRole('button', { name: /running checks/i });
|
|
expect(runningButton).toBeDisabled();
|
|
expect(runningButton.querySelector('svg')).toBeInTheDocument(); // Check for spinner
|
|
|
|
// Now resolve the promise to allow the test to clean up properly
|
|
await act(async () => {
|
|
resolvePromise(new Response('pong'));
|
|
await mockPromise;
|
|
});
|
|
|
|
// Wait for the button to become enabled again
|
|
await waitFor(() => {
|
|
expect(screen.getByRole('button', { name: /re-run checks/i })).toBeEnabled();
|
|
});
|
|
});
|
|
|
|
it('should re-run checks when the "Re-run Checks" button is clicked', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
// Wait for initial auto-run to complete
|
|
await waitFor(() => expect(screen.getByText(/finished in/i)).toBeInTheDocument());
|
|
|
|
// Reset mocks for the re-run with new messages to ensure we are testing the re-run
|
|
mockedApiClient.checkDbSchema.mockImplementationOnce(() =>
|
|
Promise.resolve(
|
|
new Response(JSON.stringify({ success: true, message: 'Schema OK (re-run)' })),
|
|
),
|
|
);
|
|
mockedApiClient.checkStorage.mockImplementationOnce(() =>
|
|
Promise.resolve(
|
|
new Response(JSON.stringify({ success: true, message: 'Storage OK (re-run)' })),
|
|
),
|
|
);
|
|
mockedApiClient.checkDbPoolHealth.mockImplementationOnce(() =>
|
|
Promise.resolve(
|
|
new Response(JSON.stringify({ success: true, message: 'DB Pool OK (re-run)' })),
|
|
),
|
|
);
|
|
|
|
const rerunButton = screen.getByRole('button', { name: /re-run checks/i });
|
|
fireEvent.click(rerunButton);
|
|
|
|
// Expect checks to go back to 'Checking...' state
|
|
await waitFor(() => {
|
|
expect(screen.getAllByText('Checking...')).toHaveLength(8);
|
|
});
|
|
|
|
// Wait for re-run to complete and check for the new messages
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Schema OK (re-run)')).toBeInTheDocument();
|
|
expect(screen.getByText('Storage OK (re-run)')).toBeInTheDocument();
|
|
expect(screen.getByText('DB Pool OK (re-run)')).toBeInTheDocument();
|
|
expect(screen.getByText('PM2 OK')).toBeInTheDocument(); // This one doesn't get a new message in this mock setup
|
|
});
|
|
expect(mockedApiClient.pingBackend).toHaveBeenCalledTimes(2); // Initial run + re-run
|
|
});
|
|
|
|
it('should display correct icons for each status (pass and fail)', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
mockedApiClient.checkDbSchema.mockImplementationOnce(() =>
|
|
Promise.resolve(new Response(JSON.stringify({ success: false, message: 'Schema mismatch' }))),
|
|
);
|
|
const { container } = renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
// Instead of test-ids, we check for the result: the icon's color class.
|
|
// This is more robust as it doesn't depend on the icon component's internal props.
|
|
const passIcons = container.querySelectorAll('.text-green-500');
|
|
expect(passIcons.length).toBeGreaterThan(0);
|
|
|
|
// Check for the fail icon's color class
|
|
const failIcon = container.querySelector('svg.text-red-500');
|
|
expect(failIcon).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should display elapsed time after checks complete', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
const elapsedTimeText = screen.getByText(/finished in \d+\.\d{2} seconds\./i);
|
|
expect(elapsedTimeText).toBeInTheDocument();
|
|
const match = elapsedTimeText.textContent?.match(/(\d+\.\d{2})/);
|
|
expect(match).not.toBeNull();
|
|
expect(parseFloat(match![1])).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('Integration: Job Queue Retries', () => {
|
|
it('should call triggerFailingJob and show a success toast', async () => {
|
|
renderWithProviders(<SystemCheck />);
|
|
const triggerButton = screen.getByRole('button', { name: /trigger failing job/i });
|
|
fireEvent.click(triggerButton);
|
|
|
|
await waitFor(() => {
|
|
expect(mockedApiClient.triggerFailingJob).toHaveBeenCalled();
|
|
expect(vi.mocked(toast).success).toHaveBeenCalledWith('Job triggered!');
|
|
});
|
|
});
|
|
|
|
it('should show a loading state while triggering the job', async () => {
|
|
let resolvePromise: (value: Response) => void;
|
|
const mockPromise = new Promise<Response>((resolve) => {
|
|
resolvePromise = resolve;
|
|
});
|
|
mockedApiClient.triggerFailingJob.mockImplementation(() => mockPromise);
|
|
|
|
renderWithProviders(<SystemCheck />);
|
|
const triggerButton = screen.getByRole('button', { name: /trigger failing job/i });
|
|
fireEvent.click(triggerButton);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByRole('button', { name: /triggering/i })).toBeDisabled();
|
|
});
|
|
|
|
await act(async () => {
|
|
resolvePromise(new Response(JSON.stringify({ message: 'Job triggered!' })));
|
|
await mockPromise;
|
|
});
|
|
});
|
|
|
|
it('should show an error toast if triggering the job fails', async () => {
|
|
mockedApiClient.triggerFailingJob.mockRejectedValueOnce(new Error('Queue is down'));
|
|
renderWithProviders(<SystemCheck />);
|
|
const triggerButton = screen.getByRole('button', { name: /trigger failing job/i });
|
|
fireEvent.click(triggerButton);
|
|
|
|
await waitFor(() => {
|
|
expect(vi.mocked(toast).error).toHaveBeenCalledWith('Queue is down');
|
|
});
|
|
});
|
|
|
|
it('should show an error toast if the API returns a non-OK response', async () => {
|
|
mockedApiClient.triggerFailingJob.mockResolvedValueOnce(
|
|
new Response(JSON.stringify({ message: 'Server error' }), { status: 500 }),
|
|
);
|
|
renderWithProviders(<SystemCheck />);
|
|
const triggerButton = screen.getByRole('button', { name: /trigger failing job/i });
|
|
fireEvent.click(triggerButton);
|
|
|
|
await waitFor(() => {
|
|
expect(vi.mocked(toast).error).toHaveBeenCalledWith('Server error');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('GeocodeCacheManager', () => {
|
|
beforeEach(() => {
|
|
// Mock window.confirm to always return true for these tests
|
|
vi.spyOn(window, 'confirm').mockReturnValue(true);
|
|
});
|
|
|
|
it('should call clearGeocodeCache and show a success toast', async () => {
|
|
renderWithProviders(<SystemCheck />);
|
|
// Wait for checks to run and Redis to be OK
|
|
await waitFor(() => expect(screen.getByText('Redis OK')).toBeInTheDocument());
|
|
|
|
const clearButton = screen.getByRole('button', { name: /clear geocode cache/i });
|
|
fireEvent.click(clearButton);
|
|
|
|
await waitFor(() => {
|
|
expect(mockedApiClient.clearGeocodeCache).toHaveBeenCalled();
|
|
expect(vi.mocked(toast).success).toHaveBeenCalledWith('Cache cleared!');
|
|
});
|
|
});
|
|
|
|
it('should show an error toast if clearing the cache fails', async () => {
|
|
mockedApiClient.clearGeocodeCache.mockRejectedValueOnce(new Error('Redis is busy'));
|
|
renderWithProviders(<SystemCheck />);
|
|
await waitFor(() => expect(screen.getByText('Redis OK')).toBeInTheDocument());
|
|
fireEvent.click(screen.getByRole('button', { name: /clear geocode cache/i }));
|
|
await waitFor(() => expect(vi.mocked(toast).error).toHaveBeenCalledWith('Redis is busy'));
|
|
});
|
|
|
|
it('should not call clearGeocodeCache if user cancels confirmation', async () => {
|
|
vi.spyOn(window, 'confirm').mockReturnValue(false);
|
|
renderWithProviders(<SystemCheck />);
|
|
await waitFor(() => expect(screen.getByText('Redis OK')).toBeInTheDocument());
|
|
|
|
const clearButton = screen.getByRole('button', { name: /clear geocode cache/i });
|
|
fireEvent.click(clearButton);
|
|
|
|
expect(mockedApiClient.clearGeocodeCache).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should show an error toast if the API returns a non-OK response', async () => {
|
|
mockedApiClient.clearGeocodeCache.mockResolvedValueOnce(
|
|
new Response(JSON.stringify({ message: 'Cache clear failed' }), { status: 500 }),
|
|
);
|
|
renderWithProviders(<SystemCheck />);
|
|
await waitFor(() => expect(screen.getByText('Redis OK')).toBeInTheDocument());
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: /clear geocode cache/i }));
|
|
|
|
await waitFor(() => {
|
|
expect(vi.mocked(toast).error).toHaveBeenCalledWith('Cache clear failed');
|
|
});
|
|
});
|
|
|
|
it('should hide Redis controls if Redis check fails', async () => {
|
|
mockedApiClient.checkRedisHealth.mockResolvedValueOnce(
|
|
new Response(JSON.stringify({ success: false, message: 'Redis down' })),
|
|
);
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => expect(screen.getByText('Redis down')).toBeInTheDocument());
|
|
|
|
expect(screen.queryByTitle('Redis cache is connected')).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByRole('button', { name: /clear geocode cache/i }),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Additional Edge Cases', () => {
|
|
it('should fail backend check if response text is not "pong"', async () => {
|
|
mockedApiClient.pingBackend.mockResolvedValueOnce(
|
|
new Response('unexpected response', { status: 200 }),
|
|
);
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByText('Backend server is not responding. Is it running?'),
|
|
).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should handle non-OK response for generic checks (e.g. Storage)', async () => {
|
|
mockedApiClient.checkStorage.mockResolvedValueOnce(
|
|
new Response(JSON.stringify({ message: 'Permission denied' }), { status: 403 }),
|
|
);
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Permission denied')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should handle non-OK response from checkDbSchema', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
mockedApiClient.checkDbSchema.mockResolvedValueOnce(
|
|
new Response(JSON.stringify({ message: 'Schema check failed 500' }), { status: 500 }),
|
|
);
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Schema check failed 500')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should handle non-OK response from checkDbPoolHealth', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
mockedApiClient.checkDbPoolHealth.mockResolvedValueOnce(
|
|
new Response(JSON.stringify({ message: 'DB Pool check failed 500' }), { status: 500 }),
|
|
);
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('DB Pool check failed 500')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should handle non-OK response from checkPm2Status', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
mockedApiClient.checkPm2Status.mockResolvedValueOnce(
|
|
new Response(JSON.stringify({ message: 'PM2 check failed 500' }), { status: 500 }),
|
|
);
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('PM2 check failed 500')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should handle non-OK response from checkRedisHealth', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
mockedApiClient.checkRedisHealth.mockResolvedValueOnce(
|
|
new Response(JSON.stringify({ message: 'Redis check failed 500' }), { status: 500 }),
|
|
);
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Redis check failed 500')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should handle Redis check returning success: false', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
mockedApiClient.checkRedisHealth.mockResolvedValueOnce(
|
|
new Response(JSON.stringify({ success: false, message: 'Redis is down' })),
|
|
);
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Redis is down')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should handle non-OK response from loginUser', async () => {
|
|
setGeminiApiKey('mock-api-key');
|
|
mockedApiClient.loginUser.mockResolvedValueOnce(
|
|
new Response(JSON.stringify({ message: 'Invalid credentials' }), { status: 401 }),
|
|
);
|
|
renderWithProviders(<SystemCheck />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Failed: Invalid credentials')).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|
|
});
|