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:
copilot-swe-agent[bot]
2026-03-29 00:58:20 +00:00
parent d79f9caf9e
commit 8eff0833fa
3 changed files with 735 additions and 0 deletions
+1
View File
@@ -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
+1
View File
@@ -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)
+733
View File
@@ -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);
});
});