Files
vps-tracker/apps/api/src/routes/censorcheck.test.ts
T
2026-08-25 12:32:46 +07:00

165 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { closeDb, MAIN_SPACE_ID, runWithSpace } from '@cfdm/db'
import { vpsRepository } from '@cfdm/db/repositories/vps'
import { resetTestDb, seedTestProvider, seedTestProviderAccount } from '@cfdm/db/test-setup'
import { buildApp } from '../index.js'
import { mintIngestToken } from '../services/censorcheck/ingest-token.js'
const SECRET = 'test-censorcheck-ingest-secret'
function ingestPayload(overrides: Record<string, unknown> = {}) {
return {
schemaVersion: 1,
runId: '11111111-1111-4111-8111-111111111111',
probe: { publicIp: '203.0.113.10' },
launcherVersion: '1',
censorcheck: { version: '1', mode: 'both' },
results: [
{
service: 'youtube.com',
raw: { https: { ipv4: { status: 200 } } },
},
],
...overrides,
}
}
describe('censorcheck ingest + reads', () => {
let app: Awaited<ReturnType<typeof buildApp>>
beforeEach(async () => {
process.env.CENSORCHECK_INGEST_SECRET = SECRET
process.env.CENSORCHECK_RATE_LIMIT = '0'
process.env.TRUST_PROXY = '1'
resetTestDb()
seedTestProvider('p1')
seedTestProviderAccount('a1', 'p1')
app = await buildApp()
})
afterEach(async () => {
await app.close()
closeDb()
delete process.env.TRUST_PROXY
})
async function post(body: unknown, token?: string, headers: Record<string, string> = {}) {
return app.inject({
method: 'POST',
url: '/api/integrations/censorcheck/runs',
headers: {
authorization: `Bearer ${token ?? mintIngestToken(SECRET)}`,
'content-type': 'application/json',
...headers,
},
payload: body as object,
})
}
it('отклоняет запрос без токена', async () => {
const res = await app.inject({
method: 'POST',
url: '/api/integrations/censorcheck/runs',
payload: ingestPayload(),
})
expect(res.statusCode).toBe(401)
})
it('отклоняет просроченный токен', async () => {
const token = mintIngestToken(SECRET, 60, Date.now() - 120_000)
const res = await post(ingestPayload(), token)
expect(res.statusCode).toBe(401)
})
it('отклоняет невалидный payload', async () => {
const res = await post({ schemaVersion: 1, runId: 'short' })
expect(res.statusCode).toBe(400)
})
it('принимает прогон и оставляет unmatched', async () => {
const res = await post(ingestPayload())
expect(res.statusCode).toBe(200)
const json = res.json() as { matchedVpsId: string | null; probePublicIp: string }
expect(json.matchedVpsId).toBeNull()
expect(json.probePublicIp).toBe('203.0.113.10')
})
it('матчит VPS по IPv4 и IPv6', async () => {
const vps = runWithSpace(MAIN_SPACE_ID, () =>
vpsRepository.create({
ip: '203.0.113.10',
ipv6: '2001:db8::55',
dns: 'edge.example.com',
providerId: 'p1',
providerAccountId: 'a1',
status: 'active',
tariffType: 'monthly',
currency: 'RUB',
vcpu: 2,
ramGb: 4,
diskGb: 40,
}),
)
const v4 = await post(ingestPayload({ runId: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa' }))
expect(v4.json().matchedVpsId).toBe(vps.id)
const v6 = await post(
ingestPayload({
runId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb',
probe: { publicIp: '2001:db8::55' },
}),
)
expect(v6.json().matchedVpsId).toBe(vps.id)
})
it('повторяет duplicate runId без второй записи', async () => {
const first = await post(ingestPayload())
const second = await post(ingestPayload())
expect(first.statusCode).toBe(200)
expect(second.statusCode).toBe(200)
expect(second.json().id).toBe(first.json().id)
expect(second.json().replayed).toBe(true)
const current = await app.inject({ method: 'GET', url: '/api/censorcheck/current' })
expect(current.json().items).toHaveLength(1)
})
it('берёт observed XFF, а claimed сохраняет если расходится', async () => {
runWithSpace(MAIN_SPACE_ID, () =>
vpsRepository.create({
ip: '198.51.100.20',
providerId: 'p1',
providerAccountId: 'a1',
status: 'active',
tariffType: 'monthly',
currency: 'RUB',
vcpu: 1,
ramGb: 1,
diskGb: 10,
}),
)
const res = await post(ingestPayload({
runId: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc',
probe: { publicIp: '203.0.113.10' },
}), undefined, { 'x-forwarded-for': '198.51.100.20' })
const json = res.json() as { probePublicIp: string; matchedVpsId: string | null }
expect(json.probePublicIp).toBe('198.51.100.20')
expect(json.matchedVpsId).not.toBeNull()
const current = await app.inject({ method: 'GET', url: '/api/censorcheck/current' })
expect(current.json().items[0].claimedPublicIp).toBe('203.0.113.10')
})
it('отдаёт историю и детали', async () => {
await post(ingestPayload())
const list = await app.inject({ method: 'GET', url: '/api/censorcheck/runs?limit=10' })
expect(list.statusCode).toBe(200)
const items = list.json().items as Array<{ id: string; results?: unknown[] }>
expect(items).toHaveLength(1)
expect(items[0]!.results).toHaveLength(1)
const detail = await app.inject({ method: 'GET', url: `/api/censorcheck/runs/${items[0]!.id}` })
expect(detail.statusCode).toBe(200)
expect(detail.json().results).toHaveLength(1)
})
})