| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995 |
- const assert = require('node:assert/strict')
- const fs = require('node:fs')
- const path = require('node:path')
- const { pathToFileURL } = require('node:url')
- const vm = require('node:vm')
- const rules = require('../utils/teamBackgroundFile')
- assert.equal(rules.validateEnterpriseWebsite(' 无 '), '')
- assert.equal(rules.validateEnterpriseWebsite('https://example.com'), '')
- assert.match(rules.validateEnterpriseWebsite('ftp://example.com'), /http/)
- assert.equal(rules.validateBackgroundFile({ name: 'brief.PDF', size: 1 }), '')
- assert.match(rules.validateBackgroundFile({ name: 'brief.zip', size: 1 }), /类型/)
- assert.match(rules.validateBackgroundFile({ name: 'brief.pdf', size: 50 * 1024 * 1024 + 1 }), /50MB/)
- assert.equal(rules.validateBackgroundFile({ name: 'brief.pdf', size: 50 * 1024 * 1024 }), '')
- assert.equal(rules.validateBackgroundFile({ name: `${'a'.repeat(251)}.pdf`, size: 1 }), '')
- assert.match(rules.validateBackgroundFile({ name: `${'a'.repeat(252)}.pdf`, size: 1 }), /255/)
- assert.match(rules.validateBackgroundFile({ name: 'empty.pdf', size: 0 }), /非空/)
- const backendExpiry = '2026-07-21 15:30:00'
- const explicitOffsetExpiry = '2026-07-21T15:30:00+08:00'
- const utcExpiry = '2026-07-21T07:30:00.250Z'
- assert.equal(rules.parseSessionExpiresAt(backendExpiry), Date.UTC(2026, 6, 21, 7, 30, 0))
- assert.equal(rules.parseSessionExpiresAt(explicitOffsetExpiry), Date.UTC(2026, 6, 21, 7, 30, 0))
- assert.equal(rules.parseSessionExpiresAt(utcExpiry), Date.UTC(2026, 6, 21, 7, 30, 0, 250))
- assert.equal(rules.getSessionRemainingSeconds(backendExpiry, Date.UTC(2026, 6, 21, 7, 29, 59, 1)), 1)
- assert.equal(rules.getSessionRemainingSeconds(backendExpiry, Date.UTC(2026, 6, 21, 7, 30, 1)), 0)
- assert.equal(rules.getSessionRemainingSeconds('2026-07-21T15:30:00', 0), 0)
- assert.equal(rules.getSessionRemainingSeconds('not-a-date', 0), 0)
- const validQueueItem = rules.createBackgroundQueueItem(
- { name: 'brief.pdf', path: '/tmp/brief.pdf', size: 1 },
- 'valid-file'
- )
- const invalidQueueItem = rules.createBackgroundQueueItem(
- { name: 'archive.zip', path: '/tmp/archive.zip', size: 1 },
- 'invalid-file'
- )
- assert.equal(validQueueItem.status, 'pending')
- assert.equal(validQueueItem.progress, 0)
- assert.equal(validQueueItem.validationError, '')
- assert.equal(invalidQueueItem.status, 'failed')
- assert.match(invalidQueueItem.validationError, /类型/)
- validQueueItem.status = 'success'
- const uploadFailure = {
- id: 'upload-failure',
- status: 'failed',
- validationError: '',
- error: '上传失败'
- }
- assert.deepEqual(
- rules.getUploadableBackgroundFiles([validQueueItem, invalidQueueItem, uploadFailure]),
- [uploadFailure]
- )
- const originalTeam = {
- id: 12,
- teamName: '研发团队',
- enterpriseWebsite: ' https://example.com/team ',
- coachId: 3,
- uploaderId: 4,
- source: 'WECHAT'
- }
- const teamSubmitPayload = rules.createTeamSubmitPayload(originalTeam)
- assert.equal(teamSubmitPayload.id, 12)
- assert.equal(teamSubmitPayload.enterpriseWebsite, 'https://example.com/team')
- assert.equal(Object.hasOwn(teamSubmitPayload, 'coachId'), false)
- assert.equal(Object.hasOwn(teamSubmitPayload, 'uploaderId'), false)
- assert.equal(Object.hasOwn(teamSubmitPayload, 'source'), false)
- assert.equal(originalTeam.coachId, 3, 'payload creation must not mutate the displayed detail')
- const textPreviewState = rules.createTextPreviewState({
- mode: 'text',
- fileName: '背景说明.md',
- content: '# 团队背景',
- truncated: true
- }, 'fallback.md')
- assert.deepEqual(textPreviewState, {
- visible: true,
- fileName: '背景说明.md',
- content: '# 团队背景',
- truncated: true
- })
- assert.deepEqual(rules.createEmptyTextPreviewState(), {
- visible: false,
- fileName: '',
- content: '',
- truncated: false
- })
- assert.notEqual(rules.createEmptyTextPreviewState(), rules.createEmptyTextPreviewState())
- const creationState = rules.createTeamCreationState()
- assert.deepEqual(creationState, {
- createdTeamId: '',
- submitting: false,
- stage: 'idle',
- completed: false
- })
- assert.equal(rules.beginTeamSubmission(creationState), true)
- assert.equal(rules.beginTeamSubmission(creationState), false, 'a second submission must be locked')
- assert.equal(rules.rememberCreatedTeam(creationState, 88), 88)
- assert.equal(rules.rememberCreatedTeam(creationState, 99), 88, 'the first server team id must be retained')
- rules.setTeamSubmissionStage(creationState, 'upload')
- assert.equal(creationState.stage, 'upload')
- rules.finishTeamSubmission(creationState)
- assert.equal(creationState.submitting, false)
- assert.equal(rules.beginTeamSubmission(creationState), true, 'a failed later stage can resume')
- rules.finishTeamSubmission(creationState, true)
- assert.equal(creationState.completed, true)
- assert.equal(rules.beginTeamSubmission(creationState), false, 'a completed flow cannot be scheduled twice')
- assert.equal(rules.hasUnresolvedBackgroundFiles([
- { status: 'success', validationError: '' },
- { status: 'failed', validationError: '文件类型不支持' }
- ]), false)
- for (const item of [
- { status: 'pending', validationError: '' },
- { status: 'uploading', validationError: '' },
- { status: 'failed', validationError: '' }
- ]) {
- assert.equal(rules.hasUnresolvedBackgroundFiles([item]), true)
- }
- console.log('team background rules: PASS')
- const transportPath = path.join(__dirname, '../http/teamBackgroundFile.js')
- async function loadTransport({ api = {}, uni = {}, wx = {} } = {}) {
- const source = fs.readFileSync(transportPath, 'utf8')
- const context = vm.createContext({ uni, wx })
- const baseApiModule = new vm.SyntheticModule(['BaseApi'], function () {
- this.setExport('BaseApi', 'https://api.example.test/app')
- }, { context, identifier: 'test:baseApi' })
- const apiModule = new vm.SyntheticModule(['default'], function () {
- this.setExport('default', api)
- }, { context, identifier: 'test:api' })
- const transportModule = new vm.SourceTextModule(source, {
- context,
- identifier: pathToFileURL(transportPath).href
- })
- await transportModule.link(specifier => {
- if (specifier === './baseApi.js') return baseApiModule
- if (specifier === './index.js') return apiModule
- throw new Error(`unexpected transport import: ${specifier}`)
- })
- await transportModule.evaluate()
- return transportModule.namespace
- }
- function assertNoIdentityFields(value) {
- if (!value || typeof value !== 'object') return
- for (const [key, child] of Object.entries(value)) {
- assert.ok(!['source', 'coachId', 'uploaderId'].includes(key), `unexpected identity field: ${key}`)
- assertNoIdentityFields(child)
- }
- }
- async function createUploadHarness(response, progress = 37) {
- let request
- const progressValues = []
- const uni = {
- getStorageSync(key) {
- assert.equal(key, 'token')
- return 'token-123'
- },
- uploadFile(options) {
- request = options
- return {
- onProgressUpdate(callback) {
- callback({ progress })
- setImmediate(() => options.success(response))
- }
- }
- }
- }
- const transport = await loadTransport({ uni })
- return {
- request: () => request,
- progressValues,
- promise: transport.uploadTeamBackgroundFile(12, '/tmp/brief.pdf', value => progressValues.push(value))
- }
- }
- async function testUploadTransport() {
- const stringHarness = await createUploadHarness({
- statusCode: 200,
- data: JSON.stringify({ code: 0, data: { id: 7 } })
- })
- assert.equal((await stringHarness.promise).id, 7)
- assert.equal(stringHarness.request().url, 'https://api.example.test/app/core/user/team/12/background-files')
- assert.equal(stringHarness.request().filePath, '/tmp/brief.pdf')
- assert.equal(stringHarness.request().name, 'file')
- assert.equal(stringHarness.request().header.token, 'token-123')
- assert.deepEqual(stringHarness.progressValues, [37])
- assertNoIdentityFields(stringHarness.request())
- const objectHarness = await createUploadHarness({ statusCode: 200, data: { code: 0, data: { id: 8 } } })
- assert.equal((await objectHarness.promise).id, 8)
- const statusHarness = await createUploadHarness({ statusCode: 500, data: { code: 0 } })
- await assert.rejects(statusHarness.promise, /上传失败,请重试/)
- const codeHarness = await createUploadHarness({ statusCode: 200, data: { code: 9, msg: '上传业务失败' } })
- await assert.rejects(codeHarness.promise, /上传业务失败/)
- const invalidJsonHarness = await createUploadHarness({ statusCode: 200, data: '<invalid-json>' })
- await assert.rejects(invalidJsonHarness.promise, /上传失败,请重试/)
- }
- async function testApiTransport() {
- const calls = []
- const api = {
- get(...args) {
- calls.push(['get', ...args])
- return Promise.resolve({ data: { code: 0, data: [{ id: 1 }] } })
- },
- del(...args) {
- calls.push(['del', ...args])
- return Promise.resolve({ data: { code: 0, data: true } })
- },
- post(...args) {
- calls.push(['post', ...args])
- return Promise.resolve({ data: { code: 0, data: { sessionId: 'session-1' } } })
- }
- }
- const transport = await loadTransport({ api })
- assert.deepEqual(await transport.listTeamBackgroundFiles(12), [{ id: 1 }])
- assert.equal(await transport.disableTeamBackgroundFile(12, 34), true)
- assert.deepEqual(await transport.createTeamBackgroundSession(12), { sessionId: 'session-1' })
- assert.equal(calls[0][0], 'get')
- assert.equal(calls[0][1], '/core/user/team/12/background-files')
- assert.equal(calls[1][0], 'del')
- assert.equal(calls[1][1], '/core/user/team/12/background-files/34')
- assert.equal(calls[2][0], 'post')
- assert.equal(calls[2][1], '/core/user/team/12/background-upload-session')
- for (const call of calls) {
- assert.equal(Object.keys(call[2]).length, 0)
- assert.equal(call[3], false)
- assertNoIdentityFields(call)
- }
- const failingTransport = await loadTransport({
- api: {
- get: () => Promise.resolve({ data: { code: 3, msg: '列表业务失败' } })
- }
- })
- await assert.rejects(failingTransport.listTeamBackgroundFiles(12), /列表业务失败/)
- assert.equal(transport.uploadTeamBackgroundFile.length, 2)
- assert.equal(transport.listTeamBackgroundFiles.length, 1)
- assert.equal(transport.disableTeamBackgroundFile.length, 2)
- assert.equal(transport.createTeamBackgroundSession.length, 1)
- assert.equal(transport.downloadTeamBackgroundFile.length, 2)
- }
- const TEXT_PREVIEW_LIMIT = 256 * 1024
- async function createDownloadHarness({ result, responseHeaders, downloadError, openError,
- fileSize, fileContent = '', fileInfoError, readError, withHeadersListener = true } = {}) {
- let request
- let openRequest
- let fileInfoRequest
- let readRequest
- let fileSystemManagerCalls = 0
- const resolvedFileSize = fileSize === undefined
- ? Buffer.byteLength(fileContent, 'utf8')
- : fileSize
- const uni = {
- getStorageSync(key) {
- assert.equal(key, 'token')
- return 'token-456'
- },
- downloadFile(options) {
- request = options
- setImmediate(() => {
- if (downloadError) options.fail(downloadError)
- else options.success(result)
- })
- if (!withHeadersListener) return {}
- return {
- onHeadersReceived(callback) {
- if (responseHeaders) callback({ header: responseHeaders })
- }
- }
- }
- }
- const wx = {
- openDocument(options) {
- openRequest = options
- setImmediate(() => {
- if (openError) options.fail(openError)
- else options.success('opened')
- })
- },
- getFileSystemManager() {
- fileSystemManagerCalls += 1
- return {
- getFileInfo(options) {
- fileInfoRequest = options
- setImmediate(() => {
- if (fileInfoError) options.fail(fileInfoError)
- else options.success({ size: resolvedFileSize })
- })
- },
- readFile(options) {
- readRequest = options
- setImmediate(() => {
- if (readError) options.fail(readError)
- else options.success({ data: fileContent })
- })
- }
- }
- }
- }
- const transport = await loadTransport({ uni, wx })
- return {
- request: () => request,
- openRequest: () => openRequest,
- fileInfoRequest: () => fileInfoRequest,
- readRequest: () => readRequest,
- fileSystemManagerCalls: () => fileSystemManagerCalls,
- promise: transport.downloadTeamBackgroundFile(12, 34)
- }
- }
- async function testDownloadTransport() {
- const successHarness = await createDownloadHarness({
- result: { statusCode: 200, tempFilePath: '/tmp/download.pdf' },
- responseHeaders: {
- 'cOnTeNt-TyPe': 'application/pdf',
- 'CONTENT-disPOSITION': 'attachment; filename="download.pdf"'
- }
- })
- const documentPreview = await successHarness.promise
- assert.equal(documentPreview.mode, 'document')
- assert.equal(documentPreview.fileName, 'download.pdf')
- assert.equal(documentPreview.filePath, '/tmp/download.pdf')
- assert.equal(successHarness.request().url, 'https://api.example.test/app/core/user/team/12/background-files/34/download')
- assert.equal(successHarness.request().header.token, 'token-456')
- assert.equal(successHarness.openRequest(), undefined, 'transport must not open a document before component context checks')
- assertNoIdentityFields(successHarness.request())
- const resultHeaderHarness = await createDownloadHarness({
- result: {
- statusCode: 200,
- tempFilePath: '/tmp/result-header.docx',
- header: {
- 'content-type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
- 'content-disposition': 'attachment; filename="result-header.docx"'
- }
- },
- withHeadersListener: false
- })
- const resultHeaderPreview = await resultHeaderHarness.promise
- assert.equal(resultHeaderPreview.mode, 'document')
- assert.equal(resultHeaderPreview.fileName, 'result-header.docx')
- assert.equal(resultHeaderPreview.filePath, '/tmp/result-header.docx')
- assert.equal(resultHeaderHarness.openRequest(), undefined)
- assert.equal(resultHeaderHarness.fileSystemManagerCalls(), 0)
- const txtHarness = await createDownloadHarness({
- result: {
- statusCode: 200,
- tempFilePath: '/tmp/text-preview.txt',
- headers: {
- 'Content-Type': 'text/plain; charset=UTF-8',
- 'Content-Disposition': 'attachment; filename="fallback.txt"; '
- + "filename*=UTF-8''%E8%83%8C%E6%99%AF%20%E8%B5%84%E6%96%99.txt"
- }
- },
- fileSize: 5,
- fileContent: 'hello',
- withHeadersListener: false
- })
- const txtPreview = await txtHarness.promise
- assert.equal(txtHarness.openRequest(), undefined, 'txt must not call openDocument')
- assert.equal(txtPreview.mode, 'text')
- assert.equal(txtPreview.fileName, '背景 资料.txt')
- assert.equal(txtPreview.content, 'hello')
- assert.equal(txtPreview.truncated, false)
- assert.equal(txtHarness.fileInfoRequest().filePath, '/tmp/text-preview.txt')
- assert.equal(txtHarness.readRequest().filePath, '/tmp/text-preview.txt')
- assert.equal(txtHarness.readRequest().encoding, 'utf8')
- assert.equal(txtHarness.readRequest().position, 0)
- assert.equal(txtHarness.readRequest().length, 5)
- const emptyTextHarness = await createDownloadHarness({
- result: { statusCode: 200, tempFilePath: '/tmp/empty.txt' },
- responseHeaders: {
- 'Content-Type': 'text/plain',
- 'Content-Disposition': 'attachment; filename="empty.txt"'
- },
- fileSize: 0
- })
- const emptyTextPreview = await emptyTextHarness.promise
- assert.equal(emptyTextPreview.mode, 'text')
- assert.equal(emptyTextPreview.fileName, 'empty.txt')
- assert.equal(emptyTextPreview.content, '')
- assert.equal(emptyTextPreview.truncated, false)
- assert.equal(emptyTextHarness.readRequest(), undefined)
- const invalidEncodedNameHarness = await createDownloadHarness({
- result: { statusCode: 200, tempFilePath: '/tmp/fallback.txt' },
- responseHeaders: {
- 'Content-Type': 'text/plain',
- 'Content-Disposition': 'attachment; filename="fallback.txt"; '
- + "filename*=UTF-8''%E0%A4%A"
- },
- fileSize: 8,
- fileContent: 'fallback'
- })
- const invalidEncodedNamePreview = await invalidEncodedNameHarness.promise
- assert.equal(invalidEncodedNamePreview.fileName, 'fallback.txt')
- assert.equal(invalidEncodedNamePreview.content, 'fallback')
- const mdContent = 'm'.repeat(TEXT_PREVIEW_LIMIT)
- const mdHarness = await createDownloadHarness({
- result: { statusCode: 200, tempFilePath: '/tmp/guide.md' },
- responseHeaders: {
- 'Content-Type': 'text/markdown; charset=UTF-8',
- 'Content-Disposition': 'attachment; filename="guide.md"'
- },
- fileSize: 50 * 1024 * 1024,
- fileContent: mdContent
- })
- const mdPreview = await mdHarness.promise
- assert.equal(mdHarness.openRequest(), undefined)
- assert.equal(mdPreview.mode, 'text')
- assert.equal(mdPreview.fileName, 'guide.md')
- assert.equal(mdPreview.content.length, TEXT_PREVIEW_LIMIT)
- assert.equal(mdPreview.truncated, true)
- assert.equal(mdHarness.readRequest().length, TEXT_PREVIEW_LIMIT)
- const csvContent = 'c'.repeat(TEXT_PREVIEW_LIMIT)
- const csvHarness = await createDownloadHarness({
- result: { statusCode: 200, tempFilePath: '/tmp/data.csv' },
- responseHeaders: {
- 'Content-Type': 'text/csv',
- 'Content-Disposition': 'attachment; filename=data.csv'
- },
- fileSize: TEXT_PREVIEW_LIMIT,
- fileContent: csvContent
- })
- const csvPreview = await csvHarness.promise
- assert.equal(csvHarness.openRequest(), undefined)
- assert.equal(csvPreview.mode, 'text')
- assert.equal(csvPreview.fileName, 'data.csv')
- assert.equal(csvPreview.content.length, TEXT_PREVIEW_LIMIT)
- assert.equal(csvPreview.truncated, false)
- assert.equal(csvHarness.readRequest().length, TEXT_PREVIEW_LIMIT)
- const fileInfoFailureHarness = await createDownloadHarness({
- result: { statusCode: 200, tempFilePath: '/tmp/file-info-failure.txt' },
- responseHeaders: {
- 'Content-Type': 'text/plain',
- 'Content-Disposition': 'attachment; filename="file-info-failure.txt"'
- },
- fileInfoError: new Error('getFileInfo failed')
- })
- await assert.rejects(fileInfoFailureHarness.promise, /文件读取失败,请重试/)
- assert.equal(fileInfoFailureHarness.openRequest(), undefined)
- assert.equal(fileInfoFailureHarness.readRequest(), undefined)
- const readFailureHarness = await createDownloadHarness({
- result: { statusCode: 200, tempFilePath: '/tmp/read-failure.md' },
- responseHeaders: {
- 'Content-Type': 'text/markdown',
- 'Content-Disposition': 'attachment; filename="read-failure.md"'
- },
- fileSize: 10,
- readError: new Error('readFile failed')
- })
- await assert.rejects(readFailureHarness.promise, /文件读取失败,请重试/)
- assert.equal(readFailureHarness.openRequest(), undefined)
- const unknownExtensionHarness = await createDownloadHarness({
- result: { statusCode: 200, tempFilePath: '/tmp/unknown-file' },
- responseHeaders: {
- 'Content-Type': 'application/octet-stream',
- 'Content-Disposition': 'attachment; filename="archive.zip"'
- }
- })
- await assert.rejects(unknownExtensionHarness.promise, /文件类型不支持预览/)
- assert.equal(unknownExtensionHarness.openRequest(), undefined)
- assert.equal(unknownExtensionHarness.fileSystemManagerCalls(), 0)
- const jsonHarness = await createDownloadHarness({
- result: { statusCode: 200, tempFilePath: '/tmp/error.json' },
- responseHeaders: {
- 'Content-Type': 'application/json;charset=UTF-8',
- 'Content-Disposition': 'attachment; filename="error.json"'
- }
- })
- await assert.rejects(jsonHarness.promise, /下载失败,请重试/)
- assert.equal(jsonHarness.openRequest(), undefined)
- const htmlHarness = await createDownloadHarness({
- result: { statusCode: 200, tempFilePath: '/tmp/error.html' },
- responseHeaders: {
- 'content-type': 'text/html; charset=UTF-8',
- 'content-disposition': 'attachment; filename="error.html"'
- }
- })
- await assert.rejects(htmlHarness.promise, /下载失败,请重试/)
- assert.equal(htmlHarness.openRequest(), undefined)
- const missingHeadersHarness = await createDownloadHarness({
- result: { statusCode: 200, tempFilePath: '/tmp/unknown.pdf' },
- withHeadersListener: false
- })
- await assert.rejects(missingHeadersHarness.promise, /下载失败,请重试/)
- assert.equal(missingHeadersHarness.openRequest(), undefined)
- const statusHarness = await createDownloadHarness({ result: { statusCode: 500 } })
- await assert.rejects(statusHarness.promise, /下载失败,请重试/)
- assert.equal(statusHarness.openRequest(), undefined)
- const downloadError = new Error('download network error')
- const downloadFailureHarness = await createDownloadHarness({ downloadError })
- await assert.rejects(downloadFailureHarness.promise, /download network error/)
- const noTransportOpenHarness = await createDownloadHarness({
- result: { statusCode: 200, tempFilePath: '/tmp/download.pdf' },
- responseHeaders: {
- 'Content-Type': 'application/pdf',
- 'Content-Disposition': 'attachment; filename="download.pdf"'
- },
- openError: new Error('must not be reached')
- })
- assert.equal((await noTransportOpenHarness.promise).mode, 'document')
- assert.equal(noTransportOpenHarness.openRequest(), undefined)
- }
- function readSfcScript(filePath) {
- const source = fs.readFileSync(filePath, 'utf8')
- const openingTag = '<script>'
- const start = source.indexOf(openingTag)
- const end = source.indexOf('</script>', start + openingTag.length)
- if (start < 0 || end < 0) throw new Error(`missing script block: ${filePath}`)
- return source.slice(start + openingTag.length, end)
- }
- async function loadVueComponent(filePath, { http = {}, uni = {}, wx = {}, apiRules = rules } = {}) {
- const context = vm.createContext({
- uni,
- wx,
- Date,
- Promise,
- String,
- Number,
- Boolean,
- Object,
- Array,
- setInterval,
- clearInterval
- })
- const vueStub = new vm.SyntheticModule(['default'], function () {
- this.setExport('default', {})
- }, { context, identifier: 'test:vue-component' })
- const rulesStub = new vm.SyntheticModule(['default'], function () {
- this.setExport('default', apiRules)
- }, { context, identifier: 'test:team-background-rules' })
- const httpNames = [
- 'createTeamBackgroundSession',
- 'disableTeamBackgroundFile',
- 'downloadTeamBackgroundFile',
- 'listTeamBackgroundFiles',
- 'uploadTeamBackgroundFile'
- ]
- const httpStub = new vm.SyntheticModule(httpNames, function () {
- for (const name of httpNames) {
- this.setExport(name, http[name] || (() => Promise.resolve()))
- }
- }, { context, identifier: 'test:team-background-http' })
- const componentModule = new vm.SourceTextModule(readSfcScript(filePath), {
- context,
- identifier: pathToFileURL(filePath).href
- })
- await componentModule.link(specifier => {
- if (specifier === '@/http/teamBackgroundFile.js') return httpStub
- if (specifier === '@/utils/teamBackgroundFile.js') return rulesStub
- if (specifier.endsWith('.vue')) return vueStub
- throw new Error(`unexpected component import: ${specifier}`)
- })
- await componentModule.evaluate()
- return componentModule.namespace.default
- }
- function instantiateComponent(component, overrides = {}) {
- const base = Object.assign({ initialCount: 0 }, overrides)
- const data = typeof component.data === 'function' ? component.data.call(base) : {}
- const instance = Object.assign(base, data, overrides)
- for (const [name, method] of Object.entries(component.methods || {})) {
- instance[name] = method.bind(instance)
- }
- return instance
- }
- function createDeferred() {
- let resolve
- let reject
- const promise = new Promise((resolvePromise, rejectPromise) => {
- resolve = resolvePromise
- reject = rejectPromise
- })
- return { promise, resolve, reject }
- }
- const backgroundComponentPath = path.join(__dirname, '../components/CusTeamBackgroundFiles/index.vue')
- const teamFillComponentPath = path.join(__dirname, '../components/CusTeamInfoFill/index.vue')
- const createTeamPagePath = path.join(__dirname, '../pagesPublish/fillTeamInfo.vue')
- const createListComponentPath = path.join(__dirname, '../pagesHome/components/createList.vue')
- const teamEditPagePath = path.join(__dirname, '../pagesMy/teamEdit.vue')
- const headerComponentPath = path.join(__dirname, '../components/CusHeader/index.vue')
- async function testDeferredPcSelectionSurvivesSessionFailure() {
- const component = await loadVueComponent(backgroundComponentPath, {
- http: {
- createTeamBackgroundSession: () => Promise.reject(new Error('session unavailable'))
- }
- })
- const instance = instantiateComponent(component, {
- teamId: '',
- editable: true,
- boundTeamId: 12,
- pcUploadDeferred: true,
- $showToast() {},
- $emit() {}
- })
- await assert.rejects(instance.waitForDeferredPcUpload(12), /session unavailable/)
- assert.equal(instance.pcUploadDeferred, true)
- assert.equal(instance.pcDialogVisible, false)
- assert.equal(instance.deferredPcResolve, null)
- }
- async function testSessionRefreshFailureKeepsOldTimer() {
- const component = await loadVueComponent(backgroundComponentPath, {
- http: {
- createTeamBackgroundSession: () => Promise.reject(new Error('refresh failed'))
- }
- })
- const instance = instantiateComponent(component, {
- teamId: '', editable: true, boundTeamId: 12, $showToast() {}, $emit() {}
- })
- const oldSession = { code: '111111', expiresAt: '2099-01-01 00:00:00' }
- const oldTimer = setInterval(() => {}, 60 * 1000)
- instance.session = oldSession
- instance.sessionTimer = oldTimer
- try {
- await assert.rejects(instance.openPcSession(12), /refresh failed/)
- assert.equal(instance.session, oldSession)
- assert.equal(instance.sessionTimer, oldTimer)
- } finally {
- clearInterval(oldTimer)
- instance.sessionTimer = null
- }
- }
- async function testDeferredPcFinishWaitsForListRefresh() {
- const component = await loadVueComponent(backgroundComponentPath, {
- http: {
- createTeamBackgroundSession: () => Promise.resolve({
- code: '123456',
- uploadUrl: 'https://upload.example/',
- expiresAt: '2099-01-01 00:00:00'
- }),
- listTeamBackgroundFiles: () => Promise.reject(new Error('list refresh failed'))
- }
- })
- const instance = instantiateComponent(component, {
- teamId: '', editable: true, boundTeamId: 12, pcUploadDeferred: true,
- $showToast() {}, $emit() {}
- })
- const waiting = instance.waitForDeferredPcUpload(12)
- await new Promise(resolve => setImmediate(resolve))
- assert.equal(typeof instance.deferredPcResolve, 'function')
- await instance.finishDeferredPcUpload()
- await assert.rejects(waiting, /list refresh failed/)
- }
- async function testTeamContextResetAndUploadIsolation() {
- const uploads = []
- const component = await loadVueComponent(backgroundComponentPath, {
- http: {
- uploadTeamBackgroundFile(teamId, filePath, onProgress) {
- const deferred = createDeferred()
- uploads.push({ teamId, filePath, onProgress, deferred })
- return deferred.promise
- },
- listTeamBackgroundFiles: () => Promise.resolve([])
- }
- })
- let resolverCalls = 0
- const instance = instantiateComponent(component, {
- teamId: '',
- editable: true,
- $showToast() {},
- $emit() {}
- })
- instance.resetTeamContext(1)
- const oldItem = rules.createBackgroundQueueItem(
- { name: 'old.pdf', path: '/tmp/old.pdf', size: 1 }, 'old'
- )
- instance.queue.push(oldItem)
- instance.deferredPcResolve = () => { resolverCalls += 1 }
- const oldFlush = instance.flushPendingFiles(1)
- await new Promise(resolve => setImmediate(resolve))
- assert.equal(uploads.length, 1)
- instance.files = [{ id: 11, fileName: 'old.pdf' }]
- instance.count = 1
- instance.filesReady = true
- instance.session = { code: '111111' }
- instance.pcDialogVisible = true
- instance.textPreview = rules.createTextPreviewState({ fileName: 'old.txt', content: 'old' })
- instance.resetTeamContext(2)
- assert.equal(instance.boundTeamId, 2)
- assert.equal(instance.files.length, 0)
- assert.equal(instance.count, 0)
- assert.equal(instance.queue.length, 0)
- assert.equal(instance.filesReady, false)
- assert.equal(instance.session, null)
- assert.equal(instance.pcDialogVisible, false)
- assert.equal(instance.textPreview.content, '')
- assert.equal(resolverCalls, 1)
- const newItem = rules.createBackgroundQueueItem(
- { name: 'new.pdf', path: '/tmp/new.pdf', size: 1 }, 'new'
- )
- instance.queue.push(newItem)
- const newFlush = instance.flushPendingFiles(2)
- await new Promise(resolve => setImmediate(resolve))
- assert.equal(uploads.length, 2, 'the new team must not reuse the old upload promise')
- assert.deepEqual(uploads.map(item => item.teamId), [1, 2])
- uploads[0].onProgress(75)
- assert.equal(oldItem.progress, 0, 'stale upload progress must not mutate the old item after reset')
- uploads[0].deferred.resolve({ id: 101 })
- await oldFlush
- assert.equal(newItem.status, 'uploading')
- uploads[1].deferred.resolve({ id: 202 })
- await newFlush
- assert.equal(newItem.status, 'success')
- }
- async function testFileActionsWaitForCurrentListAndDocumentContext() {
- const listDeferred = createDeferred()
- const downloadDeferred = createDeferred()
- const opened = []
- const toasts = []
- let downloadCalls = 0
- const wx = {
- openDocument(options) {
- opened.push(options.filePath)
- setImmediate(() => options.success())
- }
- }
- const component = await loadVueComponent(backgroundComponentPath, {
- wx,
- http: {
- listTeamBackgroundFiles: () => listDeferred.promise,
- downloadTeamBackgroundFile: () => {
- downloadCalls += 1
- return downloadDeferred.promise
- }
- }
- })
- const instance = instantiateComponent(component, {
- teamId: '',
- editable: true,
- $showToast(message) { toasts.push(message) },
- $emit() {}
- })
- instance.resetTeamContext(3)
- const oldFile = { id: 31, fileName: 'old.pdf' }
- instance.files = [oldFile]
- instance.filesReady = true
- const refresh = instance.refreshFiles(3)
- await instance.previewFile(oldFile)
- assert.equal(downloadCalls, 0, 'old file ids must be disabled while the current list is loading')
- listDeferred.resolve([oldFile])
- await refresh
- const latePreview = instance.previewFile(oldFile)
- assert.equal(downloadCalls, 1)
- instance.resetTeamContext(4)
- downloadDeferred.resolve({ mode: 'document', fileName: 'old.pdf', filePath: '/tmp/old.pdf' })
- await latePreview
- assert.deepEqual(opened, [], 'a stale download must not open a native document')
- const validComponent = await loadVueComponent(backgroundComponentPath, {
- wx,
- http: {
- downloadTeamBackgroundFile: () => Promise.resolve({
- mode: 'document', fileName: 'current.pdf', filePath: '/tmp/current.pdf'
- })
- }
- })
- const validInstance = instantiateComponent(validComponent, {
- teamId: '', editable: true, $showToast(message) { toasts.push(message) }, $emit() {}
- })
- validInstance.resetTeamContext(5)
- const currentFile = { id: 51, fileName: 'current.pdf' }
- validInstance.files = [currentFile]
- validInstance.filesReady = true
- await validInstance.previewFile(currentFile)
- assert.deepEqual(opened, ['/tmp/current.pdf'])
- const failingComponent = await loadVueComponent(backgroundComponentPath, {
- wx: {
- openDocument(options) { setImmediate(() => options.fail(new Error('open failed'))) }
- },
- http: {
- downloadTeamBackgroundFile: () => Promise.resolve({
- mode: 'document', fileName: 'broken.pdf', filePath: '/tmp/broken.pdf'
- })
- }
- })
- const failingInstance = instantiateComponent(failingComponent, {
- teamId: '', editable: true, $showToast(message) { toasts.push(message) }, $emit() {}
- })
- failingInstance.resetTeamContext(6)
- const brokenFile = { id: 61, fileName: 'broken.pdf' }
- failingInstance.files = [brokenFile]
- failingInstance.filesReady = true
- await failingInstance.previewFile(brokenFile)
- assert.ok(toasts.some(message => String(message).includes('open failed')))
- }
- async function testTeamFillFlushesBeforeEmitAndLocks() {
- const component = await loadVueComponent(teamFillComponentPath)
- const flushDeferred = createDeferred()
- let flushCalls = 0
- let emitCalls = 0
- const instance = instantiateComponent(component, {
- teamId: 12,
- qtype: false,
- $props: { qtype: false },
- $showToast() {},
- $emit() { emitCalls += 1 },
- $refs: {
- backgroundRef: {
- flushPendingFiles() { flushCalls += 1; return flushDeferred.promise },
- hasUnresolvedFiles() { return false }
- }
- }
- })
- instance.teamInfo = {
- teamName: '团队', enterpriseName: '公司', enterpriseWebsite: '无',
- districtId: 1, industryId: 2, functionIds: [], orgIds: []
- }
- const first = instance.handleConfirm()
- const second = instance.handleConfirm()
- assert.equal(flushCalls, 1)
- assert.equal(emitCalls, 0)
- flushDeferred.resolve({ success: 1, failed: 0 })
- await Promise.all([first, second])
- assert.equal(emitCalls, 1)
- }
- async function testCreatedTeamResumeAndSubmitLock() {
- const component = await loadVueComponent(createTeamPagePath)
- let teamPostCalls = 0
- let flushCalls = 0
- const toasts = []
- const instance = instantiateComponent(component, {
- $api: {
- post(url) {
- assert.equal(url, '/core/user/team')
- teamPostCalls += 1
- return Promise.resolve({ data: { code: 0, data: { teamId: 88 } } })
- }
- },
- $showToast(message) { toasts.push(message) },
- $showModal: () => Promise.resolve(),
- $refs: {
- teamRef: {
- flushBackgroundFiles() {
- flushCalls += 1
- if (flushCalls === 1) return Promise.reject(new Error('列表刷新失败'))
- return Promise.resolve({ success: 0, failed: 0 })
- },
- hasUnresolvedBackgroundFiles: () => false,
- hasDeferredPcUpload: () => false
- }
- }
- })
- let continuations = 0
- instance.continueAfterTeamCreated = async teamId => {
- assert.equal(teamId, 88)
- continuations += 1
- }
- await instance.handleConfirm({ teamName: '团队' })
- assert.equal(teamPostCalls, 1)
- assert.equal(instance.creationState.createdTeamId, 88)
- assert.ok(toasts.some(message => String(message).includes('背景资料')))
- await instance.handleConfirm({ teamName: '团队' })
- assert.equal(teamPostCalls, 1, 'retry after a later-stage failure must not POST a second team')
- assert.equal(continuations, 1)
- const lockedPost = createDeferred()
- const lockedInstance = instantiateComponent(component, {
- $api: { post: () => { teamPostCalls += 1; return lockedPost.promise } },
- $showToast() {},
- $showModal: () => Promise.resolve(),
- $refs: { teamRef: {} }
- })
- const lockedFirst = lockedInstance.handleConfirm({ teamName: '团队' })
- const callsAfterFirst = teamPostCalls
- const lockedSecond = lockedInstance.handleConfirm({ teamName: '团队' })
- assert.equal(teamPostCalls, callsAfterFirst, 'concurrent submission must be ignored')
- lockedPost.resolve({ data: { code: 1, msg: '保存接口失败' } })
- await Promise.all([lockedFirst, lockedSecond])
- }
- async function testCloseAndBackRequirePendingUploadConfirmation() {
- let modalOptions
- let navigateBackCalls = 0
- const uni = {
- showModal(options) { modalOptions = options },
- navigateBack() { navigateBackCalls += 1 }
- }
- const createList = await loadVueComponent(createListComponentPath, { uni })
- const createListInstance = instantiateComponent(createList, {
- $imgBase: 'https://image.example/',
- teamInfoShow: true,
- $refs: {
- teamRef: {
- hasUnresolvedBackgroundFiles: () => true,
- isBackgroundUploadActive: () => false
- }
- }
- })
- createListInstance.requestCloseTeamInfo()
- assert.equal(createListInstance.teamInfoShow, true)
- modalOptions.success({ confirm: false })
- assert.equal(createListInstance.teamInfoShow, true)
- createListInstance.requestCloseTeamInfo()
- modalOptions.success({ confirm: true })
- assert.equal(createListInstance.teamInfoShow, false)
- const teamEdit = await loadVueComponent(teamEditPagePath, { uni })
- const teamEditInstance = instantiateComponent(teamEdit, {
- $refs: {
- teamRef: {
- hasUnresolvedBackgroundFiles: () => true,
- isBackgroundUploadActive: () => true
- }
- }
- })
- teamEditInstance.requestBack()
- assert.equal(navigateBackCalls, 0)
- modalOptions.success({ confirm: false })
- assert.equal(navigateBackCalls, 0)
- teamEditInstance.requestBack()
- modalOptions.success({ confirm: true })
- assert.equal(navigateBackCalls, 1)
- let headerBackEvents = 0
- const header = await loadVueComponent(headerComponentPath, { uni })
- const headerInstance = instantiateComponent(header, {
- interceptBack: true,
- $emit(name) { if (name === 'back') headerBackEvents += 1 }
- })
- headerInstance.toBack('')
- assert.equal(headerBackEvents, 1)
- assert.equal(navigateBackCalls, 1, 'intercepted header back must not navigate directly')
- }
- async function main() {
- const loadedTransport = await loadTransport()
- assert.equal(typeof loadedTransport.downloadTeamBackgroundFile, 'function')
- await testUploadTransport()
- await testApiTransport()
- await testDownloadTransport()
- await testDeferredPcSelectionSurvivesSessionFailure()
- await testSessionRefreshFailureKeepsOldTimer()
- await testDeferredPcFinishWaitsForListRefresh()
- await testTeamContextResetAndUploadIsolation()
- await testFileActionsWaitForCurrentListAndDocumentContext()
- await testTeamFillFlushesBeforeEmitAndLocks()
- await testCreatedTeamResumeAndSubmitLock()
- await testCloseAndBackRequirePendingUploadConfirmation()
- console.log('team background transport: PASS')
- console.log('team background component behavior: PASS')
- }
- main().catch(error => {
- console.error(error)
- process.exitCode = 1
- })
|