Docker / build (push) Failing after 18s
Убран fallback на локальный App Switcher (192.168.x). URL из portal подставляется в форму для сохранения; кнопка sync требует URL в БД. Co-authored-by: Cursor <[email protected]>
229 lines
7.0 KiB
TypeScript
229 lines
7.0 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { closeDb } from '@cfdm/db'
|
|
import { settingsRepository } from '@cfdm/db/repositories/settings'
|
|
import { resetTestDb } from '@cfdm/db/test-setup'
|
|
import { buildApp } from '../index.js'
|
|
|
|
describe('settings telegram test', () => {
|
|
let app: Awaited<ReturnType<typeof buildApp>>
|
|
|
|
beforeEach(async () => {
|
|
resetTestDb()
|
|
settingsRepository.upsert('settings-main', {
|
|
telegramBotToken: 'db-token',
|
|
telegramChatId: '123',
|
|
telegramMessageThreadId: '99',
|
|
})
|
|
app = await buildApp()
|
|
})
|
|
|
|
afterEach(async () => {
|
|
await app.close()
|
|
vi.unstubAllGlobals()
|
|
closeDb()
|
|
})
|
|
|
|
it('returns telegram API error with hint', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async () =>
|
|
Response.json({ ok: false, description: 'Bad Request: chat not found' }),
|
|
),
|
|
)
|
|
const res = await app.inject({ method: 'POST', url: '/api/settings/telegram/test' })
|
|
expect(res.statusCode).toBe(200)
|
|
const body = res.json() as { ok: boolean; error?: string }
|
|
expect(body.ok).toBe(false)
|
|
expect(body.error).toContain('chat not found')
|
|
expect(body.error).toContain('Chat ID')
|
|
})
|
|
|
|
it('uses body overrides and falls back to db token', async () => {
|
|
const fetchMock = vi.fn(async () => Response.json({ ok: true }))
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
|
|
const res = await app.inject({
|
|
method: 'POST',
|
|
url: '/api/settings/telegram/test',
|
|
payload: {
|
|
telegramChatId: '-100999',
|
|
telegramMessageThreadId: '42',
|
|
},
|
|
})
|
|
expect(res.statusCode).toBe(200)
|
|
const body = res.json() as { ok: boolean }
|
|
expect(body.ok).toBe(true)
|
|
|
|
const call = fetchMock.mock.calls[0] as [string, RequestInit] | undefined
|
|
expect(call).toBeDefined()
|
|
const sent = JSON.parse(String(call![1].body)) as {
|
|
chat_id: string
|
|
message_thread_id: number
|
|
}
|
|
expect(sent.chat_id).toBe('-100999')
|
|
expect(sent.message_thread_id).toBe(42)
|
|
})
|
|
|
|
it('uses body token when provided', async () => {
|
|
const fetchMock = vi.fn(async () => Response.json({ ok: true }))
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
|
|
await app.inject({
|
|
method: 'POST',
|
|
url: '/api/settings/telegram/test',
|
|
payload: {
|
|
telegramBotToken: 'override-token',
|
|
telegramChatId: '-1001',
|
|
},
|
|
})
|
|
|
|
const url = String((fetchMock.mock.calls[0] as [string])[0])
|
|
expect(url).toContain('botoverride-token/')
|
|
})
|
|
})
|
|
|
|
describe('settings cfdm sync', () => {
|
|
let app: Awaited<ReturnType<typeof buildApp>>
|
|
|
|
beforeEach(async () => {
|
|
resetTestDb()
|
|
settingsRepository.upsert('settings-main', {
|
|
integrationEnabled: true,
|
|
integrationToken: 'shared-token',
|
|
cfdmApiUrl: 'http://cfdm.test',
|
|
})
|
|
app = await buildApp()
|
|
})
|
|
|
|
afterEach(async () => {
|
|
await app.close()
|
|
vi.unstubAllGlobals()
|
|
closeDb()
|
|
})
|
|
|
|
it('persists cfdmApiUrl via PUT settings', async () => {
|
|
const res = await app.inject({
|
|
method: 'PUT',
|
|
url: '/api/settings/settings-main',
|
|
payload: { cfdmApiUrl: 'http://192.168.100.67:6363' },
|
|
})
|
|
expect(res.statusCode).toBe(200)
|
|
expect(res.json()).toMatchObject({ cfdmApiUrl: 'http://192.168.100.67:6363' })
|
|
|
|
const dto = settingsRepository.get('settings-main')
|
|
expect(dto?.cfdmApiUrl).toBe('http://192.168.100.67:6363')
|
|
})
|
|
|
|
it('requests full sync from CFDM', async () => {
|
|
const fetchMock = vi.fn(async () =>
|
|
Response.json({
|
|
ok: true,
|
|
count: 1,
|
|
fullSync: true,
|
|
bindings: [
|
|
{
|
|
bindingId: 1,
|
|
serviceId: 10,
|
|
serviceName: 'web',
|
|
serviceSlug: 'web',
|
|
fqdn: 'app.example.com',
|
|
zoneName: 'example.com',
|
|
hostname: 'app',
|
|
ips: ['1.2.3.4'],
|
|
},
|
|
],
|
|
}),
|
|
)
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
|
|
const res = await app.inject({ method: 'POST', url: '/api/settings/cfdm/sync' })
|
|
expect(res.statusCode).toBe(200)
|
|
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
|
|
expect(call?.[0]).toBe('http://cfdm.test/api/v1/integrations/vps-tracker/sync')
|
|
expect((call?.[1].headers as Record<string, string>).Authorization).toBe(
|
|
'Bearer shared-token',
|
|
)
|
|
})
|
|
|
|
it('works with saved token even when accept toggle is off', async () => {
|
|
settingsRepository.upsert('settings-main', {
|
|
integrationEnabled: false,
|
|
integrationToken: 'shared-token',
|
|
cfdmApiUrl: 'http://cfdm.test',
|
|
})
|
|
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)
|
|
expect(res.json()).toEqual({ ok: true, count: 0 })
|
|
expect(fetchMock).toHaveBeenCalled()
|
|
})
|
|
|
|
it('returns clear error when CFDM is unreachable', async () => {
|
|
settingsRepository.upsert('settings-main', {
|
|
integrationEnabled: true,
|
|
integrationToken: 'shared-token',
|
|
cfdmApiUrl: 'http://cfdm.test',
|
|
})
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(async () => {
|
|
throw new TypeError('fetch failed')
|
|
}),
|
|
)
|
|
|
|
const res = await app.inject({ method: 'POST', url: '/api/settings/cfdm/sync' })
|
|
expect(res.statusCode).toBe(502)
|
|
const body = res.json() as { ok: boolean; error: string }
|
|
expect(body.ok).toBe(false)
|
|
expect(body.error).toContain('http://cfdm.test')
|
|
expect(body.error).toContain('fetch failed')
|
|
})
|
|
|
|
it('returns error when CFDM URL is missing', async () => {
|
|
settingsRepository.upsert('settings-main', {
|
|
integrationEnabled: true,
|
|
integrationToken: 'shared-token',
|
|
cfdmApiUrl: '',
|
|
})
|
|
const res = await app.inject({ method: 'POST', url: '/api/settings/cfdm/sync' })
|
|
expect(res.statusCode).toBe(502)
|
|
const body = res.json() as { ok: boolean; error: string }
|
|
expect(body.ok).toBe(false)
|
|
expect(body.error).toContain('URL API CFDM')
|
|
})
|
|
|
|
it('uses only saved cfdmApiUrl, not App Switcher defaults', async () => {
|
|
settingsRepository.upsert('settings-main', {
|
|
integrationEnabled: true,
|
|
integrationToken: 'shared-token',
|
|
cfdmApiUrl: 'https://cfdm.prod.example',
|
|
appSwitcher: {
|
|
menuLabel: 'Apps',
|
|
apps: [
|
|
{
|
|
id: 'cfdm',
|
|
name: 'CFDM',
|
|
url: 'http://192.168.100.67:6363',
|
|
},
|
|
],
|
|
},
|
|
})
|
|
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
|
|
expect(call?.[0]).toBe('https://cfdm.prod.example/api/v1/integrations/vps-tracker/sync')
|
|
})
|
|
})
|