external_only_sub_test.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. package sub
  2. import (
  3. "encoding/json"
  4. "strings"
  5. "testing"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  8. )
  9. // A subscription whose only entries are external links — no enabled standard
  10. // inbound — must still render in the JSON and Clash formats, not just the raw
  11. // one. Regression guard for the premature len(inbounds)==0 early return that
  12. // short-circuited GetJson/GetClash before external links were ever fetched.
  13. func TestJsonAndClashServeExternalLinkOnlySub(t *testing.T) {
  14. initSubDB(t)
  15. db := database.GetDB()
  16. rec := &model.ClientRecord{Email: "ext@x", SubID: "ext-only", UUID: "ext-uuid", Enable: true}
  17. if err := db.Create(rec).Error; err != nil {
  18. t.Fatalf("seed client: %v", err)
  19. }
  20. link := "vless://[email protected]:443?type=tcp&security=reality&pbk=abc&sid=12&fp=chrome#orig"
  21. if err := db.Create(&model.ClientExternalLink{ClientId: rec.Id, Kind: model.ExternalLinkKindLink, Value: link, Remark: "DE-Provider", SortIndex: 1}).Error; err != nil {
  22. t.Fatalf("seed external link: %v", err)
  23. }
  24. base := NewSubService("")
  25. jsonService := NewSubJsonService("", "", "", base)
  26. jsonOut, _, err := jsonService.GetJson("ext-only", "sub.example.com", false)
  27. if err != nil {
  28. t.Fatalf("GetJson err = %v", err)
  29. }
  30. if jsonOut == "" {
  31. t.Fatal("GetJson returned empty for an external-link-only sub")
  32. }
  33. if !strings.Contains(jsonOut, "DE-Provider") {
  34. t.Fatalf("GetJson missing external remark: %s", jsonOut)
  35. }
  36. var config map[string]any
  37. if err := json.Unmarshal([]byte(jsonOut), &config); err != nil {
  38. t.Fatalf("legacy GetJson must return an object for a single profile: %v; body=%s", err, jsonOut)
  39. }
  40. standardOut, _, err := jsonService.GetJson("ext-only", "sub.example.com", true)
  41. if err != nil {
  42. t.Fatalf("standards-compliant GetJson err = %v", err)
  43. }
  44. var configs []map[string]any
  45. if err := json.Unmarshal([]byte(standardOut), &configs); err != nil {
  46. t.Fatalf("standards-compliant GetJson must return an array for a single profile: %v; body=%s", err, standardOut)
  47. }
  48. if len(configs) != 1 {
  49. t.Fatalf("standards-compliant GetJson profile count = %d, want 1", len(configs))
  50. }
  51. clashOut, _, err := NewSubClashService(false, "", base).GetClash("ext-only", "sub.example.com")
  52. if err != nil {
  53. t.Fatalf("GetClash err = %v", err)
  54. }
  55. if clashOut == "" {
  56. t.Fatal("GetClash returned empty for an external-link-only sub")
  57. }
  58. if !strings.Contains(clashOut, "DE-Provider") {
  59. t.Fatalf("GetClash missing external proxy: %s", clashOut)
  60. }
  61. }