80 lines
5.0 KiB
JavaScript
80 lines
5.0 KiB
JavaScript
const { test } = require('node:test')
|
|
const assert = require('node:assert/strict')
|
|
const fs = require('node:fs')
|
|
const path = require('node:path')
|
|
const Module = require('node:module')
|
|
const ts = require('typescript')
|
|
const root = path.resolve(__dirname, '..')
|
|
function loadRuntime() {
|
|
const filename = path.join(root, 'src/sheep/utils/file-access.ts')
|
|
const corePath = path.join(path.dirname(filename), 'file-access-core.ts')
|
|
const compile = source => ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 } }).outputText
|
|
const core = new Module(corePath, module); core.filename = corePath; core.paths = Module._nodeModulePaths(path.dirname(corePath))
|
|
core._compile(compile(fs.readFileSync(corePath, 'utf8')), corePath)
|
|
const appId = root.endsWith('pj_mini_app_cloud') ? 'mini' : 'h5'
|
|
const env = { VITE_FILE_ACCESS_APP_ID: appId, VITE_FILE_ACCESS_KEY_ID: 'v1',
|
|
VITE_FILE_ACCESS_KEY: JSON.parse(fs.readFileSync(path.join(root, 'test-vectors/file-access.json'), 'utf8'))[0].key,
|
|
VITE_IMAGE_DEV_PATH: 'https://files.example/api', VITE_APP_DEV_SERVICE: 'https://files.example/api' }
|
|
const runtime = new Module(filename, module); runtime.filename = filename; runtime.paths = core.paths
|
|
runtime.require = name => name === './file-access-core' ? core.exports : name === './app' ? { isDevEnv: () => true } : core.require(name)
|
|
runtime._compile(compile(fs.readFileSync(filename, 'utf8').replace(/import\.meta\.env/g, '(' + JSON.stringify(env) + ')')), filename)
|
|
return { api: runtime.exports, appId }
|
|
}
|
|
test('rich-text file links use signed downloads and local files instead of browser navigation', () => {
|
|
const previousUni = global.uni, previousDocument = global.document
|
|
const downloads = [], documents = [], previews = [], browserDownloads = []
|
|
global.uni = {
|
|
downloadFile: options => { downloads.push(options); options.success({ statusCode: 200, tempFilePath: 'blob:local-file' }) },
|
|
openDocument: options => documents.push(options), previewImage: options => previews.push(options), onNetworkStatusChange() {},
|
|
}
|
|
global.document = undefined
|
|
try {
|
|
const { api, appId } = loadRuntime()
|
|
api.openFileLink('/admin/sys-file/oss/file?fileName=report.pdf')
|
|
assert.equal(new URL(downloads[0].url).searchParams.get('appId'), appId)
|
|
assert.ok(new URL(downloads[0].url).searchParams.has('sign'))
|
|
assert.equal(documents[0].filePath, 'blob:local-file')
|
|
let appended = 0, removed = 0
|
|
let onClick
|
|
global.document = {
|
|
addEventListener(name, handler, capture) { assert.equal(name, 'click'); assert.equal(capture, true); onClick = handler },
|
|
body: { appendChild() { appended++ } },
|
|
createElement: () => ({ click() { browserDownloads.push({ href: this.href, name: this.download }) }, remove() { removed++ } }),
|
|
}
|
|
api.openFileLink('/admin/sys-file/oss/file?fileName=' + encodeURIComponent('附件 +.pdf'))
|
|
assert.deepEqual(browserDownloads, [{ href: 'blob:local-file', name: '附件 +.pdf' }])
|
|
assert.equal(appended, 1); assert.equal(removed, 1)
|
|
api.openFileLink('/admin/sys-file/oss/file?fileName=photo.png')
|
|
assert.equal(previews.length, 1)
|
|
assert.ok(new URL(previews[0].urls[0]).searchParams.has('sign'))
|
|
assert.equal(downloads.length, 2)
|
|
api.installFileAccess({ config: { globalProperties: {} }, mixin() {} })
|
|
let prevented = false
|
|
onClick({
|
|
target: { closest: () => ({ getAttribute: () => '/admin/sys-file/oss/file?fileName=linked.pdf' }) },
|
|
preventDefault() { prevented = true }, stopImmediatePropagation() {},
|
|
})
|
|
assert.equal(prevented, true)
|
|
assert.equal(browserDownloads.at(-1).name, 'linked.pdf')
|
|
let outsidePrevented = false
|
|
onClick({
|
|
target: { closest: () => ({ getAttribute: () => 'https://other.example/report.pdf' }) },
|
|
preventDefault() { outsidePrevented = true }, stopImmediatePropagation() {},
|
|
})
|
|
assert.equal(outsidePrevented, false)
|
|
} finally { global.uni = previousUni; global.document = previousDocument }
|
|
})
|
|
test('the actual rich-text component routes owned links through the file helper', () => {
|
|
const filename = path.join(root, 'src/uni_modules/tm-ui/components/tm-html/mp-node.vue')
|
|
const script = fs.readFileSync(filename, 'utf8').match(/<script>\s*([\s\S]*?)<\/script>/)[1]
|
|
const loaded = new Module(filename, module); loaded.filename = filename; loaded.paths = Module._nodeModulePaths(path.dirname(filename))
|
|
const opened = []
|
|
loaded.require = name => name === './mp-node.vue' ? {} : name === '@/sheep/utils/file-access'
|
|
? { accessFileUrl: value => value, isProtectedFile: value => value.includes('/sys-file/oss/file'), openFileLink: value => opened.push(value) }
|
|
: require(name)
|
|
loaded._compile(ts.transpileModule(script, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 } }).outputText, filename)
|
|
const href = 'https://files.example/api/admin/sys-file/oss/file?fileName=report.pdf'
|
|
loaded.exports.default.methods.linkTap.call({ root: { $emit() {}, getText: () => '' } }, { href })
|
|
assert.deepEqual(opened, [href])
|
|
})
|