test(calculate-version): 整併單元測試至 app/test 並移除無法執行的重複測試目錄 #7

Closed
jiantw83 wants to merge 6 commits from test/consolidate-app-test-20260626-111823 into develop
3 changed files with 88 additions and 3 deletions
Showing only changes of commit a9d137ac9e - Show all commits
+32 -3
View File
@@ -10,15 +10,25 @@ const {
loadConfig, loadConfig,
} = require('../config'); } = require('../config');
test('isUnset undefined/空字串/null 字面值視為未設定', () => { test('isUnset undefined/null/空字串/字面 "null" 視為未設定', () => {
assert.equal(isUnset(undefined), true); assert.equal(isUnset(undefined), true);
Ghost marked this conversation as resolved
Review

嚴重等級🟡 警告
審查員:Leo
問題:測試案例名稱與內容不符,將相反行為寫在同一個 test 中,且移除了對 null 與 "false" 的特定檢查。
建議:將斷言拆分為獨立的測試案例,並補回對 null 與字串 "false" 的邊界測試。

**嚴重等級**:🟡 警告 **審查員**:Leo **問題**:測試案例名稱與內容不符,將相反行為寫在同一個 test 中,且移除了對 null 與 "false" 的特定檢查。 **建議**:將斷言拆分為獨立的測試案例,並補回對 null 與字串 "false" 的邊界測試。
assert.equal(isUnset(null), true);
assert.equal(isUnset(''), true); assert.equal(isUnset(''), true);
assert.equal(isUnset('null'), true); assert.equal(isUnset('null'), true);
assert.equal(isUnset('value'), false);
}); });
test('requireEnv 未設定時拋錯,有值時回傳原值', () => { test('isUnset 對其他非空值視為已設定', () => {
assert.equal(isUnset('value'), false);
assert.equal(isUnset('false'), false);
assert.equal(isUnset('0'), false);
});
test('requireEnv 未設定時拋錯', () => {
assert.throws(() => requireEnv('FOO', ''), /FOO 未設定/); assert.throws(() => requireEnv('FOO', ''), /FOO 未設定/);
assert.throws(() => requireEnv('FOO', undefined), /FOO 未設定/);
});
test('requireEnv 有值時回傳原值', () => {
assert.equal(requireEnv('FOO', 'bar'), 'bar'); assert.equal(requireEnv('FOO', 'bar'), 'bar');
}); });
@@ -73,3 +83,22 @@ test('loadConfig 於 GITEA_SERVER_URL 使用非 http(s) 協定時拋錯', () =>
GITEA_REPOSITORY: 'owner/repo', GITEA_REPOSITORY: 'owner/repo',
}), /必須使用 http 或 https/); }), /必須使用 http 或 https/);
Review

嚴重等級🟡 警告
審查員:Maya
問題:缺少針對 assertRepository 邊界條件(如空字串、異常格式)的測試。
建議:補上 assert.throws 測試案例以涵蓋上述邊界條件,確保驗證邏輯完整。

**嚴重等級**:🟡 警告 **審查員**:Maya **問題**:缺少針對 `assertRepository` 邊界條件(如空字串、異常格式)的測試。 **建議**:補上 `assert.throws` 測試案例以涵蓋上述邊界條件,確保驗證邏輯完整。
}); });
test('loadConfig 於 GITEA_REPOSITORY 格式錯誤或含路徑穿越時拋錯', () => {
assert.throws(() => loadConfig({
GITEA_SERVER_URL: 'https://gitea.example.com',
GITEA_REPOSITORY: '../evil',
}), /GITEA_REPOSITORY 格式錯誤/);
assert.throws(() => loadConfig({
GITEA_SERVER_URL: 'https://gitea.example.com',
GITEA_REPOSITORY: 'owner/repo/extra',
}), /GITEA_REPOSITORY 格式錯誤/);
});
test('loadConfig 接受含點號的合法 owner/repo', () => {
const config = loadConfig({
GITEA_SERVER_URL: 'https://gitea.example.com',
GITEA_REPOSITORY: 'my.org/my.repo',
});
assert.equal(config.repository, 'my.org/my.repo');
});
+55
View File
@@ -0,0 +1,55 @@
'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const logger = require('../logger');
// 暫時攔截 process.stdout.write,回傳期間內寫出的內容
Review

嚴重等級🟡 警告
審查員:Leo
問題:在測試中使用 monkey-patch 直接覆蓋 process.stdout.writeprocess.stderr.write 是極度危險的模式,可能導致狀態污染;且針對 logger.error 的測試不夠全面。
建議:建議重構 logger 使其支援依賴注入;並增加針對錯誤內容包含情況及非字串參數的斷言測試。

**嚴重等級**:🟡 警告 **審查員**:Leo **問題**:在測試中使用 monkey-patch 直接覆蓋 `process.stdout.write` 或 `process.stderr.write` 是極度危險的模式,可能導致狀態污染;且針對 `logger.error` 的測試不夠全面。 **建議**:建議重構 `logger` 使其支援依賴注入;並增加針對錯誤內容包含情況及非字串參數的斷言測試。
function captureStdout(fn) {
const original = process.stdout.write;
Review

嚴重等級🔵 建議
審查員:Bard
問題:測試工具函式 captureStdoutcaptureStderr 邏輯高度重複,不符合 DRY 原則。
建議:建議抽取出通用的 captureStream(stream, fn) 函式以提升可維護性。

**嚴重等級**:🔵 建議 **審查員**:Bard **問題**:測試工具函式 `captureStdout` 與 `captureStderr` 邏輯高度重複,不符合 DRY 原則。 **建議**:建議抽取出通用的 `captureStream(stream, fn)` 函式以提升可維護性。
let out = '';
process.stdout.write = (chunk) => { out += chunk; return true; };
try {
fn();
} finally {
process.stdout.write = original;
}
return out;
}
// 暫時攔截 process.stderr.write,回傳期間內寫出的內容
function captureStderr(fn) {
const original = process.stderr.write;
let out = '';
process.stderr.write = (chunk) => { out += chunk; return true; };
try {
fn();
} finally {
process.stderr.write = original;
}
return out;
}
test('section 以主/次分隔線包夾標題輸出至 stdout', () => {
const out = captureStdout(() => logger.section('參數檢查'));
const line = '='.repeat(50);
const subline = '-'.repeat(50);
assert.equal(out, `\n${line}\n參數檢查\n${subline}\n`);
});
test('info 以 [info] 前綴與換行輸出至 stdout', () => {
const out = captureStdout(() => logger.info('IS_BETA=false'));
assert.equal(out, '[info] IS_BETA=false\n');
});
test('error 以 [error] 前綴與換行輸出至 stderr', () => {
const out = captureStderr(() => logger.error('GITEA_SERVER_URL 未設定'));
assert.equal(out, '[error] GITEA_SERVER_URL 未設定\n');
});
test('error 僅輸出不終止行程,呼叫後仍可繼續執行', () => {
let reached = false;
captureStderr(() => { logger.error('still alive'); reached = true; });
assert.equal(reached, true);
});
+1
View File
@@ -18,6 +18,7 @@ const rel = (tag) => ({ tag_name: tag });
test('compareVersionArrays 逐區段比較且較短者較小', () => { test('compareVersionArrays 逐區段比較且較短者較小', () => {
assert.ok(compareVersionArrays([1, 10], [1, 2, 3]) > 0); assert.ok(compareVersionArrays([1, 10], [1, 2, 3]) > 0);
Ghost marked this conversation as resolved
Review

嚴重等級🟡 警告
審查員:Maya
問題:新的 compareVersionArrays 測試雖增加了案例,但移除了 compareVersionArrays([2, 0, 0], [1, 9, 9]) > 0,這對 Major 版本進位的跨區段比較極為重要。
建議:建議補回對大版本變更的比較測試,確保版本排序邏輯正確。

**嚴重等級**:🟡 警告 **審查員**:Maya **問題**:新的 `compareVersionArrays` 測試雖增加了案例,但移除了 `compareVersionArrays([2, 0, 0], [1, 9, 9]) > 0`,這對 Major 版本進位的跨區段比較極為重要。 **建議**:建議補回對大版本變更的比較測試,確保版本排序邏輯正確。
assert.ok(compareVersionArrays([1, 2], [1, 2, 0]) < 0); assert.ok(compareVersionArrays([1, 2], [1, 2, 0]) < 0);
assert.ok(compareVersionArrays([2, 0, 0], [1, 9, 9]) > 0);
assert.equal(compareVersionArrays([1, 2, 3], [1, 2, 3]), 0); assert.equal(compareVersionArrays([1, 2, 3], [1, 2, 3]), 0);
}); });