fix(tests): enhance fetch mocks and type safety in settings and audit tests
Docker / build (push) Failing after 30s

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.
This commit is contained in:
Denozordec
2026-08-02 19:52:18 +07:00
parent 0177f5d539
commit c14cf3953e
6 changed files with 36 additions and 40 deletions
+14 -13
View File
@@ -26,7 +26,7 @@ describe('settings telegram test', () => {
it('returns telegram API error with hint', async () => {
vi.stubGlobal(
'fetch',
vi.fn(async () =>
vi.fn<typeof fetch>(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<typeof fetch>(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<typeof fetch>(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<typeof fetch>(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<string, string>).Authorization).toBe(
expect((call?.[1]?.headers as Record<string, string>).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<typeof fetch>(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<typeof fetch>(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<typeof fetch>(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')
})
})
+8 -6
View File
@@ -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<typeof fetch>(
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<typeof fetch>(async () => {
throw new Error('network down')
}),
)
@@ -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<RunBillmanagerAccountSyncResult> {
): Promise<RunAccountSyncResult> {
return runAccountSync(billmanagerAdapter, account, opts)
}
+6 -1
View File
@@ -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 }[]
}
+1 -11
View File
@@ -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}`,
}
+3 -2
View File
@@ -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<T extends Record<string, unknown>>(raw: unknown): T[] {
function unwrapKeyedList<T>(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<string, unknown>)) {
@@ -218,7 +219,7 @@ function unwrapKeyedList<T extends Record<string, unknown>>(raw: unknown): T[] {
}
if (typeof item !== 'object') continue
const row = item as Record<string, unknown>
out.push({ ...row, id: row.id ?? key } as T)
out.push({ ...row, id: row.id ?? key } as unknown as T)
}
return out
}