flnxi-Wu/lib/__tests__/url-rewrite.test.ts
2026-05-25 15:36:51 +08:00

333 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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');
});
});
});