Skip to content

Commit ac1f3d0

Browse files
ci: Start implementing integration tests (xmpp)
1 parent 0e08db9 commit ac1f3d0

File tree

16 files changed

+894
-3
lines changed

16 files changed

+894
-3
lines changed

.github/workflows/ci.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Integration tests
2+
on: [push, pull_request]
3+
jobs:
4+
integration:
5+
name: Integration tests
6+
runs-on: ubuntu-latest
7+
steps:
8+
- uses: actions/checkout@v5
9+
- uses: actions/setup-go@v6
10+
with:
11+
go-version: stable
12+
# Install prosody's latest release from upstream
13+
- run: sudo wget https://prosody.im/downloads/repos/$(lsb_release -sc)/prosody.sources -O/etc/apt/sources.list.d/prosody.sources
14+
- run: sudo apt update
15+
- run: sudo apt install -y mercurial lua5.4
16+
- run: sudo update-alternatives --set lua-interpreter /usr/bin/lua5.4
17+
- run: sudo apt install -y prosody
18+
# Setup prosody community modules
19+
- run: hg clone https://hg.prosody.im/prosody-modules/ prosody-modules
20+
# Copy mod_auth_any to global prosody modules
21+
- run: sudo cp -R prosody-modules/mod_auth_any /usr/lib/prosody/modules/
22+
# Only one test is run for now
23+
- run: ./tests/test.sh xmpp outgoing-message
24+
# Upload logs when it failed
25+
- run: cat tests/xmpp/setup.log
26+
if: ${{ failure() }}
27+
- run: cat tests/xmpp/matterbridge.log
28+
if: ${{ failure() }}
29+
- run: cat tests/xmpp/xmpp.log
30+
if: ${{ failure() }}
31+
- run: cat tests/xmpp/api.log
32+
if: ${{ failure() }}

bridge/xmpp/xmpp.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ func (b *Bxmpp) JoinChannel(channel config.ChannelInfo) error {
6262
b.Log.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name)
6363
b.xc.JoinProtectedMUC(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick"), channel.Options.Key, xmpp.NoHistory, 0, nil)
6464
} else {
65-
b.xc.JoinMUCNoHistory(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick"))
65+
// b.xc.JoinMUCNoHistory(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick"))
66+
// TODO: this creates the room if it doesn't exist yet
67+
// should it be the default?
68+
_, _ = b.xc.JoinOrCreateMUCNoHistoryDoNotUseOutsideTests(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick"))
6669
}
6770
return nil
6871
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,5 @@ require (
148148
//replace github.com/matrix-org/gomatrix => github.com/matterbridge/gomatrix v0.0.0-20220205235239-607eb9ee6419
149149

150150
go 1.24.0
151+
152+
replace github.com/xmppo/go-xmpp => github.com/selfhoster1312/go-xmpp v0.0.0-20251025082217-1038b4c7a092

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,8 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g
312312
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
313313
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
314314
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
315+
github.com/selfhoster1312/go-xmpp v0.0.0-20251025082217-1038b4c7a092 h1:F7s+IBSL0UmaKRN4eEqVp/HUYN/t74NaCMkwh9meaRI=
316+
github.com/selfhoster1312/go-xmpp v0.0.0-20251025082217-1038b4c7a092/go.mod h1:hLa9WAf0VRpSVguhme06Df+7mIQ6enCEG8udUNYcqX4=
315317
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
316318
github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4 h1:zwQ1HBo5FYwn1ksMd19qBCKO8JAWE9wmHivEpkw/DvE=
317319
github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4/go.mod h1:vt2jWY/3Qw1bIzle5thrJWucsLuuX9iUNnp20CqCciI=
@@ -408,8 +410,6 @@ github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6Fk
408410
github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE=
409411
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
410412
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
411-
github.com/xmppo/go-xmpp v0.2.18 h1:MSTzKqYsAFxPCXkha36qxn09oXQ8Scnsk7OqgoiIVto=
412-
github.com/xmppo/go-xmpp v0.2.18/go.mod h1:hLa9WAf0VRpSVguhme06Df+7mIQ6enCEG8udUNYcqX4=
413413
github.com/yaegashi/msgraph.go v0.1.4 h1:leDXSczAbwBpYFSmmZrdByTiPoUw8dbTfNMetAjJvbw=
414414
github.com/yaegashi/msgraph.go v0.1.4/go.mod h1:vgeYhHa5skJt/3lTyjGXThTZhwbhRnGo6uUxzoJIGME=
415415
github.com/yaegashi/wtz.go v0.0.2/go.mod h1:nOLA5QXsmdkRxBkP5tljhua13ADHCKirLBrzPf4PEJc=

tests/README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Integration tests
2+
3+
This directory is for integration tests only. Unit tests reside in their
4+
respective modules.
5+
6+
## Motivation
7+
8+
Unit testing only checks that assumptions within the code are held. Integration
9+
tests can provide real-life testing capabilities to ensure that a feature is
10+
implemented properly, even when going through the remote chat service.
11+
12+
## Methodology
13+
14+
Each matterbrige supported protocol has its own corresponding folder in this
15+
`tests` folder. Each protocol's integration's tests:
16+
17+
- are run in a specific CI job with a specific environment
18+
- are documented to be run locally with a specific environment
19+
- provides implementation for incoming tests, and outgoing tests to avoid
20+
feature mismatch (for example, a bridge that could receive attachments
21+
from other networks but not the other way around)
22+
- are run sequentially, although different protocols are tested in parallel
23+
24+
> [!NOTE]
25+
> `incoming` refers to a message received on that protocol (to be delivered
26+
> to others via matterbridge), while `outgoing` refers to a message received
27+
> from another matterbridge protocol to be delivered to this protocol.
28+
29+
## Architecture
30+
31+
At the moment, integration tests require 3 components to play together:
32+
33+
- a real matterbridge instance configured with a protocol account (`pa`) to a protocol room (`pr`)
34+
- another protocol client (`pc`), connected to the same protocol room (`pr`), following a test scenario
35+
- a matterbridge API client (`ac`), connected client following the same test scenario (called `ac`)
36+
37+
> [!NOTE]
38+
> For each tested protocol, the test suite requires two accounts on the remote server.
39+
40+
The `matterbridge.toml` in each integration test folder determines the config
41+
used for the matterbridge daemon used in the tests.
42+
43+
The API client is located in `tests/api/api.go` and accepts as arguments:
44+
45+
- one of the supported tests (eg. `incoming-message`)
46+
- a timeout in seconds, after which it will automatically fail (default: 5)
47+
48+
The protocol client is located in `tests/PROTOCOL/PROTOCOL.go`, is a proper
49+
go module that can be run with `go run .`, and accepts as arguments:
50+
51+
- one of the supported tests (eg. `incoming-message`)
52+
- a timeout in seconds, after which it will automatically fail (default: 5)
53+
54+
Both binaries:
55+
56+
- run in the background, with the same set of CLI arguments
57+
- fail the entire pipeline if they individually fail
58+
- success as soon as the testing criteria is met
59+
60+
For example, in the case of the `incoming-message` test, the protocol client
61+
will successfully exit as soon as the message is sent without errors,
62+
but the API client will wait and either:
63+
64+
- successfully exit when receiving the `test-incoming-message`
65+
- fail after the timeout was reached
66+
67+
## Supported tests
68+
69+
The following test scenarios are supported at the moment:
70+
71+
- `incoming-message` steps:
72+
- `pc` sends `test-incoming-message` in `pr` using the native protocol
73+
- `ac` receives `<pc> test-incoming-message` in `pr` using the matterbridge API
74+
- `outgoing-message` steps:
75+
- `ac` sends `test-outgoing-message` in `pr` using the matterbridge API
76+
- `pc` receives `<ac> test-outgoing-message` in `pr` using the native protocol

tests/api/api.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"errors"
7+
"flag"
8+
"fmt"
9+
"net/http"
10+
"io"
11+
"os"
12+
"strconv"
13+
"time"
14+
)
15+
16+
type Api struct {
17+
BaseUrl string
18+
}
19+
20+
func NewApi() *Api {
21+
s := Api{ BaseUrl: "http://localhost:4242/api" }
22+
return &s
23+
}
24+
25+
func (a *Api) SendMessage(message *Message) error {
26+
endpoint := fmt.Sprintf("%s/message", a.BaseUrl)
27+
json_msg, err := json.Marshal(message)
28+
if err != nil {
29+
fmt.Printf("Failed to serialize message:\n%s", err)
30+
return err
31+
}
32+
33+
req, err := http.NewRequest("POST", endpoint, bytes.NewReader(json_msg))
34+
// req.Header.Set("X-Custom-Header", "myvalue")
35+
req.Header.Set("Content-Type", "application/json")
36+
37+
client := &http.Client{}
38+
resp, err := client.Do(req)
39+
if err != nil {
40+
return err
41+
}
42+
defer resp.Body.Close()
43+
44+
fmt.Println("response Status:", resp.Status)
45+
fmt.Println("response Headers:", resp.Header)
46+
body, _ := io.ReadAll(resp.Body)
47+
fmt.Println("response Body:", string(body))
48+
49+
if resp.StatusCode != 200 {
50+
return errors.New("Failed to POST message to the API")
51+
}
52+
53+
return nil
54+
}
55+
56+
type Message struct {
57+
Text string `json:"text"`
58+
Channel string `json:"channel"`
59+
Username string `json:"username"`
60+
UserId string `json:"userid"`
61+
Avatar string `json:"avatar"`
62+
Account string `json:"account"`
63+
Event string `json:"event"`
64+
Protocol string `json:"protocol"`
65+
Gateway string `json:"gateway"`
66+
ParentId string `json:"parent_id"`
67+
Timestamp string `json:"timestamp"`
68+
Id string `json:"id"`
69+
Extra map[string][]interface {} `json:"Extra"`
70+
}
71+
72+
func newMessage(text string) *Message {
73+
s:= Message {
74+
Text: text,
75+
Channel: "testchannel",
76+
Username: "apitest",
77+
UserId: "apitest_id",
78+
Account: "api.test",
79+
Protocol: "api",
80+
Gateway: "test",
81+
Timestamp: "2019-01-09T22:53:51.618575236+01:00",
82+
}
83+
84+
return &s
85+
}
86+
87+
func scenario_outgoing_message(api *Api) error {
88+
err := api.SendMessage(newMessage("outgoing-message-test"))
89+
if err != nil {
90+
return err
91+
}
92+
93+
return nil
94+
}
95+
96+
func apply_scenario(scenario string, api *Api) error {
97+
switch scenario {
98+
case "outgoing-message":
99+
return scenario_outgoing_message(api)
100+
default:
101+
fmt.Printf("ERROR: Unknown test scenario: %s\n", scenario)
102+
os.Exit(1)
103+
}
104+
105+
return nil
106+
}
107+
108+
func apply_scenario_timeout(scenario string, timeout int, api *Api) error {
109+
timeoutChan := make(chan error, 1)
110+
111+
go func() {
112+
timeoutChan <- apply_scenario(scenario, api)
113+
}()
114+
115+
select {
116+
case err := <-timeoutChan:
117+
if err != nil {
118+
return fmt.Errorf("ERROR: Scenario %s failed because of error:\n%s", scenario, err)
119+
}
120+
121+
fmt.Printf("OK: Scenario %s", scenario)
122+
case <-time.After(time.Duration(timeout) * time.Second):
123+
return fmt.Errorf("ERROR: Scenario %s timeout after %d seconds", scenario, timeout)
124+
}
125+
126+
return nil
127+
}
128+
129+
func main() {
130+
flag.Parse()
131+
args := flag.Args()
132+
scenario := args[0]
133+
var timeout int
134+
if len(args) < 2 {
135+
timeout = 5
136+
} else {
137+
timeout2, err := strconv.Atoi(args[1])
138+
if err != nil {
139+
fmt.Printf("ERROR: Invalid timeout: %s\n", args[1])
140+
os.Exit(1)
141+
}
142+
timeout = timeout2
143+
}
144+
145+
fmt.Println("Initializing API")
146+
api := NewApi()
147+
148+
fmt.Printf("Running scenario %s (timeout=%ds)\n", scenario, timeout)
149+
150+
err := apply_scenario_timeout(scenario, timeout, api)
151+
if err != nil {
152+
fmt.Println(err)
153+
os.Exit(1)
154+
}
155+
}

tests/api/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/selfhoster1312/matterbridge-tests/api
2+
3+
go 1.25.1

0 commit comments

Comments
 (0)