| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- package discord
- import (
- "context"
- "encoding/json"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
- "github.com/mhsanaei/3x-ui/v3/internal/database/model"
- "github.com/mhsanaei/3x-ui/v3/internal/web/service"
- "github.com/mhsanaei/3x-ui/v3/internal/xray"
- )
- type mockServerProvider struct {
- status *service.Status
- dbData []byte
- dbErr error
- filename string
- }
- func (m *mockServerProvider) GetStatus(lastStatus *service.Status) *service.Status {
- return m.status
- }
- func (m *mockServerProvider) GetDb() ([]byte, error) {
- return m.dbData, m.dbErr
- }
- func (m *mockServerProvider) BackupFilename(requestHost string) string {
- if m.filename != "" {
- return m.filename
- }
- return "x-ui_test.db"
- }
- type mockInboundProvider struct {
- inbounds []*model.Inbound
- err error
- }
- func (m *mockInboundProvider) GetAllInbounds() ([]*model.Inbound, error) {
- return m.inbounds, m.err
- }
- func TestBuildReport_NoBackup(t *testing.T) {
- settingService := setupTestDB(t)
- _ = settingService.SetDiscordBotToken("test-bot-token")
- _ = settingService.SetDiscordChannelId("12345")
- _ = settingService.SetDiscordBotBackup(false)
- _ = settingService.SetDiscordRunTime("@daily")
- mockStatus := &service.Status{
- Uptime: 172800,
- Loads: []float64{0.5, 0.4, 0.3},
- TcpCount: 15,
- UdpCount: 5,
- }
- mockStatus.Xray.State = service.Running
- mockStatus.Xray.Version = "25.1.0"
- mockServer := &mockServerProvider{
- status: mockStatus,
- dbData: []byte("sqlite-backup-bytes"),
- }
- mockInbound := &mockInboundProvider{
- inbounds: []*model.Inbound{
- {
- Id: 1,
- Remark: "VLESS-TCP",
- Enable: true,
- Port: 443,
- ClientStats: []xray.ClientTraffic{
- {Email: "[email protected]", Enable: true, Up: 100, Down: 200},
- {Email: "[email protected]", Enable: false},
- },
- },
- },
- }
- svc := NewDiscordService(settingService)
- payload, files, err := svc.BuildReport(context.Background(), mockServer, mockInbound)
- if err != nil {
- t.Fatalf("BuildReport failed: %v", err)
- }
- if len(payload.Embeds) != 1 {
- t.Fatalf("expected 1 embed, got %d", len(payload.Embeds))
- }
- embed := payload.Embeds[0]
- if embed.Color != ColorBlue {
- t.Errorf("expected ColorBlue, got %X", embed.Color)
- }
- if len(files) != 0 {
- t.Errorf("expected 0 files when backup is disabled, got %d", len(files))
- }
- foundHost, foundUptime, foundXray := false, false, false
- for _, field := range embed.Fields {
- if field.Name == "Host" {
- foundHost = true
- }
- if field.Name == "Uptime" && field.Value == "2d 0h" {
- foundUptime = true
- }
- if field.Name == "Xray Core" && field.Value == "25.1.0 (running)" {
- foundXray = true
- }
- }
- if !foundHost || !foundUptime || !foundXray {
- t.Errorf("expected fields not found in embed: %+v", embed.Fields)
- }
- }
- func TestBuildReport_WithBackup(t *testing.T) {
- settingService := setupTestDB(t)
- _ = settingService.SetDiscordBotToken("test-bot-token")
- _ = settingService.SetDiscordChannelId("12345")
- _ = settingService.SetDiscordBotBackup(true)
- mockStatus := &service.Status{
- Uptime: 3600,
- }
- mockStatus.Xray.State = service.Running
- mockStatus.Xray.Version = "25.1.0"
- mockServer := &mockServerProvider{
- status: mockStatus,
- dbData: []byte("test-db-content"),
- filename: "x-ui_backup.db",
- }
- svc := NewDiscordService(settingService)
- _, files, err := svc.BuildReport(context.Background(), mockServer, nil)
- if err != nil {
- t.Fatalf("BuildReport failed: %v", err)
- }
- if len(files) == 0 {
- t.Fatal("expected at least 1 backup file, got 0")
- }
- if files[0].Filename != "x-ui_backup.db" {
- t.Errorf("expected filename 'x-ui_backup.db', got %q", files[0].Filename)
- }
- if string(files[0].Data) != "test-db-content" {
- t.Errorf("expected db content 'test-db-content', got %q", string(files[0].Data))
- }
- }
- func TestSendReport_Integration(t *testing.T) {
- settingService := setupTestDB(t)
- _ = settingService.SetDiscordBotToken("test-token")
- _ = settingService.SetDiscordChannelId("998877")
- _ = settingService.SetDiscordBotBackup(true)
- var receivedRequest bool
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- receivedRequest = true
- w.WriteHeader(http.StatusOK)
- _, _ = w.Write([]byte(`{"id": "msg-123"}`))
- }))
- defer server.Close()
- svc := NewDiscordService(settingService)
- svc.SetBaseURL(server.URL)
- svc.SetHTTPClient(server.Client())
- mockStatus := &service.Status{
- Uptime: 86400,
- }
- mockStatus.Xray.State = service.Running
- mockStatus.Xray.Version = "25.1.0"
- mockServer := &mockServerProvider{
- status: mockStatus,
- dbData: []byte("sqlite-data"),
- }
- err := svc.SendReport(context.Background(), mockServer, nil)
- if err != nil {
- t.Fatalf("SendReport failed: %v", err)
- }
- if !receivedRequest {
- t.Error("expected server to receive report request")
- }
- }
- func TestSendReport_DeliversEmbedWhenBackupUploadIsRejected(t *testing.T) {
- settingService := setupTestDB(t)
- _ = settingService.SetDiscordBotToken("test-token")
- _ = settingService.SetDiscordChannelId("998877")
- _ = settingService.SetDiscordBotBackup(true)
- embeds := make(chan int, 4)
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/") {
- w.WriteHeader(http.StatusRequestEntityTooLarge)
- _, _ = w.Write([]byte(`{"message": "Request entity too large", "code": 40005}`))
- return
- }
- var p MessagePayload
- _ = json.NewDecoder(r.Body).Decode(&p)
- embeds <- len(p.Embeds)
- w.WriteHeader(http.StatusOK)
- }))
- defer server.Close()
- svc := NewDiscordService(settingService)
- svc.SetBaseURL(server.URL)
- svc.SetHTTPClient(server.Client())
- mockServer := &mockServerProvider{
- status: &service.Status{Uptime: 86400},
- dbData: []byte("sqlite-data-over-the-upload-cap"),
- }
- err := svc.SendReport(context.Background(), mockServer, nil)
- if err == nil || !strings.Contains(err.Error(), "(413)") {
- t.Fatalf("SendReport error = %v, want the rejected backup upload (413)", err)
- }
- select {
- case n := <-embeds:
- if n != 1 {
- t.Fatalf("report message carried %d embeds, want 1", n)
- }
- default:
- t.Fatal("report embed was never delivered: it rode on the rejected backup upload")
- }
- }
|