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
49 changes: 49 additions & 0 deletions SlackNet.Tests/AgentSessionStoppedTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using EasyAssertions;
using Newtonsoft.Json;
using NUnit.Framework;
using SlackNet.Events;

namespace SlackNet.Tests;

public class AgentSessionStoppedTests
{
private JsonSerializerSettings _sut;

[SetUp]
public void SetUp()
{
_sut = Default.JsonSettings(Default.SlackTypeResolver(Default.AssembliesContainingSlackTypes)).SerializerSettings;
}

[Test]
public void AgentSessionStopped_DeserializedFromSlackPayload()
{
var result = Deserialize<Event>(@"{ ""channel"": ""C0123ABC456"", ""event_ts"": ""1783536983.783769"", ""streaming_message_ts"": [""1782234987.693923""], ""thread_ts"": ""1782234671.392669"", ""type"": ""agent_session_stopped"", ""user"": ""U123ABC456"" }");

var stopped = result.ShouldBeA<AgentSessionStopped>().And;
stopped.Channel.ShouldBe("C0123ABC456");
stopped.ThreadTs.ShouldBe("1782234671.392669");
stopped.User.ShouldBe("U123ABC456");
stopped.StreamingMessageTs.ShouldMatch(["1782234987.693923"]);
stopped.EventTs.ShouldBe("1783536983.783769");
stopped.ExtraProperties.ShouldBeEmpty();
}

[Test]
public void AgentSessionStopped_NoActiveStream_HasEmptyStreamingMessageTs()
{
var result = Deserialize<Event>(@"{ ""type"": ""agent_session_stopped"", ""channel"": ""C1"", ""thread_ts"": ""1.2"", ""user"": ""U1"", ""streaming_message_ts"": [] }");

result.ShouldBeA<AgentSessionStopped>().And.StreamingMessageTs.ShouldBeEmpty();
}

[Test]
public void AgentSessionStopped_InSessionChannel_MayHaveNoThread()
{
var result = Deserialize<Event>(@"{ ""type"": ""agent_session_stopped"", ""channel"": ""C1"", ""user"": ""U1"", ""streaming_message_ts"": [] }");

result.ShouldBeA<AgentSessionStopped>().And.ThreadTs.ShouldBeNull();
}

private T Deserialize<T>(string json) => JsonConvert.DeserializeObject<T>(json, _sut);
}
62 changes: 62 additions & 0 deletions SlackNet.Tests/AgentsSessionsApiTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System.Threading.Tasks;
using EasyAssertions;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using SlackNet.WebApi;

namespace SlackNet.Tests;

public class AgentsSessionsApiTests
{
private TestHttp _http;
private ISlackApiClient _sut;

[SetUp]
public void SetUp()
{
var jsonSettings = Default.JsonSettings(Default.SlackTypeResolver(Default.AssembliesContainingSlackTypes));
_http = new TestHttp();
_sut = new SlackApiClient(_http, Default.UrlBuilder(jsonSettings), jsonSettings, "token");
}

[Test]
public async Task SetStatus_SendsSessionArguments()
{
await _sut.AgentsSessions.SetStatus("C123", "1717171717.123456", AgentSessionStatus.Processing,
title: "Scuba diving research", initiatorUserId: "U123", iconEmoji: ":robot_face:", username: "Custom Agent Name");

_http.Method.ShouldBe("agents.sessions.setStatus");
_http.Body.ShouldMatchJson(@"{ ""channel_id"": ""C123"", ""thread_ts"": ""1717171717.123456"", ""status"": ""processing"", ""title"": ""Scuba diving research"", ""initiator_user_id"": ""U123"", ""icon_emoji"": "":robot_face:"", ""username"": ""Custom Agent Name"" }");
}

[TestCase(AgentSessionStatus.Active, "active")]
[TestCase(AgentSessionStatus.Processing, "processing")]
[TestCase(AgentSessionStatus.Suspended, "suspended")]
[TestCase(AgentSessionStatus.Closed, "closed")]
public async Task SetStatus_SendsStatusInSlackCase(AgentSessionStatus status, string expected)
{
await _sut.AgentsSessions.SetStatus("C123", "1717171717.123456", status);

_http.Body.ShouldMatchJson(@"{ ""channel_id"": ""C123"", ""thread_ts"": ""1717171717.123456"", ""status"": """ + expected + @""" }");
}

[Test]
public async Task SetStatus_InSessionChannel_OmitsThreadTs()
{
await _sut.AgentsSessions.SetStatus("C123", null, AgentSessionStatus.Active);

_http.Body.ShouldMatchJson(@"{ ""channel_id"": ""C123"", ""status"": ""active"" }");
}

[Test]
public async Task SetStatus_ReadsSessionStatusAndAgentStatus()
{
_http.Response = JObject.Parse(@"{ ""ok"": true, ""status"": ""processing"", ""agent_status"": ""active"", ""title"": ""Scuba diving research"" }");

var result = await _sut.AgentsSessions.SetStatus("C123", "1717171717.123456", AgentSessionStatus.Active);

result.Status.ShouldBe(AgentSessionStatus.Processing);
result.AgentStatus.ShouldBe(AgentSessionStatus.Active);
result.Title.ShouldBe("Scuba diving research");
}
}
5 changes: 5 additions & 0 deletions SlackNet.Tests/ApiLintTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,10 @@ private static object DummyValue(ParameterInfo param) => ArgumentFactories.TryGe
{ typeof(UnfurlMetadata), _ => null },
{ typeof(WorkObjectEntity), _ => null },
{ typeof(EntityError), _ => null },
{ typeof(IEnumerable<StreamChunk>), _ => Enumerable.Empty<StreamChunk>() },
{ typeof(AgentSessionStatus), _ => AgentSessionStatus.Active },
{ typeof(AgentSessionStatus?), _ => null },
{ typeof(TaskDisplayMode?), _ => null },
{ typeof(CancellationToken), _ => null },
};

Expand Down Expand Up @@ -293,6 +297,7 @@ public Task<T> Post<T>(string apiMethod, Args args, HttpContent content, Cancell

public ISlackApiClient WithAccessToken(string accessToken) => this;

public IAgentsSessionsApi AgentsSessions { get; }
public IApiApi Api { get; }
public IAppsConnectionsApi AppsConnectionsApi { get; }
public IAppsEventAuthorizationsApi AppsEventAuthorizations { get; }
Expand Down
3 changes: 2 additions & 1 deletion SlackNet.Tests/BlockLintTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public void AllBlockElementsHaveACorrespondingElementValue()
static readonly HashSet<string> ExcludedBlockElements =
[
"button",
"image"
"image",
"url"
];

[Test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,7 @@ protected class TestHandlerFactory : ISlackHandlerFactory

protected class TestApiClient : ISlackApiClient
{
public IAgentsSessionsApi AgentsSessions { get; }
public IApiApi Api { get; }
public IAppsConnectionsApi AppsConnectionsApi { get; }
public IAppsEventAuthorizationsApi AppsEventAuthorizations { get; }
Expand Down
157 changes: 157 additions & 0 deletions SlackNet.Tests/StreamingApiTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using System.Threading.Tasks;
using EasyAssertions;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using SlackNet.Blocks;
using SlackNet.WebApi;

namespace SlackNet.Tests;

public class StreamingApiTests
{
private TestHttp _http;
private ISlackApiClient _sut;

[SetUp]
public void SetUp()
{
var jsonSettings = Default.JsonSettings(Default.SlackTypeResolver(Default.AssembliesContainingSlackTypes));
_http = new TestHttp();
_sut = new SlackApiClient(_http, Default.UrlBuilder(jsonSettings), jsonSettings, "token");
}

[Test]
public async Task StartStream_WithMarkdownText_SendsOnlyTheMarkdownArguments()
{
await _sut.Chat.StartStream("C123", "1717171717.123456", "Hello", recipientUserId: "U123", recipientTeamId: "T123");

_http.Method.ShouldBe("chat.startStream");
_http.Body.ShouldMatchJson(@"{ ""channel"": ""C123"", ""thread_ts"": ""1717171717.123456"", ""markdown_text"": ""Hello"", ""recipient_user_id"": ""U123"", ""recipient_team_id"": ""T123"" }");
}

[Test]
public async Task StartStream_WithChunks_SendsChunksAndTaskDisplayMode()
{
await _sut.Chat.StartStream("C123", "1717171717.123456",
chunks:
[
new MarkdownTextChunk { Text = "Let me help you with that!" },
new TaskUpdateChunk { Id = "t1", Title = "Searching", Status = TaskCardStatus.InProgress }
],
taskDisplayMode: TaskDisplayMode.Plan,
iconEmoji: ":robot_face:",
iconUrl: "https://example.com/icon.png",
username: "Agent");

_http.Body.ShouldMatchJson(@"{
""channel"": ""C123"",
""thread_ts"": ""1717171717.123456"",
""chunks"": [
{ ""type"": ""markdown_text"", ""text"": ""Let me help you with that!"" },
{ ""type"": ""task_update"", ""id"": ""t1"", ""title"": ""Searching"", ""status"": ""in_progress"" }
],
""task_display_mode"": ""plan"",
""icon_emoji"": "":robot_face:"",
""icon_url"": ""https://example.com/icon.png"",
""username"": ""Agent""
}");
}

[Test]
public async Task AppendStream_WithMarkdownText_SendsOnlyTheMarkdownArguments()
{
await _sut.Chat.AppendStream("C123", "1503435956.000247", "more");

_http.Method.ShouldBe("chat.appendStream");
_http.Body.ShouldMatchJson(@"{ ""channel"": ""C123"", ""ts"": ""1503435956.000247"", ""markdown_text"": ""more"" }");
}

[Test]
public async Task AppendStream_WithChunks_SendsEveryKindOfChunk()
{
await _sut.Chat.AppendStream("C123", "1503435956.000247", chunks:
[
new MarkdownTextChunk { Text = "Here's what I found..." },
new TaskUpdateChunk
{
Id = "t1",
Title = "Remind Sandra how amazing she is",
HideTitle = false,
Status = TaskCardStatus.Complete,
Details = "wow such good details",
Output = "amazing output here",
Sources = [new UrlSourceElement { Text = "Example.com", Url = "https://example.com" }]
},
new PlanUpdateChunk { Title = "Sandra's new and improved plan" },
new BlocksChunk { Blocks = [new MarkdownBlock { Text = "**Sandra's plan outline**" }] }
]);

_http.Body.ShouldMatchJson(@"{
""channel"": ""C123"",
""ts"": ""1503435956.000247"",
""chunks"": [
{ ""type"": ""markdown_text"", ""text"": ""Here's what I found..."" },
{
""type"": ""task_update"",
""id"": ""t1"",
""title"": ""Remind Sandra how amazing she is"",
""hide_title"": false,
""status"": ""complete"",
""details"": ""wow such good details"",
""output"": ""amazing output here"",
""sources"": [ { ""type"": ""url"", ""url"": ""https://example.com"", ""text"": ""Example.com"" } ]
},
{ ""type"": ""plan_update"", ""title"": ""Sandra's new and improved plan"" },
{ ""type"": ""blocks"", ""blocks"": [ { ""type"": ""markdown"", ""text"": ""**Sandra's plan outline**"" } ] }
]
}");
}

[Test]
public async Task TaskUpdateChunk_WithIcon_SendsIcon()
{
await _sut.Chat.AppendStream("C123", "1503435956.000247", chunks: [new TaskUpdateChunk { Id = "t1", Title = "Task", Status = TaskCardStatus.InProgress, Icon = new TaskCardIcon { Name = "bug" } }]);

_http.Body.ShouldMatchJson(@"{ ""channel"": ""C123"", ""ts"": ""1503435956.000247"", ""chunks"": [ { ""type"": ""task_update"", ""id"": ""t1"", ""title"": ""Task"", ""status"": ""in_progress"", ""icon"": { ""type"": ""icon"", ""name"": ""bug"" } } ] }");
}

[TestCase(TaskCardStatus.Pending, "pending")]
[TestCase(TaskCardStatus.InProgress, "in_progress")]
[TestCase(TaskCardStatus.Complete, "complete")]
[TestCase(TaskCardStatus.Error, "error")]
public async Task TaskUpdateChunk_SendsStatusInSlackCase(TaskCardStatus status, string expected)
{
await _sut.Chat.AppendStream("C123", "1503435956.000247", chunks: [new TaskUpdateChunk { Id = "t1", Title = "Task", Status = status }]);

((string)_http.Body["chunks"][0]["status"]).ShouldBe(expected);
}

[Test]
public async Task StopStream_WithoutChunksOrSessionStatus_SendsOnlyTheMarkdownAndBlocks()
{
await _sut.Chat.StopStream("C123", "1503435956.000247", "done", blocks: [new MarkdownBlock { Text = "footer" }]);

_http.Method.ShouldBe("chat.stopStream");
_http.Body.ShouldMatchJson(@"{ ""channel"": ""C123"", ""ts"": ""1503435956.000247"", ""markdown_text"": ""done"", ""blocks"": [ { ""type"": ""markdown"", ""text"": ""footer"" } ] }");
}

[Test]
public async Task StopStream_WithChunksAndSessionStatus_SendsThem()
{
await _sut.Chat.StopStream("C123", "1503435956.000247",
chunks: [new MarkdownTextChunk { Text = "Hope this helps!" }],
sessionStatus: AgentSessionStatus.Suspended);

_http.Body.ShouldMatchJson(@"{ ""channel"": ""C123"", ""ts"": ""1503435956.000247"", ""chunks"": [ { ""type"": ""markdown_text"", ""text"": ""Hope this helps!"" } ], ""session_status"": ""suspended"" }");
}

[Test]
public void AppendStream_StoppedByUser_ThrowsSlackExceptionWithErrorCode()
{
_http.Response = JObject.Parse(@"{ ""ok"": false, ""error"": ""stopped_by_user"" }");

var exception = Assert.ThrowsAsync<SlackException>(() => _sut.Chat.AppendStream("C123", "1503435956.000247", "more"));

exception.ErrorCode.ShouldBe("stopped_by_user");
}
}
Loading
Loading