import { describe, expect, it } from 'bun:test' import { generatedGroupName, isGeneratedGroup, lineFilter, makeFilterGroup, pinnedLineOf, pruneGeneratedGroups, } from './filter-groups.ts' import type { ConfigObject } from '../vendor/config-editor/index.ts' const LINE = '[vless]Gomami.SIN Secret' describe('筛选条件', () => { it('转义正则元字符', () => { // 线路名里的 [ ] . 不转义会当成正则语法,匹配到别的线路上去 expect(lineFilter(LINE)).toBe('^\\[vless\\]Gomami\\.SIN Secret$') }) it('前后加锚点,避免匹配到名字更长的线路', () => { // 内核的筛选是部分匹配:不加锚点时「香港01」会把「香港011」也捞进来 expect(lineFilter('香港01')).toBe('^香港01$') }) it('线路名含反引号时表达不了,返回空让调用方说明原因', () => { // 反引号在内核里是多个筛选条件的分隔符,塞进一条正则会被切成两半 expect(lineFilter('怪`名字')).toBeNull() }) it('转义后的正则确实只匹配它自己', () => { const re = new RegExp(lineFilter(LINE)!) expect(re.test(LINE)).toBe(true) expect(re.test('[vless]GomamiXSIN Secret')).toBe(false) expect(re.test(`${LINE} 备用`)).toBe(false) }) }) describe('生成的组', () => { it('名字带前缀,这是识别它的唯一可靠标记', () => { // 注释靠不住——被碰到的段落重写时会丢,见决策记录 0002 const name = generatedGroupName(LINE) expect(isGeneratedGroup(name)).toBe(true) expect(pinnedLineOf(name)).toBe(LINE) }) it('用户自己建的组不会被认成生成的', () => { expect(isGeneratedGroup('PROXY')).toBe(false) expect(pinnedLineOf('PROXY')).toBeNull() }) it('组里只有那一条线路,类型是手动选', () => { expect(makeFilterGroup(LINE, 'subs')).toEqual({ name: `patchbay:${LINE}`, type: 'select', use: ['subs'], filter: '^\\[vless\\]Gomami\\.SIN Secret$', }) }) it('表达不了的线路名不生成组', () => { expect(makeFilterGroup('怪`名字', 'subs')).toBeNull() }) }) describe('清理没人用的生成组', () => { const generated = (line: string) => ({ name: generatedGroupName(line), type: 'select' }) it('入站还指着它就留着', () => { const data: ConfigObject = { listeners: [{ name: 'A', type: 'mixed', port: 1, proxy: generatedGroupName(LINE) }], 'proxy-groups': [generated(LINE)], } expect(pruneGeneratedGroups(data)['proxy-groups']).toHaveLength(1) }) it('没人再指它就删掉', () => { const data: ConfigObject = { listeners: [{ name: 'A', type: 'mixed', port: 1 }], 'proxy-groups': [generated(LINE)], } // 不清理的话,改几次出站文件里就攒下一堆用户从没主动建过的组 expect(pruneGeneratedGroups(data)['proxy-groups']).toBeUndefined() }) it('被别的策略组当成员引用也算在用', () => { const data: ConfigObject = { 'proxy-groups': [generated(LINE), { name: 'PROXY', proxies: [generatedGroupName(LINE)] }], } expect(pruneGeneratedGroups(data)['proxy-groups']).toHaveLength(2) }) it('被规则引用也算在用', () => { const data: ConfigObject = { 'proxy-groups': [generated(LINE)], rules: [`MATCH,${generatedGroupName(LINE)}`], } expect(pruneGeneratedGroups(data)['proxy-groups']).toHaveLength(1) }) it('用户自己建的组一律不碰,哪怕真的没人引用', () => { const data: ConfigObject = { 'proxy-groups': [{ name: '我建的组', type: 'select' }] } expect(pruneGeneratedGroups(data)['proxy-groups']).toHaveLength(1) }) it('没有可删的时候原样返回,不制造无谓的改动', () => { // 返回同一个对象,落盘链路才看得出「内容没变」而跳过备份和重载 const data: ConfigObject = { 'proxy-groups': [{ name: 'PROXY' }] } expect(pruneGeneratedGroups(data)).toBe(data) }) })