'use strict'; const { test } = require('node:test'), assert = require('node:assert/strict'), vm = require('node:vm'), fs = require('node:fs'), path = require('node:path'); test('cloud transport initializes once, preserves subpath/auth, caps timeout and unwraps gateway envelopes', async () => { const config = { transport: 'cloud', baseUrl: 'https://skin.example.test', basePath: '/state', cloudAppId: 'cloud-id', cloudEnv: 'online' }; let initialized = 0, options; const cloud = { init: async ({ env }) => { assert.equal(env, 'online'); initialized++; return true; }, application: { httpRequest: async input => { options = input; return { ok: true, http_status: 200 }; } } }; const module = { exports: {} }; vm.runInNewContext(fs.readFileSync(path.join(__dirname, '../utils/transport.js'), 'utf8'), { module, Promise, setTimeout, clearTimeout, require(name) { return name === '../config' ? config : { default: cloud }; } }); const transport = module.exports; await transport.request('/api/taobao/me', {}, 'token'); await transport.request('/api/taobao/reports', {}, 'token'); assert.equal(initialized, 1); assert.equal(options.path, '/state/api/taobao/reports'); assert.equal(options.headers.Authorization, 'Bearer token'); assert.equal(options.exts.timeout, 10000); assert.equal(options.exts.cloudAppId, 'cloud-id'); assert.equal(options.exts.domain, config.baseUrl); assert.equal(transport.unwrap({ success: true, data: { ok: true } }).ok, true); assert.throws(() => transport.unwrap({ success: false, errorMessage: 'gateway rejected' }), /gateway rejected/); config.transport = 'direct'; await assert.rejects(transport.request('/api/taobao/me', {}), /仅用于本机/); }); test('聚石塔 tower transport omits domain and does not require HTTPS', async () => { const config = { transport: 'cloud', cloudType: 'tower', baseUrl: 'http://127.0.0.1:8080', cloudAppId: '', cloudEnv: 'test', basePath: '' }; let options; const cloud = { init: async ({ env }) => { assert.equal(env, 'test'); return true; }, application: { httpRequest: async input => { options = input; return { success: true, data: { ok: true } }; } } }; const module = { exports: {} }; vm.runInNewContext(fs.readFileSync(path.join(__dirname, '../utils/transport.js'), 'utf8'), { module, Promise, setTimeout, clearTimeout, require(name) { return name === '../config' ? config : { default: cloud }; } }); const transport = module.exports; const data = await transport.request('/api/taobao/ping', {}); assert.equal(data.ok, true); assert.equal(options.path, '/api/taobao/ping'); assert.equal(options.exts.domain, undefined); assert.equal(options.exts.cloudAppId, undefined); }); const flush = () => new Promise(resolve => setImmediate(resolve)); function timeoutTransport(cloud, config = {}, platform = {}) { const timers = [], module = { exports: {} }; vm.runInNewContext(fs.readFileSync(path.join(__dirname, '../utils/transport.js'), 'utf8'), { module, Promise, my: platform, setTimeout(fn, ms) { const timer = { fn, ms, active: true }; timers.push(timer); return timer; }, clearTimeout(timer) { timer.active = false; }, require(name) { return name === '../config' ? { transport: 'cloud', cloudType: 'tower', basePath: '', cloudEnv: 'test', ...config } : { default: cloud }; } }); return { transport: module.exports, timers, expire(ms) { const timer = timers.find(value => value.active && value.ms === ms); assert(timer, 'expected active timeout ' + ms); timer.active = false; timer.fn(); } }; } test('a hanging cloud initialization times out, permits retry and ignores its late result', async () => { let finishOld, initialized = 0, requests = 0; const f = timeoutTransport({ init: () => ++initialized === 1 ? new Promise(resolve => { finishOld = resolve; }) : Promise.resolve(true), application: { httpRequest: async () => { requests++; return { ok: true }; } } }); const pending = f.transport.request('/api/taobao/analysis', {}); const rejected = assert.rejects(pending, error => error.code === 'CLOUD_INIT_TIMEOUT'); await flush(); f.expire(15000); await rejected; assert.equal(requests, 0); assert.equal((await f.transport.request('/api/taobao/analysis', {})).ok, true); finishOld(false); await flush(); assert.equal((await f.transport.request('/api/taobao/analysis', {})).ok, true); assert.equal(initialized, 2); assert.equal(requests, 2); assert(!f.timers.some(timer => timer.active)); }); test('a hanging cloud request is bounded independently of SDK timeout and does not poison later polling', async () => { let finishOld, requests = 0, initialized = 0; const f = timeoutTransport({ init: async () => { initialized++; return true; }, application: { httpRequest: () => ++requests === 1 ? new Promise(resolve => { finishOld = resolve; }) : Promise.resolve({ ok: true, status: 'complete' }) } }); const pending = f.transport.request('/api/taobao/analysis', { job: 'original-job' }); const rejected = assert.rejects(pending, error => error.code === 'REQUEST_TIMEOUT'); await flush(); f.expire(12000); await rejected; const result = await f.transport.request('/api/taobao/analysis', { job: 'original-job' }); assert.equal(result.status, 'complete'); assert.equal(initialized, 1); finishOld({ ok: true, status: 'obsolete' }); await flush(); assert.equal(result.status, 'complete'); assert(!f.timers.some(timer => timer.active)); }); test('a direct request with no native callback times out and a late callback cannot complete it again', async () => { const calls = []; const f = timeoutTransport(null, { transport: 'direct', baseUrl: 'http://127.0.0.1:18180' }, { request(options) { calls.push(options); } }); const pending = f.transport.request('/api/taobao/analysis', {}); const rejected = assert.rejects(pending, error => error.code === 'REQUEST_TIMEOUT'); await flush(); f.expire(12000); await rejected; const retry = f.transport.request('/api/taobao/analysis', {}); await flush(); calls[0].success({ status: 200, data: { ok: true, obsolete: true } }); calls[1].success({ status: 200, data: { ok: true, current: true } }); assert.equal((await retry).current, true); assert(!f.timers.some(timer => timer.active)); });