fix(evolution): проверять настройки и подтверждение сохранённых полей

This commit is contained in:
Denozordec committed 2026-10-03 13:11:45 +07:00
1 parent 2215007c91
commit 2b62e0e956
8 files changed
+73 -1

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
import assert from "node:assert/strict"
import {test} from "node:test"
import {getEvoBgpSettings,putEvoBgpSettings,testEvoBgpConnection} from "./evobgp-settings"
const settings={baseUrl:"https://evo.example.test",enabled:false,secretConfigured:false},base="http://fixture.test"
const json=(value:unknown)=>new Response(JSON.stringify(value))
test("Evo settings require known boolean values and no returned secret",async()=>{const original=globalThis.fetch;try{globalThis.fetch=async()=>json(settings);assert.deepEqual(await getEvoBgpSettings(base),settings);for(const bad of [{}, {...settings,enabled:undefined},{...settings,secretConfigured:0},{...settings,apiKey:"fixture-only"},{...settings,baseUrl:"https://evo.test/v1"}]){globalThis.fetch=async()=>json(bad);await assert.rejects(getEvoBgpSettings(base),/не подтверждены/)}}finally{globalThis.fetch=original}})
test("Evo PUT normalizes explicit fields and requires matching receipt/secret flag without echoing key",async()=>{const original=globalThis.fetch;try{let calls=0;globalThis.fetch=async(_url,init)=>{calls++;assert.deepEqual(JSON.parse(String(init?.body)),{baseUrl:settings.baseUrl,enabled:false,apiKey:"fixture-only"});return json({...settings,ok:true,secretConfigured:true,savedFields:["baseUrl","enabled","apiKey"]})};await putEvoBgpSettings(base,{baseUrl:"evo.example.test/",enabled:false,apiKey:"Bearer fixture-only"});assert.equal(calls,1)
for(const bad of [{...settings},{...settings,ok:false,savedFields:["enabled"]},{...settings,ok:true,enabled:true,savedFields:["enabled"]},{...settings,ok:true,savedFields:["enabled","enabled"]}]){globalThis.fetch=async()=>json(bad);await assert.rejects(putEvoBgpSettings(base,{enabled:false}),/не подтверждено/)}
for(const patch of [{},{baseUrl:"https://evo.test/v1"},{baseUrl:"ftp://evo.test"},{apiKey:"fixture\nonly"}]){calls=0;globalThis.fetch=async()=>{calls++;return json({})};await assert.rejects(putEvoBgpSettings(base,patch));assert.equal(calls,0)}
globalThis.fetch=async()=>json({...settings,ok:true,savedFields:["apiKey"]});assert.equal((await putEvoBgpSettings(base,{apiKey:null})).secretConfigured,false)
}finally{globalThis.fetch=original}})
test("Evo test requires literal confirmation and never retries unknown POST",async()=>{const original=globalThis.fetch;try{globalThis.fetch=async()=>json({ok:true});await testEvoBgpConnection(base);for(const bad of [{},{ok:false},{ok:"true"}]){let calls=0;globalThis.fetch=async()=>{calls++;return json(bad)};await assert.rejects(testEvoBgpConnection(base),/не подтверждено/);assert.equal(calls,1)}}finally{globalThis.fetch=original}})
+24
View File
@@ -0,0 +1,24 @@
import { evoBgpSettingsSchema,evoBgpSettingsPatchSchema,evoBgpSaveReceiptSchema,evoBgpTestDraftSchema,type EvoBgpSavePayload,type EvoBgpTestDraft } from "@mmapp/contracts/evobgp"
import {requestJson} from "./http-client"
import {confirmEvoBgpConnection} from "@/lib/evobgp-test-receipt"
function initWithTimeout(init?:RequestInit):RequestInit {return {...init,signal:AbortSignal.any([...(init?.signal?[init.signal]:[]),AbortSignal.timeout(30_000)])}}
export async function getEvoBgpSettings(base:string,init?:RequestInit){
const parsed=evoBgpSettingsSchema.safeParse(await requestJson<unknown>(base,"/api/evobgp/settings",{...initWithTimeout(init),method:"GET"}))
if(!parsed.success)throw new Error("Настройки EvoBGP не подтверждены сервером")
return parsed.data
}
export async function putEvoBgpSettings(base:string,patch:EvoBgpSavePayload,init?:RequestInit){
const input=evoBgpSettingsPatchSchema.safeParse(patch)
if(!input.success)throw new Error(input.error.issues[0]?.message??"Укажите допустимые настройки EvoBGP")
const body=input.data
const parsed=evoBgpSaveReceiptSchema.safeParse(await requestJson<unknown>(base,"/api/evobgp/settings",{...initWithTimeout(init),method:"PUT",body:JSON.stringify(body)}))
if(!parsed.success)throw new Error("Сохранение EvoBGP не подтверждено сервером")
const data=parsed.data,fields=Object.keys(body).filter(field=>(body as Record<string,unknown>)[field]!==undefined)
if(data.savedFields.length!==fields.length||fields.some(field=>!data.savedFields.includes(field as typeof data.savedFields[number]))||body.baseUrl!==undefined&&data.baseUrl!==body.baseUrl||body.enabled!==undefined&&data.enabled!==body.enabled||body.apiKey!==undefined&&data.secretConfigured!==Boolean(body.apiKey))throw new Error("Сохранение EvoBGP не подтверждено: сервер вернул другие настройки")
return data
}
export async function testEvoBgpConnection(base:string,draft:EvoBgpTestDraft={},init?:RequestInit):Promise<void>{
const input=evoBgpTestDraftSchema.safeParse(draft)
if(!input.success)throw new Error(input.error.issues[0]?.message??"Укажите допустимые параметры EvoBGP")
confirmEvoBgpConnection(await requestJson<unknown>(base,"/api/evobgp/test",{...initWithTimeout(init),method:"POST",body:JSON.stringify(input.data)}))
}