Files
zeekay 8feb24ea1f One name for this fork, and it is ours
The server module named github.com/hanzoai/team/server/v8 — a path that
resolves to the retired workspace's repository, not this one — while
server/public still published github.com/mattermost/mattermost/server/public.
Both are github.com/hanzoteam/server/... now, which is where this code
actually lives.

That second half is what made the binary unbootable. Upstream's public path
stayed claimed by upstream, mattermost-plugin-ai imported it, and the build
linked public/model twice; public/plugin's gob.Register then ran twice under
one name and panicked at init, before main. Reverting the rename traded the
brand for a working binary. Rebranding the plugin alongside it needs no such
trade: the agents fork is github.com/hanzoteam/agents/v2 against our public
module, so no module named mattermost/mattermost is in the graph at all.

Measured: `go list -deps ./cmd/mattermost` names exactly one public/model, and
it is ours.
2026-08-28 23:35:36 -07:00

210 lines
5.2 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package remotecluster
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/wiggin77/merror"
"github.com/hanzoteam/server/server/public/model"
)
const (
TestTopics = " share incident "
TestTopic = "share"
NumRemotes = 50
NoteContent = "Woot!!"
)
type testPayload struct {
Note string `json:"note"`
}
func TestBroadcastMsg(t *testing.T) {
msgId := model.NewId()
t.Run("No error", func(t *testing.T) {
var countCallbacks atomic.Int32
var countWebReq atomic.Int32
merr := merror.New()
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
w.WriteHeader(200)
var resp Response
b, errMarshall := json.Marshal(&resp)
if errMarshall != nil {
merr.Append(errMarshall)
return
}
w.Write(b)
}()
countWebReq.Add(1)
var frame model.RemoteClusterFrame
jsonErr := json.NewDecoder(r.Body).Decode(&frame)
if jsonErr != nil {
merr.Append(jsonErr)
return
}
if len(frame.Msg.Payload) == 0 {
merr.Append(fmt.Errorf("webrequest missing Msg.Payload"))
}
if msgId != frame.Msg.Id {
merr.Append(fmt.Errorf("webrequest msgId expected %s, got %s", msgId, frame.Msg.Id))
return
}
note := testPayload{}
err := json.Unmarshal(frame.Msg.Payload, &note)
if err != nil {
merr.Append(err)
return
}
if note.Note != NoteContent {
merr.Append(fmt.Errorf("webrequest payload expected %s, got %s", NoteContent, note.Note))
return
}
}))
defer ts.Close()
mockServer := newMockServer(t, makeRemoteClusters(NumRemotes, ts.URL, false))
mockApp := newMockApp(t, nil)
service, err := NewRemoteClusterService(mockServer, mockApp)
require.NoError(t, err)
service.disablePing = true
err = service.Start()
require.NoError(t, err)
defer service.Shutdown()
wg := &sync.WaitGroup{}
wg.Add(NumRemotes)
msg := makeRemoteClusterMsg(msgId, NoteContent)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
defer cancel()
err = service.BroadcastMsg(ctx, msg, func(msg model.RemoteClusterMsg, remote *model.RemoteCluster, resp *Response, err error) {
defer wg.Done()
countCallbacks.Add(1)
if err != nil {
merr.Append(err)
}
if msgId != msg.Id {
merr.Append(fmt.Errorf("result callback msgId expected %s, got %s", msgId, msg.Id))
}
var note testPayload
err2 := json.Unmarshal(msg.Payload, &note)
if err2 != nil {
merr.Append(fmt.Errorf("unmarshal payload error: %w", err2))
return
}
if note.Note != NoteContent {
merr.Append(fmt.Errorf("compare payload failed: expected '%s', got '%s'", NoteContent, note))
}
})
assert.NoError(t, err)
wg.Wait()
assert.NoError(t, merr.ErrorOrNil())
assert.Equal(t, int32(NumRemotes), countCallbacks.Load())
assert.Equal(t, int32(NumRemotes), countWebReq.Load())
t.Logf("%d callbacks counted; %d web requests counted; %d expected",
countCallbacks.Load(), countWebReq.Load(), NumRemotes)
})
t.Run("HTTP error", func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
}))
defer ts.Close()
mockServer := newMockServer(t, makeRemoteClusters(NumRemotes, ts.URL, false))
mockApp := newMockApp(t, nil)
service, err := NewRemoteClusterService(mockServer, mockApp)
require.NoError(t, err)
service.disablePing = true
err = service.Start()
require.NoError(t, err)
defer service.Shutdown()
msg := makeRemoteClusterMsg(msgId, NoteContent)
var countCallbacks atomic.Int32
var countErrors atomic.Int32
wg := &sync.WaitGroup{}
wg.Add(NumRemotes)
err = service.BroadcastMsg(context.Background(), msg, func(msg model.RemoteClusterMsg, remote *model.RemoteCluster, resp *Response, err error) {
defer wg.Done()
countCallbacks.Add(1)
if err != nil {
countErrors.Add(1)
}
})
assert.NoError(t, err)
wg.Wait()
assert.Equal(t, int32(NumRemotes), countCallbacks.Load())
assert.Equal(t, int32(NumRemotes), countErrors.Load())
})
}
func makeRemoteClusters(num int, siteURL string, isPlugin bool) []*model.RemoteCluster {
var remotes []*model.RemoteCluster
for i := range num {
rc := makeRemoteCluster(fmt.Sprintf("test cluster %d", i+1), siteURL, TestTopics)
if isPlugin {
rc.PluginID = model.NewId()
}
remotes = append(remotes, rc)
}
return remotes
}
func makeRemoteCluster(name string, siteURL string, topics string) *model.RemoteCluster {
return &model.RemoteCluster{
RemoteId: model.NewId(),
Name: name,
SiteURL: siteURL,
Token: model.NewId(),
Topics: topics,
CreateAt: model.GetMillis(),
LastPingAt: model.GetMillis(),
CreatorId: model.NewId(),
}
}
func makeRemoteClusterMsg(id string, note string) model.RemoteClusterMsg {
payload := testPayload{Note: note}
raw, _ := json.Marshal(payload)
return model.RemoteClusterMsg{
Id: id,
Topic: TestTopic,
CreateAt: model.GetMillis(),
Payload: raw}
}