package main
import (
"fmt"
"github.com/bmatcuk/doublestar/v4"
)
// ============================================================
// 测试框架与用例定义
// ============================================================
// testCase 单个测试用例
type testCase struct {
name string
pattern string
path string
expected bool
}
// testGroup 一组测试
type testGroup struct {
title string
cases []testCase
}
// runTests 运行一组测试,输出结果并统计
func runTests(groups []testGroup) (passed, failed int) {
for _, g := range groups {
fmt.Printf("\n=== %s ===\n", g.title)
for i, c := range g.cases {
matched, err := doublestar.PathMatch(c.pattern, c.path)
status := "PASS"
if err != nil {
status = fmt.Sprintf("ERROR: %v", err)
} else if matched != c.expected {
status = "FAIL"
}
fmt.Printf(" [%02d] %-25s pattern=%-28s path=%-32s expected=%-5v got=%-5v → %s\n",
i+1, c.name, c.pattern, c.path, c.expected, matched, status)
if status != "PASS" {
failed++
} else {
passed++
}
}
}
return
}
// ============================================================
// main —— 定义测试用例并执行
// ============================================================
func main() {
fmt.Println("============================================================")
fmt.Println(" URL Path Matching Tests (doublestar.PathMatch only)")
fmt.Println("============================================================")
groups := []testGroup{
// 1. 基础单级通配 *
{
title: "1. 基础单级通配符 *",
cases: []testCase{
{"API 路由单级", "/api/v1/users/*", "/api/v1/users/123", true},
{"不跨多级目录", "/api/v1/users/*", "/api/v1/users/123/details", false},
{"静态文件后缀", "/static/*.css", "/static/style.css", true},
{"子目录不匹配", "/static/*.css", "/static/css/style.css", false},
{"根一级", "/*", "/hello", true},
{"根一级不跨级", "/*", "/a/b", false},
{"空模式不匹配", "", "/api", false},
},
},
// 2. 递归通配 **
{
title: "2. 递归通配符 **",
cases: []testCase{
{"任意深度 1", "/api/**", "/api/v1/users", true},
{"任意深度 2", "/api/**", "/api/v1/x/y/z", true},
{"包含自身", "/api/**", "/api", true},
{"中间插入", "/a/**/z", "/a/b/c/z", true},
{"中间跳过", "/a/**/z", "/a/z", true},
{"后缀过滤", "/api/**/*.json", "/api/v1/data/users.json", true},
{"后缀不匹配", "/api/**/*.json", "/api/v1/data/users.xml", false},
},
},
// 3. 单字符通配 ?
{
title: "3. 单字符通配符 ?",
cases: []testCase{
{"版本一位", "/api/v?/users", "/api/v1/users", true},
{"版本一位 2", "/api/v?/users", "/api/v2/users", true},
{"版本两位不匹配", "/api/v?/users", "/api/v12/users", false},
{"文件名单字符", "/img/logo?.png", "/img/logo1.png", true},
{"文件名多字符", "/img/logo?.png", "/img/logo10.png", false},
},
},
// 4. 字符类 []
{
title: "4. 字符类 [abc] [a-z] [0-9]",
cases: []testCase{
{"字符集合匹配", "/api/v[123]/users", "/api/v1/users", true},
{"字符集合不匹配", "/api/v[123]/users", "/api/v4/users", false},
{"数字范围", "/api/v[0-9]/users", "/api/v5/users", true},
{"字母范围", "/[a-z]bc", "/abc", true},
{"字母大小写敏感", "/[a-z]bc", "/Abc", false},
},
},
// 5. 交替匹配 {}
{
title: "5. 交替匹配 {a,b,c}",
cases: []testCase{
{"API 版本交替", "/api/{v1,v2}/users", "/api/v1/users", true},
{"API 版本交替 2", "/api/{v1,v2}/users", "/api/v2/users", true},
{"API 版本交替不匹配", "/api/{v1,v2}/users", "/api/v3/users", false},
{"扩展名交替", "/file.{json,yaml,yml}", "/file.json", true},
{"扩展名交替 2", "/file.{json,yaml,yml}", "/file.yaml", true},
{"扩展名不匹配", "/file.{json,yaml,yml}", "/file.xml", false},
},
},
// 6. 组合模式
{
title: "6. 组合模式 (**, *, {}, [] 混合)",
cases: []testCase{
{"版本 + 递归 + 单级", "/api/v1/**/*", "/api/v1/users/123/details", true},
{"静态资源白名单", "/static/**/*.{css,js,png,jpg}", "/static/css/main.css", true},
{"静态资源白名单 2", "/static/**/*.{css,js,png,jpg}", "/static/img/logo.png", true},
{"静态资源不匹配", "/static/**/*.{css,js,png,jpg}", "/static/data.xml", false},
{"版本范围 + 递归 + 后缀", "/api/v[0-9]/**/*.json", "/api/v2/users/list.json", true},
{"前缀交替 + 递归", "/{api,internal}/**", "/api/v1/users", true},
{"前缀交替 + 递归 2", "/{api,internal}/**", "/internal/config", true},
},
},
// 7. 精确匹配
{
title: "7. 精确匹配(无通配符)",
cases: []testCase{
{"完全相等", "/api/v1/users", "/api/v1/users", true},
{"不相等", "/api/v1/users", "/api/v2/users", false},
{"根路径", "/", "/", true},
{"末尾斜杠差异 1", "/api", "/api/", false},
{"末尾斜杠差异 2", "/api/", "/api", false},
},
},
// 8. 真实业务场景
{
title: "8. 真实业务场景(REST 路由)",
cases: []testCase{
{"REST 用户详情", "/users/*", "/users/123", true},
{"REST 用户订单", "/users/*/orders", "/users/123/orders", true},
{"REST 用户订单不匹配", "/users/*/orders", "/users/123", false},
{"管理后台", "/admin/**", "/admin/users/list", true},
{"健康检查端点", "/healthz", "/healthz", true},
{"健康检查子路径", "/health/**", "/health/live", true},
{"静态资源", "/assets/**", "/assets/js/app.js", true},
{"静态资源(自身匹配)", "/assets/**", "/assets", true},
},
},
}
// 运行所有测试
passed, failed := runTests(groups)
// 汇总
fmt.Println("\n============================================================")
fmt.Printf(" 汇总: passed=%d, failed=%d\n", passed, failed)
if failed == 0 {
fmt.Println(" ✓ 所有测试通过!")
} else {
fmt.Printf(" ✗ 有 %d 个测试失败!\n", failed)
}
fmt.Println("============================================================")
// 典型使用模式参考
fmt.Println("\n 典型使用模式参考:")
fmt.Println(" /api/** 匹配 /api 下所有子路径")
fmt.Println(" /static/**/*.{css,js,png,jpg} 匹配特定静态资源")
fmt.Println(" /users/** 匹配 /users 下所有子路径")
fmt.Println(" /api/v[0-9]/** 匹配版本化 API")
fmt.Println(" /healthz 精确匹配健康检查端点")
}