tgbot_test.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. package tgbot
  2. import (
  3. "io"
  4. "net"
  5. "reflect"
  6. "testing"
  7. "time"
  8. )
  9. func TestLoginAttemptDoesNotCarryPassword(t *testing.T) {
  10. typ := reflect.TypeFor[LoginAttempt]()
  11. if _, ok := typ.FieldByName("Password"); ok {
  12. t.Fatal("LoginAttempt must not carry attempted passwords")
  13. }
  14. }
  15. func TestIsSupportedBotProxyScheme(t *testing.T) {
  16. supported := []string{
  17. "socks5://127.0.0.1:1080",
  18. "http://127.0.0.1:8080",
  19. "https://127.0.0.1:8080",
  20. }
  21. for _, p := range supported {
  22. if !isSupportedBotProxyScheme(p) {
  23. t.Errorf("expected %q to be supported", p)
  24. }
  25. }
  26. unsupported := []string{"", "ftp://x", "127.0.0.1:1080", "socks4://1.2.3.4:1080"}
  27. for _, p := range unsupported {
  28. if isSupportedBotProxyScheme(p) {
  29. t.Errorf("expected %q to be unsupported", p)
  30. }
  31. }
  32. }
  33. func recordingDialTarget(t *testing.T, n int) (addr string, got chan []byte) {
  34. t.Helper()
  35. ln, err := net.Listen("tcp", "127.0.0.1:0")
  36. if err != nil {
  37. t.Fatal(err)
  38. }
  39. got = make(chan []byte, 1)
  40. t.Cleanup(func() { _ = ln.Close() })
  41. go func() {
  42. conn, err := ln.Accept()
  43. if err != nil {
  44. return
  45. }
  46. defer conn.Close()
  47. _ = conn.SetReadDeadline(time.Now().Add(2 * time.Second))
  48. buf := make([]byte, n)
  49. m, _ := io.ReadFull(conn, buf)
  50. got <- buf[:m]
  51. }()
  52. return ln.Addr().String(), got
  53. }
  54. func TestTgbotProxyDialerSelectsHTTPForHTTPScheme(t *testing.T) {
  55. addr, got := recordingDialTarget(t, len("CONNECT "))
  56. tg := &Tgbot{}
  57. client := tg.createRobustFastHTTPClient("http://"+addr, nil)
  58. if client.Dial == nil {
  59. t.Fatal("Dial must be set for an http:// proxy")
  60. }
  61. go func() { _, _ = client.Dial("example.com:443") }()
  62. select {
  63. case b := <-got:
  64. if string(b) != "CONNECT " {
  65. t.Fatalf("expected HTTP CONNECT to the proxy, got %q", b)
  66. }
  67. case <-time.After(3 * time.Second):
  68. t.Fatal("proxy never received a connection")
  69. }
  70. }
  71. func TestTgbotProxyDialerSelectsSOCKSForSocks5Scheme(t *testing.T) {
  72. addr, got := recordingDialTarget(t, 1)
  73. tg := &Tgbot{}
  74. client := tg.createRobustFastHTTPClient("socks5://"+addr, nil)
  75. if client.Dial == nil {
  76. t.Fatal("Dial must be set for a socks5:// proxy")
  77. }
  78. go func() { _, _ = client.Dial("example.com:443") }()
  79. select {
  80. case b := <-got:
  81. if len(b) != 1 || b[0] != 0x05 {
  82. t.Fatalf("expected SOCKS5 greeting (0x05), got %v", b)
  83. }
  84. case <-time.After(3 * time.Second):
  85. t.Fatal("proxy never received a connection")
  86. }
  87. }
  88. func TestTgbotPanelEgressBridgeAppearingAfterStartIsUsed(t *testing.T) {
  89. addr, got := recordingDialTarget(t, 1)
  90. bridge := ""
  91. tg := &Tgbot{}
  92. client := tg.createRobustFastHTTPClient("", func() string { return bridge })
  93. bridge = "socks5://" + addr
  94. go func() { _, _ = client.Dial("example.com:443") }()
  95. select {
  96. case b := <-got:
  97. if len(b) != 1 || b[0] != 0x05 {
  98. t.Fatalf("expected SOCKS5 greeting (0x05) on the late bridge, got %v", b)
  99. }
  100. case <-time.After(3 * time.Second):
  101. t.Fatal("bridge that came up after bot start never received a connection")
  102. }
  103. }
  104. func TestTgbotPanelEgressDialsDirectWithoutBridge(t *testing.T) {
  105. addr, got := recordingDialTarget(t, 1)
  106. tg := &Tgbot{}
  107. client := tg.createRobustFastHTTPClient("", func() string { return "" })
  108. conn, err := client.Dial(addr)
  109. if err != nil {
  110. t.Fatalf("direct dial: %v", err)
  111. }
  112. defer conn.Close()
  113. if _, err := conn.Write([]byte{0x42}); err != nil {
  114. t.Fatalf("write: %v", err)
  115. }
  116. select {
  117. case b := <-got:
  118. if len(b) != 1 || b[0] != 0x42 {
  119. t.Fatalf("expected the payload byte on a direct connection, got %v", b)
  120. }
  121. case <-time.After(3 * time.Second):
  122. t.Fatal("target never received the direct connection")
  123. }
  124. }
  125. func TestIsCommandForBotAllowsUntargetedCommand(t *testing.T) {
  126. if !isCommandForBot("/status", "panel_bot") {
  127. t.Fatal("untargeted commands must remain accepted")
  128. }
  129. }
  130. func TestIsCommandForBotAllowsMatchingUsername(t *testing.T) {
  131. if !isCommandForBot("/status@panel_bot", "Panel_Bot") {
  132. t.Fatal("commands targeted to this bot must be accepted")
  133. }
  134. }
  135. func TestIsCommandForBotRejectsOtherUsername(t *testing.T) {
  136. if isCommandForBot("/status@other_bot", "panel_bot") {
  137. t.Fatal("commands targeted to another bot must be ignored")
  138. }
  139. }
  140. func TestIsCommandForBotKeepsLegacyBehaviorWhenUsernameUnavailable(t *testing.T) {
  141. if !isCommandForBot("/status@panel_bot", "") {
  142. t.Fatal("commands must remain accepted when the current bot username is unavailable")
  143. }
  144. }