203 lines
7.3 KiB
TypeScript
203 lines
7.3 KiB
TypeScript
import React from 'react';
|
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
|
|
import AdminSystemPage from './pages/AdminSystemPage';
|
|
import { I18nProvider } from './i18n/I18nProvider';
|
|
import { api } from './api';
|
|
|
|
jest.mock('./api', () => ({
|
|
api: {
|
|
get: jest.fn(),
|
|
post: jest.fn(),
|
|
put: jest.fn(),
|
|
patch: jest.fn(),
|
|
delete: jest.fn(),
|
|
interceptors: { request: { use: jest.fn() }, response: { use: jest.fn() } },
|
|
},
|
|
getApiErrorMessage: (error: any, fallback?: string) => fallback || 'Request failed.',
|
|
}));
|
|
|
|
const mockedApi = api as jest.Mocked<typeof api>;
|
|
|
|
describe('AdminSystemPage', () => {
|
|
beforeEach(() => {
|
|
mockedApi.get.mockImplementation((url: string) => {
|
|
if (url === '/admin/system') {
|
|
return Promise.resolve({
|
|
data: {
|
|
environment: 'Production',
|
|
contentRoot: '/app',
|
|
version: '1.2.3',
|
|
commitSha: 'abc1234',
|
|
buildStamp: '2026-03-23 11:00 UTC',
|
|
storage: { dataRoot: '/data', dbPath: '/data/jobtracker.db', dbExists: true, dbSizeBytes: 2048, companyCount: 3, jobCount: 7, deletedCount: 1 },
|
|
email: { enabled: true, host: 'smtp.example.test', port: 587, enableSsl: true, from: 'noreply@example.test', fromName: 'Jobbjakt' },
|
|
database: { provider: 'mariadb', looksConfigured: true, canConnect: true, target: 'server=db', usesFileStorage: false, warning: null },
|
|
runtime: { framework: '.NET 9', osDescription: 'Linux', processArchitecture: 'X64', machineName: 'app-01' },
|
|
auth: { required: true, hasJwtKey: true, googleConfigured: true, gmailConfigured: true },
|
|
ai: {
|
|
healthy: true,
|
|
model: 'distilbart',
|
|
device: 'cpu',
|
|
gpuAvailable: false,
|
|
gpuName: null,
|
|
ocrAvailable: true,
|
|
ocrLanguages: 'eng',
|
|
ollamaConfigured: true,
|
|
ollamaReachable: true,
|
|
ollamaModel: 'qwen2.5:7b',
|
|
ollamaModelAvailable: true,
|
|
ollamaVersion: '0.7.0',
|
|
ollamaInstalledModels: ['qwen2.5:7b', 'nomic-embed-text'],
|
|
ollamaLoadedModels: ['qwen2.5:7b'],
|
|
ollamaLoadedCount: 1,
|
|
healthLatencyMs: 12.4,
|
|
probeLatencyMs: 25.8,
|
|
lastProbeAt: '2026-03-23T10:00:00Z',
|
|
lastProbeSuccessAt: '2026-03-23T10:00:00Z',
|
|
lastProbeFailureAt: null,
|
|
probeFailures: 0,
|
|
requests: 18,
|
|
cacheHits: 9,
|
|
cacheMisses: 9,
|
|
failures: 0,
|
|
averageLatencyMs: 42.2,
|
|
ocrRequests: 5,
|
|
ocrFailures: 0,
|
|
averageOcrLatencyMs: 88.4,
|
|
lastOcrSuccessAt: '2026-03-23T10:05:00Z',
|
|
lastOcrFailureAt: null,
|
|
lastSuccessAt: '2026-03-23T10:04:00Z',
|
|
lastFailureAt: null,
|
|
lastError: null,
|
|
},
|
|
},
|
|
} as any);
|
|
}
|
|
if (url === '/admin/system/email-settings') {
|
|
return Promise.resolve({
|
|
data: {
|
|
enabled: true,
|
|
host: 'smtp.example.test',
|
|
port: 587,
|
|
user: 'mailer@example.test',
|
|
from: 'noreply@example.test',
|
|
fromName: 'Jobbjakt',
|
|
enableSsl: true,
|
|
timeoutMs: 15000,
|
|
usesOverrides: false,
|
|
hasPassword: true,
|
|
},
|
|
} as any);
|
|
}
|
|
if (url === '/admin/system/cv-benchmark') {
|
|
return Promise.resolve({
|
|
data: {
|
|
rootPath: '/data/CvBenchmarks/latest',
|
|
lastUpdatedAtUtc: '2026-03-23T10:10:00Z',
|
|
reportMarkdown: '# CV benchmark report\n\n- Files: 4',
|
|
indexJson: JSON.stringify({
|
|
CorpusRoot: '/home/pi/cvs',
|
|
OutputRoot: '/data/CvBenchmarks/latest',
|
|
GeneratedAtUtc: '2026-03-23T10:10:00Z',
|
|
TotalFiles: 4,
|
|
AverageCoverage: 0.72,
|
|
AverageConfidence: 0.66,
|
|
AverageConsistency: 0.94,
|
|
FilesWithSuspiciousLocations: 1,
|
|
MissingApprovedFixtures: 4,
|
|
Entries: [
|
|
{
|
|
FileName: 'cv.txt',
|
|
Slug: 'cv-txt',
|
|
Extension: '.txt',
|
|
Characters: 2000,
|
|
OutputPath: '/data/CvBenchmarks/latest/outputs/cv-txt.json',
|
|
ApprovedFixturePath: null,
|
|
CandidateFixturePath: '/data/CvBenchmarks/latest/candidate-fixtures/cv-txt.json',
|
|
ContactLocation: 'San Francisco, Hobbies',
|
|
FirstJob: '* July',
|
|
FirstJobLocation: null,
|
|
FirstEducation: '* September',
|
|
FirstEducationLocation: null,
|
|
QualificationLevels: ['Other'],
|
|
SuspiciousLocations: [],
|
|
CoverageScore: 0.5,
|
|
ConfidenceScore: 0.65,
|
|
ConsistencyScore: 0.8,
|
|
DiffSummary: 'No approved fixture yet — candidate fixture written.',
|
|
},
|
|
],
|
|
}),
|
|
},
|
|
} as any);
|
|
}
|
|
return Promise.resolve({ data: {} } as any);
|
|
});
|
|
mockedApi.put.mockResolvedValue({
|
|
data: {
|
|
enabled: true,
|
|
host: 'smtp.changed.test',
|
|
port: 2525,
|
|
user: 'mailer@example.test',
|
|
from: 'noreply@example.test',
|
|
fromName: 'Jobbjakt',
|
|
enableSsl: true,
|
|
timeoutMs: 15000,
|
|
usesOverrides: true,
|
|
hasPassword: true,
|
|
},
|
|
} as any);
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
it('renders AI service health, latency, and OCR readiness', async () => {
|
|
render(
|
|
<I18nProvider>
|
|
<AdminSystemPage />
|
|
</I18nProvider>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Production')).toBeTruthy();
|
|
});
|
|
|
|
expect(screen.getByText(/25.8 ms probe/i)).toBeTruthy();
|
|
expect(screen.getByText('OCR eng')).toBeTruthy();
|
|
expect(screen.getAllByText(/ollama configured/i).length).toBeGreaterThan(0);
|
|
expect(screen.getByText(/ollama version/i)).toBeTruthy();
|
|
expect(screen.getByText(/model · qwen2.5:7b/i)).toBeTruthy();
|
|
expect(screen.getByText(/cv benchmark review/i)).toBeTruthy();
|
|
expect(screen.getByText(/top parser findings/i)).toBeTruthy();
|
|
expect(screen.getByText(/suspicious contact location: san francisco, hobbies/i)).toBeTruthy();
|
|
expect(screen.getByText('OCR avg latency')).toBeTruthy();
|
|
expect(screen.getByText('88.4 ms')).toBeTruthy();
|
|
});
|
|
|
|
it('loads and saves smtp settings from the settings tab', async () => {
|
|
render(
|
|
<I18nProvider>
|
|
<AdminSystemPage />
|
|
</I18nProvider>,
|
|
);
|
|
|
|
expect(await screen.findByText('AI service')).toBeTruthy();
|
|
fireEvent.click(screen.getByRole('tab', { name: /settings/i }));
|
|
|
|
const hostInput = await screen.findByLabelText(/host/i);
|
|
fireEvent.change(hostInput, { target: { value: 'smtp.changed.test' } });
|
|
fireEvent.change(screen.getByLabelText(/port/i), { target: { value: '2525' } });
|
|
fireEvent.click(screen.getByRole('button', { name: /save settings/i }));
|
|
|
|
await waitFor(() => {
|
|
expect(mockedApi.put).toHaveBeenCalledWith('/admin/system/email-settings', expect.objectContaining({
|
|
host: 'smtp.changed.test',
|
|
port: 2525,
|
|
}));
|
|
});
|
|
});
|
|
});
|