🛸 Sync 2026-10-27 02:27:08

This commit is contained in:
github-actions[bot] committed 2026-10-27 02:27:08 +08:00
1 parent b3798acf79
commit 8b63d5d4a9
78 files changed
+2288 -518

No files matched your search

+64
View File
@@ -0,0 +1,64 @@
const { test } = require('node:test');
const assert = require('node:assert/strict');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const lua = process.env.MINIGATE_TEST_LUA;
const controller = path.resolve(__dirname, '../luasrc/controller/minigate.lua');
test('visitor aggregation distinguishes blocked, upstream, legacy and failed requests', { skip: !lua }, () => {
const input = [
"local raw, response, limit = '', nil, '50'",
"package.preload['luci.sys'] = function() return {exec=function() return raw end} end",
"luci = {http={formvalue=function() return limit end, prepare_content=function() end, write_json=function(v) response=v end}}",
'dofile(' + JSON.stringify(controller) + ')',
"local real_time = os.time",
"os.time = function(v) return v and real_time(v) or real_time({year=2026,month=10,day=26,hour=12,min=0,sec=0}) end",
"local function row(ip, status, upstream, domain, time)",
"raw = raw .. '{\"time\":\"' .. (time or '2026-10-26T11:59:00+08:00') .. '\",\"client\":\"' .. ip .. '\",\"domain\":\"' .. (domain or 'app.example.com') .. '\"' ..",
"(status and ',\"status\":' .. status or '') .. (upstream and ',\"upstream_status\":\"' .. upstream .. '\"' or '') .. '}\\n'",
"end",
"row('mixed', 200, '200')",
"row('mixed', 444, '-')",
"row('mixed', 308, '-')",
"row('blocked', 444, '-')",
"row('default', 400, '-', '_')",
"row('upstream403', 403, '403')",
"row('upstream401', 401, '401')",
"row('legacy200', 200)",
"row('legacy403', 403)",
"row('failure', 502, '502')",
"row('closed', 499, '-')",
"row('missing', 200, '200')",
"row('missing', nil, '200', '')",
"row('old', 200, '200', nil, '2026-10-26T11:54:59+08:00')",
"row('future', 200, '200', nil, '2026-10-26T12:01:00+08:00')",
"raw = raw .. 'invalid line\\n'",
"luci.controller.minigate.action_proxy_access()",
"local by_ip = {}",
"for _, v in ipairs(response.visitors) do",
"by_ip[v.ip] = v",
"assert(v.count == v.blocked_count + v.forwarded_count + v.other_count)",
"assert(v.online == nil)",
"end",
"local v = by_ip.mixed",
"assert(v.count == 3 and v.blocked_count == 1 and v.forwarded_count == 1 and v.other_count == 1)",
"assert(v.outcome == 'responded' and v.status == 308)",
"assert(by_ip.blocked.outcome == 'blocked' and by_ip.blocked.recent)",
"assert(by_ip.default.outcome == 'blocked')",
"assert(by_ip.upstream403.outcome == 'forwarded' and by_ip.upstream403.status == 403)",
"assert(by_ip.upstream401.outcome == 'forwarded')",
"assert(by_ip.legacy200.outcome == 'responded')",
"assert(by_ip.legacy403.outcome == 'error')",
"assert(by_ip.failure.outcome == 'error' and by_ip.closed.outcome == 'error')",
"assert(by_ip.missing.outcome == 'unknown' and by_ip.missing.status == nil and by_ip.missing.domain == '')",
"assert(not by_ip.old.recent and not by_ip.future.recent)",
"limit = 'invalid'",
"luci.controller.minigate.action_proxy_access()",
"assert(#response.visitors == 5)",
"print('visitor aggregation passed')"
].join('\n');
const result = spawnSync(lua, ['-'], { encoding: 'utf8', input });
assert.equal(result.status, 0, result.stderr || result.stdout);
assert.match(result.stdout, /aggregation passed/);
});
+7
View File
@@ -78,6 +78,13 @@ test('real nginx enforces source policy before upstream, redirect and websocket'
assert.equal(fixture.reached(), reached, '拒绝的请求不得到达后端');
assert.match(await request(fixture.redirectPath, '14.196.0.1'), /308 Permanent Redirect/);
assert.match(await request(fixture.socketPath, '14.196.0.1', { websocket: true }), /101 Switching Protocols/);
const rows = fs.readFileSync(path.join(fixture.dir, 'access.log'), 'utf8').trim().split('\n').map(JSON.parse);
assert.ok(rows.some(row => row.status === 200 && row.upstream_status === '200'));
assert.ok(rows.some(row => row.status === 101 && row.upstream_status === '101'));
assert.ok(rows.some(row => row.status === 308 && !row.upstream_status));
const blocked = rows.filter(row => row.status === 444);
assert.equal(blocked.length, 18);
assert.ok(blocked.every(row => !row.upstream_status), '被拒绝请求没有上游响应');
});
test('real nginx permits all sources when disabled and fails closed if selected data is missing', { skip: !nginx }, async t => {
+29
View File
@@ -102,6 +102,35 @@ test('overview polling pauses in a hidden tab and refreshes when visible again',
assert.deepEqual(page.requests.map(item => item.url).sort(), ['au', 'su']);
});
test('overview distinguishes request results and never labels blocked requests as online', () => {
const page = fakePage(overviewScript);
const visitors = ['blocked', 'forwarded', 'responded', 'error', 'unknown'].map((outcome, i) => ({
ip: '198.51.100.' + (i + 1), last_time: '2026-10-26T11:59:00+08:00', domain: 'app.example.com',
outcome, status: [444, 403, 308, 502, null][i], recent: i === 0,
count: 6, blocked_count: i === 0 ? 0 : 1, forwarded_count: 2, other_count: 3
}));
take(page, 'au').callback(null, { visitors });
const html = page.element('mg-v-list').innerHTML;
for (const label of ['已拦截', '已转发', '已响应', '请求失败', '结果未知', 'HTTP 444', 'HTTP 403',
'无状态码', '近5分钟有请求', '5分钟内无新请求', '总计 6', '拦截 1 · 转发 2 · 其他 3', '拦截 0 · 转发 2 · 其他 3']) {
assert.ok(html.includes(label), label);
}
assert.doesNotMatch(html, /在线|离线|mg-dot/);
});
test('overview renders legacy visitor responses as unknown and escapes log content', () => {
const page = fakePage(overviewScript);
take(page, 'au').callback(null, { visitors: [{
ip: '198.51.100.1', domain: '<img src=x onerror=alert(1)>', count: 1,
last_time: '2026-10-26T11:59:00+08:00', online: true
}] });
const html = page.element('mg-v-list').innerHTML;
assert.match(html, /结果未知/);
assert.match(html, /请求时间未知/);
assert.match(html, /&lt;img/);
assert.doesNotMatch(html, /<img|已转发|在线|离线|拦截 undefined/);
});
test('login guard renders empty JSON objects as empty lists and uses POST for changes', () => {
const page = fakePage(loginGuardScript);
take(page, 'lu').callback(null, { enabled: '1', running: true, banned: {}, banned_total: 0, watching: {}, watching_total: 0, threshold: 3 });