Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions Bottomly.Tests/Slack/EventHandlers/ImageSearchHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Shouldly;
using SlackNet.Blocks;
using SlackNet.Events;

namespace Bottomly.Tests.Slack.EventHandlers;
Expand Down Expand Up @@ -52,14 +53,26 @@ public async Task HandleAsync_ValidEvent_CallsCommandWithQuery()
}

[Fact]
public async Task HandleAsync_ValidEvent_WithResult_SendsFormattedResponse()
public async Task HandleAsync_ValidEvent_WithResult_SendsImageBlock()
{
_mockCommand.Setup(c => c.ExecuteAsync("cats"))
.ReturnsAsync(new SearchResult("Cute Cat", "https://example.com/cat.jpg"));

IReadOnlyList<Block>? capturedBlocks = null;
_mockBroker
.Setup(b => b.SendBlocksMessageAsync(It.IsAny<IReadOnlyList<Block>>(), It.IsAny<string>(), It.IsAny<string?>(), It.IsAny<string?>()))
.Callback<IReadOnlyList<Block>, string, string?, string?>((blocks, _, _, _) => capturedBlocks = blocks)
.Returns(Task.CompletedTask);

await _handler.HandleAsync(CreateMessage("_gi cats"));

_mockBroker.Verify(b => b.SendMessageAsync("Cute Cat https://example.com/cat.jpg", "C1", null), Times.Once());
_mockBroker.Verify(b => b.SendBlocksMessageAsync(
It.IsAny<IReadOnlyList<Block>>(), "C1", "Cute Cat", null), Times.Once());
capturedBlocks.ShouldNotBeNull();
capturedBlocks.Count.ShouldBe(1);
var imageBlock = capturedBlocks[0].ShouldBeOfType<ImageBlock>();
imageBlock.ImageUrl.ShouldBe("https://example.com/cat.jpg");
imageBlock.AltText.ShouldBe("Cute Cat");
}

[Fact]
Expand Down
2 changes: 1 addition & 1 deletion Bottomly.Tests/Slack/EventHandlers/SearchHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public async Task HandleAsync_ValidEvent_WithResult_SendsFormattedResponse()

await _handler.HandleAsync(CreateMessage("_g dotnet"));

_mockBroker.Verify(b => b.SendMessageAsync("DotNet https://dotnet.microsoft.com", "C1", null), Times.Once());
_mockBroker.Verify(b => b.SendMessageAsync("<https://dotnet.microsoft.com|DotNet>", "C1", null), Times.Once());
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public async Task HandleAsync_ValidEvent_WithResult_SendsFormattedResponse()

await _handler.HandleAsync(CreateMessage("_wik octopus"));

_mockBroker.Verify(b => b.SendMessageAsync("Octopus https://en.wikipedia.org/wiki/Octopus", "C1", null),
_mockBroker.Verify(b => b.SendMessageAsync("<https://en.wikipedia.org/wiki/Octopus|Octopus>", "C1", null),
Times.Once());
}

Expand Down
40 changes: 40 additions & 0 deletions Bottomly.Tests/Slack/SlackMessageBrokerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.Extensions.Options;
using Moq;
using SlackNet;
using SlackNet.Blocks;
using SlackNet.WebApi;

namespace Bottomly.Tests.Slack;
Expand Down Expand Up @@ -76,6 +77,45 @@ public async Task SendMessageAsync_DebugMode_PrependsPrefixToText()
m.Text!.StartsWith("[Dev]"))), Times.Once());
}

[Fact]
public async Task SendBlocksMessageAsync_PostsBlocksToChannel()
{
var broker = CreateBroker();
var blocks = new List<Block> { new ImageBlock { ImageUrl = "https://example.com/img.jpg", AltText = "test" } };

await broker.SendBlocksMessageAsync(blocks, "C1", "fallback text");

_mockChat.Verify(c => c.PostMessage(It.Is<Message>(m =>
m.Channel == "C1" &&
m.Text == "fallback text" &&
m.Blocks.Count == 1 &&
m.Blocks[0] is ImageBlock)), Times.Once());
}

[Fact]
public async Task SendBlocksMessageAsync_WithReplyTs_SetsThreadTs()
{
var broker = CreateBroker();
var blocks = new List<Block> { new ImageBlock { ImageUrl = "https://example.com/img.jpg", AltText = "test" } };

await broker.SendBlocksMessageAsync(blocks, "C1", replyToTs: "ts123");

_mockChat.Verify(c => c.PostMessage(It.Is<Message>(m =>
m.ThreadTs == "ts123")), Times.Once());
}

[Fact]
public async Task SendBlocksMessageAsync_DebugMode_PrependsPrefixToText()
{
var broker = CreateBroker(environment: "Dev");
var blocks = new List<Block> { new ImageBlock { ImageUrl = "https://example.com/img.jpg", AltText = "test" } };

await broker.SendBlocksMessageAsync(blocks, "C1", "fallback");

_mockChat.Verify(c => c.PostMessage(It.Is<Message>(m =>
m.Text!.StartsWith("[Dev]"))), Times.Once());
}

[Fact]
public async Task SendReactionAsync_CallsSlackReactions()
{
Expand Down
3 changes: 3 additions & 0 deletions Bottomly/Slack/ISlackMessageBroker.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using SlackNet.Blocks;

namespace Bottomly.Slack;

public interface ISlackMessageBroker
{
Task SendMessageAsync(string text, string channel, string? replyToTs = null);
Task SendBlocksMessageAsync(IReadOnlyList<Block> blocks, string channel, string? text = null, string? replyToTs = null);
Task SendReactionAsync(string emoji, string channel, string timestamp);
Task SendDmAsync(string text, string username);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Bottomly.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SlackNet.Blocks;
using SlackNet.Events;

namespace Bottomly.Slack.MessageEventHandlers;
Expand Down Expand Up @@ -73,6 +74,12 @@ protected async Task SendMessageResponseAsync(string text, MessageEvent message,
await Broker.SendMessageAsync(text, message.Channel, replyTs);
}

protected async Task SendBlocksResponseAsync(IReadOnlyList<Block> blocks, MessageEvent message, string? text = null, bool asReply = false)
{
var replyTs = asReply ? message.TsForReply() : null;
await Broker.SendBlocksMessageAsync(blocks, message.Channel, text, replyTs);
}

protected Task SendReactionResponseAsync(MessageEvent message) =>
Broker.SendReactionAsync("robot_face", message.Channel, message.Ts);

Expand Down
23 changes: 18 additions & 5 deletions Bottomly/Slack/MessageEventHandlers/ImageSearchHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Bottomly.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SlackNet.Blocks;
using SlackNet.Events;

namespace Bottomly.Slack.MessageEventHandlers;
Expand All @@ -27,11 +28,23 @@ protected override async Task InvokeHandlerLogicAsync(MessageEvent message)
{
var query = message.Text![CommandTrigger.Length..];
var result = await command.ExecuteAsync(query);
var response = result switch

if (result is SearchResult success)
{
var blocks = new List<Block>
{
new ImageBlock
{
ImageUrl = success.Link,
AltText = success.Title,
Title = new PlainText { Text = success.Title }
}
};
await SendBlocksResponseAsync(blocks, message, success.Title);
}
else
{
SearchResult success => $"{success.Title} {success.Link}",
_ => $"No image results found for \"{query}\""
};
await SendMessageResponseAsync(response, message);
await SendMessageResponseAsync($"No image results found for \"{query}\"", message);
}
}
}
2 changes: 1 addition & 1 deletion Bottomly/Slack/MessageEventHandlers/SearchHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ protected override async Task InvokeHandlerLogicAsync(MessageEvent message)

var response = result switch
{
SearchResult success => $"{success.Title} {success.Link}",
SearchResult success => $"<{success.Link}|{success.Title}>",
EmptySearchTermErrorResult => $"No results found for \"{query}\"",
_ => "Left as an exercise for the reader."
};
Expand Down
2 changes: 1 addition & 1 deletion Bottomly/Slack/MessageEventHandlers/WikipediaHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ protected override async Task InvokeHandlerLogicAsync(MessageEvent message)
var result = await command.ExecuteAsync(term);
var response = result is null
? $"No results found for \"{term}\""
: $"{result.Text} {result.Link}";
: $"<{result.Link}|{result.Text}>";
await SendMessageResponseAsync(response, message);
}
}
20 changes: 20 additions & 0 deletions Bottomly/Slack/SlackMessageBroker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SlackNet;
using SlackNet.Blocks;
using SlackNet.WebApi;

namespace Bottomly.Slack;
Expand Down Expand Up @@ -44,6 +45,25 @@ public async Task SendMessageAsync(string text, string channel, string? replyToT
}
}

public async Task SendBlocksMessageAsync(IReadOnlyList<Block> blocks, string channel, string? text = null, string? replyToTs = null)
{
try
{
var message = new Message
{
Channel = channel,
Text = _options.IsDebug && text is not null ? $"[{_options.Environment}] {text}" : text ?? string.Empty,
Blocks = [..blocks],
ThreadTs = replyToTs
};
await slack.Chat.PostMessage(message);
}
catch (Exception ex)
{
logger.LogError(ex, "Error sending blocks message to channel {Channel}", channel);
}
}

public async Task SendReactionAsync(string emoji, string channel, string timestamp)
{
try
Expand Down
Loading