snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 00:29:55 +05:00
commit 240db4e2a7
15309 changed files with 2173406 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved.
// See License for license information.
package bot
import (
"github.com/pkg/errors"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/pluginapi"
)
type Bot interface {
Ensure(stored *model.Bot, iconPath string) error
MattermostUserID() string
String() string
}
type bot struct {
botService pluginapi.BotService
mattermostUserID string
displayName string
}
func New(botService pluginapi.BotService) Bot {
newBot := &bot{
botService: botService,
}
return newBot
}
func (bot *bot) Ensure(stored *model.Bot, iconPath string) error {
if bot.mattermostUserID != "" {
// Already done
return nil
}
botUserID, err := bot.botService.EnsureBot(stored, pluginapi.ProfileImagePath(iconPath))
if err != nil {
return errors.Wrap(err, "failed to ensure bot account")
}
bot.mattermostUserID = botUserID
bot.displayName = stored.DisplayName
return nil
}
func (bot *bot) MattermostUserID() string {
return bot.mattermostUserID
}
func (bot *bot) String() string {
return bot.displayName
}

View File

@@ -0,0 +1,97 @@
package admincclogger
import (
"fmt"
"github.com/mattermost/mattermost/server/public/pluginapi/experimental/bot/logger"
"github.com/mattermost/mattermost/server/public/pluginapi/experimental/bot/poster"
"github.com/mattermost/mattermost/server/public/pluginapi/experimental/common"
)
type adminCCLogger struct {
logger.Logger
dmer poster.DMer
logLevel logger.LogLevel
includeContext bool
userIDs []string
}
/*
New promotes the provided logger into a admin cc logger, sending direct messages to all the admin
ids provided through the dmer provided, about all events below the logLevel. If logVerbose is set,
it will also send the context.
- l Logger: A logger to promote.
- dmer DMer: A DMer to send the messages to the admins.
- logLevel: The highest type of message to be stored in telemetry.
- includeContext: Whether the log context should be messaged to the admins.
- userIDs: The user IDs of the admins.
*/
func New(l logger.Logger, dmer poster.DMer, logLevel logger.LogLevel, includeContext bool, userIDs ...string) logger.Logger {
return &adminCCLogger{
Logger: l,
dmer: dmer,
logLevel: logLevel,
includeContext: includeContext,
userIDs: userIDs,
}
}
// NewFromAPI creates a adminCCLogger directly from a LogAPI instead of passing a logger.
func NewFromAPI(api common.LogAPI, dmer poster.DMer, logLevel logger.LogLevel, includeContext bool, userIDs ...string) logger.Logger {
return New(logger.New(api), dmer, logLevel, includeContext, userIDs...)
}
func (l *adminCCLogger) Debugf(format string, args ...any) {
l.Logger.Debugf(format, args...)
message := fmt.Sprintf(format, args...)
if logger.Level(l.logLevel) >= 4 {
l.logToAdmins("DEBUG", message)
}
}
func (l *adminCCLogger) Errorf(format string, args ...any) {
l.Logger.Errorf(format, args...)
message := fmt.Sprintf(format, args...)
if logger.Level(l.logLevel) >= 1 {
l.logToAdmins("ERROR", message)
}
}
func (l *adminCCLogger) Infof(format string, args ...any) {
l.Logger.Infof(format, args...)
message := fmt.Sprintf(format, args...)
if logger.Level(l.logLevel) >= 3 {
l.logToAdmins("INFO", message)
}
}
func (l *adminCCLogger) Warnf(format string, args ...any) {
l.Logger.Warnf(format, args...)
message := fmt.Sprintf(format, args...)
if logger.Level(l.logLevel) >= 2 {
l.logToAdmins("WARN", message)
}
}
func (l *adminCCLogger) logToAdmins(level, message string) {
context := l.Context()
if l.includeContext && len(context) > 0 {
message += "\n" + common.JSONBlock(context)
}
_ = l.dmAdmins("(log %s) %s", level, message)
}
func (l *adminCCLogger) dmAdmins(format string, args ...any) error {
for _, id := range l.userIDs {
_, err := l.dmer.DM(id, format, args...)
if err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,81 @@
// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved.
// See License for license information.
package logger
import (
"fmt"
"maps"
"time"
"github.com/mattermost/mattermost/server/public/pluginapi/experimental/common"
)
type defaultLogger struct {
logContext LogContext
logAPI common.LogAPI
}
/*
New creates a new logger.
- api: LogAPI implementation
*/
func New(api common.LogAPI) Logger {
l := &defaultLogger{
logAPI: api,
}
return l
}
func (l *defaultLogger) With(logContext LogContext) Logger {
newLogger := *l
if len(newLogger.logContext) == 0 {
newLogger.logContext = map[string]any{}
}
maps.Copy(newLogger.logContext, logContext)
return &newLogger
}
func (l *defaultLogger) WithError(err error) Logger {
newLogger := *l
if len(newLogger.logContext) == 0 {
newLogger.logContext = map[string]any{}
}
newLogger.logContext[ErrorKey] = err.Error()
return &newLogger
}
func (l *defaultLogger) Context() LogContext {
return l.logContext
}
func (l *defaultLogger) Timed() Logger {
return l.With(LogContext{
timed: time.Now(),
})
}
func (l *defaultLogger) Debugf(format string, args ...any) {
measure(l.logContext)
message := fmt.Sprintf(format, args...)
l.logAPI.LogDebug(message, toKeyValuePairs(l.logContext)...)
}
func (l *defaultLogger) Errorf(format string, args ...any) {
measure(l.logContext)
message := fmt.Sprintf(format, args...)
l.logAPI.LogError(message, toKeyValuePairs(l.logContext)...)
}
func (l *defaultLogger) Infof(format string, args ...any) {
measure(l.logContext)
message := fmt.Sprintf(format, args...)
l.logAPI.LogInfo(message, toKeyValuePairs(l.logContext)...)
}
func (l *defaultLogger) Warnf(format string, args ...any) {
measure(l.logContext)
message := fmt.Sprintf(format, args...)
l.logAPI.LogWarn(message, toKeyValuePairs(l.logContext)...)
}

View File

@@ -0,0 +1,78 @@
package logger
import "time"
const (
timed = "__since"
elapsed = "Elapsed"
ErrorKey = "error"
)
// LogLevel defines the level of log messages
type LogLevel string
const (
// LogLevelDebug denotes debug messages
LogLevelDebug = "debug"
// LogLevelInfo denotes info messages
LogLevelInfo = "info"
// LogLevelWarn denotes warn messages
LogLevelWarn = "warn"
// LogLevelError denotes error messages
LogLevelError = "error"
)
// LogContext defines the context for the logs.
type LogContext map[string]any
// Logger defines an object able to log messages.
type Logger interface {
// With adds a logContext to the logger.
With(LogContext) Logger
// WithError adds an Error to the logger.
WithError(error) Logger
// Context returns the current context
Context() LogContext
// Timed add a timed log context.
Timed() Logger
// Debugf logs a formatted string as a debug message.
Debugf(format string, args ...any)
// Errorf logs a formatted string as an error message.
Errorf(format string, args ...any)
// Infof logs a formatted string as an info message.
Infof(format string, args ...any)
// Warnf logs a formatted string as an warning message.
Warnf(format string, args ...any)
}
func measure(lc LogContext) {
if lc[timed] == nil {
return
}
started := lc[timed].(time.Time)
lc[elapsed] = time.Since(started).String()
delete(lc, timed)
}
// Level assigns an integer to the LogLevel string
func Level(l LogLevel) int {
switch l {
case LogLevelDebug:
return 4
case LogLevelInfo:
return 3
case LogLevelWarn:
return 2
case LogLevelError:
return 1
}
return 0
}
func toKeyValuePairs(in map[string]any) (out []any) {
for k, v := range in {
out = append(out, k, v)
}
return out
}

View File

@@ -0,0 +1,17 @@
package logger
type nilLogger struct{}
// NewNilLogger returns a logger that performs no action.
func NewNilLogger() Logger {
return &nilLogger{}
}
func (l *nilLogger) With(LogContext) Logger { return l }
func (l *nilLogger) WithError(error) Logger { return l }
func (l *nilLogger) Context() LogContext { return nil }
func (l *nilLogger) Timed() Logger { return l }
func (l *nilLogger) Debugf(string, ...any) {}
func (l *nilLogger) Errorf(string, ...any) {}
func (l *nilLogger) Infof(string, ...any) {}
func (l *nilLogger) Warnf(string, ...any) {}

View File

@@ -0,0 +1,60 @@
package logger
import (
"fmt"
"maps"
"testing"
"time"
)
type testLogger struct {
testing.TB
logContext LogContext
}
// NewTestLogger creates a logger for testing purposes.
func NewTestLogger() Logger {
return &testLogger{}
}
func (l *testLogger) With(logContext LogContext) Logger {
newl := *l
if len(newl.logContext) == 0 {
newl.logContext = map[string]any{}
}
maps.Copy(newl.logContext, logContext)
return &newl
}
func (l *testLogger) WithError(err error) Logger {
newl := *l
if len(newl.logContext) == 0 {
newl.logContext = map[string]any{}
}
newl.logContext[ErrorKey] = err.Error()
return &newl
}
func (l *testLogger) Context() LogContext {
return l.logContext
}
func (l *testLogger) Timed() Logger {
return l.With(LogContext{
timed: time.Now(),
})
}
func (l *testLogger) logf(prefix, format string, args ...any) {
out := fmt.Sprintf(prefix+": "+format, args...)
if len(l.logContext) > 0 {
measure(l.logContext)
out += fmt.Sprintf(" -- %+v", l.logContext)
}
l.TB.Log(out)
}
func (l *testLogger) Debugf(format string, args ...any) { l.logf("DEBUG", format, args...) }
func (l *testLogger) Errorf(format string, args ...any) { l.logf("ERROR", format, args...) }
func (l *testLogger) Infof(format string, args ...any) { l.logf("INFO", format, args...) }
func (l *testLogger) Warnf(format string, args ...any) { l.logf("WARN", format, args...) }

View File

@@ -0,0 +1,77 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/mattermost/mattermost/server/public/pluginapi/experimental/bot (interfaces: Bot)
// Package mock_bot is a generated GoMock package.
package mock_bot
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
model "github.com/mattermost/mattermost/server/public/model"
)
// MockBot is a mock of Bot interface.
type MockBot struct {
ctrl *gomock.Controller
recorder *MockBotMockRecorder
}
// MockBotMockRecorder is the mock recorder for MockBot.
type MockBotMockRecorder struct {
mock *MockBot
}
// NewMockBot creates a new mock instance.
func NewMockBot(ctrl *gomock.Controller) *MockBot {
mock := &MockBot{ctrl: ctrl}
mock.recorder = &MockBotMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockBot) EXPECT() *MockBotMockRecorder {
return m.recorder
}
// Ensure mocks base method.
func (m *MockBot) Ensure(arg0 *model.Bot, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Ensure", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// Ensure indicates an expected call of Ensure.
func (mr *MockBotMockRecorder) Ensure(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ensure", reflect.TypeOf((*MockBot)(nil).Ensure), arg0, arg1)
}
// MattermostUserID mocks base method.
func (m *MockBot) MattermostUserID() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MattermostUserID")
ret0, _ := ret[0].(string)
return ret0
}
// MattermostUserID indicates an expected call of MattermostUserID.
func (mr *MockBotMockRecorder) MattermostUserID() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MattermostUserID", reflect.TypeOf((*MockBot)(nil).MattermostUserID))
}
// String mocks base method.
func (m *MockBot) String() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "String")
ret0, _ := ret[0].(string)
return ret0
}
// String indicates an expected call of String.
func (mr *MockBotMockRecorder) String() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockBot)(nil).String))
}

View File

@@ -0,0 +1,159 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/mattermost/mattermost/server/public/pluginapi/experimental/bot/logger (interfaces: Logger)
// Package mock_bot is a generated GoMock package.
package mock_bot
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
logger "github.com/mattermost/mattermost/server/public/pluginapi/experimental/bot/logger"
)
// MockLogger is a mock of Logger interface.
type MockLogger struct {
ctrl *gomock.Controller
recorder *MockLoggerMockRecorder
}
// MockLoggerMockRecorder is the mock recorder for MockLogger.
type MockLoggerMockRecorder struct {
mock *MockLogger
}
// NewMockLogger creates a new mock instance.
func NewMockLogger(ctrl *gomock.Controller) *MockLogger {
mock := &MockLogger{ctrl: ctrl}
mock.recorder = &MockLoggerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
return m.recorder
}
// Context mocks base method.
func (m *MockLogger) Context() logger.LogContext {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Context")
ret0, _ := ret[0].(logger.LogContext)
return ret0
}
// Context indicates an expected call of Context.
func (mr *MockLoggerMockRecorder) Context() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockLogger)(nil).Context))
}
// Debugf mocks base method.
func (m *MockLogger) Debugf(arg0 string, arg1 ...interface{}) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Debugf", varargs...)
}
// Debugf indicates an expected call of Debugf.
func (mr *MockLoggerMockRecorder) Debugf(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debugf", reflect.TypeOf((*MockLogger)(nil).Debugf), varargs...)
}
// Errorf mocks base method.
func (m *MockLogger) Errorf(arg0 string, arg1 ...interface{}) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Errorf", varargs...)
}
// Errorf indicates an expected call of Errorf.
func (mr *MockLoggerMockRecorder) Errorf(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Errorf", reflect.TypeOf((*MockLogger)(nil).Errorf), varargs...)
}
// Infof mocks base method.
func (m *MockLogger) Infof(arg0 string, arg1 ...interface{}) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Infof", varargs...)
}
// Infof indicates an expected call of Infof.
func (mr *MockLoggerMockRecorder) Infof(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Infof", reflect.TypeOf((*MockLogger)(nil).Infof), varargs...)
}
// Timed mocks base method.
func (m *MockLogger) Timed() logger.Logger {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Timed")
ret0, _ := ret[0].(logger.Logger)
return ret0
}
// Timed indicates an expected call of Timed.
func (mr *MockLoggerMockRecorder) Timed() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Timed", reflect.TypeOf((*MockLogger)(nil).Timed))
}
// Warnf mocks base method.
func (m *MockLogger) Warnf(arg0 string, arg1 ...interface{}) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Warnf", varargs...)
}
// Warnf indicates an expected call of Warnf.
func (mr *MockLoggerMockRecorder) Warnf(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warnf", reflect.TypeOf((*MockLogger)(nil).Warnf), varargs...)
}
// With mocks base method.
func (m *MockLogger) With(arg0 logger.LogContext) logger.Logger {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "With", arg0)
ret0, _ := ret[0].(logger.Logger)
return ret0
}
// With indicates an expected call of With.
func (mr *MockLoggerMockRecorder) With(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "With", reflect.TypeOf((*MockLogger)(nil).With), arg0)
}
// WithError mocks base method.
func (m *MockLogger) WithError(arg0 error) logger.Logger {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "WithError", arg0)
ret0, _ := ret[0].(logger.Logger)
return ret0
}
// WithError indicates an expected call of WithError.
func (mr *MockLoggerMockRecorder) WithError(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithError", reflect.TypeOf((*MockLogger)(nil).WithError), arg0)
}

View File

@@ -0,0 +1,151 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/mattermost/mattermost/server/public/pluginapi/experimental/bot/poster (interfaces: Poster)
// Package mock_bot is a generated GoMock package.
package mock_bot
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
model "github.com/mattermost/mattermost/server/public/model"
)
// MockPoster is a mock of Poster interface.
type MockPoster struct {
ctrl *gomock.Controller
recorder *MockPosterMockRecorder
}
// MockPosterMockRecorder is the mock recorder for MockPoster.
type MockPosterMockRecorder struct {
mock *MockPoster
}
// NewMockPoster creates a new mock instance.
func NewMockPoster(ctrl *gomock.Controller) *MockPoster {
mock := &MockPoster{ctrl: ctrl}
mock.recorder = &MockPosterMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPoster) EXPECT() *MockPosterMockRecorder {
return m.recorder
}
// DM mocks base method.
func (m *MockPoster) DM(arg0, arg1 string, arg2 ...interface{}) (string, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DM", varargs...)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DM indicates an expected call of DM.
func (mr *MockPosterMockRecorder) DM(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DM", reflect.TypeOf((*MockPoster)(nil).DM), varargs...)
}
// DMWithAttachments mocks base method.
func (m *MockPoster) DMWithAttachments(arg0 string, arg1 ...*model.MessageAttachment) (string, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DMWithAttachments", varargs...)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DMWithAttachments indicates an expected call of DMWithAttachments.
func (mr *MockPosterMockRecorder) DMWithAttachments(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DMWithAttachments", reflect.TypeOf((*MockPoster)(nil).DMWithAttachments), varargs...)
}
// DeletePost mocks base method.
func (m *MockPoster) DeletePost(arg0 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeletePost", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// DeletePost indicates an expected call of DeletePost.
func (mr *MockPosterMockRecorder) DeletePost(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePost", reflect.TypeOf((*MockPoster)(nil).DeletePost), arg0)
}
// Ephemeral mocks base method.
func (m *MockPoster) Ephemeral(arg0, arg1, arg2 string, arg3 ...interface{}) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1, arg2}
for _, a := range arg3 {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Ephemeral", varargs...)
}
// Ephemeral indicates an expected call of Ephemeral.
func (mr *MockPosterMockRecorder) Ephemeral(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1, arg2}, arg3...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ephemeral", reflect.TypeOf((*MockPoster)(nil).Ephemeral), varargs...)
}
// UpdatePost mocks base method.
func (m *MockPoster) UpdatePost(arg0 *model.Post) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdatePost", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// UpdatePost indicates an expected call of UpdatePost.
func (mr *MockPosterMockRecorder) UpdatePost(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePost", reflect.TypeOf((*MockPoster)(nil).UpdatePost), arg0)
}
// UpdatePostByID mocks base method.
func (m *MockPoster) UpdatePostByID(arg0, arg1 string, arg2 ...interface{}) error {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UpdatePostByID", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// UpdatePostByID indicates an expected call of UpdatePostByID.
func (mr *MockPosterMockRecorder) UpdatePostByID(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePostByID", reflect.TypeOf((*MockPoster)(nil).UpdatePostByID), varargs...)
}
// UpdatePosterID mocks base method.
func (m *MockPoster) UpdatePosterID(arg0 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "UpdatePosterID", arg0)
}
// UpdatePosterID indicates an expected call of UpdatePosterID.
func (mr *MockPosterMockRecorder) UpdatePosterID(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePosterID", reflect.TypeOf((*MockPoster)(nil).UpdatePosterID), arg0)
}

View File

@@ -0,0 +1,76 @@
package poster
import (
"fmt"
"github.com/mattermost/mattermost/server/public/model"
)
type defaultPoster struct {
postAPI PostAPI
id string
}
// NewPoster creates a new default poster
func NewPoster(postAPI PostAPI, id string) Poster {
return &defaultPoster{
postAPI: postAPI,
id: id,
}
}
// DM posts a simple Direct Message to the specified user
func (p *defaultPoster) DM(mattermostUserID, format string, args ...any) (string, error) {
post := &model.Post{
Message: fmt.Sprintf(format, args...),
}
err := p.postAPI.DM(p.id, mattermostUserID, post)
if err != nil {
return "", err
}
return post.Id, nil
}
// DMWithAttachments posts a Direct Message that contains Message Attachments.
// Often used to include post actions.
func (p *defaultPoster) DMWithAttachments(mattermostUserID string, attachments ...*model.MessageAttachment) (string, error) {
post := model.Post{}
model.ParseMessageAttachment(&post, attachments)
err := p.postAPI.DM(p.id, mattermostUserID, &post)
if err != nil {
return "", err
}
return post.Id, nil
}
// Ephemeral sends an ephemeral message to a user
func (p *defaultPoster) Ephemeral(userID, channelID, format string, args ...any) {
post := &model.Post{
UserId: p.id,
ChannelId: channelID,
Message: fmt.Sprintf(format, args...),
}
p.postAPI.SendEphemeralPost(userID, post)
}
func (p *defaultPoster) UpdatePostByID(postID, format string, args ...any) error {
post, err := p.postAPI.GetPost(postID)
if err != nil {
return err
}
post.Message = fmt.Sprintf(format, args...)
return p.UpdatePost(post)
}
func (p *defaultPoster) DeletePost(postID string) error {
return p.postAPI.DeletePost(postID)
}
func (p *defaultPoster) UpdatePost(post *model.Post) error {
return p.postAPI.UpdatePost(post)
}
func (p *defaultPoster) UpdatePosterID(id string) {
p.id = id
}

View File

@@ -0,0 +1,414 @@
package poster
import (
"errors"
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin/plugintest"
"github.com/mattermost/mattermost/server/public/pluginapi"
"github.com/mattermost/mattermost/server/public/pluginapi/experimental/bot/poster/mock_import"
)
const (
botID = "test-bot-user"
userID = "test-user-1"
dmChannelID = "dm-channel-id"
)
func TestInterface(t *testing.T) {
t.Run("Plugin API satisfy the interface", func(t *testing.T) {
api := &plugintest.API{}
driver := &plugintest.Driver{}
client := pluginapi.NewClient(api, driver)
_ = NewPoster(&client.Post, botID)
})
}
func TestDM(t *testing.T) {
format := "test format, string: %s int: %d value: %v"
args := []any{"some string", 5, 8.423}
expectedMessage := "test format, string: some string int: 5 value: 8.423"
expectedPostID := "expected-post-id"
post := &model.Post{
Message: expectedMessage,
}
postWithID := model.Post{
Id: expectedPostID,
UserId: botID,
ChannelId: dmChannelID,
Message: expectedMessage,
}
mockError := errors.New("mock error")
t.Run("DM Success", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
postAPI := mock_import.NewMockPostAPI(ctrl)
poster := NewPoster(postAPI, botID)
//nolint:govet //copy lock, but only used in tests
postAPI.
EXPECT().
DM(botID, userID, post).
SetArg(2, postWithID).
Return(nil).
Times(1)
postID, err := poster.DM(userID, format, args...)
assert.Equal(t, expectedPostID, postID)
assert.NoError(t, err)
})
t.Run("DM error", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
postAPI := mock_import.NewMockPostAPI(ctrl)
poster := NewPoster(postAPI, botID)
postAPI.
EXPECT().
DM(botID, userID, post).
Return(mockError).
Times(1)
_, err := poster.DM(userID, format, args...)
assert.Error(t, err)
})
}
func TestDMWithAttachments(t *testing.T) {
expectedPostID := "expected-post-id"
attachments := []*model.MessageAttachment{
{},
{},
}
post := &model.Post{}
model.ParseMessageAttachment(post, attachments)
postWithID := model.Post{
Id: expectedPostID,
UserId: botID,
ChannelId: dmChannelID,
Type: model.PostTypeMessageAttachment,
Props: model.StringInterface{
model.PostPropsAttachments: attachments,
},
}
mockError := errors.New("mock error")
t.Run("DM Success", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
postAPI := mock_import.NewMockPostAPI(ctrl)
poster := NewPoster(postAPI, botID)
//nolint:govet //copy lock, but only used in tests
postAPI.
EXPECT().
DM(botID, userID, post).
SetArg(2, postWithID).
Return(nil).
Times(1)
postID, err := poster.DMWithAttachments(userID, attachments...)
assert.Equal(t, expectedPostID, postID)
assert.NoError(t, err)
})
t.Run("DM error", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
postAPI := mock_import.NewMockPostAPI(ctrl)
poster := NewPoster(postAPI, botID)
postAPI.
EXPECT().
DM(botID, userID, post).
Return(mockError).
Times(1)
_, err := poster.DMWithAttachments(userID, attachments...)
assert.Error(t, err)
})
}
func TestEphemeral(t *testing.T) {
format := "test format, string: %s int: %d value: %v"
args := []any{"some string", 5, 8.423}
expectedMessage := "test format, string: some string int: 5 value: 8.423"
channelID := "some-channel"
post := &model.Post{
UserId: botID,
ChannelId: channelID,
Message: expectedMessage,
}
expectedPostID := "some-post-ID"
postWithID := model.Post{
Id: expectedPostID,
UserId: botID,
ChannelId: channelID,
Message: expectedMessage,
}
t.Run("Success", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
postAPI := mock_import.NewMockPostAPI(ctrl)
poster := NewPoster(postAPI, botID)
//nolint:govet //copy lock, but only used in tests
postAPI.
EXPECT().
SendEphemeralPost(userID, post).
SetArg(1, postWithID).
Times(1)
poster.Ephemeral(userID, channelID, format, args...)
})
}
func TestUpdatePostByID(t *testing.T) {
format := "test format, string: %s int: %d value: %v"
args := []any{"some string", 5, 8.423}
expectedMessage := "test format, string: some string int: 5 value: 8.423"
postID := "some-post-id"
originalPost := &model.Post{
Id: postID,
Message: "some message",
}
updatedPost := &model.Post{
Id: postID,
Message: expectedMessage,
}
mockError := errors.New("mock error")
t.Run("Success", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
postAPI := mock_import.NewMockPostAPI(ctrl)
poster := NewPoster(postAPI, botID)
postAPI.
EXPECT().
GetPost(postID).
Return(originalPost, nil).
Times(1)
postAPI.
EXPECT().
UpdatePost(updatedPost).
Return(nil).
Times(1)
err := poster.UpdatePostByID(postID, format, args...)
assert.NoError(t, err)
})
t.Run("Error fetching", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
postAPI := mock_import.NewMockPostAPI(ctrl)
poster := NewPoster(postAPI, botID)
postAPI.
EXPECT().
GetPost(postID).
Return(nil, mockError).
Times(1)
err := poster.UpdatePostByID(postID, format, args...)
assert.Error(t, err)
})
t.Run("Error updating", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
postAPI := mock_import.NewMockPostAPI(ctrl)
poster := NewPoster(postAPI, botID)
postAPI.
EXPECT().
GetPost(postID).
Return(originalPost, nil).
Times(1)
postAPI.
EXPECT().
UpdatePost(updatedPost).
Return(mockError).
Times(1)
err := poster.UpdatePostByID(postID, format, args...)
assert.Error(t, err)
})
}
func TestDeletePost(t *testing.T) {
postID := "some-post-id"
mockError := errors.New("mock channel error")
t.Run("Success", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
postAPI := mock_import.NewMockPostAPI(ctrl)
poster := NewPoster(postAPI, botID)
postAPI.
EXPECT().
DeletePost(postID).
Return(nil).
Times(1)
err := poster.DeletePost(postID)
assert.NoError(t, err)
})
t.Run("Error", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
postAPI := mock_import.NewMockPostAPI(ctrl)
poster := NewPoster(postAPI, botID)
postAPI.
EXPECT().
DeletePost(postID).
Return(mockError).
Times(1)
err := poster.DeletePost(postID)
assert.Error(t, err)
})
}
func TestUpdatePost(t *testing.T) {
post := &model.Post{
Id: "some-post-id",
Message: "some message",
}
mockError := errors.New("mock channel error")
t.Run("Success", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
postAPI := mock_import.NewMockPostAPI(ctrl)
poster := NewPoster(postAPI, botID)
postAPI.
EXPECT().
UpdatePost(post).
Return(nil).
Times(1)
err := poster.UpdatePost(post)
assert.NoError(t, err)
})
t.Run("Error", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
postAPI := mock_import.NewMockPostAPI(ctrl)
poster := NewPoster(postAPI, botID)
postAPI.
EXPECT().
UpdatePost(post).
Return(mockError).
Times(1)
err := poster.UpdatePost(post)
assert.Error(t, err)
})
}
func TestUpdatePosterID(t *testing.T) {
format := "test format, string: %s int: %d value: %v"
args := []any{"some string", 5, 8.423}
expectedMessage := "test format, string: some string int: 5 value: 8.423"
expectedPostID := "expected-post-id"
post := &model.Post{
Message: expectedMessage,
}
postWithID := model.Post{
Id: expectedPostID,
UserId: botID,
ChannelId: dmChannelID,
Message: expectedMessage,
}
newBotID := "new-bot-id"
t.Run("Success", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
postAPI := mock_import.NewMockPostAPI(ctrl)
poster := NewPoster(postAPI, botID)
//nolint:govet //copy lock, but only used in tests
postAPI.
EXPECT().
DM(botID, userID, post).
SetArg(2, postWithID).
Return(nil).
Times(1)
_, _ = poster.DM(userID, format, args...)
poster.UpdatePosterID(newBotID)
//nolint:govet //copy lock, but only used in tests
postAPI.
EXPECT().
DM(newBotID, userID, post).
SetArg(2, postWithID).
Return(nil).
Times(1)
_, _ = poster.DM(userID, format, args...)
})
}

View File

@@ -0,0 +1,14 @@
package poster
import (
"github.com/mattermost/mattermost/server/public/model"
)
// PostAPI defines the portion of the Post Service used by the poster
type PostAPI interface {
DM(senderUserID, receiverUserID string, post *model.Post) error
GetPost(postID string) (*model.Post, error)
UpdatePost(post *model.Post) error
DeletePost(postID string) error
SendEphemeralPost(userID string, post *model.Post)
}

View File

@@ -0,0 +1,104 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/mattermost/mattermost/server/public/pluginapi/experimental/bot/poster (interfaces: PostAPI)
// Package mock_import is a generated GoMock package.
package mock_import
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
model "github.com/mattermost/mattermost/server/public/model"
)
// MockPostAPI is a mock of PostAPI interface.
type MockPostAPI struct {
ctrl *gomock.Controller
recorder *MockPostAPIMockRecorder
}
// MockPostAPIMockRecorder is the mock recorder for MockPostAPI.
type MockPostAPIMockRecorder struct {
mock *MockPostAPI
}
// NewMockPostAPI creates a new mock instance.
func NewMockPostAPI(ctrl *gomock.Controller) *MockPostAPI {
mock := &MockPostAPI{ctrl: ctrl}
mock.recorder = &MockPostAPIMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPostAPI) EXPECT() *MockPostAPIMockRecorder {
return m.recorder
}
// DM mocks base method.
func (m *MockPostAPI) DM(arg0, arg1 string, arg2 *model.Post) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DM", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// DM indicates an expected call of DM.
func (mr *MockPostAPIMockRecorder) DM(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DM", reflect.TypeOf((*MockPostAPI)(nil).DM), arg0, arg1, arg2)
}
// DeletePost mocks base method.
func (m *MockPostAPI) DeletePost(arg0 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeletePost", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// DeletePost indicates an expected call of DeletePost.
func (mr *MockPostAPIMockRecorder) DeletePost(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePost", reflect.TypeOf((*MockPostAPI)(nil).DeletePost), arg0)
}
// GetPost mocks base method.
func (m *MockPostAPI) GetPost(arg0 string) (*model.Post, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPost", arg0)
ret0, _ := ret[0].(*model.Post)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPost indicates an expected call of GetPost.
func (mr *MockPostAPIMockRecorder) GetPost(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPost", reflect.TypeOf((*MockPostAPI)(nil).GetPost), arg0)
}
// SendEphemeralPost mocks base method.
func (m *MockPostAPI) SendEphemeralPost(arg0 string, arg1 *model.Post) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SendEphemeralPost", arg0, arg1)
}
// SendEphemeralPost indicates an expected call of SendEphemeralPost.
func (mr *MockPostAPIMockRecorder) SendEphemeralPost(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendEphemeralPost", reflect.TypeOf((*MockPostAPI)(nil).SendEphemeralPost), arg0, arg1)
}
// UpdatePost mocks base method.
func (m *MockPostAPI) UpdatePost(arg0 *model.Post) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdatePost", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// UpdatePost indicates an expected call of UpdatePost.
func (mr *MockPostAPIMockRecorder) UpdatePost(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePost", reflect.TypeOf((*MockPostAPI)(nil).UpdatePost), arg0)
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved.
// See License for license information.
package poster
import (
"github.com/mattermost/mattermost/server/public/model"
)
// Poster defines an entity that can post DMs and Ephemerals and update and delete those posts
type Poster interface {
DMer
// DMWithAttachments posts a Direct Message that contains Message Attachments.
// Often used to include post actions.
DMWithAttachments(mattermostUserID string, attachments ...*model.MessageAttachment) (string, error)
// Ephemeral sends an ephemeral message to a user
Ephemeral(mattermostUserID, channelID, format string, args ...any)
// UpdatePostByID updates the post with postID with the formatted message
UpdatePostByID(postID, format string, args ...any) error
// DeletePost deletes a single post
DeletePost(postID string) error
// DMUpdatePost substitute one post with another
UpdatePost(post *model.Post) error
// UpdatePosterID updates the Mattermost User ID of the poster
UpdatePosterID(id string)
}
// DMer defines an entity that can send Direct Messages
type DMer interface {
// DM posts a simple Direct Message to the specified user
DM(mattermostUserID, format string, args ...any) (string, error)
}