Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
63 lines
3.8 KiB
JavaScript
63 lines
3.8 KiB
JavaScript
const assert = require('node:assert/strict')
|
|
const { execFileSync } = require('node:child_process')
|
|
const fs = require('node:fs')
|
|
const os = require('node:os')
|
|
const path = require('node:path')
|
|
const vm = require('node:vm')
|
|
const test = require('node:test')
|
|
|
|
const frontend = path.resolve(__dirname, '../frontend')
|
|
const docker = (...args) => execFileSync('docker', [
|
|
...(process.env.NGINX_TEST_DOCKER_CONTEXT ? ['--context', process.env.NGINX_TEST_DOCKER_CONTEXT] : []), ...args,
|
|
], { encoding: 'utf8', timeout: 30000, stdio: ['ignore', 'pipe', 'pipe'] }).trim()
|
|
|
|
test('startup embeds current validated config in both entry points on every run', { timeout: 90000 }, async () => {
|
|
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'weknora-runtime-config-'))
|
|
const name = `weknora-runtime-config-${process.pid}-${Date.now()}`
|
|
const image = process.env.NGINX_TEST_IMAGE || fs.readFileSync(path.join(frontend, 'Dockerfile'), 'utf8').match(/^FROM (\S+)/m)[1]
|
|
let created = false
|
|
try {
|
|
// The stand-in nginx exits, so the real startup script can be run repeatedly.
|
|
fs.writeFileSync(path.join(temp, 'nginx'), '#!/bin/sh\nexit 0\n', { mode: 0o755 })
|
|
docker('create', '--rm', '--pull', 'never', '--network', 'none', '--name', name,
|
|
'--entrypoint', 'sh', image, '-c', 'while :; do sleep 30; done')
|
|
created = true
|
|
docker('start', name)
|
|
docker('exec', name, 'mkdir', '-p', '/usr/share/nginx/runtime-templates', '/etc/nginx/templates', '/fixture-bin')
|
|
for (const page of ['index.html', 'embed.html']) docker('cp', path.join(frontend, page), `${name}:/usr/share/nginx/runtime-templates/${page}`)
|
|
docker('cp', path.join(frontend, 'nginx.conf'), `${name}:/etc/nginx/templates/default.conf.template`)
|
|
docker('cp', path.join(frontend, 'docker-entrypoint.sh'), `${name}:/fixture-entrypoint.sh`)
|
|
docker('cp', path.join(temp, 'nginx'), `${name}:/fixture-bin/nginx`)
|
|
const run = env => docker('exec', '-e', 'PATH=/fixture-bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
|
|
...Object.entries(env).flatMap(([key, value]) => ['-e', `${key}=${value}`]), name, 'sh', '/fixture-entrypoint.sh')
|
|
const readConfig = (source) => {
|
|
const matches = [...source.matchAll(/<script data-runtime-config>([\s\S]*?)<\/script>/g)]
|
|
assert.equal(matches.length, 1)
|
|
assert.ok(!source.includes('src="/config.js"'))
|
|
const context = { window: {} }
|
|
vm.runInNewContext(matches[0][1], context, { timeout: 500 })
|
|
return JSON.parse(JSON.stringify(context.window.__RUNTIME_CONFIG__))
|
|
}
|
|
for (const [env, expected] of [
|
|
[{ DEFAULT_LOCALE: 'zh-CN', MAX_FILE_SIZE_MB: '50', MAX_SKILL_BUNDLE_SIZE_MB: '300' },
|
|
{ DEFAULT_LOCALE: 'zh-CN', MAX_FILE_SIZE_MB: 50, MAX_SKILL_BUNDLE_SIZE_MB: 300 }],
|
|
[{ DEFAULT_LOCALE: 'en-US', MAX_FILE_SIZE_MB: '99999', MAX_SKILL_BUNDLE_SIZE_MB: '600' },
|
|
{ DEFAULT_LOCALE: 'en-US', MAX_FILE_SIZE_MB: 20480, MAX_SKILL_BUNDLE_SIZE_MB: 512 }],
|
|
[{ DEFAULT_LOCALE: '</script><script>alert(1)</script>', MAX_FILE_SIZE_MB: 'invalid', MAX_SKILL_BUNDLE_SIZE_MB: '1;alert(1)' },
|
|
{ DEFAULT_LOCALE: '', MAX_FILE_SIZE_MB: 50, MAX_SKILL_BUNDLE_SIZE_MB: 256 }],
|
|
]) {
|
|
run(env)
|
|
for (const page of ['index.html', 'embed.html']) {
|
|
const body = docker('exec', name, 'cat', `/usr/share/nginx/html/${page}`)
|
|
assert.deepEqual(readConfig(body), expected)
|
|
assert.ok(body.includes('type="module"'))
|
|
}
|
|
const compatibility = { window: {} }
|
|
vm.runInNewContext(docker('exec', name, 'cat', '/usr/share/nginx/html/config.js'), compatibility, { timeout: 500 })
|
|
assert.deepEqual(JSON.parse(JSON.stringify(compatibility.window.__RUNTIME_CONFIG__)), expected)
|
|
}
|
|
} finally {
|
|
try { if (created) docker('rm', '--force', name) } finally { fs.rmSync(temp, { recursive: true, force: true }) }
|
|
}
|
|
})
|