Extract packAndSaveCertZip for reuse; keep vehicle API; name folders with _2 on collisions. Co-authored-by: Cursor <cursoragent@cursor.com>
354 lines
9.8 KiB
TypeScript
354 lines
9.8 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
import { describe, it } from 'node:test'
|
|
import {
|
|
collectVehicleCertZipEntries,
|
|
buildVehicleCertZipFilename,
|
|
buildAndSaveVehicleCertZip,
|
|
collectPersonnelCertZipEntries,
|
|
buildPersonnelCertZipFilename,
|
|
buildAndSavePersonnelCertZip,
|
|
uniqueZipPath,
|
|
sanitizeZipPathSegment
|
|
} from './pl-vehicle-cert-zip'
|
|
|
|
describe('sanitizeZipPathSegment', () => {
|
|
it('replaces unsafe path chars', () => {
|
|
assert.equal(sanitizeZipPathSegment('皖A/D12\\345'), '皖A_D12_345')
|
|
assert.equal(sanitizeZipPathSegment(' 皖A '), '皖A')
|
|
})
|
|
})
|
|
|
|
describe('collectVehicleCertZipEntries', () => {
|
|
it('skips vehicles and certs without urls; prefixes 主·/挂· when trailer certs exist', () => {
|
|
const entries = collectVehicleCertZipEntries([
|
|
{
|
|
id: 'v1',
|
|
licensePlate: '皖AD12345',
|
|
trailerNo: '挂A8888',
|
|
certList: [
|
|
{
|
|
id: 'c1',
|
|
ownerType: 'vehicle',
|
|
ownerId: 'v1',
|
|
status: '1',
|
|
certName: '行驶证',
|
|
vehiclePart: 'head',
|
|
attachmentUrls: 'https://cdn.example/a.jpg'
|
|
},
|
|
{
|
|
id: 'c2',
|
|
ownerType: 'vehicle',
|
|
ownerId: 'v1',
|
|
status: '1',
|
|
certName: '道路运输证',
|
|
vehiclePart: 'head',
|
|
attachmentUrls: ''
|
|
},
|
|
{
|
|
id: 'c3',
|
|
ownerType: 'vehicle',
|
|
ownerId: 'v1',
|
|
status: '1',
|
|
certName: '行驶证',
|
|
vehiclePart: 'trailer',
|
|
attachmentUrls: 'https://cdn.example/b.pdf'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'v2',
|
|
licensePlate: '皖BF67890',
|
|
certList: []
|
|
}
|
|
])
|
|
|
|
assert.equal(entries.length, 1)
|
|
assert.equal(entries[0].vehicleId, 'v1')
|
|
assert.equal(entries[0].plateFolder, '皖AD12345挂A8888')
|
|
assert.equal(entries[0].files.length, 2)
|
|
assert.equal(entries[0].files[0].baseName, '主·行驶证')
|
|
assert.equal(entries[0].files[0].ext, 'jpg')
|
|
assert.equal(entries[0].files[1].baseName, '挂·行驶证')
|
|
assert.equal(entries[0].files[1].ext, 'pdf')
|
|
})
|
|
|
|
it('omits 主·/挂· prefix when no trailer certs on that vehicle', () => {
|
|
const entries = collectVehicleCertZipEntries([
|
|
{
|
|
id: 'v1',
|
|
licensePlate: '皖BF67890',
|
|
certList: [
|
|
{
|
|
id: 'c1',
|
|
ownerType: 'vehicle',
|
|
ownerId: 'v1',
|
|
status: '1',
|
|
certName: '行驶证',
|
|
vehiclePart: 'head',
|
|
attachmentUrls: 'https://cdn.example/a.jpg,https://cdn.example/a2.png'
|
|
}
|
|
]
|
|
}
|
|
])
|
|
assert.equal(entries[0].files.length, 2)
|
|
assert.equal(entries[0].files[0].baseName, '行驶证')
|
|
assert.equal(entries[0].files[1].baseName, '行驶证')
|
|
})
|
|
})
|
|
|
|
describe('buildVehicleCertZipFilename', () => {
|
|
it('names single-vehicle zip with plate', () => {
|
|
assert.equal(
|
|
buildVehicleCertZipFilename([{ plateFolder: '皖AD12345挂A8888' }], '20260908'),
|
|
'车辆证书_皖AD12345挂A8888.zip'
|
|
)
|
|
})
|
|
|
|
it('names multi-vehicle zip with count and date', () => {
|
|
assert.equal(
|
|
buildVehicleCertZipFilename(
|
|
[{ plateFolder: 'A' }, { plateFolder: 'B' }],
|
|
'20260908'
|
|
),
|
|
'车辆证书批量_2辆_20260908.zip'
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('uniqueZipPath', () => {
|
|
it('appends _2 _3 on collisions', () => {
|
|
const used = new Set<string>()
|
|
assert.equal(uniqueZipPath(used, '行驶证.jpg'), '行驶证.jpg')
|
|
assert.equal(uniqueZipPath(used, '行驶证.jpg'), '行驶证_2.jpg')
|
|
assert.equal(uniqueZipPath(used, '行驶证.jpg'), '行驶证_3.jpg')
|
|
})
|
|
})
|
|
|
|
describe('buildAndSaveVehicleCertZip', () => {
|
|
it('packs single vehicle flat; skips failed blobs; returns counts', async () => {
|
|
const saved: { name: string; size: number }[] = []
|
|
const result = await buildAndSaveVehicleCertZip(
|
|
[
|
|
{
|
|
id: 'v1',
|
|
licensePlate: '皖AD12345',
|
|
certList: [
|
|
{
|
|
id: 'c1',
|
|
ownerType: 'vehicle',
|
|
ownerId: 'v1',
|
|
status: '1',
|
|
certName: '行驶证',
|
|
vehiclePart: 'head',
|
|
attachmentUrls: 'https://cdn.example/ok.jpg,https://cdn.example/bad.jpg'
|
|
}
|
|
]
|
|
}
|
|
],
|
|
{
|
|
yyyymmdd: '20260908',
|
|
fetchBlob: async (fileUrl) => {
|
|
if (fileUrl.includes('bad')) throw new Error('fail')
|
|
return new Blob([fileUrl], { type: 'application/octet-stream' })
|
|
},
|
|
saveAs: async (blob, name) => {
|
|
saved.push({ name, size: blob.size })
|
|
}
|
|
}
|
|
)
|
|
|
|
assert.equal(result.okCount, 1)
|
|
assert.equal(result.failCount, 1)
|
|
assert.equal(result.zipName, '车辆证书_皖AD12345.zip')
|
|
assert.equal(saved.length, 1)
|
|
assert.equal(saved[0].name, '车辆证书_皖AD12345.zip')
|
|
})
|
|
|
|
it('uses subfolders for multiple vehicles', async () => {
|
|
const paths: string[] = []
|
|
await buildAndSaveVehicleCertZip(
|
|
[
|
|
{
|
|
id: 'v1',
|
|
licensePlate: '皖A1',
|
|
certList: [
|
|
{
|
|
id: 'c1',
|
|
ownerType: 'vehicle',
|
|
ownerId: 'v1',
|
|
status: '1',
|
|
certName: '行驶证',
|
|
vehiclePart: 'head',
|
|
attachmentUrls: 'https://cdn.example/a.jpg'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'v2',
|
|
licensePlate: '皖B2',
|
|
certList: [
|
|
{
|
|
id: 'c2',
|
|
ownerType: 'vehicle',
|
|
ownerId: 'v2',
|
|
status: '1',
|
|
certName: '营运证',
|
|
vehiclePart: 'head',
|
|
attachmentUrls: 'https://cdn.example/b.pdf'
|
|
}
|
|
]
|
|
}
|
|
],
|
|
{
|
|
yyyymmdd: '20260908',
|
|
fetchBlob: async () => new Blob(['x']),
|
|
saveAs: async () => {},
|
|
onFileAdded: (zipPath) => paths.push(zipPath)
|
|
}
|
|
)
|
|
assert.deepEqual(paths.sort(), ['皖A1/行驶证.jpg', '皖B2/营运证.pdf'].sort())
|
|
})
|
|
|
|
it('returns empty when no attachments', async () => {
|
|
const result = await buildAndSaveVehicleCertZip(
|
|
[{ id: 'v1', licensePlate: '皖A1', certList: [] }],
|
|
{
|
|
yyyymmdd: '20260908',
|
|
fetchBlob: async () => new Blob(['x']),
|
|
saveAs: async () => {}
|
|
}
|
|
)
|
|
assert.equal(result.okCount, 0)
|
|
assert.equal(result.failCount, 0)
|
|
assert.equal(result.zipName, '')
|
|
})
|
|
})
|
|
|
|
describe('collectPersonnelCertZipEntries', () => {
|
|
it('uses personnel name folders; dedupes same names with _2; skips empty', () => {
|
|
const entries = collectPersonnelCertZipEntries([
|
|
{
|
|
id: 'p1',
|
|
personnelName: '张三',
|
|
certList: [
|
|
{
|
|
id: 'c1',
|
|
ownerType: 'personnel',
|
|
ownerId: 'p1',
|
|
status: '1',
|
|
certName: '驾驶证',
|
|
attachmentUrls: 'https://cdn.example/d.jpg'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'p2',
|
|
personnelName: '张三',
|
|
certList: [
|
|
{
|
|
id: 'c2',
|
|
ownerType: 'personnel',
|
|
ownerId: 'p2',
|
|
status: '1',
|
|
certName: '从业资格证',
|
|
attachmentUrls: 'https://cdn.example/q.pdf'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'p3',
|
|
personnelName: '李四',
|
|
certList: []
|
|
}
|
|
])
|
|
assert.equal(entries.length, 2)
|
|
assert.equal(entries[0].folderLabel, '张三')
|
|
assert.equal(entries[1].folderLabel, '张三_2')
|
|
assert.equal(entries[0].files[0].baseName, '驾驶证')
|
|
assert.equal(entries[1].files[0].baseName, '从业资格证')
|
|
})
|
|
|
|
it('does not add 主·/挂· for personnel certs', () => {
|
|
const entries = collectPersonnelCertZipEntries([
|
|
{
|
|
id: 'p1',
|
|
personnelName: '王五',
|
|
certList: [
|
|
{
|
|
id: 'c1',
|
|
ownerType: 'personnel',
|
|
ownerId: 'p1',
|
|
status: '1',
|
|
certName: '驾驶证',
|
|
vehiclePart: 'trailer',
|
|
attachmentUrls: 'https://cdn.example/a.jpg'
|
|
}
|
|
]
|
|
}
|
|
])
|
|
assert.equal(entries[0].files[0].baseName, '驾驶证')
|
|
})
|
|
})
|
|
|
|
describe('buildPersonnelCertZipFilename', () => {
|
|
it('names single and multi personnel zips', () => {
|
|
assert.equal(
|
|
buildPersonnelCertZipFilename([{ folderLabel: '张三' }], '20260908'),
|
|
'人员证书_张三.zip'
|
|
)
|
|
assert.equal(
|
|
buildPersonnelCertZipFilename(
|
|
[{ folderLabel: '张三' }, { folderLabel: '张三_2' }],
|
|
'20260908'
|
|
),
|
|
'人员证书批量_2人_20260908.zip'
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('buildAndSavePersonnelCertZip', () => {
|
|
it('packs multi personnel into name subfolders', async () => {
|
|
const paths: string[] = []
|
|
const result = await buildAndSavePersonnelCertZip(
|
|
[
|
|
{
|
|
id: 'p1',
|
|
personnelName: '张三',
|
|
certList: [
|
|
{
|
|
id: 'c1',
|
|
ownerType: 'personnel',
|
|
ownerId: 'p1',
|
|
status: '1',
|
|
certName: '驾驶证',
|
|
attachmentUrls: 'https://cdn.example/a.jpg'
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'p2',
|
|
personnelName: '李四',
|
|
certList: [
|
|
{
|
|
id: 'c2',
|
|
ownerType: 'personnel',
|
|
ownerId: 'p2',
|
|
status: '1',
|
|
certName: '身份证',
|
|
attachmentUrls: 'https://cdn.example/b.png'
|
|
}
|
|
]
|
|
}
|
|
],
|
|
{
|
|
yyyymmdd: '20260908',
|
|
fetchBlob: async () => new Blob(['x']),
|
|
saveAs: async () => {},
|
|
onFileAdded: (p) => paths.push(p)
|
|
}
|
|
)
|
|
assert.equal(result.zipName, '人员证书批量_2人_20260908.zip')
|
|
assert.deepEqual(paths.sort(), ['张三/驾驶证.jpg', '李四/身份证.png'].sort())
|
|
})
|
|
})
|