test: expand api.ts test coverage with 56 new tests for all API objects
Add comprehensive tests for all 10 exported API objects: authApi, userApi, mailAccountsApi, processingRunsApi, gmailApi, smtpApi, adminApi, notificationsApi, adminNotificationsApi, and versionApi. Each method is tested for correct HTTP method, URL, parameters/data, and return value. Total api.test.ts tests: 65 (up from 9). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- Expanded `api.ts` test coverage from 20% to near-complete: 56 new tests covering all 10 API objects (`authApi`, `userApi`, `mailAccountsApi`, `processingRunsApi`, `gmailApi`, `smtpApi`, `adminApi`, `notificationsApi`, `adminNotificationsApi`, `versionApi`) — 65 total tests in `api.test.ts`
|
||||
- Unit tests for `AddMailAccountModal` component (`AddMailAccountModal.test.tsx`): 28 tests covering create/edit mode rendering, provider wizard flow, form field changes, checkbox toggles, auto-detect, test connection, submit mutations, error extraction, and modal close interactions
|
||||
- Unit tests for provider endpoints (`test_providers.py`): 23 tests covering provider presets, Gmail credential CRUD, import labels, authorize URL, debug email, and OAuth callback
|
||||
- Unit tests for mail account endpoints (`test_mail_accounts.py`): 30 tests covering CRUD, toggle, pull-now, test connection, auto-detect, processing runs, and processing logs
|
||||
|
||||
@@ -121,6 +121,7 @@ Comprehensive task breakdown for repository improvements and production readines
|
||||
- [x] Write unit tests for admin endpoints (87 tests, 100% coverage on admin.py)
|
||||
- [x] **Frontend test coverage**: Added 113 new tests across 7 new test suites covering all components and utility functions. Installed `@testing-library/react`, `@testing-library/jest-dom`, `@testing-library/user-event`. New suites: `date-utils` (30 tests), API interceptors (9 tests), `AuthGuard` (6 tests), `QueryProvider` (2 tests), `DashboardLayout` (14 tests), `NotificationWizard` (32 tests), `ProviderWizard` (20 tests). Total frontend: 119 tests across 8 suites.
|
||||
- [x] **AddMailAccountModal test coverage**: 28 tests covering create/edit mode rendering, provider wizard flow, form fields, auto-detect, test connection, submit mutations, error extraction, and modal interactions. Total frontend: 147 tests across 9 suites.
|
||||
- [x] **Expanded `api.ts` test coverage**: Added 56 new tests covering all 10 API objects (`authApi`, `userApi`, `mailAccountsApi`, `processingRunsApi`, `gmailApi`, `smtpApi`, `adminApi`, `notificationsApi`, `adminNotificationsApi`, `versionApi`). Every exported method now has at least one test verifying correct HTTP method, URL, parameters, and return value. Total: 65 tests in `api.test.ts`.
|
||||
|
||||
### In Progress 🔨
|
||||
- [x] Write unit tests for authentication (22 tests covering register, login, Google OAuth, authorize-url, and helper functions)
|
||||
|
||||
@@ -126,3 +126,736 @@ describe('Response interceptor', () => {
|
||||
await expect(responseErrorInterceptor!(error)).rejects.toBe(error);
|
||||
});
|
||||
});
|
||||
|
||||
// ── API Object Tests ────────────────────────────────────────────────────
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const {
|
||||
authApi,
|
||||
userApi,
|
||||
mailAccountsApi,
|
||||
processingRunsApi,
|
||||
gmailApi,
|
||||
smtpApi,
|
||||
adminApi,
|
||||
notificationsApi,
|
||||
adminNotificationsApi,
|
||||
versionApi,
|
||||
} = require('./api');
|
||||
|
||||
// ── authApi ─────────────────────────────────────────────────────────────
|
||||
|
||||
describe('authApi', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('login should post form data to /auth/login', async () => {
|
||||
const tokenData = { access_token: 'tok', refresh_token: 'ref', token_type: 'bearer' };
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: tokenData });
|
||||
|
||||
const result = await authApi.login({ username: 'u@test.com', password: 'pw' });
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith(
|
||||
'/auth/login',
|
||||
expect.any(URLSearchParams),
|
||||
expect.objectContaining({
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
})
|
||||
);
|
||||
const formData: URLSearchParams = mockAxiosInstance.post.mock.calls[0][1];
|
||||
expect(formData.get('username')).toBe('u@test.com');
|
||||
expect(formData.get('password')).toBe('pw');
|
||||
expect(result).toEqual(tokenData);
|
||||
});
|
||||
|
||||
it('register should post user data to /auth/register', async () => {
|
||||
const user = { id: 1, email: 'u@test.com', full_name: 'Test' };
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: user });
|
||||
|
||||
const result = await authApi.register({ email: 'u@test.com', password: 'pw', full_name: 'Test' });
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/auth/register', {
|
||||
email: 'u@test.com',
|
||||
password: 'pw',
|
||||
full_name: 'Test',
|
||||
});
|
||||
expect(result).toEqual(user);
|
||||
});
|
||||
|
||||
it('googleAuth should post code and redirect_uri to /auth/google', async () => {
|
||||
const tokenData = { access_token: 'tok', refresh_token: 'ref', token_type: 'bearer' };
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: tokenData });
|
||||
|
||||
const result = await authApi.googleAuth('code123', 'http://redirect');
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/auth/google', {
|
||||
code: 'code123',
|
||||
redirect_uri: 'http://redirect',
|
||||
});
|
||||
expect(result).toEqual(tokenData);
|
||||
});
|
||||
|
||||
it('getGoogleAuthUrl should get authorization URL with redirect_uri param', async () => {
|
||||
mockAxiosInstance.get.mockResolvedValue({
|
||||
data: { authorization_url: 'https://google.com/auth' },
|
||||
});
|
||||
|
||||
const result = await authApi.getGoogleAuthUrl('http://redirect');
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/auth/google/authorize-url', {
|
||||
params: { redirect_uri: 'http://redirect' },
|
||||
});
|
||||
expect(result).toBe('https://google.com/auth');
|
||||
});
|
||||
});
|
||||
|
||||
// ── userApi ─────────────────────────────────────────────────────────────
|
||||
|
||||
describe('userApi', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('getCurrentUser should get /users/me', async () => {
|
||||
const user = { id: 1, email: 'u@test.com' };
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: user });
|
||||
|
||||
const result = await userApi.getCurrentUser();
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/users/me');
|
||||
expect(result).toEqual(user);
|
||||
});
|
||||
|
||||
it('updateProfile should put data to /users/me', async () => {
|
||||
const user = { id: 1, email: 'new@test.com', full_name: 'New' };
|
||||
mockAxiosInstance.put.mockResolvedValue({ data: user });
|
||||
|
||||
const result = await userApi.updateProfile({ full_name: 'New', email: 'new@test.com' });
|
||||
|
||||
expect(mockAxiosInstance.put).toHaveBeenCalledWith('/users/me', {
|
||||
full_name: 'New',
|
||||
email: 'new@test.com',
|
||||
});
|
||||
expect(result).toEqual(user);
|
||||
});
|
||||
});
|
||||
|
||||
// ── mailAccountsApi ─────────────────────────────────────────────────────
|
||||
|
||||
describe('mailAccountsApi', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('list should get /mail-accounts', async () => {
|
||||
const accounts = [{ id: 1, name: 'acc1' }];
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: accounts });
|
||||
|
||||
const result = await mailAccountsApi.list();
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/mail-accounts');
|
||||
expect(result).toEqual(accounts);
|
||||
});
|
||||
|
||||
it('create should post data to /mail-accounts', async () => {
|
||||
const account = { id: 1, name: 'new' };
|
||||
const createData = {
|
||||
name: 'new',
|
||||
email_address: 'a@b.com',
|
||||
protocol: 'imap',
|
||||
host: 'imap.b.com',
|
||||
port: 993,
|
||||
use_ssl: true,
|
||||
username: 'a@b.com',
|
||||
password: 'secret',
|
||||
forward_to: 'c@d.com',
|
||||
};
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: account });
|
||||
|
||||
const result = await mailAccountsApi.create(createData);
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/mail-accounts', createData);
|
||||
expect(result).toEqual(account);
|
||||
});
|
||||
|
||||
it('update should put data to /mail-accounts/:id', async () => {
|
||||
const account = { id: 5, name: 'updated' };
|
||||
mockAxiosInstance.put.mockResolvedValue({ data: account });
|
||||
|
||||
const result = await mailAccountsApi.update(5, { name: 'updated' });
|
||||
|
||||
expect(mockAxiosInstance.put).toHaveBeenCalledWith('/mail-accounts/5', { name: 'updated' });
|
||||
expect(result).toEqual(account);
|
||||
});
|
||||
|
||||
it('toggle should patch /mail-accounts/:id/toggle', async () => {
|
||||
const account = { id: 3, is_enabled: false };
|
||||
mockAxiosInstance.patch.mockResolvedValue({ data: account });
|
||||
|
||||
const result = await mailAccountsApi.toggle(3);
|
||||
|
||||
expect(mockAxiosInstance.patch).toHaveBeenCalledWith('/mail-accounts/3/toggle');
|
||||
expect(result).toEqual(account);
|
||||
});
|
||||
|
||||
it('pullNow should post to /mail-accounts/:id/pull-now', async () => {
|
||||
const msg = { message: 'Pull initiated' };
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: msg });
|
||||
|
||||
const result = await mailAccountsApi.pullNow(7);
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/mail-accounts/7/pull-now');
|
||||
expect(result).toEqual(msg);
|
||||
});
|
||||
|
||||
it('delete should delete /mail-accounts/:id', async () => {
|
||||
mockAxiosInstance.delete.mockResolvedValue({});
|
||||
|
||||
await mailAccountsApi.delete(4);
|
||||
|
||||
expect(mockAxiosInstance.delete).toHaveBeenCalledWith('/mail-accounts/4');
|
||||
});
|
||||
|
||||
it('test should post config to /mail-accounts/test', async () => {
|
||||
const response = { success: true, message: 'OK' };
|
||||
const config = {
|
||||
host: 'imap.test.com',
|
||||
port: 993,
|
||||
protocol: 'imap',
|
||||
username: 'user',
|
||||
password: 'pass',
|
||||
use_ssl: true,
|
||||
};
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: response });
|
||||
|
||||
const result = await mailAccountsApi.test(config);
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/mail-accounts/test', config);
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
|
||||
it('testExisting should post to /mail-accounts/:id/test', async () => {
|
||||
const response = { success: true, message: 'Connected' };
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: response });
|
||||
|
||||
const result = await mailAccountsApi.testExisting(10);
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/mail-accounts/10/test');
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
|
||||
it('autoDetect should post email address to /mail-accounts/auto-detect', async () => {
|
||||
const response = { success: true, suggestions: [{ protocol: 'imap', host: 'imap.x.com' }] };
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: response });
|
||||
|
||||
const result = await mailAccountsApi.autoDetect('user@x.com');
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/mail-accounts/auto-detect', {
|
||||
email_address: 'user@x.com',
|
||||
});
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
// ── processingRunsApi ───────────────────────────────────────────────────
|
||||
|
||||
describe('processingRunsApi', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('list should get /processing-runs with params', async () => {
|
||||
const paginated = { items: [], total: 0, page: 1, page_size: 20, pages: 0 };
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: paginated });
|
||||
|
||||
const params = { page: 1, page_size: 20, status: 'completed' };
|
||||
const result = await processingRunsApi.list(params);
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/processing-runs', { params });
|
||||
expect(result).toEqual(paginated);
|
||||
});
|
||||
|
||||
it('list should work without params', async () => {
|
||||
const paginated = { items: [], total: 0, page: 1, page_size: 20, pages: 0 };
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: paginated });
|
||||
|
||||
const result = await processingRunsApi.list();
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/processing-runs', { params: undefined });
|
||||
expect(result).toEqual(paginated);
|
||||
});
|
||||
|
||||
it('get should get /processing-runs/:id', async () => {
|
||||
const run = { id: 42, status: 'completed' };
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: run });
|
||||
|
||||
const result = await processingRunsApi.get(42);
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/processing-runs/42');
|
||||
expect(result).toEqual(run);
|
||||
});
|
||||
|
||||
it('getLogs should get /processing-runs/:id/logs with params', async () => {
|
||||
const paginated = { items: [], total: 0, page: 1, page_size: 50, pages: 0 };
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: paginated });
|
||||
|
||||
const params = { page: 2, page_size: 50 };
|
||||
const result = await processingRunsApi.getLogs(42, params);
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/processing-runs/42/logs', { params });
|
||||
expect(result).toEqual(paginated);
|
||||
});
|
||||
|
||||
it('getLogs should work without params', async () => {
|
||||
const paginated = { items: [], total: 0, page: 1, page_size: 20, pages: 0 };
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: paginated });
|
||||
|
||||
const result = await processingRunsApi.getLogs(10);
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/processing-runs/10/logs', {
|
||||
params: undefined,
|
||||
});
|
||||
expect(result).toEqual(paginated);
|
||||
});
|
||||
|
||||
it('listForAccount should get /mail-accounts/:id/processing-runs with params', async () => {
|
||||
const paginated = { items: [], total: 0, page: 1, page_size: 20, pages: 0 };
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: paginated });
|
||||
|
||||
const params = { page: 1, page_size: 20, status: 'failed' };
|
||||
const result = await processingRunsApi.listForAccount(5, params);
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/mail-accounts/5/processing-runs', {
|
||||
params,
|
||||
});
|
||||
expect(result).toEqual(paginated);
|
||||
});
|
||||
|
||||
it('listLogsForAccount should get /mail-accounts/:id/logs with params', async () => {
|
||||
const paginated = { items: [], total: 0, page: 1, page_size: 20, pages: 0 };
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: paginated });
|
||||
|
||||
const params = { page: 1, page_size: 20, level: 'error' };
|
||||
const result = await processingRunsApi.listLogsForAccount(3, params);
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/mail-accounts/3/logs', { params });
|
||||
expect(result).toEqual(paginated);
|
||||
});
|
||||
});
|
||||
|
||||
// ── gmailApi ────────────────────────────────────────────────────────────
|
||||
|
||||
describe('gmailApi', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('getAuthorizeUrl should get /providers/gmail/authorize-url with redirect_uri', async () => {
|
||||
mockAxiosInstance.get.mockResolvedValue({
|
||||
data: { authorization_url: 'https://accounts.google.com/o/oauth2' },
|
||||
});
|
||||
|
||||
const result = await gmailApi.getAuthorizeUrl('http://localhost/callback');
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/providers/gmail/authorize-url', {
|
||||
params: { redirect_uri: 'http://localhost/callback' },
|
||||
});
|
||||
expect(result).toBe('https://accounts.google.com/o/oauth2');
|
||||
});
|
||||
|
||||
it('saveCallback should post code and redirect_uri to /providers/gmail/callback', async () => {
|
||||
const credential = { id: 1, gmail_email: 'test@gmail.com', is_valid: true };
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: credential });
|
||||
|
||||
const result = await gmailApi.saveCallback('authcode', 'http://localhost/callback');
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/providers/gmail/callback', {
|
||||
code: 'authcode',
|
||||
redirect_uri: 'http://localhost/callback',
|
||||
});
|
||||
expect(result).toEqual(credential);
|
||||
});
|
||||
|
||||
it('getCredential should get /providers/gmail-credential', async () => {
|
||||
const credential = { id: 1, gmail_email: 'test@gmail.com', is_valid: true };
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: credential });
|
||||
|
||||
const result = await gmailApi.getCredential();
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/providers/gmail-credential');
|
||||
expect(result).toEqual(credential);
|
||||
});
|
||||
|
||||
it('disconnect should delete /providers/gmail-credential', async () => {
|
||||
mockAxiosInstance.delete.mockResolvedValue({});
|
||||
|
||||
await gmailApi.disconnect();
|
||||
|
||||
expect(mockAxiosInstance.delete).toHaveBeenCalledWith('/providers/gmail-credential');
|
||||
});
|
||||
|
||||
it('sendDebugEmail should post to /providers/gmail/debug-email', async () => {
|
||||
const response = {
|
||||
message: 'Sent',
|
||||
message_id: 'msg1',
|
||||
thread_id: 'thr1',
|
||||
label_ids: ['INBOX'],
|
||||
};
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: response });
|
||||
|
||||
const result = await gmailApi.sendDebugEmail();
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/providers/gmail/debug-email');
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
|
||||
it('updateImportLabels should put label templates to /providers/gmail-credential/labels', async () => {
|
||||
const credential = { id: 1, import_label_templates: ['Label1'] };
|
||||
mockAxiosInstance.put.mockResolvedValue({ data: credential });
|
||||
|
||||
const result = await gmailApi.updateImportLabels(['Label1']);
|
||||
|
||||
expect(mockAxiosInstance.put).toHaveBeenCalledWith('/providers/gmail-credential/labels', {
|
||||
import_label_templates: ['Label1'],
|
||||
});
|
||||
expect(result).toEqual(credential);
|
||||
});
|
||||
});
|
||||
|
||||
// ── smtpApi ─────────────────────────────────────────────────────────────
|
||||
|
||||
describe('smtpApi', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('get should get /users/smtp-config', async () => {
|
||||
const config = { id: 1, host: 'smtp.test.com', port: 587 };
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: config });
|
||||
|
||||
const result = await smtpApi.get();
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/users/smtp-config');
|
||||
expect(result).toEqual(config);
|
||||
});
|
||||
|
||||
it('save should put config to /users/smtp-config', async () => {
|
||||
const config = { id: 1, host: 'smtp.test.com', port: 587 };
|
||||
const saveData = { host: 'smtp.test.com', port: 587, username: 'user', use_tls: true };
|
||||
mockAxiosInstance.put.mockResolvedValue({ data: config });
|
||||
|
||||
const result = await smtpApi.save(saveData);
|
||||
|
||||
expect(mockAxiosInstance.put).toHaveBeenCalledWith('/users/smtp-config', saveData);
|
||||
expect(result).toEqual(config);
|
||||
});
|
||||
|
||||
it('remove should delete /users/smtp-config', async () => {
|
||||
mockAxiosInstance.delete.mockResolvedValue({});
|
||||
|
||||
await smtpApi.remove();
|
||||
|
||||
expect(mockAxiosInstance.delete).toHaveBeenCalledWith('/users/smtp-config');
|
||||
});
|
||||
});
|
||||
|
||||
// ── adminApi ────────────────────────────────────────────────────────────
|
||||
|
||||
describe('adminApi', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('getStats should get /admin/stats', async () => {
|
||||
const stats = { total_users: 10, total_mail_accounts: 20, total_processing_runs: 100 };
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: stats });
|
||||
|
||||
const result = await adminApi.getStats();
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/admin/stats');
|
||||
expect(result).toEqual(stats);
|
||||
});
|
||||
|
||||
it('listUsers should get /admin/users with skip and limit', async () => {
|
||||
const users = [{ id: 1, email: 'a@b.com' }];
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: users });
|
||||
|
||||
const result = await adminApi.listUsers(10, 50);
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/admin/users', {
|
||||
params: { skip: 10, limit: 50 },
|
||||
});
|
||||
expect(result).toEqual(users);
|
||||
});
|
||||
|
||||
it('listUsers should use defaults for skip and limit', async () => {
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: [] });
|
||||
|
||||
await adminApi.listUsers();
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/admin/users', {
|
||||
params: { skip: 0, limit: 100 },
|
||||
});
|
||||
});
|
||||
|
||||
it('getUser should get /admin/users/:id', async () => {
|
||||
const user = { id: 5, email: 'u@t.com' };
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: user });
|
||||
|
||||
const result = await adminApi.getUser(5);
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/admin/users/5');
|
||||
expect(result).toEqual(user);
|
||||
});
|
||||
|
||||
it('updateUser should put data to /admin/users/:id', async () => {
|
||||
const user = { id: 5, is_active: false };
|
||||
mockAxiosInstance.put.mockResolvedValue({ data: user });
|
||||
|
||||
const result = await adminApi.updateUser(5, { is_active: false });
|
||||
|
||||
expect(mockAxiosInstance.put).toHaveBeenCalledWith('/admin/users/5', { is_active: false });
|
||||
expect(result).toEqual(user);
|
||||
});
|
||||
|
||||
it('deleteUser should delete /admin/users/:id', async () => {
|
||||
mockAxiosInstance.delete.mockResolvedValue({});
|
||||
|
||||
await adminApi.deleteUser(5);
|
||||
|
||||
expect(mockAxiosInstance.delete).toHaveBeenCalledWith('/admin/users/5');
|
||||
});
|
||||
|
||||
it('listPlans should get /admin/plans', async () => {
|
||||
const plans = [{ id: 1, tier: 'free' }];
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: plans });
|
||||
|
||||
const result = await adminApi.listPlans();
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/admin/plans');
|
||||
expect(result).toEqual(plans);
|
||||
});
|
||||
|
||||
it('createPlan should post data to /admin/plans', async () => {
|
||||
const plan = { id: 1, tier: 'pro', name: 'Pro Plan' };
|
||||
const createData = {
|
||||
tier: 'pro',
|
||||
name: 'Pro Plan',
|
||||
price_monthly: 9.99,
|
||||
max_mail_accounts: 10,
|
||||
max_emails_per_day: 1000,
|
||||
check_interval_minutes: 5,
|
||||
support_level: 'email',
|
||||
is_active: true,
|
||||
};
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: plan });
|
||||
|
||||
const result = await adminApi.createPlan(createData);
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/admin/plans', createData);
|
||||
expect(result).toEqual(plan);
|
||||
});
|
||||
|
||||
it('updatePlan should put data to /admin/plans/:id', async () => {
|
||||
const plan = { id: 2, name: 'Updated Plan' };
|
||||
mockAxiosInstance.put.mockResolvedValue({ data: plan });
|
||||
|
||||
const result = await adminApi.updatePlan(2, { name: 'Updated Plan' });
|
||||
|
||||
expect(mockAxiosInstance.put).toHaveBeenCalledWith('/admin/plans/2', { name: 'Updated Plan' });
|
||||
expect(result).toEqual(plan);
|
||||
});
|
||||
|
||||
it('deletePlan should delete /admin/plans/:id', async () => {
|
||||
mockAxiosInstance.delete.mockResolvedValue({});
|
||||
|
||||
await adminApi.deletePlan(3);
|
||||
|
||||
expect(mockAxiosInstance.delete).toHaveBeenCalledWith('/admin/plans/3');
|
||||
});
|
||||
|
||||
it('listProcessingRuns should get /admin/processing-runs with params', async () => {
|
||||
const paginated = { items: [], total: 0, page: 1, page_size: 20, pages: 0 };
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: paginated });
|
||||
|
||||
const params = { page: 1, page_size: 20, user_id: 5 };
|
||||
const result = await adminApi.listProcessingRuns(params);
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/admin/processing-runs', { params });
|
||||
expect(result).toEqual(paginated);
|
||||
});
|
||||
|
||||
it('listProcessingRuns should work without params', async () => {
|
||||
const paginated = { items: [], total: 0, page: 1, page_size: 20, pages: 0 };
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: paginated });
|
||||
|
||||
const result = await adminApi.listProcessingRuns();
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/admin/processing-runs', {
|
||||
params: undefined,
|
||||
});
|
||||
expect(result).toEqual(paginated);
|
||||
});
|
||||
|
||||
it('listProcessingLogs should get /admin/processing-logs with params', async () => {
|
||||
const paginated = { items: [], total: 0, page: 1, page_size: 20, pages: 0 };
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: paginated });
|
||||
|
||||
const params = { page: 1, page_size: 20, level: 'error' };
|
||||
const result = await adminApi.listProcessingLogs(params);
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/admin/processing-logs', { params });
|
||||
expect(result).toEqual(paginated);
|
||||
});
|
||||
|
||||
it('listProcessingLogs should work without params', async () => {
|
||||
const paginated = { items: [], total: 0, page: 1, page_size: 20, pages: 0 };
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: paginated });
|
||||
|
||||
const result = await adminApi.listProcessingLogs();
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/admin/processing-logs', {
|
||||
params: undefined,
|
||||
});
|
||||
expect(result).toEqual(paginated);
|
||||
});
|
||||
});
|
||||
|
||||
// ── notificationsApi ────────────────────────────────────────────────────
|
||||
|
||||
describe('notificationsApi', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('list should get /notifications', async () => {
|
||||
const configs = [{ id: 1, name: 'Notif 1' }];
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: configs });
|
||||
|
||||
const result = await notificationsApi.list();
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/notifications');
|
||||
expect(result).toEqual(configs);
|
||||
});
|
||||
|
||||
it('create should post data to /notifications', async () => {
|
||||
const config = { id: 1, name: 'New Notif', channel: 'email' };
|
||||
const createData = { name: 'New Notif', channel: 'email' };
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: config });
|
||||
|
||||
const result = await notificationsApi.create(createData);
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/notifications', createData);
|
||||
expect(result).toEqual(config);
|
||||
});
|
||||
|
||||
it('update should put data to /notifications/:id', async () => {
|
||||
const config = { id: 2, name: 'Updated' };
|
||||
mockAxiosInstance.put.mockResolvedValue({ data: config });
|
||||
|
||||
const result = await notificationsApi.update(2, { name: 'Updated' });
|
||||
|
||||
expect(mockAxiosInstance.put).toHaveBeenCalledWith('/notifications/2', { name: 'Updated' });
|
||||
expect(result).toEqual(config);
|
||||
});
|
||||
|
||||
it('delete should delete /notifications/:id', async () => {
|
||||
mockAxiosInstance.delete.mockResolvedValue({});
|
||||
|
||||
await notificationsApi.delete(3);
|
||||
|
||||
expect(mockAxiosInstance.delete).toHaveBeenCalledWith('/notifications/3');
|
||||
});
|
||||
|
||||
it('test should post apprise_url to /notifications/test', async () => {
|
||||
const response = { success: true, message: 'Test sent' };
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: response });
|
||||
|
||||
const result = await notificationsApi.test('apprise://slack/webhook');
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/notifications/test', {
|
||||
apprise_url: 'apprise://slack/webhook',
|
||||
});
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
// ── adminNotificationsApi ───────────────────────────────────────────────
|
||||
|
||||
describe('adminNotificationsApi', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('list should get /admin/notifications', async () => {
|
||||
const configs = [{ id: 1, name: 'Admin Notif' }];
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: configs });
|
||||
|
||||
const result = await adminNotificationsApi.list();
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/admin/notifications');
|
||||
expect(result).toEqual(configs);
|
||||
});
|
||||
|
||||
it('create should post data to /admin/notifications', async () => {
|
||||
const config = { id: 1, name: 'Admin Notif', apprise_url: 'apprise://test' };
|
||||
const createData = { name: 'Admin Notif', apprise_url: 'apprise://test' };
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: config });
|
||||
|
||||
const result = await adminNotificationsApi.create(createData);
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/admin/notifications', createData);
|
||||
expect(result).toEqual(config);
|
||||
});
|
||||
|
||||
it('update should put data to /admin/notifications/:id', async () => {
|
||||
const config = { id: 2, name: 'Updated Admin' };
|
||||
mockAxiosInstance.put.mockResolvedValue({ data: config });
|
||||
|
||||
const result = await adminNotificationsApi.update(2, { name: 'Updated Admin' });
|
||||
|
||||
expect(mockAxiosInstance.put).toHaveBeenCalledWith('/admin/notifications/2', {
|
||||
name: 'Updated Admin',
|
||||
});
|
||||
expect(result).toEqual(config);
|
||||
});
|
||||
|
||||
it('delete should delete /admin/notifications/:id', async () => {
|
||||
mockAxiosInstance.delete.mockResolvedValue({});
|
||||
|
||||
await adminNotificationsApi.delete(4);
|
||||
|
||||
expect(mockAxiosInstance.delete).toHaveBeenCalledWith('/admin/notifications/4');
|
||||
});
|
||||
|
||||
it('test should post apprise_url to /admin/notifications/test', async () => {
|
||||
const response = { success: true, message: 'Admin test sent' };
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: response });
|
||||
|
||||
const result = await adminNotificationsApi.test('apprise://discord/webhook');
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/admin/notifications/test', {
|
||||
apprise_url: 'apprise://discord/webhook',
|
||||
});
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
// ── versionApi ──────────────────────────────────────────────────────────
|
||||
|
||||
describe('versionApi', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('get should get /version', async () => {
|
||||
const versionInfo = { version: '1.2.3', build_date: '2024-01-01' };
|
||||
mockAxiosInstance.get.mockResolvedValue({ data: versionInfo });
|
||||
|
||||
const result = await versionApi.get();
|
||||
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/version');
|
||||
expect(result).toEqual(versionInfo);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user