424 lines
12 KiB
Go
424 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/mattermost/mattermost-plugin-matrix-bridge/server/matrix"
|
|
"github.com/mattermost/mattermost-plugin-matrix-bridge/server/store/kvstore"
|
|
matrixtest "github.com/mattermost/mattermost-plugin-matrix-bridge/testcontainers/matrix"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
"github.com/mattermost/mattermost/server/public/plugin/plugintest"
|
|
"github.com/pkg/errors"
|
|
"github.com/stretchr/testify/mock"
|
|
)
|
|
|
|
// testLogger implements Logger interface for testing
|
|
type testLogger struct {
|
|
t *testing.T
|
|
}
|
|
|
|
func (l *testLogger) LogDebug(message string, keyValuePairs ...any) {
|
|
if l.t != nil {
|
|
l.t.Logf("[DEBUG] %s %v", message, keyValuePairs)
|
|
}
|
|
}
|
|
|
|
func (l *testLogger) LogInfo(message string, keyValuePairs ...any) {
|
|
if l.t != nil {
|
|
l.t.Logf("[INFO] %s %v", message, keyValuePairs)
|
|
}
|
|
}
|
|
|
|
func (l *testLogger) LogWarn(message string, keyValuePairs ...any) {
|
|
if l.t != nil {
|
|
l.t.Logf("[WARN] %s %v", message, keyValuePairs)
|
|
}
|
|
}
|
|
|
|
func (l *testLogger) LogError(message string, keyValuePairs ...any) {
|
|
if l.t != nil {
|
|
l.t.Logf("[ERROR] %s %v", message, keyValuePairs)
|
|
}
|
|
}
|
|
|
|
// TestSetup contains common test setup data for integration tests
|
|
type TestSetup struct {
|
|
Plugin *Plugin
|
|
ChannelID string
|
|
UserID string
|
|
RoomID string
|
|
GhostUserID string
|
|
API *plugintest.API
|
|
}
|
|
|
|
// setupPluginForTest creates a basic plugin instance with mock API for unit tests
|
|
func setupPluginForTest() *Plugin {
|
|
api := &plugintest.API{}
|
|
|
|
// Allow any logging calls since we're not testing logging behavior
|
|
api.On("LogDebug", mock.Anything, mock.Anything).Maybe()
|
|
api.On("LogDebug", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe()
|
|
api.On("LogDebug", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe()
|
|
|
|
plugin := &Plugin{}
|
|
plugin.SetAPI(api)
|
|
plugin.logger = &testLogger{}
|
|
return plugin
|
|
}
|
|
|
|
// setupPluginForTestWithLogger creates a plugin instance with test logger that logs to testing.T
|
|
func setupPluginForTestWithLogger(t *testing.T, api plugin.API) *Plugin {
|
|
plugin := &Plugin{}
|
|
plugin.API = api
|
|
plugin.logger = &testLogger{t: t}
|
|
return plugin
|
|
}
|
|
|
|
// setupPluginForTestWithKVStore creates a plugin instance with test logger, API, and KV store
|
|
func setupPluginForTestWithKVStore(t *testing.T, api plugin.API, kvstore kvstore.KVStore) *Plugin {
|
|
plugin := &Plugin{}
|
|
plugin.API = api
|
|
plugin.kvstore = kvstore
|
|
plugin.logger = &testLogger{t: t}
|
|
return plugin
|
|
}
|
|
|
|
// createMatrixClientWithTestLogger creates a matrix client with test logger for testing
|
|
func createMatrixClientWithTestLogger(t *testing.T, serverURL, asToken, remoteID string) *matrix.Client {
|
|
testLogger := matrix.NewTestLogger(t)
|
|
return matrix.NewClientWithLogger(serverURL, asToken, remoteID, testLogger)
|
|
}
|
|
|
|
// TestMatrixClientTestLogger verifies that matrix client uses test logger correctly
|
|
func TestMatrixClientTestLogger(t *testing.T) {
|
|
// Create a matrix client with test logger
|
|
client := createMatrixClientWithTestLogger(t, "https://test.example.com", "test_token", "test_remote")
|
|
|
|
// This would trigger logging if the matrix client were to log something
|
|
// Since we can't easily test actual HTTP calls without a server, this test mainly
|
|
// verifies that the client is created correctly with a test logger
|
|
if client == nil {
|
|
t.Error("Matrix client should not be nil")
|
|
}
|
|
|
|
// Log success - this confirms the test logger interface is working
|
|
t.Log("Matrix client created successfully with test logger")
|
|
}
|
|
|
|
// setupTestPlugin creates a test plugin instance with Matrix container for integration tests
|
|
func setupTestPlugin(t *testing.T, matrixContainer *matrixtest.MatrixContainer) *TestSetup {
|
|
api := &plugintest.API{}
|
|
|
|
testChannelID := model.NewId()
|
|
testUserID := model.NewId()
|
|
testRoomID := matrixContainer.CreateRoom(t, "Test Room")
|
|
testGhostUserID := "@_mattermost_" + testUserID + ":" + matrixContainer.ServerDomain
|
|
|
|
plugin := &Plugin{remoteID: "test-remote-id"}
|
|
plugin.SetAPI(api)
|
|
|
|
// Initialize kvstore with in-memory implementation for testing
|
|
plugin.kvstore = NewMemoryKVStore()
|
|
|
|
// Initialize required plugin components
|
|
plugin.pendingFiles = NewPendingFileTracker()
|
|
plugin.postTracker = NewPostTracker(DefaultPostTrackerMaxEntries)
|
|
|
|
plugin.matrixClient = createMatrixClientWithTestLogger(
|
|
t,
|
|
matrixContainer.ServerURL,
|
|
matrixContainer.ASToken,
|
|
plugin.remoteID,
|
|
)
|
|
// Set explicit server domain for testing
|
|
plugin.matrixClient.SetServerDomain(matrixContainer.ServerDomain)
|
|
|
|
config := &configuration{
|
|
MatrixServerURL: matrixContainer.ServerURL,
|
|
MatrixASToken: matrixContainer.ASToken,
|
|
MatrixHSToken: matrixContainer.HSToken,
|
|
}
|
|
plugin.configuration = config
|
|
|
|
// Set up basic mocks
|
|
setupBasicMocks(api, testUserID)
|
|
|
|
// Set up test data in KV store
|
|
setupTestKVData(plugin.kvstore, testChannelID, testRoomID)
|
|
|
|
// Initialize the logger with test implementation
|
|
plugin.logger = &testLogger{t: t}
|
|
|
|
// Initialize bridges for testing
|
|
plugin.initBridges()
|
|
|
|
return &TestSetup{
|
|
Plugin: plugin,
|
|
ChannelID: testChannelID,
|
|
UserID: testUserID,
|
|
RoomID: testRoomID,
|
|
GhostUserID: testGhostUserID,
|
|
API: api,
|
|
}
|
|
}
|
|
|
|
// setupBasicMocks sets up common API mocks for integration tests
|
|
func setupBasicMocks(api *plugintest.API, testUserID string) {
|
|
// Basic user mock
|
|
testUser := &model.User{
|
|
Id: testUserID,
|
|
Username: "testuser",
|
|
Email: "test@example.com",
|
|
Nickname: "Test User",
|
|
}
|
|
api.On("GetUser", testUserID).Return(testUser, nil)
|
|
api.On("GetUser", mock.AnythingOfType("string")).Return(&model.User{Id: "default", Username: "default"}, nil)
|
|
|
|
// Mock profile image for ghost user creation
|
|
api.On("GetProfileImage", testUserID).Return([]byte("fake-image-data"), nil)
|
|
|
|
// Post update mock - return the updated post with current timestamp
|
|
api.On("UpdatePost", mock.AnythingOfType("*model.Post")).Return(func(post *model.Post) *model.Post {
|
|
// Simulate what Mattermost does - update the UpdateAt timestamp
|
|
updatedPost := post.Clone() // Copy the post
|
|
updatedPost.UpdateAt = time.Now().UnixMilli()
|
|
return updatedPost
|
|
}, nil)
|
|
|
|
// Logging mocks - handle variable argument types
|
|
api.On("LogDebug", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
|
|
api.On("LogInfo", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
|
|
api.On("LogWarn", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
|
|
}
|
|
|
|
// setupTestKVData sets up initial test data in the KV store
|
|
func setupTestKVData(kvstore kvstore.KVStore, testChannelID, testRoomID string) {
|
|
// Set up channel mapping
|
|
_ = kvstore.Set("channel_mapping_"+testChannelID, []byte(testRoomID))
|
|
|
|
// Ghost users and ghost rooms are intentionally not set up here
|
|
// to trigger creation during tests, which validates the creation logic
|
|
}
|
|
|
|
// setupMentionMocks sets up mocks for testing user mentions
|
|
func setupMentionMocks(api *plugintest.API, userID, username string) {
|
|
user := &model.User{Id: userID, Username: username, Email: username + "@example.com"}
|
|
api.On("GetUserByUsername", username).Return(user, nil)
|
|
// Mock profile image for ghost user creation
|
|
api.On("GetProfileImage", userID).Return([]byte("fake-image-data"), nil)
|
|
}
|
|
|
|
// clearMockExpectations clears all previous mock expectations for reuse in subtests
|
|
func clearMockExpectations(api *plugintest.API) {
|
|
api.ExpectedCalls = nil
|
|
}
|
|
|
|
// Helper function to compare file attachment arrays (moved from sync_to_matrix_test.go)
|
|
func compareFileAttachmentArrays(currentFiles, newFiles []matrix.FileAttachment) bool {
|
|
if len(currentFiles) != len(newFiles) {
|
|
return false
|
|
}
|
|
|
|
for i, newFile := range newFiles {
|
|
if i >= len(currentFiles) {
|
|
return false
|
|
}
|
|
|
|
currentFile := currentFiles[i]
|
|
if currentFile.Filename != newFile.Filename ||
|
|
currentFile.MxcURI != newFile.MxcURI ||
|
|
currentFile.MimeType != newFile.MimeType ||
|
|
currentFile.Size != newFile.Size {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// MemoryKVStore provides an in-memory implementation of the KVStore interface for testing.
|
|
type MemoryKVStore struct {
|
|
data map[string][]byte
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewMemoryKVStore creates a new in-memory KV store for testing.
|
|
func NewMemoryKVStore() kvstore.KVStore {
|
|
return &MemoryKVStore{
|
|
data: make(map[string][]byte),
|
|
}
|
|
}
|
|
|
|
// GetTemplateData retrieves template data for a specific user from the KV store.
|
|
func (m *MemoryKVStore) GetTemplateData(userID string) (string, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
key := "template_key-" + userID
|
|
if data, exists := m.data[key]; exists {
|
|
return string(data), nil
|
|
}
|
|
return "", errors.New("key not found")
|
|
}
|
|
|
|
// Get retrieves a value from the KV store by key.
|
|
func (m *MemoryKVStore) Get(key string) ([]byte, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
if data, exists := m.data[key]; exists {
|
|
// Return a copy to prevent external modification
|
|
result := make([]byte, len(data))
|
|
copy(result, data)
|
|
return result, nil
|
|
}
|
|
return nil, errors.New("key not found")
|
|
}
|
|
|
|
// Set stores a key-value pair in the KV store.
|
|
func (m *MemoryKVStore) Set(key string, value []byte) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
// Store a copy to prevent external modification
|
|
data := make([]byte, len(value))
|
|
copy(data, value)
|
|
m.data[key] = data
|
|
return nil
|
|
}
|
|
|
|
// Delete removes a key-value pair from the KV store.
|
|
func (m *MemoryKVStore) Delete(key string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
delete(m.data, key)
|
|
return nil
|
|
}
|
|
|
|
// ListKeys retrieves a paginated list of keys from the KV store.
|
|
func (m *MemoryKVStore) ListKeys(page, perPage int) ([]string, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
// Collect all keys
|
|
keys := make([]string, 0, len(m.data))
|
|
for key := range m.data {
|
|
keys = append(keys, key)
|
|
}
|
|
|
|
// Sort keys for consistent ordering
|
|
sort.Strings(keys)
|
|
|
|
// Apply pagination
|
|
start := page * perPage
|
|
if start >= len(keys) {
|
|
return []string{}, nil
|
|
}
|
|
|
|
end := start + perPage
|
|
if end > len(keys) {
|
|
end = len(keys)
|
|
}
|
|
|
|
return keys[start:end], nil
|
|
}
|
|
|
|
// ListKeysWithPrefix retrieves a paginated list of keys with a specific prefix from the KV store.
|
|
func (m *MemoryKVStore) ListKeysWithPrefix(page, perPage int, prefix string) ([]string, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
// Collect keys with the specified prefix
|
|
keys := make([]string, 0, len(m.data))
|
|
for key := range m.data {
|
|
if strings.HasPrefix(key, prefix) {
|
|
keys = append(keys, key)
|
|
}
|
|
}
|
|
|
|
// Sort keys for consistent ordering
|
|
sort.Strings(keys)
|
|
|
|
// Apply pagination
|
|
start := page * perPage
|
|
if start >= len(keys) {
|
|
return []string{}, nil
|
|
}
|
|
|
|
end := start + perPage
|
|
if end > len(keys) {
|
|
end = len(keys)
|
|
}
|
|
|
|
return keys[start:end], nil
|
|
}
|
|
|
|
// Clear removes all data from the store (useful for test cleanup).
|
|
func (m *MemoryKVStore) Clear() {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
m.data = make(map[string][]byte)
|
|
}
|
|
|
|
// Size returns the number of key-value pairs in the store.
|
|
func (m *MemoryKVStore) Size() int {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
return len(m.data)
|
|
}
|
|
|
|
// TestMemoryKVStore tests the in-memory KV store implementation
|
|
func TestMemoryKVStore(t *testing.T) {
|
|
store := NewMemoryKVStore()
|
|
|
|
// Test Set and Get
|
|
err := store.Set("test-key", []byte("test-value"))
|
|
if err != nil {
|
|
t.Errorf("Expected no error, got %v", err)
|
|
}
|
|
|
|
value, err := store.Get("test-key")
|
|
if err != nil {
|
|
t.Errorf("Expected no error, got %v", err)
|
|
}
|
|
if string(value) != "test-value" {
|
|
t.Errorf("Expected 'test-value', got '%s'", string(value))
|
|
}
|
|
|
|
// Test Get non-existent key
|
|
_, err = store.Get("non-existent")
|
|
if err == nil {
|
|
t.Error("Expected error for non-existent key")
|
|
}
|
|
|
|
// Test Delete
|
|
err = store.Delete("test-key")
|
|
if err != nil {
|
|
t.Errorf("Expected no error, got %v", err)
|
|
}
|
|
|
|
_, err = store.Get("test-key")
|
|
if err == nil {
|
|
t.Error("Expected error for deleted key")
|
|
}
|
|
}
|
|
|
|
// TestMain provides global test setup and cleanup
|
|
func TestMain(m *testing.M) {
|
|
// Run tests
|
|
code := m.Run()
|
|
|
|
// Ensure all Matrix containers are cleaned up
|
|
matrixtest.CleanupAllContainers()
|
|
|
|
os.Exit(code)
|
|
}
|