inbound_import_external_proxy_test.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. package service
  2. import (
  3. "testing"
  4. "github.com/mhsanaei/3x-ui/v3/internal/database"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  6. )
  7. // TestAddInbound_ImportConvertsExternalProxyToHosts reproduces the panel report:
  8. // an inbound exported from a build that predated the hosts table carries its
  9. // external proxies inline in streamSettings.externalProxy. The one-time startup
  10. // migration that converts those to host rows is gated off after first run, so a
  11. // freshly imported inbound used to land with zero hosts (its external proxies
  12. // silently lost). AddInbound must convert them on import.
  13. func TestAddInbound_ImportConvertsExternalProxyToHosts(t *testing.T) {
  14. setupConflictDB(t)
  15. svc := &InboundService{}
  16. stream := `{
  17. "network":"ws",
  18. "wsSettings":{"path":"/req3","host":"astr.khafanha.ir"},
  19. "security":"none",
  20. "externalProxy":[
  21. {"forceTls":"same","dest":"snapp.ir","port":8080,"remark":"","sni":"","alpn":[],"pinnedPeerCertSha256":[],"echConfigList":""},
  22. {"forceTls":"tls","dest":"cdn.example.com","port":8443,"remark":"front","sni":"sni.example.com","fingerprint":"chrome","alpn":["h2","h3"],"pinnedPeerCertSha256":["AAAA"],"echConfigList":"ECHV"}
  23. ]
  24. }`
  25. settings := `{"clients":[{"id":"6df5616b-ebfd-4186-86d5-4bce29fe8805","email":"imp_user","subId":"s-imp","enable":true}],"decryption":"none","encryption":"none"}`
  26. in := &model.Inbound{
  27. UserId: 1,
  28. Tag: "in-8080-tcp",
  29. Enable: true,
  30. Listen: "",
  31. Port: 8080,
  32. Protocol: model.VLESS,
  33. StreamSettings: stream,
  34. Settings: settings,
  35. }
  36. created, _, err := svc.AddInbound(in)
  37. if err != nil {
  38. t.Fatalf("import inbound: %v", err)
  39. }
  40. var hosts []model.Host
  41. if err := database.GetDB().Where("inbound_id = ?", created.Id).Order("sort_order asc").Find(&hosts).Error; err != nil {
  42. t.Fatalf("load hosts: %v", err)
  43. }
  44. if len(hosts) != 2 {
  45. t.Fatalf("hosts = %d, want 2 (one per externalProxy entry)", len(hosts))
  46. }
  47. a := hosts[0]
  48. if a.SortOrder != 0 || a.Security != "same" || a.Address != "snapp.ir" || a.Port != 8080 {
  49. t.Fatalf("host A mapping wrong: %+v", a)
  50. }
  51. if a.Remark == "" {
  52. t.Fatalf("host A remark must be backfilled for a blank externalProxy remark, got empty")
  53. }
  54. b := hosts[1]
  55. if b.SortOrder != 1 || b.Security != "tls" || b.Address != "cdn.example.com" || b.Port != 8443 ||
  56. b.Remark != "front" || b.Sni != "sni.example.com" || b.Fingerprint != "chrome" || b.EchConfigList != "ECHV" {
  57. t.Fatalf("host B mapping wrong: %+v", b)
  58. }
  59. if len(b.Alpn) != 2 || b.Alpn[0] != "h2" || b.Alpn[1] != "h3" {
  60. t.Fatalf("host B alpn = %v, want [h2 h3]", b.Alpn)
  61. }
  62. if len(b.PinnedPeerCertSha256) != 1 || b.PinnedPeerCertSha256[0] != "AAAA" {
  63. t.Fatalf("host B pins = %v, want [AAAA]", b.PinnedPeerCertSha256)
  64. }
  65. }
  66. // TestAddInbound_NoExternalProxyCreatesNoHosts guards the no-op path: an inbound
  67. // built by the current UI (no externalProxy) must not gain phantom host rows.
  68. func TestAddInbound_NoExternalProxyCreatesNoHosts(t *testing.T) {
  69. setupConflictDB(t)
  70. svc := &InboundService{}
  71. in := &model.Inbound{
  72. UserId: 1,
  73. Tag: "in-9201-tcp",
  74. Enable: true,
  75. Listen: "0.0.0.0",
  76. Port: 9201,
  77. Protocol: model.VLESS,
  78. StreamSettings: `{"network":"tcp","security":"none"}`,
  79. Settings: `{"clients":[{"id":"77777777-7777-7777-7777-777777777777","email":"plain","subId":"s-plain","enable":true}],"decryption":"none","encryption":"none"}`,
  80. }
  81. created, _, err := svc.AddInbound(in)
  82. if err != nil {
  83. t.Fatalf("add inbound: %v", err)
  84. }
  85. var count int64
  86. if err := database.GetDB().Model(&model.Host{}).Where("inbound_id = ?", created.Id).Count(&count).Error; err != nil {
  87. t.Fatalf("count hosts: %v", err)
  88. }
  89. if count != 0 {
  90. t.Fatalf("host count = %d, want 0", count)
  91. }
  92. }