import assert from 'node:assert/strict' import fs from 'node:fs' import {spawn} from 'node:child_process' // Never part of test:all: full mode sends three messages; resume mode sends one intentional duplicate. const label=process.env.MMAPP_LIVE_TELEGRAM_TEST_LABEL??'197' assert(/^\d{3}$/.test(label)) const resume=process.env.MMAPP_LIVE_TELEGRAM_RESUME==='manual-retry-only' const database=process.env.MMAPP_OWN_SQL_DB assert(/^mm_accept_166_\d+$/.test(database??''),'An explicitly owned test database is required') const url=new URL(process.env.DATABASE_URL) assert.equal(url.pathname,'/'+database);assert.equal(url.username,database) assert.equal(process.env.MMAPP_LIVE_TELEGRAM_SEND_ACK,'owned-test-chat') assert.equal(process.env.MMAPP_LIVE_TELEGRAM_ACK_DUPLICATE,'yes') const bot=JSON.parse(fs.readFileSync(process.env.MMAPP_LIVE_TELEGRAM_PRIVATE_FILE)) assert.equal(bot.chatId,process.env.MMAPP_LIVE_TELEGRAM_CHAT_ACK) assert(/^\d+:[A-Za-z0-9_-]+$/.test(bot.token)) const portal=JSON.parse(fs.readFileSync(process.env.MMAPP_LIVE_PORTAL_PRIVATE_FILE)) assert.equal(process.env.AUTH_REQUIRED,'true');assert.equal(process.env.AUTH_JWT_SECRET,portal.jwtSecret) const {pool,closePool}=await import('/app/backend/dist/db/index.js') const {initDatabase}=await import('/app/backend/dist/db/bootstrap.js') const {updateTelegramSettings}=await import('/app/backend/dist/services/alerts-service.js') const {enqueueTelegramOutbox,dispatchPendingOutbox,getAlertsOutboxOverview}=await import('/app/backend/dist/services/alert-engine/outbox.js') assert.equal((await pool.query('SELECT current_database() AS name')).rows[0].name,database) const permissions=(await pool.query('SELECT rolsuper,rolcreatedb,rolcreaterole,rolbypassrls FROM pg_roles WHERE rolname=current_user')).rows[0] assert(Object.values(permissions).every(v=>v===false)) const root='/app/backend/acceptance',prefix='own-live'+label+'-'+Date.now(),ownedUsers=[],cases=[] const report={date:'2026-10-13',block:'12.5.'+label,database,resumeOnly:resume,scope:'Production compiled outbox, owned PostgreSQL, actual Portal JWT and real Telegram provider',cases,providerSendCalls:0,requestTrace:[],providerAcceptanceIds:[],ownPortalUsersRemoved:false,passed:false} let app,adminToken,originalFetch=globalThis.fetch const portalUrl='http://portal:8080' async function portalCall(path,method='GET',body,token){ const r=await originalFetch(portalUrl+path,{method,headers:{...(body!==undefined?{'content-type':'application/json'}:{}),...(token?{Authorization:'Bearer '+token}:{})},body:body===undefined?undefined:JSON.stringify(body)}) const text=await r.text();return {status:r.status,data:text?JSON.parse(text):null} } const payload=(number)=>({text:`TEST${label} №${number} — очередь Telegram. ${number===1?'Проверка dedupe и двух отправителей.':'Проверка unknown и намеренного ручного повтора.'}`,chatId:bot.chatId, history:{id:prefix+'-history'+number,ruleId:null,groupId:null,ruleName:'Own TEST'+label+' №'+number,severity:'info',message:'Контролируемая приёмка очереди Telegram',firedAt:new Date().toISOString()}}) async function sender(){ const code=`import assert from 'node:assert/strict';import {pool,closePool} from '/app/backend/dist/db/index.js';import {dispatchPendingOutbox} from '/app/backend/dist/services/alert-engine/outbox.js';assert.equal((await pool.query('SELECT current_database() AS name')).rows[0].name,process.env.MMAPP_OWN_SQL_DB);let calls=0,ids=[];const native=globalThis.fetch;globalThis.fetch=async(...args)=>{const r=await native(...args);if(new URL(String(args[0])).pathname.endsWith('/sendMessage')){calls++;const d=await r.clone().json();if(d.ok)ids.push(d.result.message_id)}return r};try{const result=await dispatchPendingOutbox(1);console.log(JSON.stringify({result,calls,ids}))}finally{await closePool()}` return new Promise((resolve,reject)=>{ const child=spawn(process.execPath,['--input-type=module','-e',code],{env:process.env,stdio:['ignore','pipe','pipe']}) let output='',error='';child.stdout.on('data',b=>output+=b);child.stderr.on('data',b=>error+=b) child.on('error',reject);child.on('exit',status=>{if(status!==0)return reject(Error('Owned sender failed: '+error));try{resolve(JSON.parse(output.trim()))}catch(e){reject(e)}}) }) } let suppressReceipt=false try{ await initDatabase();assert.equal(Number((await pool.query('SELECT count(*) AS n FROM alert_outbox')).rows[0].n),0) await updateTelegramSettings({token:bot.token,chatId:bot.chatId,messageThreadId:null}) const login=await portalCall('/api/v1/auth/login','POST',{email:portal.email,password:portal.password});assert.equal(login.status,200);adminToken=login.data.access_token const readerEmail=prefix+'@mmapp.test' const created=await portalCall('/api/v1/admin/users','POST',{email:readerEmail,password:portal.password,name:'Own TEST'+label+' reader',apps:['mm'],permissions:['mm:alerts:read']},adminToken) assert.equal(created.status,201);ownedUsers.push(created.data.id) fs.writeFileSync(root+'/owned-portal-users-private.json',JSON.stringify(ownedUsers),{mode:0o600}) const readerLogin=await portalCall('/api/v1/auth/login','POST',{email:readerEmail,password:portal.password});assert.equal(readerLogin.status,200) const {buildApp}=await import('/app/backend/dist/index.js');app=await buildApp({logger:false,startScheduler:false}) const id1=prefix+'-1',id2=prefix+'-2' if(!resume){ assert.equal(await enqueueTelegramOutbox({id:id1,dedupeKey:id1,payload:payload(1)}),true) assert.equal(await enqueueTelegramOutbox({id:id1+'-duplicate',dedupeKey:id1,payload:payload(1)}),false) const senders=await Promise.all([sender(),sender()]) assert.equal(senders.reduce((n,s)=>n+s.result.sent,0),1);assert.equal(senders.reduce((n,s)=>n+s.calls,0),1) report.providerSendCalls+=1;report.providerAcceptanceIds.push(...senders.flatMap(s=>s.ids)) let overview=await getAlertsOutboxOverview();assert.equal(overview.counts.sent,1) assert.equal(Number((await pool.query('SELECT count(*) AS n FROM alert_history WHERE id=$1 AND sent_ok=true',[prefix+'-history1'])).rows[0].n),1) cases.push({name:'dedupe and independent senders',senders:2,queuedRows:1,providerSends:1,sentHistoryRows:1}) } let lastSendAt=Date.now() globalThis.fetch=async(...args)=>{ const sending=new URL(String(args[0])).pathname.endsWith('/sendMessage') if(sending){await new Promise(resolve=>setTimeout(resolve,Math.max(0,1500-(Date.now()-lastSendAt))));lastSendAt=Date.now();assert(report.providerSendCalls<(resume?1:3),'Live send budget exhausted');report.providerSendCalls++} const trace=sending?{requestNumber:report.providerSendCalls,startedAt:new Date().toISOString()}:null if(trace)report.requestTrace.push(trace) let response try{response=await originalFetch(...args)}catch(e){if(trace)trace.fetchFailed=true;throw e} if(sending){ trace.httpStatus=response.status const accepted=await response.clone().json().catch(()=>({ok:false})) trace.providerAccepted=accepted.ok===true if(accepted.ok!==true){report.providerRejectionCodes??=[];report.providerRejectionCodes.push(accepted.error_code??null);return response} report.providerAcceptanceIds.push(accepted.result.message_id) if(suppressReceipt){trace.receiptSuppressed=true;suppressReceipt=false;throw Error('Own TEST197 receipt suppressed after confirmed provider acceptance')} } return response } assert.equal(await enqueueTelegramOutbox({id:id2,dedupeKey:id2,payload:payload(2)}),true) let outcome;let overview; if(!resume){ suppressReceipt=true;outcome=await dispatchPendingOutbox(1);assert.equal(outcome.sent,0);assert.equal(outcome.held,1) overview=await getAlertsOutboxOverview();assert.equal(overview.counts.unknown,1);assert.equal(overview.items[0].id,id2) const before=report.providerSendCalls;await dispatchPendingOutbox(1);assert.equal(report.providerSendCalls,before) cases.push({name:'controlled lost receipt',providerAccepted:true,status:'unknown',automaticResend:false,physicalDisconnect:false}) }else{await pool.query("UPDATE alert_outbox SET status='unknown',last_error='Own resume fixture: controlled unknown, no additional provider send' WHERE id=$1",[id2]);cases.push({name:'resume fixture',status:'unknown',providerSend:false,priorLiveEvidence:'telegram197-partial.json'})} const retry='/api/alerts/outbox/'+id2+'/retry',ack={expectedVersion:0,acknowledgedDuplicateRisk:true} const reader=await app.inject({method:'POST',url:retry,headers:{authorization:'Bearer '+readerLogin.data.access_token},payload:ack});assert.equal(reader.statusCode,403) const missing=await app.inject({method:'POST',url:retry,headers:{authorization:'Bearer '+adminToken},payload:{expectedVersion:0}});assert.equal(missing.statusCode,400) const approved=await app.inject({method:'POST',url:retry,headers:{authorization:'Bearer '+adminToken},payload:ack});assert.equal(approved.statusCode,200);assert.equal(approved.json().resolutionVersion,1) const stale=await app.inject({method:'POST',url:retry,headers:{authorization:'Bearer '+adminToken},payload:ack});assert.equal(stale.statusCode,409) outcome=await dispatchPendingOutbox(1);report.retryDiagnostic={outcome,rows:(await pool.query("SELECT status,retry_count,resolution_version,next_attempt_at,last_error FROM alert_outbox WHERE id=$1",[id2])).rows};assert.equal(outcome.sent,1) overview=await getAlertsOutboxOverview();assert.equal(overview.counts.sent,resume?1:2);assert.equal(overview.counts.unknown,0) assert.equal(Number((await pool.query('SELECT count(*) AS n FROM alert_history WHERE sent_ok=true')).rows[0].n),resume?1:2) cases.push({name:'explicit retry with real Portal JWT',reader:403,missingAck:400,admin:200,staleVersion:409,resolutionVersion:1,intentionalDuplicate:true,sentHistoryRows:resume?1:2}) assert.equal(report.providerSendCalls,resume?1:3);assert.equal(report.providerAcceptanceIds.length,resume?1:3) report.passed=true }catch(e){ fs.writeFileSync(root+'/private-failure.log',String(e.stack??e),{mode:0o600});report.errorName=e.name }finally{ globalThis.fetch=originalFetch if(app)await app.close() for(const id of ownedUsers){const deleted=await portalCall('/api/v1/admin/users/'+id,'DELETE',undefined,adminToken);assert.equal(deleted.status,204)} report.ownPortalUsersRemoved=true await closePool() fs.writeFileSync(root+'/result.json',JSON.stringify(report,null,2));console.log(JSON.stringify({passed:report.passed,providerSendCalls:report.providerSendCalls,errorName:report.errorName,ownPortalUsersRemoved:report.ownPortalUsersRemoved})) } if(!report.passed)process.exitCode=1