feat: 清理缓存支持按项勾选清理 (#441)
* feat: 清理缓存支持按项勾选清理
上游原版 handleClearCache() 不区分具体项,调用即一次性重置全部内存缓存(animes、episodeIds、episodeNum、lastSelectMap、searchCache、commentCache、requestHistory 及 Bangumi Data)。
本提交改为按项勾选清理:
- apis/system-api.js:handleClearCache 接收可选请求体 { items: string[] },按 clearActions 映射逐项重置;请求记录(reqRecords)与今日计数(todayReqNum)归入「请求历史记录」项连带清理;未提供请求体或解析失败时回退为全清,兼容旧路由与测试的无参调用。未知键经 key in clearActions 过滤,无注入面。
- worker.js:POST /api/cache/clear 透传请求体。
- ui/template.js:清理缓存弹窗改为 8 项可勾选清单,含全选/全不选与已选计数。
- ui/js/systemsettings.js:新增 selectAllCacheItems、updateCacheClearCount;confirmClearCache 收集勾选项发送 {items},空选拦截。
- ui/css/components.css.js:新增缓存弹窗与通用复选框样式。
收藏(favoriteCache)按上游契约不在清理范围内,worker.test.js「清理缓存保留收藏」断言仍然成立。
* feat: 优化通用复选框样式与偏移设置交互
承接清理缓存弹窗的通用复选框,继续完善前端交互(均基于上游原版):
- ui/css/components.css.js:通用复选框由 accent-color 改为 appearance 自绘,以 --theme-check-color 控制勾选色(浅色恒白、暗色恒黑,规避浅强调色在浅色模式得黑勾);以 body[data-theme] 前缀将特异性提升至 3002,压过主题规则 themes.css.js:191 对 .form-group input 背景与边框的重设(2002),使位于 .form-group 内的偏移百分比复选框与缓存复选框表现一致;补 padding:0 避免继承 .form-group input 内边距导致长方形。
- ui/css/themes.css.js:浅色与暗色模式分别定义 --theme-check-color。
- ui/js/systemsettings.js:偏移百分比复选框标签去除 white-space:nowrap,窄屏长文案可换行,复选框不被压缩。
* test: 新增 handleClearCache 按项清理测试
覆盖 handleClearCache 的全部关键路径:
- 按项选清:单选一项只清该项,其余缓存保留
- 非法键过滤:不在 clearActions 的键被忽略,不触发任何清理动作
- requestHistory 项同时清空 reqRecords 并重置 todayReqNum 为 0
- episodeNum 重置为 10001
- 全清(无 req 或畸形 body 非数组)保留 favorites
- 空数组 items 不清理任何项
- bangumiData 作为合法键被识别且独立清理
全部子测试在 node --test 下通过,整文件 36/36 通过。
* fix: 修复清理缓存原型键误放行并优化弹窗提示与计数样式
清理缓存功能:修复 handleClearCache 的原型键过滤缺陷,并优化弹窗提示与计数视觉。
- apis/system-api.js:handleClearCache 的 items 过滤由 'key in clearActions' 改为 Object.prototype.hasOwnProperty.call(clearActions, key),排除 __proto__/constructor 等原型链键误放行导致整次清理失败(success:false)。
- ui/template.js:提示文案改为「请勾选需要清理的缓存项:」,采用上游风格的全可见度清晰提示。
- ui/css/components.css.js:.cache-clear-hint 字号 13px 增至 16px、颜色由灰改为正文色,与全选/全不选按钮形成清晰层级;.cache-clear-count 增加淡中性全圆角状态标签底色,平衡左右视觉重量且刻意不像按钮。
- worker.test.js:新增原型键回归测试,固化上述修复。
* fix: 统一清理缓存项标签大小写并调整项顺序
清理缓存弹窗:统一缓存项标签写法并调整为预期顺序。
- ui/template.js:动画元数据缓存标签由「动画元数据缓存 (Bangumi Data)」改为「动画元数据缓存 (bangumiData)」,与另 7 项括号展示真实键名的写法(animes / episodeIds / episodeNum / lastSelectMap)统一,便于 UI 与代码键名对照。
- 清理项顺序调整为:搜索结果缓存、弹幕内容缓存、请求历史记录、动漫搜索缓存、动画元数据缓存、剧集ID缓存、剧集编号缓存、最后选择映射缓存。
- 各 checkbox 的 value 与 handleClearCache 的 clearActions 键名一致,无功能影响;仓库其余「Bangumi Data」均为数据源专有名词,不涉及此改动。
This commit is contained in:
1 parent
65ab6d0658
commit
00efe4a815
7 files changed
+364
-60
No files matched your search
@@ -169,39 +169,62 @@ export function handleClearLogs() {
|
||||
|
||||
/**
|
||||
* 处理清理缓存的请求
|
||||
* @returns {Response} 表示操作成功的响应
|
||||
* @param {Request} [req] 可选请求体 { items: string[] },用于指定待清理项;未提供时清理全部已知项
|
||||
* @returns {Response} 表示操作结果的响应
|
||||
*/
|
||||
export async function handleClearCache() {
|
||||
try {
|
||||
// 清理 globals 中的缓存数据
|
||||
globals.animes = [];
|
||||
globals.episodeIds = [];
|
||||
globals.episodeNum = 10001; // 重置为初始值
|
||||
globals.lastSelectMap = new Map(); // 重新创建 Map 对象
|
||||
globals.reqRecords = []; // 清空请求记录
|
||||
globals.todayReqNum = 0; // 重置今日请求次数
|
||||
|
||||
export async function handleClearCache(req) {
|
||||
// 各清理项对应的重置逻辑;请求记录与今日计数归入「请求历史记录」项
|
||||
const clearActions = {
|
||||
animes: () => { globals.animes = []; },
|
||||
episodeIds: () => { globals.episodeIds = []; },
|
||||
episodeNum: () => { globals.episodeNum = 10001; }, // 重置为初始值
|
||||
lastSelectMap: () => { globals.lastSelectMap = new Map(); }, // 重新创建 Map 对象
|
||||
// 清理搜索和弹幕缓存
|
||||
globals.searchCache = new Map();
|
||||
globals.commentCache = new Map();
|
||||
globals.requestHistory = new Map();
|
||||
|
||||
try {
|
||||
// 清理 Bangumi-Data 内存与磁盘缓存
|
||||
clearBangumiDataCache(true);
|
||||
|
||||
// 触发异步数据重载
|
||||
if (globals.useBangumiData) {
|
||||
initBangumiData(globals.deployPlatform, false).catch(e => {
|
||||
log("warn", `[system] [server] Bangumi-Data background reload failed: ${e.message}`);
|
||||
});
|
||||
searchCache: () => { globals.searchCache = new Map(); },
|
||||
commentCache: () => { globals.commentCache = new Map(); },
|
||||
requestHistory: () => {
|
||||
globals.requestHistory = new Map();
|
||||
globals.reqRecords = []; // 清空请求记录
|
||||
globals.todayReqNum = 0; // 重置今日请求次数
|
||||
},
|
||||
bangumiData: () => {
|
||||
try {
|
||||
clearBangumiDataCache(true); // 清理 Bangumi-Data 内存与磁盘缓存
|
||||
if (globals.useBangumiData) {
|
||||
// 触发异步数据重载
|
||||
initBangumiData(globals.deployPlatform, false).catch(e => {
|
||||
log("warn", `[system] [server] Bangumi-Data background reload failed: ${e.message}`);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log("error", `[system] [server] Failed to clear Bangumi-Data cache: ${e.message}`);
|
||||
}
|
||||
} catch (e) {
|
||||
log("error", `[system] [server] Failed to clear Bangumi-Data cache: ${e.message}`);
|
||||
}
|
||||
|
||||
};
|
||||
const allItems = Object.keys(clearActions);
|
||||
|
||||
let effectiveItems;
|
||||
if (req) {
|
||||
let parsed = null;
|
||||
try {
|
||||
parsed = await req.json();
|
||||
} catch (e) {
|
||||
parsed = null;
|
||||
}
|
||||
const items = parsed && Array.isArray(parsed.items) ? parsed.items : null;
|
||||
// 仅保留自身属性键,排除 __proto__ 等原型链键,避免误放行导致整次清理失败
|
||||
effectiveItems = items ? items.filter(key => Object.prototype.hasOwnProperty.call(clearActions, key)) : allItems;
|
||||
} else {
|
||||
effectiveItems = allItems;
|
||||
}
|
||||
|
||||
try {
|
||||
for (const key of effectiveItems) {
|
||||
clearActions[key]();
|
||||
}
|
||||
|
||||
log("info", `[system] [server] Memory cache cleared successfully`);
|
||||
|
||||
|
||||
// 同步清理本地缓存和Redis缓存
|
||||
try {
|
||||
// 如果本地缓存有效,更新本地缓存
|
||||
@@ -213,7 +236,7 @@ export async function handleClearCache() {
|
||||
} catch (localError) {
|
||||
log("warn", `[system] [server] Local cache may not be available: ${localError.message}`);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// 如果Redis有效,更新Redis缓存
|
||||
if (globals.redisValid) {
|
||||
@@ -235,19 +258,22 @@ export async function handleClearCache() {
|
||||
} catch (redisError) {
|
||||
log("warn", `[system] [server] LocalRedis may not be available: ${redisError.message}`);
|
||||
}
|
||||
|
||||
log("info", `[system] [server] All caches cleared successfully`);
|
||||
return jsonResponse({ success: true, message: "Cache cleared successfully", clearedItems: {
|
||||
animes: 0,
|
||||
episodeIds: 0,
|
||||
episodeNum: 10001,
|
||||
lastSelectMap: 0,
|
||||
searchCache: 0,
|
||||
commentCache: 0,
|
||||
requestHistory: 0,
|
||||
reqRecords: 0,
|
||||
todayReqNum: 0
|
||||
}}, 200);
|
||||
|
||||
const clearedItems = {};
|
||||
for (const key of effectiveItems) {
|
||||
if (key === "requestHistory") {
|
||||
clearedItems.requestHistory = 0;
|
||||
clearedItems.reqRecords = 0;
|
||||
clearedItems.todayReqNum = 0;
|
||||
} else if (key === "episodeNum") {
|
||||
clearedItems.episodeNum = 10001;
|
||||
} else {
|
||||
clearedItems[key] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
log("info", `[system] [server] Selected caches cleared successfully`);
|
||||
return jsonResponse({ success: true, message: "Cache cleared successfully", clearedItems }, 200);
|
||||
} catch (error) {
|
||||
log("error", `[system] [server] Cache clear failed: ${error.message}`);
|
||||
return jsonResponse({ success: false, message: `Cache clear failed: ${error.message}` }, 500);
|
||||
|
||||
@@ -296,6 +296,17 @@ body[data-theme] .favorite-schedule-btn:disabled:hover {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--theme-panel-strong);
|
||||
color: var(--theme-text);
|
||||
border: 1px solid var(--theme-border);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
border-color: var(--theme-accent);
|
||||
color: var(--theme-accent);
|
||||
}
|
||||
|
||||
/* ============ 配置预览 ============ */
|
||||
.preview-description {
|
||||
color: var(--theme-muted);
|
||||
@@ -959,6 +970,115 @@ body.modal-open {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 通用复选框:appearance 自绘控制勾选色;body[data-theme] 前缀将特异性提至 3002,压过 .form-group input 的背景/边框(2002)与尺寸/内边距(1001) */
|
||||
body[data-theme] input[type="checkbox"].app-checkbox {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
position: relative;
|
||||
background: var(--theme-panel-bg);
|
||||
border: 1px solid var(--theme-muted);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
body[data-theme] input[type="checkbox"].app-checkbox:checked {
|
||||
background: var(--theme-accent);
|
||||
border-color: var(--theme-accent);
|
||||
}
|
||||
|
||||
body[data-theme] input[type="checkbox"].app-checkbox:checked::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 1px;
|
||||
width: 4px;
|
||||
height: 9px;
|
||||
border: solid var(--theme-check-color);
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
body[data-theme] input[type="checkbox"].app-checkbox:focus-visible {
|
||||
outline: 2px solid var(--theme-accent-soft);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.cache-clear-hint {
|
||||
margin: 0 0 12px;
|
||||
font-size: 16px;
|
||||
color: var(--theme-text);
|
||||
}
|
||||
|
||||
.cache-clear-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.cache-clear-count {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--theme-muted);
|
||||
background: color-mix(in srgb, var(--theme-muted) 10%, transparent); /* 状态标签底色,非按钮 */
|
||||
padding: 5px 10px;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.cache-clear-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cache-clear-actions .btn {
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
min-width: 64px;
|
||||
}
|
||||
|
||||
.cache-clear-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
border: 1px solid var(--theme-border);
|
||||
border-radius: var(--app-radius-card-sm);
|
||||
overflow: hidden;
|
||||
background: var(--theme-panel-bg);
|
||||
}
|
||||
|
||||
.cache-clear-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
padding: 9px 12px;
|
||||
border-bottom: 1px solid var(--theme-border);
|
||||
}
|
||||
|
||||
.cache-clear-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.cache-clear-item:hover {
|
||||
background: var(--theme-panel-strong);
|
||||
}
|
||||
|
||||
.cache-clear-note {
|
||||
margin: 12px 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--theme-muted);
|
||||
}
|
||||
|
||||
.warning-box {
|
||||
background: rgba(240, 160, 96, 0.10);
|
||||
border-left: 3px solid #f0a060;
|
||||
|
||||
@@ -40,6 +40,7 @@ body {
|
||||
--theme-code-bg: #1a1b24;
|
||||
--theme-code-text: #e2e4ea;
|
||||
--theme-link: #8cb48c;
|
||||
--theme-check-color: #ffffff;
|
||||
color-scheme: light;
|
||||
background: var(--theme-page-bg);
|
||||
color: var(--theme-text);
|
||||
@@ -112,6 +113,7 @@ body[data-color-scheme="dark"] {
|
||||
--theme-input-bg: rgba(255, 255, 255, 0.07);
|
||||
--theme-code-bg: #111118;
|
||||
--theme-code-text: #e2e4ea;
|
||||
--theme-check-color: #000000;
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
|
||||
@@ -309,9 +309,28 @@ async function importSystemConfigFile(file) {
|
||||
|
||||
// 显示清理缓存确认模态框
|
||||
function showClearCacheModal() {
|
||||
selectAllCacheItems(true); // 每次打开恢复默认全选
|
||||
document.getElementById('clear-cache-modal').classList.add('active');
|
||||
}
|
||||
|
||||
// 批量勾选或取消勾选所有缓存项,并同步已选数量
|
||||
function selectAllCacheItems(checked) {
|
||||
document.querySelectorAll('#clear-cache-modal input[name="cacheItem"]').forEach(cb => {
|
||||
cb.checked = checked;
|
||||
});
|
||||
updateCacheClearCount();
|
||||
}
|
||||
|
||||
// 刷新清理缓存弹窗中已勾选项的数量统计
|
||||
function updateCacheClearCount() {
|
||||
const all = document.querySelectorAll('#clear-cache-modal input[name="cacheItem"]');
|
||||
const checked = document.querySelectorAll('#clear-cache-modal input[name="cacheItem"]:checked');
|
||||
const countEl = document.getElementById('cache-clear-count');
|
||||
if (countEl) {
|
||||
countEl.textContent = '已选 ' + checked.length + ' / ' + all.length;
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏清理缓存确认模态框
|
||||
function hideClearCacheModal() {
|
||||
document.getElementById('clear-cache-modal').classList.remove('active');
|
||||
@@ -319,6 +338,14 @@ function hideClearCacheModal() {
|
||||
|
||||
// 确认清理缓存
|
||||
async function confirmClearCache() {
|
||||
// 收集勾选的缓存项
|
||||
const checkboxes = document.querySelectorAll('#clear-cache-modal input[name="cacheItem"]:checked');
|
||||
const items = Array.from(checkboxes).map(cb => cb.value);
|
||||
if (items.length === 0) {
|
||||
customAlert('请至少选择一项要清理的缓存');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查部署平台配置
|
||||
const configCheck = await checkDeployPlatformConfig();
|
||||
if (!configCheck.success) {
|
||||
@@ -332,12 +359,13 @@ async function confirmClearCache() {
|
||||
addLog('开始清理缓存', 'info');
|
||||
|
||||
try {
|
||||
// 调用真实的清理缓存API
|
||||
// 调用真实的清理缓存API,附带勾选的待清理项
|
||||
const response = await fetch(buildApiUrl('/api/cache/clear', true), { // 使用admin token
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
},
|
||||
body: JSON.stringify({ items })
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
@@ -973,9 +1001,9 @@ function renderValueInput(item) {
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-bottom: 10px; display: flex; align-items: center; width: 100%;">
|
||||
<label class="offset-label" style="display: flex; align-items: center; gap: 8px; cursor: pointer; margin: 0; white-space: nowrap;">
|
||||
<label class="offset-label" style="display: flex; align-items: center; gap: 8px; cursor: pointer; margin: 0;">
|
||||
启用百分比模式(按视频时长缩放全部弹幕时间)
|
||||
<input type="checkbox" id="offset-use-percent" style="width: 16px; height: 16px; margin: 0; flex-shrink: 0;">
|
||||
<input type="checkbox" id="offset-use-percent" class="app-checkbox">
|
||||
</label>
|
||||
</div>
|
||||
\${offsetSources.length > 0 ? \`
|
||||
|
||||
+20
-13
@@ -318,22 +318,29 @@ export const HTML_TEMPLATE = /* html */ `
|
||||
<div class="modal" id="clear-cache-modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>确认清理缓存</h3>
|
||||
<h3>选择要清理的缓存</h3>
|
||||
<button class="close-btn" onclick="hideClearCacheModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p style="margin-bottom: 20px;">确定要清理所有缓存吗?这将清除:</p>
|
||||
<ul class="confirmation-list">
|
||||
<li>动漫搜索缓存 (animes)</li>
|
||||
<li>剧集ID缓存 (episodeIds)</li>
|
||||
<li>剧集编号缓存 (episodeNum)</li>
|
||||
<li>最后选择映射缓存 (lastSelectMap)</li>
|
||||
<li>动画元数据缓存 (Bangumi Data)</li>
|
||||
<li>搜索结果缓存</li>
|
||||
<li>弹幕内容缓存</li>
|
||||
<li>请求历史记录</li>
|
||||
</ul>
|
||||
<p style="color: #666; margin-top: 20px;">清理后可能需要重新登录</p>
|
||||
<p class="cache-clear-hint">请勾选需要清理的缓存项:</p>
|
||||
<div class="cache-clear-toolbar">
|
||||
<span class="cache-clear-count" id="cache-clear-count"></span>
|
||||
<div class="cache-clear-actions">
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="selectAllCacheItems(true)">全选</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="selectAllCacheItems(false)">全不选</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cache-clear-options">
|
||||
<label class="cache-clear-item"><input type="checkbox" class="app-checkbox" name="cacheItem" value="searchCache" checked onchange="updateCacheClearCount()"> 搜索结果缓存</label>
|
||||
<label class="cache-clear-item"><input type="checkbox" class="app-checkbox" name="cacheItem" value="commentCache" checked onchange="updateCacheClearCount()"> 弹幕内容缓存</label>
|
||||
<label class="cache-clear-item"><input type="checkbox" class="app-checkbox" name="cacheItem" value="requestHistory" checked onchange="updateCacheClearCount()"> 请求历史记录</label>
|
||||
<label class="cache-clear-item"><input type="checkbox" class="app-checkbox" name="cacheItem" value="animes" checked onchange="updateCacheClearCount()"> 动漫搜索缓存 (animes)</label>
|
||||
<label class="cache-clear-item"><input type="checkbox" class="app-checkbox" name="cacheItem" value="bangumiData" checked onchange="updateCacheClearCount()"> 动画元数据缓存 (bangumiData)</label>
|
||||
<label class="cache-clear-item"><input type="checkbox" class="app-checkbox" name="cacheItem" value="episodeIds" checked onchange="updateCacheClearCount()"> 剧集ID缓存 (episodeIds)</label>
|
||||
<label class="cache-clear-item"><input type="checkbox" class="app-checkbox" name="cacheItem" value="episodeNum" checked onchange="updateCacheClearCount()"> 剧集编号缓存 (episodeNum)</label>
|
||||
<label class="cache-clear-item"><input type="checkbox" class="app-checkbox" name="cacheItem" value="lastSelectMap" checked onchange="updateCacheClearCount()"> 最后选择映射缓存 (lastSelectMap)</label>
|
||||
</div>
|
||||
<p class="cache-clear-note">清理后可能需要重新登录</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-success" onclick="confirmClearCache()">确认清理</button>
|
||||
|
||||
+1
-1
@@ -592,7 +592,7 @@ async function handleRequest(req, env, deployPlatform, clientIp, ctx) {
|
||||
|
||||
// POST /api/cache/clear - 清理缓存
|
||||
if (path === "/api/cache/clear" && method === "POST") {
|
||||
return handleClearCache();
|
||||
return handleClearCache(req);
|
||||
}
|
||||
|
||||
// ========== Cookie 管理 API ==========
|
||||
|
||||
@@ -1004,6 +1004,127 @@ test('worker.js API endpoints', async (t) => {
|
||||
assert.doesNotThrow(() => new Function(previewJsContent));
|
||||
assert.match(previewJsContent, /AUTO_MATCH_MAPPING_TABLE/);
|
||||
});
|
||||
|
||||
await t.test('handleClearCache clears only the selected cache items', async t => {
|
||||
// 各清理项对应的全局状态种子;favorites 不在清理范围内,用于验证不被误清
|
||||
const seed = () => {
|
||||
Globals.animes = [{ id: 1 }];
|
||||
Globals.episodeIds = ['ep1'];
|
||||
Globals.episodeNum = 50000;
|
||||
Globals.lastSelectMap = new Map([['k', {}]]);
|
||||
Globals.searchCache = new Map([['k', {}]]);
|
||||
Globals.commentCache = new Map([['k', {}]]);
|
||||
Globals.requestHistory = new Map([['ip', []]]);
|
||||
Globals.reqRecords = [{ a: 1 }];
|
||||
Globals.todayReqNum = 42;
|
||||
Globals.favoriteCache = new Map([['fav', {}]]);
|
||||
Globals.useBangumiData = false;
|
||||
};
|
||||
|
||||
await t.test('single item clears only that item', async () => {
|
||||
seed();
|
||||
const res = await handleClearCache({ json: async () => ({ items: ['animes'] }) });
|
||||
const body = await parseResponse(res);
|
||||
assert.equal(body.success, true);
|
||||
assert.equal(body.clearedItems.animes, 0);
|
||||
assert.equal(Globals.animes.length, 0);
|
||||
assert.equal(Globals.episodeIds.length, 1);
|
||||
assert.equal(Globals.lastSelectMap.size, 1);
|
||||
assert.equal(Globals.searchCache.size, 1);
|
||||
assert.equal(Globals.commentCache.size, 1);
|
||||
assert.equal(Globals.requestHistory.size, 1);
|
||||
});
|
||||
|
||||
await t.test('invalid keys are filtered out and do not throw', async () => {
|
||||
seed();
|
||||
const res = await handleClearCache({ json: async () => ({ items: ['animes', 'notARealKey', 'animesX'] }) });
|
||||
const body = await parseResponse(res);
|
||||
assert.equal(body.success, true);
|
||||
assert.equal(Globals.animes.length, 0);
|
||||
assert.equal(Globals.searchCache.size, 1);
|
||||
assert.equal(Globals.commentCache.size, 1);
|
||||
});
|
||||
|
||||
await t.test('requestHistory folds reqRecords and todayReqNum', async () => {
|
||||
seed();
|
||||
const res = await handleClearCache({ json: async () => ({ items: ['requestHistory'] }) });
|
||||
const body = await parseResponse(res);
|
||||
assert.equal(body.success, true);
|
||||
assert.equal(body.clearedItems.requestHistory, 0);
|
||||
assert.equal(body.clearedItems.reqRecords, 0);
|
||||
assert.equal(body.clearedItems.todayReqNum, 0);
|
||||
assert.equal(Globals.requestHistory.size, 0);
|
||||
assert.deepEqual(Globals.reqRecords, []);
|
||||
assert.equal(Globals.todayReqNum, 0);
|
||||
assert.equal(Globals.animes.length, 1);
|
||||
});
|
||||
|
||||
await t.test('episodeNum resets to the initial value 10001', async () => {
|
||||
seed();
|
||||
const res = await handleClearCache({ json: async () => ({ items: ['episodeNum'] }) });
|
||||
const body = await parseResponse(res);
|
||||
assert.equal(body.success, true);
|
||||
assert.equal(body.clearedItems.episodeNum, 10001);
|
||||
assert.equal(Globals.episodeNum, 10001);
|
||||
assert.equal(Globals.animes.length, 1);
|
||||
});
|
||||
|
||||
await t.test('favorites are preserved across full clear', async () => {
|
||||
seed();
|
||||
const res = await handleClearCache();
|
||||
const body = await parseResponse(res);
|
||||
assert.equal(body.success, true);
|
||||
assert.equal(Globals.favoriteCache.size, 1);
|
||||
assert.equal(Globals.animes.length, 0);
|
||||
assert.equal(Globals.episodeIds.length, 0);
|
||||
assert.equal(Globals.lastSelectMap.size, 0);
|
||||
assert.equal(Globals.searchCache.size, 0);
|
||||
assert.equal(Globals.commentCache.size, 0);
|
||||
assert.equal(Globals.requestHistory.size, 0);
|
||||
assert.equal(Globals.todayReqNum, 0);
|
||||
assert.deepEqual(Globals.reqRecords, []);
|
||||
});
|
||||
|
||||
await t.test('empty items array clears nothing', async () => {
|
||||
seed();
|
||||
const res = await handleClearCache({ json: async () => ({ items: [] }) });
|
||||
const body = await parseResponse(res);
|
||||
assert.equal(body.success, true);
|
||||
assert.equal(Globals.animes.length, 1);
|
||||
assert.equal(Globals.searchCache.size, 1);
|
||||
assert.equal(Globals.commentCache.size, 1);
|
||||
});
|
||||
|
||||
await t.test('malformed body (non-array items) triggers full clear', async () => {
|
||||
seed();
|
||||
const res = await handleClearCache({ json: async () => ({ items: 'animes' }) });
|
||||
const body = await parseResponse(res);
|
||||
assert.equal(body.success, true);
|
||||
assert.equal(Globals.animes.length, 0);
|
||||
assert.equal(Globals.searchCache.size, 0);
|
||||
});
|
||||
|
||||
await t.test('bangumiData is a recognized key and isolated from other caches', async () => {
|
||||
seed();
|
||||
const res = await handleClearCache({ json: async () => ({ items: ['bangumiData'] }) });
|
||||
const body = await parseResponse(res);
|
||||
assert.equal(body.success, true);
|
||||
assert.equal(body.clearedItems.bangumiData, 0);
|
||||
assert.equal(Globals.animes.length, 1);
|
||||
assert.equal(Globals.searchCache.size, 1);
|
||||
});
|
||||
|
||||
await t.test('prototype keys like __proto__ are rejected and do not break the clear', async () => {
|
||||
seed();
|
||||
const res = await handleClearCache({ json: async () => ({ items: ['animes', '__proto__', 'constructor', 'animes'] }) });
|
||||
const body = await parseResponse(res);
|
||||
assert.equal(body.success, true);
|
||||
assert.equal(Globals.animes.length, 0);
|
||||
assert.equal(Globals.searchCache.size, 1);
|
||||
assert.equal(Globals.commentCache.size, 1);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// await t.test('GET /api/v2/comment/:id?format=json&duration=true should return segment duration and reuse comment cache', async () => {
|
||||
|
||||
Reference in new issue
Block a user