mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-16 08:10:58 +00:00
338822ab07
Constrain the copy-only subscription page to the dynamic viewport, wrap long localized text, and infer text direction so mobile browsers cannot render a horizontally shifted desktop-width page. Add regression coverage for the responsive layout contract.
156 lines
4.8 KiB
Go
156 lines
4.8 KiB
Go
package sub
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
|
"golang.org/x/text/language"
|
|
)
|
|
|
|
func TestIsBrowserSubscriptionRequest(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
tests := []struct {
|
|
name string
|
|
accept string
|
|
ua string
|
|
dest string
|
|
mode string
|
|
query string
|
|
want bool
|
|
}{
|
|
{name: "explicit html query is not implicit navigation", query: "?html=1", want: false},
|
|
{name: "html accept", accept: "text/html,application/xhtml+xml", want: true},
|
|
{name: "browser navigation with wildcard accept", accept: "*/*", ua: "Mozilla/5.0 Safari/605.1.15", dest: "document", mode: "navigate", want: true},
|
|
{name: "browser ua fallback", accept: "*/*", ua: "Mozilla/5.0 Chrome/126.0.0.0", want: true},
|
|
{name: "vpn client wildcard", accept: "*/*", ua: "Incy/3.3.0", want: false},
|
|
{name: "vpn client with mozilla token", accept: "*/*", ua: "Mozilla/5.0 Incy/3.3.0", want: false},
|
|
{name: "plain client", accept: "*/*", ua: "Go-http-client/2.0", want: false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
req := httptest.NewRequest(http.MethodGet, "/sub/abc"+tt.query, nil)
|
|
if tt.accept != "" {
|
|
req.Header.Set("Accept", tt.accept)
|
|
}
|
|
if tt.ua != "" {
|
|
req.Header.Set("User-Agent", tt.ua)
|
|
}
|
|
if tt.dest != "" {
|
|
req.Header.Set("Sec-Fetch-Dest", tt.dest)
|
|
}
|
|
if tt.mode != "" {
|
|
req.Header.Set("Sec-Fetch-Mode", tt.mode)
|
|
}
|
|
c.Request = req
|
|
|
|
if got := (&SUBController{}).isBrowserSubscriptionRequest(c); got != tt.want {
|
|
t.Fatalf("isBrowserSubscriptionRequest() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBrowserClassificationHonorsConfiguredFormatMatchers(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
new func() *SUBController
|
|
}{
|
|
{"clash", func() *SUBController {
|
|
return &SUBController{subClashAutoDetect: true, clashEnabled: true, clashUserAgent: regexp.MustCompile(`Custom-Client`)}
|
|
}},
|
|
{"json", func() *SUBController {
|
|
return &SUBController{jsonAutoDetect: true, jsonEnabled: true, jsonUserAgent: regexp.MustCompile(`Custom-Client`)}
|
|
}},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
|
c.Request = httptest.NewRequest(http.MethodGet, "/sub/abc", nil)
|
|
c.Request.Header.Set("User-Agent", "Mozilla/5.0 Custom-Client/1.0")
|
|
if tc.new().isBrowserSubscriptionRequest(c) {
|
|
t.Fatal("configured subscription client was classified as a browser")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSubscriptionCopyPageUsesRequestLocale(t *testing.T) {
|
|
bundle := i18n.NewBundle(language.English)
|
|
for id, text := range map[string]string{
|
|
"subCopyPageTitle": "Titre localisé",
|
|
"subCopyPageHeading": "En-tête localisé",
|
|
"subCopyPageInstructions": "Instructions localisées",
|
|
} {
|
|
bundle.AddMessages(language.French, &i18n.Message{ID: id, Other: text})
|
|
}
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = httptest.NewRequest(http.MethodGet, "/sub/abc", nil)
|
|
c.Request.Header.Set("Accept-Language", "fr-FR")
|
|
c.Set("localizer", i18n.NewLocalizer(bundle, "fr-FR"))
|
|
|
|
(&SUBController{}).serveSubscriptionCopyPage(c)
|
|
if body := w.Body.String(); !strings.Contains(body, `<html lang="fr-FR">`) ||
|
|
!strings.Contains(body, "Titre localisé") || !strings.Contains(body, "Instructions localisées") {
|
|
t.Fatalf("copy page was not localized from the request: %s", body)
|
|
}
|
|
}
|
|
|
|
func TestSubscriptionCopyPageIsMobileSafe(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = httptest.NewRequest(http.MethodGet, "/sub/abc", nil)
|
|
|
|
(&SUBController{}).serveSubscriptionCopyPage(c)
|
|
body := w.Body.String()
|
|
for _, required := range []string{
|
|
`<meta name="viewport" content="width=device-width, initial-scale=1">`,
|
|
`* { box-sizing: border-box; }`,
|
|
`min-height: 100dvh`,
|
|
`overflow-x: hidden`,
|
|
`overflow-wrap: anywhere`,
|
|
`@media (max-width: 480px)`,
|
|
`<main dir="auto">`,
|
|
} {
|
|
if !strings.Contains(body, required) {
|
|
t.Fatalf("copy page is missing mobile layout constraint %q", required)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExplicitSubPageRequest(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
tests := []struct {
|
|
name string
|
|
query string
|
|
want bool
|
|
}{
|
|
{name: "html=1", query: "?html=1", want: true},
|
|
{name: "view=html", query: "?view=HTML", want: true},
|
|
{name: "no query", query: "", want: false},
|
|
{name: "unrelated query", query: "?format=info", want: false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = httptest.NewRequest(http.MethodGet, "/sub/abc"+tt.query, nil)
|
|
|
|
if got := explicitSubPageRequest(c); got != tt.want {
|
|
t.Fatalf("explicitSubPageRequest() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|