package help import ( "strings" "testing" "git.nakama.town/fmartingr/butterrobot/internal/db" "git.nakama.town/fmartingr/butterrobot/internal/model" "git.nakama.town/fmartingr/butterrobot/internal/plugin" ) // MockPlugin implements the Plugin interface for testing type MockPlugin struct { id string name string help string } func (m *MockPlugin) GetID() string { return m.id } func (m *MockPlugin) GetName() string { return m.name } func (m *MockPlugin) GetHelp() string { return m.help } func (m *MockPlugin) RequiresConfig() bool { return false } func (m *MockPlugin) OnMessage(msg *model.Message, config map[string]interface{}, cache model.CacheInterface) []*model.MessageAction { return nil } // MockDatabase implements the ChannelPluginGetter interface for testing type MockDatabase struct { channelPlugins map[int64][]*model.ChannelPlugin platformChannelPlugins map[string][]*model.ChannelPlugin // key: "platform:platformChannelID" } func (m *MockDatabase) GetChannelPlugins(channelID int64) ([]*model.ChannelPlugin, error) { if plugins, exists := m.channelPlugins[channelID]; exists { return plugins, nil } return nil, db.ErrNotFound } func (m *MockDatabase) GetChannelPluginsFromPlatformID(platform, platformChannelID string) ([]*model.ChannelPlugin, error) { key := platform + ":" + platformChannelID if plugins, exists := m.platformChannelPlugins[key]; exists { return plugins, nil } return nil, db.ErrNotFound } func TestHelpPlugin_OnMessage(t *testing.T) { tests := []struct { name string messageText string enabledPlugins map[string]*MockPlugin expectResponse bool expectNoPlugins bool expectCategories []string }{ { name: "responds to !help command", messageText: "!help", enabledPlugins: map[string]*MockPlugin{ "dev.ping": { id: "dev.ping", name: "Ping", help: "Responds to 'ping' with 'pong'", }, "fun.dice": { id: "fun.dice", name: "Dice Roller", help: "Rolls dice when you type '!dice [formula]'", }, }, expectResponse: true, expectCategories: []string{"Development", "Fun and Entertainment"}, }, { name: "ignores non-help messages", messageText: "hello world", enabledPlugins: map[string]*MockPlugin{}, expectResponse: false, }, { name: "ignores case variation", messageText: "!HELP", enabledPlugins: map[string]*MockPlugin{}, expectResponse: true, expectNoPlugins: true, }, { name: "handles no enabled plugins", messageText: "!help", enabledPlugins: map[string]*MockPlugin{}, expectResponse: true, expectNoPlugins: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create mock database mockDB := &MockDatabase{ channelPlugins: make(map[int64][]*model.ChannelPlugin), platformChannelPlugins: make(map[string][]*model.ChannelPlugin), } // Setup channel plugins in mock database var channelPluginList []*model.ChannelPlugin pluginCounter := int64(1) for pluginID := range tt.enabledPlugins { channelPluginList = append(channelPluginList, &model.ChannelPlugin{ ID: pluginCounter, ChannelID: 1, PluginID: pluginID, Enabled: true, Config: make(map[string]interface{}), }) pluginCounter++ } // Set up both mapping approaches for the test mockDB.channelPlugins[1] = channelPluginList mockDB.platformChannelPlugins["test:test-channel"] = channelPluginList // Create help plugin p := New(mockDB) // Create mock channel channel := &model.Channel{ ID: 1, Platform: "test", PlatformChannelID: "test-channel", } // Create test message msg := &model.Message{ ID: "test-msg", Text: tt.messageText, Chat: "test-chat", Channel: channel, } // Mock the plugin registry originalRegistry := plugin.GetAvailablePlugins() // Override the registry for this test plugin.ClearRegistry() for _, mockPlugin := range tt.enabledPlugins { plugin.Register(mockPlugin) } // Call OnMessage actions := p.OnMessage(msg, map[string]interface{}{}, nil) // Restore original registry plugin.ClearRegistry() for _, p := range originalRegistry { plugin.Register(p) } if !tt.expectResponse { if len(actions) != 0 { t.Errorf("Expected no response, but got %d actions", len(actions)) } return } if len(actions) != 1 { t.Errorf("Expected 1 action, got %d", len(actions)) return } action := actions[0] if action.Type != model.ActionSendMessage { t.Errorf("Expected ActionSendMessage, got %v", action.Type) return } responseText := action.Message.Text if tt.expectNoPlugins { if !strings.Contains(responseText, "No plugins are currently enabled") { t.Errorf("Expected 'no plugins' message, got: %s", responseText) } return } // Check that expected categories appear in response for _, category := range tt.expectCategories { if !strings.Contains(responseText, "**"+category+":**") { t.Errorf("Expected category '%s' in response, got: %s", category, responseText) } } // Check that plugin names and help text appear for _, mockPlugin := range tt.enabledPlugins { if !strings.Contains(responseText, mockPlugin.GetName()) { t.Errorf("Expected plugin name '%s' in response", mockPlugin.GetName()) } if !strings.Contains(responseText, mockPlugin.GetHelp()) { t.Errorf("Expected plugin help '%s' in response", mockPlugin.GetHelp()) } } }) } }