Files
vue-driven-cloud-storage/backend/test_share_edge_cases.js
237899745 4350113979 fix: 修复配额说明重复和undefined问题
- 在editStorageForm中初始化oss_storage_quota_value和oss_quota_unit
- 删除重复的旧配额说明块,保留新的当前配额设置显示

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 19:39:53 +08:00

527 lines
16 KiB
JavaScript
Raw Permalink 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.
/**
* 分享功能边界条件深度测试
*
* 测试场景:
* 1. 已过期的分享
* 2. 分享者被删除
* 3. 存储类型切换后的分享
* 4. 路径遍历攻击
* 5. 并发访问限流
*/
const http = require('http');
const { db, ShareDB, UserDB } = require('./database');
const BASE_URL = process.env.TEST_BASE_URL || 'http://127.0.0.1:40001';
const results = {
passed: 0,
failed: 0,
errors: []
};
// HTTP 请求工具
function request(method, path, data = null, headers = {}) {
return new Promise((resolve, reject) => {
const url = new URL(path, BASE_URL);
const port = url.port ? parseInt(url.port, 10) : 80;
const options = {
hostname: url.hostname,
port: port,
path: url.pathname + url.search,
method: method,
headers: {
'Content-Type': 'application/json',
...headers
}
};
const req = http.request(options, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
try {
const json = JSON.parse(body);
resolve({ status: res.statusCode, data: json, headers: res.headers });
} catch (e) {
resolve({ status: res.statusCode, data: body, headers: res.headers });
}
});
});
req.on('error', reject);
if (data) {
req.write(JSON.stringify(data));
}
req.end();
});
}
function assert(condition, message) {
if (condition) {
results.passed++;
console.log(` [PASS] ${message}`);
} else {
results.failed++;
results.errors.push(message);
console.log(` [FAIL] ${message}`);
}
}
// ===== 测试用例 =====
async function testExpiredShare() {
console.log('\n[测试] 已过期的分享...');
// 直接在数据库中创建一个已过期的分享
const expiredShareCode = 'expired_' + Date.now();
try {
// 插入一个已过期的分享(过期时间设为昨天)
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const expiresAt = yesterday.toISOString().replace('T', ' ').substring(0, 19);
db.prepare(`
INSERT INTO shares (user_id, share_code, share_path, share_type, expires_at)
VALUES (?, ?, ?, ?, ?)
`).run(1, expiredShareCode, '/expired-test.txt', 'file', expiresAt);
console.log(` 创建过期分享: ${expiredShareCode}, 过期时间: ${expiresAt}`);
// 尝试访问过期分享
const res = await request('POST', `/api/share/${expiredShareCode}/verify`, {});
assert(res.status === 404, `过期分享应返回 404, 实际: ${res.status}`);
assert(res.data.message === '分享不存在', '应提示分享不存在');
// 清理测试数据
db.prepare('DELETE FROM shares WHERE share_code = ?').run(expiredShareCode);
return true;
} catch (error) {
console.log(` [ERROR] ${error.message}`);
results.failed++;
// 清理
db.prepare('DELETE FROM shares WHERE share_code = ?').run(expiredShareCode);
return false;
}
}
async function testShareWithDeletedFile() {
console.log('\n[测试] 分享的文件不存在...');
// 创建一个指向不存在文件的分享
const shareCode = 'nofile_' + Date.now();
try {
db.prepare(`
INSERT INTO shares (user_id, share_code, share_path, share_type, storage_type)
VALUES (?, ?, ?, ?, ?)
`).run(1, shareCode, '/non_existent_file_xyz.txt', 'file', 'local');
console.log(` 创建分享: ${shareCode}, 路径: /non_existent_file_xyz.txt`);
// 访问分享
const res = await request('POST', `/api/share/${shareCode}/verify`, {});
// 应该返回错误(文件不存在)
// 注意verify 接口在缓存未命中时会查询存储
if (res.status === 500) {
assert(res.data.message && res.data.message.includes('不存在'), '应提示文件不存在');
} else if (res.status === 200) {
// 如果成功返回file 字段应该没有正确的文件信息
console.log(` [INFO] verify 返回 200检查文件信息`);
}
// 清理
db.prepare('DELETE FROM shares WHERE share_code = ?').run(shareCode);
return true;
} catch (error) {
console.log(` [ERROR] ${error.message}`);
db.prepare('DELETE FROM shares WHERE share_code = ?').run(shareCode);
return false;
}
}
async function testShareByBannedUser() {
console.log('\n[测试] 被封禁用户的分享...');
// 创建测试用户
let testUserId = null;
const shareCode = 'banned_' + Date.now();
try {
// 创建测试用户
testUserId = UserDB.create({
username: 'test_banned_' + Date.now(),
email: `test_banned_${Date.now()}@test.com`,
password: 'test123',
is_verified: 1
});
// 创建分享
db.prepare(`
INSERT INTO shares (user_id, share_code, share_path, share_type, storage_type)
VALUES (?, ?, ?, ?, ?)
`).run(testUserId, shareCode, '/test.txt', 'file', 'local');
console.log(` 创建测试用户 ID: ${testUserId}`);
console.log(` 创建分享: ${shareCode}`);
// 封禁用户
UserDB.setBanStatus(testUserId, true);
console.log(` 封禁用户: ${testUserId}`);
// 访问分享
const res = await request('POST', `/api/share/${shareCode}/verify`, {});
// 当前实现:被封禁用户的分享仍然可以访问
// 如果需要阻止,应该在 ShareDB.findByCode 中检查用户状态
console.log(` 被封禁用户分享访问状态码: ${res.status}`);
// 注意:这里可能是一个潜在的功能增强点
// 如果希望被封禁用户的分享也被禁止访问,需要修改代码
// 清理
db.prepare('DELETE FROM shares WHERE share_code = ?').run(shareCode);
UserDB.delete(testUserId);
assert(true, '被封禁用户分享测试完成');
return true;
} catch (error) {
console.log(` [ERROR] ${error.message}`);
if (shareCode) db.prepare('DELETE FROM shares WHERE share_code = ?').run(shareCode);
if (testUserId) UserDB.delete(testUserId);
return false;
}
}
async function testPathTraversalAttacks() {
console.log('\n[测试] 路径遍历攻击防护...');
// 创建测试分享
const shareCode = 'traverse_' + Date.now();
try {
db.prepare(`
INSERT INTO shares (user_id, share_code, share_path, share_type, storage_type)
VALUES (?, ?, ?, ?, ?)
`).run(1, shareCode, '/allowed-folder', 'directory', 'local');
// 测试各种路径遍历攻击
const attackPaths = [
'../../../etc/passwd',
'..\\..\\..\\etc\\passwd',
'%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd',
'/allowed-folder/../../../etc/passwd',
'/allowed-folder/./../../etc/passwd',
'....//....//....//etc/passwd',
'/allowed-folder%00.txt/../../../etc/passwd'
];
let blocked = 0;
for (const attackPath of attackPaths) {
const res = await request('GET', `/api/share/${shareCode}/download-url?path=${encodeURIComponent(attackPath)}`);
if (res.status === 403 || res.status === 400) {
blocked++;
console.log(` [BLOCKED] ${attackPath.substring(0, 40)}...`);
} else {
console.log(` [WARN] 可能未阻止: ${attackPath}, 状态: ${res.status}`);
}
}
assert(blocked >= attackPaths.length - 1, `路径遍历攻击应被阻止 (${blocked}/${attackPaths.length})`);
// 清理
db.prepare('DELETE FROM shares WHERE share_code = ?').run(shareCode);
return true;
} catch (error) {
console.log(` [ERROR] ${error.message}`);
db.prepare('DELETE FROM shares WHERE share_code = ?').run(shareCode);
return false;
}
}
async function testSpecialCharactersInPath() {
console.log('\n[测试] 特殊字符路径处理...');
// 测试创建包含特殊字符的分享
const specialPaths = [
'/文件夹/中文文件.txt',
'/folder with spaces/file.txt',
'/folder-with-dashes/file_underscore.txt',
'/folder.with.dots/file.name.ext.txt',
"/folder'with'quotes/file.txt"
];
let handled = 0;
for (const path of specialPaths) {
try {
const res = await request('POST', '/api/share/create', {
share_type: 'file',
file_path: path
}, { Cookie: authCookie });
if (res.status === 200 || res.status === 400) {
handled++;
console.log(` [OK] ${path.substring(0, 30)}... - 状态: ${res.status}`);
// 如果创建成功,清理
if (res.data.share_code) {
const myShares = await request('GET', '/api/share/my', null, { Cookie: authCookie });
const share = myShares.data.shares?.find(s => s.share_code === res.data.share_code);
if (share) {
await request('DELETE', `/api/share/${share.id}`, null, { Cookie: authCookie });
}
}
}
} catch (error) {
console.log(` [ERROR] ${path}: ${error.message}`);
}
}
assert(handled === specialPaths.length, '特殊字符路径处理完成');
return true;
}
async function testConcurrentPasswordAttempts() {
console.log('\n[测试] 并发密码尝试限流...');
// 创建一个带密码的分享
const shareCode = 'concurrent_' + Date.now();
try {
// 使用 bcrypt 哈希密码
const bcrypt = require('bcryptjs');
const hashedPassword = bcrypt.hashSync('correct123', 10);
db.prepare(`
INSERT INTO shares (user_id, share_code, share_path, share_type, share_password, storage_type)
VALUES (?, ?, ?, ?, ?, ?)
`).run(1, shareCode, '/test.txt', 'file', hashedPassword, 'local');
// 发送大量并发错误密码请求
const promises = [];
for (let i = 0; i < 20; i++) {
promises.push(request('POST', `/api/share/${shareCode}/verify`, {
password: 'wrong' + i
}));
}
const results = await Promise.all(promises);
// 检查是否有请求被限流
const rateLimited = results.filter(r => r.status === 429).length;
const unauthorized = results.filter(r => r.status === 401).length;
console.log(` 并发请求: 20, 限流: ${rateLimited}, 401错误: ${unauthorized}`);
// 注意:限流是否触发取决于配置
if (rateLimited > 0) {
assert(true, '限流机制生效');
} else {
console.log(' [INFO] 限流未触发(可能配置较宽松)');
assert(true, '并发测试完成');
}
// 清理
db.prepare('DELETE FROM shares WHERE share_code = ?').run(shareCode);
return true;
} catch (error) {
console.log(` [ERROR] ${error.message}`);
db.prepare('DELETE FROM shares WHERE share_code = ?').run(shareCode);
return false;
}
}
async function testShareStatistics() {
console.log('\n[测试] 分享统计功能...');
const shareCode = 'stats_' + Date.now();
try {
db.prepare(`
INSERT INTO shares (user_id, share_code, share_path, share_type, storage_type, view_count, download_count)
VALUES (?, ?, ?, ?, ?, ?, ?)
`).run(1, shareCode, '/test.txt', 'file', 'local', 0, 0);
// 验证多次(增加查看次数)
for (let i = 0; i < 3; i++) {
await request('POST', `/api/share/${shareCode}/verify`, {});
}
// 记录下载次数
for (let i = 0; i < 2; i++) {
await request('POST', `/api/share/${shareCode}/download`, {});
}
// 检查统计数据
const share = db.prepare('SELECT view_count, download_count FROM shares WHERE share_code = ?').get(shareCode);
assert(share.view_count === 3, `查看次数应为 3, 实际: ${share.view_count}`);
assert(share.download_count === 2, `下载次数应为 2, 实际: ${share.download_count}`);
console.log(` 查看次数: ${share.view_count}, 下载次数: ${share.download_count}`);
// 清理
db.prepare('DELETE FROM shares WHERE share_code = ?').run(shareCode);
return true;
} catch (error) {
console.log(` [ERROR] ${error.message}`);
db.prepare('DELETE FROM shares WHERE share_code = ?').run(shareCode);
return false;
}
}
async function testShareCodeUniqueness() {
console.log('\n[测试] 分享码唯一性...');
try {
// 创建多个分享,检查分享码是否唯一
const codes = new Set();
for (let i = 0; i < 10; i++) {
const code = ShareDB.generateShareCode();
if (codes.has(code)) {
console.log(` [WARN] 发现重复分享码: ${code}`);
}
codes.add(code);
}
assert(codes.size === 10, `应生成 10 个唯一分享码, 实际: ${codes.size}`);
console.log(` 生成了 ${codes.size} 个唯一分享码`);
// 检查分享码长度和字符
const sampleCode = ShareDB.generateShareCode();
assert(sampleCode.length === 8, `分享码长度应为 8, 实际: ${sampleCode.length}`);
assert(/^[a-zA-Z0-9]+$/.test(sampleCode), '分享码应只包含字母数字');
return true;
} catch (error) {
console.log(` [ERROR] ${error.message}`);
return false;
}
}
async function testExpiryTimeFormat() {
console.log('\n[测试] 过期时间格式...');
try {
// 测试不同的过期天数
const testDays = [1, 7, 30, 365];
for (const days of testDays) {
const result = ShareDB.create(1, {
share_type: 'file',
file_path: `/test_${days}_days.txt`,
expiry_days: days
});
const share = db.prepare('SELECT expires_at FROM shares WHERE share_code = ?').get(result.share_code);
// 验证过期时间格式
const expiresAt = new Date(share.expires_at);
const now = new Date();
const diffDays = Math.round((expiresAt - now) / (1000 * 60 * 60 * 24));
// 允许1天的误差由于时区等因素
assert(Math.abs(diffDays - days) <= 1, `${days}天过期应正确设置, 实际差异: ${diffDays}`);
// 清理
db.prepare('DELETE FROM shares WHERE share_code = ?').run(result.share_code);
}
return true;
} catch (error) {
console.log(` [ERROR] ${error.message}`);
return false;
}
}
// 全局认证 Cookie
let authCookie = '';
async function login() {
console.log('\n[准备] 登录获取认证...');
try {
const res = await request('POST', '/api/login', {
username: 'admin',
password: 'admin123'
});
if (res.status === 200 && res.data.success) {
const setCookie = res.headers['set-cookie'];
if (setCookie) {
authCookie = setCookie.map(c => c.split(';')[0]).join('; ');
console.log(' 认证成功');
return true;
}
}
console.log(' 认证失败');
return false;
} catch (error) {
console.log(` [ERROR] ${error.message}`);
return false;
}
}
// ===== 主测试流程 =====
async function runTests() {
console.log('========================================');
console.log(' 分享功能边界条件深度测试');
console.log('========================================');
// 登录
const loggedIn = await login();
if (!loggedIn) {
console.log('\n[WARN] 登录失败,部分测试可能无法执行');
}
// 运行测试
await testExpiredShare();
await testShareWithDeletedFile();
await testShareByBannedUser();
await testPathTraversalAttacks();
await testSpecialCharactersInPath();
await testConcurrentPasswordAttempts();
await testShareStatistics();
await testShareCodeUniqueness();
await testExpiryTimeFormat();
// 结果统计
console.log('\n========================================');
console.log(' 测试结果统计');
console.log('========================================');
console.log(`通过: ${results.passed}`);
console.log(`失败: ${results.failed}`);
if (results.errors.length > 0) {
console.log('\n失败的测试:');
results.errors.forEach((err, i) => {
console.log(` ${i + 1}. ${err}`);
});
}
console.log('\n========================================');
process.exit(results.failed > 0 ? 1 : 0);
}
runTests().catch(error => {
console.error('测试执行失败:', error);
process.exit(1);
});