test: 整併測試至 app/test 並解決 AI 審查 findings #8

Closed
jiantw83 wants to merge 23 commits from test/consolidate-app-test-20260626-111823 into develop
Member

變更摘要

將單元測試整併至 app/test/(移除根目錄下無法被執行的重複 test/ 目錄),並解決多輪 AI 程式碼審查的 findings(輸入驗證、錯誤訊息、測試覆蓋與測試品質)。對外契約(action inputs/outputs、版本進位與 beta 規則)維持不變。

影響範圍

  • 測試位置test/app/test/(與 app/ 原始碼同層,確保可被 node --test 執行)。
  • 輸入驗證config.loadConfig 新增 GITEA_REPOSITORYowner/repo 格式驗證(拒絕路徑穿越 ..、空段、過長與多段)。
  • 錯誤訊息releases JSON 解析失敗時,於錯誤訊息附上以字元安全截斷的回應內容片段。
  • 行為:上述皆為防禦性驗證與訊息強化,正常輸入下行為不變。

重點檔案

  • app/config.js:新增 assertRepositoryowner/repo 格式 + 長度上限)。
  • app/releases.js:JSON 解析錯誤訊息以 Array.from 依字元截斷片段,避免多位元組亂碼。
  • app/test/logger.test.js(新增):以 captureStream 攔截 stdout/stderr,驗證 section/info/error 輸出格式。
  • app/test/config.test.jsapp/test/version.test.js:拆分混合測試、補 repository 邊界與 major 跨區段比較測試。
  • .gitea/ai-review/:findings 清空、exclusions 累計記錄判定為誤報/過度設計的項目。

風險與注意事項

  • 測試目錄搬移後,請確認 CI 的測試執行路徑指向 app/test/package.json test script)。
  • GITEA_REPOSITORY 驗證採白名單字元集(A-Za-z0-9._-)與單一 /;若有特殊命名的 repo 需放寬,再行調整。

驗證

  • node --test 'app/test/**/*.test.js':43 項測試全數通過。
## 變更摘要 將單元測試整併至 `app/test/`(移除根目錄下無法被執行的重複 `test/` 目錄),並解決多輪 AI 程式碼審查的 findings(輸入驗證、錯誤訊息、測試覆蓋與測試品質)。對外契約(action inputs/outputs、版本進位與 beta 規則)維持不變。 ## 影響範圍 - **測試位置**:`test/` → `app/test/`(與 `app/` 原始碼同層,確保可被 `node --test` 執行)。 - **輸入驗證**:`config.loadConfig` 新增 `GITEA_REPOSITORY` 的 `owner/repo` 格式驗證(拒絕路徑穿越 `..`、空段、過長與多段)。 - **錯誤訊息**:`releases` JSON 解析失敗時,於錯誤訊息附上以字元安全截斷的回應內容片段。 - **行為**:上述皆為防禦性驗證與訊息強化,正常輸入下行為不變。 ## 重點檔案 - `app/config.js`:新增 `assertRepository`(`owner/repo` 格式 + 長度上限)。 - `app/releases.js`:JSON 解析錯誤訊息以 `Array.from` 依字元截斷片段,避免多位元組亂碼。 - `app/test/logger.test.js`(新增):以 `captureStream` 攔截 stdout/stderr,驗證 section/info/error 輸出格式。 - `app/test/config.test.js`、`app/test/version.test.js`:拆分混合測試、補 repository 邊界與 major 跨區段比較測試。 - `.gitea/ai-review/`:findings 清空、exclusions 累計記錄判定為誤報/過度設計的項目。 ## 風險與注意事項 - 測試目錄搬移後,請確認 CI 的測試執行路徑指向 `app/test/`(`package.json` test script)。 - `GITEA_REPOSITORY` 驗證採白名單字元集(`A-Za-z0-9._-`)與單一 `/`;若有特殊命名的 repo 需放寬,再行調整。 ## 驗證 - `node --test 'app/test/**/*.test.js'`:43 項測試全數通過。
jiantw83 added 9 commits 2026-06-26 06:08:36 +00:00
app/package.json 的 npm test(node --test,cwd 為 app/)只會探索 app/test/,
repo 根目錄另一份 test/ 中的 main 整合測試(index.test.js)因此從未被執行。
將 config/index/releases/version 收斂至 app/test/(修正 require 路徑),
保留 output 測試,移除重複且無法執行的 repo 根 test/。現 app/test/ 共 34 筆測試皆可執行。
- config 對 GITEA_REPOSITORY 加入 owner/repo 格式驗證,拒絕路徑穿越與特殊字元
- releases JSON 解析失敗時,錯誤訊息納入截斷的回傳內容片段以利除錯

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 新增 logger section/info/error 的輸出格式測試
- config 補上 GITEA_REPOSITORY 驗證測試,並拆分 isUnset/requireEnv 的正反例
- version 補回 compareVersionArrays 的 major 跨區段比較案例

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chore(ai-review 狀態): 更新 findings.json 與 exclusions.json
CI / AI Code Review (pull_request) Failing after 23s
dd8977ffa4
已解決 6 條(warning x6:輸入驗證、錯誤訊息、logger 測試、測試拆分與補強),1 條判定為誤報寫入 exclusions(info:withFetch 抽離屬過度設計),findings 清空。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- releases 改以 Array.from 依字元截斷回應片段,避免拆分多位元組字元造成亂碼,並標記片段內容
- config assertRepository 加入每段長度上限,並將 valid 更名為語義明確的 isRepoFormatValid

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 補上 GITEA_REPOSITORY 空段/缺斜線/多段等邊界格式的拋錯測試
- 將 logger 測試的 captureStdout/captureStderr 合併為單一 captureStream

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chore(ai-review 狀態): 更新 findings.json 與 exclusions.json
CI / AI Code Review (pull_request) Failing after 24s
14c7ac4ed0
已解決 5 條(critical x1、warning x2、info x2:多位元組截斷、repository 驗證與命名、邊界測試、串流攔截 DRY),1 條判定為誤報寫入 exclusions(warning:logger 改 DI 屬過度設計),findings 清空。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

🤖 AI Code Review 團隊

👤 角色 🎯 面向 🧠 個性
🗡️ Assassin security 多疑偏執、以攻擊者視角看世界,假設每筆輸入都是惡意的,每個信任都會被濫用
🎼 Bard style 唯美龜毛、追求優雅,把可讀性與一致性當作旋律,最受不了走調的命名與排版
🧰 Leo maintainability 有遠見、重視長期維護成本,凡事先問「六個月後的自己還看得懂嗎?」,討厭把債留給未來
🔮 Mage logic 嚴謹冷靜、滴水不漏,凡事推演到最壞情況,深信「沒驗證過的假設都是 bug」
🧪 Maya testing 對測試覆蓋率有執念,深信「沒有測試的程式碼等於沒寫完」,溫和但堅持,最在意邊界與失敗路徑
Rogue efficiency 急性子、講求速度,最痛恨被浪費的 CPU 週期與記憶體,凡事先問「這能不能更快、更省」

🔍 服務:opencode 模型:gemini-2.5-flash

## 🤖 AI Code Review 團隊 | 👤 角色 | 🎯 面向 | 🧠 個性 | |--------|--------|--------| | **🗡️ Assassin** | security | 多疑偏執、以攻擊者視角看世界,假設每筆輸入都是惡意的,每個信任都會被濫用 | | **🎼 Bard** | style | 唯美龜毛、追求優雅,把可讀性與一致性當作旋律,最受不了走調的命名與排版 | | **🧰 Leo** | maintainability | 有遠見、重視長期維護成本,凡事先問「六個月後的自己還看得懂嗎?」,討厭把債留給未來 | | **🔮 Mage** | logic | 嚴謹冷靜、滴水不漏,凡事推演到最壞情況,深信「沒驗證過的假設都是 bug」 | | **🧪 Maya** | testing | 對測試覆蓋率有執念,深信「沒有測試的程式碼等於沒寫完」,溫和但堅持,最在意邊界與失敗路徑 | | **⚡ Rogue** | efficiency | 急性子、講求速度,最痛恨被浪費的 CPU 週期與記憶體,凡事先問「這能不能更快、更省」 | > 🔍 服務:opencode 模型:gemini-2.5-flash
gitea-actions bot reviewed 2026-06-26 06:08:59 +00:00
gitea-actions bot left a comment

AI Code Review 統計

類型 🔴 嚴重 🟡 警告 🔵 建議 無法標示
新問題 2 筆 4 筆 4 筆 0 筆
舊問題 0 筆 0 筆 0 筆 0 筆

🤖 AI 助理使用量

本次審查(opencode / gemini-2.5-flash,共 17 次呼叫)

提示 token 回應 token 合計
130,205 4,164 280,978

剩餘可用

剩餘可用:無法計算百分比(自架服務,無帳號額度概念)

## AI Code Review 統計 | 類型 | 🔴 嚴重 | 🟡 警告 | 🔵 建議 | ⚪ 無法標示 | | --- | --- | --- | --- | --- | | 新問題 | 2 筆 | 4 筆 | 4 筆 | 0 筆 | | 舊問題 | 0 筆 | 0 筆 | 0 筆 | 0 筆 | ## 🤖 AI 助理使用量 **本次審查**(opencode / gemini-2.5-flash,共 17 次呼叫) | 提示 token | 回應 token | 合計 | | --- | --- | --- | | 130,205 | 4,164 | 280,978 | **剩餘可用** 剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
app/config.js Outdated
@@ -58,0 +59,4 @@
function assertRepository(name, value) {
const parts = value.split('/');
const isRepoFormatValid = parts.length === 2
&& parts.every((part) => part.length > 0 && part.length <= 100

嚴重等級🔴 嚴重
審查員:Maya
問題:新增的 assertRepository 函數對 GITEA_REPOSITORY 的檢查邏輯並未測試到所有邊界條件,例如:包含特殊字元(非字母數字點下底線連字號)、極長字串段(> 100 字元)、以及完全空白的 value
建議:請在 app/test/config.test.js 中增加針對非法字元、超過 100 字元的字串段,以及空字串的測試案例,確保 assertRepository 的防禦性功能完全被驗證。

**嚴重等級**:🔴 嚴重 **審查員**:Maya **問題**:新增的 `assertRepository` 函數對 `GITEA_REPOSITORY` 的檢查邏輯並未測試到所有邊界條件,例如:包含特殊字元(非字母數字點下底線連字號)、極長字串段(> 100 字元)、以及完全空白的 `value`。 **建議**:請在 `app/test/config.test.js` 中增加針對非法字元、超過 100 字元的字串段,以及空字串的測試案例,確保 `assertRepository` 的防禦性功能完全被驗證。

嚴重等級🟡 警告
審查員:Mage
問題:在 assertRepository 中,parts.every 僅檢查了單一區段長度,未對總體長度(owner/repo)做任何限制。
建議:建議在分割前,先對 value 的總長度進行合理範圍的限制。

**嚴重等級**:🟡 警告 **審查員**:Mage **問題**:在 `assertRepository` 中,`parts.every` 僅檢查了單一區段長度,未對總體長度(`owner/repo`)做任何限制。 **建議**:建議在分割前,先對 `value` 的總長度進行合理範圍的限制。
Ghost marked this conversation as resolved
app/config.js Outdated
@@ -58,0 +60,4 @@
const parts = value.split('/');
const isRepoFormatValid = parts.length === 2
&& parts.every((part) => part.length > 0 && part.length <= 100
&& /^[A-Za-z0-9._-]+$/.test(part)

嚴重等級🔴 嚴重
審查員:Mage
問題:在 assertRepository 函數中,part !== '.' && part !== '..' 的檢查雖然能防止路徑穿越,但正規表達式 ^[A-Za-z0-9._-]+$ 仍然允許 . 開頭或結尾的檔案名稱(例如 .foofoo.),這在某些檔案系統中可能導致意外行為或被視為隱藏檔案,且未明確禁止僅由 . 組成的名稱(儘管後面有手動排除)。
建議:建議調整正規表達式,禁止名稱以點號開頭或結尾,例如使用 /^[A-Za-z0-9_-]+([.][A-Za-z0-9_-]+)*$/ 來確保點號只能存在於中間。

**嚴重等級**:🔴 嚴重 **審查員**:Mage **問題**:在 `assertRepository` 函數中,`part !== '.' && part !== '..'` 的檢查雖然能防止路徑穿越,但正規表達式 `^[A-Za-z0-9._-]+$` 仍然允許 `.` 開頭或結尾的檔案名稱(例如 `.foo` 或 `foo.`),這在某些檔案系統中可能導致意外行為或被視為隱藏檔案,且未明確禁止僅由 `.` 組成的名稱(儘管後面有手動排除)。 **建議**:建議調整正規表達式,禁止名稱以點號開頭或結尾,例如使用 `/^[A-Za-z0-9_-]+([.][A-Za-z0-9_-]+)*$/` 來確保點號只能存在於中間。

嚴重等級🟡 警告
審查員:Leo
問題:在 assertRepository 函式中使用了硬編碼的 100 作為長度限制,這是一個魔術數字,難以維護且無法從環境配置中調整。
建議:建議將 100 抽離為常數(例如 MAX_REPO_NAME_LENGTH),提高可維護性。

**嚴重等級**:🟡 警告 **審查員**:Leo **問題**:在 `assertRepository` 函式中使用了硬編碼的 `100` 作為長度限制,這是一個魔術數字,難以維護且無法從環境配置中調整。 **建議**:建議將 `100` 抽離為常數(例如 `MAX_REPO_NAME_LENGTH`),提高可維護性。

嚴重等級🔵 建議
審查員:Bard
問題:正規表達式 /^[A-Za-z0-9._-]+$/ 描述稍顯冗長,且未利用字元類別的特性,略顯不夠優雅。
建議:考慮簡化寫法,或確保該格式驗證邏輯與專案其他地方一致並抽取為常數。

**嚴重等級**:🔵 建議 **審查員**:Bard **問題**:正規表達式 `/^[A-Za-z0-9._-]+$/` 描述稍顯冗長,且未利用字元類別的特性,略顯不夠優雅。 **建議**:考慮簡化寫法,或確保該格式驗證邏輯與專案其他地方一致並抽取為常數。
Ghost marked this conversation as resolved
app/config.js Outdated
@@ -58,0 +62,4 @@
&& parts.every((part) => part.length > 0 && part.length <= 100
&& /^[A-Za-z0-9._-]+$/.test(part)
&& part !== '.' && part !== '..');
if (!isRepoFormatValid) {

嚴重等級🟡 警告
審查員:Rogue
問題:在 every 迴圈中重複宣告與編譯 Regex /^[A-Za-z0-9._-]+$/,若該函數被頻繁呼叫,將造成無謂的 CPU 與記憶體浪費。
建議:將 Regex 移至函數外層宣告為常數。

**嚴重等級**:🟡 警告 **審查員**:Rogue **問題**:在 `every` 迴圈中重複宣告與編譯 Regex `/^[A-Za-z0-9._-]+$/`,若該函數被頻繁呼叫,將造成無謂的 CPU 與記憶體浪費。 **建議**:將 Regex 移至函數外層宣告為常數。
Ghost marked this conversation as resolved
app/releases.js Outdated
@@ -65,2 +65,3 @@
} catch {
throw new Error(`release API 回傳資料無法解析 (page=${page})`);
// 以字元(而非 UTF-16 碼元)截斷回傳內容片段,避免拆分多位元組字元造成亂碼
const contentSnippet = Array.from(text).slice(0, 200).join('');

嚴重等級🔵 建議
審查員:Leo
問題:硬編碼了截斷字串長度 200,這是一個魔術數字,若未來需要調整截斷長度以容納更多偵錯資訊或減少輸出,維護者需要直接修改此處邏輯。
建議:建議定義一個具名的常數(例如 API_ERROR_SNIPPET_LENGTH),讓此數字具備語意,且未來調整時只需改動一處。

**嚴重等級**:🔵 建議 **審查員**:Leo **問題**:硬編碼了截斷字串長度 `200`,這是一個魔術數字,若未來需要調整截斷長度以容納更多偵錯資訊或減少輸出,維護者需要直接修改此處邏輯。 **建議**:建議定義一個具名的常數(例如 `API_ERROR_SNIPPET_LENGTH`),讓此數字具備語意,且未來調整時只需改動一處。
Ghost marked this conversation as resolved
app/releases.js Outdated
@@ -66,1 +66,3 @@
throw new Error(`release API 回傳資料無法解析 (page=${page})`);
// 以字元(而非 UTF-16 碼元)截斷回傳內容片段,避免拆分多位元組字元造成亂碼
const contentSnippet = Array.from(text).slice(0, 200).join('');
throw new Error(`release API 回傳資料無法解析 (page=${page}),回應內容片段:「${contentSnippet}`);

嚴重等級🔵 建議
審查員:Assassin
問題:將未經清洗的 API 回應片段直接納入 Error 物件,若 API 返回敏感資訊(內部路徑、堆疊追蹤、使用者資料),可能導致敏感資訊外洩。
建議:錯誤報告應僅包含概括性的描述,若必須包含內容片段,務必先對片段進行內容脫敏或格式化處理。

**嚴重等級**:🔵 建議 **審查員**:Assassin **問題**:將未經清洗的 API 回應片段直接納入 `Error` 物件,若 API 返回敏感資訊(內部路徑、堆疊追蹤、使用者資料),可能導致敏感資訊外洩。 **建議**:錯誤報告應僅包含概括性的描述,若必須包含內容片段,務必先對片段進行內容脫敏或格式化處理。
Ghost marked this conversation as resolved
@@ -60,2 +62,4 @@
GITEA_TOKEN: 'null',
});
assert.equal(config.token, null);

嚴重等級🟡 警告
審查員:Bard
問題:測試檔案中的迴圈測試(for...of)將多種不同的錯誤案例綁在同一個測試名稱下,一旦其中一個案例失敗,難以快速辨識具體是哪一種格式導致驗證失效。
建議:建議將其拆分為獨立的測試案例,或在 assert.throws 中加入更具描述性的訊息。

**嚴重等級**:🟡 警告 **審查員**:Bard **問題**:測試檔案中的迴圈測試(for...of)將多種不同的錯誤案例綁在同一個測試名稱下,一旦其中一個案例失敗,難以快速辨識具體是哪一種格式導致驗證失效。 **建議**:建議將其拆分為獨立的測試案例,或在 `assert.throws` 中加入更具描述性的訊息。
Ghost marked this conversation as resolved
@@ -55,2 +30,4 @@
assert.deepEqual(releases, [{ tag_name: 'v1.0.0' }]);
},
);
});

嚴重等級🔵 建議
審查員:Bard
問題:與 config.test.js 相比,這裡的 jsonResponse 輔助函式定義在測試檔案上方,風格雖然簡潔,但若未來有更多測試檔案需要類似的 mock 工具,建議統一抽離至 test/support/ 之類的資料夾中,保持測試主體清爽。
建議:建立 app/test/support/mock.js 將這類常用的測試輔助函式集中管理。

**嚴重等級**:🔵 建議 **審查員**:Bard **問題**:與 `config.test.js` 相比,這裡的 `jsonResponse` 輔助函式定義在測試檔案上方,風格雖然簡潔,但若未來有更多測試檔案需要類似的 mock 工具,建議統一抽離至 `test/support/` 之類的資料夾中,保持測試主體清爽。 **建議**:建立 `app/test/support/mock.js` 將這類常用的測試輔助函式集中管理。
Ghost marked this conversation as resolved
gitea-actions bot added 1 commit 2026-06-26 06:09:00 +00:00
jiantw83 added 3 commits 2026-06-26 06:11:40 +00:00
- config 將每段長度上限與允許字元抽為 MAX_REPO_NAME_LENGTH / REPO_SEGMENT_PATTERN(regex 提升至模組層級,避免每次重編譯)
- releases 將片段截斷長度抽為 API_ERROR_SNIPPET_LENGTH

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
新增含空白/星號/冒號等非法字元,以及單段超過長度上限的拒絕測試。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chore(ai-review 狀態): 更新 findings.json 與 exclusions.json
CI / AI Code Review (pull_request) Failing after 39s
fcb1dc4525
已解決 5 條(critical x1、warning x2、info x2:抽常數、提升 regex、補邊界測試),5 條判定為誤報寫入 exclusions(過度收緊 regex、迴圈測試已帶訊息、總長度冗餘、片段建議互相衝突、mock 過早抽離),findings 清空。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

🤖 AI Code Review 團隊

👤 角色 🎯 面向 🧠 個性
🗡️ Assassin security 多疑偏執、以攻擊者視角看世界,假設每筆輸入都是惡意的,每個信任都會被濫用
🎼 Bard style 唯美龜毛、追求優雅,把可讀性與一致性當作旋律,最受不了走調的命名與排版
🧰 Leo maintainability 有遠見、重視長期維護成本,凡事先問「六個月後的自己還看得懂嗎?」,討厭把債留給未來
🔮 Mage logic 嚴謹冷靜、滴水不漏,凡事推演到最壞情況,深信「沒驗證過的假設都是 bug」
🧪 Maya testing 對測試覆蓋率有執念,深信「沒有測試的程式碼等於沒寫完」,溫和但堅持,最在意邊界與失敗路徑
Rogue efficiency 急性子、講求速度,最痛恨被浪費的 CPU 週期與記憶體,凡事先問「這能不能更快、更省」

🔍 服務:opencode 模型:gemini-2.5-flash

## 🤖 AI Code Review 團隊 | 👤 角色 | 🎯 面向 | 🧠 個性 | |--------|--------|--------| | **🗡️ Assassin** | security | 多疑偏執、以攻擊者視角看世界,假設每筆輸入都是惡意的,每個信任都會被濫用 | | **🎼 Bard** | style | 唯美龜毛、追求優雅,把可讀性與一致性當作旋律,最受不了走調的命名與排版 | | **🧰 Leo** | maintainability | 有遠見、重視長期維護成本,凡事先問「六個月後的自己還看得懂嗎?」,討厭把債留給未來 | | **🔮 Mage** | logic | 嚴謹冷靜、滴水不漏,凡事推演到最壞情況,深信「沒驗證過的假設都是 bug」 | | **🧪 Maya** | testing | 對測試覆蓋率有執念,深信「沒有測試的程式碼等於沒寫完」,溫和但堅持,最在意邊界與失敗路徑 | | **⚡ Rogue** | efficiency | 急性子、講求速度,最痛恨被浪費的 CPU 週期與記憶體,凡事先問「這能不能更快、更省」 | > 🔍 服務:opencode 模型:gemini-2.5-flash
gitea-actions bot reviewed 2026-06-26 06:12:18 +00:00
gitea-actions bot left a comment

AI Code Review 統計

類型 🔴 嚴重 🟡 警告 🔵 建議 無法標示
新問題 0 筆 3 筆 1 筆 0 筆
舊問題 1 筆 0 筆 0 筆 0 筆

🤖 AI 助理使用量

本次審查(opencode / gemini-2.5-flash,共 13 次呼叫)

提示 token 回應 token 合計
113,669 3,094 236,316

剩餘可用

剩餘可用:無法計算百分比(自架服務,無帳號額度概念)

## AI Code Review 統計 | 類型 | 🔴 嚴重 | 🟡 警告 | 🔵 建議 | ⚪ 無法標示 | | --- | --- | --- | --- | --- | | 新問題 | 0 筆 | 3 筆 | 1 筆 | 0 筆 | | 舊問題 | 1 筆 | 0 筆 | 0 筆 | 0 筆 | ## 🤖 AI 助理使用量 **本次審查**(opencode / gemini-2.5-flash,共 13 次呼叫) | 提示 token | 回應 token | 合計 | | --- | --- | --- | | 113,669 | 3,094 | 236,316 | **剩餘可用** 剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
app/config.js Outdated
@@ -58,0 +60,4 @@
const REPO_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
// 驗證 repository 為 owner/repo 格式(拒絕 . 與 .. 路徑穿越段、空段、過長或含非法字元的段)
function assertRepository(name, value) {

嚴重等級🔵 建議
審查員:Leo
問題:驗證邏輯 parts.every 內包含過多條件,邏輯稍顯複雜,閱讀起來需要花點時間拆解。
建議:將 parts.every 的回呼函數提取為具名函數(例如 isValidPart),以提升代碼的可讀性與未來維護的清晰度。

**嚴重等級**:🔵 建議 **審查員**:Leo **問題**:驗證邏輯 parts.every 內包含過多條件,邏輯稍顯複雜,閱讀起來需要花點時間拆解。 **建議**:將 parts.every 的回呼函數提取為具名函數(例如 isValidPart),以提升代碼的可讀性與未來維護的清晰度。
Ghost marked this conversation as resolved
app/releases.js Outdated
@@ -65,2 +68,3 @@
} catch {
throw new Error(`release API 回傳資料無法解析 (page=${page})`);
// 以字元(而非 UTF-16 碼元)截斷回傳內容片段,避免拆分多位元組字元造成亂碼
const contentSnippet = Array.from(text).slice(0, API_ERROR_SNIPPET_LENGTH).join('');

嚴重等級🟡 警告
審查員:Leo
問題:為了處理多位元組字元而使用 Array.from(text),如果回傳的錯誤內容(text)非常巨大(例如幾十 MB 的 HTML 錯誤頁面),會瞬間配置大量記憶體,可能導致效能瓶頸。
建議:若預期回應可能非常大,建議先檢查 text.length 是否遠大於限制,或是考慮使用更輕量的字串處理方式來避免不必要的記憶體配置。

**嚴重等級**:🟡 警告 **審查員**:Leo **問題**:為了處理多位元組字元而使用 Array.from(text),如果回傳的錯誤內容(text)非常巨大(例如幾十 MB 的 HTML 錯誤頁面),會瞬間配置大量記憶體,可能導致效能瓶頸。 **建議**:若預期回應可能非常大,建議先檢查 text.length 是否遠大於限制,或是考慮使用更輕量的字串處理方式來避免不必要的記憶體配置。
Ghost marked this conversation as resolved
app/releases.js Outdated
@@ -67,1 +69,4 @@
// 以字元(而非 UTF-16 碼元)截斷回傳內容片段,避免拆分多位元組字元造成亂碼
const contentSnippet = Array.from(text).slice(0, API_ERROR_SNIPPET_LENGTH).join('');
throw new Error(`release API 回傳資料無法解析 (page=${page}),回應內容片段:「${contentSnippet}`);
}

嚴重等級🟡 警告
審查員:Mage
問題:雖然使用 Array.from(text) 可以正確處理多位元組字元(避免在 Unicode code point 中間截斷),但此操作會將整個回應內容轉為陣列,若 API 回傳的錯誤回應內容極大(例如數 MB 的 HTML 錯誤頁面),這會導致嚴重的記憶體耗用,甚至造成執行緒卡頓或記憶體溢位 (OOM)。
建議:應先對 text 進行長度檢查,或者改用 Bufferstring.substring() 搭配適當的 Unicode 感知邏輯來截斷,而不是將完整內容陣列化。

**嚴重等級**:🟡 警告 **審查員**:Mage **問題**:雖然使用 `Array.from(text)` 可以正確處理多位元組字元(避免在 Unicode code point 中間截斷),但此操作會將整個回應內容轉為陣列,若 API 回傳的錯誤回應內容極大(例如數 MB 的 HTML 錯誤頁面),這會導致嚴重的記憶體耗用,甚至造成執行緒卡頓或記憶體溢位 (OOM)。 **建議**:應先對 `text` 進行長度檢查,或者改用 `Buffer` 或 `string.substring()` 搭配適當的 Unicode 感知邏輯來截斷,而不是將完整內容陣列化。
Ghost marked this conversation as resolved
@@ -70,0 +77,4 @@
);
});
test('fetchReleases 對非陣列回應拋錯', async () => {

嚴重等級🟡 警告
審查員:Maya
問題:新增的 JSON 解析錯誤處理邏輯中,對於回應內容片段(contentSnippet)的生成與截斷行為並未進行驗證。目前的測試僅檢查錯誤訊息是否包含特定字串,未確保截斷邏輯在邊界條件(如超長回應、包含特殊字元)下是否正確。
建議:建議在 app/test/releases.test.js 中增加針對無法解析 JSON 的測試案例,並具體斷言錯誤訊息中包含預期的片段內容,特別是需要測試超長字串截斷是否符合預期。

**嚴重等級**:🟡 警告 **審查員**:Maya **問題**:新增的 JSON 解析錯誤處理邏輯中,對於回應內容片段(contentSnippet)的生成與截斷行為並未進行驗證。目前的測試僅檢查錯誤訊息是否包含特定字串,未確保截斷邏輯在邊界條件(如超長回應、包含特殊字元)下是否正確。 **建議**:建議在 `app/test/releases.test.js` 中增加針對無法解析 JSON 的測試案例,並具體斷言錯誤訊息中包含預期的片段內容,特別是需要測試超長字串截斷是否符合預期。
Ghost marked this conversation as resolved
gitea-actions bot added 1 commit 2026-06-26 06:12:21 +00:00
jiantw83 added 3 commits 2026-06-26 06:14:34 +00:00
- releases 在 Array.from 前先以長度上限截斷字串,避免將可能極大的回應整個陣列化
- config 將 repository 區段驗證抽為具名函式 isValidRepoSegment 提升可讀性

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
新增超長無法解析回應的測試,斷言錯誤訊息中的片段截斷至 200 字元內。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chore(ai-review 狀態): 更新 findings.json 與 exclusions.json
CI / AI Code Review (pull_request) Successful in 47s
f4dd59d681
已解決 4 條(warning x3、info x1:片段記憶體安全、截斷測試、抽出 isValidRepoSegment),1 條判定為誤報寫入 exclusions(assertRepository 邊界測試前提已不成立),findings 清空。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

🤖 AI Code Review 團隊

👤 角色 🎯 面向 🧠 個性
🗡️ Assassin security 多疑偏執、以攻擊者視角看世界,假設每筆輸入都是惡意的,每個信任都會被濫用
🎼 Bard style 唯美龜毛、追求優雅,把可讀性與一致性當作旋律,最受不了走調的命名與排版
🧰 Leo maintainability 有遠見、重視長期維護成本,凡事先問「六個月後的自己還看得懂嗎?」,討厭把債留給未來
🔮 Mage logic 嚴謹冷靜、滴水不漏,凡事推演到最壞情況,深信「沒驗證過的假設都是 bug」
🧪 Maya testing 對測試覆蓋率有執念,深信「沒有測試的程式碼等於沒寫完」,溫和但堅持,最在意邊界與失敗路徑
Rogue efficiency 急性子、講求速度,最痛恨被浪費的 CPU 週期與記憶體,凡事先問「這能不能更快、更省」

🔍 服務:opencode 模型:gemini-2.5-flash

## 🤖 AI Code Review 團隊 | 👤 角色 | 🎯 面向 | 🧠 個性 | |--------|--------|--------| | **🗡️ Assassin** | security | 多疑偏執、以攻擊者視角看世界,假設每筆輸入都是惡意的,每個信任都會被濫用 | | **🎼 Bard** | style | 唯美龜毛、追求優雅,把可讀性與一致性當作旋律,最受不了走調的命名與排版 | | **🧰 Leo** | maintainability | 有遠見、重視長期維護成本,凡事先問「六個月後的自己還看得懂嗎?」,討厭把債留給未來 | | **🔮 Mage** | logic | 嚴謹冷靜、滴水不漏,凡事推演到最壞情況,深信「沒驗證過的假設都是 bug」 | | **🧪 Maya** | testing | 對測試覆蓋率有執念,深信「沒有測試的程式碼等於沒寫完」,溫和但堅持,最在意邊界與失敗路徑 | | **⚡ Rogue** | efficiency | 急性子、講求速度,最痛恨被浪費的 CPU 週期與記憶體,凡事先問「這能不能更快、更省」 | > 🔍 服務:opencode 模型:gemini-2.5-flash
gitea-actions bot reviewed 2026-06-26 06:15:19 +00:00
gitea-actions bot left a comment

AI Code Review 統計

類型 🔴 嚴重 🟡 警告 🔵 建議 無法標示
新問題 0 筆 2 筆 1 筆 0 筆
舊問題 0 筆 0 筆 0 筆 0 筆

🤖 AI 助理使用量

本次審查(opencode / gemini-2.5-flash,共 14 次呼叫)

提示 token 回應 token 合計
125,140 2,472 261,987

剩餘可用

剩餘可用:無法計算百分比(自架服務,無帳號額度概念)

## AI Code Review 統計 | 類型 | 🔴 嚴重 | 🟡 警告 | 🔵 建議 | ⚪ 無法標示 | | --- | --- | --- | --- | --- | | 新問題 | 0 筆 | 2 筆 | 1 筆 | 0 筆 | | 舊問題 | 0 筆 | 0 筆 | 0 筆 | 0 筆 | ## 🤖 AI 助理使用量 **本次審查**(opencode / gemini-2.5-flash,共 14 次呼叫) | 提示 token | 回應 token | 合計 | | --- | --- | --- | | 125,140 | 2,472 | 261,987 | **剩餘可用** 剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
@@ -58,0 +62,4 @@
// 單一 repository 區段是否合法:長度在範圍內、僅含允許字元,且非 . 或 .. 路徑穿越段
function isValidRepoSegment(part) {
return part.length > 0
&& part.length <= MAX_REPO_NAME_LENGTH

嚴重等級🟡 警告
審查員:Bard
問題:函數 assertRepository 命名較為通用,但其實際行為僅在驗證 Gitea 的 owner/repo 格式。命名未能直接體現其檢查邏輯與該領域規則。
建議:建議重新命名為 assertGiteaRepositoryFormat,讓開發者一眼就能看出該函數在檢查特定的 Gitea 倉庫格式規範。

**嚴重等級**:🟡 警告 **審查員**:Bard **問題**:函數 `assertRepository` 命名較為通用,但其實際行為僅在驗證 Gitea 的 `owner/repo` 格式。命名未能直接體現其檢查邏輯與該領域規則。 **建議**:建議重新命名為 `assertGiteaRepositoryFormat`,讓開發者一眼就能看出該函數在檢查特定的 Gitea 倉庫格式規範。
Ghost marked this conversation as resolved
app/releases.js Outdated
@@ -64,3 +67,3 @@
pageJson = JSON.parse(text);
} catch {
throw new Error(`release API 回傳資料無法解析 (page=${page})`);
// 先以 UTF-16 長度粗略上限截斷(避免將可能極大的回應整個陣列化),

嚴重等級🟡 警告
審查員:Maya
問題:雖然實作了針對多位元組字元的截斷處理邏輯,但目前的測試案例僅使用 ASCII 字元('x'),缺乏對於包含多位元組字元(如 Emoji 或特殊符號)的真實邊界情境驗證,無法確保在截斷邊界處不會產生亂碼或非預期的行為。
建議:請在 app/test/releases.test.js 中新增一個測試案例,使用包含多位元組字元(例如 Emoji 或代理對字元)的長字串作為 fetch 回應內容,並驗證截斷後的內容片段是否完整且無亂碼。

**嚴重等級**:🟡 警告 **審查員**:Maya **問題**:雖然實作了針對多位元組字元的截斷處理邏輯,但目前的測試案例僅使用 ASCII 字元('x'),缺乏對於包含多位元組字元(如 Emoji 或特殊符號)的真實邊界情境驗證,無法確保在截斷邊界處不會產生亂碼或非預期的行為。 **建議**:請在 app/test/releases.test.js 中新增一個測試案例,使用包含多位元組字元(例如 Emoji 或代理對字元)的長字串作為 fetch 回應內容,並驗證截斷後的內容片段是否完整且無亂碼。
Ghost marked this conversation as resolved
app/releases.js Outdated
@@ -65,2 +68,3 @@
} catch {
throw new Error(`release API 回傳資料無法解析 (page=${page})`);
// 先以 UTF-16 長度粗略上限截斷(避免將可能極大的回應整個陣列化),
// 再以字元(而非 UTF-16 碼元)精準截斷,避免拆分多位元組字元造成亂碼

嚴重等級🔵 建議
審查員:Leo
問題:在錯誤訊息中進行字串截斷處理,程式碼寫得較為複雜,使用了 Array.from(boundedText).slice(...)。雖然考慮了 UTF-16 碼元問題,但這段邏輯與核心業務功能(fetchReleases)混在一起,未來維護者若需調整錯誤訊息格式,容易誤傷功能。
建議:將此段截斷邏輯抽取為一個獨立的工具函式,例如 truncateString(text, limit),放在 utils 檔案中以提升可讀性與測試獨立性。

**嚴重等級**:🔵 建議 **審查員**:Leo **問題**:在錯誤訊息中進行字串截斷處理,程式碼寫得較為複雜,使用了 Array.from(boundedText).slice(...)。雖然考慮了 UTF-16 碼元問題,但這段邏輯與核心業務功能(fetchReleases)混在一起,未來維護者若需調整錯誤訊息格式,容易誤傷功能。 **建議**:將此段截斷邏輯抽取為一個獨立的工具函式,例如 truncateString(text, limit),放在 utils 檔案中以提升可讀性與測試獨立性。
Ghost marked this conversation as resolved
gitea-actions bot added 1 commit 2026-06-26 06:15:21 +00:00
jiantw83 added 4 commits 2026-06-26 07:21:53 +00:00
- releases 將片段截斷邏輯抽為可重用、可獨立測試的 truncateString(text, limit) 並 export
- config 將 assertRepository 更名為 assertGiteaRepositoryFormat,凸顯其驗證 Gitea owner/repo 格式的領域語義

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
新增以 emoji(代理對)驗證截斷不拆分多位元組字元、不殘留落單代理碼元的測試。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
將 comment_token/secrets.COMMENT_TOKEN 改為 token/secrets.TOKEN,對齊 action 輸入。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chore(ai-review 狀態): 更新 findings.json
CI / AI Code Review (pull_request) Successful in 2m41s
a06257a0bc
已解決 3 條(warning x2、info x1:抽出 truncateString、補多位元組截斷測試、assertRepository 改名),findings 清空。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

🤖 AI Code Review 團隊

👤 角色 🎯 面向 🧠 個性
🗡️ Assassin security 多疑偏執、以攻擊者視角看世界,假設每筆輸入都是惡意的,每個信任都會被濫用
🎼 Bard style 唯美龜毛、追求優雅,把可讀性與一致性當作旋律,最受不了走調的命名與排版
🧰 Leo maintainability 有遠見、重視長期維護成本,凡事先問「六個月後的自己還看得懂嗎?」,討厭把債留給未來
🔮 Mage logic 嚴謹冷靜、滴水不漏,凡事推演到最壞情況,深信「沒驗證過的假設都是 bug」
🧪 Maya testing 對測試覆蓋率有執念,深信「沒有測試的程式碼等於沒寫完」,溫和但堅持,最在意邊界與失敗路徑
Rogue efficiency 急性子、講求速度,最痛恨被浪費的 CPU 週期與記憶體,凡事先問「這能不能更快、更省」

🔍 服務:opencode 模型:gemini-2.5-flash

## 🤖 AI Code Review 團隊 | 👤 角色 | 🎯 面向 | 🧠 個性 | |--------|--------|--------| | **🗡️ Assassin** | security | 多疑偏執、以攻擊者視角看世界,假設每筆輸入都是惡意的,每個信任都會被濫用 | | **🎼 Bard** | style | 唯美龜毛、追求優雅,把可讀性與一致性當作旋律,最受不了走調的命名與排版 | | **🧰 Leo** | maintainability | 有遠見、重視長期維護成本,凡事先問「六個月後的自己還看得懂嗎?」,討厭把債留給未來 | | **🔮 Mage** | logic | 嚴謹冷靜、滴水不漏,凡事推演到最壞情況,深信「沒驗證過的假設都是 bug」 | | **🧪 Maya** | testing | 對測試覆蓋率有執念,深信「沒有測試的程式碼等於沒寫完」,溫和但堅持,最在意邊界與失敗路徑 | | **⚡ Rogue** | efficiency | 急性子、講求速度,最痛恨被浪費的 CPU 週期與記憶體,凡事先問「這能不能更快、更省」 | > 🔍 服務:opencode 模型:gemini-2.5-flash
gitea-actions bot reviewed 2026-06-26 07:24:32 +00:00
gitea-actions bot left a comment

AI Code Review 統計

類型 🔴 嚴重 🟡 警告 🔵 建議 無法標示
新問題 0 筆 1 筆 1 筆 0 筆
舊問題 0 筆 1 筆 0 筆 0 筆

🤖 AI 助理使用量

本次審查(opencode / gemini-2.5-flash,共 12 次呼叫)

提示 token 回應 token 合計
115,652 2,611 219,240

剩餘可用

剩餘可用:無法計算百分比(自架服務,無帳號額度概念)

## AI Code Review 統計 | 類型 | 🔴 嚴重 | 🟡 警告 | 🔵 建議 | ⚪ 無法標示 | | --- | --- | --- | --- | --- | | 新問題 | 0 筆 | 1 筆 | 1 筆 | 0 筆 | | 舊問題 | 0 筆 | 1 筆 | 0 筆 | 0 筆 | ## 🤖 AI 助理使用量 **本次審查**(opencode / gemini-2.5-flash,共 12 次呼叫) | 提示 token | 回應 token | 合計 | | --- | --- | --- | | 115,652 | 2,611 | 219,240 | **剩餘可用** 剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
@@ -58,0 +60,4 @@
const REPO_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
// 單一 repository 區段是否合法:長度在範圍內、僅含允許字元,且非 . 或 .. 路徑穿越段
function isValidRepoSegment(part) {

嚴重等級🔵 建議
審查員:Leo
問題:在 assertGiteaRepositoryFormat 函式中,當 value.split('/') 的長度不為 2 時,拋出的錯誤訊息僅籠統地說「格式錯誤」。若使用者輸入了包含多個斜線或完全沒有斜線的字串,這類訊息對修正環境變數幫助有限。
建議:建議區分「格式不符」與「內容不符」的錯誤細節,例如提示「必須為 owner/repo 格式,包含一個斜線」。

**嚴重等級**:🔵 建議 **審查員**:Leo **問題**:在 `assertGiteaRepositoryFormat` 函式中,當 `value.split('/')` 的長度不為 2 時,拋出的錯誤訊息僅籠統地說「格式錯誤」。若使用者輸入了包含多個斜線或完全沒有斜線的字串,這類訊息對修正環境變數幫助有限。 **建議**:建議區分「格式不符」與「內容不符」的錯誤細節,例如提示「必須為 owner/repo 格式,包含一個斜線」。
@@ -6,0 +11,4 @@
function truncateString(text, limit) {
const bounded = text.slice(0, limit * 2);
return Array.from(bounded).slice(0, limit).join('');
}

嚴重等級🟡 警告
審查員:Leo
問題truncateString 使用 Array.from(bounded).slice(0, limit).join('') 的方式處理字串截斷,雖然能避免拆分代理對(surrogate pairs),但在處理極大字串(例如 API_ERROR_SNIPPET_LENGTH 很大時)會因為 Array.from 產生巨大的陣列而導致記憶體使用量激增。考慮到這是在解析失敗時處理的錯誤訊息,這種設計可能讓原本就已經吃緊的記憶體狀況雪上加霜。
建議:若不需要嚴格支援所有 Unicode 字元組合,考慮改用更節省記憶體的方式(如 Intl.Segmenter 或調整截斷邏輯),或者明確說明此處對記憶體的使用考量。若目的是為了錯誤記錄,或許直接截斷原始字串的長度後確保不要在最後一個字元產生半個代理對即可。

**嚴重等級**:🟡 警告 **審查員**:Leo **問題**:`truncateString` 使用 `Array.from(bounded).slice(0, limit).join('')` 的方式處理字串截斷,雖然能避免拆分代理對(surrogate pairs),但在處理極大字串(例如 `API_ERROR_SNIPPET_LENGTH` 很大時)會因為 `Array.from` 產生巨大的陣列而導致記憶體使用量激增。考慮到這是在解析失敗時處理的錯誤訊息,這種設計可能讓原本就已經吃緊的記憶體狀況雪上加霜。 **建議**:若不需要嚴格支援所有 Unicode 字元組合,考慮改用更節省記憶體的方式(如 `Intl.Segmenter` 或調整截斷邏輯),或者明確說明此處對記憶體的使用考量。若目的是為了錯誤記錄,或許直接截斷原始字串的長度後確保不要在最後一個字元產生半個代理對即可。
gitea-actions bot added 1 commit 2026-06-26 07:24:34 +00:00
admin closed this pull request 2026-06-30 06:02:38 +00:00

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: old-docker-actions/calculate-version#8