333 lines
13 KiB
TypeScript
333 lines
13 KiB
TypeScript
/**
|
||
* URL 重写工具测试
|
||
* 测试 lib/url-rewrite.ts 中的核心功能
|
||
*/
|
||
|
||
export {}
|
||
|
||
// 保存原始环境变量
|
||
const originalEnv = process.env;
|
||
|
||
describe('url-rewrite', () => {
|
||
beforeEach(() => {
|
||
jest.resetModules();
|
||
process.env = { ...originalEnv };
|
||
});
|
||
|
||
afterEach(() => {
|
||
process.env = originalEnv;
|
||
});
|
||
|
||
describe('rewriteStorageUrlForClient', () => {
|
||
describe('基本功能', () => {
|
||
it('应该返回 null 当输入为空', async () => {
|
||
const { rewriteStorageUrlForClient } = await import('../url-rewrite');
|
||
expect(rewriteStorageUrlForClient(null)).toBeNull();
|
||
expect(rewriteStorageUrlForClient(undefined)).toBeNull();
|
||
// 空字符串在第一个检查中被判断为 falsy,返回 '' ?? null = ''
|
||
// 这是代码的实际行为:空字符串返回空字符串
|
||
expect(rewriteStorageUrlForClient('')).toBe('');
|
||
// 空白字符串通过第一个检查,trim 后为空,返回 null
|
||
expect(rewriteStorageUrlForClient(' ')).toBeNull();
|
||
});
|
||
|
||
it('应该保持相对路径不变', async () => {
|
||
const { rewriteStorageUrlForClient } = await import('../url-rewrite');
|
||
expect(rewriteStorageUrlForClient('path/to/file.jpg')).toBe('path/to/file.jpg');
|
||
expect(rewriteStorageUrlForClient('/absolute/path.jpg')).toBe('/absolute/path.jpg');
|
||
});
|
||
|
||
it('应该保持 data: URL 不变', async () => {
|
||
const { rewriteStorageUrlForClient } = await import('../url-rewrite');
|
||
const dataUrl = 'data:image/png;base64,iVBORw0KGgo=';
|
||
expect(rewriteStorageUrlForClient(dataUrl)).toBe(dataUrl);
|
||
});
|
||
|
||
it('应该保持非本地 URL 不变', async () => {
|
||
process.env.SERVER_IP = '192.168.1.100';
|
||
process.env.MINIO_API_PORT = '9000';
|
||
|
||
const { rewriteStorageUrlForClient } = await import('../url-rewrite');
|
||
const externalUrl = 'https://cdn.example.com/file.jpg';
|
||
expect(rewriteStorageUrlForClient(externalUrl)).toBe(externalUrl);
|
||
});
|
||
});
|
||
|
||
describe('localhost 重写', () => {
|
||
it('应该将 localhost URL 重写为 SERVER_IP', async () => {
|
||
process.env.SERVER_IP = '192.168.1.100';
|
||
process.env.MINIO_API_PORT = '9000';
|
||
|
||
const { rewriteStorageUrlForClient } = await import('../url-rewrite');
|
||
const result = rewriteStorageUrlForClient('http://localhost:9000/bucket/file.jpg');
|
||
|
||
expect(result).toBe('http://192.168.1.100:9000/bucket/file.jpg');
|
||
});
|
||
|
||
it('应该将 127.0.0.1 URL 重写为 SERVER_IP', async () => {
|
||
process.env.SERVER_IP = '192.168.1.100';
|
||
process.env.MINIO_API_PORT = '9000';
|
||
|
||
const { rewriteStorageUrlForClient } = await import('../url-rewrite');
|
||
const result = rewriteStorageUrlForClient('http://127.0.0.1:9000/bucket/file.jpg');
|
||
|
||
expect(result).toBe('http://192.168.1.100:9000/bucket/file.jpg');
|
||
});
|
||
|
||
it('没有 SERVER_IP 时应该保持原样', async () => {
|
||
delete process.env.SERVER_IP;
|
||
delete process.env.APP_PUBLIC_URL;
|
||
delete process.env.NEXTAUTH_URL;
|
||
delete process.env.S3_PUBLIC_DOMAIN;
|
||
|
||
const { rewriteStorageUrlForClient } = await import('../url-rewrite');
|
||
const url = 'http://localhost:9000/bucket/file.jpg';
|
||
expect(rewriteStorageUrlForClient(url)).toBe(url);
|
||
});
|
||
});
|
||
|
||
describe('Docker 内部 IP 重写', () => {
|
||
it('应该将 172.17.x.x (docker0) 重写为 SERVER_IP', async () => {
|
||
process.env.SERVER_IP = '192.168.1.100';
|
||
process.env.MINIO_API_PORT = '9000';
|
||
|
||
const { rewriteStorageUrlForClient } = await import('../url-rewrite');
|
||
const result = rewriteStorageUrlForClient('http://172.17.0.2:9000/bucket/file.jpg');
|
||
|
||
expect(result).toBe('http://192.168.1.100:9000/bucket/file.jpg');
|
||
});
|
||
|
||
it('应该将 172.18.x.x (docker network) 重写为 SERVER_IP', async () => {
|
||
process.env.SERVER_IP = '192.168.1.100';
|
||
process.env.MINIO_API_PORT = '9000';
|
||
|
||
const { rewriteStorageUrlForClient } = await import('../url-rewrite');
|
||
const result = rewriteStorageUrlForClient('http://172.18.0.5:9000/bucket/file.jpg');
|
||
|
||
expect(result).toBe('http://192.168.1.100:9000/bucket/file.jpg');
|
||
});
|
||
|
||
it('应该将 172.19-31.x.x 范围内的 IP 重写为 SERVER_IP', async () => {
|
||
process.env.SERVER_IP = '192.168.1.100';
|
||
process.env.MINIO_API_PORT = '9000';
|
||
|
||
const { rewriteStorageUrlForClient } = await import('../url-rewrite');
|
||
|
||
// 测试多个 Docker 网络 IP 段
|
||
const dockerIPs = [
|
||
'172.19.0.1',
|
||
'172.20.0.1',
|
||
'172.25.0.1',
|
||
'172.30.0.1',
|
||
'172.31.255.255',
|
||
];
|
||
|
||
for (const ip of dockerIPs) {
|
||
jest.resetModules();
|
||
process.env.SERVER_IP = '192.168.1.100';
|
||
process.env.MINIO_API_PORT = '9000';
|
||
|
||
const { rewriteStorageUrlForClient: rewrite } = await import('../url-rewrite');
|
||
const result = rewrite(`http://${ip}:9000/bucket/file.jpg`);
|
||
expect(result).toBe('http://192.168.1.100:9000/bucket/file.jpg');
|
||
}
|
||
});
|
||
|
||
it('不应该重写非 Docker 内部 IP (172.16.x.x)', async () => {
|
||
process.env.SERVER_IP = '192.168.1.100';
|
||
process.env.MINIO_API_PORT = '9000';
|
||
|
||
const { rewriteStorageUrlForClient } = await import('../url-rewrite');
|
||
// 172.16.x.x 不在 Docker 默认网络范围内
|
||
const result = rewriteStorageUrlForClient('http://172.16.0.1:9000/bucket/file.jpg');
|
||
|
||
expect(result).toBe('http://172.16.0.1:9000/bucket/file.jpg');
|
||
});
|
||
|
||
it('不应该重写 10.x.x.x 私有 IP', async () => {
|
||
process.env.SERVER_IP = '192.168.1.100';
|
||
process.env.MINIO_API_PORT = '9000';
|
||
|
||
const { rewriteStorageUrlForClient } = await import('../url-rewrite');
|
||
const result = rewriteStorageUrlForClient('http://10.0.0.5:9000/bucket/file.jpg');
|
||
|
||
expect(result).toBe('http://10.0.0.5:9000/bucket/file.jpg');
|
||
});
|
||
|
||
it('不应该重写 192.168.x.x 私有 IP', async () => {
|
||
process.env.SERVER_IP = '192.168.1.100';
|
||
process.env.MINIO_API_PORT = '9000';
|
||
|
||
const { rewriteStorageUrlForClient } = await import('../url-rewrite');
|
||
const result = rewriteStorageUrlForClient('http://192.168.0.5:9000/bucket/file.jpg');
|
||
|
||
expect(result).toBe('http://192.168.0.5:9000/bucket/file.jpg');
|
||
});
|
||
});
|
||
|
||
describe('端口处理', () => {
|
||
it('应该使用 S3_PUBLIC_DOMAIN 中的端口', async () => {
|
||
process.env.SERVER_IP = '192.168.1.100';
|
||
process.env.S3_PUBLIC_DOMAIN = 'http://example.com:8080/bucket';
|
||
process.env.MINIO_API_PORT = '9000';
|
||
|
||
const { rewriteStorageUrlForClient } = await import('../url-rewrite');
|
||
const result = rewriteStorageUrlForClient('http://localhost:9000/bucket/file.jpg');
|
||
|
||
expect(result).toBe('http://192.168.1.100:8080/bucket/file.jpg');
|
||
});
|
||
|
||
it('应该使用默认端口 9000 当没有配置时', async () => {
|
||
process.env.SERVER_IP = '192.168.1.100';
|
||
delete process.env.S3_PUBLIC_DOMAIN;
|
||
delete process.env.MINIO_API_PORT;
|
||
|
||
const { rewriteStorageUrlForClient } = await import('../url-rewrite');
|
||
const result = rewriteStorageUrlForClient('http://localhost:3000/bucket/file.jpg');
|
||
|
||
expect(result).toBe('http://192.168.1.100:9000/bucket/file.jpg');
|
||
});
|
||
});
|
||
|
||
describe('从 APP_PUBLIC_URL 推断主机', () => {
|
||
it('应该从 APP_PUBLIC_URL 推断主机名', async () => {
|
||
delete process.env.SERVER_IP;
|
||
process.env.APP_PUBLIC_URL = 'http://myapp.example.com:3000';
|
||
process.env.MINIO_API_PORT = '9000';
|
||
|
||
const { rewriteStorageUrlForClient } = await import('../url-rewrite');
|
||
const result = rewriteStorageUrlForClient('http://localhost:9000/bucket/file.jpg');
|
||
|
||
expect(result).toBe('http://myapp.example.com:9000/bucket/file.jpg');
|
||
});
|
||
|
||
it('应该从 NEXTAUTH_URL 推断主机名', async () => {
|
||
delete process.env.SERVER_IP;
|
||
delete process.env.APP_PUBLIC_URL;
|
||
process.env.NEXTAUTH_URL = 'http://auth.example.com:3000';
|
||
process.env.MINIO_API_PORT = '9000';
|
||
|
||
const { rewriteStorageUrlForClient } = await import('../url-rewrite');
|
||
const result = rewriteStorageUrlForClient('http://localhost:9000/bucket/file.jpg');
|
||
|
||
expect(result).toBe('http://auth.example.com:9000/bucket/file.jpg');
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('rewriteMaterialStorageUrlsForClient', () => {
|
||
it('默认应该将素材对象中的文件 URL 改为应用代理地址', async () => {
|
||
const { rewriteMaterialStorageUrlsForClient } = await import('../url-rewrite');
|
||
|
||
const material = {
|
||
id: '123',
|
||
previewUrl: 'http://localhost:9000/bucket/preview.jpg',
|
||
videoCoverUrl: 'http://172.17.0.2:9000/bucket/cover.jpg',
|
||
videoPreview540Url: 'http://127.0.0.1:9000/bucket/preview540.mp4',
|
||
audioUrl: 'http://172.18.0.5:9000/bucket/audio.mp3',
|
||
displayImages: [
|
||
'http://localhost:9000/bucket/img1.jpg',
|
||
'http://172.17.0.2:9000/bucket/img2.jpg',
|
||
],
|
||
name: 'Test Material',
|
||
};
|
||
|
||
const result = rewriteMaterialStorageUrlsForClient(material);
|
||
|
||
expect(result.previewUrl).toBe('/api/materials/asset?url=http%3A%2F%2Flocalhost%3A9000%2Fbucket%2Fpreview.jpg');
|
||
expect(result.videoCoverUrl).toBe('/api/materials/asset?url=http%3A%2F%2F172.17.0.2%3A9000%2Fbucket%2Fcover.jpg');
|
||
expect(result.videoPreview540Url).toBe('/api/materials/asset?url=http%3A%2F%2F127.0.0.1%3A9000%2Fbucket%2Fpreview540.mp4');
|
||
expect(result.audioUrl).toBe('/api/materials/asset?url=http%3A%2F%2F172.18.0.5%3A9000%2Fbucket%2Faudio.mp3');
|
||
expect(result.displayImages).toEqual([
|
||
'/api/materials/asset?url=http%3A%2F%2Flocalhost%3A9000%2Fbucket%2Fimg1.jpg',
|
||
'/api/materials/asset?url=http%3A%2F%2F172.17.0.2%3A9000%2Fbucket%2Fimg2.jpg',
|
||
]);
|
||
expect(result.name).toBe('Test Material');
|
||
expect(result.id).toBe('123');
|
||
});
|
||
|
||
it('关闭素材代理时应该重写素材对象中的所有 URL 字段', async () => {
|
||
process.env.MATERIAL_ASSET_PROXY_ENABLED = '0';
|
||
process.env.SERVER_IP = '192.168.1.100';
|
||
process.env.MINIO_API_PORT = '9000';
|
||
|
||
const { rewriteMaterialStorageUrlsForClient } = await import('../url-rewrite');
|
||
|
||
const material = {
|
||
id: '123',
|
||
previewUrl: 'http://localhost:9000/bucket/preview.jpg',
|
||
videoCoverUrl: 'http://172.17.0.2:9000/bucket/cover.jpg',
|
||
videoPreview540Url: 'http://127.0.0.1:9000/bucket/preview540.mp4',
|
||
audioUrl: 'http://172.18.0.5:9000/bucket/audio.mp3',
|
||
displayImages: [
|
||
'http://localhost:9000/bucket/img1.jpg',
|
||
'http://172.17.0.2:9000/bucket/img2.jpg',
|
||
],
|
||
name: 'Test Material',
|
||
};
|
||
|
||
const result = rewriteMaterialStorageUrlsForClient(material);
|
||
|
||
expect(result.previewUrl).toBe('http://192.168.1.100:9000/bucket/preview.jpg');
|
||
expect(result.videoCoverUrl).toBe('http://192.168.1.100:9000/bucket/cover.jpg');
|
||
expect(result.videoPreview540Url).toBe('http://192.168.1.100:9000/bucket/preview540.mp4');
|
||
expect(result.audioUrl).toBe('http://192.168.1.100:9000/bucket/audio.mp3');
|
||
expect(result.displayImages).toEqual([
|
||
'http://192.168.1.100:9000/bucket/img1.jpg',
|
||
'http://192.168.1.100:9000/bucket/img2.jpg',
|
||
]);
|
||
expect(result.name).toBe('Test Material');
|
||
expect(result.id).toBe('123');
|
||
});
|
||
|
||
it('应该处理空对象', async () => {
|
||
const { rewriteMaterialStorageUrlsForClient } = await import('../url-rewrite');
|
||
expect(rewriteMaterialStorageUrlsForClient({})).toEqual({});
|
||
});
|
||
|
||
it('应该处理 null/undefined', async () => {
|
||
const { rewriteMaterialStorageUrlsForClient } = await import('../url-rewrite');
|
||
expect(rewriteMaterialStorageUrlsForClient(null as any)).toBeNull();
|
||
expect(rewriteMaterialStorageUrlsForClient(undefined as any)).toBeUndefined();
|
||
});
|
||
|
||
it('应该保持非 URL 字段不变', async () => {
|
||
process.env.SERVER_IP = '192.168.1.100';
|
||
|
||
const { rewriteMaterialStorageUrlsForClient } = await import('../url-rewrite');
|
||
|
||
const material = {
|
||
id: '123',
|
||
previewUrl: null,
|
||
videoCoverUrl: undefined,
|
||
displayImages: [],
|
||
name: 'Test',
|
||
count: 42,
|
||
};
|
||
|
||
const result = rewriteMaterialStorageUrlsForClient(material);
|
||
|
||
expect(result.previewUrl).toBeNull();
|
||
// rewriteStorageUrlForClient 将 undefined 转换为 null
|
||
expect(result.videoCoverUrl).toBeNull();
|
||
expect(result.displayImages).toEqual([]);
|
||
expect(result.name).toBe('Test');
|
||
expect(result.count).toBe(42);
|
||
});
|
||
|
||
it('应该保持 data: URL 和相对路径不变', async () => {
|
||
const { rewriteMaterialStorageUrlsForClient } = await import('../url-rewrite');
|
||
|
||
const material = {
|
||
previewUrl: 'data:image/png;base64,iVBORw0KGgo=',
|
||
videoCoverUrl: '/logo/header.png',
|
||
};
|
||
|
||
const result = rewriteMaterialStorageUrlsForClient(material);
|
||
|
||
expect(result.previewUrl).toBe(material.previewUrl);
|
||
expect(result.videoCoverUrl).toBe('/logo/header.png');
|
||
});
|
||
});
|
||
});
|