Skip to content

feat(service): Add service layer for /mention-each command#93

Open
Devashish08 wants to merge 17 commits intoRealDevSquad:developfrom
Devashish08:mention-each-service
Open

feat(service): Add service layer for /mention-each command#93
Devashish08 wants to merge 17 commits intoRealDevSquad:developfrom
Devashish08:mention-each-service

Conversation

@Devashish08
Copy link
Contributor

@Devashish08 Devashish08 commented Apr 29, 2025

Date: 29 Feb 2025

Developer Name: @Devashish08


Issue Ticket Number

#70

Description

Notes for Reviewers:

This Pull Request implements the service layer for the /mention-each slash command. It handles the initial Discord interaction, performs validation, manages the feature flag check, prepares data for background processing, sends the job to the queue, and returns an immediate public acknowledgement to Discord.

This is the second PR in a stack for the mention-each feature:

Testing:

  • Unit tests for this service layer (covering validation, flag logic, response content, queue mocking) will be added in a separate, subsequent PR ([Link to placeholder/issue if available, otherwise state "link to follow"]).

Documentation Updated?

  • Yes
  • No

Under Feature Flag

  • Yes
  • No

Database Changes

  • Yes
  • No

Breaking Changes

  • Yes
  • No

Development Tested?

  • Yes
  • No

Screenshots

Screenshot 1

Test Coverage

Screenshot 1

Screenshot 2025-04-29 060935

Additional Notes

  • This PR implements the /mention-each feature flag as a command option (ff_enabled) as discussed.
  • The background handler logic is not included here and will be in the next PR in the stack.

@coderabbitai
Copy link

coderabbitai bot commented Apr 29, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a new Discord command to mention all users with a specific role, supporting options for custom messages and display formats.
  • Bug Fixes

    • Corrected typos in method names to ensure proper functionality.
  • Tests

    • Added comprehensive unit tests for member role utilities and new command features.
    • Introduced mock implementations for Discord session interactions to enhance testing reliability.
  • Chores

    • Updated dependencies and improved code consistency and formatting.

Walkthrough

This update introduces a new Discord slash command, "MentionEach", which allows mentioning all users with a specified role, with several options for message customization and feature toggling. The implementation includes the addition of the command declaration, its service handler, and supporting utilities for querying and formatting user mentions. The Discord session interface and its mock/test implementations are extended to support the necessary API calls. Comprehensive unit tests and a new mock for Discord sessions are also included. Minor refactoring, typo corrections, and import order adjustments are made for consistency and correctness.

Changes

File(s) Change Summary
commands/main.go, dtos/commands.go, utils/constants.go Added "MentionEach" command with options to command declarations, command name types, and constants.
service/mentionEachService.go Implemented MentionEachService handler to process the new command, validate input, serialize metadata, and enqueue processing.
service/main.go Added dispatch case for "MentionEach" command to route to the new service handler.
utils/membersUtils.go, utils/membersUtils_test.go Added utility functions and unit tests for querying users by role, formatting mentions, and response strings.
models/discord.go, models/discord_test.go Added GuildMembers and ChannelMessageSend methods to the Discord session interface and wrapper; updated tests for new and renamed methods.
commands/main/register.go, commands/register/register.go Fixed typo in method call: GetUerId()GetUserId().
commands/main/resgister_test.go, commands/register/register_test.go Updated mocks for session interface: added methods and fields to support new API calls; corrected method names.
tests/mocks/discordSessionMock.go Introduced a new testify-based mock for the Discord session interface, supporting all relevant methods for testing.
go.mod Added indirect dependency: github.com/stretchr/objx v0.5.2.
config/config.go Reordered import statements; no functional changes.

Sequence Diagram(s)

sequenceDiagram
    participant DiscordUser
    participant DiscordAPI
    participant MainService
    participant CommandService
    participant Utils
    participant Queue

    DiscordUser->>DiscordAPI: Issues /mention-each command
    DiscordAPI->>MainService: Forwards interaction event
    MainService->>CommandService: Calls MentionEachService
    CommandService->>CommandService: Validates and parses options
    CommandService->>Utils: Finds users with specified role
    Utils-->>CommandService: Returns user list / mentions
    CommandService->>Queue: Enqueues DataPacket for async processing
    Queue-->>CommandService: Confirms enqueue
    CommandService->>DiscordAPI: Responds with confirmation or error
    DiscordAPI-->>DiscordUser: Shows result message
Loading

Suggested reviewers

  • pankajjs
  • iamitprakash

Poem

🐇
A hop, a skip, a mention each,
Now every bunny’s within reach!
With roles and flags and messages too,
The code now knows just what to do.
From queues to mocks, tests run with cheer—
“Hello, new feature! Glad you’re here!”
🥕

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🔭 Outside diff range comments (1)
commands/main/resgister_test.go (1)

98-104: ⚠️ Potential issue

Remove duplicate test case.

This test case is identical to the one at lines 91-97. Both have the same name and are testing the same condition.

-	t.Run("should panic when openSession.ApplicationCommandCreate() returns an error", func(t *testing.T) {
-		mockSess := &mockSession{openError: nil, commandError: assert.AnError}
-
-		assert.Panics(t, func() {
-			RegisterCommands(mockSess)
-		}, "RegisterCommands should panic when ApplicationCommandCreate returns an error")
-	})
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ecf9b55 and 1abd78f.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (16)
  • commands/main.go (1 hunks)
  • commands/main/register.go (1 hunks)
  • commands/main/resgister_test.go (2 hunks)
  • commands/register/register.go (1 hunks)
  • commands/register/register_test.go (2 hunks)
  • config/config.go (1 hunks)
  • dtos/commands.go (1 hunks)
  • go.mod (1 hunks)
  • models/discord.go (1 hunks)
  • models/discord_test.go (2 hunks)
  • service/main.go (1 hunks)
  • service/mentionEachService.go (1 hunks)
  • tests/mocks/discordSessionMock.go (1 hunks)
  • utils/constants.go (1 hunks)
  • utils/membersUtils.go (1 hunks)
  • utils/membersUtils_test.go (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
commands/main/resgister_test.go (1)
Learnt from: Devashish08
PR: Real-Dev-Squad/discord-service#80
File: utils/members_utils_test.go:28-37
Timestamp: 2025-04-11T16:51:24.677Z
Learning: The `ChannelMessageSend` mock implementation in `utils/members_utils_test.go` is necessary because it's part of the `DiscordSessionInterface` interface in the Discord service. In Go, all methods of an interface must be implemented for a mock to properly satisfy the interface, even if some methods aren't directly used in the current tests.
tests/mocks/discordSessionMock.go (1)
Learnt from: Devashish08
PR: Real-Dev-Squad/discord-service#80
File: utils/members_utils_test.go:28-37
Timestamp: 2025-04-11T16:51:24.677Z
Learning: The `ChannelMessageSend` mock implementation in `utils/members_utils_test.go` is necessary because it's part of the `DiscordSessionInterface` interface in the Discord service. In Go, all methods of an interface must be implemented for a mock to properly satisfy the interface, even if some methods aren't directly used in the current tests.
commands/register/register_test.go (1)
Learnt from: Devashish08
PR: Real-Dev-Squad/discord-service#80
File: utils/members_utils_test.go:28-37
Timestamp: 2025-04-11T16:51:24.677Z
Learning: The `ChannelMessageSend` mock implementation in `utils/members_utils_test.go` is necessary because it's part of the `DiscordSessionInterface` interface in the Discord service. In Go, all methods of an interface must be implemented for a mock to properly satisfy the interface, even if some methods aren't directly used in the current tests.
🧬 Code Graph Analysis (5)
service/main.go (2)
utils/constants.go (1)
  • CommandNames (11-16)
commands/handlers/main.go (1)
  • CS (18-18)
utils/membersUtils_test.go (3)
tests/mocks/discordSessionMock.go (1)
  • DiscordSession (12-14)
utils/constants.go (1)
  • DISCORD_GUILD_MEMBER_API_LIMIT (8-8)
utils/membersUtils.go (4)
  • GetUsersWithRole (12-55)
  • FormatUserMentions (57-67)
  • FormatMentionResponse (69-77)
  • FormatUserListResponse (79-92)
utils/membersUtils.go (2)
models/discord.go (1)
  • SessionInterface (33-40)
utils/constants.go (1)
  • DISCORD_GUILD_MEMBER_API_LIMIT (8-8)
utils/constants.go (1)
dtos/commands.go (1)
  • CommandNameTypes (3-8)
commands/main.go (1)
utils/constants.go (1)
  • CommandNames (11-16)
🔇 Additional comments (30)
config/config.go (1)

4-8: Import reorder is fine.

The os package import has been moved within the block without any functional impact.

dtos/commands.go (1)

7-7: Looking good - addition of MentionEach command constant.

The new MentionEach field in the CommandNameTypes struct aligns with the PR objective of adding support for the /mention-each command. This field will be used to reference the command across the codebase consistently.

go.mod (1)

19-19: Addition of stretchr/objx dependency looks appropriate.

The indirect dependency on github.com/stretchr/objx v0.5.2 is likely pulled in to support mock objects for testing the new functionality. This is a common dependency used with the testify framework for creating mocks.

commands/register/register.go (1)

35-35: Good catch - fixed typo in method name.

Corrected the method name from GetUerId() to GetUserId(), ensuring consistent method naming throughout the codebase. This improves code quality and avoids potential runtime errors.

commands/main/register.go (1)

33-33: Good catch - fixed method name here as well.

Corrected the method name from GetUerId() to GetUserId() for consistency with the rest of the codebase, matching the fix made in commands/register/register.go. This ensures proper functioning of command registration.

service/main.go (1)

25-26: Addition of MentionEach handler looks correct!

The integration of the new /mention-each command follows the established pattern for command routing. This cleanly connects the command defined in commands/main.go to its handler implementation.

utils/constants.go (2)

5-9: Good constant organization

Consolidating related constants into a single block improves code organization and readability. The DISCORD_GUILD_MEMBER_API_LIMIT of 1000 appears to align with Discord's API pagination limits.


11-16: Command name addition looks correct

The addition of the MentionEach field to the CommandNames map properly aligns with the command structure defined in commands/main.go and matches the DTO structure in dtos/commands.go.

models/discord_test.go (3)

10-11: Good refactoring to local constant

Replacing the external package dependency with a local constant reduces coupling and makes the test more self-contained.


13-14: Correctly updated command reference

Updated the command reference to use the local constant.


44-54: Good test coverage for new methods

The tests for GuildMembers() and ChannelMessageSend() follow the established pattern and ensure that these methods panic when called on the dummy session wrapper, maintaining consistency with the other method tests.

commands/main.go (1)

29-68: Well-structured command definition with clear documentation

The new MentionEach command is well-defined with appropriate options and descriptive comments. The inclusion of a feature flag (ff_enabled) is a good practice for controlling access to new features.

A few observations:

  1. The role option is properly set as required
  2. The message option is correctly set as optional string
  3. The dev flags have clear descriptions
  4. The feature flag implementation allows for controlled rollout

This command structure aligns well with the PR objective of implementing the service layer for the /mention-each command.

commands/register/register_test.go (3)

40-43: LGTM: Additional mock tracking fields added for new session methods.

The new fields added to the mockSession struct properly track calls to the ChannelMessageSend and GuildMembers methods and their error returns. This aligns with the other tracking fields in the mock.


62-65: Typo correction in method name.

The method was renamed from GetUerId to GetUserId, fixing a typo in the method name. This improves consistency across the codebase.


67-74: LGTM: Mock implementations for new session interface methods.

These new methods properly implement the GuildMembers and ChannelMessageSend methods required by the SessionInterface. The implementation follows the existing pattern of setting the tracking flag and returning the configured error.

commands/main/resgister_test.go (3)

40-43: LGTM: Additional mock tracking fields added.

The new fields properly track calls to ChannelMessageSend and GuildMembers methods and their error returns, consistent with other tracking fields in the mock.


62-65: Typo correction in method name.

The method was renamed from GetUerId to GetUserId, fixing a typo and improving consistency across the codebase.


67-75: LGTM: Mock implementations for new session interface methods.

These implementations properly satisfy the SessionInterface requirements, allowing for testing of code that depends on these methods.

models/discord.go (3)

21-23: Typo correction in method name.

The method was renamed from GetUerId to GetUserId, fixing a typo and improving consistency across the codebase.


25-31: LGTM: New session wrapper methods.

These methods properly wrap the underlying Discord session methods for retrieving guild members and sending channel messages, which are needed for the new /mention-each command functionality.


37-39: LGTM: Updated interface with new method signatures.

The SessionInterface has been updated with the corrected method name and new methods required for the /mention-each command functionality.

tests/mocks/discordSessionMock.go (1)

1-70: LGTM: Comprehensive Discord session mock implementation.

This new mock implementation using the testify/mock package provides a more sophisticated approach to mocking the Discord session for testing. Key strengths:

  1. Uses the established mock pattern from testify
  2. Properly records method calls and arguments
  3. Includes type assertions with descriptive error messages
  4. Implements all required methods of the SessionInterface

This will make testing Discord session interactions more robust, especially for the new /mention-each command functionality.

service/mentionEachService.go (2)

145-154: LGTM! Error response handling is well-implemented.

The error response function correctly uses ephemeral messages to show errors only to the user who triggered the command.


52-58: ⚠️ Potential issue

Fix undeclared variable 'ok'

There's a reference to an undeclared variable "ok" on line 52. You need to declare this variable before using it.

-	roleID, ok := roleOption.Value.(string)
+	var ok bool
+	roleID, ok = roleOption.Value.(string)

Likely an incorrect or invalid review comment.

utils/membersUtils_test.go (3)

106-144: Excellent pagination testing

The pagination test is comprehensive and well-structured, covering multiple pages of members and ensuring the correct behavior when reaching the end of available members.


164-167: LGTM! Good edge case handling

Testing with nil member lists is important as it could happen in production and lead to bugs if not handled properly.


168-177: LGTM! Thorough null checking

The test for handling members with nil User is important as it verifies the robustness of the utility function.

utils/membersUtils.go (3)

57-67: LGTM! Robust handling of nil User references

The function correctly checks for nil User references and provides appropriate logging, which is important for robustness in production.


79-92: LGTM! Clean implementation with good user experience

The function elegantly handles different cases with specific, user-friendly messages for each scenario.


32-48: ⚠️ Potential issue

Potential nil pointer in lastIdInChunk assignment

There's a potential nil pointer dereference when assigning lastIdInChunk. If a member with a nil User reference is the last member in the chunk, line 47 will panic.

for _, member := range membersChunk {
    if member.User == nil {
        logrus.Warnf("Guild %s: Member object or User data is nil, skipping member: %+v", guildID, member)
        continue
    }
    for _, r := range member.Roles {
        if r == roleID {
            membersWithRole = append(membersWithRole, *member)
            foundInChunk++
            break
        }
    }

-   lastIdInChunk = member.User.ID
+   // We've already checked member.User != nil above
+   lastIdInChunk = member.User.ID
}

To fix this issue, make sure the code correctly handles nil User references throughout the chunk processing.

Likely an incorrect or invalid review comment.

Comment on lines +41 to 42
sessionWrapper.GetUserId()
}, "should panic when getUerId() is called")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Method name typo fixed

The method name has been corrected from GetUerId() to GetUserId(), though the test description in line 39 and error message in line 42 still refer to "getUerId". Consider updating these for consistency.

-	t.Run("SessionWrapper should always implement getUerId() method", func(t *testing.T) {
+	t.Run("SessionWrapper should always implement GetUserId() method", func(t *testing.T) {
		assert.Panics(t, func() {
			sessionWrapper.GetUserId()
-		}, "should panic when getUerId() is called")
+		}, "should panic when GetUserId() is called")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sessionWrapper.GetUserId()
}, "should panic when getUerId() is called")
t.Run("SessionWrapper should always implement GetUserId() method", func(t *testing.T) {
assert.Panics(t, func() {
sessionWrapper.GetUserId()
}, "should panic when GetUserId() is called")

Comment on lines +116 to +121
if err := queue.SendMessage(bytePacket); err != nil {
errorMsg := "Failed to process your request"
logrus.Errorf("Failed to enqueue message: %v", err)
sendErrorResponse(response, errorMsg)
return
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider enhancing error message for queue failure

The current error message for queue failure is generic. Consider adding more context to help users understand what happened.

 	if err := queue.SendMessage(bytePacket); err != nil {
-		errorMsg := "Failed to process your request"
+		errorMsg := "Failed to queue your request for processing. Please try again later."
 		logrus.Errorf("Failed to enqueue message: %v", err)
 		sendErrorResponse(response, errorMsg)
 		return
 	}

Comment on lines +14 to +39
func (s *CommandService) MentionEachService(response http.ResponseWriter, request *http.Request) {
logrus.Info("Mention-each command received")

if s.discordMessage == nil || s.discordMessage.Data == nil ||
s.discordMessage.Member == nil || s.discordMessage.Member.User == nil {
errorMsg := "Invalid request data"
logrus.Error(errorMsg)
sendErrorResponse(response, errorMsg)
return
}
var featureEnabled bool
ffOption := findOption(s.discordMessage.Data.Options, "ff_enabled")
if ffOption != nil && ffOption.Value != nil {
var ok bool
featureEnabled, ok = ffOption.Value.(bool)
if !ok {
logrus.Warnf("Invalid non-boolean value provided for 'ff_enabled' flag: Type=%T, Value=%v. Treating as disabled.", ffOption.Value, ffOption.Value)
featureEnabled = false
}
}

if !featureEnabled {
logrus.Warnf("Mention-each command run without 'ff_enabled:true'. User: %s", s.discordMessage.Member.User.ID)
sendErrorResponse(response, "Sorry, the /mention-each command requires the `ff_enabled:True` option to run currently.")
return
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider adding panic recovery

As a critical service function handling Discord interactions, it would be beneficial to add panic recovery to prevent unexpected crashes.

 func (s *CommandService) MentionEachService(response http.ResponseWriter, request *http.Request) {
+	defer func() {
+		if r := recover(); r != nil {
+			logrus.Errorf("Recovered from panic in MentionEachService: %v", r)
+			sendErrorResponse(response, "An unexpected error occurred")
+		}
+	}()
+
 	logrus.Info("Mention-each command received")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (s *CommandService) MentionEachService(response http.ResponseWriter, request *http.Request) {
logrus.Info("Mention-each command received")
if s.discordMessage == nil || s.discordMessage.Data == nil ||
s.discordMessage.Member == nil || s.discordMessage.Member.User == nil {
errorMsg := "Invalid request data"
logrus.Error(errorMsg)
sendErrorResponse(response, errorMsg)
return
}
var featureEnabled bool
ffOption := findOption(s.discordMessage.Data.Options, "ff_enabled")
if ffOption != nil && ffOption.Value != nil {
var ok bool
featureEnabled, ok = ffOption.Value.(bool)
if !ok {
logrus.Warnf("Invalid non-boolean value provided for 'ff_enabled' flag: Type=%T, Value=%v. Treating as disabled.", ffOption.Value, ffOption.Value)
featureEnabled = false
}
}
if !featureEnabled {
logrus.Warnf("Mention-each command run without 'ff_enabled:true'. User: %s", s.discordMessage.Member.User.ID)
sendErrorResponse(response, "Sorry, the /mention-each command requires the `ff_enabled:True` option to run currently.")
return
}
func (s *CommandService) MentionEachService(response http.ResponseWriter, request *http.Request) {
defer func() {
if r := recover(); r != nil {
logrus.Errorf("Recovered from panic in MentionEachService: %v", r)
sendErrorResponse(response, "An unexpected error occurred")
}
}()
logrus.Info("Mention-each command received")
if s.discordMessage == nil || s.discordMessage.Data == nil ||
s.discordMessage.Member == nil || s.discordMessage.Member.User == nil {
errorMsg := "Invalid request data"
logrus.Error(errorMsg)
sendErrorResponse(response, errorMsg)
return
}
var featureEnabled bool
ffOption := findOption(s.discordMessage.Data.Options, "ff_enabled")
if ffOption != nil && ffOption.Value != nil {
var ok bool
featureEnabled, ok = ffOption.Value.(bool)
if !ok {
logrus.Warnf("Invalid non-boolean value provided for 'ff_enabled' flag: Type=%T, Value=%v. Treating as disabled.", ffOption.Value, ffOption.Value)
featureEnabled = false
}
}
if !featureEnabled {
logrus.Warnf("Mention-each command run without 'ff_enabled:true'. User: %s", s.discordMessage.Member.User.ID)
sendErrorResponse(response, "Sorry, the /mention-each command requires the `ff_enabled:True` option to run currently.")
return
}
// ... rest of implementation ...
}

Comment on lines +84 to +91
var responseContent string
if devTitle {
responseContent = fmt.Sprintf("Fetching users with the <@&%s>", roleID)
} else if dev {
responseContent = fmt.Sprintf("Sending individual mentions to users with the <@&%s>", roleID)
} else {
responseContent = fmt.Sprintf("Mentioning all users with the <@&%s>", roleID)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Extract response content creation into a separate function

The response content creation logic could be extracted to improve readability and maintainability.

+func constructResponseContent(roleID string, devTitle bool, dev bool) string {
+	if devTitle {
+		return fmt.Sprintf("Fetching users with the <@&%s>", roleID)
+	} else if dev {
+		return fmt.Sprintf("Sending individual mentions to users with the <@&%s>", roleID)
+	} else {
+		return fmt.Sprintf("Mentioning all users with the <@&%s>", roleID)
+	}
+}
+
 func (s *CommandService) MentionEachService(response http.ResponseWriter, request *http.Request) {
     // ... existing code ...
     
-	var responseContent string
-	if devTitle {
-		responseContent = fmt.Sprintf("Fetching users with the <@&%s>", roleID)
-	} else if dev {
-		responseContent = fmt.Sprintf("Sending individual mentions to users with the <@&%s>", roleID)
-	} else {
-		responseContent = fmt.Sprintf("Mentioning all users with the <@&%s>", roleID)
-	}
+	responseContent := constructResponseContent(roleID, devTitle, dev)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var responseContent string
if devTitle {
responseContent = fmt.Sprintf("Fetching users with the <@&%s>", roleID)
} else if dev {
responseContent = fmt.Sprintf("Sending individual mentions to users with the <@&%s>", roleID)
} else {
responseContent = fmt.Sprintf("Mentioning all users with the <@&%s>", roleID)
}
// service/mentionEachService.go
// extract the response‐content logic into its own function
func constructResponseContent(roleID string, devTitle bool, dev bool) string {
if devTitle {
return fmt.Sprintf("Fetching users with the <@&%s>", roleID)
} else if dev {
return fmt.Sprintf("Sending individual mentions to users with the <@&%s>", roleID)
} else {
return fmt.Sprintf("Mentioning all users with the <@&%s>", roleID)
}
}
func (s *CommandService) MentionEachService(response http.ResponseWriter, request *http.Request) {
// ... existing code ...
// replace the inline if/else block with a call into our new helper
responseContent := constructResponseContent(roleID, devTitle, dev)
// ... remainder of the method ...
}

Comment on lines +222 to +236

t.Run("handles nil mentions", func(t *testing.T) {
response := FormatUserListResponse(nil, roleID)
expected := fmt.Sprintf("Found 0 users with the %s role", roleMention)
assert.Equal(t, expected, response)
})

t.Run("handles empty role ID", func(t *testing.T) {
mentions := []string{"<@123>"}
emptyRoleMention := "<@&>"
response := FormatUserListResponse([]string{"<@123>"}, "")
expected := fmt.Sprintf("Found 1 user with the %s role: %s", emptyRoleMention, mentions[0])
assert.Equal(t, expected, response)
})
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider adding tests for large member lists

The current tests cover basic cases well, but consider adding tests for large member lists to ensure performance and formatting remain correct with many users.

t.Run("formats response with many users (performance test)", func(t *testing.T) {
    // Create a slice with many mentions (e.g., 100+)
    mentions := make([]string, 150)
    for i := range mentions {
        mentions[i] = fmt.Sprintf("<@user%d>", i)
    }
    
    response := FormatUserListResponse(mentions, roleID)
    
    // Check that the first part of the response is correct
    expected := fmt.Sprintf("Found %d users with the %s role:", len(mentions), roleMention)
    assert.Contains(t, response, expected)
    
    // Check that the response contains the first and last mention
    assert.Contains(t, response, mentions[0])
    assert.Contains(t, response, mentions[len(mentions)-1])
})

Comment on lines +69 to +77
func FormatMentionResponse(mentions []string, message string) string {
mentionStrings := strings.Join(mentions, " ")
if message == "" {
return mentionStrings
}

return fmt.Sprintf("%s %s", message, mentionStrings)

}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider handling case when mentions array is empty

The current implementation doesn't specifically handle the case when the mentions array is empty. While it works correctly (returning an empty string or just the message), explicit handling would improve readability.

func FormatMentionResponse(mentions []string, message string) string {
+	if len(mentions) == 0 {
+		return message
+	}
+
	mentionStrings := strings.Join(mentions, " ")
	if message == "" {
		return mentionStrings
	}

	return fmt.Sprintf("%s %s", message, mentionStrings)
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func FormatMentionResponse(mentions []string, message string) string {
mentionStrings := strings.Join(mentions, " ")
if message == "" {
return mentionStrings
}
return fmt.Sprintf("%s %s", message, mentionStrings)
}
func FormatMentionResponse(mentions []string, message string) string {
if len(mentions) == 0 {
return message
}
mentionStrings := strings.Join(mentions, " ")
if message == "" {
return mentionStrings
}
return fmt.Sprintf("%s %s", message, mentionStrings)
}

Comment on lines +49 to +55
lastMemberID = lastIdInChunk
logrus.Debugf("Found %d members with role %s in this chunk (Guild %s)", foundInChunk, roleID, guildID)
}

logrus.Infof("Finished fetching. Found %d total members with role %s in guild %s", len(membersWithRole), roleID, guildID)
return membersWithRole, nil
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add safety check for empty lastIdInChunk

If all members in a chunk have nil User fields, lastIdInChunk would remain empty, which could lead to fetching the same members repeatedly.

-		lastMemberID = lastIdInChunk
+		if lastIdInChunk == "" {
+			logrus.Warnf("No valid user IDs found in chunk for guild %s, breaking pagination loop", guildID)
+			break
+		}
+		lastMemberID = lastIdInChunk
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
lastMemberID = lastIdInChunk
logrus.Debugf("Found %d members with role %s in this chunk (Guild %s)", foundInChunk, roleID, guildID)
}
logrus.Infof("Finished fetching. Found %d total members with role %s in guild %s", len(membersWithRole), roleID, guildID)
return membersWithRole, nil
}
if lastIdInChunk == "" {
logrus.Warnf("No valid user IDs found in chunk for guild %s, breaking pagination loop", guildID)
break
}
lastMemberID = lastIdInChunk
logrus.Debugf("Found %d members with role %s in this chunk (Guild %s)", foundInChunk, roleID, guildID)
}
logrus.Infof("Finished fetching. Found %d total members with role %s in guild %s", len(membersWithRole), roleID, guildID)
return membersWithRole, nil
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant