1144 lines
70 KiB
JavaScript
1144 lines
70 KiB
JavaScript
import assert from 'node:assert/strict'
|
||
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
||
import { createServer } from 'node:http'
|
||
import { tmpdir } from 'node:os'
|
||
import { join } from 'node:path'
|
||
import test from 'node:test'
|
||
import { buildSystemPluginClient } from '../tools/build-system-plugin-client.mjs'
|
||
import { apply as applyPlatformManagement, inject, managedNetworkInternals } from '../../environment/resources/plugins/platform-management/package/lib/index.js'
|
||
import {
|
||
installProviderRouting, providerRoutingInternals,
|
||
} from '../../environment/resources/plugins/platform-management/package/lib/provider-routing.js'
|
||
|
||
const root = new URL('../../environment/resources/plugins/platform-management/package/', import.meta.url)
|
||
|
||
test('Platform Management classifies official DSH Shell subprocesses as Agent network traffic', () => {
|
||
assert.deepEqual(inject, ['subprocess'])
|
||
const environment = {
|
||
DSH_PLATFORM_AGENT_PROXY_URL: 'http://127.0.0.1:17895',
|
||
NO_PROXY: 'localhost,.example.com',
|
||
ALL_PROXY: 'http://127.0.0.1:17898',
|
||
}
|
||
const ordinary = { env: { FEATURE: 'ordinary' } }
|
||
assert.equal(managedNetworkInternals.managedSubprocessSpec(ordinary, environment), ordinary)
|
||
assert.equal(managedNetworkInternals.managedSubprocessSpec({ env: { DSH_SHELL: '1' } }, {}).env.DSH_SHELL, '1')
|
||
const managed = managedNetworkInternals.managedSubprocessSpec({
|
||
env: { DSH_SHELL: '1', FEATURE: 'kept', HTTP_PROXY: 'http://127.0.0.1:17898' },
|
||
}, environment)
|
||
assert.deepEqual(managed.env, {
|
||
DSH_SHELL: '1',
|
||
FEATURE: 'kept',
|
||
HTTP_PROXY: 'http://127.0.0.1:17895',
|
||
HTTPS_PROXY: 'http://127.0.0.1:17895',
|
||
http_proxy: 'http://127.0.0.1:17895',
|
||
https_proxy: 'http://127.0.0.1:17895',
|
||
NO_PROXY: 'localhost,.example.com',
|
||
no_proxy: 'localhost,.example.com',
|
||
ALL_PROXY: 'http://127.0.0.1:17895',
|
||
all_proxy: 'http://127.0.0.1:17895',
|
||
})
|
||
|
||
const observed = []
|
||
const subprocess = {
|
||
spawn(spec) { observed.push(['spawn', spec]); return 'spawned' },
|
||
spawnTerminal(spec) { observed.push(['terminal', spec]); return Promise.resolve('terminal') },
|
||
}
|
||
let cleanup
|
||
applyPlatformManagement({
|
||
subprocess,
|
||
on() { return () => {} },
|
||
effect(factory) { cleanup = factory() },
|
||
})
|
||
const previous = process.env.DSH_PLATFORM_AGENT_PROXY_URL
|
||
process.env.DSH_PLATFORM_AGENT_PROXY_URL = 'http://127.0.0.1:17895'
|
||
try {
|
||
assert.equal(subprocess.spawn({ env: { DSH_SHELL: '1' } }), 'spawned')
|
||
assert.equal(observed[0][1].env.HTTP_PROXY, 'http://127.0.0.1:17895')
|
||
cleanup()
|
||
subprocess.spawn({ env: { DSH_SHELL: '1' } })
|
||
assert.equal(observed[1][1].env.HTTP_PROXY, undefined)
|
||
} finally {
|
||
if (previous === undefined) delete process.env.DSH_PLATFORM_AGENT_PROXY_URL
|
||
else process.env.DSH_PLATFORM_AGENT_PROXY_URL = previous
|
||
}
|
||
})
|
||
|
||
test('Platform Management gives disabled Agent and Provider routes a real direct path', async () => {
|
||
const root = await mkdtemp(join(tmpdir(), 'dsh-direct-routing-'))
|
||
const routingStatePath = join(root, 'routing.json')
|
||
await writeFile(routingStatePath, JSON.stringify({
|
||
schema: 1,
|
||
revision: 'direct-revision',
|
||
enabled: true,
|
||
scopes: { dshCore: false, dshPlugins: false, agentNetwork: false },
|
||
modelApi: {
|
||
default: { proxyEnabled: false },
|
||
providers: {},
|
||
},
|
||
}))
|
||
try {
|
||
const spec = managedNetworkInternals.managedSubprocessSpec({
|
||
env: { DSH_SHELL: '1' },
|
||
}, {
|
||
HTTP_PROXY: 'http://127.0.0.1:17898',
|
||
HTTPS_PROXY: 'http://127.0.0.1:17898',
|
||
DSH_PLATFORM_AGENT_PROXY_URL: 'http://127.0.0.1:17895',
|
||
}, routingStatePath)
|
||
assert.equal(spec.env.HTTP_PROXY, undefined)
|
||
assert.equal(spec.env.HTTPS_PROXY, undefined)
|
||
assert.equal(spec.env.ALL_PROXY, undefined)
|
||
|
||
const listeners = new Map()
|
||
let directClosed = false
|
||
let agentClosed = false
|
||
let sharedClosed = false
|
||
const directDispatcher = { close: async () => { directClosed = true } }
|
||
const agentDispatcher = { close: async () => { agentClosed = true } }
|
||
const sharedDispatcher = { close: async () => { sharedClosed = true } }
|
||
const previousFetch = globalThis.fetch
|
||
const observed = []
|
||
globalThis.fetch = async () => { throw new Error('ambient proxy fetch must not be used') }
|
||
const remove = installProviderRouting({
|
||
on(event, callback) { listeners.set(event, callback); return () => { listeners.delete(event) } },
|
||
}, {
|
||
socketPath: join(root, 'missing.sock'),
|
||
routingStatePath,
|
||
createDirectDispatcher: () => directDispatcher,
|
||
createAgentDispatcher: () => agentDispatcher,
|
||
createSharedDispatcher: () => sharedDispatcher,
|
||
routedFetch: async (_input, init) => {
|
||
observed.push(init.dispatcher)
|
||
return { ok: true }
|
||
},
|
||
})
|
||
try {
|
||
await fetch('https://ordinary-dsh.invalid')
|
||
assert.deepEqual(observed, [directDispatcher])
|
||
const stream = listeners.get('llm/stream')({ provider: 'deepseek-official' }, () => (async function* () {
|
||
await fetch('https://provider.invalid')
|
||
yield 'direct'
|
||
})())
|
||
assert.deepEqual(await stream.next(), { value: 'direct', done: false })
|
||
assert.deepEqual(await stream.next(), { value: undefined, done: true })
|
||
assert.deepEqual(observed, [directDispatcher, directDispatcher])
|
||
|
||
await listeners.get('tools/execute')({ name: 'web_fetch' }, async () => {
|
||
await fetch('https://agent-direct.invalid')
|
||
})
|
||
assert.equal(observed.at(-1), directDispatcher)
|
||
|
||
await writeFile(routingStatePath, JSON.stringify({
|
||
schema: 1,
|
||
revision: 'proxy-revision',
|
||
enabled: true,
|
||
scopes: { dshCore: false, dshPlugins: false, agentNetwork: true },
|
||
modelApi: {
|
||
default: { proxyEnabled: true },
|
||
providers: {},
|
||
},
|
||
}))
|
||
await listeners.get('tools/execute')({ name: 'web_fetch' }, async () => {
|
||
await fetch('https://agent-proxied.invalid')
|
||
})
|
||
assert.equal(observed.at(-1), agentDispatcher)
|
||
|
||
const protectedStream = listeners.get('llm/stream')({ provider: 'deepseek-official' }, () => (async function* () {
|
||
yield 'must-not-run'
|
||
})())
|
||
await assert.rejects(protectedStream.next(), /ENOENT|ECONNREFUSED/)
|
||
|
||
const sharedDirect = listeners.get('llm/stream')({
|
||
provider: 'shared-provider', supportsIndependentRouting: false,
|
||
}, () => (async function* () {
|
||
await fetch('https://shared-direct.invalid')
|
||
yield 'shared-direct'
|
||
})())
|
||
assert.deepEqual(await sharedDirect.next(), { value: 'shared-direct', done: false })
|
||
assert.equal(observed.at(-1), directDispatcher)
|
||
|
||
await writeFile(routingStatePath, JSON.stringify({
|
||
schema: 1,
|
||
revision: 'shared-revision',
|
||
enabled: true,
|
||
scopes: { dshCore: false, dshPlugins: true, agentNetwork: false },
|
||
modelApi: {
|
||
default: { proxyEnabled: false },
|
||
providers: { 'shared-provider': { proxyEnabled: true } },
|
||
},
|
||
}))
|
||
const sharedProxied = listeners.get('llm/stream')({
|
||
provider: 'shared-provider', routingCapability: 'shared-dsh',
|
||
}, () => (async function* () {
|
||
await fetch('https://shared-proxied.invalid')
|
||
yield 'shared-proxied'
|
||
})())
|
||
assert.deepEqual(await sharedProxied.next(), { value: 'shared-proxied', done: false })
|
||
assert.equal(observed.at(-1), sharedDispatcher)
|
||
|
||
await writeFile(routingStatePath, JSON.stringify({
|
||
schema: 1,
|
||
revision: 'shared-direct-revision',
|
||
enabled: true,
|
||
scopes: { dshCore: true, dshPlugins: true, agentNetwork: false },
|
||
modelApi: {
|
||
default: { proxyEnabled: false },
|
||
providers: { 'shared-provider': { proxyEnabled: false } },
|
||
},
|
||
}))
|
||
const sharedDisabled = listeners.get('llm/stream')({
|
||
provider: 'shared-provider', supportsIndependentRouting: false,
|
||
}, () => (async function* () {
|
||
await fetch('https://shared-disabled.invalid')
|
||
yield 'shared-disabled'
|
||
})())
|
||
assert.deepEqual(await sharedDisabled.next(), { value: 'shared-disabled', done: false })
|
||
assert.equal(observed.at(-1), directDispatcher)
|
||
} finally {
|
||
remove()
|
||
globalThis.fetch = previousFetch
|
||
}
|
||
await new Promise(resolve => setImmediate(resolve))
|
||
assert.equal(directClosed, true)
|
||
assert.equal(agentClosed, true)
|
||
assert.equal(sharedClosed, true)
|
||
} finally {
|
||
await rm(root, { recursive: true, force: true })
|
||
}
|
||
})
|
||
|
||
test('Platform Management retries one Provider policy revision conflict and no more', async () => {
|
||
const root = await mkdtemp(join(tmpdir(), 'dsh-provider-revision-retry-'))
|
||
const socketPath = join(root, 'proxy.sock')
|
||
const requests = []
|
||
let revision = 'revision-one'
|
||
let conflicts = 0
|
||
const server = createServer(async (request, response) => {
|
||
const chunks = []
|
||
for await (const chunk of request) chunks.push(chunk)
|
||
const body = chunks.length === 0 ? undefined : JSON.parse(Buffer.concat(chunks).toString('utf8'))
|
||
requests.push([request.method, request.url, body])
|
||
response.setHeader('content-type', 'application/json')
|
||
if (request.method === 'GET') {
|
||
response.end(JSON.stringify({ revision }))
|
||
return
|
||
}
|
||
conflicts += 1
|
||
revision = `revision-${conflicts + 1}`
|
||
response.statusCode = 409
|
||
response.end(JSON.stringify({ error: { code: 'REVISION_CONFLICT', message: 'changed' } }))
|
||
})
|
||
await new Promise((resolve, reject) => {
|
||
server.once('error', reject)
|
||
server.listen(socketPath, resolve)
|
||
})
|
||
try {
|
||
await assert.rejects(
|
||
providerRoutingInternals.issueProviderHandle('deepseek-official', socketPath),
|
||
error => error.code === 'REVISION_CONFLICT',
|
||
)
|
||
assert.deepEqual(requests, [
|
||
['GET', '/v1/configuration', undefined],
|
||
['POST', '/v1/provider-handles', { providerId: 'deepseek-official', policyRevision: 'revision-one' }],
|
||
['GET', '/v1/configuration', undefined],
|
||
['POST', '/v1/provider-handles', { providerId: 'deepseek-official', policyRevision: 'revision-2' }],
|
||
])
|
||
} finally {
|
||
await new Promise(resolve => server.close(resolve))
|
||
await rm(root, { recursive: true, force: true })
|
||
}
|
||
})
|
||
|
||
test('Platform Management removes stale shared DSH proxy variables when routing becomes direct', () => {
|
||
const environment = {
|
||
HTTP_PROXY: 'http://127.0.0.1:17898',
|
||
HTTPS_PROXY: 'http://127.0.0.1:17898',
|
||
ALL_PROXY: 'http://127.0.0.1:17898',
|
||
NO_PROXY: 'stale.invalid',
|
||
}
|
||
managedNetworkInternals.synchronizeSharedDshEnvironment(environment, {
|
||
schema: 1,
|
||
enabled: false,
|
||
scopes: { dshCore: true, dshPlugins: true },
|
||
environment: { allProxy: 'scope-proxy' },
|
||
noProxy: { system: ['localhost', '127.0.0.1'], user: ['.example.com'] },
|
||
})
|
||
assert.equal(environment.HTTP_PROXY, undefined)
|
||
assert.equal(environment.HTTPS_PROXY, undefined)
|
||
assert.equal(environment.ALL_PROXY, undefined)
|
||
assert.equal(environment.NO_PROXY, 'localhost,127.0.0.1,.example.com')
|
||
assert.equal(environment.no_proxy, environment.NO_PROXY)
|
||
|
||
managedNetworkInternals.synchronizeSharedDshEnvironment(environment, {
|
||
schema: 1,
|
||
enabled: true,
|
||
scopes: { dshCore: false, dshPlugins: true },
|
||
environment: { allProxy: 'scope-proxy' },
|
||
noProxy: { system: ['localhost'], user: [] },
|
||
})
|
||
assert.equal(environment.HTTP_PROXY, 'http://127.0.0.1:17898')
|
||
assert.equal(environment.HTTPS_PROXY, 'http://127.0.0.1:17898')
|
||
assert.equal(environment.ALL_PROXY, 'http://127.0.0.1:17898')
|
||
})
|
||
|
||
test('Platform Management routes every valid llm/stream Provider through opaque handles', async () => {
|
||
const root = await mkdtemp(join(tmpdir(), 'dsh-provider-routing-'))
|
||
const socketPath = join(root, 'proxy.sock')
|
||
const requests = []
|
||
const server = createServer(async (request, response) => {
|
||
const chunks = []
|
||
for await (const chunk of request) chunks.push(chunk)
|
||
const body = chunks.length === 0 ? undefined : JSON.parse(Buffer.concat(chunks).toString('utf8'))
|
||
requests.push([request.method, request.url, body])
|
||
response.setHeader('content-type', 'application/json')
|
||
if (request.method === 'GET') response.end(JSON.stringify({ revision: 'revision-one' }))
|
||
else response.end(JSON.stringify({ handle: 'a'.repeat(43), policyRevision: 'revision-one' }))
|
||
})
|
||
await new Promise((resolve, reject) => {
|
||
server.once('error', reject)
|
||
server.listen(socketPath, resolve)
|
||
})
|
||
|
||
const previousFetch = globalThis.fetch
|
||
const observed = []
|
||
const dispatchers = []
|
||
globalThis.fetch = async (_input, init) => {
|
||
observed.push(init?.dispatcher ?? null)
|
||
return { ok: true }
|
||
}
|
||
const listeners = new Map()
|
||
const remove = installProviderRouting({
|
||
on(event, callback, options) {
|
||
assert.deepEqual(options, { global: true, prepend: true })
|
||
listeners.set(event, callback)
|
||
return () => { listeners.delete(event) }
|
||
},
|
||
}, {
|
||
socketPath,
|
||
routedFetch: async (_input, init) => {
|
||
observed.push(init?.dispatcher ?? null)
|
||
return { ok: true }
|
||
},
|
||
createDispatcher(options) {
|
||
const dispatcher = { options, closed: false, async close() { this.closed = true } }
|
||
dispatchers.push(dispatcher)
|
||
return dispatcher
|
||
},
|
||
})
|
||
|
||
try {
|
||
const routed = listeners.get('llm/stream')({ provider: 'deepseek-official' }, () => (async function* () {
|
||
await fetch('https://provider.invalid')
|
||
yield 'chunk'
|
||
})())
|
||
assert.deepEqual(await routed.next(), { value: 'chunk', done: false })
|
||
assert.deepEqual(await routed.next(), { value: undefined, done: true })
|
||
assert.equal(observed[0], dispatchers[0])
|
||
assert.deepEqual(dispatchers[0].options, {
|
||
uri: 'http://127.0.0.1:17897',
|
||
token: `DSH-Provider ${'a'.repeat(43)}`,
|
||
})
|
||
assert.equal(dispatchers[0].closed, true)
|
||
assert.deepEqual(requests, [
|
||
['GET', '/v1/configuration', undefined],
|
||
['POST', '/v1/provider-handles', { providerId: 'deepseek-official', policyRevision: 'revision-one' }],
|
||
])
|
||
|
||
const custom = listeners.get('llm/stream')({ provider: 'custom-provider' }, () => (async function* () {
|
||
await fetch('https://provider.invalid')
|
||
yield 'custom'
|
||
})())
|
||
assert.deepEqual(await custom.next(), { value: 'custom', done: false })
|
||
assert.equal(observed[1], dispatchers[1])
|
||
assert.deepEqual(requests.slice(-2), [
|
||
['GET', '/v1/configuration', undefined],
|
||
['POST', '/v1/provider-handles', { providerId: 'custom-provider', policyRevision: 'revision-one' }],
|
||
])
|
||
|
||
const invalid = listeners.get('llm/stream')({ provider: '../invalid' }, () => (async function* () {
|
||
await fetch('https://provider.invalid')
|
||
yield 'invalid'
|
||
})())
|
||
assert.deepEqual(await invalid.next(), { value: 'invalid', done: false })
|
||
assert.equal(observed[2], null)
|
||
} finally {
|
||
remove()
|
||
globalThis.fetch = previousFetch
|
||
await new Promise(resolve => server.close(resolve))
|
||
await rm(root, { recursive: true, force: true })
|
||
}
|
||
})
|
||
|
||
test('Platform Management keeps Provider fetch and dispatcher ownership aligned across scopes', async () => {
|
||
const root = await mkdtemp(join(tmpdir(), 'dsh-provider-scope-routing-'))
|
||
const socketPath = join(root, 'proxy.sock')
|
||
const server = createServer((request, response) => {
|
||
response.setHeader('content-type', 'application/json')
|
||
response.end(JSON.stringify(request.method === 'GET'
|
||
? { revision: 'scope-revision' }
|
||
: { handle: 'b'.repeat(43), policyRevision: 'scope-revision' }))
|
||
})
|
||
await new Promise((resolve, reject) => {
|
||
server.once('error', reject)
|
||
server.listen(socketPath, resolve)
|
||
})
|
||
|
||
const previousFetch = globalThis.fetch
|
||
globalThis.fetch = async () => ({ ok: true })
|
||
const listeners = []
|
||
const observed = []
|
||
const install = owner => installProviderRouting({
|
||
on(_event, listener) { listeners.push(listener); return () => {} },
|
||
}, {
|
||
socketPath,
|
||
createDispatcher: () => ({ owner, async close() {} }),
|
||
routedFetch: async (_input, init) => {
|
||
observed.push([owner, init.dispatcher.owner])
|
||
return { ok: true }
|
||
},
|
||
})
|
||
const removeFirst = install('first')
|
||
const removeSecond = install('second')
|
||
try {
|
||
const stream = listeners.at(-1)({ provider: 'deepseek-official' }, () => (async function* () {
|
||
await fetch('https://provider.invalid')
|
||
yield 'done'
|
||
})())
|
||
assert.deepEqual(await stream.next(), { value: 'done', done: false })
|
||
assert.deepEqual(observed, [['second', 'second']])
|
||
} finally {
|
||
removeSecond()
|
||
removeFirst()
|
||
globalThis.fetch = previousFetch
|
||
await new Promise(resolve => server.close(resolve))
|
||
await rm(root, { recursive: true, force: true })
|
||
}
|
||
})
|
||
|
||
test('Platform Management closes Provider dispatchers when a minimal stream is cancelled', async () => {
|
||
const root = await mkdtemp(join(tmpdir(), 'dsh-provider-cancel-'))
|
||
const socketPath = join(root, 'proxy.sock')
|
||
const server = createServer((request, response) => {
|
||
response.setHeader('content-type', 'application/json')
|
||
if (request.method === 'GET') response.end(JSON.stringify({ revision: 'revision-one' }))
|
||
else response.end(JSON.stringify({ handle: 'b'.repeat(43), policyRevision: 'revision-one' }))
|
||
})
|
||
await new Promise((resolve, reject) => {
|
||
server.once('error', reject)
|
||
server.listen(socketPath, resolve)
|
||
})
|
||
|
||
let listener
|
||
const dispatchers = []
|
||
const remove = installProviderRouting({
|
||
on(_event, callback) { listener = callback; return () => { listener = undefined } },
|
||
}, {
|
||
socketPath,
|
||
createDispatcher(options) {
|
||
const dispatcher = { options, closed: false, async close() { this.closed = true } }
|
||
dispatchers.push(dispatcher)
|
||
return dispatcher
|
||
},
|
||
})
|
||
|
||
try {
|
||
const stream = listener({ provider: 'deepseek-official' }, () => ({
|
||
[Symbol.asyncIterator]() {
|
||
return { next: async () => ({ value: 'unused', done: false }) }
|
||
},
|
||
}))
|
||
assert.deepEqual(await stream.return('cancelled'), { value: 'cancelled', done: true })
|
||
assert.equal(dispatchers[0].closed, true)
|
||
} finally {
|
||
remove()
|
||
await new Promise(resolve => server.close(resolve))
|
||
await rm(root, { recursive: true, force: true })
|
||
}
|
||
})
|
||
|
||
test('Platform Management declares a DSH web client and a platform-namespaced overlay row', async () => {
|
||
const packageJson = JSON.parse(await readFile(new URL('package.json', root)))
|
||
const patch = JSON.parse(await readFile(new URL('cordis.patch.json', root)))
|
||
assert.equal(packageJson.dsh.client.platform, 'web')
|
||
assert.ok(packageJson.dsh.client.inject.includes('@deepseek-ai/dsh-client-connection'))
|
||
assert.equal(packageJson.dsh.client.inject.includes('@deepseek-ai/dsh-client-runtime'), false)
|
||
assert.equal(packageJson.peerDependencies['@deepseek-ai/dsh-client-connection'], '*')
|
||
assert.equal(packageJson.peerDependencies['@deepseek-ai/dsh-client-runtime'], undefined)
|
||
assert.equal(packageJson.exports['./client'], './lib/client.bundle.js')
|
||
assert.equal(packageJson.exports['./package.json'], './package.json')
|
||
assert.equal(await readFile(new URL('lib/style.module.css', root), 'utf8').then(value => value.includes('@media (max-width: 640px)')), true)
|
||
assert.equal(packageJson.name, '@dsh-docker/platform-management')
|
||
assert.equal(packageJson.dshDocker.description.zh, '管理 DSH Docker 更新、运行维护、系统资源与登录会话。')
|
||
assert.equal(patch[0].insert[0].id, 'dsh-docker.platform-management.plugin')
|
||
})
|
||
|
||
test('Platform Management checked-in client bundle matches its source and DSH loader protocol', async () => {
|
||
const style = await readFile(new URL('lib/style.module.css', root), 'utf8')
|
||
const bundle = await readFile(new URL('lib/client.bundle.js', root), 'utf8')
|
||
const rebuilt = await buildSystemPluginClient({
|
||
pluginId: '@dsh-docker/platform-management',
|
||
sourcePath: new URL('lib/client.js', root),
|
||
stylePath: new URL('lib/style.module.css', root),
|
||
})
|
||
assert.equal(bundle, rebuilt)
|
||
assert.doesNotThrow(() => new Function(bundle))
|
||
assert.match(bundle, /^window\.__ModuleLoader__\.load\(/)
|
||
assert.doesNotMatch(bundle, /^import /m)
|
||
assert.doesNotMatch(style, /\[role=['"]dialog['"]\]/)
|
||
assert.doesNotMatch(style, /:has\(\.root\)/)
|
||
})
|
||
|
||
test('Platform Management is embedded in the official settings.section slot', async () => {
|
||
const source = await readFile(new URL('lib/client.js', root), 'utf8')
|
||
assert.match(source, /settings\.section/)
|
||
assert.match(source, /require\('@deepseek-ai\/dsh-client-ui-primitives'\)/)
|
||
assert.match(source, /function SelectMenu\(/)
|
||
assert.doesNotMatch(source, /h\('select'/)
|
||
assert.match(source, /settings\.dshPlatformManagement/)
|
||
assert.match(source, /id: 'dsh-platform-management'/)
|
||
assert.doesNotMatch(source, /dshPlatformUpdate|dsh-platform-update/)
|
||
const authentication = source.slice(source.indexOf('function AuthenticationSettings('), source.indexOf('\nfunction UpdateDashboard('))
|
||
assert.match(authentication, /`\$\{managementConsoleHref\}#auth-settings`/)
|
||
assert.doesNotMatch(source, /\/_dsh_platform\/auth\/management\/start/)
|
||
assert.match(source, /setManagementConsoleHref\(value\.managementConsoleHref/)
|
||
assert.match(source, /addEventListener\('focus', refreshManagementConsoleHref\)/)
|
||
assert.match(source, /fetch\(`/)
|
||
assert.match(source, /new EventSource/)
|
||
assert.match(source, /const value = await refresh\(\)[\s\S]*checkedOnOpen[\s\S]*void checkUpdates\('page-open'\)/)
|
||
const managementSource = source.slice(source.indexOf('function PlatformManagement('), source.indexOf('\nexport function apply('))
|
||
const refreshStatus = managementSource.slice(managementSource.indexOf('const refresh = useCallback'), managementSource.indexOf('const refreshInventory = useCallback'))
|
||
assert.match(refreshStatus, /const nextStatus = await request\('status'\)/)
|
||
assert.doesNotMatch(refreshStatus, /bundled-plugins|system-skills/)
|
||
const refreshInventory = managementSource.slice(managementSource.indexOf('const refreshInventory = useCallback'), managementSource.indexOf('const act = useCallback'))
|
||
assert.match(refreshInventory, /key === 'plugins'[\s\S]*path: 'bundled-plugins'[\s\S]*path: 'system-skills'/)
|
||
assert.match(refreshInventory, /loader\.apply\(await request\(loader\.path\)\)/)
|
||
assert.doesNotMatch(refreshInventory, /Promise\.all/)
|
||
assert.match(source, /activeTab === 'plugins' \|\| activeTab === 'skills'[\s\S]*refreshInventory\(activeTab\)/)
|
||
assert.doesNotMatch(source, /void refreshInventor(?:y|ies)\(\)[\s\S]*await refreshAndConnect\(\)/)
|
||
assert.match(source, /LIST_PAGE_SIZES = Object\.freeze\(\[5, 10, 20, 50\]\)/)
|
||
assert.match(source, /function usePaginatedItems\(key, items\)[\s\S]*items: items\.slice\(start, start \+ pageSize\)/)
|
||
assert.doesNotMatch(source, /function ListPagination[\s\S]*total <= LIST_PAGE_SIZES\[0\]/)
|
||
assert.match(source, /LIST_PAGE_SIZE_KEY_PREFIX = 'dsh-platform:plugin-page-size:'/)
|
||
assert.match(source, /h\(ListPagination, \{ pagination, total: filteredPlugins\.length, t \}\)/)
|
||
assert.match(source, /h\(ListPagination, \{ pagination, total: filteredSkills\.length, t \}\)/)
|
||
assert.match(source, /filteredPlugins = plugins\.filter\(plugin => matchesResourceSearch\(query, \[plugin\.id\]\)\)/)
|
||
assert.match(source, /filteredSkills = skills\.filter\(skill => matchesResourceSearch\(query, \[skill\.id\]\)\)/)
|
||
assert.doesNotMatch(source, /matchesResourceSearch\(query, \[(?:plugin|skill)\.id, (?:plugin|skill)\.description/)
|
||
assert.match(source, /function ListPagination[\s\S]*pagination\.lastPage[\s\S]*className: css\.pageJump/)
|
||
assert.match(source, /Math\.min\(pagination\.lastPage, Math\.max\(0, value - 1\)\)[\s\S]*setJumpValue\(String\(page \+ 1\)\)/)
|
||
assert.match(source, /function ExpandableDescription[\s\S]*scrollWidth > node\.clientWidth[\s\S]*aria-expanded/)
|
||
assert.match(source, /function scrollableAncestorPositions\(element\)[\s\S]*function restoreScrollableAncestors\(positions\)[\s\S]*current\.scrollTop = top[\s\S]*requestAnimationFrame/)
|
||
assert.match(source, /preserveScrollableAncestors\(event\.currentTarget, \(\) => setExpanded\(value => !value\)\)/)
|
||
assert.match(source, /h\('div', \{ className: css\.resourceHeading \}[\s\S]*h\('strong', null, `@dsh-docker\/\$\{plugin\.id\}`\)\),\s*h\(ExpandableDescription/)
|
||
assert.match(source, /h\('div', \{ className: css\.resourceHeading \}[\s\S]*h\('strong', null, skill\.id\)\),\s*h\(ExpandableDescription/)
|
||
assert.match(source, /function matchesResourceSearch/)
|
||
assert.match(source, /setConnection\('offline'\)/)
|
||
assert.doesNotMatch(source, /locked: '需要验证'|locked: 'Sign-in required'/)
|
||
assert.match(source, /className: css\.checkSpinner/)
|
||
assert.match(source, /function updateProgressModel\(update, t\)/)
|
||
assert.match(source, /ROLLBACK_PROGRESS_STAGE/)
|
||
assert.match(source, /const ROLLBACK_STAGE_ITEMS = Object\.freeze\([\s\S]*itemValidateRollback[\s\S]*itemRestoreSnapshot/)
|
||
assert.match(source, /stage\.key\.startsWith\('rollback'\) \? ROLLBACK_STAGE_ITEMS : UPDATE_STAGE_ITEMS/)
|
||
assert.match(source, /recoveryProgress \? progressModel\.title/)
|
||
assert.match(source, /function isRecoveryOperation\(operation\)[\s\S]*operation === 'return-stable'/)
|
||
assert.match(source, /returnStableProgress: '返回稳定通道'/)
|
||
assert.match(source, /const groupPrefix = isRecoveryOperation\(update\?\.operation\) \? 'rollback' : 'update'/)
|
||
assert.match(source, /function TransactionStageLogs\(\{ update, visible, t, onViewFullLog \}\)/)
|
||
assert.match(source, /'restoring-data': 3/)
|
||
assert.match(source, /const UPDATE_RECOVERY_STAGE_ITEMS = Object\.freeze\(\[[\s\S]*'itemSwitchPrevious'[\s\S]*'itemRestoreSnapshot'[\s\S]*'itemStartRuntime'[\s\S]*'itemCheckHealth'/)
|
||
assert.match(source, /phase === 'restoring-data'[\s\S]*'recovery:snapshot': 1[\s\S]*'recovery:health': 3/)
|
||
assert.match(source, /function transactionStageState\(index, currentIndex, status\)[\s\S]*status === 'success'[\s\S]*return 'completed'/)
|
||
assert.match(source, /setExpanded\(\{ \[currentStage\.key\]: update\.status !== 'success' \}\)/)
|
||
assert.match(source, /const toggleStage = key => \{[\s\S]*setExpanded\(value => \(\{ \.\.\.value, \[key\]: !\(value\[key\] \?\? key === currentStage\.key\) \}\)\)/)
|
||
assert.match(source, /aria-expanded': isExpanded[\s\S]*t\(isExpanded \? 'collapseStage' : 'expandStage'\)/)
|
||
assert.match(source, /'data-complete': progress === 100/)
|
||
assert.match(source, /progressLogGroupListPopulated/)
|
||
assert.match(source, /operation: String\(update\.operation \?\? 'update'\)[\s\S]*logs\/stream\?\$\{params\.toString\(\)\}/)
|
||
assert.match(source, /function LogViewer\(\{ active, focusTaskId, t \}\)/)
|
||
assert.match(source, /if \(focusTaskId\) setQuery\(String\(focusTaskId\)\)/)
|
||
assert.doesNotMatch(source, /progressVisible \? h\('div', \{ className: css\.statusLine \}/)
|
||
assert.match(source, /const failedDismissed = update\.status === 'failed'[\s\S]*dismissedProgressTaskId/)
|
||
assert.match(source, /!progressVisible && !failedDismissed && \(update\.error \|\| update\.outcome\)/)
|
||
assert.doesNotMatch(source, /onClick: \(\) => \{ void act\('rollback'/)
|
||
assert.doesNotMatch(source, /className: css\.progressSteps/)
|
||
assert.match(source, /className: `\$\{css\.progressStageMarker\} \$\{css\[`progressStageMarker/)
|
||
assert.match(source, /function stageItems\(stage, update, state, t\)/)
|
||
assert.match(source, /className: css\.progressStageItemMarker, 'aria-hidden': 'true'/)
|
||
assert.match(source, /className: css\.progressLogChevron, 'aria-hidden': 'true'/)
|
||
assert.match(source, /metricBytesCopied: '已复制 \{processed\} \/ \{total\}'/)
|
||
assert.match(source, /metricBytesRestored: '已恢复 \{processed\} \/ \{total\}'/)
|
||
assert.match(source, /metricProbationRemaining: '剩余观察 \{seconds\} 秒'/)
|
||
assert.match(source, /function probationRemainingSeconds\(update\)/)
|
||
assert.match(source, /setDismissedProgressTaskId\(String\(update\.taskId \?\? ''\)\)/)
|
||
assert.match(source, /t\(isExpanded \? 'collapseStage' : 'expandStage'\)\.replace\('\{count\}', String\(group\.entries\.length\)\)/)
|
||
assert.match(source, /progressDetailProbation: '候选 Runtime 正在持续接受健康检查,观察至 \{until\}。'/)
|
||
const style = await readFile(new URL('lib/style.module.css', root), 'utf8')
|
||
assert.match(style, /\.progress\[data-complete='true'\] span \{ width: 100% !important; transition: none; \}/)
|
||
assert.match(style, /\.progressStageMarker \{ --marker-size: 10px;/)
|
||
assert.match(style, /\.progressStageItemMarker \{ --marker-size: 10px;/)
|
||
assert.doesNotMatch(source, /[✓⟳✗○]/u)
|
||
assert.match(source, /changeChannel[\s\S]*void checkUpdates\('channel-change'\)/)
|
||
assert.match(source, /ctx\.locale\.getLocale\(\)/)
|
||
assert.match(source, /ctx\.on\('locale\/change'/)
|
||
assert.match(source, /dsh_locale/)
|
||
assert.match(source, /localizedError\(update\.error, t\)/)
|
||
assert.match(source, /localizedHoldReason\(hold, t\)/)
|
||
assert.match(source, /metadataUnavailable/)
|
||
assert.match(source, /remoteCheckError[\s\S]*remoteCheckSource === 'upstream'[\s\S]*upstreamCheckFailedNoResult[\s\S]*remoteCheckFailedNoResult/)
|
||
assert.match(source, /hasSupportedTarget/)
|
||
assert.match(source, /update\.updateAvailable !== true/)
|
||
assert.match(source, /className: css\.titleRow[\s\S]*className: css\.title[\s\S]*className: `\$\{css\.connection\}/)
|
||
assert.match(source, /ref: tabsRef, className: css\.tabs, role: 'tablist'/)
|
||
const tabs = source.slice(source.indexOf("h('div', { ref: tabsRef, className: css.tabs"), source.indexOf("\n\n h('div', {\n id: 'platform-tab-updates'"))
|
||
assert.doesNotMatch(tabs, /disabled:/)
|
||
assert.match(source, /function makeHorizontalTabStripScrollable\(tablist\)/)
|
||
assert.match(source, /addEventListener\('wheel', wheel, \{ passive: false \}\)/)
|
||
assert.match(source, /if \(event\.deltaX !== 0\) return/)
|
||
assert.match(source, /requestAnimationFrame\(animateWheel\)/)
|
||
assert.match(source, /addEventListener\('pointermove', pointerMove\)/)
|
||
assert.match(source, /if \(Math\.abs\(distance\) >= 4 && !dragged\) \{\s*dragged = true\s*tablist\.setPointerCapture\?\.\(pointerId\)/)
|
||
assert.doesNotMatch(source, /dragTarget = event\.target\s*tablist\.setPointerCapture/)
|
||
assert.match(source, /const displacedClickTarget = !dragged && event\.target !== dragTarget \? dragTarget : undefined[\s\S]*displacedClickTarget\?\.click\(\)/)
|
||
assert.match(source, /useEffect\(\(\) => makeHorizontalTabStripScrollable\(tabsRef\.current\), \[\]\)/)
|
||
assert.match(source, /role: 'tabpanel'/)
|
||
assert.match(source, /updatesTab: '更新管理', maintenanceTab: '运行维护', pluginsTab: '系统插件', skillsTab: '系统技能', authenticationTab: '认证设置', proxyTab: '代理设置'/)
|
||
assert.match(source, /updatesTab: 'Updates', maintenanceTab: 'Maintenance', pluginsTab: 'System plugins', skillsTab: 'System skills', authenticationTab: 'Authentication', proxyTab: 'Proxy'/)
|
||
assert.match(source, /\['maintenance', 'plugins', 'skills', 'proxy', 'updates', 'authentication'\]/)
|
||
assert.match(source, /authenticationRequest\('session-context'\)/)
|
||
assert.match(source, /authenticationRequest\('browser-logout', \{ method: 'POST', body: \{ scope \} \}\)/)
|
||
assert.match(authentication, /scope === 'all'[\s\S]*closePlatformStateEvents\(\)[\s\S]*authenticationRequest\('browser-logout'/)
|
||
assert.match(authentication, /catch \(nextError\) \{[\s\S]*openPlatformStateEvents\(\)/)
|
||
assert.match(authentication, /window\.setTimeout\(\(\) => setMessage\(''\), 3_000\)/)
|
||
assert.match(authentication, /window\.clearTimeout\(timer\)/)
|
||
assert.match(source, /dsh-platform access reset/)
|
||
assert.match(source, /const \[activeTab, setActiveTab\] = useState\('maintenance'\)/)
|
||
assert.match(source, /\['maintenance', 'plugins', 'skills', 'proxy', 'updates', 'authentication'\]\.map/)
|
||
assert.doesNotMatch(source, /platform-tab-(?:automatic|logs)(?:-button)?/)
|
||
assert.match(source, /h\(LogViewer, \{ active: activeTab === 'maintenance', focusTaskId: focusedLogTaskId, t \}\)/)
|
||
assert.match(source, /LOG_STREAM_LIMIT = 5_000/)
|
||
assert.match(source, /logs\/stream\?limit=\$\{String\(LOG_STREAM_LIMIT\)\}/)
|
||
assert.match(source, /const logIdentities = useRef\(new Set\(\)\)/)
|
||
assert.match(source, /const pendingEntries = useRef\(\[\]\)/)
|
||
assert.match(source, /requestAnimationFrame\(commitPendingEntries\)/)
|
||
assert.match(source, /logIdentities\.current\.has\(identity\)/)
|
||
assert.doesNotMatch(source, /previous\.some\(item => item\.identity === identity\)/)
|
||
assert.match(source, /function compactLogEntries\(entries\)/)
|
||
assert.match(source, /const \[expanded, setExpanded\] = useState/)
|
||
assert.match(source, /JSON\.stringify\(entry, null, 2\)/)
|
||
assert.match(source, /'aria-expanded': isExpanded/)
|
||
assert.match(source, /className: css\.logChevron/)
|
||
assert.match(source, /JSON\.parse\(lines\.join\('\\n'\)\)/)
|
||
assert.match(source, /message: JSON\.stringify\(value\)/)
|
||
assert.match(source, /if \(!isJsonFragment\(first\.value\.message \?\? ''\)\) compacted\.push\(first\)/)
|
||
assert.match(source, /const visibleEntries = limitProcessedLogEntries\(entries, displayLimit\)/)
|
||
assert.match(source, /replace\('\{total\}', String\(displayLimit\)\)/)
|
||
assert.match(source, /DEFAULT_LOG_DISPLAY_LIMIT = 500/)
|
||
assert.match(source, /LOG_DISPLAY_LIMITS = Object\.freeze\(\[100, 250, 500, 1_000\]\)/)
|
||
assert.match(source, /localStorage\.setItem\(LOG_DISPLAY_LIMIT_KEY, String\(value\)\)/)
|
||
assert.match(source, /logDisplayLimit: '显示条数'/)
|
||
assert.match(source, /searchLogs: '搜索日志'/)
|
||
assert.match(source, /allSources: '全部模块'/)
|
||
assert.match(source, /levelWarning: '警告'/)
|
||
assert.match(source, /autoScroll: '自动滚动'/)
|
||
assert.match(source, /clearLogView: '清空显示'/)
|
||
assert.match(source, /refreshLogs: '刷新日志'/)
|
||
assert.match(source, /refreshLogs: 'Refresh logs'/)
|
||
assert.match(source, /exportLogs: '导出日志'/)
|
||
assert.match(source, /exportLogs: 'Export logs'/)
|
||
assert.match(source, /application\/x-ndjson;charset=utf-8/)
|
||
assert.match(source, /downloadLogJsonl\(exportEntries\)/)
|
||
assert.match(source, /addEventListener\('heartbeat'/)
|
||
assert.match(source, /setStreamRevision\(value => value \+ 1\)/)
|
||
assert.match(source, /LOG_CLEAR_CUTOFF_KEY = 'dsh-platform:log-clear-cutoff'/)
|
||
assert.match(source, /sessionStorage\.setItem\(LOG_CLEAR_CUTOFF_KEY, clearCutoff\.current\)/)
|
||
assert.match(source, /isClearedLog\(entry, clearCutoff\.current\)/)
|
||
assert.match(source, /listRef\.current\.scrollTop = listRef\.current\.scrollHeight/)
|
||
assert.match(source, /requestAnimationFrame[\s\S]*requestAnimationFrame/)
|
||
assert.match(source, /\[active, autoScroll, displayLimit, entries, level, query, source\]/)
|
||
assert.match(source, /checked: autoScroll/)
|
||
const checkUpdates = source.slice(source.indexOf('const checkUpdates = useCallback'), source.indexOf('const changeChannel = useCallback'))
|
||
assert.match(checkUpdates, /await request\('check'/)
|
||
assert.doesNotMatch(checkUpdates, /await act\(/)
|
||
assert.doesNotMatch(source, /运行详情|平台日志/)
|
||
for (const route of ['status', 'check', 'update', 'channel', 'automatic-check', 'holds\\/retry', 'restart-dsh', 'bundled-plugins', 'bundled-plugins\\/action', 'bundled-plugins\\/recovery-action', 'bundled-plugins\\/discard', 'system-skills', 'system-skills\\/action', 'proxy', 'proxy\\/provider-inventory', 'proxy\\/test']) {
|
||
assert.match(source, new RegExp(`['"]${route}['"]`))
|
||
}
|
||
assert.match(source, /function ProxySettings\(\{ active, t \}\)/)
|
||
assert.match(source, /const saveConnection = async[\s\S]*candidate\(\)/)
|
||
assert.doesNotMatch(source, /const saveRules = async/)
|
||
assert.match(source, /const saveImmediate = async mutate[\s\S]*rules: 'saved'/)
|
||
assert.match(source, /schema: 1, enabled: source\.enabled === true/)
|
||
assert.match(source, /const candidate = \(\{[\s\S]*includeSecret = true[\s\S]*if \(includeSecret\)/)
|
||
assert.match(source, /saveImmediate[\s\S]*includeSecret: false/)
|
||
assert.match(source, /setPassword\(''\)[\s\S]*setClearPassword\(false\)/)
|
||
assert.match(source, /className: css\.proxyClearPassword[\s\S]*'aria-pressed': clearPassword/)
|
||
assert.match(source, /className: `\$\{css\.proxySettingRow\} \$\{css\.proxyAdvancedSetting\}`[\s\S]*className: css\.toggle[\s\S]*checked: connection\.allProxy/)
|
||
assert.match(source, /const \[providerQuery, setProviderQuery\] = useState\(''\)/)
|
||
assert.match(source, /className: `\$\{css\.section\} \$\{css\.proxyProviderSection\}`/)
|
||
assert.match(style, /\.proxyProviderSection \{ padding-bottom: 0; border-bottom: 0; \}/)
|
||
assert.match(style, /\.primaryButton,[\s\S]*?\.smallButton \{[^}]*border-radius: 18px;/)
|
||
assert.match(style, /\.smallButton \{[^}]*border-radius: 14px;/)
|
||
assert.match(style, /\.toggle > span \{[^}]*border-radius: 10px;/)
|
||
assert.match(style, /\.statusBadge \{[^}]*display: inline-flex;[^}]*min-height: 18px;[^}]*border-radius: 999px;/)
|
||
assert.match(style, /\.managedBadge \{[^}]*border-radius: 12px;/)
|
||
assert.match(style, /\.autoScrollButton,[\s\S]*?\.clearLogsButton \{[^}]*border-radius: 999px;/)
|
||
assert.match(style, /\.logAutoScroll > span \{[^}]*border-radius: 8px;/)
|
||
assert.match(source, /visibleProviders = providers\.filter[\s\S]*proxyProviderSearch/)
|
||
assert.match(source, /providerInfoDialog[\s\S]*event\.target === event\.currentTarget[\s\S]*\.close\(\)/)
|
||
assert.match(source, /function splitDirectRules\(value\)[\s\S]*noProxy:[\s\S]*bypass:/)
|
||
assert.match(source, /setDirectRulesText\(directRuleText\(next\)\)/)
|
||
assert.match(source, /proxySystemRulesTitle/)
|
||
assert.doesNotMatch(source, /const \[noProxyText|const \[bypassText/)
|
||
assert.match(source, /connectionSource\.port === '' \|\| connectionSource\.port == null \? null/)
|
||
assert.match(source, /function ExpandableProxyDescription\([\s\S]*preserveScrollableAncestors/)
|
||
assert.match(source, /const rememberToggleScroll = useCallback\([\s\S]*scrollableAncestorPositions\(event\.currentTarget\)/)
|
||
assert.match(source, /const preserveToggleScroll = useCallback\([\s\S]*toggleScrollPositions\.current \?\? scrollableAncestorPositions\(element\)[\s\S]*restoreScrollableAncestors\(positions\)/)
|
||
assert.match(source, /advanced \? h\('div', \{ className: css\.proxyAdvanced \}[\s\S]*checked: connection\.allProxy/)
|
||
assert.doesNotMatch(source, /next\.environment\.allProxy/)
|
||
assert.match(source, /visibleSaveState = formDirty \? 'unsaved' : saveState[\s\S]*3_000/)
|
||
assert.match(source, /const displayName =[\s\S]*return matchesResourceSearch\(providerQuery, \[displayName\]\)/)
|
||
assert.doesNotMatch(source, /matchesResourceSearch\(providerQuery, \[provider\.displayName, provider\.id, provider\.type\]\)/)
|
||
assert.match(source, /ref: testDialog, className: css\.proxyTestDialog/)
|
||
assert.match(source, /task\.status === 'failed' \? css\.error : css\.proxyHint/)
|
||
assert.match(source, /const toggleProxy = event =>/)
|
||
assert.match(source, /toggleProxy[\s\S]*saveImmediate\(next => \{ next\.enabled = event\.target\.checked \}\)/)
|
||
assert.ok(source.indexOf("'platform-proxy-master-title'") < source.indexOf("'platform-proxy-title'"))
|
||
assert.match(source, /h\('h3', \{ id: 'platform-proxy-master-title' \}[\s\S]*configuration\.componentReady === false[\s\S]*proxyComponentAlert/)
|
||
assert.match(source, /const componentUnavailable = configuration\.componentReady === false/)
|
||
assert.match(source, /checked: configuration\.enabled === true, disabled: busy \|\| componentUnavailable/)
|
||
assert.match(source, /disabled: busy \|\| taskRunning \|\| componentUnavailable/)
|
||
assert.match(source, /value: connection\.protocol,[\s\S]*disabled: componentUnavailable,[\s\S]*items: \[\{ id: 'http', label: 'HTTP' \}, \{ id: 'socks5', label: 'SOCKS5' \}\]/)
|
||
assert.match(source, /value: connection\.host, disabled: componentUnavailable/)
|
||
assert.match(source, /value: connection\.username, disabled: componentUnavailable/)
|
||
assert.match(source, /value: password, disabled: clearPassword \|\| componentUnavailable/)
|
||
assert.match(source, /value: directRulesText, disabled: componentUnavailable/)
|
||
assert.match(source, /value: providerQuery, disabled: componentUnavailable/)
|
||
assert.match(source, /checked: configuration\.scopes\[id\], disabled: busy \|\| componentUnavailable/)
|
||
assert.match(source, /disabled: provider\.routingCapability === 'forced-direct' \|\| componentUnavailable/)
|
||
assert.match(source, /M12 6\.5v7[\s\S]*M12 17\.5h\.01/)
|
||
assert.match(source, /ref: componentDialog[\s\S]*proxyComponentIssueTitle[\s\S]*proxyComponentIssueDetail/)
|
||
assert.doesNotMatch(source, /proxyReadyShort|proxyUnavailableShort|proxyComponentState/)
|
||
assert.match(style, /\.proxyComponentAlert circle \{[^}]*fill: var\(--dsw-alias-state-error-primary\)/)
|
||
assert.match(style, /\.proxyComponentAlert path \{ stroke: var\(--dsw-alias-label-primary-foreground\)/)
|
||
assert.match(style, /\.proxyMasterRow > div > small \{ font-size: 13px; line-height: 20px; \}/)
|
||
assert.match(style, /\.proxyAdvancedSetting > div > small \{ font-size: 13px; line-height: 20px; \}/)
|
||
assert.match(source, /h\('h4', null, t\('proxyRules'\)[\s\S]*h\('h4', null, t\('proxyAllProxy'\)/)
|
||
assert.doesNotMatch(source, /h\('span', null, t\('proxyDirectRules'\)\)/)
|
||
assert.match(source, /taskRunning[\s\S]*cancelTest\(\)[\s\S]*testDialog\.current\?\.close\(\)/)
|
||
assert.match(source, /task && !taskRunning[\s\S]*proxyTestSuccess[\s\S]*taskRunning/)
|
||
assert.doesNotMatch(source, /connection\.enabled/)
|
||
assert.match(source, /const policy = configuration\.modelApi\.providers\[provider\.id\][\s\S]*proxyEnabled:/)
|
||
assert.doesNotMatch(source, /followDsh/)
|
||
const proxySettings = source.slice(source.indexOf('function ProxySettings('), source.indexOf('\nfunction PlatformManagement('))
|
||
assert.match(proxySettings, /useEffect\(\(\) => \{\s*void load\(\)\s*\}, \[load\]\)/)
|
||
assert.match(proxySettings, /const next = await request\('proxy'\)[\s\S]*setConfiguration\(next\)[\s\S]*void refreshProviders\(\)/)
|
||
assert.doesNotMatch(proxySettings, /if \(active\) void load\(\)/)
|
||
assert.match(proxySettings, /advanced \? h\('div', \{ className: css\.proxyAdvanced \},[\s\S]*t\('proxyRules'\)[\s\S]*t\('proxyAllProxy'\)[\s\S]*connection\.protocol === 'socks5'[\s\S]*t\('proxyRemoteDns'\)/)
|
||
assert.match(source, /configuration\.scopeCatalog/)
|
||
assert.match(source, /proxyTransportWarning/)
|
||
assert.doesNotMatch(source, /setPassword\(next\.proxy|value: configuration\.proxy\.password/)
|
||
assert.doesNotMatch(source, /confirmDataLoss: true/)
|
||
assert.match(source, /status\?\.dshLifecycle/)
|
||
assert.match(source, /await request\('restart-dsh'[\s\S]*sessionStorage\.removeItem\(PLUGIN_DRAFT_KEY\)/)
|
||
assert.doesNotMatch(source, /if \(hadDraft\) sessionStorage\.removeItem\(PLUGIN_DRAFT_KEY\)/)
|
||
assert.doesNotMatch(source, /window\.location\.reload\(\)/)
|
||
assert.match(source, /const \[confirmRestart, setConfirmRestart\] = useState\(false\)/)
|
||
assert.match(source, /function transactionLogStage\(phase, update, t\)/)
|
||
assert.match(source, /taskId: String\(update\.taskId\), operation: String\(update\.operation \?\? 'update'\), limit: '1000'/)
|
||
assert.match(source, /setExpanded\(\{ \[currentStage\.key\]: update\.status !== 'success' \}\)/)
|
||
assert.match(source, /viewFullTransactionLog: '查看完整事务日志'/)
|
||
assert.match(source, /setConfirmRestart\(value => !value\)/)
|
||
assert.match(source, /'aria-controls': 'restart-dsh-confirmation'/)
|
||
assert.match(source, /'aria-expanded': confirmRestart/)
|
||
assert.match(source, /t\(confirmRestart \? 'cancelRestartDsh' : 'restartDsh'\)/)
|
||
assert.match(source, /const API = '\/_dsh_platform\/plugin-api\/v1'/)
|
||
assert.match(source, /function subscribePlatformStateEvents/)
|
||
assert.equal(source.match(/new EventSource\(`\$\{API\}\/events`\)/g)?.length, 1)
|
||
assert.doesNotMatch(source, /platformAuthRequired|platformSignIn|authRequired/)
|
||
assert.equal(source.match(/href: managementConsoleHref/g)?.length, 2)
|
||
assert.doesNotMatch(source, /href: '\/_dsh_platform\/auth\/management\/start/)
|
||
assert.match(source, /nav: '平台管理', title: '平台管理'/)
|
||
assert.match(source, /nav: 'Platform Management', title: 'Platform Management'/)
|
||
assert.match(source, /standaloneManagement: 'DSH 管理中心'/)
|
||
assert.match(source, /standaloneManagement: 'DSH Management Console'/)
|
||
assert.match(source, /restartDshSection: '重启 DSH'/)
|
||
assert.match(source, /restartDshSection: 'Restart DSH'/)
|
||
assert.match(source, /cancelRestartDsh: '取消重启 DSH'/)
|
||
assert.match(source, /cancelRestartDsh: 'Cancel DSH restart'/)
|
||
assert.match(source, /restartWarning: '当前 DSH 连接会暂时中断/)
|
||
assert.match(source, /restartWarning: 'The current DSH connection will be interrupted briefly/)
|
||
assert.match(source, /plugin\.protected/)
|
||
assert.match(source, /platformManaged: '平台核心组件,始终保持安装和启用。'/)
|
||
assert.match(source, /systemPluginsDetail: '管理 DSH Docker 提供的系统插件。'/)
|
||
assert.match(source, /plugins\.some\(plugin => plugin\.pendingRestart\)/)
|
||
assert.match(source, /visiblePlugins = pagination\.items/)
|
||
assert.match(source, /filteredPlugins = plugins\.filter[\s\S]*usePaginatedItems\('system-plugins', filteredPlugins\)/)
|
||
assert.match(source, /filteredSkills = skills\.filter[\s\S]*usePaginatedItems\('system-skills', filteredSkills\)/)
|
||
assert.match(source, /searchSystemPlugins: '搜索系统插件'/)
|
||
assert.match(source, /searchSystemSkills: '搜索系统技能'/)
|
||
assert.doesNotMatch(source, /pluginInstallHint|missingPluginsPrefix|platformManagementPage|missingPluginsSuffix/)
|
||
assert.doesNotMatch(source, /pluginPendingRestart/)
|
||
assert.match(source, /function ResourceStatusBadge/)
|
||
const systemPluginManager = source.slice(source.indexOf('function SystemPluginManager('), source.indexOf('function PlatformManagement('))
|
||
const systemSkillManager = source.slice(source.indexOf('function SystemSkillManager('), source.indexOf('function PlatformManagement('))
|
||
assert.match(systemPluginManager, /const restartRequired = draft\.size > 0 \|\| plugins\.some\(plugin => plugin\.pendingRestart\)[\s\S]*const applyingAction = restartRequired[\s\S]*applyingDraft\.get\(plugin\.id\)/)
|
||
assert.match(systemPluginManager, /h\(ResourceStatusBadge[\s\S]*enabled: applyingAction === undefined && action === undefined && plugin\.enabled, pending: applyingAction !== undefined \|\| action !== undefined/)
|
||
assert.doesNotMatch(systemPluginManager, /pluginPendingRestart/)
|
||
assert.match(systemPluginManager, /action !== undefined[\s\S]*pendingInstall[\s\S]*pendingUninstall[\s\S]*pendingEnable[\s\S]*pendingDisable/)
|
||
assert.match(source, /const applySystemPluginChanges = useCallback[\s\S]*setSystemPluginApplyingDraft\(new Map\(systemPluginDraft\)\)[\s\S]*waitForSystemPluginTask/)
|
||
assert.match(systemSkillManager, /h\(ResourceStatusBadge[\s\S]*pending: isActive/)
|
||
assert.doesNotMatch(systemPluginManager, /h\('b', null, projected\.enabled/)
|
||
assert.doesNotMatch(systemSkillManager, /h\('b', null, skill\.enabled/)
|
||
assert.doesNotMatch(systemPluginManager, /isActive \? h\('p', \{ className: css\.pluginOperation/)
|
||
assert.doesNotMatch(systemSkillManager, /isActive \? h\('p', \{ className: css\.pluginOperation/)
|
||
assert.equal(systemPluginManager.indexOf('className: css.emptyPlugins') < systemPluginManager.indexOf('h(ListPagination'), true)
|
||
assert.equal(systemSkillManager.indexOf('className: css.emptyPlugins') < systemSkillManager.indexOf('h(ListPagination'), true)
|
||
assert.match(source, /pluginChangesPending: '有待应用的修改'/)
|
||
assert.match(source, /pluginChangesPending: 'Changes pending'/)
|
||
assert.match(source, /pendingSystemPluginChanges: '有 \{count\} 项修改待应用'/)
|
||
assert.match(source, /setSystemPluginProgress\(\{ phase: 'restarting', total: changes\.length, taskId: restartTask\.taskId \}\)/)
|
||
assert.match(source, /lifecycle\?\.taskId !== systemPluginProgress\.taskId[\s\S]*\['running', 'failed', 'stopped'\]/)
|
||
assert.match(systemPluginManager, /systemPluginSummary\(progress, draft, t\)/)
|
||
assert.match(source, /setSystemPluginProgress\(\{ phase: 'applying', id, action, current: index \+ 1, total: changes\.length \}\)/)
|
||
assert.match(source, /plugin\.description\?\.\[t\('localeCode'\)\]/)
|
||
assert.match(source, /PLUGIN_DRAFT_KEY = 'dsh-platform:system-plugin-draft'/)
|
||
assert.match(source, /const \[systemPluginDraft, setSystemPluginDraft\] = useState\(\(\) => new Map\(\)\)/)
|
||
assert.match(source, /const manageSystemPlugin = useCallback\([\s\S]*setSystemPluginDraft[\s\S]*next\.set\(plugin\.id, action\)/)
|
||
assert.match(source, /const applySystemPluginChanges = useCallback\([\s\S]*for \(const \[index, \[id, action\]\] of changes\.entries\(\)\)[\s\S]*waitForSystemPluginTask\(task\.taskId\)[\s\S]*request\('restart-dsh'/)
|
||
assert.match(source, /waitForSystemPluginTask[\s\S]*request\(`bundled-plugins\/task\/\$\{taskId\}`\)/)
|
||
assert.match(source, /const cancelSystemPluginChanges = useCallback\([\s\S]*setSystemPluginDraft\(new Map\(\)\)/)
|
||
assert.match(source, /sessionStorage\.setItem\(PLUGIN_DRAFT_KEY, '1'\)/)
|
||
assert.match(source, /bundled-plugins\/discard[\s\S]*sessionStorage\.removeItem\(PLUGIN_DRAFT_KEY\)/)
|
||
assert.match(source, /\(acting && !checking\)/)
|
||
assert.match(source, /const checkingProgress = update\.operation === 'check'[\s\S]*checkingProgress[\s\S]*t\('statusChecking'\)[\s\S]*updateToTarget/)
|
||
assert.doesNotMatch(source, /插件设置并重启 DSH|settings and restarting DSH/)
|
||
for (const action of ['enable', 'disable']) assert.match(source, new RegExp(`'${action}'`))
|
||
assert.match(systemPluginManager, /!projected\.installed[\s\S]*onAction\(plugin, 'install'\)/)
|
||
assert.doesNotMatch(systemPluginManager, /onAction\([^\n]+, 'uninstall'\)|t\('uninstallPlugin'\)/)
|
||
assert.match(systemSkillManager, /!skill\.installed[\s\S]*onAction\(skill, 'install'\)/)
|
||
assert.match(systemSkillManager, /event\.target\.checked \? 'enable' : 'disable'/)
|
||
assert.doesNotMatch(systemSkillManager, /onAction\([^\n]+, 'uninstall'\)|t\('uninstallPlugin'\)/)
|
||
assert.doesNotMatch(source, /trust\/reset/)
|
||
assert.match(source, /status\?\.updateChannel === 'experimental'\s*\? h\(VersionCell, \{ label: t\('upstream'\)/)
|
||
assert.match(source, /`env-\$\{String\(value\)\}`/)
|
||
assert.equal((source.match(/detail: displayEnvironment/g) ?? []).length, 2)
|
||
assert.doesNotMatch(source, /display\(update\.status\)/)
|
||
assert.match(source, /stable: '稳定', experimental: '实验'/)
|
||
assert.match(source, /}, t\(channel\)\)\)\)\)/)
|
||
assert.doesNotMatch(source, /回滚 previous|正式 Environment|恢复 Stable|立即回 Stable/)
|
||
assert.match(source, /shell\.overlay/)
|
||
assert.match(source, /export const inject = \['slots', 'locale', 'connection'\]/)
|
||
assert.match(source, /id: 'dsh-platform-lifecycle-guard'/)
|
||
assert.match(source, /const generation = connection\.generation \?\? connection\.hostDescription/)
|
||
assert.match(source, /generation\.subscribe\(connectionChanged\)/)
|
||
assert.match(source, /CONNECTION_LOSS_GRACE_MS = 1_000/)
|
||
assert.match(source, /fetch\(LIFECYCLE_READINESS_PATH/)
|
||
assert.match(source, /response\.status === 503/)
|
||
assert.match(source, /window\.location\.replace\(lifecycleWaitUrl\(\)\)/)
|
||
assert.match(source, /encodeURIComponent\(lifecycleReturnPath\(locationValue\)\)/)
|
||
assert.match(source, /latestAutomatic/)
|
||
assert.match(source, /notificationsEnabled/)
|
||
assert.match(source, /updateNotifications: '更新提醒'/)
|
||
assert.match(source, /updateNotificationsDetail: '自动检查发现新版本时,弹窗提醒更新。'/)
|
||
assert.match(source, /updateNotifications: 'Update notifications'/)
|
||
assert.match(source, /status\?\.updateChannel === 'stable' && status\?\.latestAutomatic\?\.stable/)
|
||
assert.match(source, /status\?\.updateChannel === 'experimental' && upstream/)
|
||
assert.match(source, /stableNoticeBothTitle: '正式更新:DSH 与 Environment'/)
|
||
assert.match(source, /updateNoticeDshLine: 'DSH:\{version\}\{state\}'/)
|
||
assert.match(source, /updateNoticeEnvironmentLine: 'ENV:\{version\}\{state\}'/)
|
||
assert.match(source, /stableNoticeBothTitle: 'Supported update: DSH and Environment'/)
|
||
assert.match(source, /updateNoticeDshLine: 'DSH: \{version\}\{state\}'/)
|
||
assert.match(source, /updateNoticeEnvironmentLine: 'ENV: \{version\}\{state\}'/)
|
||
assert.doesNotMatch(source, /noticeUnchanged|保持不变|\(unchanged\)/)
|
||
assert.match(source, /不再提醒此版本/)
|
||
assert.match(source, /Do not remind for this version/)
|
||
assert.match(source, /closeReminder: '关闭更新提醒'/)
|
||
assert.match(source, /closeReminder: 'Close update reminder'/)
|
||
assert.match(source, /previousFocus\.current\?\.isConnected/)
|
||
assert.match(source, /'data-notice-action': 'snooze', 'aria-label': t\('closeReminder'\)/)
|
||
assert.match(source, /source = 'manual'/)
|
||
assert.doesNotMatch(source, /detail: t\(detailKey\)/)
|
||
})
|
||
|
||
test('update reminders identify DSH, Environment, or combined changes', async () => {
|
||
const source = await readFile(new URL('lib/client.js', root), 'utf8')
|
||
const candidatesSource = source.slice(
|
||
source.indexOf('function candidateIdentity('),
|
||
source.indexOf('\nfunction clearSatisfiedDismissals('),
|
||
)
|
||
const eligibleCandidates = new Function('storageValue', `${candidatesSource}; return eligibleCandidates`)(() => null)
|
||
const modelSource = source.slice(
|
||
source.indexOf('function updateNoticeModel('),
|
||
source.indexOf('\nfunction UpdateReminder('),
|
||
)
|
||
const updateNoticeModel = new Function(`${modelSource}; return updateNoticeModel`)()
|
||
|
||
const automatic = {
|
||
notificationsEnabled: true,
|
||
}
|
||
const latestAutomatic = {
|
||
stable: { dsh: '0.1.1-rc.2', environment: '1.0.6', targetSequence: 2 },
|
||
upstream: { version: '0.1.1-rc.3' },
|
||
}
|
||
assert.deepEqual(eligibleCandidates({ automaticCheck: automatic, latestAutomatic, updateChannel: 'stable' }).map(candidate => candidate.kind), ['stable'])
|
||
assert.deepEqual(eligibleCandidates({ automaticCheck: automatic, latestAutomatic, updateChannel: 'experimental', holds: [] }).map(candidate => candidate.kind), ['upstream'])
|
||
|
||
assert.deepEqual(updateNoticeModel({
|
||
kind: 'stable', dsh: '0.1.1-rc.2', environment: '1.0.6',
|
||
}, {
|
||
current: { dsh: '0.1.1-rc.1', environment: '1.0.5' },
|
||
}), {
|
||
kind: 'stable', scope: 'both', dsh: '0.1.1-rc.2', environment: '1.0.6',
|
||
dshChanged: true, environmentChanged: true,
|
||
})
|
||
assert.equal(updateNoticeModel({
|
||
kind: 'stable', dsh: '0.1.1-rc.1', environment: '1.0.6',
|
||
}, {
|
||
current: { dsh: '0.1.1-rc.1', environment: '1.0.5' },
|
||
}).scope, 'environment')
|
||
assert.deepEqual(updateNoticeModel({ kind: 'upstream', version: '0.1.1-rc.2' }, {
|
||
current: { dsh: '0.1.1-rc.1', environment: '1.0.6' },
|
||
supported: { environment: '1.0.6' },
|
||
}), {
|
||
kind: 'upstream', scope: 'dsh', dsh: '0.1.1-rc.2', dshChanged: true,
|
||
})
|
||
})
|
||
|
||
test('Platform Management compacts multiline JSON only in the log presentation', async () => {
|
||
const source = await readFile(new URL('lib/client.js', root), 'utf8')
|
||
const helpers = source.slice(source.indexOf('function logLevel('), source.indexOf('function LogViewer('))
|
||
const compactLogEntries = new Function(`${helpers}; return compactLogEntries`)()
|
||
const status = {
|
||
platformLayout: 1,
|
||
recoveryMode: null,
|
||
trust: { keyringGeneration: 1, targetSequence: null, officialDshVersion: null },
|
||
dshLifecycle: { state: 'running', taskId: null, error: null, updatedAt: null },
|
||
}
|
||
let index = 0
|
||
const entry = message => ({
|
||
identity: `entry-${String(index)}`,
|
||
value: {
|
||
timestamp: new Date(Date.UTC(2026, 7, 20, 0, 0, 0, index++)).toISOString(),
|
||
source: 'platform-management',
|
||
stream: 'stdout',
|
||
level: 'info',
|
||
message,
|
||
},
|
||
})
|
||
const visible = compactLogEntries([
|
||
entry('"orphanedAtBoundary": null,'),
|
||
...JSON.stringify(status, null, 2).split('\n').map(entry),
|
||
entry('ordinary output'),
|
||
entry('{"already":"compact"}'),
|
||
])
|
||
assert.deepEqual(visible.map(item => item.value.message), [
|
||
JSON.stringify(status),
|
||
'ordinary output',
|
||
'{"already":"compact"}',
|
||
])
|
||
})
|
||
|
||
test('Platform Management does not present ordinary DSH stderr as an error', async () => {
|
||
const source = await readFile(new URL('lib/client.js', root), 'utf8')
|
||
const helpers = source.slice(source.indexOf('function logLevel('), source.indexOf('function LogViewer('))
|
||
const logLevel = new Function(`${helpers}; return logLevel`)()
|
||
assert.equal(logLevel({
|
||
source: 'dsh-runtime', stream: 'stderr', level: 'error', message: '[net-proxy] 已启用代理 http://172.17.0.1:7890',
|
||
}), 'info')
|
||
assert.equal(logLevel({ source: 'dsh-runtime', stream: 'stderr', level: 'error', message: 'Error: startup failed' }), 'error')
|
||
assert.equal(logLevel({ source: 'dsh-runtime', stream: 'stderr', level: 'info', message: 'file:///app/index.js:2\nError: startup failed\n at boot' }), 'error')
|
||
assert.equal(logLevel({ source: 'dsh-runtime', stream: 'stderr', level: 'error', message: 'warning: retrying' }), 'warning')
|
||
})
|
||
|
||
test('Platform Management summarizes multiline errors until expanded', async () => {
|
||
const source = await readFile(new URL('lib/client.js', root), 'utf8')
|
||
const helpers = source.slice(source.indexOf('function logLevel('), source.indexOf('function LogViewer('))
|
||
const logMessageSummary = new Function(`${helpers}; return logMessageSummary`)()
|
||
assert.equal(logMessageSummary([
|
||
'file:///app/index.js:2',
|
||
' throw new Error("startup failed")',
|
||
'Error: startup failed',
|
||
' at boot (file:///app/index.js:2:9)',
|
||
].join('\n')), 'Error: startup failed')
|
||
assert.equal(logMessageSummary('ordinary output'), 'ordinary output')
|
||
})
|
||
|
||
test('Platform Management lifecycle guard recognizes only registered transition states', async () => {
|
||
const source = await readFile(new URL('lib/client.js', root), 'utf8')
|
||
const helpers = source.slice(
|
||
source.indexOf('function requiresLifecycleHoldingPage('),
|
||
source.indexOf('\n\nfunction display('),
|
||
)
|
||
const { requiresLifecycleHoldingPage, lifecycleWaitUrl } = new Function(
|
||
`const LIFECYCLE_WAIT_PATH = '/_dsh_gateway/wait'; ${helpers}; return { requiresLifecycleHoldingPage, lifecycleWaitUrl }`,
|
||
)()
|
||
for (const operation of ['restarting', 'switching', 'recovering', 'restart-failed']) {
|
||
assert.equal(requiresLifecycleHoldingPage({ operation }), true)
|
||
}
|
||
for (const state of ['starting', 'stopping', 'stopped', 'restarting', 'recovering', 'failed']) {
|
||
assert.equal(requiresLifecycleHoldingPage({ dshLifecycle: { state } }), true)
|
||
}
|
||
assert.equal(requiresLifecycleHoldingPage({ dshLifecycle: { state: 'running' }, update: { status: 'checking' } }), false)
|
||
assert.equal(
|
||
lifecycleWaitUrl({ pathname: '/sessions/current', search: '?view=chat', hash: '#latest' }),
|
||
'/_dsh_gateway/wait?return=%2Fsessions%2Fcurrent%3Fview%3Dchat%23latest',
|
||
)
|
||
})
|
||
|
||
test('Platform Management leaves lifecycle navigation to the global holding-page guard', async () => {
|
||
const source = await readFile(new URL('lib/client.js', root), 'utf8')
|
||
assert.match(source, /window\.location\.replace\(lifecycleWaitUrl\(\)\)/)
|
||
assert.doesNotMatch(source, /window\.location\.reload\(\)/)
|
||
assert.doesNotMatch(source, /requestedRestart/)
|
||
})
|
||
|
||
test('Platform Management limits the processed log entries instead of raw fragments', async () => {
|
||
const source = await readFile(new URL('lib/client.js', root), 'utf8')
|
||
const helpers = source.slice(source.indexOf('function logLevel('), source.indexOf('function LogViewer('))
|
||
const limitProcessedLogEntries = new Function(`${helpers}; return limitProcessedLogEntries`)()
|
||
let index = 0
|
||
const entry = message => ({
|
||
identity: `entry-${String(index)}`,
|
||
value: {
|
||
timestamp: new Date(Date.UTC(2026, 7, 20, 0, 0, 0, index++)).toISOString(),
|
||
source: 'platform-management', stream: 'stdout', level: 'info', message,
|
||
},
|
||
})
|
||
const raw = [
|
||
...JSON.stringify({ first: true }, null, 2).split('\n').map(entry),
|
||
...Array.from({ length: 1_000 }, (_, value) => entry(`line-${String(value)}`)),
|
||
]
|
||
const visible = limitProcessedLogEntries(raw, 1_000)
|
||
assert.equal(visible.length, 1_000)
|
||
assert.equal(visible[0].value.message, 'line-0')
|
||
assert.equal(visible.at(-1).value.message, 'line-999')
|
||
})
|
||
|
||
test('Platform Management keeps a browser-tab log clear cutoff across refreshes', async () => {
|
||
const source = await readFile(new URL('lib/client.js', root), 'utf8')
|
||
const helpers = source.slice(source.indexOf('function logLevel('), source.indexOf('function LogViewer('))
|
||
const values = new Function(`${helpers}; return { latestLogCutoff, isClearedLog }`)()
|
||
const now = Date.parse('2026-08-20T12:00:00.000Z')
|
||
const cutoff = values.latestLogCutoff([
|
||
{ value: { timestamp: '2026-08-20T11:59:59.000Z' } },
|
||
{ value: { timestamp: '2026-08-20T12:00:01.000Z' } },
|
||
], now)
|
||
assert.equal(cutoff, '2026-08-20T12:00:01.000Z')
|
||
assert.equal(values.isClearedLog({ timestamp: cutoff }, cutoff), true)
|
||
assert.equal(values.isClearedLog({ timestamp: '2026-08-20T12:00:01.001Z' }, cutoff), false)
|
||
assert.equal(values.isClearedLog({ timestamp: 'invalid' }, cutoff), false)
|
||
})
|
||
|
||
test('Platform Management follows DSH settings tokens and responsive layout', async () => {
|
||
const source = await readFile(new URL('lib/client.js', root), 'utf8')
|
||
const style = await readFile(new URL('lib/style.module.css', root), 'utf8')
|
||
assert.match(style, /--dsw-alias-label-primary/)
|
||
assert.match(style, /--dsw-alias-button-primary-fill/)
|
||
assert.match(style, /--dsw-alias-border-l2/)
|
||
assert.match(style, /\.progressLogGroupList \{[^}]*background: var\(--dsw-alias-bg-layer-2\);/)
|
||
assert.match(style, /\.progressLogEntry \{[^}]*background: var\(--dsw-alias-bg-layer-2\);/)
|
||
assert.doesNotMatch(style, /\.progressLogEntry:last-child \{[^}]*border-bottom: 0;/)
|
||
assert.match(style, /@media \(max-width: 640px\)/)
|
||
assert.match(style, /\.pluginRestartNotice \{[\s\S]*justify-content: space-between/)
|
||
assert.match(style, /\.pluginActions \.toggle \{ width: auto; \}/)
|
||
assert.match(style, /\.logEntry\[aria-expanded='false'\] \.logMessageRow > pre \{[^}]*white-space: nowrap;/)
|
||
assert.match(source, /className: css\.resourceHeadingDetail[\s\S]*className: css\.resourceSearch/)
|
||
assert.match(style, /\.resourceHeadingDetail \{[^}]*display: flex;[^}]*align-items: center;/)
|
||
assert.match(style, /\.resourceSearch \{[^}]*width: min\(320px, 45%\);[^}]*margin: 0 0 0 auto;/)
|
||
assert.match(style, /\.pluginIdentity > \.resourceDescription \{[^}]*display: block;[^}]*margin-top: 2px;/)
|
||
assert.match(style, /@media \(max-width: 640px\)[\s\S]*\.resourceHeadingDetail \{[^}]*flex-direction: column;[^}]*\}[\s\S]*\.resourceSearch \{[^}]*width: 100%;[^}]*margin-left: 0;/)
|
||
assert.match(source, /className: css\.platformHeader[\s\S]*className: css\.heading[\s\S]*className: css\.tabs/)
|
||
assert.match(source, /SystemPluginManager\(\{ plugins, draft, applyingDraft,[\s\S]*const applyingAction = restartRequired[\s\S]*applyingDraft\.get\(plugin\.id\)[\s\S]*statusEnabling[\s\S]*statusDisabling/)
|
||
assert.match(source, /function preserveResourceRowPosition\(element, update\)[\s\S]*current === panel\) break[\s\S]*querySelectorAll\('article'\)[\s\S]*current\.scrollTop \+= offset/)
|
||
assert.match(source, /onChange: event => preserveResourceRowPosition\(event\.currentTarget, \(\) => onAction\(plugin, event\.target\.checked \? 'enable' : 'disable'\)\)/)
|
||
assert.match(source, /onChange: event => preserveResourceRowPosition\(event\.currentTarget, \(\) => onAction\(skill, event\.target\.checked \? 'enable' : 'disable'\)\)/)
|
||
assert.match(style, /\.root \{[^}]*height: min\(720px, calc\(100dvh - 152px\)\);[^}]*overflow: hidden;/)
|
||
assert.match(style, /\.platformHeader \{[^}]*width: 100%;[^}]*min-width: 0;[^}]*max-width: 100%;[^}]*flex: none;[^}]*background: var\(--dsw-alias-bg-layer-2\);/)
|
||
assert.match(style, /\.tabPanel \{[^}]*min-height: 0;[^}]*flex: 1;[^}]*overflow-y: auto;[^}]*scrollbar-gutter: stable;/)
|
||
assert.match(style, /\.pluginSection \{[^}]*height: 100%;[^}]*min-height: 0;[^}]*overflow: visible;/)
|
||
assert.match(style, /\.pluginSection > \.resourceSectionHeading,[\s\S]*\.pluginSection > \.listPagination,[\s\S]*\.pluginSection > \.error \{ flex: none; \}/)
|
||
assert.match(style, /\.pluginList \{[^}]*min-height: 68px;[^}]*max-height:[^}]*flex: 0 1 auto;[^}]*overflow-y: auto;/)
|
||
assert.match(style, /\.pluginSection \.listPagination \{ position: static; \}/)
|
||
assert.match(style, /@media \(max-width: 640px\)[\s\S]*\.pluginList \{ min-height: 122px; \}/)
|
||
assert.match(style, /\.proxyProviderList \{[^}]*max-height: 320px;[^}]*overflow-y: auto;/)
|
||
assert.match(style, /\.proxyScopeDescription \{[^}]*-webkit-line-clamp: 1;/)
|
||
assert.match(source, /className: css\.proxyScopeCard[\s\S]*className: css\.toggle[\s\S]*checked: configuration\.scopes\[id\]/)
|
||
assert.match(source, /DSH Docker 系统插件等的非本地外部请求/)
|
||
assert.match(source, /DSH Docker System Plugins, and other platform components/)
|
||
assert.doesNotMatch(source, /第三方及 DSH Docker 系统插件的联网/)
|
||
assert.doesNotMatch(source, /third-party, and DSH Docker System Plugin traffic/)
|
||
assert.match(source, /className: css\.proxyProviderRoute[\s\S]*proxyProviderShared[\s\S]*className: css\.toggle[\s\S]*proxyEnabled/)
|
||
assert.doesNotMatch(source, /proxyProviderFollow|data-shared-route/)
|
||
assert.match(source, /function localizedProxyTestDetail\(stage, t, fallback\)/)
|
||
assert.match(source, /localizedProxyTestDetail\(stage, t, status\)/)
|
||
assert.match(source, /proxyStageDetailAddress: '已解析 \{count\} 个代理地址记录。'/)
|
||
assert.match(source, /proxyStageDetailAddress: 'Resolved \{count\} proxy address record\(s\)\.'/)
|
||
assert.match(source, /function pendingProxyTestStages\(proxy = \{\}\)[\s\S]*stage !== 'target-dns'[\s\S]*proxy\.protocol === 'socks5'[\s\S]*status: 'pending'/)
|
||
assert.match(source, /const startTest = async \(\) => \{[\s\S]*status: 'starting'[\s\S]*pendingProxyTestStages\(connection\)[\s\S]*showModal\(\)/)
|
||
assert.match(source, /className: css\.proxyTestStages[\s\S]*task\.stages\.map/)
|
||
assert.match(source, /const \[expandedTestStages, setExpandedTestStages\] = useState\(\(\) => new Set\(\)\)/)
|
||
assert.match(source, /role: 'button'[\s\S]*'aria-expanded': expanded[\s\S]*onClick: event => toggleDetails\(event\.currentTarget\)[\s\S]*h\('small', null, status\)/)
|
||
assert.match(style, /\.proxyProviderRoute \{[^}]*background: var\(--dsw-alias-bg-module-platform\)/)
|
||
assert.match(style, /proxyTestStages > li\[data-state='running'\][^{]*\{[^}]*animation: checkSpin/)
|
||
assert.match(style, /\.proxyTestStages > li > span:nth-child\(2\) \{[^}]*display: grid;[^}]*gap: 1px;/)
|
||
assert.match(style, /\.proxyTestDialog footer \{[^}]*display: grid;[^}]*grid-template-columns: minmax\(0, 1fr\) auto;/)
|
||
assert.match(style, /\.proxyTestDialog footer > \.proxyHint, \.proxyTestDialog footer > \.error \{[^}]*margin: 0;/)
|
||
assert.doesNotMatch(style, /@media \(max-width: 640px\)[\s\S]*\.proxyTestDialog \{[^}]*height: 100dvh;/)
|
||
assert.match(style, /\.proxyScopeDialog \{[^}]*height: min\(720px, calc\(100dvh - 40px\)\)/)
|
||
assert.match(style, /\.proxyScopeCatalog \{[^}]*min-height: 0;[^}]*overflow: auto;/)
|
||
assert.match(source, /h\(ExpandableProxyDescription, \{ text: localize\(entry\.detail\), identity: entry\.id \}\)/)
|
||
assert.match(style, /progressStageMarkerActive::before[^{]*\{[^}]*animation: checkSpin/)
|
||
assert.doesNotMatch(source, /className: css\.proxyProvider[\s\S]{0,800}h\('select'/)
|
||
assert.match(source, /className: css\.proxyProviderIdentity[\s\S]*className: css\.proxyProviderInfo/)
|
||
assert.match(source, /ref: providerInfoDialog[\s\S]*providerInfo\?\.detail/)
|
||
assert.doesNotMatch(style, /\.proxyProviderDescription/)
|
||
assert.match(style, /\.listPagination \{[^}]*position: sticky;[^}]*bottom: 0;[^}]*display: grid;[^}]*grid-template-columns: minmax\(0, 1fr\) auto auto auto;/)
|
||
assert.match(style, /\.listPagination \{[^}]*background: var\(--dsw-alias-bg-layer-2\);/)
|
||
assert.match(style, /\.pageArrow::before \{[^}]*border-top: 1\.5px solid currentColor;[^}]*border-right: 1\.5px solid currentColor;/)
|
||
assert.doesNotMatch(source, /className: `\$\{css\.smallButton\} \$\{css\.pageArrow\}`[^\n]*[‹›]/)
|
||
assert.match(style, /\.statusEnabled \{[^}]*color: var\(--dsw-alias-state-success-primary\);[^}]*color-mix/)
|
||
assert.match(style, /@media \(max-width: 640px\)[\s\S]*\.listPagination \{ grid-template-columns: minmax\(0, 1fr\) auto;/)
|
||
assert.match(style, /\.tabs \{[\s\S]*width: 100%;[\s\S]*overflow-x: auto/)
|
||
assert.match(style, /\.tabs \{[\s\S]*touch-action: pan-x;[\s\S]*white-space: nowrap/)
|
||
assert.match(style, /\.root \{[\s\S]*gap: 12px;[\s\S]*max-width: 760px/)
|
||
assert.match(style, /\.heading \{[\s\S]*gap: 12px/)
|
||
assert.match(style, /\.tabs \{[\s\S]*gap: 22px;[\s\S]*margin-top: 2px/)
|
||
assert.match(style, /padding: 7px 1px 9px/)
|
||
assert.match(style, /\.tabs button:focus-visible/)
|
||
assert.match(style, /\.tabs button \{[\s\S]*scroll-snap-align: start/)
|
||
assert.match(style, /@media \(hover: hover\) and \(pointer: fine\) \{\s*\.tabs \{ scroll-snap-type: none; \}/)
|
||
assert.match(style, /\.tabs button\[aria-selected='true'\]::after/)
|
||
assert.match(style, /\.tabPanel\[hidden\] \{ display: none; \}/)
|
||
assert.match(style, /\.versions \{[\s\S]*grid-template-columns: repeat\(2,/)
|
||
assert.match(style, /\.experimentalVersions \{ grid-template-columns: repeat\(3,/)
|
||
assert.match(style, /@media \(max-width: 640px\)[\s\S]*\.actionHeading \{ align-items: flex-start; \}/)
|
||
assert.match(style, /@media \(max-width: 640px\)[\s\S]*\.actions \{ width: 100%; justify-content: flex-start; \}/)
|
||
assert.match(style, /@media \(max-width: 640px\)[\s\S]*\.maintenanceButton \{ width: 100%; \}/)
|
||
assert.match(style, /\.updateReminder/)
|
||
assert.match(style, /body\[data-dsh-platform-select-menu='open'\] > \[role='menu'\][\s\S]*?max-height: min\(320px, 60dvh\);[\s\S]*?overflow: hidden;/)
|
||
assert.match(style, /body\[data-dsh-platform-select-menu='open'\] > \[role='menu'\] > \[role='presentation'\][\s\S]*?overflow-y: auto;/)
|
||
assert.match(source, /dataset\.dshPlatformSelectMenu = 'open'/)
|
||
assert.match(source, /proxyEndpointRequired/)
|
||
assert.match(source, /addEventListener\('sources'/)
|
||
assert.match(style, /\.proxyFormGrid input:focus-visible,[\s\S]*?border-color: var\(--dsw-alias-label-primary\);[\s\S]*?outline: none;/)
|
||
assert.match(style, /\.settingRows/)
|
||
assert.match(style, /\.logFilters/)
|
||
assert.match(style, /\.logAutoScroll \{[^}]*margin-left: auto;/)
|
||
assert.match(source, /className: css\.logSummaryRow[\s\S]*className: css\.logSummary[\s\S]*className: css\.logAutoScroll/)
|
||
assert.match(style, /\.logResizeFrame \{[\s\S]*height: 280px;[\s\S]*min-height: 140px;[\s\S]*max-height: min\(560px, 70dvh\);/)
|
||
assert.match(style, /@media \(max-width: 640px\)[\s\S]*\.logResizeFrame \{[\s\S]*height: min\(260px, 36dvh\);[\s\S]*max-height: min\(520px, 72dvh\);/)
|
||
assert.match(style, /\.logResizeHandle \{[\s\S]*right: 0;[\s\S]*bottom: 0;[\s\S]*left: 0;[\s\S]*height: 16px;/)
|
||
assert.match(source, /frame\.style\.height = `\$\{String\(height\)\}px`/)
|
||
assert.match(source, /scrollContainer\.scrollTop \+= Math\.min\(overflow, 24\)/)
|
||
assert.match(style, /\.logDetails \{/)
|
||
assert.match(style, /\.logEntry\[aria-expanded='true'\] \.logChevron/)
|
||
assert.match(style, /\.logChevron::before \{[\s\S]*?width: 8px[\s\S]*?height: 5px[\s\S]*?clip-path: polygon[\s\S]*?transform-origin: center/)
|
||
assert.match(style, /\.logChevron \{[^}]*top: 2px/)
|
||
assert.doesNotMatch(style, /\.logChevron(?:::before)? \{[^}]*transition:/)
|
||
assert.match(style, /\.logEntry\[aria-expanded='true'\] \.logChevron::before \{[^}]*transform: rotate\(180deg\)/)
|
||
assert.match(source, /className: css\.logMeta[\s\S]*className: css\.logMessageRow[\s\S]*className: css\.logChevron/)
|
||
assert.match(style, /\.logWarning/)
|
||
assert.match(style, /\.autoScrollButton/)
|
||
assert.match(style, /\.clearLogsButton/)
|
||
assert.doesNotMatch(style, /#[0-9a-f]{3,8}\b/i)
|
||
})
|