Files
dsh/upstream/container/platform/test/proxy-test-manager.test.mjs

142 lines
6.5 KiB
JavaScript

import assert from 'node:assert/strict'
import { mkdtemp, readFile, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import test from 'node:test'
import { defaultProxyConfiguration, validateProxyConfiguration } from '../../control-plane/services/outbound-proxy/lib/contracts.mjs'
import { ProxyTestManager, proxyTestInternals } from '../../control-plane/services/outbound-proxy/lib/test-manager.mjs'
function current() {
const validated = validateProxyConfiguration(defaultProxyConfiguration())
return Object.freeze({ revision: 'current-revision', ...validated })
}
function candidate(overrides = {}) {
const defaults = defaultProxyConfiguration()
return {
...defaults,
...overrides,
proxy: { ...defaults.proxy, ...overrides.proxy },
scopes: { ...defaults.scopes, ...overrides.scopes },
environment: { ...defaults.environment, ...overrides.environment },
modelApi: { ...defaults.modelApi, providers: {}, ...overrides.modelApi },
noProxy: { user: [], ...overrides.noProxy },
bypass: { additional: [], ...overrides.bypass },
}
}
async function terminal(manager, taskId) {
for (let attempt = 0; attempt < 1_000; attempt += 1) {
const state = manager.get(taskId)
if (state.status !== 'running') return state
await new Promise(resolve => setTimeout(resolve, 2))
}
throw new Error('proxy test did not reach a terminal state')
}
test('proxy test requires and exercises the candidate proxy without changing current configuration', async () => {
const root = await mkdtemp(join(tmpdir(), 'dsh-proxy-test-candidate-'))
const manager = new ProxyTestManager({
statePath: join(root, 'tasks.json'),
resolve: async host => {
if (host === 'proxy.invalid') throw Object.assign(new Error('proxy host was not found'), { code: 'ENOTFOUND' })
return [{ address: '127.0.0.1', family: 4 }]
},
targets: [{ id: 'unreachable', host: 'target.invalid', port: 443, path: '/' }],
phaseTimeoutMs: 500,
})
const state = current()
await assert.rejects(
manager.start({ baseRevision: state.revision, value: candidate() }, state),
error => error.code === 'INVALID_CONFIG' && /requires a host and port/.test(error.message),
)
const started = await manager.start({
baseRevision: state.revision,
value: candidate({ proxy: { host: 'proxy.invalid', port: 8080 } }),
}, state)
const completed = await terminal(manager, started.taskId)
assert.equal(completed.status, 'failed')
assert.equal(completed.mode, 'proxy')
assert.equal(completed.stages.find(stage => stage.stage === 'proxy-address').status, 'failed')
assert.deepEqual(completed.stages.slice(1).map(stage => stage.status), ['skipped', 'skipped', 'skipped', 'skipped'])
assert.equal(completed.stages.some(stage => stage.stage === 'target-dns'), false)
assert.equal(completed.stages.find(stage => stage.stage === 'target-http').status, 'skipped')
assert.equal(state.configuration.enabled, false)
})
test('proxy tests list target DNS only when SOCKS5 resolves names locally', () => {
const remote = candidate({ proxy: { protocol: 'socks5', remoteDns: true } })
const local = candidate({ proxy: { protocol: 'socks5', remoteDns: false } })
assert.equal(proxyTestInternals.stagesFor(remote).includes('target-dns'), false)
assert.equal(proxyTestInternals.stagesFor(local).includes('target-dns'), true)
})
test('proxy test details carry locale-independent result data', () => {
assert.deepEqual(
proxyTestInternals.stageDetail(
'resolved 2 proxy address record(s)',
'proxy-addresses-resolved',
{ count: 2 },
),
{
detail: 'resolved 2 proxy address record(s)',
detailCode: 'proxy-addresses-resolved',
detailData: { count: 2 },
},
)
})
test('proxy tests allow one in-memory candidate, persist no credentials, and cancel safely', async () => {
const root = await mkdtemp(join(tmpdir(), 'dsh-proxy-test-cancel-'))
const statePath = join(root, 'tasks.json')
const never = () => new Promise(() => {})
const manager = new ProxyTestManager({
statePath,
resolve: never,
targets: [{ id: 'blocked', host: 'blocked.example', port: 443, path: '/' }],
})
const state = current()
const value = candidate({
enabled: true,
proxy: { host: 'proxy.example', port: 1080, username: 'alice', password: 'never-persist-this' },
scopes: { updates: true },
})
const started = await manager.start({ baseRevision: state.revision, value }, state)
await assert.rejects(manager.start({ baseRevision: state.revision, value, unexpected: true }, state), error => (
error.code === 'INVALID_CONFIG' && error.stage === 'test'
))
await assert.rejects(manager.start({ baseRevision: state.revision, value }, state), error => error.code === 'PROXY_TEST_BUSY')
assert.equal((await readFile(statePath, 'utf8')).includes('never-persist-this'), false)
await manager.cancel(started.taskId)
const cancelled = await terminal(manager, started.taskId)
assert.equal(cancelled.status, 'cancelled')
assert.equal(cancelled.error.errorCode, 'REQUEST_CANCELLED')
assert.equal(cancelled.stages.find(stage => stage.stage === 'proxy-address').status, 'cancelled')
assert.equal(cancelled.stages.find(stage => stage.stage === 'proxy-address').errorCode, 'REQUEST_CANCELLED')
assert.equal((await readFile(statePath, 'utf8')).includes('alice'), false)
})
test('proxy test reload marks an interrupted credential-free task failed', async () => {
const root = await mkdtemp(join(tmpdir(), 'dsh-proxy-test-reload-'))
const statePath = join(root, 'tasks.json')
const taskId = '123e4567-e89b-42d3-a456-426614174000'
const timestamp = '2026-08-25T00:00:00.000Z'
await writeFile(statePath, `${JSON.stringify({
schema: 1,
tasks: [{
schema: 1, taskId, status: 'running', baseRevision: 'old-revision',
candidateHash: `sha256:${'a'.repeat(64)}`, mode: 'proxy', currentStage: 'proxy-connect',
stages: proxyTestInternals.STAGES.map(stage => ({
stage, status: stage === 'proxy-connect' ? 'running' : 'pending', durationMs: null, errorCode: null, detail: null,
})),
error: null, createdAt: timestamp, updatedAt: timestamp,
}],
})}\n`)
const manager = new ProxyTestManager({ statePath, now: () => new Date('2026-08-25T00:00:01.000Z') })
await manager.initialize(async path => JSON.parse(await readFile(path, 'utf8')))
const restored = manager.get(taskId)
assert.equal(restored.status, 'failed')
assert.equal(restored.error.errorCode, 'PROXY_TEST_INTERRUPTED')
assert.equal(restored.stages.every(stage => !['running', 'pending'].includes(stage.status)), true)
})