fix(resource-rule): 帳號上資源超過一頁時,規則看到的必須是全部(Arcrun#123 續集)
三支清單方法只打 `?per_page=100`,也就是**只看第一頁**。這個洞在 #123 的修法 前後嚴重度不同,這才是它必須跟那張票一起修的理由: · 修法前:被截掉的是「worker 綁著的那顆」→ 2b 判「綁著的資源不見了」 → blocker → 停手。誣告使用者,但安全。 · 修法後:被截掉的是「同名殘骸」→ 2c 判「這個名字沒被佔走」 → 去建 → CF 回 title already exists → #123 的死路原樣回來。 ⇒ 修法把它從「叫得太大聲」變成「安靜地復發」。分開出貨等於把 #123 的災情 延後到「資源比較多的帳號」再爆。 做法:`cfListAll()` 翻到底;翻不完、或數量對不上 CF 回報的 `total_count`, 一律 throw ⇒ 變 blocker ⇒ 整趟停手(README 規則第 3 條)。 「我不知道」不准被當成「它沒有」。 三支端點的分頁行為不一樣(2026-08-14 在 geek6688 帳號實測,唯讀): /storage/kv/namespaces result_info 有 total_pages /d1/database result_info **沒有** total_pages ⇒ 不能拿它當終止條件 /vectorize/v2/indexes result_info 是 null,不分頁(分頁參數被忽略) 所以終止條件只用「三支都有或都沒有」的兩件事:result_info 在不在、total_count 對不對得上。 fixture 的清單端點同步照真 CF 的形狀分頁(三支各自不同)——假資料失真就會養出 「拿 total_pages 當終止條件」這種在 D1 上必壞的實作,而測試全綠。 新增 tests/list-pagination.mjs(在舊碼上實測會紅,且第 ③ 段直接重現 「無 blocker → 排 10 顆新建 → CF 回 title already exists」的 #123 死路)。 cli 73 項全綠、demo 與 half-finished-install 全綠。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,9 @@
|
||||
|
||||
import { planResources, applyResourcePlan, ResourcePlanBlocked, bindingKey } from '../rule.mjs';
|
||||
import { createCloudflareResourceApi } from '../cf-resource-api.mjs';
|
||||
import { makeAccount, installerRequirements, requirements, KV_BINDINGS, BASE_NAME } from './fixture-account.mjs';
|
||||
import {
|
||||
makeAccount, installerRequirements, requirements, KV_BINDINGS, BASE_NAME, cfRejectsDuplicateNames,
|
||||
} from './fixture-account.mjs';
|
||||
|
||||
let failed = 0;
|
||||
/** @param {boolean} cond @param {string} what */
|
||||
@@ -32,31 +34,8 @@ function section(title) {
|
||||
console.log(`\n━━━ ${title} ━━━`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 真實 CF 的行為:**同名 KV 建不出來**。這一行就是封測者撞到的那道牆——
|
||||
* fixture 過去沒有模擬它,所以「重建一批孤兒」這個當年的假設從來沒有被戳破。
|
||||
* @param {ReturnType<typeof makeAccount>} account
|
||||
*/
|
||||
function cfRejectsDuplicateNames(account) {
|
||||
const inner = account.fetch;
|
||||
/** @type {typeof globalThis.fetch} */
|
||||
// @ts-expect-error — 測試替身
|
||||
return async (input, init) => {
|
||||
const url = new URL(typeof input === 'string' ? input : String(input));
|
||||
const path = url.pathname.replace(/^\/client\/v4\/accounts\/[^/]+/, '');
|
||||
if (path === '/storage/kv/namespaces' && (init?.method ?? 'GET').toUpperCase() === 'POST') {
|
||||
const title = JSON.parse(String(init?.body)).title;
|
||||
const listed = await (await inner(`https://api.cloudflare.com/client/v4/accounts/x/storage/kv/namespaces`, {})).json();
|
||||
if (listed.result.some((/** @type {{title: string}} */ n) => n.title === title)) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false, result: null,
|
||||
errors: [{ message: 'a namespace with this account ID and title already exists' }],
|
||||
}), { status: 400, headers: { 'Content-Type': 'application/json' } });
|
||||
}
|
||||
}
|
||||
return inner(input, init);
|
||||
};
|
||||
}
|
||||
// 「真 CF 會拒絕同名」這個替身搬去 fixture-account.mjs 了(pagination.mjs 也要用同一份,
|
||||
// 而且它必須自己會翻頁——只看第一頁的版本會在「帳號上資源很多」的測試裡漏認同名)。
|
||||
|
||||
/** @param {ReturnType<typeof makeAccount>} account */
|
||||
const apiFor = (account, fetchImpl) =>
|
||||
|
||||
Reference in New Issue
Block a user