Files
ghproxy/proxy/matcher_test.go
wjqserver 208ce8a4f9 4.2.5
2025-07-31 20:01:03 +08:00

334 lines
9.8 KiB
Go

package proxy
import (
"ghproxy/config"
"net/url"
"reflect"
"testing"
)
func TestMatcher_Compatibility(t *testing.T) {
// --- 准备各种配置用于测试 ---
cfgWithAuth := &config.Config{
Auth: config.AuthConfig{Enabled: true, Method: "header", ForceAllowApi: false},
}
cfgNoAuth := &config.Config{
Auth: config.AuthConfig{Enabled: false},
}
cfgApiForceAllowed := &config.Config{
Auth: config.AuthConfig{ForceAllowApi: true},
}
cfgWrongAuthMethod := &config.Config{
Auth: config.AuthConfig{Enabled: true, Method: "none"},
}
testCases := []struct {
name string
rawPath string
config *config.Config
expectedUser string
expectedRepo string
expectedMatcher string
expectError bool
expectedErrCode int
}{
{
name: "GH Releases Path 1",
rawPath: "https://github.com/owner/repo/releases/download/v1.0/asset.zip",
config: cfgWithAuth,
expectedUser: "owner", expectedRepo: "repo", expectedMatcher: "releases",
},
{
name: "GH Releases Path 2",
rawPath: "https://github.com/owner/repo/releases/v1.0/download/asset.zip",
config: cfgWithAuth,
expectedUser: "owner", expectedRepo: "repo", expectedMatcher: "releases",
},
{
name: "GH Releases Path Page",
rawPath: "https://github.com/owner/repo/releases",
config: cfgWithAuth,
expectError: true, expectedErrCode: 400,
},
{
name: "GH Releases Path Tag Page",
rawPath: "https://github.com/owner/repo/releases/tag/v0.0.1",
config: cfgWithAuth,
expectError: true, expectedErrCode: 400,
},
{
name: "GH Archive Path",
rawPath: "https://github.com/owner/repo.git/archive/main.zip",
config: cfgWithAuth,
expectedUser: "owner", expectedRepo: "repo.git", expectedMatcher: "releases",
},
{
name: "GH Blob Path",
rawPath: "https://github.com/owner/repo/blob/main/path/to/file.go",
config: cfgWithAuth,
expectedUser: "owner", expectedRepo: "repo", expectedMatcher: "blob",
},
{
name: "GH Raw Path",
rawPath: "https://github.com/owner/repo/raw/main/image.png",
config: cfgWithAuth,
expectedUser: "owner", expectedRepo: "repo", expectedMatcher: "raw",
},
{
name: "GH Clone Info Refs",
rawPath: "https://github.com/owner/repo.git/info/refs?service=git-upload-pack",
config: cfgWithAuth,
expectedUser: "owner", expectedRepo: "repo.git", expectedMatcher: "clone",
},
{
name: "GH Clone Git Upload Pack",
rawPath: "https://github.com/owner/repo/git-upload-pack",
config: cfgWithAuth,
expectedUser: "owner", expectedRepo: "repo", expectedMatcher: "clone",
},
{
name: "Girhub Broken Path",
rawPath: "https://github.com/owner",
config: cfgWithAuth,
expectError: true, expectedErrCode: 400,
},
{
name: "RawGHUserContent Path",
rawPath: "https://raw.githubusercontent.com/owner/repo/branch/file.sh",
config: cfgWithAuth,
expectedUser: "owner", expectedRepo: "repo", expectedMatcher: "raw",
},
{
name: "Gist Path",
rawPath: "https://gist.github.com/user/abcdef1234567890",
config: cfgWithAuth,
expectedUser: "user", expectedRepo: "", expectedMatcher: "gist",
},
{
name: "Gist UserContent Path",
rawPath: "https://gist.githubusercontent.com/user/abcdef1234567890",
config: cfgWithAuth,
expectedUser: "user", expectedRepo: "", expectedMatcher: "gist",
},
{
name: "API Repos Path (with Auth)",
rawPath: "https://api.github.com/repos/owner/repo/pulls",
config: cfgWithAuth,
expectedUser: "owner", expectedRepo: "repo", expectedMatcher: "api",
},
{
name: "API Users Path (with Auth)",
rawPath: "https://api.github.com/users/someuser/repos",
config: cfgWithAuth,
expectedUser: "someuser", expectedRepo: "", expectedMatcher: "api",
},
{
name: "API Other Path (with Auth)",
rawPath: "https://api.github.com/octocat",
config: cfgWithAuth,
expectedUser: "", expectedRepo: "", expectedMatcher: "api",
},
{
name: "API Path (Force Allowed)",
rawPath: "https://api.github.com/repos/owner/repo",
config: cfgApiForceAllowed, // Auth disabled, but force allowed
expectedUser: "owner", expectedRepo: "repo", expectedMatcher: "api",
},
{
name: "Malformed GH Path (no repo)",
rawPath: "https://github.com/owner/",
config: cfgWithAuth,
expectError: true, expectedErrCode: 400,
},
{
name: "Malformed GH Path (no action)",
rawPath: "https://github.com/owner/repo",
config: cfgWithAuth,
expectError: true, expectedErrCode: 400,
},
{
name: "Malformed GH Path (empty user)",
rawPath: "https://github.com//repo/blob/main/file.go",
config: cfgWithAuth,
expectError: true, expectedErrCode: 400,
},
{
name: "Malformed Raw Path (no repo)",
rawPath: "https://raw.githubusercontent.com/owner/",
config: cfgWithAuth,
expectError: true, expectedErrCode: 400,
},
{
name: "Malformed Gist Path (no user)",
rawPath: "https://gist.github.com/",
config: cfgWithAuth,
expectError: true, expectedErrCode: 400,
},
{
name: "Unsupported GH Action",
rawPath: "https://github.com/owner/repo/issues/123",
config: cfgWithAuth,
expectError: true, expectedErrCode: 400,
},
{
name: "API Path (No Auth)",
rawPath: "https://api.github.com/user",
config: cfgNoAuth,
expectError: true, expectedErrCode: 403,
},
{
name: "API Path (Wrong Auth Method)",
rawPath: "https://api.github.com/user",
config: cfgWrongAuthMethod,
expectError: true, expectedErrCode: 403,
},
{
name: "No Matcher Found (other domain)",
rawPath: "https://bitbucket.org/owner/repo",
config: cfgWithAuth,
expectError: true, expectedErrCode: 404,
},
{
name: "No Matcher Found (path too short)",
rawPath: "https://a.co",
config: cfgWithAuth,
expectError: true, expectedErrCode: 404,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
user, repo, matcher, ghErr := Matcher(tc.rawPath, tc.config)
if tc.expectError {
if ghErr == nil {
t.Fatalf("Expected a GHProxyErrors error, but got nil")
}
if ghErr.StatusCode != tc.expectedErrCode {
t.Errorf("Expected error code %d, but got %d (msg: %s)",
tc.expectedErrCode, ghErr.StatusCode, ghErr.ErrorMessage)
}
} else {
if ghErr != nil {
t.Fatalf("Expected no error, but got: %s", ghErr.ErrorMessage)
}
if user != tc.expectedUser {
t.Errorf("user: got %q, want %q", user, tc.expectedUser)
}
if repo != tc.expectedRepo {
t.Errorf("repo: got %q, want %q", repo, tc.expectedRepo)
}
if matcher != tc.expectedMatcher {
t.Errorf("matcher: got %q, want %q", matcher, tc.expectedMatcher)
}
}
})
}
}
func TestExtractParts_Compatibility(t *testing.T) {
testCases := []struct {
name string
rawURL string
expectedOwner string
expectedRepo string
expectedRem string
expectedQuery url.Values
expectError bool
}{
{
name: "Standard git clone URL",
rawURL: "https://github.com/WJQSERVER-STUDIO/go-utils.git/info/refs?service=git-upload-pack",
expectedOwner: "/WJQSERVER-STUDIO",
expectedRepo: "/go-utils.git",
expectedRem: "/info/refs",
expectedQuery: url.Values{"service": []string{"git-upload-pack"}},
},
{
name: "No remaining path",
rawURL: "https://example.com/owner/repo",
expectedOwner: "/owner",
expectedRepo: "/repo",
expectedRem: "",
expectedQuery: url.Values{},
},
{
name: "Root path only",
rawURL: "https://example.com/",
expectError: true, // Path is too short
},
{
name: "One level path",
rawURL: "https://example.com/owner",
expectError: true, // Path is too short
},
{
name: "Empty path segments",
rawURL: "https://example.com//repo/a", // Will be treated as /repo/a
expectedOwner: "", // First part is empty
expectedRepo: "/repo",
expectedRem: "/a",
},
{
name: "Invalid URL format",
rawURL: "://invalid",
expectError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
owner, repo, rem, query, err := extractParts(tc.rawURL)
if (err != nil) != tc.expectError {
t.Fatalf("extractParts() error = %v, expectError %v", err, tc.expectError)
}
if !tc.expectError {
if owner != tc.expectedOwner {
t.Errorf("owner: got %q, want %q", owner, tc.expectedOwner)
}
if repo != tc.expectedRepo {
t.Errorf("repo: got %q, want %q", repo, tc.expectedRepo)
}
if rem != tc.expectedRem {
t.Errorf("remaining path: got %q, want %q", rem, tc.expectedRem)
}
if !reflect.DeepEqual(query, tc.expectedQuery) {
t.Errorf("query: got %v, want %v", query, tc.expectedQuery)
}
}
})
}
}
func TestMatchString_Compatibility(t *testing.T) {
testCases := []struct {
target string
expected bool
}{
{"blob", true}, {"raw", true}, {"gist", true},
{"clone", false}, {"releases", false},
}
for _, tc := range testCases {
t.Run(tc.target, func(t *testing.T) {
if got := matchString(tc.target); got != tc.expected {
t.Errorf("matchString('%s') = %v; want %v", tc.target, got, tc.expected)
}
})
}
}
func BenchmarkMatcher(b *testing.B) {
cfg := &config.Config{}
path := "https://github.com/WJQSERVER/speedtest-ex/releases/download/v1.2.0/speedtest-linux-amd64.tar.gz"
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, _, _ = Matcher(path, cfg)
}
}