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
41
server/logger.go
Normal file
41
server/logger.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package main
|
||||
|
||||
import "github.com/mattermost/mattermost/server/public/plugin"
|
||||
|
||||
// Logger interface for logging operations
|
||||
type Logger interface {
|
||||
LogDebug(message string, keyValuePairs ...any)
|
||||
LogInfo(message string, keyValuePairs ...any)
|
||||
LogWarn(message string, keyValuePairs ...any)
|
||||
LogError(message string, keyValuePairs ...any)
|
||||
}
|
||||
|
||||
// PluginAPILogger adapts the plugin.API to implement the Logger interface
|
||||
type PluginAPILogger struct {
|
||||
api plugin.API
|
||||
}
|
||||
|
||||
// NewPluginAPILogger creates a new PluginAPILogger
|
||||
func NewPluginAPILogger(api plugin.API) Logger {
|
||||
return &PluginAPILogger{api: api}
|
||||
}
|
||||
|
||||
// LogDebug logs a debug message
|
||||
func (l *PluginAPILogger) LogDebug(message string, keyValuePairs ...any) {
|
||||
l.api.LogDebug(message, keyValuePairs...)
|
||||
}
|
||||
|
||||
// LogInfo logs an info message
|
||||
func (l *PluginAPILogger) LogInfo(message string, keyValuePairs ...any) {
|
||||
l.api.LogInfo(message, keyValuePairs...)
|
||||
}
|
||||
|
||||
// LogWarn logs a warning message
|
||||
func (l *PluginAPILogger) LogWarn(message string, keyValuePairs ...any) {
|
||||
l.api.LogWarn(message, keyValuePairs...)
|
||||
}
|
||||
|
||||
// LogError logs an error message
|
||||
func (l *PluginAPILogger) LogError(message string, keyValuePairs ...any) {
|
||||
l.api.LogError(message, keyValuePairs...)
|
||||
}
|
79
server/store/kvstore/constants.go
Normal file
79
server/store/kvstore/constants.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
package kvstore
|
||||
|
||||
// KV Store key prefixes and constants
|
||||
// This file centralizes all KV store key patterns used throughout the plugin
|
||||
// to ensure consistency and avoid key conflicts.
|
||||
|
||||
const (
|
||||
// CurrentKVStoreVersion is the current version requiring migrations
|
||||
CurrentKVStoreVersion = 2
|
||||
// KeyPrefixMatrixUser is the prefix for Matrix user ID -> Mattermost user ID mappings
|
||||
KeyPrefixMatrixUser = "matrix_user_"
|
||||
// KeyPrefixMattermostUser is the prefix for Mattermost user ID -> Matrix user ID mappings
|
||||
KeyPrefixMattermostUser = "mattermost_user_"
|
||||
|
||||
// KeyPrefixChannelMapping is the prefix for Mattermost channel ID -> Matrix room mappings
|
||||
KeyPrefixChannelMapping = "channel_mapping_"
|
||||
// KeyPrefixRoomMapping is the prefix for Matrix room identifier -> Mattermost channel ID mappings
|
||||
KeyPrefixRoomMapping = "room_mapping_"
|
||||
|
||||
// KeyPrefixGhostUser is the prefix for Mattermost user ID -> Matrix ghost user ID cache
|
||||
KeyPrefixGhostUser = "ghost_user_"
|
||||
// KeyPrefixGhostRoom is the prefix for ghost user room membership tracking
|
||||
KeyPrefixGhostRoom = "ghost_room_"
|
||||
|
||||
// KeyPrefixMatrixEventPost is the prefix for Matrix event ID -> Mattermost post ID mappings
|
||||
KeyPrefixMatrixEventPost = "matrix_event_post_"
|
||||
// KeyPrefixMatrixReaction is the prefix for Matrix reaction event ID -> reaction info mappings
|
||||
KeyPrefixMatrixReaction = "matrix_reaction_"
|
||||
|
||||
// KeyStoreVersion is the key for tracking the current KV store schema version
|
||||
KeyStoreVersion = "kv_store_version"
|
||||
|
||||
// KeyPrefixLegacyDMMapping was the old prefix for DM mappings (migrated to channel_mapping_)
|
||||
KeyPrefixLegacyDMMapping = "dm_mapping_"
|
||||
// KeyPrefixLegacyMatrixDMMapping was the old prefix for Matrix DM mappings (migrated to room_mapping_)
|
||||
KeyPrefixLegacyMatrixDMMapping = "matrix_dm_mapping_"
|
||||
)
|
||||
|
||||
// Helper functions for building KV store keys
|
||||
|
||||
// BuildMatrixUserKey creates a key for Matrix user -> Mattermost user mapping
|
||||
func BuildMatrixUserKey(matrixUserID string) string {
|
||||
return KeyPrefixMatrixUser + matrixUserID
|
||||
}
|
||||
|
||||
// BuildMattermostUserKey creates a key for Mattermost user -> Matrix user mapping
|
||||
func BuildMattermostUserKey(mattermostUserID string) string {
|
||||
return KeyPrefixMattermostUser + mattermostUserID
|
||||
}
|
||||
|
||||
// BuildChannelMappingKey creates a key for channel -> room mapping
|
||||
func BuildChannelMappingKey(channelID string) string {
|
||||
return KeyPrefixChannelMapping + channelID
|
||||
}
|
||||
|
||||
// BuildRoomMappingKey creates a key for room -> channel mapping
|
||||
func BuildRoomMappingKey(roomIdentifier string) string {
|
||||
return KeyPrefixRoomMapping + roomIdentifier
|
||||
}
|
||||
|
||||
// BuildGhostUserKey creates a key for ghost user cache
|
||||
func BuildGhostUserKey(mattermostUserID string) string {
|
||||
return KeyPrefixGhostUser + mattermostUserID
|
||||
}
|
||||
|
||||
// BuildGhostRoomKey creates a key for ghost user room membership
|
||||
func BuildGhostRoomKey(mattermostUserID, roomID string) string {
|
||||
return KeyPrefixGhostRoom + mattermostUserID + "_" + roomID
|
||||
}
|
||||
|
||||
// BuildMatrixEventPostKey creates a key for Matrix event -> post mapping
|
||||
func BuildMatrixEventPostKey(matrixEventID string) string {
|
||||
return KeyPrefixMatrixEventPost + matrixEventID
|
||||
}
|
||||
|
||||
// BuildMatrixReactionKey creates a key for Matrix reaction storage
|
||||
func BuildMatrixReactionKey(reactionEventID string) string {
|
||||
return KeyPrefixMatrixReaction + reactionEventID
|
||||
}
|
|
@ -1,6 +1,13 @@
|
|||
// Package kvstore provides a key-value store interface for plugin data persistence.
|
||||
package kvstore
|
||||
|
||||
// KVStore provides an interface for key-value storage operations.
|
||||
type KVStore interface {
|
||||
// Define your methods here. This package is used to access the KVStore pluginapi methods.
|
||||
GetTemplateData(userID string) (string, error)
|
||||
Get(key string) ([]byte, error)
|
||||
Set(key string, value []byte) error
|
||||
Delete(key string) error
|
||||
ListKeys(page, perPage int) ([]string, error)
|
||||
ListKeysWithPrefix(page, perPage int, prefix string) ([]string, error)
|
||||
}
|
||||
|
|
|
@ -8,17 +8,19 @@ import (
|
|||
// We expose our calls to the KVStore pluginapi methods through this interface for testability and stability.
|
||||
// This allows us to better control which values are stored with which keys.
|
||||
|
||||
// Client wraps the Mattermost plugin API client for KV store operations.
|
||||
type Client struct {
|
||||
client *pluginapi.Client
|
||||
}
|
||||
|
||||
// NewKVStore creates a new KVStore implementation using the provided plugin API client.
|
||||
func NewKVStore(client *pluginapi.Client) KVStore {
|
||||
return Client{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// Sample method to get a key-value pair in the KV store
|
||||
// GetTemplateData retrieves template data for a specific user from the KV store.
|
||||
func (kv Client) GetTemplateData(userID string) (string, error) {
|
||||
var templateData string
|
||||
err := kv.client.KV.Get("template_key-"+userID, &templateData)
|
||||
|
@ -27,3 +29,49 @@ func (kv Client) GetTemplateData(userID string) (string, error) {
|
|||
}
|
||||
return templateData, nil
|
||||
}
|
||||
|
||||
// Get retrieves a value from the KV store by key.
|
||||
func (kv Client) Get(key string) ([]byte, error) {
|
||||
var data []byte
|
||||
err := kv.client.KV.Get(key, &data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get key from KV store")
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Set stores a key-value pair in the KV store.
|
||||
func (kv Client) Set(key string, value []byte) error {
|
||||
_, appErr := kv.client.KV.Set(key, value)
|
||||
if appErr != nil {
|
||||
return errors.Wrap(appErr, "failed to set key in KV store")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes a key-value pair from the KV store.
|
||||
func (kv Client) Delete(key string) error {
|
||||
appErr := kv.client.KV.Delete(key)
|
||||
if appErr != nil {
|
||||
return errors.Wrap(appErr, "failed to delete key from KV store")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListKeys retrieves a paginated list of keys from the KV store.
|
||||
func (kv Client) ListKeys(page, perPage int) ([]string, error) {
|
||||
keys, appErr := kv.client.KV.ListKeys(page, perPage)
|
||||
if appErr != nil {
|
||||
return nil, errors.Wrap(appErr, "failed to list keys from KV store")
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// ListKeysWithPrefix retrieves a paginated list of keys with a specific prefix from the KV store.
|
||||
func (kv Client) ListKeysWithPrefix(page, perPage int, prefix string) ([]string, error) {
|
||||
keys, appErr := kv.client.KV.ListKeys(page, perPage, pluginapi.WithPrefix(prefix))
|
||||
if appErr != nil {
|
||||
return nil, errors.Wrap(appErr, "failed to list keys with prefix from KV store")
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
|
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