chore: retrieved base logic files from matrix bridge
This commit is contained in:
parent
b10a439a29
commit
202622f2c4
5 changed files with 600 additions and 1 deletions
424
server/testhelpers_test.go
Normal file
424
server/testhelpers_test.go
Normal file
|
@ -0,0 +1,424 @@
|
|||
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)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue