const { test } = require('node:test') const assert = require('node:assert/strict') const fs = require('node:fs') const path = require('node:path') const vue = require('vue') const { parse, compileScript, compileTemplate } = require('@vue/compiler-sfc') const { compile, root, runtime } = require('./helpers/file-access-runtime.cjs') function setup(relative, props, request, fileAccess = {}) { const descriptor = parse(fs.readFileSync(path.join(root, relative), 'utf8')).descriptor assert.deepEqual(compileTemplate({ source: descriptor.template.content, filename: relative, id: 'upload-transport' }).errors, []) const errors = [], events = [], loaded = { exports: {} } const vueHooks = { ...vue, onBeforeUnmount() {} } const mocks = { vue: vueHooks, '/@/utils/request': { default: request }, '/@/utils/storage': { Session: { get: () => 'token', getTenant: () => '42' } }, '/@/hooks/message': { useMessage: () => ({ error: value => errors.push(value) }) }, '/@/utils/other': { default: { adaptationUrl: url => url, addUnit: value => value } }, '/@/utils/file-access': fileAccess, '/@/utils/file-access-dom': { bindFileAccessDom() {} }, 'vue-i18n': { useI18n: () => ({ t: key => key }) }, '@wangeditor/editor/dist/css/style.css': {}, '@wangeditor/editor-for-vue': { Toolbar: {}, Editor: {} }, } const globals = { ref: vue.ref, computed: vue.computed, watch: vue.watch, getCurrentInstance: () => ({ proxy: { baseURL: '/api' } }), onMounted() {}, nextTick: vue.nextTick } new Function('module', 'exports', 'require', ...Object.keys(globals), compile(compileScript(descriptor, { id: 'upload-transport' }).content))( loaded, loaded.exports, name => name in mocks ? mocks[name] : require(name), ...Object.values(globals)) const state = loaded.exports.default.setup(props, { expose() {}, emit: (...args) => events.push(args) }) return { state, errors, events } } test('generic uploads preserve multipart fields, delegate URL adaptation to the common transport, and finish a batch after one file fails', async () => { const requests = [] const file = new File(['%PDF-1.4\n%%EOF'], 'report.PDF', { type: 'application/pdf' }) for (const type of ['default', 'simple']) { const { state, errors, events } = setup('src/components/Upload/index.vue', { type, modelValue: '', fileType: ['pdf'], fileSize: 100, data: {}, dir: 'reports', uploadFileUrl: '/admin/sys-file/upload', }, async options => { requests.push(options); return { code: 0, data: { url: '/admin/sys-file/oss/file?fileName=report.pdf' } } }) assert.equal(state.handleBeforeUpload(file), true) assert.equal(state.handleBeforeUpload(file), true) const result = await state.handleHttpUpload({ action: '/api/admin/sys-file/upload', filename: 'file', file, data: { dir: 'reports', groupId: 12, type: 'pdf' } }) const request = requests.at(-1) assert.equal(request.url, '/admin/sys-file/upload') assert.equal(request.baseURL, undefined) assert.equal(request.data.get('file').name, 'report.PDF') assert.equal(request.data.get('dir'), 'reports') assert.equal(request.data.get('groupId'), '12') assert.equal(request.data.get('type'), 'pdf') state.handleUploadSuccess(result, { name: file.name, raw: file, size: file.size }) assert.equal(events.length, 0) state.handleUploadError({ msg: '不支持上传此文件类型' }) assert.deepEqual(errors, ['不支持上传此文件类型']) assert.equal(state.number.value, 0) assert.equal(events.find(event => event[0] === 'update:modelValue')[1], '/admin/sys-file/oss/file?fileName=report.pdf') } }) test('rich text images and videos use the common transport, preserve raw saved references, and propagate upload failures', async () => { const env = runtime(), requests = [], inserted = [] const raw = '/admin/sys-file/oss/file?fileName=photo.png' let result = { code: 0, data: { url: raw } } let transportError try { const { state, events } = setup('src/components/Editor/index.vue', { getHtml: '', placeholder: '', height: '310', width: 'auto', uploadFileUrl: '/admin/sys-file/upload', }, async options => { requests.push(options); if (transportError) throw transportError; return result }, env.api) for (const name of ['uploadImage', 'uploadVideo']) { const file = new File(['test content'], name === 'uploadImage' ? 'photo.png' : 'video.mp4') await state.state.editorConfig.MENU_CONF[name].customUpload(file, value => inserted.push(value)) assert.equal(requests.at(-1).url, '/admin/sys-file/upload') assert.equal(requests.at(-1).method, 'post') assert.equal(requests.at(-1).data.get('file').name, file.name) } state.handleChange({ getHtml: () => '', getText: () => '' }) const saved = events.find(event => event[0] === 'update:getHtml')[1] assert.doesNotMatch(saved, /[?&](?:sign|appId|kid|ts)=/) assert.match(saved, /fileName=photo\.png/) result = { code: 1, msg: '文件内容与后缀类型不符' } await assert.rejects(state.uploadAttr.customUpload(new File(['bad'], 'bad.png'), value => inserted.push(value)), /文件内容与后缀类型不符/) transportError = { msg: '上传身份校验失败' } await assert.rejects(state.uploadAttr.customUpload(new File(['bad'], 'bad.png'), value => inserted.push(value)), /上传身份校验失败/) assert.equal(inserted.length, 2) } finally { env.close() } })