profile_mode_test.go 3.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. package sub
  2. import (
  3. "net/http"
  4. "net/http/httptest"
  5. "strings"
  6. "testing"
  7. "github.com/gin-gonic/gin"
  8. "github.com/mhsanaei/3x-ui/v3/internal/database"
  9. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  10. )
  11. func TestSubscriptionProfileModesFromSavedSettings(t *testing.T) {
  12. oldFS, oldMode := distFS, gin.Mode()
  13. oldWriter, oldErrorWriter := gin.DefaultWriter, gin.DefaultErrorWriter
  14. SetDistFS(testDistFS)
  15. t.Cleanup(func() {
  16. SetDistFS(oldFS)
  17. gin.SetMode(oldMode)
  18. gin.DefaultWriter, gin.DefaultErrorWriter = oldWriter, oldErrorWriter
  19. })
  20. for _, config := range []struct {
  21. name, mode, profileURL, want string
  22. }{
  23. {name: "new installation"},
  24. {name: "legacy whitespace", profileURL: " "},
  25. {name: "legacy custom", profileURL: "https://portal.example/account", want: "https://portal.example/account"},
  26. {name: "none retains custom", mode: "none", profileURL: "https://portal.example/account"},
  27. {name: "builtin", mode: "builtin", profileURL: "https://portal.example/account"},
  28. {name: "custom", mode: "custom", profileURL: "https://portal.example/?sub={{SUB_ID}}", want: "https://portal.example/?sub=profile-sub"},
  29. {name: "empty custom", mode: "custom"},
  30. {name: "invalid mode", mode: "invalid", profileURL: "https://portal.example/account"},
  31. } {
  32. t.Run(config.name, func(t *testing.T) {
  33. initSubDB(t)
  34. seedInfoEndpointSub(t, "profile-sub", "[email protected]")
  35. settings := []model.Setting{
  36. {Key: "subPath", Value: "/sub/"},
  37. {Key: "subJsonPath", Value: "/json/"},
  38. {Key: "subClashPath", Value: "/clash/"},
  39. {Key: "subJsonEnable", Value: "true"},
  40. {Key: "subClashEnable", Value: "true"},
  41. {Key: "subProfileUrl", Value: config.profileURL},
  42. }
  43. if config.mode != "" {
  44. settings = append(settings, model.Setting{Key: "subProfileMode", Value: config.mode})
  45. }
  46. for _, setting := range settings {
  47. if err := database.GetDB().Where("key = ?", setting.Key).Delete(&model.Setting{}).Error; err != nil {
  48. t.Fatal(err)
  49. }
  50. if err := database.GetDB().Create(&setting).Error; err != nil {
  51. t.Fatal(err)
  52. }
  53. }
  54. router, err := (&Server{}).initRouter()
  55. if err != nil {
  56. t.Fatal(err)
  57. }
  58. for _, path := range []string{"/sub/profile-sub", "/json/profile-sub", "/clash/profile-sub", "/json/profile-sub?view=raw", "/clash/profile-sub?view=raw", "/mihomo/profile-sub"} {
  59. for _, userAgent := range []string{"Happ/3.22.0 (Android)", "v2rayNG/1.8.5"} {
  60. t.Run(path+"/"+userAgent, func(t *testing.T) {
  61. req := httptest.NewRequest(http.MethodGet, "https://sub.example.com:8443"+path, nil)
  62. req.Header.Set("User-Agent", userAgent)
  63. resp := httptest.NewRecorder()
  64. router.ServeHTTP(resp, req)
  65. if resp.Code != http.StatusOK {
  66. t.Fatalf("status = %d; body=%s", resp.Code, resp.Body.String())
  67. }
  68. want := config.want
  69. if config.mode == "builtin" {
  70. want = "https://sub.example.com:8443" + req.URL.EscapedPath() + "?html=1"
  71. }
  72. if got := resp.Header().Get("Profile-Web-Page-Url"); got != want {
  73. t.Fatalf("Profile-Web-Page-Url = %q, want %q", got, want)
  74. }
  75. if want == "" {
  76. if _, present := resp.Header()["Profile-Web-Page-Url"]; present {
  77. t.Fatal("disabled profile header must be absent")
  78. }
  79. }
  80. if config.mode == "builtin" {
  81. // The restored link must open the page, even when copied from a raw download.
  82. page := httptest.NewRecorder()
  83. router.ServeHTTP(page, httptest.NewRequest(http.MethodGet, want, nil))
  84. if page.Code != http.StatusOK || !strings.Contains(page.Header().Get("Content-Type"), "text/html") {
  85. t.Fatalf("builtin link did not serve HTML: status=%d, type=%q", page.Code, page.Header().Get("Content-Type"))
  86. }
  87. }
  88. })
  89. }
  90. }
  91. })
  92. }
  93. }