Files
2026-09-03 08:38:17 +08:00

98 lines
4.7 KiB
JavaScript

import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
import ts from 'typescript';
import * as vue from 'vue';
import { compileScript, parse } from 'vue/compiler-sfc';
function loadTS(source, imports = {}, globals = {}) {
const js = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 } }).outputText;
const exports = {};
new Function('exports', 'require', ...Object.keys(globals), js)(exports, name => {
assert.ok(imports[name], `unexpected module ${name}`);
return imports[name];
}, ...Object.values(globals));
return exports;
}
const helper = loadTS(readFileSync(new URL('../src/utils/avatar-image.ts', import.meta.url), 'utf8'));
test('versioned uploads use thumbnails, while external, local and signed URLs remain intact', () => {
for (const prefix of ['https://cdn.example.com/media/2026/08/', '/uploads/']) {
const src = `${prefix}42-1788189400000-av1.jpg`;
assert.equal(helper.avatarImageSource(src), `${prefix}42-1788189400000-av1-thumb.jpg`);
assert.equal(helper.avatarImageSource(src, 'display'), src);
}
for (const src of ['', undefined, 'https://cdn.example.com/42-1.png', 'https://oauth.example.com/photo.jpg',
'https://cdn.example.com/42-1-av1.jpg?signature=secret', 'https://cdn.example.com/42-1-av1.jpg#hash',
'https://cdn.example.com/42-1-av1-thumb.jpg', 'https://cdn.example.com/42-1-av1-original.png',
'/tmp/42-1-av1.jpg', 'file:///tmp/42-1-av1.jpg', 'blob:https://app.example.com/42-1-av1.jpg']) {
assert.equal(helper.avatarImageSource(src), src || '');
}
});
test('avatar component falls back once, then resets when reused for another user', async () => {
const source = readFileSync(new URL('../src/components/AvatarImage.vue', import.meta.url), 'utf8');
const { descriptor } = parse(source);
const script = compileScript(descriptor, { id: 'avatar-regression' });
const component = loadTS(script.content, { vue, '../utils/avatar-image': helper }).default;
const props = vue.reactive({ src: 'https://cdn.example.com/42-1-av1.jpg', variant: 'thumbnail', lazy: true });
const scope = vue.effectScope();
const state = scope.run(() => component.setup(props, { expose() {} }));
try {
assert.match(state.imageSource.value, /-thumb\.jpg$/);
state.onError();
assert.equal(state.imageSource.value, props.src);
state.onError();
assert.equal(state.imageSource.value, props.src);
props.src = 'https://cdn.example.com/43-2-av1.jpg';
await vue.nextTick();
assert.equal(state.imageSource.value, 'https://cdn.example.com/43-2-av1-thumb.jpg');
props.variant = 'display';
await vue.nextTick();
assert.equal(state.imageSource.value, props.src);
} finally { scope.stop(); }
});
function uploadClient(retry = false) {
const uploads = [];
const storage = { accessToken: 'old-token', refreshToken: 'refresh-token' };
const uni = {
getStorageSync: key => storage[key],
setStorageSync: (key, value) => { storage[key] = value; },
uploadFile(options) {
uploads.push(options);
queueMicrotask(() => options.success(retry && uploads.length === 1
? { statusCode: 401, data: JSON.stringify({ code: 401, message: 'expired' }) }
: { statusCode: 200, data: JSON.stringify({ code: 0, data: { url: 'https://cdn.example.com/42-1-av1.jpg' } }) }));
},
request(options) {
assert.match(options.url, /\/auth\/token\/refresh$/);
queueMicrotask(() => options.success({ statusCode: 200, data: { code: 0, data: { accessToken: 'new-token' } } }));
},
};
const source = readFileSync(new URL('../src/utils/request.ts', import.meta.url), 'utf8')
.replaceAll('import.meta.env.VITE_API_ORIGIN', '"https://api.example.com"');
const client = loadTS(source, { './device': { getDeviceHeaders: () => ({ 'X-Device-Id': 'test-device' }) },
'./network-errors': { isLoopbackServer: () => false } }, { uni });
return { client, uploads };
}
test('avatar upload purpose and refreshed authorization survive a token retry', async () => {
const { client, uploads } = uploadClient(true);
assert.equal(await client.api.uploadAvatar('/tmp/camera.jpg'), 'https://cdn.example.com/42-1-av1.jpg');
assert.equal(uploads.length, 2);
for (const upload of uploads) {
assert.deepEqual(upload.formData, { purpose: 'avatar' });
assert.equal(upload.filePath, '/tmp/camera.jpg');
assert.equal(upload.header['X-Device-Id'], 'test-device');
}
assert.equal(uploads[1].header.Authorization, 'Bearer new-token');
});
test('ordinary media uploads do not request avatar processing', async () => {
const { client, uploads } = uploadClient();
await client.api.uploadMedia('/tmp/evidence.png');
assert.equal(uploads.length, 1);
assert.equal(uploads[0].formData, undefined);
});