From c14cf3953e0b6bb3e690aacec2f9adee3fa0f3e9 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sun, 2 Aug 2026 19:52:18 +0700 Subject: [PATCH] fix(tests): enhance fetch mocks and type safety in settings and audit tests Updated fetch mocks in settings and audit test files to improve type safety by specifying the fetch type. Adjusted handling of mock call parameters to ensure proper access to request options. This enhances test reliability and clarity in error handling scenarios. --- apps/api/src/routes/settings.test.ts | 27 ++++++++++--------- apps/api/src/services/audit.test.ts | 14 +++++----- apps/api/src/services/billmanager/sync-job.ts | 11 +++----- apps/api/src/services/providers/types.ts | 7 ++++- apps/api/src/services/veesp/mappers.ts | 12 +-------- apps/api/src/services/veesp/operations.ts | 5 ++-- 6 files changed, 36 insertions(+), 40 deletions(-) diff --git a/apps/api/src/routes/settings.test.ts b/apps/api/src/routes/settings.test.ts index 1c964fc..1542652 100644 --- a/apps/api/src/routes/settings.test.ts +++ b/apps/api/src/routes/settings.test.ts @@ -26,7 +26,7 @@ describe('settings telegram test', () => { it('returns telegram API error with hint', async () => { vi.stubGlobal( 'fetch', - vi.fn(async () => + vi.fn(async () => Response.json({ ok: false, description: 'Bad Request: chat not found' }), ), ) @@ -39,7 +39,7 @@ describe('settings telegram test', () => { }) it('uses body overrides and falls back to db token', async () => { - const fetchMock = vi.fn(async () => Response.json({ ok: true })) + const fetchMock = vi.fn(async () => Response.json({ ok: true })) vi.stubGlobal('fetch', fetchMock) const res = await app.inject({ @@ -54,9 +54,9 @@ describe('settings telegram test', () => { const body = res.json() as { ok: boolean } expect(body.ok).toBe(true) - const call = fetchMock.mock.calls[0] as [string, RequestInit] | undefined + const call = fetchMock.mock.calls[0] expect(call).toBeDefined() - const sent = JSON.parse(String(call![1].body)) as { + const sent = JSON.parse(String(call![1]?.body)) as { chat_id: string message_thread_id: number } @@ -65,7 +65,7 @@ describe('settings telegram test', () => { }) it('uses body token when provided', async () => { - const fetchMock = vi.fn(async () => Response.json({ ok: true })) + const fetchMock = vi.fn(async () => Response.json({ ok: true })) vi.stubGlobal('fetch', fetchMock) await app.inject({ @@ -77,7 +77,7 @@ describe('settings telegram test', () => { }, }) - const url = String((fetchMock.mock.calls[0] as [string])[0]) + const url = String(fetchMock.mock.calls[0]![0]) expect(url).toContain('botoverride-token/') }) }) @@ -115,7 +115,7 @@ describe('settings cfdm sync', () => { }) it('requests full sync from CFDM', async () => { - const fetchMock = vi.fn(async () => + const fetchMock = vi.fn(async () => Response.json({ ok: true, count: 1, @@ -141,9 +141,9 @@ describe('settings cfdm sync', () => { expect(res.json()).toMatchObject({ ok: true }) expect((res.json() as { count: number }).count).toBeGreaterThanOrEqual(1) - const call = fetchMock.mock.calls[0] as [string, RequestInit] | undefined + const call = fetchMock.mock.calls[0] expect(call?.[0]).toBe('http://cfdm.test/api/v1/integrations/vps-tracker/sync') - expect((call?.[1].headers as Record).Authorization).toBe( + expect((call?.[1]?.headers as Record).Authorization).toBe( 'Bearer shared-token', ) }) @@ -154,7 +154,7 @@ describe('settings cfdm sync', () => { integrationToken: 'shared-token', cfdmApiUrl: 'http://cfdm.test', }) - const fetchMock = vi.fn(async () => + const fetchMock = vi.fn(async () => Response.json({ ok: true, count: 0, bindings: [], fullSync: true }), ) vi.stubGlobal('fetch', fetchMock) @@ -173,7 +173,7 @@ describe('settings cfdm sync', () => { }) vi.stubGlobal( 'fetch', - vi.fn(async () => { + vi.fn(async () => { throw new TypeError('fetch failed') }), ) @@ -211,18 +211,19 @@ describe('settings cfdm sync', () => { id: 'cfdm', name: 'CFDM', url: 'http://192.168.100.67:6363', + icon: 'cloud', }, ], }, }) - const fetchMock = vi.fn(async () => + const fetchMock = vi.fn(async () => Response.json({ ok: true, count: 0, bindings: [], fullSync: true }), ) vi.stubGlobal('fetch', fetchMock) const res = await app.inject({ method: 'POST', url: '/api/settings/cfdm/sync' }) expect(res.statusCode).toBe(200) - const call = fetchMock.mock.calls[0] as [string] | undefined + const call = fetchMock.mock.calls[0] expect(call?.[0]).toBe('https://cfdm.prod.example/api/v1/integrations/vps-tracker/sync') }) }) diff --git a/apps/api/src/services/audit.test.ts b/apps/api/src/services/audit.test.ts index ac6131f..dcb9e8b 100644 --- a/apps/api/src/services/audit.test.ts +++ b/apps/api/src/services/audit.test.ts @@ -46,7 +46,9 @@ describe('audit dual-write', () => { }) it('fire-and-forgets portal ingest with vps action key', async () => { - const fetchMock = vi.fn(async () => new Response(JSON.stringify({ accepted: 1, duplicates: 0 }))) + const fetchMock = vi.fn( + async () => new Response(JSON.stringify({ accepted: 1, duplicates: 0 })), + ) vi.stubGlobal('fetch', fetchMock) auditCreate( @@ -63,15 +65,15 @@ describe('audit dual-write', () => { await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1)) - const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit] + const [url, init] = fetchMock.mock.calls[0]! expect(url).toBe('http://portal.test/api/v1/ingest/audit') - expect(init.method).toBe('POST') - expect(init.headers).toMatchObject({ + expect(init?.method).toBe('POST') + expect(init?.headers).toMatchObject({ Authorization: 'Bearer test-ingest-secret', 'Content-Type': 'application/json', }) - const body = JSON.parse(String(init.body)) as { + const body = JSON.parse(String(init?.body)) as { events: Array<{ event_id: string source_app: string @@ -94,7 +96,7 @@ describe('audit dual-write', () => { it('does not throw when portal ingest fails', () => { vi.stubGlobal( 'fetch', - vi.fn(async () => { + vi.fn(async () => { throw new Error('network down') }), ) diff --git a/apps/api/src/services/billmanager/sync-job.ts b/apps/api/src/services/billmanager/sync-job.ts index 6d14670..42c8bbe 100644 --- a/apps/api/src/services/billmanager/sync-job.ts +++ b/apps/api/src/services/billmanager/sync-job.ts @@ -3,18 +3,15 @@ */ import type { BillmanagerSyncAccount } from './context.js' -import type { SyncFromBillmanagerOptions, SyncFromBillmanagerResult } from './sync.js' +import type { SyncFromBillmanagerOptions } from './sync.js' import { billmanagerAdapter } from '../providers/billmanager-adapter.js' -import { runAccountSync } from '../providers/sync-job.js' +import { runAccountSync, type RunAccountSyncResult } from '../providers/sync-job.js' -export interface RunBillmanagerAccountSyncResult extends SyncFromBillmanagerResult { - ok: true - logId: string -} +export type RunBillmanagerAccountSyncResult = RunAccountSyncResult export async function runBillmanagerAccountSync( account: BillmanagerSyncAccount, opts: SyncFromBillmanagerOptions = {}, -): Promise { +): Promise { return runAccountSync(billmanagerAdapter, account, opts) } diff --git a/apps/api/src/services/providers/types.ts b/apps/api/src/services/providers/types.ts index babb851..8a0a20f 100644 --- a/apps/api/src/services/providers/types.ts +++ b/apps/api/src/services/providers/types.ts @@ -9,7 +9,12 @@ export interface SyncResult { vpsCount: number paymentsCount: number tariffsCount: number - balance: { balance?: number; currency?: string } | null + balance: { + balance?: number + currency?: string + enoughmoneyto?: string + realbalance?: string + } | null syncSummary: SyncSummary newTariffs: { name: string; price: string; providerId: string }[] } diff --git a/apps/api/src/services/veesp/mappers.ts b/apps/api/src/services/veesp/mappers.ts index 92df922..b7542d9 100644 --- a/apps/api/src/services/veesp/mappers.ts +++ b/apps/api/src/services/veesp/mappers.ts @@ -30,20 +30,10 @@ function roundRate(n: number): number { return Math.round(n * 100) / 100 } -function mapBillingCycle(cycle: string | undefined): { - tariffType: string - dailyRate: number | null - monthlyRate: number | null -} { - const c = String(cycle ?? '').toLowerCase() - return { tariffType: c.includes('day') ? 'daily' : 'monthly', dailyRate: null, monthlyRate: null } -} - function ratesFromTotal( total: number, cycle: string | undefined, ): { tariffType: string; dailyRate: number | null; monthlyRate: number | null } { - const base = mapBillingCycle(cycle) const c = String(cycle ?? '').toLowerCase() if (c.includes('day')) { return { tariffType: 'daily', dailyRate: roundRate(total), monthlyRate: null } @@ -253,7 +243,7 @@ export function mapVpsRecordToVps( currency, dailyRate: rates.dailyRate, monthlyRate: rates.monthlyRate, - createdAt: dateToIso(detail.date_created), + createdAt: dateToIso(serviceDetail?.date_created), paidUntil: dateToIso(detail.next_due ?? service.next_due), notes: label ? `${label} [veesp-${externalId}]` : `veesp-${externalId}`, } diff --git a/apps/api/src/services/veesp/operations.ts b/apps/api/src/services/veesp/operations.ts index dcf28cf..02a94a7 100644 --- a/apps/api/src/services/veesp/operations.ts +++ b/apps/api/src/services/veesp/operations.ts @@ -62,6 +62,7 @@ export interface VeespVmDetail extends VeespVmListItem { } export interface VeespIpItem { + id?: string | number ip?: string address?: string ipaddress?: string @@ -207,7 +208,7 @@ export function isVpsService(service: VeespServiceListItem, vpsCategoryIds?: Set return false } -function unwrapKeyedList>(raw: unknown): T[] { +function unwrapKeyedList(raw: unknown): T[] { if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return [] const out: T[] = [] for (const [key, item] of Object.entries(raw as Record)) { @@ -218,7 +219,7 @@ function unwrapKeyedList>(raw: unknown): T[] { } if (typeof item !== 'object') continue const row = item as Record - out.push({ ...row, id: row.id ?? key } as T) + out.push({ ...row, id: row.id ?? key } as unknown as T) } return out }