test(calculate-version): 整併單元測試至 app/test 並移除無法執行的重複測試目錄 #7
+28
-21
@@ -1,7 +1,8 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { test } = require('node:test');
|
const test = require('node:test');
|
||||||
const assert = require('node:assert/strict');
|
const assert = require('node:assert/strict');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isUnset,
|
isUnset,
|
||||||
requireEnv,
|
requireEnv,
|
||||||
@@ -9,42 +10,33 @@ const {
|
|||||||
loadConfig,
|
loadConfig,
|
||||||
} = require('../config');
|
} = require('../config');
|
||||||
|
|
||||||
test('isUnset 對未設定值回傳 true', () => {
|
test('isUnset 將 undefined/空字串/null 字面值視為未設定', () => {
|
||||||
assert.equal(isUnset(undefined), true);
|
assert.equal(isUnset(undefined), true);
|
||||||
|
Ghost marked this conversation as resolved
|
|||||||
assert.equal(isUnset(null), true);
|
|
||||||
assert.equal(isUnset(''), true);
|
assert.equal(isUnset(''), true);
|
||||||
assert.equal(isUnset('null'), true);
|
assert.equal(isUnset('null'), true);
|
||||||
});
|
|
||||||
|
|
||||||
test('isUnset 對有效值回傳 false', () => {
|
|
||||||
assert.equal(isUnset('value'), false);
|
assert.equal(isUnset('value'), false);
|
||||||
assert.equal(isUnset('false'), false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('requireEnv 對未設定值丟出錯誤', () => {
|
test('requireEnv 未設定時拋錯,有值時回傳原值', () => {
|
||||||
assert.throws(() => requireEnv('NAME', ''), /NAME 未設定/);
|
assert.throws(() => requireEnv('FOO', ''), /FOO 未設定/);
|
||||||
assert.throws(() => requireEnv('NAME', 'null'), /NAME 未設定/);
|
assert.equal(requireEnv('FOO', 'bar'), 'bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Ghost marked this conversation as resolved
Outdated
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Leo
**問題**:測試案例同時包含「錯誤處理」與「成功回傳」兩種行為。測試應遵循單一職責原則,若邏輯調整導致其中一項失敗,目前寫法會使得錯誤定位變得困難。
**建議**:建議將 requireEnv 的拋錯測試與成功回傳測試拆分為兩個獨立的測試案例。
|
|||||||
test('requireEnv 對有效值回傳原值', () => {
|
test('normalizeBetaFlag 僅 "true" 視為啟用', () => {
|
||||||
assert.equal(requireEnv('NAME', 'value'), 'value');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('normalizeBetaFlag 僅將字面值 "true" 視為啟用', () => {
|
|
||||||
assert.equal(normalizeBetaFlag('true'), true);
|
assert.equal(normalizeBetaFlag('true'), true);
|
||||||
assert.equal(normalizeBetaFlag('false'), false);
|
assert.equal(normalizeBetaFlag('false'), false);
|
||||||
assert.equal(normalizeBetaFlag('yes'), false);
|
|
||||||
assert.equal(normalizeBetaFlag(undefined), false);
|
assert.equal(normalizeBetaFlag(undefined), false);
|
||||||
assert.equal(normalizeBetaFlag('null'), false);
|
assert.equal(normalizeBetaFlag('TRUE'), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('loadConfig 從環境變數組出設定', () => {
|
test('loadConfig 解析完整設定', () => {
|
||||||
const config = loadConfig({
|
const config = loadConfig({
|
||||||
GITEA_SERVER_URL: 'https://gitea.example.com',
|
GITEA_SERVER_URL: 'https://gitea.example.com',
|
||||||
GITEA_REPOSITORY: 'owner/repo',
|
GITEA_REPOSITORY: 'owner/repo',
|
||||||
GITEA_TOKEN: 'secret',
|
GITEA_TOKEN: 'secret',
|
||||||
IS_BETA: 'true',
|
IS_BETA: 'true',
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.deepEqual(config, {
|
assert.deepEqual(config, {
|
||||||
serverUrl: 'https://gitea.example.com',
|
serverUrl: 'https://gitea.example.com',
|
||||||
repository: 'owner/repo',
|
repository: 'owner/repo',
|
||||||
@@ -53,16 +45,31 @@ test('loadConfig 從環境變數組出設定', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('loadConfig 將未提供的 token 正規化為 null,beta 預設 false', () => {
|
test('loadConfig 缺少 token 視為匿名(null)', () => {
|
||||||
const config = loadConfig({
|
const config = loadConfig({
|
||||||
GITEA_SERVER_URL: 'https://gitea.example.com',
|
GITEA_SERVER_URL: 'https://gitea.example.com',
|
||||||
GITEA_REPOSITORY: 'owner/repo',
|
GITEA_REPOSITORY: 'owner/repo',
|
||||||
|
GITEA_TOKEN: 'null',
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(config.token, null);
|
assert.equal(config.token, null);
|
||||||
assert.equal(config.isBeta, false);
|
assert.equal(config.isBeta, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('loadConfig 在缺少必填環境變數時丟出錯誤', () => {
|
test('loadConfig 缺少必填項目時拋錯', () => {
|
||||||
assert.throws(() => loadConfig({ GITEA_REPOSITORY: 'owner/repo' }), /GITEA_SERVER_URL 未設定/);
|
assert.throws(() => loadConfig({ GITEA_REPOSITORY: 'owner/repo' }), /GITEA_SERVER_URL 未設定/);
|
||||||
assert.throws(() => loadConfig({ GITEA_SERVER_URL: 'https://x' }), /GITEA_REPOSITORY 未設定/);
|
});
|
||||||
|
|
||||||
|
test('loadConfig 於 GITEA_SERVER_URL 非合法 URL 時拋錯', () => {
|
||||||
|
assert.throws(() => loadConfig({
|
||||||
|
GITEA_SERVER_URL: 'not-a-url',
|
||||||
|
GITEA_REPOSITORY: 'owner/repo',
|
||||||
|
}), /GITEA_SERVER_URL 格式錯誤/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('loadConfig 於 GITEA_SERVER_URL 使用非 http(s) 協定時拋錯', () => {
|
||||||
|
assert.throws(() => loadConfig({
|
||||||
|
GITEA_SERVER_URL: 'ftp://gitea.example.com',
|
||||||
|
GITEA_REPOSITORY: 'owner/repo',
|
||||||
|
}), /必須使用 http 或 https/);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
const test = require('node:test');
|
const test = require('node:test');
|
||||||
const assert = require('node:assert/strict');
|
const assert = require('node:assert/strict');
|
||||||
|
|
||||||
const { main } = require('../app/index');
|
const { main } = require('../index');
|
||||||
|
|
||||||
// 安靜的 log 記錄器,避免測試輸出雜訊
|
// 安靜的 log 記錄器,避免測試輸出雜訊
|
||||||
const silentLog = { section() {}, info() {}, error() {} };
|
const silentLog = { section() {}, info() {}, error() {} };
|
||||||
+89
-54
@@ -1,70 +1,105 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { test, afterEach } = require('node:test');
|
const test = require('node:test');
|
||||||
const assert = require('node:assert/strict');
|
const assert = require('node:assert/strict');
|
||||||
const { RELEASES_PER_PAGE, fetchReleases } = require('../releases');
|
|
||||||
|
|
||||||
const realFetch = globalThis.fetch;
|
const { fetchReleases } = require('../releases');
|
||||||
|
|
||||||
afterEach(() => {
|
// 以可控的假回應替換全域 fetch,並於結束後還原
|
||||||
globalThis.fetch = realFetch;
|
function withFetch(handler, run) {
|
||||||
});
|
const original = globalThis.fetch;
|
||||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🔵 建議 **嚴重等級**:🔵 建議
**審查員**:Leo
**問題**:withFetch 為全域 fetch 的通用封裝工具,目前定義在特定測試檔案內。若後續專案中其他測試檔案也需要模擬 fetch,將導致測試工具邏輯重複分散。
**建議**:建議將此類通用的測試輔助函式提取至獨立的測試工具檔案(例如 app/test/test-utils.js),以提升測試程式碼的重用性與維護一致性。
|
|||||||
|
globalThis.fetch = handler;
|
||||||
function mockResponse(items) {
|
return Promise.resolve()
|
||||||
return { ok: true, text: async () => JSON.stringify(items) };
|
.then(run)
|
||||||
|
.finally(() => {
|
||||||
|
globalThis.fetch = original;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
test('fetchReleases 逐頁讀取直到不足一頁', async () => {
|
// 建立模擬 Response 物件
|
||||||
const fullPage = Array.from({ length: RELEASES_PER_PAGE }, (_, i) => ({ id: i }));
|
const jsonResponse = (body, ok = true) => ({
|
||||||
const lastPage = [{ id: 100 }, { id: 101 }];
|
ok,
|
||||||
const requested = [];
|
text: async () => (typeof body === 'string' ? body : JSON.stringify(body)),
|
||||||
globalThis.fetch = async (url) => {
|
|
||||||
const page = Number(new URL(url).searchParams.get('page'));
|
|
||||||
requested.push(page);
|
|
||||||
return mockResponse(page === 1 ? fullPage : lastPage);
|
|
||||||
};
|
|
||||||
|
|
||||||
const releases = await fetchReleases('https://gitea.example.com/api/v1/repos/o/r/releases');
|
|
||||||
|
|
||||||
assert.equal(releases.length, RELEASES_PER_PAGE + lastPage.length);
|
|
||||||
assert.deepEqual(requested, [1, 2]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fetchReleases 在空陣列頁面停止', async () => {
|
test('fetchReleases 單頁(不足一頁即停止)', async () => {
|
||||||
globalThis.fetch = async () => mockResponse([]);
|
await withFetch(
|
||||||
const releases = await fetchReleases('https://gitea.example.com/api/v1/repos/o/r/releases');
|
async () => jsonResponse([{ tag_name: 'v1.0.0' }]),
|
||||||
assert.deepEqual(releases, []);
|
async () => {
|
||||||
});
|
const releases = await fetchReleases('https://gitea.example.com/api');
|
||||||
|
assert.deepEqual(releases, [{ tag_name: 'v1.0.0' }]);
|
||||||
test('fetchReleases 帶上授權標頭', async () => {
|
},
|
||||||
let seenHeaders;
|
|
||||||
globalThis.fetch = async (_url, opts) => {
|
|
||||||
seenHeaders = opts.headers;
|
|
||||||
return mockResponse([]);
|
|
||||||
};
|
|
||||||
|
|
||||||
await fetchReleases('https://gitea.example.com/api/v1/repos/o/r/releases', { token: 'secret' });
|
|
||||||
assert.equal(seenHeaders.Authorization, 'token secret');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fetchReleases 在 HTTP 錯誤時丟出例外', async () => {
|
|
||||||
globalThis.fetch = async () => ({ ok: false, status: 500 });
|
|
||||||
await assert.rejects(
|
|
||||||
() => fetchReleases('https://gitea.example.com/api/v1/repos/o/r/releases'),
|
|
||||||
/release API 請求失敗/,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fetchReleases 在回傳非陣列時丟出例外', async () => {
|
test('fetchReleases 跨頁合併直到不足一頁', async () => {
|
||||||
globalThis.fetch = async () => mockResponse({ message: 'not an array' });
|
const pages = {
|
||||||
await assert.rejects(
|
1: Array.from({ length: 10 }, (_, i) => ({ tag_name: `v1.0.${i}` })),
|
||||||
() => fetchReleases('https://gitea.example.com/api/v1/repos/o/r/releases'),
|
2: [{ tag_name: 'v1.1.0' }],
|
||||||
/回傳非陣列資料/,
|
};
|
||||||
|
const seen = [];
|
||||||
|
|
||||||
|
await withFetch(
|
||||||
|
async (url) => {
|
||||||
|
const page = Number(new URL(url).searchParams.get('page'));
|
||||||
|
seen.push(page);
|
||||||
|
return jsonResponse(pages[page]);
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
const releases = await fetchReleases('https://gitea.example.com/api');
|
||||||
|
assert.equal(releases.length, 11);
|
||||||
|
assert.deepEqual(seen, [1, 2]);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fetchReleases 在 null 回應時視為無更多資料', async () => {
|
test('fetchReleases 對非 2xx 回應拋錯', async () => {
|
||||||
globalThis.fetch = async () => ({ ok: true, text: async () => 'null' });
|
await withFetch(
|
||||||
const releases = await fetchReleases('https://gitea.example.com/api/v1/repos/o/r/releases');
|
async () => jsonResponse('Not Found', false),
|
||||||
assert.deepEqual(releases, []);
|
async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
() => fetchReleases('https://gitea.example.com/api'),
|
||||||
|
/release API 請求失敗/,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fetchReleases 對無法解析的 JSON 回應拋錯', async () => {
|
||||||
|
await withFetch(
|
||||||
|
async () => jsonResponse('{ this is not valid json', true),
|
||||||
|
async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
() => fetchReleases('https://gitea.example.com/api'),
|
||||||
|
/回傳資料無法解析/,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fetchReleases 對非陣列回應拋錯', async () => {
|
||||||
|
await withFetch(
|
||||||
|
async () => jsonResponse({ message: 'oops' }),
|
||||||
|
async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
() => fetchReleases('https://gitea.example.com/api'),
|
||||||
|
/回傳非陣列資料/,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fetchReleases 有 token 時帶入授權標頭', async () => {
|
||||||
|
let captured;
|
||||||
|
|
||||||
|
await withFetch(
|
||||||
|
async (url, options) => {
|
||||||
|
captured = options;
|
||||||
|
return jsonResponse([]);
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
await fetchReleases('https://gitea.example.com/api', { token: 'secret' });
|
||||||
|
assert.equal(captured.headers.Authorization, 'token secret');
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
+42
-58
@@ -1,7 +1,8 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { test } = require('node:test');
|
const test = require('node:test');
|
||||||
const assert = require('node:assert/strict');
|
const assert = require('node:assert/strict');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
compareVersionArrays,
|
compareVersionArrays,
|
||||||
parseStableVersions,
|
parseStableVersions,
|
||||||
@@ -11,91 +12,74 @@ const {
|
|||||||
calculateVersion,
|
calculateVersion,
|
||||||
} = require('../version');
|
} = require('../version');
|
||||||
|
|
||||||
test('compareVersionArrays 逐區段數值比較,較短者視為較小', () => {
|
// 便於建構測試資料的小工具
|
||||||
assert.ok(compareVersionArrays([1, 10, 0], [1, 9, 0]) > 0);
|
const rel = (tag) => ({ tag_name: tag });
|
||||||
|
|
||||||
|
test('compareVersionArrays 逐區段比較且較短者較小', () => {
|
||||||
|
assert.ok(compareVersionArrays([1, 10], [1, 2, 3]) > 0);
|
||||||
|
Ghost marked this conversation as resolved
gitea-actions
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**: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.equal(compareVersionArrays([1, 2, 3], [1, 2, 3]), 0);
|
assert.equal(compareVersionArrays([1, 2, 3], [1, 2, 3]), 0);
|
||||||
assert.ok(compareVersionArrays([2, 0, 0], [1, 9, 9]) > 0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parseStableVersions 排除 beta、去除前綴 v 並驗證數字格式', () => {
|
test('parseStableVersions 排除 beta 與非法 tag 並去除 v 前綴', () => {
|
||||||
const releases = [
|
const releases = [
|
||||||
{ tag_name: 'v1.2.3' },
|
rel('v1.2.3'),
|
||||||
{ tag_name: '2.0.0' },
|
rel('2.0.0'),
|
||||||
{ tag_name: 'v1.3.0-beta.1' },
|
rel('v1.0.0-beta.1'),
|
||||||
{ tag_name: 'nightly' },
|
rel('latest'),
|
||||||
{ tag_name: null },
|
{ name: '沒有 tag_name' },
|
||||||
null,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
assert.deepEqual(parseStableVersions(releases), [[1, 2, 3], [2, 0, 0]]);
|
assert.deepEqual(parseStableVersions(releases), [[1, 2, 3], [2, 0, 0]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parseStableVersions 對非陣列回傳空陣列', () => {
|
test('latestStableVersion 無資料時回傳 0.0.0', () => {
|
||||||
assert.deepEqual(parseStableVersions(null), []);
|
|
||||||
assert.deepEqual(parseStableVersions(undefined), []);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('latestStableVersion 在無資料時回傳 0.0.0', () => {
|
|
||||||
assert.equal(latestStableVersion([]), '0.0.0');
|
assert.equal(latestStableVersion([]), '0.0.0');
|
||||||
assert.equal(latestStableVersion([{ tag_name: 'v1.0.0-beta.1' }]), '0.0.0');
|
assert.equal(latestStableVersion(null), '0.0.0');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('latestStableVersion 取最新的穩定版(含跨區段比較)', () => {
|
test('latestStableVersion 取出最大穩定版', () => {
|
||||||
assert.equal(latestStableVersion([
|
const releases = [rel('v1.2.3'), rel('v1.9.9'), rel('v1.2.9'), rel('v0.5.0-beta.2')];
|
||||||
{ tag_name: 'v1.9.0' },
|
assert.equal(latestStableVersion(releases), '1.9.9');
|
||||||
{ tag_name: 'v1.10.0' },
|
|
||||||
{ tag_name: 'v1.2.3' },
|
|
||||||
]), '1.10.0');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('nextReleaseVersion 對 patch 進位', () => {
|
test('nextReleaseVersion patch 進位', () => {
|
||||||
assert.equal(nextReleaseVersion('0.0.0'), '0.0.1');
|
assert.equal(nextReleaseVersion('0.0.0'), '0.0.1');
|
||||||
assert.equal(nextReleaseVersion('1.2.3'), '1.2.4');
|
assert.equal(nextReleaseVersion('1.2.3'), '1.2.4');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('nextReleaseVersion 在 patch 達 10 時進位至 minor', () => {
|
test('nextReleaseVersion patch 達 10 進位至 minor', () => {
|
||||||
assert.equal(nextReleaseVersion('1.2.9'), '1.3.0');
|
assert.equal(nextReleaseVersion('1.2.9'), '1.3.0');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('nextReleaseVersion 在 minor 也達 10 時進位至 major', () => {
|
test('nextReleaseVersion minor 達 10 進位至 major', () => {
|
||||||
assert.equal(nextReleaseVersion('1.9.9'), '2.0.0');
|
assert.equal(nextReleaseVersion('1.9.9'), '2.0.0');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('nextBetaNumber 在查無對應 beta 時回傳 1', () => {
|
test('nextBetaNumber 無對應 beta 時回傳 1', () => {
|
||||||
assert.equal(nextBetaNumber([], '1.2.4'), 1);
|
assert.equal(nextBetaNumber([], '0.0.1'), 1);
|
||||||
assert.equal(nextBetaNumber([{ tag_name: 'v9.9.9-beta.5' }], '1.2.4'), 1);
|
assert.equal(nextBetaNumber([rel('v0.0.1')], '0.0.1'), 1);
|
||||||
assert.equal(nextBetaNumber(null, '1.2.4'), 1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('nextBetaNumber 取對應 beta 流水號最大值 + 1', () => {
|
test('nextBetaNumber 取最大流水號加一', () => {
|
||||||
const releases = [
|
const releases = [rel('v1.3.0-beta.1'), rel('v1.3.0-beta.3'), rel('v1.3.0-beta.2')];
|
||||||
{ tag_name: 'v1.2.4-beta.1' },
|
assert.equal(nextBetaNumber(releases, '1.3.0'), 4);
|
||||||
{ tag_name: 'v1.2.4-beta.3' },
|
|
||||||
{ tag_name: 'v1.2.4-beta.2' },
|
|
||||||
{ tag_name: 'v1.2.5-beta.9' },
|
|
||||||
];
|
|
||||||
assert.equal(nextBetaNumber(releases, '1.2.4'), 4);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('calculateVersion 計算下一個穩定版', () => {
|
test('calculateVersion 無 release 的穩定版', () => {
|
||||||
assert.deepEqual(
|
|
||||||
calculateVersion([{ tag_name: 'v1.2.3' }], false),
|
|
||||||
{ latest: '1.2.3', version: '1.2.4' },
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('calculateVersion 計算下一個 beta 版', () => {
|
|
||||||
const releases = [
|
|
||||||
{ tag_name: 'v1.2.3' },
|
|
||||||
{ tag_name: 'v1.2.4-beta.2' },
|
|
||||||
];
|
|
||||||
assert.deepEqual(
|
|
||||||
calculateVersion(releases, true),
|
|
||||||
{ latest: '1.2.3', version: '1.2.4-beta.3' },
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('calculateVersion 在無任何 release 時的預設值', () => {
|
|
||||||
assert.deepEqual(calculateVersion([], false), { latest: '0.0.0', version: '0.0.1' });
|
assert.deepEqual(calculateVersion([], false), { latest: '0.0.0', version: '0.0.1' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calculateVersion 無 release 的 beta 版', () => {
|
||||||
assert.deepEqual(calculateVersion([], true), { latest: '0.0.0', version: '0.0.1-beta.1' });
|
assert.deepEqual(calculateVersion([], true), { latest: '0.0.0', version: '0.0.1-beta.1' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('calculateVersion 既有穩定版推下一個穩定版', () => {
|
||||||
|
const releases = [rel('v1.2.3'), rel('v1.2.2')];
|
||||||
|
assert.deepEqual(calculateVersion(releases, false), { latest: '1.2.3', version: '1.2.4' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calculateVersion 既有穩定版推下一個 beta 版', () => {
|
||||||
|
const releases = [rel('v1.2.3'), rel('v1.2.4-beta.1')];
|
||||||
|
assert.deepEqual(calculateVersion(releases, true), { latest: '1.2.3', version: '1.2.4-beta.2' });
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const test = require('node:test');
|
|
||||||
const assert = require('node:assert/strict');
|
|
||||||
|
|
||||||
const {
|
|
||||||
isUnset,
|
|
||||||
requireEnv,
|
|
||||||
normalizeBetaFlag,
|
|
||||||
loadConfig,
|
|
||||||
} = require('../app/config');
|
|
||||||
|
|
||||||
test('isUnset 將 undefined/空字串/null 字面值視為未設定', () => {
|
|
||||||
assert.equal(isUnset(undefined), true);
|
|
||||||
assert.equal(isUnset(''), true);
|
|
||||||
assert.equal(isUnset('null'), true);
|
|
||||||
assert.equal(isUnset('value'), false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('requireEnv 未設定時拋錯,有值時回傳原值', () => {
|
|
||||||
assert.throws(() => requireEnv('FOO', ''), /FOO 未設定/);
|
|
||||||
assert.equal(requireEnv('FOO', 'bar'), 'bar');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('normalizeBetaFlag 僅 "true" 視為啟用', () => {
|
|
||||||
assert.equal(normalizeBetaFlag('true'), true);
|
|
||||||
assert.equal(normalizeBetaFlag('false'), false);
|
|
||||||
assert.equal(normalizeBetaFlag(undefined), false);
|
|
||||||
assert.equal(normalizeBetaFlag('TRUE'), false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('loadConfig 解析完整設定', () => {
|
|
||||||
const config = loadConfig({
|
|
||||||
GITEA_SERVER_URL: 'https://gitea.example.com',
|
|
||||||
GITEA_REPOSITORY: 'owner/repo',
|
|
||||||
GITEA_TOKEN: 'secret',
|
|
||||||
IS_BETA: 'true',
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.deepEqual(config, {
|
|
||||||
serverUrl: 'https://gitea.example.com',
|
|
||||||
repository: 'owner/repo',
|
|
||||||
token: 'secret',
|
|
||||||
isBeta: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('loadConfig 缺少 token 視為匿名(null)', () => {
|
|
||||||
const config = loadConfig({
|
|
||||||
GITEA_SERVER_URL: 'https://gitea.example.com',
|
|
||||||
GITEA_REPOSITORY: 'owner/repo',
|
|
||||||
GITEA_TOKEN: 'null',
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(config.token, null);
|
|
||||||
assert.equal(config.isBeta, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('loadConfig 缺少必填項目時拋錯', () => {
|
|
||||||
assert.throws(() => loadConfig({ GITEA_REPOSITORY: 'owner/repo' }), /GITEA_SERVER_URL 未設定/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('loadConfig 於 GITEA_SERVER_URL 非合法 URL 時拋錯', () => {
|
|
||||||
assert.throws(() => loadConfig({
|
|
||||||
GITEA_SERVER_URL: 'not-a-url',
|
|
||||||
GITEA_REPOSITORY: 'owner/repo',
|
|
||||||
}), /GITEA_SERVER_URL 格式錯誤/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('loadConfig 於 GITEA_SERVER_URL 使用非 http(s) 協定時拋錯', () => {
|
|
||||||
assert.throws(() => loadConfig({
|
|
||||||
GITEA_SERVER_URL: 'ftp://gitea.example.com',
|
|
||||||
GITEA_REPOSITORY: 'owner/repo',
|
|
||||||
}), /必須使用 http 或 https/);
|
|
||||||
});
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const test = require('node:test');
|
|
||||||
const assert = require('node:assert/strict');
|
|
||||||
|
|
||||||
const { fetchReleases } = require('../app/releases');
|
|
||||||
|
|
||||||
// 以可控的假回應替換全域 fetch,並於結束後還原
|
|
||||||
function withFetch(handler, run) {
|
|
||||||
const original = globalThis.fetch;
|
|
||||||
globalThis.fetch = handler;
|
|
||||||
return Promise.resolve()
|
|
||||||
.then(run)
|
|
||||||
.finally(() => {
|
|
||||||
globalThis.fetch = original;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 建立模擬 Response 物件
|
|
||||||
const jsonResponse = (body, ok = true) => ({
|
|
||||||
ok,
|
|
||||||
text: async () => (typeof body === 'string' ? body : JSON.stringify(body)),
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fetchReleases 單頁(不足一頁即停止)', async () => {
|
|
||||||
await withFetch(
|
|
||||||
async () => jsonResponse([{ tag_name: 'v1.0.0' }]),
|
|
||||||
async () => {
|
|
||||||
const releases = await fetchReleases('https://gitea.example.com/api');
|
|
||||||
assert.deepEqual(releases, [{ tag_name: 'v1.0.0' }]);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fetchReleases 跨頁合併直到不足一頁', async () => {
|
|
||||||
const pages = {
|
|
||||||
1: Array.from({ length: 10 }, (_, i) => ({ tag_name: `v1.0.${i}` })),
|
|
||||||
2: [{ tag_name: 'v1.1.0' }],
|
|
||||||
};
|
|
||||||
const seen = [];
|
|
||||||
|
|
||||||
await withFetch(
|
|
||||||
async (url) => {
|
|
||||||
const page = Number(new URL(url).searchParams.get('page'));
|
|
||||||
seen.push(page);
|
|
||||||
return jsonResponse(pages[page]);
|
|
||||||
},
|
|
||||||
async () => {
|
|
||||||
const releases = await fetchReleases('https://gitea.example.com/api');
|
|
||||||
assert.equal(releases.length, 11);
|
|
||||||
assert.deepEqual(seen, [1, 2]);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fetchReleases 對非 2xx 回應拋錯', async () => {
|
|
||||||
await withFetch(
|
|
||||||
async () => jsonResponse('Not Found', false),
|
|
||||||
async () => {
|
|
||||||
await assert.rejects(
|
|
||||||
() => fetchReleases('https://gitea.example.com/api'),
|
|
||||||
/release API 請求失敗/,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fetchReleases 對無法解析的 JSON 回應拋錯', async () => {
|
|
||||||
await withFetch(
|
|
||||||
async () => jsonResponse('{ this is not valid json', true),
|
|
||||||
async () => {
|
|
||||||
await assert.rejects(
|
|
||||||
() => fetchReleases('https://gitea.example.com/api'),
|
|
||||||
/回傳資料無法解析/,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fetchReleases 對非陣列回應拋錯', async () => {
|
|
||||||
await withFetch(
|
|
||||||
async () => jsonResponse({ message: 'oops' }),
|
|
||||||
async () => {
|
|
||||||
await assert.rejects(
|
|
||||||
() => fetchReleases('https://gitea.example.com/api'),
|
|
||||||
/回傳非陣列資料/,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fetchReleases 有 token 時帶入授權標頭', async () => {
|
|
||||||
let captured;
|
|
||||||
|
|
||||||
await withFetch(
|
|
||||||
async (url, options) => {
|
|
||||||
captured = options;
|
|
||||||
return jsonResponse([]);
|
|
||||||
},
|
|
||||||
async () => {
|
|
||||||
await fetchReleases('https://gitea.example.com/api', { token: 'secret' });
|
|
||||||
assert.equal(captured.headers.Authorization, 'token secret');
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const test = require('node:test');
|
|
||||||
const assert = require('node:assert/strict');
|
|
||||||
|
|
||||||
const {
|
|
||||||
compareVersionArrays,
|
|
||||||
parseStableVersions,
|
|
||||||
latestStableVersion,
|
|
||||||
nextReleaseVersion,
|
|
||||||
nextBetaNumber,
|
|
||||||
calculateVersion,
|
|
||||||
} = require('../app/version');
|
|
||||||
|
|
||||||
// 便於建構測試資料的小工具
|
|
||||||
const rel = (tag) => ({ tag_name: tag });
|
|
||||||
|
|
||||||
test('compareVersionArrays 逐區段比較且較短者較小', () => {
|
|
||||||
assert.ok(compareVersionArrays([1, 10], [1, 2, 3]) > 0);
|
|
||||||
assert.ok(compareVersionArrays([1, 2], [1, 2, 0]) < 0);
|
|
||||||
assert.equal(compareVersionArrays([1, 2, 3], [1, 2, 3]), 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('parseStableVersions 排除 beta 與非法 tag 並去除 v 前綴', () => {
|
|
||||||
const releases = [
|
|
||||||
rel('v1.2.3'),
|
|
||||||
rel('2.0.0'),
|
|
||||||
rel('v1.0.0-beta.1'),
|
|
||||||
rel('latest'),
|
|
||||||
{ name: '沒有 tag_name' },
|
|
||||||
];
|
|
||||||
|
|
||||||
assert.deepEqual(parseStableVersions(releases), [[1, 2, 3], [2, 0, 0]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('latestStableVersion 無資料時回傳 0.0.0', () => {
|
|
||||||
assert.equal(latestStableVersion([]), '0.0.0');
|
|
||||||
assert.equal(latestStableVersion(null), '0.0.0');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('latestStableVersion 取出最大穩定版', () => {
|
|
||||||
const releases = [rel('v1.2.3'), rel('v1.9.9'), rel('v1.2.9'), rel('v0.5.0-beta.2')];
|
|
||||||
assert.equal(latestStableVersion(releases), '1.9.9');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('nextReleaseVersion patch 進位', () => {
|
|
||||||
assert.equal(nextReleaseVersion('0.0.0'), '0.0.1');
|
|
||||||
assert.equal(nextReleaseVersion('1.2.3'), '1.2.4');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('nextReleaseVersion patch 達 10 進位至 minor', () => {
|
|
||||||
assert.equal(nextReleaseVersion('1.2.9'), '1.3.0');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('nextReleaseVersion minor 達 10 進位至 major', () => {
|
|
||||||
assert.equal(nextReleaseVersion('1.9.9'), '2.0.0');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('nextBetaNumber 無對應 beta 時回傳 1', () => {
|
|
||||||
assert.equal(nextBetaNumber([], '0.0.1'), 1);
|
|
||||||
assert.equal(nextBetaNumber([rel('v0.0.1')], '0.0.1'), 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('nextBetaNumber 取最大流水號加一', () => {
|
|
||||||
const releases = [rel('v1.3.0-beta.1'), rel('v1.3.0-beta.3'), rel('v1.3.0-beta.2')];
|
|
||||||
assert.equal(nextBetaNumber(releases, '1.3.0'), 4);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('calculateVersion 無 release 的穩定版', () => {
|
|
||||||
assert.deepEqual(calculateVersion([], false), { latest: '0.0.0', version: '0.0.1' });
|
|
||||||
});
|
|
||||||
|
|
||||||
test('calculateVersion 無 release 的 beta 版', () => {
|
|
||||||
assert.deepEqual(calculateVersion([], true), { latest: '0.0.0', version: '0.0.1-beta.1' });
|
|
||||||
});
|
|
||||||
|
|
||||||
test('calculateVersion 既有穩定版推下一個穩定版', () => {
|
|
||||||
const releases = [rel('v1.2.3'), rel('v1.2.2')];
|
|
||||||
assert.deepEqual(calculateVersion(releases, false), { latest: '1.2.3', version: '1.2.4' });
|
|
||||||
});
|
|
||||||
|
|
||||||
test('calculateVersion 既有穩定版推下一個 beta 版', () => {
|
|
||||||
const releases = [rel('v1.2.3'), rel('v1.2.4-beta.1')];
|
|
||||||
assert.deepEqual(calculateVersion(releases, true), { latest: '1.2.3', version: '1.2.4-beta.2' });
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user
嚴重等級:🟡 警告
審查員:Leo
問題:測試案例名稱與內容不符,將相反行為寫在同一個 test 中,且移除了對 null 與 "false" 的特定檢查。
建議:將斷言拆分為獨立的測試案例,並補回對 null 與字串 "false" 的邊界測試。