diff --git a/static/js/app.js b/static/js/app.js index 4e1132b..54050ae 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -1306,7 +1306,7 @@ function connectBatchWebSocket(batchId) { if (shouldPoll && currentBatch) { console.log('切换到轮询模式'); - startOutlookBatchPolling(currentBatch.batch_id); + startCurrentBatchPolling(currentBatch.batch_id); } }; @@ -1314,12 +1314,12 @@ function connectBatchWebSocket(batchId) { console.error('批量任务 WebSocket 错误:', error); stopBatchWebSocketHeartbeat(); // 切换到轮询 - startOutlookBatchPolling(batchId); + startCurrentBatchPolling(batchId); }; } catch (error) { console.error('批量任务 WebSocket 连接失败:', error); - startOutlookBatchPolling(batchId); + startCurrentBatchPolling(batchId); } } @@ -1332,6 +1332,15 @@ function disconnectBatchWebSocket() { } } +function startCurrentBatchPolling(batchId) { + if (isOutlookBatchMode) { + startOutlookBatchPolling(batchId); + return; + } + + startBatchPolling(batchId); +} + // 开始批量任务心跳 function startBatchWebSocketHeartbeat() { stopBatchWebSocketHeartbeat(); diff --git a/tests/test_batch_websocket_fallback.cjs b/tests/test_batch_websocket_fallback.cjs new file mode 100644 index 0000000..f6bd068 --- /dev/null +++ b/tests/test_batch_websocket_fallback.cjs @@ -0,0 +1,133 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const vm = require('node:vm'); + +const APP_JS_PATH = '/Users/zhoukailian/.config/superpowers/worktrees/codex-manager/repro-batch-monitor/static/js/app.js'; + +function createElementStub() { + return { + style: {}, + dataset: {}, + value: '', + checked: false, + disabled: false, + innerHTML: '', + textContent: '', + className: '', + appendChild() {}, + addEventListener() {}, + removeEventListener() {}, + querySelector() { + return createElementStub(); + }, + querySelectorAll() { + return []; + }, + closest() { + return null; + }, + }; +} + +function createSandbox() { + const sandbox = { + console, + setTimeout, + clearTimeout, + setInterval: () => 1, + clearInterval: () => {}, + Event: class Event { + constructor(type) { + this.type = type; + } + }, + document: { + getElementById() { + return createElementStub(); + }, + createElement() { + return createElementStub(); + }, + addEventListener() {}, + querySelector() { + return createElementStub(); + }, + querySelectorAll() { + return []; + }, + }, + sessionStorage: { + getItem() { + return null; + }, + setItem() {}, + removeItem() {}, + }, + toast: { + info() {}, + success() {}, + warning() {}, + error() {}, + }, + api: { + get() { + throw new Error('api.get should not be called in this test'); + }, + post() { + throw new Error('api.post should not be called in this test'); + }, + }, + window: null, + WebSocket: null, + }; + + sandbox.window = sandbox; + sandbox.window.location = { protocol: 'http:', host: '127.0.0.1:8003' }; + + vm.createContext(sandbox); + vm.runInContext(fs.readFileSync(APP_JS_PATH, 'utf8'), sandbox, { filename: 'app.js' }); + + return sandbox; +} + +async function runFallback(mode) { + const sandbox = createSandbox(); + + vm.runInContext( + ` + var __calls = []; + currentBatch = { batch_id: 'test-batch' }; + isOutlookBatchMode = ${mode === 'outlook' ? 'true' : 'false'}; + batchCompleted = false; + batchFinalStatus = null; + startOutlookBatchPolling = function(batchId) { __calls.push(['outlook', batchId]); }; + startBatchPolling = function(batchId) { __calls.push(['batch', batchId]); }; + WebSocket = function(url) { + this.url = url; + this.readyState = 0; + setTimeout(() => { + if (this.onerror) { + this.onerror({ type: 'error' }); + } + }, 0); + }; + WebSocket.OPEN = 1; + connectBatchWebSocket('test-batch'); + `, + sandbox, + ); + + await new Promise((resolve) => setTimeout(resolve, 20)); + return JSON.parse(vm.runInContext('JSON.stringify(__calls)', sandbox)); +} + +test('normal batch websocket fallback uses standard batch polling', async () => { + const calls = await runFallback('batch'); + assert.deepEqual(calls, [['batch', 'test-batch']]); +}); + +test('outlook batch websocket fallback uses outlook polling', async () => { + const calls = await runFallback('outlook'); + assert.deepEqual(calls, [['outlook', 'test-batch']]); +});