feat: add direct message testing to XMPP doctor command

- Add reusable MessageBody and XMPPMessage structs to xmpp client
- Refactor SendMessage to use shared structs instead of inline definitions
- Add SendDirectMessage method for direct user messaging (type="chat")
- Enhance doctor command with --test-dm flag (enabled by default)
- Add testDirectMessage function that sends test message to admin@localhost
- Update help text, examples, and timing measurements for direct messages

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Felipe M 2025-08-01 16:58:02 +02:00
parent 5551a8bc8d
commit 5d143808a3
No known key found for this signature in database
GPG key ID: 52E5D65FCF99808A
4 changed files with 1110 additions and 24 deletions

View file

@ -27,6 +27,7 @@ type Config struct {
Resource string
TestRoom string
TestMUC bool
TestDirectMessage bool
Verbose bool
InsecureSkipVerify bool
}
@ -41,20 +42,22 @@ func main() {
flag.StringVar(&config.Resource, "resource", defaultResource, "XMPP resource")
flag.StringVar(&config.TestRoom, "test-room", defaultTestRoom, "MUC room JID for testing")
flag.BoolVar(&config.TestMUC, "test-muc", true, "Enable MUC room testing (join/wait/leave)")
flag.BoolVar(&config.TestDirectMessage, "test-dm", true, "Enable direct message testing (send message to admin user)")
flag.BoolVar(&config.Verbose, "verbose", true, "Enable verbose logging")
flag.BoolVar(&config.InsecureSkipVerify, "insecure-skip-verify", true, "Skip TLS certificate verification (for development)")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "xmpp-client-doctor - Test XMPP client connectivity and MUC operations\n\n")
fmt.Fprintf(os.Stderr, "This tool tests the XMPP client implementation by connecting to an XMPP server,\n")
fmt.Fprintf(os.Stderr, "performing connection tests, optionally testing MUC room operations,\n")
fmt.Fprintf(os.Stderr, "performing connection tests, optionally testing MUC room operations and direct messages,\n")
fmt.Fprintf(os.Stderr, "and then disconnecting gracefully.\n\n")
fmt.Fprintf(os.Stderr, "Usage:\n")
fmt.Fprintf(os.Stderr, " %s [flags]\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Examples:\n")
fmt.Fprintf(os.Stderr, " %s # Test basic connectivity\n", os.Args[0])
fmt.Fprintf(os.Stderr, " %s --test-muc # Test connectivity and MUC operations\n", os.Args[0])
fmt.Fprintf(os.Stderr, " %s --test-muc=false # Test connectivity only\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, " %s --test-dm # Test connectivity and direct messages\n", os.Args[0])
fmt.Fprintf(os.Stderr, " %s --test-muc=false --test-dm=false # Test connectivity only\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Flags:\n")
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\nDefault values are configured for the development server in ./sidecar/\n")
@ -75,6 +78,9 @@ func main() {
if config.TestMUC {
log.Printf(" Test Room: %s", config.TestRoom)
}
if config.TestDirectMessage {
log.Printf(" Test Direct Messages: enabled")
}
}
// Test the XMPP client
@ -89,6 +95,9 @@ func main() {
if config.TestMUC {
fmt.Println("✅ XMPP MUC operations test passed!")
}
if config.TestDirectMessage {
fmt.Println("✅ XMPP direct message test passed!")
}
}
}
@ -154,6 +163,7 @@ func testXMPPClient(config *Config) error {
}
var mucDuration time.Duration
var dmDuration time.Duration
// Test MUC operations if requested
if config.TestMUC {
@ -165,6 +175,16 @@ func testXMPPClient(config *Config) error {
mucDuration = time.Since(start)
}
// Test direct message if requested
if config.TestDirectMessage {
start = time.Now()
err = testDirectMessage(client, config)
if err != nil {
return fmt.Errorf("direct message test failed: %w", err)
}
dmDuration = time.Since(start)
}
if config.Verbose {
log.Printf("Disconnecting from XMPP server...")
}
@ -185,11 +205,17 @@ func testXMPPClient(config *Config) error {
if config.TestMUC {
log.Printf(" MUC operations time: %v", mucDuration)
}
if config.TestDirectMessage {
log.Printf(" Direct message time: %v", dmDuration)
}
log.Printf(" Disconnect time: %v", disconnectDuration)
totalTime := connectDuration + pingDuration + disconnectDuration
if config.TestMUC {
totalTime += mucDuration
}
if config.TestDirectMessage {
totalTime += dmDuration
}
log.Printf(" Total time: %v", totalTime)
}
@ -265,6 +291,34 @@ func testMUCOperations(client *xmpp.Client, config *Config) error {
return nil
}
func testDirectMessage(client *xmpp.Client, config *Config) error {
if config.Verbose {
log.Printf("Testing direct message functionality...")
log.Printf("Sending test message to admin user...")
}
// Send a test message to the admin user
testMessage := fmt.Sprintf("Test direct message from XMPP doctor at %s", time.Now().Format("15:04:05"))
adminJID := "admin@localhost" // Default admin user for development server
start := time.Now()
err := client.SendDirectMessage(adminJID, testMessage)
if err != nil {
return fmt.Errorf("failed to send direct message to %s: %w", adminJID, err)
}
sendDuration := time.Since(start)
if config.Verbose {
log.Printf("✅ Successfully sent direct message in %v", sendDuration)
log.Printf("Message: %s", testMessage)
log.Printf("Recipient: %s", adminJID)
log.Printf("Direct message test summary:")
log.Printf(" Send message time: %v", sendDuration)
}
return nil
}
func maskPassword(password string) string {
if len(password) <= 2 {
return "****"

173
go.mod
View file

@ -3,7 +3,6 @@ module github.com/mattermost/mattermost-plugin-bridge-xmpp
go 1.24.3
require (
github.com/golang/mock v1.6.0
github.com/gorilla/mux v1.8.1
github.com/mattermost/mattermost/server/public v0.1.10
github.com/pkg/errors v0.9.1
@ -13,53 +12,225 @@ require (
)
require (
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
4d63.com/gochecknoglobals v0.2.1 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/4meepo/tagalign v1.3.4 // indirect
github.com/Abirdcfly/dupword v0.1.1 // indirect
github.com/Antonboom/errname v0.1.13 // indirect
github.com/Antonboom/nilnil v0.1.9 // indirect
github.com/Antonboom/testifylint v1.4.3 // indirect
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
github.com/Crocmagnon/fatcontext v0.5.2 // indirect
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect
github.com/alecthomas/go-check-sumtype v0.1.4 // indirect
github.com/alexkohler/nakedret/v2 v2.0.4 // indirect
github.com/alexkohler/prealloc v1.0.0 // indirect
github.com/alingse/asasalint v0.0.11 // indirect
github.com/ashanbrown/forbidigo v1.6.0 // indirect
github.com/ashanbrown/makezero v1.1.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bkielbasa/cyclop v1.2.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/blizzy78/varnamelen v0.8.0 // indirect
github.com/bombsimon/wsl/v4 v4.4.1 // indirect
github.com/breml/bidichk v0.2.7 // indirect
github.com/breml/errchkjson v0.3.6 // indirect
github.com/butuzov/ireturn v0.3.0 // indirect
github.com/butuzov/mirror v1.2.0 // indirect
github.com/catenacyber/perfsprint v0.7.1 // indirect
github.com/ccojocar/zxcvbn-go v1.0.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charithe/durationcheck v0.0.10 // indirect
github.com/chavacava/garif v0.1.0 // indirect
github.com/ckaznocha/intrange v0.2.0 // indirect
github.com/curioswitch/go-reassign v0.2.0 // indirect
github.com/daixiang0/gci v0.13.5 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/denis-tingaikin/go-header v0.5.0 // indirect
github.com/dnephin/pflag v1.0.7 // indirect
github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a // indirect
github.com/ettle/strcase v0.2.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/firefart/nonamedreturns v1.0.5 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
github.com/ghostiam/protogetter v0.3.6 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
github.com/go-critic/go-critic v0.11.4 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/go-toolsmith/astcast v1.1.0 // indirect
github.com/go-toolsmith/astcopy v1.1.0 // indirect
github.com/go-toolsmith/astequal v1.2.0 // indirect
github.com/go-toolsmith/astfmt v1.1.0 // indirect
github.com/go-toolsmith/astp v1.1.0 // indirect
github.com/go-toolsmith/strparse v1.1.0 // indirect
github.com/go-toolsmith/typep v1.1.0 // indirect
github.com/go-viper/mapstructure/v2 v2.1.0 // indirect
github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect
github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 // indirect
github.com/golangci/golangci-lint v1.61.0 // indirect
github.com/golangci/misspell v0.6.0 // indirect
github.com/golangci/modinfo v0.3.4 // indirect
github.com/golangci/plugin-module-register v0.1.1 // indirect
github.com/golangci/revgrep v0.5.3 // indirect
github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gordonklaus/ineffassign v0.1.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.4.2 // indirect
github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.6.3 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jgautheron/goconst v1.7.1 // indirect
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect
github.com/jjti/go-spancheck v0.6.2 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/julz/importas v0.1.0 // indirect
github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect
github.com/kisielk/errcheck v1.7.0 // indirect
github.com/kkHAIKE/contextcheck v1.1.5 // indirect
github.com/kulti/thelper v0.6.3 // indirect
github.com/kunwardeep/paralleltest v1.0.10 // indirect
github.com/kyoh86/exportloopref v0.1.11 // indirect
github.com/lasiar/canonicalheader v1.1.1 // indirect
github.com/ldez/gomoddirectives v0.2.4 // indirect
github.com/ldez/tagliatelle v0.5.0 // indirect
github.com/leonklingele/grouper v1.1.2 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/lufeee/execinquery v1.2.1 // indirect
github.com/macabu/inamedparam v0.1.3 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/maratori/testableexamples v1.0.0 // indirect
github.com/maratori/testpackage v1.1.1 // indirect
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect
github.com/mattermost/ldap v0.0.0-20231116144001-0f480c025956 // indirect
github.com/mattermost/logr/v2 v2.0.21 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mgechev/revive v1.3.9 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moricho/tparallel v0.3.2 // indirect
github.com/nakabonne/nestif v0.3.1 // indirect
github.com/nishanths/exhaustive v0.12.0 // indirect
github.com/nishanths/predeclared v0.2.2 // indirect
github.com/nunnatsa/ginkgolinter v0.16.2 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pborman/uuid v1.2.1 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/polyfloyd/go-errorlint v1.6.0 // indirect
github.com/prometheus/client_golang v1.12.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect
github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect
github.com/quasilyte/gogrep v0.5.0 // indirect
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
github.com/ryancurrah/gomodguard v1.3.5 // indirect
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect
github.com/securego/gosec/v2 v2.21.2 // indirect
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sivchari/containedctx v1.0.3 // indirect
github.com/sivchari/tenv v1.10.0 // indirect
github.com/sonatard/noctx v0.0.2 // indirect
github.com/sourcegraph/go-diff v0.7.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.12.0 // indirect
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/tdakkota/asciicheck v0.2.0 // indirect
github.com/tetafro/godot v1.4.17 // indirect
github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect
github.com/timonwong/loggercheck v0.9.4 // indirect
github.com/tinylib/msgp v1.2.5 // indirect
github.com/tomarrell/wrapcheck/v2 v2.9.0 // indirect
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
github.com/ultraware/funlen v0.1.0 // indirect
github.com/ultraware/whitespace v0.1.1 // indirect
github.com/uudashr/gocognit v1.1.3 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/wiggin77/merror v1.0.5 // indirect
github.com/wiggin77/srslog v1.0.1 // indirect
github.com/xen0n/gosmopolitan v1.2.2 // indirect
github.com/yagipy/maintidx v1.0.0 // indirect
github.com/yeya24/promlinter v0.3.0 // indirect
github.com/ykadowak/zerologlint v0.1.5 // indirect
gitlab.com/bosi/decorder v0.4.2 // indirect
go-simpler.org/musttag v0.12.2 // indirect
go-simpler.org/sloglint v0.7.2 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/automaxprocs v1.5.3 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.29.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47 // indirect
google.golang.org/grpc v1.70.0 // indirect
google.golang.org/protobuf v1.36.4 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/gotestsum v1.7.0 // indirect
honnef.co/go/tools v0.5.1 // indirect
mellium.im/reader v0.1.0 // indirect
mellium.im/xmlstream v0.15.4 // indirect
mvdan.cc/gofumpt v0.7.0 // indirect
mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect
)
tool (
github.com/golangci/golangci-lint/cmd/golangci-lint
gotest.tools/gotestsum
)

837
go.sum

File diff suppressed because it is too large Load diff

View file

@ -52,6 +52,21 @@ type SendMessageResponse struct {
StanzaID string `json:"stanza_id"`
}
// MessageBody represents the body element of an XMPP message
type MessageBody struct {
XMLName xml.Name `xml:"body"`
Text string `xml:",chardata"`
}
// XMPPMessage represents a complete XMPP message stanza
type XMPPMessage struct {
XMLName xml.Name `xml:"jabber:client message"`
Type string `xml:"type,attr"`
To string `xml:"to,attr"`
From string `xml:"from,attr"`
Body MessageBody `xml:"body"`
}
// GhostUser represents an XMPP ghost user
type GhostUser struct {
JID string `json:"jid"`
@ -324,26 +339,12 @@ func (c *Client) SendMessage(req MessageRequest) (*SendMessageResponse, error) {
sendCtx, cancel := context.WithTimeout(c.ctx, 10*time.Second)
defer cancel()
// Create the message body structure
type messageBody struct {
XMLName xml.Name `xml:"body"`
Text string `xml:",chardata"`
}
// Create complete message with body
type message struct {
XMLName xml.Name `xml:"jabber:client message"`
Type string `xml:"type,attr"`
To string `xml:"to,attr"`
From string `xml:"from,attr"`
Body messageBody `xml:"body"`
}
fullMsg := message{
fullMsg := XMPPMessage{
Type: "groupchat",
To: to.String(),
From: c.jidAddr.String(),
Body: messageBody{Text: req.Message},
Body: MessageBody{Text: req.Message},
}
// Send the message using the session encoder
@ -359,6 +360,39 @@ func (c *Client) SendMessage(req MessageRequest) (*SendMessageResponse, error) {
return response, nil
}
// SendDirectMessage sends a direct message to a specific user
func (c *Client) SendDirectMessage(userJID, message string) error {
if c.session == nil {
if err := c.Connect(); err != nil {
return err
}
}
to, err := jid.Parse(userJID)
if err != nil {
return fmt.Errorf("failed to parse user JID: %w", err)
}
// Create a context with timeout for the send operation
sendCtx, cancel := context.WithTimeout(c.ctx, 10*time.Second)
defer cancel()
// Create direct message using reusable structs
msg := XMPPMessage{
Type: "chat",
To: to.String(),
From: c.jidAddr.String(),
Body: MessageBody{Text: message},
}
// Send the message using the session encoder
if err := c.session.Encode(sendCtx, msg); err != nil {
return fmt.Errorf("failed to send direct message: %w", err)
}
return nil
}
// ResolveRoomAlias resolves a room alias to room JID
func (c *Client) ResolveRoomAlias(roomAlias string) (string, error) {
// For XMPP, return the alias as-is if it's already a valid JID