feat(api, web): интеграция Macloud и VDSina через унифицированный UserAPI
Docker / build (push) Has been cancelled
Docker / build (push) Has been cancelled
Один модуль userapi для идентичного API обоих хостеров: синк VPS, баланса, тарифов и пополнений. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { closeDb, getSqlite } from '@cfdm/db'
|
||||
import { resetTestDb } from '@cfdm/db/test-setup'
|
||||
import { providerAccountsRepository } from '@cfdm/db/repositories/provider-accounts'
|
||||
|
||||
import { syncFromUserApi } from './sync.js'
|
||||
import type { UserApiSyncAccount } from './context.js'
|
||||
|
||||
vi.mock('./operations.js', () => ({
|
||||
fetchServersWithDetails: vi.fn(),
|
||||
fetchBalance: vi.fn(),
|
||||
fetchTariffList: vi.fn(),
|
||||
fetchOperations: vi.fn(),
|
||||
}))
|
||||
|
||||
import {
|
||||
fetchBalance,
|
||||
fetchOperations,
|
||||
fetchServersWithDetails,
|
||||
fetchTariffList,
|
||||
} from './operations.js'
|
||||
|
||||
function makeAccount(apiType: 'macloud' | 'vdsina'): UserApiSyncAccount {
|
||||
return {
|
||||
id: `acc-${apiType}`,
|
||||
providerId: `prov-${apiType}`,
|
||||
name: `${apiType} Account`,
|
||||
panelUrl: '',
|
||||
currency: 'RUB',
|
||||
billingMode: 'daily',
|
||||
notes: '',
|
||||
apiType,
|
||||
apiBaseUrl:
|
||||
apiType === 'macloud'
|
||||
? 'https://userapi.macloud.ru/v1'
|
||||
: 'https://userapi.vdsina.com/v1',
|
||||
apiCredentials: 'secret-token',
|
||||
apiToken: 'secret-token',
|
||||
balanceApi: null,
|
||||
balanceCurrency: null,
|
||||
balanceUpdatedAt: null,
|
||||
enoughmoneyto: '',
|
||||
balanceAlertBelow: null,
|
||||
}
|
||||
}
|
||||
|
||||
describe('syncFromUserApi', () => {
|
||||
beforeEach(() => {
|
||||
resetTestDb()
|
||||
vi.mocked(fetchServersWithDetails).mockResolvedValue([
|
||||
{
|
||||
id: 100,
|
||||
name: 'Server #100',
|
||||
status: 'active',
|
||||
end: '2029-01-01',
|
||||
ip: { ip: '1.2.3.4', type: '4' },
|
||||
template: { name: 'Debian 12' },
|
||||
datacenter: { id: 1, name: 'DC1', country: 'ru' },
|
||||
'server-plan': { name: '2 RAM / 1 CPU / 40 NVMe' },
|
||||
data: { cpu: { value: 1 }, ram: { value: 2 }, disk: { value: 40 } },
|
||||
},
|
||||
])
|
||||
vi.mocked(fetchBalance).mockResolvedValue({
|
||||
balance: 500,
|
||||
currency: 'RUB',
|
||||
enoughmoneyto: '2029-12-01',
|
||||
})
|
||||
vi.mocked(fetchTariffList).mockResolvedValue([
|
||||
{
|
||||
externalId: '13',
|
||||
datacenterKey: '11',
|
||||
datacenterName: 'Cloud',
|
||||
name: '2 RAM / 1 CPU / 40 NVMe',
|
||||
desc: '',
|
||||
vcpu: 1,
|
||||
ramGb: 2,
|
||||
diskGb: 40,
|
||||
diskType: 'NVMe',
|
||||
virtualization: 'KVM',
|
||||
channel: '',
|
||||
location: 'Cloud',
|
||||
country: '',
|
||||
cpuModel: '',
|
||||
orderAvailable: true,
|
||||
price: '1.55 ₽/день',
|
||||
},
|
||||
])
|
||||
vi.mocked(fetchOperations).mockResolvedValue([
|
||||
{
|
||||
id: 999,
|
||||
purse: 'real',
|
||||
type: 1,
|
||||
status: 1,
|
||||
summ: '200',
|
||||
created: '2025-03-01 10:00:00',
|
||||
comment: 'Top up',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
closeDb()
|
||||
})
|
||||
|
||||
it('syncs macloud account with correct id prefix', async () => {
|
||||
getSqlite()
|
||||
.prepare(
|
||||
`INSERT INTO providers (id, name, apiType, apiBaseUrl) VALUES ('prov-macloud', 'Macloud', 'macloud', 'https://userapi.macloud.ru/v1')`,
|
||||
)
|
||||
.run()
|
||||
providerAccountsRepository.create({
|
||||
id: 'acc-macloud',
|
||||
providerId: 'prov-macloud',
|
||||
name: 'Macloud',
|
||||
apiCredentials: 'secret-token',
|
||||
})
|
||||
|
||||
const result = await syncFromUserApi(makeAccount('macloud'))
|
||||
|
||||
expect(result.vpsCount).toBe(1)
|
||||
expect(result.paymentsCount).toBe(1)
|
||||
expect(result.tariffsCount).toBe(1)
|
||||
expect(result.balance?.balance).toBe(500)
|
||||
|
||||
const vps = getSqlite().prepare('SELECT id, notes FROM vps WHERE id = ?').get('vps-macloud-acc-macloud-100') as {
|
||||
id: string
|
||||
notes: string
|
||||
}
|
||||
expect(vps.notes).toContain('macloud-100')
|
||||
})
|
||||
|
||||
it('syncs vdsina account with vdsina id prefix', async () => {
|
||||
getSqlite()
|
||||
.prepare(
|
||||
`INSERT INTO providers (id, name, apiType, apiBaseUrl) VALUES ('prov-vdsina', 'VDSina', 'vdsina', 'https://userapi.vdsina.com/v1')`,
|
||||
)
|
||||
.run()
|
||||
providerAccountsRepository.create({
|
||||
id: 'acc-vdsina',
|
||||
providerId: 'prov-vdsina',
|
||||
name: 'VDSina',
|
||||
apiCredentials: 'secret-token',
|
||||
})
|
||||
|
||||
const result = await syncFromUserApi(makeAccount('vdsina'))
|
||||
|
||||
expect(result.vpsCount).toBe(1)
|
||||
const vps = getSqlite().prepare('SELECT id FROM vps WHERE id = ?').get('vps-vdsina-acc-vdsina-100')
|
||||
expect(vps).toBeTruthy()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user