├── .gitattributes ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── README.md ├── SlackConnector.sln ├── appveyor.yml ├── src └── SlackConnector │ ├── BotHelpers │ ├── ChatHubInterpreter.cs │ ├── IChatHubInterpreter.cs │ ├── IMentionDetector.cs │ └── MentionDetector.cs │ ├── Connections │ ├── Clients │ │ ├── Channel │ │ │ ├── FlurlChannelClient.cs │ │ │ └── IChannelClient.cs │ │ ├── Chat │ │ │ ├── FlurlChatClient.cs │ │ │ └── IChatClient.cs │ │ ├── ClientConstants.cs │ │ ├── File │ │ │ ├── FlurlFileClient.cs │ │ │ └── IFileClient.cs │ │ ├── Handshake │ │ │ ├── FlurlHandshakeClient.cs │ │ │ └── IHandshakeClient.cs │ │ ├── IResponseVerifier.cs │ │ └── ResponseVerifier.cs │ ├── ConnectionFactory.cs │ ├── IConnectionFactory.cs │ ├── Models │ │ ├── Channel.cs │ │ ├── Detail.cs │ │ ├── Group.cs │ │ ├── Im.cs │ │ ├── Profile.cs │ │ └── User.cs │ ├── Monitoring │ │ ├── DateTimeKeeper.cs │ │ ├── IDateTimeKeeper.cs │ │ ├── IMonitoringFactory.cs │ │ ├── IPingPongMonitor.cs │ │ ├── ITimer.cs │ │ ├── MonitorAlreadyStartedException.cs │ │ ├── MonitoringFactory.cs │ │ ├── PingPongMonitor.cs │ │ └── Timer.cs │ ├── Responses │ │ ├── ChannelResponse.cs │ │ ├── ChannelsResponse.cs │ │ ├── GroupsResponse.cs │ │ ├── HandshakeResponse.cs │ │ ├── JoinChannelResponse.cs │ │ ├── PurposeResponse.cs │ │ ├── StandardResponse.cs │ │ ├── TopicResponse.cs │ │ └── UsersResponse.cs │ └── Sockets │ │ ├── IWebSocketClient.cs │ │ ├── Messages │ │ ├── Inbound │ │ │ ├── ChannelCreatedMessage.cs │ │ │ ├── ChannelJoinedMessage.cs │ │ │ ├── ChatMessage.cs │ │ │ ├── DmChannelJoinedMessage.cs │ │ │ ├── File.cs │ │ │ ├── GroupJoinedMessage.cs │ │ │ ├── IMessageInterpreter.cs │ │ │ ├── InboundMessage.cs │ │ │ ├── MessageInterpreter.cs │ │ │ ├── MessageSubType.cs │ │ │ ├── MessageType.cs │ │ │ ├── PongMessage.cs │ │ │ ├── ReactionItem │ │ │ │ ├── FileCommentReaction.cs │ │ │ │ ├── FileReaction.cs │ │ │ │ ├── IReactionItem.cs │ │ │ │ ├── MessageReaction.cs │ │ │ │ ├── ReactionItemType.cs │ │ │ │ └── UnknownReaction.cs │ │ │ ├── ReactionMessage.cs │ │ │ ├── UnknownMessage.cs │ │ │ └── UserJoinedMessage.cs │ │ └── Outbound │ │ │ ├── BaseMessage.cs │ │ │ ├── PingMessage.cs │ │ │ └── TypingIndicatorMessage.cs │ │ └── WebSocketClient.cs │ ├── ConsoleLoggingLevel.cs │ ├── EventHandlers │ ├── ChannelCreatedHandler.cs │ ├── ChatHubJoinedEventHandler.cs │ ├── DisconnectEventHandler.cs │ ├── MessageReceivedEventHandler.cs │ ├── PongEventHandler.cs │ ├── ReactionRecievedEventHandler.cs │ ├── ReconnectEventHandler.cs │ └── UserJoinedEventHandler.cs │ ├── Exceptions │ ├── CommunicationException.cs │ ├── ConnectionTimeout.cs │ ├── HandshakeException.cs │ └── MissingChannelException.cs │ ├── Extensions │ ├── ChannelExtensions.cs │ ├── FileExtensions.cs │ ├── GroupExtensions.cs │ ├── ImExtensions.cs │ ├── MessageSubTypeExtensions.cs │ └── UserExtensions.cs │ ├── ISlackConnection.cs │ ├── ISlackConnectionFactory.cs │ ├── ISlackConnector.cs │ ├── Internals.cs │ ├── Logging │ ├── ILogger.cs │ └── Logger.cs │ ├── Models │ ├── BotMessage.cs │ ├── ConnectionInformation.cs │ ├── ContactDetails.cs │ ├── ISlackReaction.cs │ ├── Reactions │ │ ├── SlackFileCommentReaction.cs │ │ ├── SlackFileReaction.cs │ │ ├── SlackMessageReaction.cs │ │ └── SlackUnknownReaction.cs │ ├── SlackAttachment.cs │ ├── SlackAttachmentAction.cs │ ├── SlackAttachmentActionStyle.cs │ ├── SlackAttachmentField.cs │ ├── SlackAttachmentStatics.cs │ ├── SlackChannelCreated.cs │ ├── SlackChatHub.cs │ ├── SlackChatHubType.cs │ ├── SlackFile.cs │ ├── SlackMessage.cs │ ├── SlackMessageSubType.cs │ ├── SlackPurpose.cs │ ├── SlackThumbnail.cs │ ├── SlackTopic.cs │ └── SlackUser.cs │ ├── Serialising │ └── EnumConverter.cs │ ├── SlackConnection.cs │ ├── SlackConnectionExtensions.cs │ ├── SlackConnectionFactory.cs │ ├── SlackConnector.cs │ └── SlackConnector.csproj └── tests ├── SlackConnector.Tests.Integration ├── Configuration │ ├── Config.cs │ ├── ConfigReader.cs │ ├── IConfigReader.cs │ ├── RunnableInDebugOnlyAttribute.cs │ ├── SlackConfig.cs │ └── config.default.json ├── Connections │ └── Clients │ │ └── FlurlHandshakeClientTests.cs ├── FileDownloadTests.cs ├── FileUploadTests.cs ├── IntegrationTest.cs ├── JoinDmChannelTests.cs ├── PingPongTests.cs ├── Resources │ ├── EmbeddedResourceFileReader.cs │ └── UploadTest.txt ├── SayTests.cs ├── SlackConnector.Tests.Integration.csproj ├── SlackConnectorTests.cs ├── SlackGetChannels.cs └── TypingIndicatorTests.cs └── SlackConnector.Tests.Unit ├── AutoMoqDataAttribute.cs ├── BotHelpers ├── ChatHubInterpreterTests.cs └── MentionDetectorTests.cs ├── Connections ├── Clients │ ├── Flurl │ │ ├── FlurlChannelClientTests.cs │ │ ├── FlurlChatClientTests.cs │ │ ├── FlurlFileClientTests.cs │ │ └── FlurlHandshakeClientTests.cs │ └── ResponseVerifierTests.cs ├── Monitoring │ ├── DateTimeKeeperTests.cs │ ├── PingPongMonitorTests.cs │ └── TimerTests.cs └── Sockets │ └── Messages │ └── MessageInterpreterTests.cs ├── Extensions ├── FileExtensionsTests.cs ├── MessageSubTypeExtensionsTests.cs └── UserExtensionsTests.cs ├── Models ├── MessageSubTypeEnumTests.cs └── SlackAttachmentSerialisationTests.cs ├── Resources ├── Inputs │ └── Attachments.json ├── ResourceManager.cs └── Responses │ └── HandShake.json ├── Serialising └── EnumConverterTests.cs ├── SlackConnectionTests ├── ArchiveChannelTests.cs ├── CloseConnectionTests.cs ├── CreateChannelTests.cs ├── DownloadFileTests.cs ├── InboundMessageTests │ ├── ChannelCreatedTests.cs │ ├── ChannelJoinedTests.cs │ ├── ChatMessageTests.cs │ ├── DmJoinedTests.cs │ ├── GroupJoinedTests.cs │ ├── PongTests.cs │ ├── ReactionTests.cs │ └── UserJoinedTests.cs ├── InitialiseTests.cs ├── JoinChannelTests.cs ├── JoinDirectMessageChannelTests.cs ├── PingTests.cs ├── SayTests.cs ├── SetChannelPurposeTests.cs ├── SetChannelTopicTests.cs ├── TypingIndicatorTests.cs ├── UploadFileTests.cs └── WebSocketTests.cs ├── SlackConnector.Tests.Unit.csproj ├── SlackConnectorTests ├── ConnectedStatusTests.cs └── HubsTests.cs ├── Stubs ├── SlackConnectionFactoryStub.cs ├── SlackConnectionStub.cs ├── TimerStub.cs └── WebSocketClientStub.cs └── TestExtensions ├── ExpectedObjectExtensions.cs ├── ShouldExtensions.cs └── ShouldLooksLikeExtensions.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | *.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | 198 | config.json 199 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceRoot}/tests/SlackConnector.Tests.Integration/bin/Debug/netcoreapp2.0/SlackConnector.Tests.Integration.dll", 14 | "args": [], 15 | "cwd": "${workspaceRoot}/tests/SlackConnector.Tests.Integration", 16 | // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window 17 | "console": "internalConsole", 18 | "stopAtEntry": false, 19 | "internalConsoleOptions": "openOnSessionStart" 20 | }, 21 | { 22 | "name": ".NET Core Attach", 23 | "type": "coreclr", 24 | "request": "attach", 25 | "processId": "${command:pickProcess}" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "dotnet", 4 | "isShellCommand": true, 5 | "args": [], 6 | "tasks": [ 7 | { 8 | "taskName": "build", 9 | "args": [ 10 | "${workspaceRoot}/tests/SlackConnector.Tests.Integration/SlackConnector.Tests.Integration.csproj" 11 | ], 12 | "isBuildCommand": true, 13 | "problemMatcher": "$msCompile" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 noobot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SlackConnector 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/m92929hjx6ab3jpl?svg=true)](https://ci.appveyor.com/project/Workshop2/slackconnector-glqir) [![Test status](http://teststatusbadge.azurewebsites.net/api/status/Workshop2/slackconnector-glqir)](https://ci.appveyor.com/project/Workshop2/slackconnector-glqir) [![Nuget.org](https://img.shields.io/nuget/v/SlackConnector.svg?style=flat)](https://www.nuget.org/packages/SlackConnector) 4 | 5 | 6 | 7 | 8 | SlackConnector is a C# client to initiate and manage an open connection between you and the Slack servers. SlackConnector uses web-sockets to allow real-time messages to be received and handled within your application. 9 | 10 | ## History 11 | This library was originally extracted MargieBot and has iterated on it's own to become testable and progress without being coupled to any one implementation. This library has been built for [noobot](http://github.com/noobot/noobot), however it can easily be used in any project due to it's decoupling. 12 | 13 | 14 | ## Installation 15 | 16 | ``` 17 | Install-Package SlackConnector 18 | ``` 19 | 20 | 21 | ## Usage 22 | 23 | ``` cs 24 | ISlackConnector connector = new SlackConnector.SlackConnector(); 25 | ISlackConnection connection = await connector.Connect(botAccessToken); 26 | connection.OnMessageReceived += MessageReceived; 27 | connection.OnDisconnect += Disconnected; 28 | ``` 29 | 30 | ## Features 31 | 32 | - Async by default 33 | - Easy setup 34 | - Real-time communication 35 | - Unit tested 36 | -------------------------------------------------------------------------------- /SlackConnector.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{01D3C8E6-DA36-4D50-B35E-3B3FC78AFFEA}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SlackConnector", "src\SlackConnector\SlackConnector.csproj", "{90677E16-CD23-4E7A-9CF8-6AD5E9A59678}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SlackConnector.Tests.Unit", "tests\SlackConnector.Tests.Unit\SlackConnector.Tests.Unit.csproj", "{9AF6B3B4-6259-46C7-8568-63BD078D0CB3}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SlackConnector.Tests.Integration", "tests\SlackConnector.Tests.Integration\SlackConnector.Tests.Integration.csproj", "{D1CF1D61-776F-4FF8-A810-85225C1F450A}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {90677E16-CD23-4E7A-9CF8-6AD5E9A59678}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {90677E16-CD23-4E7A-9CF8-6AD5E9A59678}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {90677E16-CD23-4E7A-9CF8-6AD5E9A59678}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {90677E16-CD23-4E7A-9CF8-6AD5E9A59678}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {9AF6B3B4-6259-46C7-8568-63BD078D0CB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {9AF6B3B4-6259-46C7-8568-63BD078D0CB3}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {9AF6B3B4-6259-46C7-8568-63BD078D0CB3}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {9AF6B3B4-6259-46C7-8568-63BD078D0CB3}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {D1CF1D61-776F-4FF8-A810-85225C1F450A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {D1CF1D61-776F-4FF8-A810-85225C1F450A}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {D1CF1D61-776F-4FF8-A810-85225C1F450A}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {D1CF1D61-776F-4FF8-A810-85225C1F450A}.Release|Any CPU.Build.0 = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(SolutionProperties) = preSolution 34 | HideSolutionNode = FALSE 35 | EndGlobalSection 36 | GlobalSection(NestedProjects) = preSolution 37 | {9AF6B3B4-6259-46C7-8568-63BD078D0CB3} = {01D3C8E6-DA36-4D50-B35E-3B3FC78AFFEA} 38 | {D1CF1D61-776F-4FF8-A810-85225C1F450A} = {01D3C8E6-DA36-4D50-B35E-3B3FC78AFFEA} 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {69AD9C27-0383-4CCD-9980-75E9D8ACA096} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 5.1.{build} 2 | skip_tags: true 3 | image: Visual Studio 2017 4 | configuration: Release 5 | dotnet_csproj: 6 | patch: true 7 | file: '**\*.csproj' 8 | version: '{version}' 9 | package_version: '{version}' 10 | nuget: 11 | project_feed: true 12 | before_build: 13 | - cmd: >- 14 | dotnet restore 15 | 16 | type NUL > src/SlackConnector.Tests.Integration/Configuration/config.json 17 | build: 18 | publish_nuget: true 19 | publish_nuget_symbols: true 20 | include_nuget_references: true 21 | verbosity: minimal 22 | test_script: 23 | - ps: dotnet test .\tests\SlackConnector.Tests.Unit\SlackConnector.Tests.Unit.csproj 24 | deploy: 25 | - provider: GitHub 26 | auth_token: 27 | secure: 8t4+xbVlkXA/FY3516ThrMibGxfCT7ZcSwyZw7KNwUlCH08KMPtznY2QphEn1n/z 28 | artifact: /.*\.nupkg/ 29 | draft: true 30 | on: 31 | branch: master 32 | - provider: NuGet 33 | api_key: 34 | secure: ImI1DBI5d+r46a2qVVgGlXjmjQ4uAk57eETmeY30OI3Um8UnYADfQzWXZ4r37Zo6 35 | skip_symbols: false 36 | on: 37 | branch: master 38 | -------------------------------------------------------------------------------- /src/SlackConnector/BotHelpers/ChatHubInterpreter.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Models; 2 | 3 | namespace SlackConnector.BotHelpers 4 | { 5 | public class ChatHubInterpreter : IChatHubInterpreter 6 | { 7 | public SlackChatHub FromId(string hubId) 8 | { 9 | if (!string.IsNullOrEmpty(hubId)) 10 | { 11 | SlackChatHubType? hubType = null; 12 | 13 | switch (hubId.ToCharArray()[0]) 14 | { 15 | case 'C': 16 | hubType = SlackChatHubType.Channel; 17 | break; 18 | case 'D': 19 | hubType = SlackChatHubType.DM; 20 | break; 21 | case 'G': 22 | hubType = SlackChatHubType.Group; 23 | break; 24 | } 25 | 26 | if (hubType != null) 27 | { 28 | return new SlackChatHub() 29 | { 30 | Id = hubId, 31 | Name = hubId, 32 | Type = hubType.Value 33 | }; 34 | } 35 | } 36 | 37 | return null; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/SlackConnector/BotHelpers/IChatHubInterpreter.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Models; 2 | 3 | namespace SlackConnector.BotHelpers 4 | { 5 | public interface IChatHubInterpreter 6 | { 7 | SlackChatHub FromId(string hubId); 8 | } 9 | } -------------------------------------------------------------------------------- /src/SlackConnector/BotHelpers/IMentionDetector.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.BotHelpers 2 | { 3 | internal interface IMentionDetector 4 | { 5 | bool WasBotMentioned(string username, string userId, string messageText); 6 | } 7 | } -------------------------------------------------------------------------------- /src/SlackConnector/BotHelpers/MentionDetector.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace SlackConnector.BotHelpers 4 | { 5 | internal class MentionDetector : IMentionDetector 6 | { 7 | public bool WasBotMentioned(string username, string userId, string messageText) 8 | { 9 | bool mentioned = false; 10 | 11 | if (!string.IsNullOrEmpty(messageText)) 12 | { 13 | string regexText = $"<@{userId}>|{username}"; 14 | mentioned = Regex.IsMatch(messageText, regexText, RegexOptions.IgnoreCase); 15 | } 16 | 17 | return mentioned; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Clients/Channel/IChannelClient.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SlackConnector.Connections.Responses; 3 | 4 | namespace SlackConnector.Connections.Clients.Channel 5 | { 6 | internal interface IChannelClient 7 | { 8 | Task JoinDirectMessageChannel(string slackKey, string user); 9 | 10 | Task CreateChannel(string slackKey, string channelName); 11 | 12 | Task JoinChannel(string slackKey, string channelName); 13 | 14 | Task ArchiveChannel(string slackKey, string channelName); 15 | 16 | Task SetPurpose(string slackKey, string channelName, string purpose); 17 | 18 | Task SetTopic(string slackKey, string channelName, string topic); 19 | 20 | Task GetChannels(string slackKey); 21 | 22 | Task GetGroups(string slackKey); 23 | 24 | Task GetUsers(string slackKey); 25 | } 26 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Clients/Chat/FlurlChatClient.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Flurl; 5 | using Flurl.Http; 6 | using Newtonsoft.Json; 7 | using SlackConnector.Connections.Responses; 8 | using SlackConnector.Models; 9 | 10 | namespace SlackConnector.Connections.Clients.Chat 11 | { 12 | internal class FlurlChatClient : IChatClient 13 | { 14 | private readonly IResponseVerifier _responseVerifier; 15 | internal const string SEND_MESSAGE_PATH = "/api/chat.postMessage"; 16 | 17 | public FlurlChatClient(IResponseVerifier responseVerifier) 18 | { 19 | _responseVerifier = responseVerifier; 20 | } 21 | 22 | public async Task PostMessage(string slackKey, string channel, string text, IList attachments) 23 | { 24 | var request = ClientConstants 25 | .SlackApiHost 26 | .AppendPathSegment(SEND_MESSAGE_PATH) 27 | .SetQueryParam("token", slackKey) 28 | .SetQueryParam("channel", channel) 29 | .SetQueryParam("text", text) 30 | .SetQueryParam("as_user", "true") 31 | .SetQueryParam("link_names", "true"); 32 | 33 | if (attachments != null && attachments.Any()) 34 | { 35 | request.SetQueryParam("attachments", JsonConvert.SerializeObject(attachments)); 36 | } 37 | 38 | var response = await request.GetJsonAsync(); 39 | _responseVerifier.VerifyResponse(response); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Clients/Chat/IChatClient.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using SlackConnector.Models; 4 | 5 | namespace SlackConnector.Connections.Clients.Chat 6 | { 7 | internal interface IChatClient 8 | { 9 | Task PostMessage(string slackKey, string channel, string text, IList attachments); 10 | } 11 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Clients/ClientConstants.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Connections.Clients 2 | { 3 | internal static class ClientConstants 4 | { 5 | internal const string SlackApiHost = "https://slack.com"; 6 | } 7 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Clients/File/FlurlFileClient.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using Flurl; 4 | using Flurl.Http; 5 | using Newtonsoft.Json; 6 | using SlackConnector.Connections.Responses; 7 | using SlackConnector.Models; 8 | 9 | namespace SlackConnector.Connections.Clients.File 10 | { 11 | internal class FlurlFileClient : IFileClient 12 | { 13 | private readonly IResponseVerifier _responseVerifier; 14 | internal const string FILE_UPLOAD_PATH = "/api/files.upload"; 15 | internal const string POST_FILE_VARIABLE_NAME = "file"; 16 | 17 | public FlurlFileClient(IResponseVerifier responseVerifier) 18 | { 19 | _responseVerifier = responseVerifier; 20 | } 21 | 22 | public async Task PostFile(string slackKey, string channel, string filePath) 23 | { 24 | var httpResponse = await ClientConstants 25 | .SlackApiHost 26 | .AppendPathSegment(FILE_UPLOAD_PATH) 27 | .SetQueryParam("token", slackKey) 28 | .SetQueryParam("channels", channel) 29 | .PostMultipartAsync(content => content.AddFile(POST_FILE_VARIABLE_NAME, filePath)); 30 | 31 | var responseContent = await httpResponse.Content.ReadAsStringAsync(); 32 | var response = JsonConvert.DeserializeObject(responseContent); 33 | _responseVerifier.VerifyResponse(response); 34 | } 35 | 36 | public async Task PostFile(string slackKey, string channel, Stream stream, string fileName) 37 | { 38 | var httpResponse = await ClientConstants 39 | .SlackApiHost 40 | .AppendPathSegment(FILE_UPLOAD_PATH) 41 | .SetQueryParam("token", slackKey) 42 | .SetQueryParam("channels", channel) 43 | .PostMultipartAsync(content => content.AddFile(POST_FILE_VARIABLE_NAME, stream, fileName)); 44 | 45 | var responseContent = await httpResponse.Content.ReadAsStringAsync(); 46 | var response = JsonConvert.DeserializeObject(responseContent); 47 | _responseVerifier.VerifyResponse(response); 48 | } 49 | 50 | public async Task DownloadFile(string slackKey, SlackFile file, string path) 51 | { 52 | await file.UrlPrivateDownload 53 | .AbsoluteUri 54 | .WithOAuthBearerToken(slackKey) 55 | .DownloadFileAsync(path, file.Name); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Clients/File/IFileClient.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | 4 | using SlackConnector.Models; 5 | 6 | namespace SlackConnector.Connections.Clients.File 7 | { 8 | internal interface IFileClient 9 | { 10 | Task PostFile(string slackKey, string channel, string filePath); 11 | Task PostFile(string slackKey, string channel, Stream stream, string fileName); 12 | Task DownloadFile(string slackKey, SlackFile file, string path); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Clients/Handshake/FlurlHandshakeClient.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Flurl; 3 | using Flurl.Http; 4 | using SlackConnector.Connections.Responses; 5 | 6 | namespace SlackConnector.Connections.Clients.Handshake 7 | { 8 | internal class FlurlHandshakeClient : IHandshakeClient 9 | { 10 | private readonly IResponseVerifier _responseVerifier; 11 | internal const string HANDSHAKE_PATH = "/api/rtm.connect"; 12 | 13 | public FlurlHandshakeClient(IResponseVerifier responseVerifier) 14 | { 15 | _responseVerifier = responseVerifier; 16 | } 17 | 18 | public async Task FirmShake(string slackKey) 19 | { 20 | var response = await ClientConstants 21 | .SlackApiHost 22 | .AppendPathSegment(HANDSHAKE_PATH) 23 | .SetQueryParam("token", slackKey) 24 | .GetJsonAsync(); 25 | 26 | _responseVerifier.VerifyResponse(response); 27 | return response; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Clients/Handshake/IHandshakeClient.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SlackConnector.Connections.Responses; 3 | 4 | namespace SlackConnector.Connections.Clients.Handshake 5 | { 6 | internal interface IHandshakeClient 7 | { 8 | /// 9 | /// No one likes a limp shake - AMIRITE? 10 | /// 11 | Task FirmShake(string slackKey); 12 | } 13 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Clients/IResponseVerifier.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Connections.Responses; 2 | 3 | namespace SlackConnector.Connections.Clients 4 | { 5 | internal interface IResponseVerifier 6 | { 7 | void VerifyResponse(StandardResponse response); 8 | } 9 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Clients/ResponseVerifier.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Connections.Responses; 2 | using SlackConnector.Exceptions; 3 | 4 | namespace SlackConnector.Connections.Clients 5 | { 6 | internal class ResponseVerifier : IResponseVerifier 7 | { 8 | public void VerifyResponse(StandardResponse response) 9 | { 10 | if (!response.Ok) 11 | { 12 | throw new CommunicationException($"Error occured while posting message '{response.Error}'"); 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/ConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SlackConnector.Connections.Clients; 3 | using SlackConnector.Connections.Clients.Channel; 4 | using SlackConnector.Connections.Clients.Chat; 5 | using SlackConnector.Connections.Clients.File; 6 | using SlackConnector.Connections.Clients.Handshake; 7 | using SlackConnector.Connections.Sockets; 8 | using SlackConnector.Connections.Sockets.Messages.Inbound; 9 | using SlackConnector.Logging; 10 | 11 | namespace SlackConnector.Connections 12 | { 13 | internal class ConnectionFactory : IConnectionFactory 14 | { 15 | public async Task CreateWebSocketClient(string url) 16 | { 17 | var socket = new WebSocketClient(new MessageInterpreter(new Logger())); 18 | await socket.Connect(url); 19 | return socket; 20 | } 21 | 22 | public IHandshakeClient CreateHandshakeClient() 23 | { 24 | return new FlurlHandshakeClient(new ResponseVerifier()); 25 | } 26 | 27 | public IChatClient CreateChatClient() 28 | { 29 | return new FlurlChatClient(new ResponseVerifier()); 30 | } 31 | 32 | public IFileClient CreateFileClient() 33 | { 34 | return new FlurlFileClient(new ResponseVerifier()); 35 | } 36 | 37 | public IChannelClient CreateChannelClient() 38 | { 39 | return new FlurlChannelClient(new ResponseVerifier()); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/IConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SlackConnector.Connections.Clients.Channel; 3 | using SlackConnector.Connections.Clients.Chat; 4 | using SlackConnector.Connections.Clients.File; 5 | using SlackConnector.Connections.Clients.Handshake; 6 | using SlackConnector.Connections.Sockets; 7 | 8 | namespace SlackConnector.Connections 9 | { 10 | internal interface IConnectionFactory 11 | { 12 | Task CreateWebSocketClient(string url); 13 | IHandshakeClient CreateHandshakeClient(); 14 | IChatClient CreateChatClient(); 15 | IFileClient CreateFileClient(); 16 | IChannelClient CreateChannelClient(); 17 | } 18 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Models/Channel.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SlackConnector.Connections.Models 4 | { 5 | internal class Channel : Detail 6 | { 7 | [JsonProperty("is_channel")] 8 | public bool IsChannel { get; set; } 9 | 10 | [JsonProperty("is_archived")] 11 | public bool IsArchived { get; set; } 12 | 13 | [JsonProperty("is_member")] 14 | public bool IsMember { get; set; } 15 | 16 | [JsonProperty("members")] 17 | public string[] Members { get; set; } 18 | 19 | public string Creator { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Models/Detail.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Connections.Models 2 | { 3 | internal class Detail 4 | { 5 | public string Id { get; set; } 6 | public string Name { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Models/Group.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SlackConnector.Connections.Models 4 | { 5 | internal class Group : Detail 6 | { 7 | [JsonProperty("is_group")] 8 | public bool IsGroup { get; set; } 9 | 10 | [JsonProperty("is_archived")] 11 | public bool IsArchived { get; set; } 12 | 13 | [JsonProperty("is_open")] 14 | public bool IsOpen { get; set; } 15 | 16 | [JsonProperty("members")] 17 | public string[] Members { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Models/Im.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SlackConnector.Connections.Models 4 | { 5 | internal class Im 6 | { 7 | public string Id { get; set; } 8 | 9 | public string User { get; set; } 10 | 11 | [JsonProperty("is_im")] 12 | public bool IsIm { get; set; } 13 | 14 | [JsonProperty("is_open")] 15 | public bool IsOpen { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Models/Profile.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SlackConnector.Connections.Models 4 | { 5 | internal class Profile 6 | { 7 | [JsonProperty("first_name")] 8 | public string FirstName { get; set; } 9 | 10 | [JsonProperty("last_name")] 11 | public string LastName { get; set; } 12 | 13 | [JsonProperty("real_name")] 14 | public string RealName { get; set; } 15 | 16 | [JsonProperty("real_name_normalized")] 17 | public string RealNameNormalised { get; set; } 18 | 19 | [JsonProperty("image_512")] 20 | public string Image { get; set; } 21 | 22 | public string Email { get; set; } 23 | 24 | public string Title { get; set; } 25 | 26 | [JsonProperty("status_text")] 27 | public string StatusText { get; set; } 28 | } 29 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Models/User.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SlackConnector.Connections.Models 4 | { 5 | internal class User : Detail 6 | { 7 | public bool Deleted { get; set; } 8 | 9 | public Profile Profile { get; set; } 10 | 11 | [JsonProperty("tz_offset")] 12 | public long TimeZoneOffset { get; set; } 13 | 14 | [JsonProperty("is_admin")] 15 | public bool IsAdmin { get; set; } 16 | 17 | [JsonProperty("is_bot")] 18 | public bool IsBot { get; set; } 19 | 20 | [JsonProperty("is_restricted")] 21 | public bool IsGuest { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Monitoring/DateTimeKeeper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SlackConnector.Connections.Monitoring 4 | { 5 | internal class DateTimeKeeper : IDateTimeKeeper 6 | { 7 | private DateTime? _dateTime; 8 | 9 | public void SetDateTimeToNow() 10 | { 11 | _dateTime = DateTime.Now; 12 | } 13 | 14 | public bool HasDateTime() 15 | { 16 | return _dateTime.HasValue; 17 | } 18 | 19 | public TimeSpan TimeSinceDateTime() 20 | { 21 | if (!_dateTime.HasValue) 22 | { 23 | throw new DateTimeNotSetException(); 24 | } 25 | 26 | return DateTime.Now - _dateTime.Value; 27 | } 28 | 29 | public class DateTimeNotSetException : Exception 30 | { } 31 | } 32 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Monitoring/IDateTimeKeeper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SlackConnector.Connections.Monitoring 4 | { 5 | internal interface IDateTimeKeeper 6 | { 7 | void SetDateTimeToNow(); 8 | bool HasDateTime(); 9 | TimeSpan TimeSinceDateTime(); 10 | } 11 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Monitoring/IMonitoringFactory.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Connections.Monitoring 2 | { 3 | internal interface IMonitoringFactory 4 | { 5 | IPingPongMonitor CreatePingPongMonitor(); 6 | } 7 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Monitoring/IPingPongMonitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace SlackConnector.Connections.Monitoring 5 | { 6 | internal interface IPingPongMonitor 7 | { 8 | Task StartMonitor(Func pingMethod, Func reconnectMethod, TimeSpan pongTimeout); 9 | void Pong(); 10 | } 11 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Monitoring/ITimer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SlackConnector.Connections.Monitoring 4 | { 5 | internal interface ITimer : IDisposable 6 | { 7 | void RunEvery(Action action, TimeSpan tick); 8 | } 9 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Monitoring/MonitorAlreadyStartedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SlackConnector.Connections.Monitoring 4 | { 5 | public class MonitorAlreadyStartedException : Exception 6 | { } 7 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Monitoring/MonitoringFactory.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Connections.Monitoring 2 | { 3 | internal class MonitoringFactory : IMonitoringFactory 4 | { 5 | public IPingPongMonitor CreatePingPongMonitor() 6 | { 7 | return new PingPongMonitor(new Timer(), new DateTimeKeeper()); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Monitoring/PingPongMonitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace SlackConnector.Connections.Monitoring 5 | { 6 | internal class PingPongMonitor : IPingPongMonitor 7 | { 8 | private readonly ITimer _timer; 9 | private readonly IDateTimeKeeper _dateTimeKeeper; 10 | 11 | private TimeSpan _pongTimeout; 12 | private Func _pingMethod; 13 | private Func _reconnectMethod; 14 | private bool _isReconnecting; 15 | private readonly object _reconnectLock = new object(); 16 | 17 | public PingPongMonitor(ITimer timer, IDateTimeKeeper dateTimeKeeper) 18 | { 19 | _timer = timer; 20 | _dateTimeKeeper = dateTimeKeeper; 21 | } 22 | 23 | public async Task StartMonitor(Func pingMethod, Func reconnectMethod, TimeSpan pongTimeout) 24 | { 25 | if (_dateTimeKeeper.HasDateTime()) 26 | { 27 | throw new MonitorAlreadyStartedException(); 28 | } 29 | 30 | _pingMethod = pingMethod; 31 | _reconnectMethod = reconnectMethod; 32 | _pongTimeout = pongTimeout; 33 | 34 | _timer.RunEvery(TimerTick, TimeSpan.FromSeconds(5)); 35 | 36 | await pingMethod().ConfigureAwait(false); 37 | } 38 | 39 | private void TimerTick() 40 | { 41 | if (NeedsToReconnect() && !_isReconnecting) 42 | { 43 | lock (_reconnectLock) 44 | { 45 | _isReconnecting = true; 46 | _reconnectMethod() 47 | .ContinueWith(task => _isReconnecting = false) 48 | .ConfigureAwait(false) 49 | .GetAwaiter() 50 | .GetResult(); 51 | } 52 | } 53 | 54 | _pingMethod(); 55 | } 56 | 57 | private bool NeedsToReconnect() 58 | { 59 | return _dateTimeKeeper.HasDateTime() && _dateTimeKeeper.TimeSinceDateTime() > _pongTimeout; 60 | } 61 | 62 | public void Pong() 63 | { 64 | _dateTimeKeeper.SetDateTimeToNow(); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Monitoring/Timer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SlackConnector.Connections.Monitoring 4 | { 5 | internal class Timer : ITimer 6 | { 7 | private System.Threading.Timer _timer; 8 | 9 | public void RunEvery(Action action, TimeSpan tick) 10 | { 11 | if (_timer != null) 12 | { 13 | throw new TimerAlreadyInitialisedException(); 14 | } 15 | 16 | _timer = new System.Threading.Timer(state => action(), null, TimeSpan.Zero, tick); 17 | } 18 | 19 | public void Dispose() 20 | { 21 | _timer?.Dispose(); 22 | } 23 | 24 | public class TimerAlreadyInitialisedException : Exception 25 | { } 26 | } 27 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Responses/ChannelResponse.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Connections.Models; 2 | 3 | namespace SlackConnector.Connections.Responses 4 | { 5 | internal class ChannelResponse : StandardResponse 6 | { 7 | public Channel Channel { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Responses/ChannelsResponse.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Connections.Models; 2 | 3 | namespace SlackConnector.Connections.Responses 4 | { 5 | internal class ChannelsResponse : StandardResponse 6 | { 7 | public Channel[] Channels { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Responses/GroupsResponse.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Connections.Models; 2 | 3 | namespace SlackConnector.Connections.Responses 4 | { 5 | internal class GroupsResponse : StandardResponse 6 | { 7 | public Group[] Groups { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Responses/HandshakeResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using SlackConnector.Connections.Models; 3 | 4 | namespace SlackConnector.Connections.Responses 5 | { 6 | internal class HandshakeResponse : StandardResponse 7 | { 8 | [JsonProperty("Url")] 9 | public string WebSocketUrl { get; set; } 10 | 11 | public Detail Team { get; set; } = new Detail(); 12 | public Detail Self { get; set; } = new Detail(); 13 | public User[] Users { get; set; } = new User[0]; 14 | public Channel[] Channels { get; set; } = new Channel[0]; 15 | public Group[] Groups { get; set; } = new Group[0]; 16 | public Im[] Ims { get; set; } = new Im[0]; 17 | } 18 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Responses/JoinChannelResponse.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Connections.Models; 2 | 3 | namespace SlackConnector.Connections.Responses 4 | { 5 | internal class JoinChannelResponse : StandardResponse 6 | { 7 | public Channel Channel { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Responses/PurposeResponse.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Connections.Responses 2 | { 3 | internal class PurposeResponse : StandardResponse 4 | { 5 | public string Purpose { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Responses/StandardResponse.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Connections.Responses 2 | { 3 | internal class StandardResponse 4 | { 5 | public bool Ok { get; set; } 6 | public string Error { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Responses/TopicResponse.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Connections.Responses 2 | { 3 | internal class TopicResponse : StandardResponse 4 | { 5 | public string Topic { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Responses/UsersResponse.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Connections.Models; 2 | 3 | namespace SlackConnector.Connections.Responses 4 | { 5 | internal class UsersResponse : StandardResponse 6 | { 7 | public User[] Members { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/IWebSocketClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using SlackConnector.Connections.Sockets.Messages.Inbound; 4 | using SlackConnector.Connections.Sockets.Messages.Outbound; 5 | 6 | namespace SlackConnector.Connections.Sockets 7 | { 8 | internal interface IWebSocketClient 9 | { 10 | bool IsAlive { get; } 11 | 12 | Task Connect(string webSocketUrl); 13 | Task SendMessage(BaseMessage message); 14 | Task Close(); 15 | 16 | event EventHandler OnMessage; 17 | event EventHandler OnClose; 18 | } 19 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/ChannelCreatedMessage.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Connections.Models; 2 | 3 | namespace SlackConnector.Connections.Sockets.Messages.Inbound 4 | { 5 | internal class ChannelCreatedMessage : InboundMessage 6 | { 7 | public ChannelCreatedMessage() 8 | { 9 | MessageType = MessageType.Channel_Created; 10 | } 11 | 12 | public Channel Channel { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/ChannelJoinedMessage.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Connections.Models; 2 | 3 | namespace SlackConnector.Connections.Sockets.Messages.Inbound 4 | { 5 | internal class ChannelJoinedMessage : InboundMessage 6 | { 7 | public ChannelJoinedMessage() 8 | { 9 | MessageType = MessageType.Channel_Joined; 10 | } 11 | 12 | public Channel Channel { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/ChatMessage.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using SlackConnector.Serialising; 3 | 4 | namespace SlackConnector.Connections.Sockets.Messages.Inbound 5 | { 6 | internal class ChatMessage : InboundMessage 7 | { 8 | public ChatMessage() 9 | { 10 | MessageType = MessageType.Message; 11 | } 12 | 13 | [JsonProperty("subtype")] 14 | [JsonConverter(typeof(EnumConverter))] 15 | public MessageSubType MessageSubType { get; set; } 16 | 17 | public string Channel { get; set; } 18 | public string User { get; set; } 19 | public string Text { get; set; } 20 | public string Team { get; set; } 21 | public File[] Files { get; set; } 22 | 23 | [JsonProperty("ts")] 24 | public double Timestamp { get; set; } 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/DmChannelJoinedMessage.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Connections.Models; 2 | 3 | namespace SlackConnector.Connections.Sockets.Messages.Inbound 4 | { 5 | internal class DmChannelJoinedMessage : InboundMessage 6 | { 7 | public DmChannelJoinedMessage() 8 | { 9 | MessageType = MessageType.Im_Created; 10 | } 11 | 12 | public Im Channel { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/File.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SlackConnector.Connections.Sockets.Messages.Inbound 4 | { 5 | internal class File 6 | { 7 | public string Id { get; set; } 8 | public int Created { get; set; } 9 | public int Timestamp { get; set; } 10 | public string Name { get; set; } 11 | public string Title { get; set; } 12 | public string Mimetype { get; set; } 13 | public string FileType { get; set; } 14 | 15 | [JsonProperty("pretty_type")] 16 | public string PrettyType { get; set; } 17 | 18 | public string User { get; set; } 19 | public bool Editable { get; set; } 20 | public int Size { get; set; } 21 | public string Mode { get; set; } 22 | 23 | [JsonProperty("is_external")] 24 | public bool IsExternal { get; set; } 25 | 26 | [JsonProperty("external_type")] 27 | public string ExternalType { get; set; } 28 | 29 | [JsonProperty("is_public")] 30 | public bool IsPublic { get; set; } 31 | 32 | [JsonProperty("public_url_shared")] 33 | public bool PublicUrlShared { get; set; } 34 | 35 | [JsonProperty("display_as_bot")] 36 | public bool DisplayAsBot { get; set; } 37 | 38 | public string Username { get; set; } 39 | 40 | [JsonProperty("url_private")] 41 | public string UrlPrivate { get; set; } 42 | 43 | [JsonProperty("url_private_download")] 44 | public string UrlPrivateDownload { get; set; } 45 | 46 | [JsonProperty("thumb_64")] 47 | public string Thumb64 { get; set; } 48 | 49 | [JsonProperty("thumb_80")] 50 | public string Thumb80 { get; set; } 51 | 52 | [JsonProperty("thumb_360")] 53 | public string Thumb360 { get; set; } 54 | 55 | [JsonProperty("thumb_360_w")] 56 | public int Thumb360Width { get; set; } 57 | 58 | [JsonProperty("thumb_360_h")] 59 | public int Thumb360Height { get; set; } 60 | 61 | [JsonProperty("thumb_160")] 62 | public string Thumb160 { get; set; } 63 | 64 | [JsonProperty("thumb_360_gif")] 65 | public string Thumb360Gif { get; set; } 66 | 67 | [JsonProperty("image_exif_rotation")] 68 | public int ImageExifRotation { get; set; } 69 | 70 | [JsonProperty("original_w")] 71 | public int OriginalWidth { get; set; } 72 | 73 | [JsonProperty("original_h")] 74 | public int OriginalHeight { get; set; } 75 | 76 | [JsonProperty("deanimate_gif")] 77 | public string DeanimateGif { get; set; } 78 | 79 | public string Permalink { get; set; } 80 | 81 | [JsonProperty("permalink_public")] 82 | public string PermalinkPublic { get; set; } 83 | } 84 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/GroupJoinedMessage.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Connections.Models; 2 | 3 | namespace SlackConnector.Connections.Sockets.Messages.Inbound 4 | { 5 | internal class GroupJoinedMessage : InboundMessage 6 | { 7 | public GroupJoinedMessage() 8 | { 9 | MessageType = MessageType.Group_Joined; 10 | } 11 | 12 | public Group Channel { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/IMessageInterpreter.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Connections.Sockets.Messages.Inbound 2 | { 3 | internal interface IMessageInterpreter 4 | { 5 | InboundMessage InterpretMessage(string json); 6 | } 7 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/InboundMessage.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using SlackConnector.Serialising; 3 | 4 | namespace SlackConnector.Connections.Sockets.Messages.Inbound 5 | { 6 | //TOOD: Turn into interface? 7 | internal abstract class InboundMessage 8 | { 9 | [JsonProperty("type")] 10 | [JsonConverter(typeof(EnumConverter))] 11 | public MessageType MessageType { get; set; } 12 | 13 | public string RawData { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/MessageSubType.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Connections.Sockets.Messages.Inbound 2 | { 3 | internal enum MessageSubType 4 | { 5 | Unknown = 0, 6 | bot_message, 7 | me_message, 8 | message_changed, 9 | message_deleted, 10 | channel_join, 11 | channel_leave, 12 | channel_topic, 13 | channel_purpose, 14 | channel_name, 15 | channel_archive, 16 | channel_unarchive, 17 | group_join, 18 | group_leave, 19 | group_topic, 20 | group_purpose, 21 | group_name, 22 | group_archive, 23 | group_unarchive, 24 | file_share, 25 | file_comment, 26 | file_mention, 27 | pinned_item, 28 | unpinned_item 29 | } 30 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/MessageType.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Connections.Sockets.Messages.Inbound 2 | { 3 | internal enum MessageType 4 | { 5 | Unknown = 0, 6 | Message, 7 | Group_Joined, 8 | Channel_Joined, 9 | Im_Created, 10 | Team_Join, 11 | Pong, 12 | Reaction_Added, 13 | Channel_Created 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/PongMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace SlackConnector.Connections.Sockets.Messages.Inbound 5 | { 6 | internal class PongMessage : InboundMessage 7 | { 8 | public PongMessage() 9 | { 10 | MessageType = MessageType.Pong; 11 | } 12 | 13 | [JsonProperty("time")] 14 | public DateTime Timestamp { get; set; } 15 | [JsonProperty("reply_to")] 16 | public int ReplyTo { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/ReactionItem/FileCommentReaction.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SlackConnector.Connections.Sockets.Messages.Inbound.ReactionItem 4 | { 5 | internal class FileCommentReaction : IReactionItem 6 | { 7 | [JsonProperty("file")] 8 | public string File { get; set; } 9 | 10 | [JsonProperty("file_comment")] 11 | public string FileComment { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/ReactionItem/FileReaction.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Connections.Sockets.Messages.Inbound.ReactionItem 2 | { 3 | internal class FileReaction : IReactionItem 4 | { 5 | public string File { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/ReactionItem/IReactionItem.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Connections.Sockets.Messages.Inbound.ReactionItem 2 | { 3 | interface IReactionItem 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/ReactionItem/MessageReaction.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SlackConnector.Connections.Sockets.Messages.Inbound.ReactionItem 4 | { 5 | internal class MessageReaction : IReactionItem 6 | { 7 | [JsonProperty("channel")] 8 | public string Channel { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/ReactionItem/ReactionItemType.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Connections.Sockets.Messages.Inbound.ReactionItem 2 | { 3 | internal enum ReactionItemType 4 | { 5 | unknown = 0, 6 | message, 7 | file, 8 | file_comment 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/ReactionItem/UnknownReaction.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Connections.Sockets.Messages.Inbound.ReactionItem 2 | { 3 | internal class UnknownReaction : IReactionItem 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/ReactionMessage.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using SlackConnector.Connections.Sockets.Messages.Inbound.ReactionItem; 3 | 4 | namespace SlackConnector.Connections.Sockets.Messages.Inbound 5 | { 6 | internal class ReactionMessage : InboundMessage 7 | { 8 | public ReactionMessage() 9 | { 10 | MessageType = MessageType.Reaction_Added; 11 | } 12 | 13 | [JsonProperty("user")] 14 | public string User { get; set; } 15 | 16 | [JsonProperty("reaction")] 17 | public string Reaction { get; set; } 18 | 19 | [JsonProperty("event_ts")] 20 | public double Timestamp { get; set; } 21 | 22 | public IReactionItem ReactingTo { get; set; } 23 | 24 | [JsonProperty("item_user")] 25 | public string ReactingToUser { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/UnknownMessage.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Connections.Sockets.Messages.Inbound 2 | { 3 | internal class UnknownMessage : InboundMessage 4 | { 5 | public UnknownMessage() 6 | { 7 | MessageType = MessageType.Unknown; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Inbound/UserJoinedMessage.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Connections.Models; 2 | 3 | namespace SlackConnector.Connections.Sockets.Messages.Inbound 4 | { 5 | internal class UserJoinedMessage : InboundMessage 6 | { 7 | public UserJoinedMessage() 8 | { 9 | MessageType = MessageType.Team_Join; 10 | } 11 | 12 | public User User { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Outbound/BaseMessage.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SlackConnector.Connections.Sockets.Messages.Outbound 4 | { 5 | internal abstract class BaseMessage 6 | { 7 | [JsonProperty("id")] 8 | public int Id { get; set; } 9 | 10 | [JsonProperty("type")] 11 | public string Type { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Outbound/PingMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SlackConnector.Connections.Sockets.Messages.Outbound 4 | { 5 | internal class PingMessage : BaseMessage 6 | { 7 | public DateTime Timestamp { get; } = DateTime.Now; 8 | 9 | public PingMessage() 10 | { 11 | Type = "ping"; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/Messages/Outbound/TypingIndicatorMessage.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SlackConnector.Connections.Sockets.Messages.Outbound 4 | { 5 | internal class TypingIndicatorMessage : BaseMessage 6 | { 7 | public TypingIndicatorMessage() 8 | { 9 | Type = "typing"; 10 | } 11 | 12 | [JsonProperty("channel")] 13 | public string Channel { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /src/SlackConnector/Connections/Sockets/WebSocketClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Newtonsoft.Json; 4 | using SlackConnector.Connections.Sockets.Messages.Inbound; 5 | using SlackConnector.Connections.Sockets.Messages.Outbound; 6 | using SlackConnector.Exceptions; 7 | using Websocket.Client; 8 | 9 | namespace SlackConnector.Connections.Sockets 10 | { 11 | internal class WebSocketClient : IWebSocketClient 12 | { 13 | private readonly IMessageInterpreter _interpreter; 14 | private WebsocketClient _client; 15 | private readonly TimeSpan _reconnectTimeout = TimeSpan.FromSeconds(30); 16 | private int _currentMessageId; 17 | 18 | public WebSocketClient(IMessageInterpreter interpreter) 19 | { 20 | _interpreter = interpreter; 21 | } 22 | 23 | public bool IsAlive => _client.IsRunning; 24 | 25 | public async Task Connect(string webSocketUrl) 26 | { 27 | if (_client != null) 28 | { 29 | await Close(); 30 | } 31 | 32 | var uri = new Uri(webSocketUrl); 33 | _client = new WebsocketClient(uri) 34 | { 35 | ReconnectTimeout = _reconnectTimeout, 36 | IsReconnectionEnabled = true 37 | }; 38 | 39 | _client.MessageReceived.Subscribe(MessageReceived); 40 | _client.DisconnectionHappened.Subscribe(Disconnected); 41 | 42 | await _client.Start(); 43 | } 44 | 45 | public Task SendMessage(BaseMessage message) 46 | { 47 | if (!IsAlive) 48 | { 49 | throw new CommunicationException("Connection not Alive"); 50 | } 51 | 52 | System.Threading.Interlocked.Increment(ref _currentMessageId); 53 | message.Id = _currentMessageId; 54 | var json = JsonConvert.SerializeObject(message); 55 | 56 | _client.Send(json); 57 | 58 | return Task.CompletedTask; 59 | } 60 | 61 | public Task Close() 62 | { 63 | _client.Dispose(); 64 | return Task.CompletedTask; 65 | } 66 | 67 | public event EventHandler OnClose; 68 | private void Disconnected(DisconnectionInfo obj) 69 | { 70 | OnClose?.Invoke(this, null); 71 | } 72 | 73 | public event EventHandler OnMessage; 74 | private void MessageReceived(ResponseMessage message) 75 | { 76 | string messageJson = message.Text ?? ""; 77 | var inboundMessage = _interpreter.InterpretMessage(messageJson); 78 | OnMessage?.Invoke(this, inboundMessage); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /src/SlackConnector/ConsoleLoggingLevel.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector 2 | { 3 | public enum ConsoleLoggingLevel 4 | { 5 | None = 0, 6 | FatalErrors, 7 | All 8 | } 9 | } -------------------------------------------------------------------------------- /src/SlackConnector/EventHandlers/ChannelCreatedHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SlackConnector.Models; 3 | 4 | namespace SlackConnector.EventHandlers 5 | { 6 | public delegate Task ChannelCreatedHandler(SlackChannelCreated chatHub); 7 | } 8 | -------------------------------------------------------------------------------- /src/SlackConnector/EventHandlers/ChatHubJoinedEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SlackConnector.Models; 3 | 4 | namespace SlackConnector.EventHandlers 5 | { 6 | public delegate Task ChatHubJoinedEventHandler(SlackChatHub chatHub); 7 | } 8 | -------------------------------------------------------------------------------- /src/SlackConnector/EventHandlers/DisconnectEventHandler.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.EventHandlers 2 | { 3 | public delegate void DisconnectEventHandler(); 4 | } -------------------------------------------------------------------------------- /src/SlackConnector/EventHandlers/MessageReceivedEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SlackConnector.Models; 3 | 4 | namespace SlackConnector.EventHandlers 5 | { 6 | public delegate Task MessageReceivedEventHandler(SlackMessage message); 7 | } -------------------------------------------------------------------------------- /src/SlackConnector/EventHandlers/PongEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace SlackConnector.EventHandlers 5 | { 6 | public delegate Task PongEventHandler(DateTime timestamp); 7 | } 8 | -------------------------------------------------------------------------------- /src/SlackConnector/EventHandlers/ReactionRecievedEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SlackConnector.Models; 3 | 4 | namespace SlackConnector.EventHandlers 5 | { 6 | public delegate Task ReactionReceivedEventHandler(ISlackReaction message); 7 | } -------------------------------------------------------------------------------- /src/SlackConnector/EventHandlers/ReconnectEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace SlackConnector.EventHandlers 4 | { 5 | public delegate Task ReconnectEventHandler(); 6 | } -------------------------------------------------------------------------------- /src/SlackConnector/EventHandlers/UserJoinedEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SlackConnector.Models; 3 | 4 | namespace SlackConnector.EventHandlers 5 | { 6 | public delegate Task UserJoinedEventHandler(SlackUser user); 7 | } 8 | -------------------------------------------------------------------------------- /src/SlackConnector/Exceptions/CommunicationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace SlackConnector.Exceptions 5 | { 6 | public class CommunicationException : Exception 7 | { 8 | public CommunicationException() 9 | { } 10 | 11 | public CommunicationException(string message) : base(message) 12 | { } 13 | 14 | public CommunicationException(string message, Exception innerException) : base(message, innerException) 15 | { } 16 | } 17 | } -------------------------------------------------------------------------------- /src/SlackConnector/Exceptions/ConnectionTimeout.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SlackConnector.Exceptions 4 | { 5 | public class ConnectionTimeout : Exception 6 | { 7 | public ConnectionTimeout(string message) 8 | : base(message) 9 | { } 10 | } 11 | } -------------------------------------------------------------------------------- /src/SlackConnector/Exceptions/HandshakeException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace SlackConnector.Exceptions 5 | { 6 | public class HandshakeException : Exception 7 | { 8 | public HandshakeException() 9 | { } 10 | 11 | public HandshakeException(string message) : base(message) 12 | { } 13 | 14 | public HandshakeException(string message, Exception innerException) : base(message, innerException) 15 | { } 16 | } 17 | } -------------------------------------------------------------------------------- /src/SlackConnector/Exceptions/MissingChannelException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SlackConnector.Exceptions 4 | { 5 | public class MissingChannelException : Exception 6 | { 7 | public MissingChannelException() 8 | { } 9 | 10 | public MissingChannelException(string message) : base(message) 11 | { } 12 | 13 | public MissingChannelException(string message, Exception innerException) : base(message, innerException) 14 | { } 15 | } 16 | } -------------------------------------------------------------------------------- /src/SlackConnector/Extensions/ChannelExtensions.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Connections.Models; 2 | using SlackConnector.Models; 3 | 4 | namespace SlackConnector.Extensions 5 | { 6 | internal static class ChannelExtensions 7 | { 8 | public static SlackChatHub ToChatHub(this Channel channel) 9 | { 10 | var newChannel = new SlackChatHub 11 | { 12 | Id = channel.Id, 13 | Name = "#" + channel.Name, 14 | Type = SlackChatHubType.Channel, 15 | Members = channel.Members 16 | }; 17 | return newChannel; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/SlackConnector/Extensions/FileExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using SlackConnector.Connections.Sockets.Messages.Inbound; 5 | using SlackConnector.Models; 6 | 7 | namespace SlackConnector.Extensions 8 | { 9 | internal static class FileExtensions 10 | { 11 | public static IEnumerable ToSlackFiles(this IEnumerable file) 12 | { 13 | if (file == null) 14 | { 15 | return Enumerable.Empty(); 16 | } 17 | 18 | return file.Select(ToSlackFile); 19 | } 20 | 21 | private static SlackFile ToSlackFile(this File file) 22 | { 23 | if (file == null) 24 | return null; 25 | 26 | return new SlackFile( 27 | file.Id, 28 | file.Created, 29 | file.Timestamp, 30 | file.Name, 31 | file.Title, 32 | file.Mimetype, 33 | file.FileType, 34 | file.PrettyType, 35 | file.User, 36 | file.Editable, 37 | file.Size, 38 | file.Mode, 39 | file.IsExternal, 40 | file.ExternalType, 41 | file.IsPublic, 42 | file.PublicUrlShared, 43 | file.DisplayAsBot, 44 | file.Username, 45 | CreateUri(file.UrlPrivate), 46 | CreateUri(file.UrlPrivateDownload), 47 | file.ImageExifRotation, 48 | file.OriginalWidth, 49 | file.OriginalHeight, 50 | CreateUri(file.DeanimateGif), 51 | CreateUri(file.Permalink), 52 | CreateUri(file.PermalinkPublic), 53 | new SlackThumbnail( 54 | CreateUri(file.Thumb64), 55 | CreateUri(file.Thumb80), 56 | CreateUri(file.Thumb360), 57 | file.Thumb360Width, 58 | file.Thumb360Height, 59 | CreateUri(file.Thumb160), 60 | CreateUri(file.Thumb360Gif) 61 | ) 62 | ); 63 | } 64 | 65 | private static Uri CreateUri(string url) 66 | { 67 | return Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri) 68 | ? uri 69 | : null; 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/SlackConnector/Extensions/GroupExtensions.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Connections.Models; 2 | using SlackConnector.Models; 3 | 4 | namespace SlackConnector.Extensions 5 | { 6 | internal static class GroupExtensions 7 | { 8 | public static SlackChatHub ToChatHub(this Group group) 9 | { 10 | var newGroup = new SlackChatHub 11 | { 12 | Id = group.Id, 13 | Name = "#" + group.Name, 14 | Type = SlackChatHubType.Group, 15 | Members = group.Members 16 | }; 17 | 18 | return newGroup; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/SlackConnector/Extensions/ImExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using SlackConnector.Connections.Models; 3 | using SlackConnector.Models; 4 | 5 | namespace SlackConnector.Extensions 6 | { 7 | internal static class ImExtensions 8 | { 9 | public static SlackChatHub ToChatHub(this Im im, SlackUser[] users) 10 | { 11 | SlackUser user = users.FirstOrDefault(x => x.Id == im.User); 12 | return new SlackChatHub 13 | { 14 | Id = im.Id, 15 | Name = "@" + (user == null ? im.User : user.Name), 16 | Type = SlackChatHubType.DM 17 | }; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/SlackConnector/Extensions/MessageSubTypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Connections.Sockets.Messages.Inbound; 2 | using SlackConnector.Models; 3 | 4 | namespace SlackConnector.Extensions 5 | { 6 | internal static class MessageSubTypeExtensions 7 | { 8 | public static SlackMessageSubType ToSlackMessageSubType(this MessageSubType subType) 9 | { 10 | return (SlackMessageSubType)subType; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/SlackConnector/Extensions/UserExtensions.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Connections.Models; 2 | using SlackConnector.Models; 3 | 4 | namespace SlackConnector.Extensions 5 | { 6 | internal static class UserExtensions 7 | { 8 | public static SlackUser ToSlackUser(this User user) 9 | { 10 | var slackUser = new SlackUser 11 | { 12 | Id = user.Id, 13 | Name = user.Name, 14 | Email = user.Profile?.Email, 15 | TimeZoneOffset = user.TimeZoneOffset, 16 | IsBot = user.IsBot, 17 | FirstName = user.Profile?.FirstName, 18 | LastName = user.Profile?.LastName, 19 | Image = user.Profile?.Image, 20 | WhatIDo = user.Profile?.Title, 21 | Deleted = user.Deleted, 22 | IsGuest = user.IsGuest, 23 | StatusText = user.Profile?.StatusText, 24 | IsAdmin = user.IsAdmin 25 | }; 26 | 27 | return slackUser; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/SlackConnector/ISlackConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SlackConnector.Models; 3 | 4 | namespace SlackConnector 5 | { 6 | internal interface ISlackConnectionFactory 7 | { 8 | Task Create(ConnectionInformation connectionInformation); 9 | } 10 | } -------------------------------------------------------------------------------- /src/SlackConnector/ISlackConnector.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace SlackConnector 4 | { 5 | public interface ISlackConnector 6 | { 7 | Task Connect(string slackKey); 8 | } 9 | } -------------------------------------------------------------------------------- /src/SlackConnector/Internals.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | [assembly: InternalsVisibleTo("SlackConnector.Tests.Unit")] 3 | [assembly: InternalsVisibleTo("SlackConnector.Tests.Integration")] 4 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] -------------------------------------------------------------------------------- /src/SlackConnector/Logging/ILogger.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Logging 2 | { 3 | public interface ILogger 4 | { 5 | void LogError(string message); 6 | } 7 | } -------------------------------------------------------------------------------- /src/SlackConnector/Logging/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SlackConnector.Logging 4 | { 5 | public class Logger : ILogger 6 | { 7 | public void LogError(string message) 8 | { 9 | Console.WriteLine(message); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/BotMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SlackConnector.Models 4 | { 5 | public class BotMessage 6 | { 7 | public IList Attachments { get; set; } 8 | public SlackChatHub ChatHub { get; set; } 9 | public string Text { get; set; } 10 | 11 | public BotMessage() 12 | { 13 | Attachments = new List(); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/ConnectionInformation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using SlackConnector.Connections.Sockets; 3 | 4 | namespace SlackConnector.Models 5 | { 6 | internal class ConnectionInformation 7 | { 8 | public string SlackKey { get; set; } 9 | public ContactDetails Self { get; set; } = new ContactDetails(); 10 | public ContactDetails Team { get; set; } = new ContactDetails(); 11 | public Dictionary Users { get; set; } = new Dictionary(); 12 | public Dictionary SlackChatHubs { get; set; } = new Dictionary(); 13 | public IWebSocketClient WebSocket { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/ContactDetails.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Models 2 | { 3 | public class ContactDetails 4 | { 5 | public string Id { get; internal set; } 6 | public string Name { get; internal set; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/ISlackReaction.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Models 2 | { 3 | public interface ISlackReaction 4 | { 5 | string RawData { get; } 6 | SlackUser User { get; } 7 | double Timestamp { get; } 8 | string Reaction { get; } 9 | SlackUser ReactingToUser { get; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SlackConnector/Models/Reactions/SlackFileCommentReaction.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Models.Reactions 2 | { 3 | public class SlackFileCommentReaction : ISlackReaction 4 | { 5 | public string RawData { get; internal set; } 6 | public SlackUser User { get; internal set; } 7 | public double Timestamp { get; internal set; } 8 | public string Reaction { get; internal set; } 9 | public string File { get; internal set; } 10 | public string FileComment { get; internal set; } 11 | public SlackUser ReactingToUser { get; internal set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/Reactions/SlackFileReaction.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Models.Reactions 2 | { 3 | public class SlackFileReaction : ISlackReaction 4 | { 5 | public string RawData { get; internal set; } 6 | public SlackUser User { get; internal set; } 7 | public double Timestamp { get; internal set; } 8 | public string Reaction { get; internal set; } 9 | public string File { get; internal set; } 10 | public SlackUser ReactingToUser { get; internal set; } 11 | } 12 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/Reactions/SlackMessageReaction.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Models.Reactions 2 | { 3 | public class SlackMessageReaction : ISlackReaction 4 | { 5 | public SlackChatHub ChatHub { get; internal set; } 6 | public string RawData { get; internal set; } 7 | public SlackUser User { get; internal set; } 8 | public double Timestamp { get; internal set; } 9 | public string Reaction { get; internal set; } 10 | public SlackUser ReactingToUser { get; internal set; } 11 | } 12 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/Reactions/SlackUnknownReaction.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Models.Reactions 2 | { 3 | public class SlackUnknownReaction : ISlackReaction 4 | { 5 | public string RawData { get; internal set; } 6 | public SlackUser User { get; internal set; } 7 | public double Timestamp { get; internal set; } 8 | public string Reaction { get; internal set; } 9 | public SlackUser ReactingToUser { get; internal set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SlackConnector/Models/SlackAttachment.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | 4 | namespace SlackConnector.Models 5 | { 6 | public partial class SlackAttachment 7 | { 8 | [JsonProperty(PropertyName = "fallback")] 9 | public string Fallback { get; set; } 10 | 11 | [JsonProperty(PropertyName = "color")] 12 | public string ColorHex { get; set; } 13 | 14 | [JsonProperty(PropertyName = "pretext")] 15 | public string PreText { get; set; } 16 | 17 | [JsonProperty(PropertyName = "author_name")] 18 | public string AuthorName { get; set; } 19 | 20 | [JsonProperty(PropertyName = "author_link")] 21 | public string AuthorLink { get; set; } 22 | 23 | [JsonProperty(PropertyName = "author_icon")] 24 | public string AuthorIcon { get; set; } 25 | 26 | [JsonProperty(PropertyName = "title")] 27 | public string Title { get; set; } 28 | 29 | [JsonProperty(PropertyName = "title_link")] 30 | public string TitleLink { get; set; } 31 | 32 | [JsonProperty(PropertyName = "text")] 33 | public string Text { get; set; } 34 | 35 | [JsonProperty(PropertyName = "fields")] 36 | public IList Fields { get; set; } 37 | 38 | [JsonProperty(PropertyName = "callback_id")] 39 | public string CallbackId { get; set; } 40 | 41 | [JsonProperty(PropertyName = "actions")] 42 | public IList Actions { get; set; } 43 | 44 | [JsonProperty(PropertyName = "image_url")] 45 | public string ImageUrl { get; set; } 46 | 47 | [JsonProperty(PropertyName = "thumb_url")] 48 | public string ThumbUrl { get; set; } 49 | 50 | [JsonProperty(PropertyName = "footer")] 51 | public string Footer { get; set; } 52 | 53 | [JsonProperty(PropertyName = "footer_icon")] 54 | public string FooterIcon { get; set; } 55 | 56 | [JsonProperty(PropertyName = "mrkdwn_in")] 57 | public List MarkdownIn { get; set; } 58 | 59 | public SlackAttachment() 60 | { 61 | Fields = new List(); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/SlackAttachmentAction.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SlackConnector.Models 4 | { 5 | public class SlackAttachmentAction 6 | { 7 | [JsonProperty(PropertyName = "name")] 8 | public string Name { get; set; } 9 | 10 | [JsonProperty(PropertyName = "text")] 11 | public string Text { get; set; } 12 | 13 | [JsonProperty(PropertyName = "style", NullValueHandling = NullValueHandling.Ignore)] 14 | public SlackAttachmentActionStyle? Style { get; set; } 15 | 16 | [JsonProperty(PropertyName = "type")] 17 | public string Type { get; set; } 18 | 19 | [JsonProperty(PropertyName = "value")] 20 | public string Value { get; set; } 21 | 22 | [JsonProperty(PropertyName = "url", NullValueHandling = NullValueHandling.Ignore)] 23 | public string Url { get; set; } 24 | 25 | 26 | public SlackAttachmentAction() 27 | { 28 | Type = "button"; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/SlackAttachmentActionStyle.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Converters; 3 | using System.Runtime.Serialization; 4 | 5 | namespace SlackConnector.Models 6 | { 7 | [JsonConverter(typeof(StringEnumConverter))] 8 | public enum SlackAttachmentActionStyle 9 | { 10 | [EnumMember(Value = "default")] 11 | Default, 12 | [EnumMember(Value = "primary")] 13 | Primary, 14 | [EnumMember(Value = "danger")] 15 | Danger 16 | } 17 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/SlackAttachmentField.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SlackConnector.Models 4 | { 5 | public class SlackAttachmentField 6 | { 7 | [JsonProperty(PropertyName = "short")] 8 | public bool IsShort { get; set; } 9 | 10 | [JsonProperty(PropertyName = "title")] 11 | public string Title { get; set; } 12 | 13 | [JsonProperty(PropertyName = "value")] 14 | public string Value { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/SlackAttachmentStatics.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SlackConnector.Models 4 | { 5 | public partial class SlackAttachment 6 | { 7 | public const string MARKDOWN_IN_PRETEXT = "pretext"; 8 | public const string MARKDOWN_IN_TEXT = "text"; 9 | public const string MARKDOWN_IN_FIELDS = "fields"; 10 | 11 | public static List GetAllMarkdownInTypes() 12 | { 13 | return new List 14 | { 15 | MARKDOWN_IN_FIELDS, 16 | MARKDOWN_IN_PRETEXT, 17 | MARKDOWN_IN_TEXT 18 | }; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/SlackChannelCreated.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Models 2 | { 3 | public class SlackChannelCreated 4 | { 5 | public string Id { get; internal set; } 6 | public string Name { get; internal set; } 7 | public SlackUser Creator { get; internal set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/SlackChatHub.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Models 2 | { 3 | /// 4 | /// This represents a place in Slack where people can chat - typically a channel, group, or DM. 5 | /// 6 | public class SlackChatHub 7 | { 8 | public string Id { get; set; } 9 | public string Name { get; set; } 10 | public SlackChatHubType Type { get; set; } 11 | public string[] Members { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/SlackChatHubType.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Models 2 | { 3 | public enum SlackChatHubType 4 | { 5 | DM, 6 | Channel, 7 | Group 8 | } 9 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/SlackFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SlackConnector.Models 4 | { 5 | public class SlackFile 6 | { 7 | public string Id { get; } 8 | public int Created { get; } 9 | public int Timestamp { get; } 10 | public string Name { get; } 11 | public string Title { get; } 12 | public string Mimetype { get; } 13 | public string FileType { get; } 14 | public string PrettyType { get; } 15 | public string User { get; } 16 | public bool Editable { get; } 17 | public int Size { get; } 18 | public string Mode { get; } 19 | public bool IsExternal { get; } 20 | public string ExternalType { get; } 21 | public bool IsPublic { get; } 22 | public bool PublicUrlShared { get; } 23 | public bool DisplayAsBot { get; } 24 | public string Username { get; } 25 | public Uri UrlPrivate { get; } 26 | public Uri UrlPrivateDownload { get; } 27 | public int ImageExifRotation { get; } 28 | public int OriginalWidth { get; } 29 | public int OriginalHeight { get; } 30 | public Uri DeanimateGif { get; } 31 | public Uri Permalink { get; } 32 | public Uri PermalinkPublic { get; } 33 | public SlackThumbnail Thumbnail { get; } 34 | 35 | public SlackFile( 36 | string id, 37 | int created, 38 | int timestamp, 39 | string name, 40 | string title, 41 | string mimetype, 42 | string fileType, 43 | string prettyType, 44 | string user, 45 | bool editable, 46 | int size, 47 | string mode, 48 | bool isExternal, 49 | string externalType, 50 | bool isPublic, 51 | bool publicUrlShared, 52 | bool displayAsBot, 53 | string username, 54 | Uri urlPrivate, 55 | Uri urlPrivateDownload, 56 | int imageExifRotation, 57 | int originalWidth, 58 | int originalHeight, 59 | Uri deanimateGif, 60 | Uri permalink, 61 | Uri permalinkPublic, 62 | SlackThumbnail thumbnail) 63 | { 64 | Id = id; 65 | Created = created; 66 | Timestamp = timestamp; 67 | Name = name; 68 | Title = title; 69 | Mimetype = mimetype; 70 | FileType = fileType; 71 | PrettyType = prettyType; 72 | User = user; 73 | Editable = editable; 74 | Size = size; 75 | Mode = mode; 76 | IsExternal = isExternal; 77 | ExternalType = externalType; 78 | IsPublic = isPublic; 79 | PublicUrlShared = publicUrlShared; 80 | DisplayAsBot = displayAsBot; 81 | Username = username; 82 | UrlPrivate = urlPrivate; 83 | UrlPrivateDownload = urlPrivateDownload; 84 | ImageExifRotation = imageExifRotation; 85 | OriginalWidth = originalWidth; 86 | OriginalHeight = originalHeight; 87 | DeanimateGif = deanimateGif; 88 | Permalink = permalink; 89 | PermalinkPublic = permalinkPublic; 90 | Thumbnail = thumbnail; 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/SlackMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SlackConnector.Models 4 | { 5 | public class SlackMessage 6 | { 7 | public SlackChatHub ChatHub { get; set; } 8 | public bool MentionsBot { get; set; } 9 | public string RawData { get; set; } 10 | public string Text { get; set; } 11 | public SlackUser User { get; set; } 12 | public double Timestamp { get; set; } 13 | public SlackMessageSubType MessageSubType { get; set; } 14 | public IEnumerable Files { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/SlackMessageSubType.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Models 2 | { 3 | public enum SlackMessageSubType 4 | { 5 | Unknown = 0, 6 | BotMessage, 7 | MeMessage, 8 | MessageChanged, 9 | MessageDeleted, 10 | ChannelJoin, 11 | ChannelLeave, 12 | ChannelTopic, 13 | ChannelPurpose, 14 | ChannelName, 15 | ChannelArchive, 16 | ChannelUnarchive, 17 | GroupJoin, 18 | GroupLeave, 19 | GroupTopic, 20 | GroupPurpose, 21 | GroupName, 22 | GroupArchive, 23 | GroupUnarchive, 24 | FileShare, 25 | FileComment, 26 | FileMention, 27 | PinnedItem, 28 | UnpinnedItem 29 | } 30 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/SlackPurpose.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Models 2 | { 3 | /// 4 | /// This represents the purpose value of the requested channel. 5 | /// 6 | public class SlackPurpose 7 | { 8 | public string ChannelName { get; set; } 9 | public string Purpose { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SlackConnector/Models/SlackThumbnail.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SlackConnector.Models 4 | { 5 | public class SlackThumbnail 6 | { 7 | public Uri Thumb64 { get; } 8 | public Uri Thumb80 { get; } 9 | public Uri Thumb360 { get; } 10 | public int Thumb360Width { get; } 11 | public int Thumb360Height { get; } 12 | public Uri Thumb160 { get; } 13 | public Uri Thumb360Gif { get; } 14 | 15 | public SlackThumbnail( 16 | Uri thumb64, 17 | Uri thumb80, 18 | Uri thumb360, 19 | int thumb360Width, 20 | int thumb360Height, 21 | Uri thumb160, 22 | Uri thumb360Gif) 23 | { 24 | Thumb64 = thumb64; 25 | Thumb80 = thumb80; 26 | Thumb360 = thumb360; 27 | Thumb360Width = thumb360Width; 28 | Thumb360Height = thumb360Height; 29 | Thumb160 = thumb160; 30 | Thumb360Gif = thumb360Gif; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/SlackConnector/Models/SlackTopic.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Models 2 | { 3 | /// 4 | /// This represents the topic value of the requested channel. 5 | /// 6 | public class SlackTopic 7 | { 8 | public string ChannelName { get; set; } 9 | public string Topic { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SlackConnector/Models/SlackUser.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Models 2 | { 3 | public class SlackUser 4 | { 5 | public string Id { get; set; } 6 | public string Name { get; set; } 7 | public string Email { get; set; } 8 | public string FirstName { get; set; } 9 | public string LastName { get; set; } 10 | public string Image { get; set; } 11 | public string WhatIDo { get; set; } 12 | public bool Deleted { get; set; } 13 | public long TimeZoneOffset { get; set; } 14 | public bool IsBot { get; set; } 15 | public bool IsGuest { get; set; } 16 | public string StatusText { get; set; } 17 | public bool IsAdmin { get; set; } 18 | 19 | public string FormattedUserId 20 | { 21 | get 22 | { 23 | if (!string.IsNullOrEmpty(Id)) 24 | { 25 | return "<@" + Id + ">"; 26 | } 27 | return string.Empty; 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/SlackConnector/Serialising/EnumConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Newtonsoft.Json; 4 | 5 | namespace SlackConnector.Serialising 6 | { 7 | internal class EnumConverter : JsonConverter 8 | { 9 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 10 | { 11 | throw new NotImplementedException(); 12 | } 13 | 14 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 15 | { 16 | object result = null; 17 | if (objectType.GetTypeInfo().IsEnum && reader.Value != null) 18 | { 19 | result = Activator.CreateInstance(objectType); 20 | 21 | try 22 | { 23 | result = Enum.Parse(objectType, reader.Value.ToString(), true); 24 | } 25 | catch (ArgumentException) 26 | { } 27 | } 28 | 29 | return result; 30 | } 31 | 32 | public override bool CanConvert(Type objectType) 33 | { 34 | return objectType.GetTypeInfo().IsEnum; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/SlackConnector/SlackConnectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using SlackConnector.Models; 5 | 6 | namespace SlackConnector 7 | { 8 | public static class SlackConnectionExtensions 9 | { 10 | /// 11 | /// Will try and find a direct messag the bot is connected to with that user name, e.g. simon 12 | /// 13 | public static SlackChatHub ConnectedDM(this ISlackConnection slackConnection, string userName) 14 | { 15 | return slackConnection.ConnectedDMs().FirstOrDefault(x => x.Name.Equals(userName, StringComparison.OrdinalIgnoreCase)); 16 | } 17 | 18 | public static IEnumerable ConnectedDMs(this ISlackConnection slackConnection) 19 | { 20 | return slackConnection.ConnectedHubs.Values.Where(hub => hub.Type == SlackChatHubType.DM); 21 | } 22 | 23 | /// 24 | /// Will try and find a channel the bot is connected to with that name, e.g. #general 25 | /// 26 | public static SlackChatHub ConnectedChannel(this ISlackConnection slackConnection, string channelName) 27 | { 28 | return slackConnection.ConnectedChannels().FirstOrDefault(x => x.Name.Equals(channelName, StringComparison.OrdinalIgnoreCase)); 29 | } 30 | 31 | public static IEnumerable ConnectedChannels(this ISlackConnection slackConnection) 32 | { 33 | return slackConnection.ConnectedHubs.Values.Where(hub => hub.Type == SlackChatHubType.Channel); 34 | } 35 | 36 | /// 37 | /// Will try and find a channel the bot is connected to with that name, e.g. #group 38 | /// 39 | public static SlackChatHub ConnectedGroup(this ISlackConnection slackConnection, string groupName) 40 | { 41 | return slackConnection.ConnectedGroups().FirstOrDefault(x => x.Name.Equals(groupName, StringComparison.OrdinalIgnoreCase)); 42 | } 43 | 44 | public static IEnumerable ConnectedGroups(this ISlackConnection slackConnection) 45 | { 46 | return slackConnection.ConnectedHubs.Values.Where(hub => hub.Type == SlackChatHubType.Group); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/SlackConnector/SlackConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SlackConnector.BotHelpers; 3 | using SlackConnector.Connections; 4 | using SlackConnector.Connections.Monitoring; 5 | using SlackConnector.Models; 6 | 7 | namespace SlackConnector 8 | { 9 | internal class SlackConnectionFactory : ISlackConnectionFactory 10 | { 11 | public async Task Create(ConnectionInformation connectionInformation) 12 | { 13 | var slackConnection = new SlackConnection(new ConnectionFactory(), new MentionDetector(), new MonitoringFactory()); 14 | await slackConnection.Initialise(connectionInformation); 15 | return slackConnection; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/SlackConnector/SlackConnector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using SlackConnector.Connections; 6 | using SlackConnector.Connections.Models; 7 | using SlackConnector.Connections.Responses; 8 | using SlackConnector.Connections.Sockets; 9 | using SlackConnector.Exceptions; 10 | using SlackConnector.Extensions; 11 | using SlackConnector.Models; 12 | 13 | namespace SlackConnector 14 | { 15 | public class SlackConnector : ISlackConnector 16 | { 17 | public static ConsoleLoggingLevel LoggingLevel = ConsoleLoggingLevel.None; 18 | 19 | private readonly IConnectionFactory _connectionFactory; 20 | private readonly ISlackConnectionFactory _slackConnectionFactory; 21 | 22 | public SlackConnector() : this(new ConnectionFactory(), new SlackConnectionFactory()) 23 | { } 24 | 25 | internal SlackConnector(IConnectionFactory connectionFactory, ISlackConnectionFactory slackConnectionFactory) 26 | { 27 | _connectionFactory = connectionFactory; 28 | _slackConnectionFactory = slackConnectionFactory; 29 | } 30 | 31 | public async Task Connect(string slackKey) 32 | { 33 | if (string.IsNullOrEmpty(slackKey)) 34 | { 35 | throw new ArgumentNullException(nameof(slackKey)); 36 | } 37 | 38 | var handshakeClient = _connectionFactory.CreateHandshakeClient(); 39 | HandshakeResponse handshakeResponse = await handshakeClient.FirmShake(slackKey); 40 | 41 | if (!handshakeResponse.Ok) 42 | { 43 | throw new HandshakeException(handshakeResponse.Error); 44 | } 45 | 46 | Dictionary users = GenerateUsers(handshakeResponse.Users); 47 | 48 | var connectionInfo = new ConnectionInformation 49 | { 50 | SlackKey = slackKey, 51 | Self = new ContactDetails { Id = handshakeResponse.Self.Id, Name = handshakeResponse.Self.Name }, 52 | Team = new ContactDetails { Id = handshakeResponse.Team.Id, Name = handshakeResponse.Team.Name }, 53 | Users = users, 54 | SlackChatHubs = GetChatHubs(handshakeResponse, users.Values.ToArray()), 55 | WebSocket = await _connectionFactory.CreateWebSocketClient(handshakeResponse.WebSocketUrl) 56 | }; 57 | 58 | var connection = await _slackConnectionFactory.Create(connectionInfo); 59 | return connection; 60 | } 61 | 62 | private Dictionary GenerateUsers(User[] users) 63 | { 64 | return users.ToDictionary(user => user.Id, user => user.ToSlackUser()); 65 | } 66 | 67 | private Dictionary GetChatHubs(HandshakeResponse handshakeResponse, SlackUser[] users) 68 | { 69 | var hubs = new Dictionary(); 70 | 71 | foreach (Channel channel in handshakeResponse.Channels.Where(x => !x.IsArchived)) 72 | { 73 | if (channel.IsMember) 74 | { 75 | var newChannel = channel.ToChatHub(); 76 | hubs.Add(channel.Id, newChannel); 77 | } 78 | } 79 | 80 | foreach (Group group in handshakeResponse.Groups.Where(x => !x.IsArchived)) 81 | { 82 | if ((group.Members ?? new string[0]).Any(x => x == handshakeResponse.Self.Id)) 83 | { 84 | var newGroup = group.ToChatHub(); 85 | hubs.Add(group.Id, newGroup); 86 | } 87 | } 88 | 89 | foreach (Im im in handshakeResponse.Ims) 90 | { 91 | hubs.Add(im.Id, im.ToChatHub(users)); 92 | } 93 | 94 | return hubs; 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /src/SlackConnector/SlackConnector.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net461 5 | 1.0.1 6 | 1.0.0.1 7 | 1.0.0.1 8 | Slack API SlackApi MargieBot SlackConnector noobot bot slackbot 9 | SlackConnector initiates an open connection between you and the Slack api servers. SlackConnector uses web-sockets to allow real-time messages to be received and handled within your application. 10 | Workshop2 11 | https://github.com/noobot/slackconnector 12 | https://github.com/noobot/SlackConnector/blob/master/LICENSE 13 | 14 | Workshop2 15 | https://github.com/noobot/slackconnector 16 | 17 | 18 | 19 | true 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/Configuration/Config.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Tests.Integration.Configuration 2 | { 3 | public class Config 4 | { 5 | public SlackConfig Slack { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/Configuration/ConfigReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using Newtonsoft.Json; 5 | 6 | namespace SlackConnector.Tests.Integration.Configuration 7 | { 8 | public class ConfigReader : IConfigReader 9 | { 10 | private Config Current { get; set; } 11 | 12 | public Config GetConfig() 13 | { 14 | if (Current == null) 15 | { 16 | string fileName = Path.Combine(GetAssemblyDirectory(), "configuration", "config.json"); 17 | if (!File.Exists(fileName)) 18 | { 19 | throw new InvalidConfiguration("Unable to load config file from: " + fileName); 20 | } 21 | 22 | string json = File.ReadAllText(fileName); 23 | if (string.IsNullOrEmpty(json)) 24 | { 25 | throw new InvalidConfiguration("Unable to load config"); 26 | } 27 | 28 | Current = JsonConvert.DeserializeObject(json); 29 | } 30 | 31 | if (string.IsNullOrEmpty(Current?.Slack?.ApiToken)) 32 | { 33 | throw new InvalidConfiguration("Slack API is missing"); 34 | } 35 | 36 | return Current; 37 | } 38 | 39 | public static string GetAssemblyDirectory() 40 | { 41 | string codeBase = Assembly.GetExecutingAssembly().CodeBase; 42 | UriBuilder uri = new UriBuilder(codeBase); 43 | string path = Uri.UnescapeDataString(uri.Path); 44 | return Path.GetDirectoryName(path); 45 | } 46 | } 47 | 48 | public class InvalidConfiguration : Exception 49 | { 50 | public InvalidConfiguration(string message) : base(message) 51 | { } 52 | } 53 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/Configuration/IConfigReader.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Tests.Integration.Configuration 2 | { 3 | public interface IConfigReader 4 | { 5 | Config GetConfig(); 6 | } 7 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/Configuration/RunnableInDebugOnlyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Xunit; 3 | 4 | namespace SlackConnector.Tests.Integration.Configuration 5 | { 6 | public class RunnableInDebugOnlyAttribute : FactAttribute 7 | { 8 | public RunnableInDebugOnlyAttribute() 9 | { 10 | if (!Debugger.IsAttached) 11 | { 12 | Skip = "Only running in interactive mode."; 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/Configuration/SlackConfig.cs: -------------------------------------------------------------------------------- 1 | namespace SlackConnector.Tests.Integration.Configuration 2 | { 3 | public class SlackConfig 4 | { 5 | public string ApiToken { get; set; } 6 | public string TestUserName { get; set; } 7 | public string TestChannel { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/Configuration/config.default.json: -------------------------------------------------------------------------------- 1 | { 2 | "slack": { 3 | "apiToken": "", 4 | "testUserName": "", 5 | "testChannel": "#general", 6 | "testFile": "Configuration\\UploadTest.txt" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/Connections/Clients/FlurlHandshakeClientTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SlackConnector.Connections.Clients; 3 | using SlackConnector.Connections.Clients.Handshake; 4 | using SlackConnector.Connections.Responses; 5 | using SlackConnector.Tests.Integration.Configuration; 6 | using Xunit; 7 | using Shouldly; 8 | 9 | namespace SlackConnector.Tests.Integration.Connections.Clients 10 | { 11 | public class FlurlHandshakeClientTests 12 | { 13 | [Fact] 14 | public async Task should_perform_handshake_with_flurl() 15 | { 16 | // given 17 | var config = new ConfigReader().GetConfig(); 18 | var client = new FlurlHandshakeClient(new ResponseVerifier()); 19 | 20 | // when 21 | HandshakeResponse response = await client.FirmShake(config.Slack.ApiToken); 22 | 23 | // then 24 | response.ShouldNotBeNull(); 25 | response.WebSocketUrl.ShouldNotBeEmpty(); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/FileDownloadTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Shouldly; 4 | using SixLabors.ImageSharp; 5 | using Xunit; 6 | 7 | namespace SlackConnector.Tests.Integration 8 | { 9 | public class FileDownloadTests : IntegrationTest 10 | { 11 | [Fact] 12 | public async Task should_download_file() 13 | { 14 | // given 15 | var uri = new Uri("https://files.slack.com/files-pri/T3NFBGBAS-FBUSTA0P4/fuuuu.gif"); 16 | 17 | // when 18 | var download = await SlackConnection.DownloadFile(uri); 19 | 20 | // then 21 | using (download) 22 | { 23 | download.ShouldNotBeNull(); 24 | download.Length.ShouldBeGreaterThan(0); 25 | 26 | var image = Image.Load(download); 27 | image.Width.ShouldBe(43); 28 | image.Height.ShouldBe(29); 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/FileUploadTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using SlackConnector.Tests.Integration.Resources; 4 | using Xunit; 5 | 6 | namespace SlackConnector.Tests.Integration 7 | { 8 | public class FileUploadTests : IntegrationTest 9 | { 10 | private readonly string _filePath; 11 | 12 | public FileUploadTests() 13 | { 14 | _filePath = Path.GetTempFileName(); 15 | File.WriteAllText(_filePath, EmbeddedResourceFileReader.ReadEmbeddedFileAsText("UploadTest.txt")); 16 | } 17 | 18 | public override void Dispose() 19 | { 20 | base.Dispose(); 21 | File.Delete(_filePath); 22 | } 23 | 24 | [Fact] 25 | public async Task should_upload_to_channel_from_file_system() 26 | { 27 | // given 28 | var chatHub = SlackConnection.ConnectedChannel(Config.Slack.TestChannel); 29 | 30 | // when 31 | await SlackConnection.Upload(chatHub, _filePath); 32 | 33 | // then 34 | } 35 | 36 | [Fact] 37 | public async Task should_upload_to_channel_from_stream() 38 | { 39 | // given 40 | var chatHub = SlackConnection.ConnectedChannel(Config.Slack.TestChannel); 41 | const string fileName = "slackconnector-test-stream-upload.txt"; 42 | 43 | // when 44 | using (var fileStream = EmbeddedResourceFileReader.ReadEmbeddedFile("UploadTest.txt")) 45 | { 46 | await SlackConnection.Upload(chatHub, fileStream, fileName); 47 | } 48 | 49 | // then 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/IntegrationTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using SlackConnector.Tests.Integration.Configuration; 4 | 5 | namespace SlackConnector.Tests.Integration 6 | { 7 | public abstract class IntegrationTest : IDisposable 8 | { 9 | protected ISlackConnection SlackConnection; 10 | protected Config Config; 11 | 12 | protected IntegrationTest() 13 | { 14 | Config = new ConfigReader().GetConfig(); 15 | 16 | var slackConnector = new SlackConnector(); 17 | SlackConnection = Task.Run(() => slackConnector.Connect(Config.Slack.ApiToken)) 18 | .GetAwaiter() 19 | .GetResult(); 20 | } 21 | 22 | public virtual void Dispose() 23 | { 24 | Task.Run(() => SlackConnection.Close()) 25 | .GetAwaiter() 26 | .GetResult(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/JoinDmChannelTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using SlackConnector.Models; 5 | using SlackConnector.Tests.Integration.Configuration; 6 | using Xunit; 7 | using Shouldly; 8 | 9 | namespace SlackConnector.Tests.Integration 10 | { 11 | public class JoinDmChannelTests : IntegrationTest 12 | { 13 | [Fact] 14 | public async Task should_join_channel() 15 | { 16 | // given 17 | if (string.IsNullOrEmpty(Config.Slack.TestUserName)) 18 | { 19 | throw new InvalidConfiguration("TestUserName is missing from config"); 20 | } 21 | 22 | var users = await SlackConnection.GetUsers(); 23 | string userId = users.First(x => x.Name.Equals(Config.Slack.TestUserName, StringComparison.InvariantCultureIgnoreCase)).Id; 24 | 25 | // when 26 | SlackChatHub result = await SlackConnection.JoinDirectMessageChannel(userId); 27 | 28 | // then 29 | result.ShouldNotBeNull(); 30 | 31 | var dmChannel = SlackConnection.ConnectedDM($"@{Config.Slack.TestUserName}"); 32 | dmChannel.ShouldNotBeNull(); 33 | await SlackConnection.Say(new BotMessage { ChatHub = dmChannel, Text = "Wuzzup - testing in da haus" }); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/PingPongTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | using Shouldly; 6 | 7 | namespace SlackConnector.Tests.Integration 8 | { 9 | public class PingPongTests : IntegrationTest 10 | { 11 | [Fact] 12 | public async Task should_pong_to_our_ping() 13 | { 14 | // given 15 | bool hasPonged = false; 16 | SlackConnection.OnPong += timestamp => { hasPonged = true; return Task.CompletedTask;}; 17 | 18 | // when 19 | await SlackConnection.Ping(); 20 | 21 | // then 22 | for (int i = 0; i < 10; i++) 23 | { 24 | if (hasPonged) 25 | { 26 | break; 27 | } 28 | 29 | Thread.Sleep(TimeSpan.FromSeconds(1)); 30 | } 31 | 32 | hasPonged.ShouldBeTrue(); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/Resources/EmbeddedResourceFileReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | 5 | namespace SlackConnector.Tests.Integration.Resources 6 | { 7 | public static class EmbeddedResourceFileReader 8 | { 9 | public static string ReadEmbeddedFileAsText(string file) 10 | { 11 | using (var reader = new StreamReader(ReadEmbeddedFile(file))) 12 | { 13 | return reader.ReadToEnd(); 14 | } 15 | } 16 | 17 | public static Stream ReadEmbeddedFile(string file) 18 | { 19 | string resourcePath = $"{typeof(EmbeddedResourceFileReader).Namespace}.{file}"; 20 | 21 | Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourcePath); 22 | if (stream == null) 23 | { 24 | throw new InvalidOperationException($"Unable to find '{resourcePath}' as an embedded resource"); 25 | } 26 | 27 | return stream; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/Resources/UploadTest.txt: -------------------------------------------------------------------------------- 1 | This is the file we are using for integration testing. -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/SayTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using SlackConnector.Models; 4 | using Xunit; 5 | using Xunit.Abstractions; 6 | 7 | namespace SlackConnector.Tests.Integration 8 | { 9 | public class SayTests : IntegrationTest 10 | { 11 | private readonly ITestOutputHelper _output; 12 | 13 | public SayTests(ITestOutputHelper output) 14 | { 15 | _output = output; 16 | } 17 | 18 | [Fact] 19 | public async Task should_say_something_on_channel() 20 | { 21 | // given 22 | var message = new BotMessage 23 | { 24 | Text = "Test text for INT test", 25 | ChatHub = SlackConnection.ConnectedChannel(Config.Slack.TestChannel) 26 | }; 27 | 28 | // when 29 | await SlackConnection.Say(message); 30 | 31 | SlackConnection.OnMessageReceived += slackMessage => 32 | { 33 | _output.WriteLine(slackMessage.Text); 34 | return Task.CompletedTask; 35 | }; 36 | 37 | // then 38 | //await Task.Delay(TimeSpan.FromMinutes(2)); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/SlackConnector.Tests.Integration.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Always 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/SlackConnectorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading.Tasks; 4 | using SlackConnector.Models; 5 | using Xunit; 6 | using Shouldly; 7 | using SlackConnector.Tests.Integration.Configuration; 8 | 9 | namespace SlackConnector.Tests.Integration 10 | { 11 | public class SlackConnectorTests : IntegrationTest 12 | { 13 | [Fact] 14 | public async Task should_connect() 15 | { 16 | SlackConnection.IsConnected.ShouldBeTrue(); 17 | 18 | await SlackConnection.Close(); 19 | 20 | SlackConnection.IsConnected.ShouldBeFalse(); 21 | } 22 | 23 | [RunnableInDebugOnly] 24 | public async Task connect_and_wait_5_mins() 25 | { 26 | // given 27 | 28 | // when 29 | SlackConnection.OnDisconnect += SlackConnector_OnDisconnect; 30 | SlackConnection.OnMessageReceived += SlackConnectorOnMessageReceived; 31 | SlackConnection.OnReaction += SlackConnectionOnOnReaction; 32 | 33 | // then 34 | SlackConnection.IsConnected.ShouldBeTrue(); 35 | await Task.Delay(TimeSpan.FromMinutes(5)); 36 | 37 | // when 38 | await SlackConnection.Close(); 39 | 40 | SlackConnection.IsConnected.ShouldBeFalse(); 41 | } 42 | 43 | private Task SlackConnectionOnOnReaction(ISlackReaction message) 44 | { 45 | return Task.CompletedTask; 46 | } 47 | 48 | private void SlackConnector_OnDisconnect() 49 | { 50 | 51 | } 52 | 53 | private Task SlackConnectorOnMessageReceived(SlackMessage message) 54 | { 55 | Debug.WriteLine(message.Text); 56 | Console.WriteLine(message.Text); 57 | return Task.CompletedTask; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/SlackGetChannels.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Xunit; 4 | using Shouldly; 5 | 6 | namespace SlackConnector.Tests.Integration 7 | { 8 | public class SlackGetChannels : IntegrationTest 9 | { 10 | [Fact] 11 | public async Task should_connect_and_get_channels() 12 | { 13 | // given 14 | 15 | // when 16 | var channels = await SlackConnection.GetChannels(); 17 | 18 | // then 19 | channels.Any().ShouldBeTrue(); 20 | } 21 | 22 | [Fact] 23 | public async Task should_connect_and_get_users() 24 | { 25 | // given 26 | 27 | // when 28 | var users = await SlackConnection.GetUsers(); 29 | 30 | // then 31 | users.Any().ShouldBeTrue(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Integration/TypingIndicatorTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SlackConnector.Models; 3 | using Xunit; 4 | 5 | namespace SlackConnector.Tests.Integration 6 | { 7 | public class TypingIndicatorTests : IntegrationTest 8 | { 9 | [Fact] 10 | public async Task should_send_typing_indicator() 11 | { 12 | // given 13 | SlackChatHub channel = SlackConnection.ConnectedChannel(Config.Slack.TestChannel); 14 | 15 | // when 16 | await SlackConnection.IndicateTyping(channel); 17 | 18 | // then 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/AutoMoqDataAttribute.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using AutoFixture.AutoMoq; 3 | using AutoFixture.Xunit2; 4 | 5 | namespace SlackConnector.Tests.Unit 6 | { 7 | public class AutoMoqDataAttribute : AutoDataAttribute 8 | { 9 | public AutoMoqDataAttribute() 10 | : base(() => new Fixture().Customize(new AutoMoqCustomization())) 11 | {} 12 | } 13 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/BotHelpers/ChatHubInterpreterTests.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.BotHelpers; 2 | using SlackConnector.Models; 3 | using SlackConnector.Tests.Unit.TestExtensions; 4 | using Xunit; 5 | using Shouldly; 6 | 7 | namespace SlackConnector.Tests.Unit.BotHelpers 8 | { 9 | public class ChatHubInterpreterTests 10 | { 11 | [Theory] 12 | [InlineData("C-hannelId", SlackChatHubType.Channel)] 13 | [InlineData("D-Something", SlackChatHubType.DM)] 14 | [InlineData("G-roup", SlackChatHubType.Group)] 15 | public void given_hub_id_then_should_return_expected_slack_hub(string hubId, SlackChatHubType hubType) 16 | { 17 | // given 18 | 19 | // when 20 | var interpreter = new ChatHubInterpreter(); 21 | SlackChatHub chatHub = interpreter.FromId(hubId); 22 | 23 | // then 24 | var expected = new SlackChatHub 25 | { 26 | Id = hubId, 27 | Name = hubId, 28 | Type = hubType 29 | }; 30 | chatHub.ShouldLookLike(expected); 31 | } 32 | 33 | [Fact] 34 | public void shouldnt_return_slack_hub_if_type_cant_be_detected() 35 | { 36 | // given 37 | const string hubId = "SOMETHING THAT ISN'T CORRECT"; 38 | 39 | // when 40 | var interpreter = new ChatHubInterpreter(); 41 | SlackChatHub chatHub = interpreter.FromId(hubId); 42 | 43 | // then 44 | chatHub.ShouldBeNull(); 45 | } 46 | 47 | [Theory] 48 | [InlineData(null)] 49 | [InlineData("")] 50 | public void shouldnt_return_slack_hub_if_hub_id_is_null_or_empty(string hubId) 51 | { 52 | // given 53 | 54 | // when 55 | var interpreter = new ChatHubInterpreter(); 56 | SlackChatHub chatHub = interpreter.FromId(hubId); 57 | 58 | // then 59 | chatHub.ShouldBeNull(); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/BotHelpers/MentionDetectorTests.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.BotHelpers; 2 | using Xunit; 3 | using Shouldly; 4 | 5 | namespace SlackConnector.Tests.Unit.BotHelpers 6 | { 7 | public class MentionDetectorTests 8 | { 9 | [Theory] 10 | [InlineData("sir-name")] 11 | [InlineData("<@my-id>")] 12 | [InlineData("SIR-name")] 13 | [InlineData("Hello sir-name, how you doing?")] 14 | [InlineData("Hello SIR-name, how you doing?")] 15 | [InlineData("I love <@my-id>")] 16 | public void should_detect_when_mentioned(string messageText) 17 | { 18 | // given 19 | const string userId = "my-id"; 20 | const string userName = "sir-name"; 21 | 22 | // when 23 | var detector = new MentionDetector(); 24 | bool detected = detector.WasBotMentioned(userName, userId, messageText); 25 | 26 | // then 27 | detected.ShouldBeTrue(); 28 | } 29 | 30 | [Theory] 31 | [InlineData(null)] 32 | [InlineData("")] 33 | [InlineData("something unrelated")] 34 | public void should_not_detect_when_not_mentioned(string messageText) 35 | { 36 | // given 37 | const string userId = "my-id"; 38 | const string userName = "sir-name"; 39 | 40 | // when 41 | var detector = new MentionDetector(); 42 | bool detected = detector.WasBotMentioned(userName, userId, messageText); 43 | 44 | // then 45 | detected.ShouldBeFalse(); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/Connections/Clients/Flurl/FlurlChatClientTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Flurl; 5 | using Flurl.Http.Testing; 6 | using Moq; 7 | using Newtonsoft.Json; 8 | using SlackConnector.Connections.Clients; 9 | using SlackConnector.Connections.Clients.Chat; 10 | using SlackConnector.Connections.Responses; 11 | using SlackConnector.Models; 12 | using SlackConnector.Tests.Unit.TestExtensions; 13 | using Xunit; 14 | 15 | namespace SlackConnector.Tests.Unit.Connections.Clients.Flurl 16 | { 17 | public class FlurlChatClientTests : IDisposable 18 | { 19 | private readonly HttpTest _httpTest; 20 | private readonly Mock _responseVerifierMock; 21 | private readonly FlurlChatClient _chatClient; 22 | 23 | public FlurlChatClientTests() 24 | { 25 | _httpTest = new HttpTest(); 26 | _responseVerifierMock = new Mock(); 27 | _chatClient = new FlurlChatClient(_responseVerifierMock.Object); 28 | } 29 | 30 | public void Dispose() 31 | { 32 | _httpTest.Dispose(); 33 | } 34 | 35 | [Fact] 36 | public async Task should_call_expected_url_with_given_slack_key() 37 | { 38 | // given 39 | const string slackKey = "something-that-looks-like-a-slack-key"; 40 | const string channel = "channel-name"; 41 | const string text = "some-text-for-you"; 42 | 43 | var expectedResponse = new StandardResponse(); 44 | _httpTest.RespondWithJson(expectedResponse); 45 | 46 | // when 47 | await _chatClient.PostMessage(slackKey, channel, text, null); 48 | 49 | // then 50 | _responseVerifierMock.Verify(x => x.VerifyResponse(Looks.Like(expectedResponse))); 51 | _httpTest 52 | .ShouldHaveCalled(ClientConstants.SlackApiHost.AppendPathSegment(FlurlChatClient.SEND_MESSAGE_PATH)) 53 | .WithQueryParamValue("token", slackKey) 54 | .WithQueryParamValue("channel", channel) 55 | .WithQueryParamValue("text", text) 56 | .WithQueryParamValue("as_user", "true") 57 | .WithQueryParamValue("link_names", "true") 58 | .WithoutQueryParam("attachments") 59 | .Times(1); 60 | } 61 | 62 | [Fact] 63 | public async Task should_add_attachments_if_given() 64 | { 65 | // given 66 | _httpTest.RespondWithJson(new StandardResponse()); 67 | var attachments = new List 68 | { 69 | new SlackAttachment { Text = "dummy text" }, 70 | new SlackAttachment { AuthorName = "dummy author" }, 71 | }; 72 | 73 | // when 74 | await _chatClient.PostMessage(It.IsAny(), It.IsAny(), It.IsAny(), attachments); 75 | 76 | // then 77 | _httpTest 78 | .ShouldHaveCalled(ClientConstants.SlackApiHost.AppendPathSegment(FlurlChatClient.SEND_MESSAGE_PATH)) 79 | .WithQueryParamValue("attachments", JsonConvert.SerializeObject(attachments)) 80 | .Times(1); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/Connections/Clients/Flurl/FlurlFileClientTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using Flurl; 5 | using Flurl.Http.Testing; 6 | using Moq; 7 | using SlackConnector.Connections.Clients; 8 | using SlackConnector.Connections.Clients.File; 9 | using SlackConnector.Connections.Responses; 10 | using SlackConnector.Models; 11 | using SlackConnector.Tests.Unit.TestExtensions; 12 | using Xunit; 13 | 14 | namespace SlackConnector.Tests.Unit.Connections.Clients.Flurl 15 | { 16 | public class FlurlFileClientTests : IDisposable 17 | { 18 | private readonly HttpTest _httpTest; 19 | private readonly Mock _responseVerifierMock; 20 | private readonly FlurlFileClient _fileClient; 21 | 22 | public FlurlFileClientTests() 23 | { 24 | _httpTest = new HttpTest(); 25 | _responseVerifierMock = new Mock(); 26 | _fileClient = new FlurlFileClient(_responseVerifierMock.Object); 27 | } 28 | 29 | public void Dispose() 30 | { 31 | _httpTest.Dispose(); 32 | } 33 | 34 | [Fact] 35 | public async Task should_call_expected_url_with_given_slack_key() 36 | { 37 | // given 38 | const string slackKey = "something-that-looks-like-a-slack-key"; 39 | const string channel = "channel-name"; 40 | const string filePath = @"C:\test-file-name.exe"; 41 | 42 | var expectedResponse = new StandardResponse(); 43 | _httpTest.RespondWithJson(expectedResponse); 44 | 45 | // when 46 | await _fileClient.PostFile(slackKey, channel, filePath); 47 | 48 | // then 49 | _responseVerifierMock.Verify(x => x.VerifyResponse(Looks.Like(expectedResponse))); 50 | _httpTest 51 | .ShouldHaveCalled(ClientConstants.SlackApiHost.AppendPathSegment(FlurlFileClient.FILE_UPLOAD_PATH)) 52 | .WithQueryParamValue("token", slackKey) 53 | .WithQueryParamValue("channels", channel) 54 | .WithVerb(HttpMethod.Post) 55 | .WithContentType("multipart/form-data") 56 | .Times(1); 57 | } 58 | 59 | [Theory, AutoMoqData] 60 | public async Task should_download_file_with_given_slack_key( 61 | string slackKey, 62 | SlackFile slackFile, 63 | string path) 64 | { 65 | // given 66 | 67 | // when 68 | await _fileClient.DownloadFile(slackKey, slackFile, path); 69 | 70 | // then 71 | _httpTest 72 | .ShouldHaveCalled(slackFile.UrlPrivateDownload.AbsoluteUri) 73 | .WithOAuthBearerToken(slackKey) 74 | .Times(1); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/Connections/Clients/Flurl/FlurlHandshakeClientTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using ExpectedObjects; 4 | using Flurl; 5 | using Flurl.Http.Testing; 6 | using Moq; 7 | using SlackConnector.Connections.Clients; 8 | using SlackConnector.Connections.Clients.Handshake; 9 | using SlackConnector.Connections.Responses; 10 | using SlackConnector.Tests.Unit.TestExtensions; 11 | using Xunit; 12 | 13 | namespace SlackConnector.Tests.Unit.Connections.Clients.Flurl 14 | { 15 | public class FlurlHandshakeClientTests : IDisposable 16 | { 17 | private readonly HttpTest _httpTest; 18 | private readonly Mock _responseVerifierMock; 19 | private readonly FlurlHandshakeClient _handshakeClient; 20 | 21 | public FlurlHandshakeClientTests() 22 | { 23 | _httpTest = new HttpTest(); 24 | _responseVerifierMock = new Mock(); 25 | _handshakeClient = new FlurlHandshakeClient(_responseVerifierMock.Object); 26 | } 27 | 28 | public void Dispose() 29 | { 30 | _httpTest.Dispose(); 31 | } 32 | 33 | [Fact] 34 | public async Task should_call_expected_url_with_given_slack_key() 35 | { 36 | // given 37 | const string slackKey = "I-is-da-key-yeah"; 38 | 39 | var expectedResponse = new HandshakeResponse 40 | { 41 | Ok = true, 42 | WebSocketUrl = "some-url" 43 | }; 44 | _httpTest.RespondWithJson(expectedResponse); 45 | 46 | // when 47 | var result = await _handshakeClient.FirmShake(slackKey); 48 | 49 | // then 50 | _responseVerifierMock.Verify(x => x.VerifyResponse(Looks.Like(expectedResponse))); 51 | _httpTest 52 | .ShouldHaveCalled(ClientConstants.SlackApiHost.AppendPathSegment(FlurlHandshakeClient.HANDSHAKE_PATH)) 53 | .WithQueryParamValue("token", slackKey) 54 | .Times(1); 55 | 56 | result.ToExpectedObject().ShouldEqual(expectedResponse); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/Connections/Clients/ResponseVerifierTests.cs: -------------------------------------------------------------------------------- 1 | using SlackConnector.Connections.Clients; 2 | using SlackConnector.Connections.Responses; 3 | using SlackConnector.Exceptions; 4 | using Xunit; 5 | using Shouldly; 6 | 7 | namespace SlackConnector.Tests.Unit.Connections.Clients 8 | { 9 | public class ResponseVerifierTests 10 | { 11 | [Fact] 12 | public void should_throw_exception_with_given_error_message_when_request_failed() 13 | { 14 | // given 15 | var response = new StandardResponse { Ok = false, Error = "I AM A ERROR-message" }; 16 | var verifier = new ResponseVerifier(); 17 | 18 | // when && then 19 | var exception = Assert.Throws(() => verifier.VerifyResponse(response)); 20 | exception.Message.ShouldBe($"Error occured while posting message '{response.Error}'"); 21 | } 22 | 23 | [Fact] 24 | public void should_not_throw_exception() 25 | { 26 | // given 27 | var response = new StandardResponse { Ok = true }; 28 | var verifier = new ResponseVerifier(); 29 | 30 | // when && then 31 | verifier.VerifyResponse(response); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/Connections/Monitoring/DateTimeKeeperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using SlackConnector.Connections.Monitoring; 4 | using Xunit; 5 | using Shouldly; 6 | 7 | namespace SlackConnector.Tests.Unit.Connections.Monitoring 8 | { 9 | public class DateTimeKeeperTests 10 | { 11 | [Theory, AutoMoqData] 12 | private void should_throw_exception_if_get_time_is_called_before_being_set(DateTimeKeeper dateTimeKeeper) 13 | { 14 | Assert.Throws(() => dateTimeKeeper.TimeSinceDateTime()); 15 | dateTimeKeeper.HasDateTime().ShouldBeFalse(); 16 | } 17 | 18 | [Theory, AutoMoqData] 19 | private void should_return_time_since_if_datetime_has_been_set(DateTimeKeeper dateTimeKeeper) 20 | { 21 | // given 22 | 23 | // when 24 | dateTimeKeeper.SetDateTimeToNow(); 25 | 26 | // then 27 | dateTimeKeeper.HasDateTime().ShouldBeTrue(); 28 | 29 | Thread.Sleep(TimeSpan.FromMilliseconds(200)); 30 | dateTimeKeeper.TimeSinceDateTime().ShouldBeGreaterThan(TimeSpan.FromMilliseconds(100)); 31 | dateTimeKeeper.TimeSinceDateTime().ShouldBeLessThan(TimeSpan.FromMilliseconds(600)); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/Connections/Monitoring/TimerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Shouldly; 4 | using Xunit; 5 | using Xunit.Abstractions; 6 | using Timer = SlackConnector.Connections.Monitoring.Timer; 7 | 8 | namespace SlackConnector.Tests.Unit.Connections.Monitoring 9 | { 10 | public class TimerTests : IDisposable 11 | { 12 | private readonly ITestOutputHelper _output; 13 | private readonly Timer _timer = new Timer(); 14 | 15 | public TimerTests(ITestOutputHelper output) 16 | { 17 | _output = output; 18 | } 19 | 20 | [Fact] 21 | public void should_run_task_at_least_5_times() 22 | { 23 | // given 24 | var eventTriggered = new AutoResetEvent(false); 25 | int count = 0; 26 | 27 | // when 28 | _timer.RunEvery(() => 29 | { 30 | _output.WriteLine("Trigger"); 31 | eventTriggered.Set(); 32 | count++; 33 | }, TimeSpan.FromMilliseconds(1)); 34 | 35 | // then 36 | eventTriggered.WaitOne(TimeSpan.FromSeconds(1)); 37 | eventTriggered.WaitOne(TimeSpan.FromSeconds(1)); 38 | eventTriggered.WaitOne(TimeSpan.FromSeconds(1)); 39 | eventTriggered.WaitOne(TimeSpan.FromSeconds(1)); 40 | eventTriggered.WaitOne(TimeSpan.FromSeconds(1)); 41 | eventTriggered.WaitOne(TimeSpan.FromSeconds(1)); 42 | count.ShouldBeGreaterThanOrEqualTo(5); 43 | } 44 | 45 | [Fact] 46 | public void should_throw_exception_if_a_second_timer_is_created() 47 | { 48 | // given 49 | _timer.RunEvery(() => { }, TimeSpan.FromMilliseconds(1)); 50 | 51 | // when + then 52 | Assert.Throws(() => _timer.RunEvery(() => { }, TimeSpan.FromMinutes(1))); 53 | } 54 | 55 | public void Dispose() 56 | { 57 | _timer?.Dispose(); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/Extensions/MessageSubTypeExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using SlackConnector.Connections.Sockets.Messages.Inbound; 3 | using SlackConnector.Extensions; 4 | using SlackConnector.Models; 5 | using Xunit; 6 | 7 | namespace SlackConnector.Tests.Unit.Extensions 8 | { 9 | public class MessageSubTypeExtensionsTests 10 | { 11 | [Theory] 12 | [InlineData(MessageSubType.bot_message, SlackMessageSubType.BotMessage)] 13 | [InlineData(MessageSubType.channel_name, SlackMessageSubType.ChannelName)] 14 | [InlineData(MessageSubType.channel_topic, SlackMessageSubType.ChannelTopic)] 15 | [InlineData(MessageSubType.group_join, SlackMessageSubType.GroupJoin)] 16 | private void should_convert_to_expected_enum(MessageSubType inbound, SlackMessageSubType expected) 17 | { 18 | inbound 19 | .ToSlackMessageSubType() 20 | .ShouldBe(expected); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/Extensions/UserExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using SlackConnector.Connections.Models; 3 | using SlackConnector.Extensions; 4 | using Xunit; 5 | using Shouldly; 6 | 7 | namespace SlackConnector.Tests.Unit.Extensions 8 | { 9 | public class UserExtensionsTests 10 | { 11 | [Fact] 12 | public void should_create_slack_user_from_user() 13 | { 14 | // given 15 | var fixture = new Fixture(); 16 | var user = new User 17 | { 18 | Id = fixture.Create(), 19 | Name = fixture.Create(), 20 | TimeZoneOffset = fixture.Create(), 21 | IsBot = fixture.Create(), 22 | Deleted = fixture.Create(), 23 | Profile = new Profile 24 | { 25 | Email = fixture.Create(), 26 | FirstName = fixture.Create(), 27 | LastName = fixture.Create(), 28 | Image = fixture.Create(), 29 | Title = fixture.Create(), 30 | StatusText = fixture.Create() 31 | }, 32 | IsAdmin = fixture.Create() 33 | }; 34 | 35 | // when 36 | var slackUser = user.ToSlackUser(); 37 | 38 | // then 39 | user.Id.ShouldBe(slackUser.Id); 40 | user.Name.ShouldBe(slackUser.Name); 41 | user.Profile.Email.ShouldBe(slackUser.Email); 42 | user.TimeZoneOffset.ShouldBe(slackUser.TimeZoneOffset); 43 | user.IsBot.ShouldBe(slackUser.IsBot); 44 | user.Profile.FirstName.ShouldBe(slackUser.FirstName); 45 | user.Profile.LastName.ShouldBe(slackUser.LastName); 46 | user.Profile.Image.ShouldBe(slackUser.Image); 47 | user.Profile.Title.ShouldBe(slackUser.WhatIDo); 48 | user.Deleted.ShouldBe(slackUser.Deleted); 49 | slackUser.IsGuest.ShouldBeFalse(); 50 | slackUser.StatusText.ShouldBe(user.Profile.StatusText); 51 | slackUser.IsAdmin.ShouldBe(user.IsAdmin); 52 | } 53 | 54 | [Fact] 55 | public void should_create_guest_slack_user_from_guest_user() 56 | { 57 | // given 58 | var user = new User 59 | { 60 | IsGuest = true 61 | }; 62 | 63 | // when 64 | var slackUser = user.ToSlackUser(); 65 | 66 | // then 67 | slackUser.IsGuest.ShouldBeTrue(); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/Models/MessageSubTypeEnumTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using Shouldly; 6 | using SlackConnector.Connections.Sockets.Messages.Inbound; 7 | using SlackConnector.Models; 8 | using Xunit; 9 | 10 | namespace SlackConnector.Tests.Unit.Models 11 | { 12 | public class MessageSubTypeEnumTests 13 | { 14 | [Fact] 15 | public void should_have_same_number_of_enum_values_as_internal_enum_type() 16 | { 17 | // given 18 | int numberOfInternalEnumNames = Enum.GetNames(typeof(MessageSubType)).Length; 19 | int numberOfPublicEnumNames = Enum.GetNames(typeof(SlackMessageSubType)).Length; 20 | 21 | // then 22 | numberOfPublicEnumNames.ShouldBe(numberOfInternalEnumNames); 23 | } 24 | 25 | [Fact] 26 | public void should_have_same_message_types_as_internal_models() 27 | { 28 | // given 29 | var internalEnumNames = Enum.GetNames(typeof(MessageSubType)) 30 | .Select(x => x.Replace("_", string.Empty)) 31 | .Select(x => x.ToLower()); 32 | var publicEnumNames = Enum.GetNames(typeof(SlackMessageSubType)) 33 | .Select(x => x.ToLower()); 34 | 35 | // then 36 | publicEnumNames.All(x => internalEnumNames.Contains(x)).ShouldBeTrue(); 37 | } 38 | 39 | [Fact] 40 | public void should_have_same_message_type_values_as_internal_models() 41 | { 42 | // given 43 | var internalEnumValues = GetEnumValues(); 44 | var publicEnumValues = GetEnumValues(); 45 | 46 | // then 47 | foreach (var publicEnumName in publicEnumValues.Keys) 48 | { 49 | publicEnumValues[publicEnumName].ShouldBe(internalEnumValues[publicEnumName]); 50 | } 51 | } 52 | 53 | private static Dictionary GetEnumValues() where T : struct, IConvertible 54 | { 55 | var internalEnumNames = Enum.GetNames(typeof(T)); 56 | 57 | var vals = new Dictionary(); 58 | foreach (var enumName in internalEnumNames) 59 | { 60 | T thingy; 61 | Enum.TryParse(enumName, out thingy); 62 | 63 | var name = enumName 64 | .Replace("_", string.Empty) 65 | .ToLower(); 66 | 67 | vals.Add(name, thingy.ToInt32(new NumberFormatInfo())); 68 | } 69 | return vals; 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/Models/SlackAttachmentSerialisationTests.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Shouldly; 3 | using SlackConnector.Models; 4 | using System.Text.RegularExpressions; 5 | using Xunit; 6 | 7 | namespace SlackConnector.Tests.Unit.Models 8 | { 9 | public class SlackAttachmentSerialisationTests 10 | { 11 | [Fact] 12 | public void should_return_expected_json_when_serialised() 13 | { 14 | // given 15 | string expectedJson = Resources.ResourceManager.GetAttachmentsJson(); 16 | expectedJson = RemoveLinesAndStuffFromJson(expectedJson); 17 | 18 | var attachment = 19 | new SlackAttachment 20 | { 21 | Fallback = "Required plain-text summary of the attachment.", 22 | ColorHex = "#36a64f", 23 | PreText = "Optional text that appears above the attachment block", 24 | AuthorName = "Bobby Tables", 25 | AuthorLink = "http://flickr.com/bobby/", 26 | AuthorIcon = "http://flickr.com/icons/bobby.jpg", 27 | Title = "Slack API Documentation", 28 | TitleLink = "https://api.slack.com/", 29 | Text = "Optional text that appears within the attachment", 30 | CallbackId = "mycallbackid", 31 | MarkdownIn = SlackAttachment.GetAllMarkdownInTypes(), 32 | Fields = new[] 33 | { 34 | new SlackAttachmentField 35 | { 36 | IsShort = true, 37 | Title = "Priority", 38 | Value = "High" 39 | } 40 | }, 41 | Actions = new[] 42 | { 43 | new SlackAttachmentAction 44 | { 45 | Name = "yes", 46 | Value = "yep", 47 | Text = "Yes", 48 | Style = SlackAttachmentActionStyle.Primary 49 | }, 50 | new SlackAttachmentAction 51 | { 52 | Name = "no", 53 | Value = "nop", 54 | Text = "No" 55 | }, 56 | new SlackAttachmentAction 57 | { 58 | Url = "https://test.com/", 59 | Text = "LinkButton" 60 | } 61 | }, 62 | ImageUrl = "http://my-website.com/path/to/image.jpg", 63 | ThumbUrl = "http://example.com/path/to/thumb.png", 64 | Footer = "Brief text to help contextualize an attachment", 65 | FooterIcon = "http://flickr.com/icons/footer.jpg" 66 | }; 67 | 68 | // when 69 | string resultJson = JsonConvert.SerializeObject(attachment); 70 | resultJson = RemoveLinesAndStuffFromJson(resultJson); 71 | 72 | // then 73 | resultJson.ShouldBe(expectedJson); 74 | } 75 | 76 | private static string RemoveLinesAndStuffFromJson(string json) 77 | { 78 | return Regex.Replace(json, @"(""(?:[^""\\]|\\.)*"")|\s+", "$1"); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/Resources/Inputs/Attachments.json: -------------------------------------------------------------------------------- 1 | { 2 | "fallback": "Required plain-text summary of the attachment.", 3 | "color": "#36a64f", 4 | "pretext": "Optional text that appears above the attachment block", 5 | "author_name": "Bobby Tables", 6 | "author_link": "http://flickr.com/bobby/", 7 | "author_icon": "http://flickr.com/icons/bobby.jpg", 8 | "title": "Slack API Documentation", 9 | "title_link": "https://api.slack.com/", 10 | "text": "Optional text that appears within the attachment", 11 | "fields": [ 12 | { 13 | "short": true, 14 | "title": "Priority", 15 | "value": "High" 16 | } 17 | ], 18 | "callback_id": "mycallbackid", 19 | "actions": [ 20 | { 21 | "name": "yes", 22 | "text": "Yes", 23 | "style": "primary", 24 | "type": "button", 25 | "value": "yep" 26 | }, 27 | { 28 | "name": "no", 29 | "text": "No", 30 | "type": "button", 31 | "value": "nop" 32 | }, 33 | { 34 | "name": null, 35 | "text": "LinkButton", 36 | "type": "button", 37 | "value": null, 38 | "url": "https://test.com/" 39 | } 40 | ], 41 | "image_url": "http://my-website.com/path/to/image.jpg", 42 | "thumb_url": "http://example.com/path/to/thumb.png", 43 | "footer": "Brief text to help contextualize an attachment", 44 | "footer_icon": "http://flickr.com/icons/footer.jpg", 45 | "mrkdwn_in": ["fields","pretext","text"] 46 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/Resources/ResourceManager.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Reflection; 3 | 4 | namespace SlackConnector.Tests.Unit.Resources 5 | { 6 | public static class ResourceManager 7 | { 8 | public static string GetHandShakeResponseJson() 9 | { 10 | return ReadResource("Responses.HandShake.json"); 11 | } 12 | 13 | public static string GetAttachmentsJson() 14 | { 15 | return ReadResource("Inputs.Attachments.json"); 16 | } 17 | 18 | private static string ReadResource(string path) 19 | { 20 | Assembly assembly = Assembly.GetExecutingAssembly(); 21 | string resourceName = "SlackConnector.Tests.Unit.Resources." + path; 22 | 23 | using (Stream stream = assembly.GetManifestResourceStream(resourceName)) 24 | { 25 | using (StreamReader reader = new StreamReader(stream)) 26 | { 27 | return reader.ReadToEnd(); 28 | } 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/Resources/Responses/HandShake.json: -------------------------------------------------------------------------------- 1 | { 2 | "ok": true, 3 | "self": { 4 | "id": "self-id", 5 | "name": "self-name" 6 | }, 7 | "team": { 8 | "id": "team-id", 9 | "name": "team-name" 10 | }, 11 | "channels": [ 12 | { 13 | "id": "channel-id", 14 | "name": "channel-name", 15 | "is_channel": true, 16 | "is_archived": true, 17 | "is_member": true 18 | } 19 | ], 20 | "groups": [ 21 | { 22 | "id": "group-id", 23 | "name": "group-name", 24 | "is_group": true, 25 | "is_archived": true, 26 | "is_open": true, 27 | "members": [ 28 | "member1" 29 | ] 30 | } 31 | ], 32 | "ims": [ 33 | { 34 | "id": "im-id", 35 | "user": "im-user", 36 | "is_im": true, 37 | "is_open": true 38 | } 39 | ], 40 | "users": [ 41 | { 42 | "id": "user-id", 43 | "name": "user-name", 44 | "deleted": true, 45 | "profile": { 46 | "first_name": "first-name", 47 | "last_name": "last-name", 48 | "real_name": "real-name", 49 | "real_name_normalized": "real-name-normalized", 50 | "email": "email" 51 | }, 52 | "is_admin": true, 53 | "is_bot": true 54 | } 55 | ], 56 | "url": "wss:\/\/ms331.slack-msgs.com\/websocket\/999" 57 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/Serialising/EnumConverterTests.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using Newtonsoft.Json; 3 | using AutoFixture.Xunit2; 4 | using SlackConnector.Serialising; 5 | using Xunit; 6 | using Shouldly; 7 | 8 | namespace SlackConnector.Tests.Unit.Serialising 9 | { 10 | public class EnumConverterTests 11 | { 12 | [Theory, AutoMoqData] 13 | private void should_return_true_given_enum_type_when_checking_can_convert(EnumConverter converter) 14 | { 15 | // given 16 | 17 | // when 18 | var result = converter.CanConvert(typeof(TestControl)); 19 | 20 | // then 21 | result.ShouldBeTrue(); 22 | } 23 | 24 | [Theory, AutoMoqData] 25 | private void should_return_false_given_non_enum_type_when_checking_can_convert(EnumConverter converter) 26 | { 27 | // given 28 | 29 | // when 30 | var result = converter.CanConvert(typeof(EnumConverter)); 31 | 32 | // then 33 | result.ShouldBeFalse(); 34 | } 35 | 36 | [Theory, AutoMoqData] 37 | private void should_ruturn_enum_given_valid_enum_value_when_converting_to_enum([Frozen]Mock jsonReader, EnumConverter converter) 38 | { 39 | // given 40 | jsonReader 41 | .Setup(x => x.Value) 42 | .Returns(TestControl.SomethingElse.ToString); 43 | 44 | // when 45 | var result = converter.ReadJson(jsonReader.Object, typeof(TestControl), null, null); 46 | 47 | // then 48 | result.ShouldBe(TestControl.SomethingElse); 49 | } 50 | 51 | [Theory, AutoMoqData] 52 | private void should_ruturn_enum_given_valid_enum_with_different_casing_when_converting_to_enum([Frozen]Mock jsonReader, EnumConverter converter) 53 | { 54 | // given 55 | jsonReader 56 | .Setup(x => x.Value) 57 | .Returns(TestControl.ThirdOption.ToString().ToLower); 58 | 59 | // when 60 | var result = converter.ReadJson(jsonReader.Object, typeof(TestControl), null, null); 61 | 62 | // then 63 | result.ShouldBe(TestControl.ThirdOption); 64 | } 65 | 66 | [Theory, AutoMoqData] 67 | private void should_return_default_enum_given_invalid_enum_when_converting_to_enum([Frozen]Mock jsonReader, EnumConverter converter) 68 | { 69 | // given 70 | jsonReader 71 | .Setup(x => x.Value) 72 | .Returns("I AM NOT AN ENUM"); 73 | 74 | // when 75 | var result = converter.ReadJson(jsonReader.Object, typeof(TestControl), null, null); 76 | 77 | // then 78 | result.ShouldBe(TestControl.Default); 79 | } 80 | 81 | private enum TestControl 82 | { 83 | Default = 0, 84 | SomethingElse = 1, 85 | ThirdOption = 2 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/ArchiveChannelTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Moq; 4 | using AutoFixture.Xunit2; 5 | using SlackConnector.Connections; 6 | using SlackConnector.Connections.Clients.Channel; 7 | using SlackConnector.Connections.Models; 8 | using SlackConnector.Connections.Sockets; 9 | using SlackConnector.Models; 10 | using SlackConnector.Tests.Unit.TestExtensions; 11 | using Xunit; 12 | using Shouldly; 13 | 14 | namespace SlackConnector.Tests.Unit.SlackConnectionTests 15 | { 16 | public class ArchiveChannelTests 17 | { 18 | [Theory, AutoMoqData] 19 | private async Task should_be_successful( 20 | [Frozen]Mock connectionFactory, 21 | Mock channelClient, 22 | Mock webSocket, 23 | SlackConnection slackConnection) 24 | { 25 | // given 26 | const string slackKey = "key-yay"; 27 | const string channelName = "public-channel-name"; 28 | 29 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object, SlackKey = slackKey }; 30 | await slackConnection.Initialise(connectionInfo); 31 | 32 | connectionFactory 33 | .Setup(x => x.CreateChannelClient()) 34 | .Returns(channelClient.Object); 35 | 36 | // when 37 | await slackConnection.ArchiveChannel(channelName); 38 | 39 | // then 40 | channelClient.Verify(x => x.ArchiveChannel(slackKey, channelName), Times.Once); 41 | } 42 | 43 | [Theory, AutoMoqData] 44 | private async Task should_throw_exception_given_null_channel_name( 45 | Mock webSocket, 46 | SlackConnection slackConnection) 47 | { 48 | // given 49 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 50 | await slackConnection.Initialise(connectionInfo); 51 | 52 | // when 53 | var exception = await Assert.ThrowsAsync(() => slackConnection.ArchiveChannel(null)); 54 | 55 | // then 56 | exception.Message.ShouldBe("Value cannot be null.\r\nParameter name: channelName"); 57 | } 58 | 59 | [Theory, AutoMoqData] 60 | private async Task should_throw_exception_given_empty_channel_name(Mock webSocket, SlackConnection slackConnection) 61 | { 62 | // given 63 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 64 | await slackConnection.Initialise(connectionInfo); 65 | 66 | // when 67 | var exception = await Assert.ThrowsAsync(() => slackConnection.ArchiveChannel(string.Empty)); 68 | 69 | // then 70 | exception.Message.ShouldBe("Value cannot be null.\r\nParameter name: channelName"); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/CloseConnectionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Moq; 4 | using AutoFixture.Xunit2; 5 | using SlackConnector.Connections.Sockets; 6 | using SlackConnector.Models; 7 | using Xunit; 8 | 9 | namespace SlackConnector.Tests.Unit.SlackConnectionTests 10 | { 11 | public class CloseConnectionTests 12 | { 13 | [Theory, AutoMoqData] 14 | private async Task should_close_websocket_when_websocket_is_connected( 15 | [Frozen]Mock webSocket, 16 | SlackConnection slackConnection) 17 | { 18 | // given 19 | webSocket 20 | .Setup(x => x.IsAlive) 21 | .Returns(true); 22 | 23 | var info = GetDummyConnectionInformation(webSocket); 24 | await slackConnection.Initialise(info); 25 | 26 | // when 27 | await slackConnection.Close(); 28 | 29 | // then 30 | webSocket.Verify(x => x.Close(), Times.Once); 31 | } 32 | 33 | [Theory, AutoMoqData] 34 | private async Task should_not_close_websocket_when_websocket_is_disconnected( 35 | [Frozen]Mock webSocket, 36 | SlackConnection slackConnection) 37 | { 38 | // given 39 | webSocket 40 | .Setup(x => x.IsAlive) 41 | .Returns(false); 42 | 43 | var info = GetDummyConnectionInformation(webSocket); 44 | await slackConnection.Initialise(info); 45 | 46 | // when 47 | await slackConnection.Close(); 48 | 49 | // then 50 | webSocket.Verify(x => x.Close(), Times.Never); 51 | } 52 | 53 | private static ConnectionInformation GetDummyConnectionInformation(Mock webSocket) 54 | { 55 | var info = new ConnectionInformation 56 | { 57 | Self = new ContactDetails { Id = "self-id" }, 58 | Team = new ContactDetails { Id = "team-id" }, 59 | Users = new Dictionary { { "userid", new SlackUser() { Name = "userName" } } }, 60 | SlackChatHubs = new Dictionary { { "some-hub", new SlackChatHub() } }, 61 | WebSocket = webSocket.Object 62 | }; 63 | return info; 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/CreateChannelTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Moq; 4 | using AutoFixture.Xunit2; 5 | using SlackConnector.Connections; 6 | using SlackConnector.Connections.Clients.Channel; 7 | using SlackConnector.Connections.Models; 8 | using SlackConnector.Connections.Sockets; 9 | using SlackConnector.Models; 10 | using SlackConnector.Tests.Unit.TestExtensions; 11 | using Xunit; 12 | using Shouldly; 13 | 14 | namespace SlackConnector.Tests.Unit.SlackConnectionTests 15 | { 16 | public class CreateChannelTests 17 | { 18 | [Theory, AutoMoqData] 19 | private async Task should_return_expected_slack_hub( 20 | [Frozen]Mock connectionFactory, 21 | Mock channelClient, 22 | Mock webSocket, 23 | SlackConnection slackConnection) 24 | { 25 | // given 26 | const string slackKey = "key-yay"; 27 | const string channelName = "public-channel-name"; 28 | 29 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object, SlackKey = slackKey }; 30 | await slackConnection.Initialise(connectionInfo); 31 | 32 | connectionFactory 33 | .Setup(x => x.CreateChannelClient()) 34 | .Returns(channelClient.Object); 35 | 36 | var returnChannel = new Channel { Id = "super-id", Name = "public-channel-name" }; 37 | channelClient 38 | .Setup(x => x.CreateChannel(slackKey, channelName)) 39 | .ReturnsAsync(returnChannel); 40 | 41 | // when 42 | var result = await slackConnection.CreateChannel(channelName); 43 | 44 | // then 45 | result.ShouldLookLike(new SlackChatHub 46 | { 47 | Id = returnChannel.Id, 48 | Name = returnChannel.Name, 49 | Type = SlackChatHubType.Channel 50 | }); 51 | } 52 | 53 | [Theory, AutoMoqData] 54 | private async Task should_throw_exception_given_null_channel_name( 55 | Mock webSocket, 56 | SlackConnection slackConnection) 57 | { 58 | // given 59 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 60 | await slackConnection.Initialise(connectionInfo); 61 | 62 | // when 63 | var exception = await Assert.ThrowsAsync(() => slackConnection.CreateChannel(null)); 64 | 65 | // then 66 | exception.Message.ShouldBe("Value cannot be null.\r\nParameter name: channelName"); 67 | } 68 | 69 | [Theory, AutoMoqData] 70 | private async Task should_throw_exception_given_empty_channel_name(Mock webSocket, SlackConnection slackConnection) 71 | { 72 | // given 73 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 74 | await slackConnection.Initialise(connectionInfo); 75 | 76 | // when 77 | var exception = await Assert.ThrowsAsync(() => slackConnection.CreateChannel(string.Empty)); 78 | 79 | // then 80 | exception.Message.ShouldBe("Value cannot be null.\r\nParameter name: channelName"); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/DownloadFileTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using AutoFixture; 4 | using Flurl.Http.Testing; 5 | using Moq; 6 | using Shouldly; 7 | using SlackConnector.Connections.Sockets; 8 | using SlackConnector.Models; 9 | using Xunit; 10 | 11 | namespace SlackConnector.Tests.Unit.SlackConnectionTests 12 | { 13 | public class DownloadFileTests 14 | { 15 | [Theory, AutoMoqData] 16 | private async Task should_return_stream( 17 | Mock webSocket, 18 | SlackConnection slackConnection, 19 | string slackKey, 20 | Fixture fixture) 21 | { 22 | using (var httpTest = new HttpTest()) 23 | { 24 | // given 25 | var downloadUri = new Uri($"https://files.slack.com/{fixture.Create()}"); 26 | 27 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object, SlackKey = slackKey }; 28 | await slackConnection.Initialise(connectionInfo); 29 | 30 | httpTest 31 | .RespondWithJson(new { fakeObject = true }); 32 | 33 | // when 34 | var result = await slackConnection.DownloadFile(downloadUri); 35 | 36 | // then 37 | result.ShouldNotBeNull(); 38 | httpTest 39 | .ShouldHaveCalled(downloadUri.AbsoluteUri) 40 | .WithOAuthBearerToken(slackKey); 41 | } 42 | } 43 | 44 | [Theory, AutoMoqData] 45 | private async Task throws_exception_if_uri_isnt_slack( 46 | Mock webSocket, 47 | SlackConnection slackConnection, 48 | string slackKey, 49 | Fixture fixture) 50 | { 51 | // given 52 | var downloadUri = new Uri($"https://something.com/{fixture.Create()}"); 53 | 54 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object, SlackKey = slackKey }; 55 | await slackConnection.Initialise(connectionInfo); 56 | 57 | // when 58 | var exception = Should.Throw(async () => await slackConnection.DownloadFile(downloadUri)); 59 | 60 | // then 61 | exception.ShouldNotBeNull(); 62 | exception.Message.ShouldContain("Invalid uri"); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/InboundMessageTests/ChannelCreatedTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Moq; 4 | using AutoFixture; 5 | using SlackConnector.Connections.Models; 6 | using SlackConnector.Connections.Sockets; 7 | using SlackConnector.Connections.Sockets.Messages.Inbound; 8 | using SlackConnector.Models; 9 | using Xunit; 10 | using Shouldly; 11 | 12 | namespace SlackConnector.Tests.Unit.SlackConnectionTests.InboundMessageTests 13 | { 14 | public class ChannelCreatedTests 15 | { 16 | [Theory, AutoMoqData] 17 | private async Task should_raise_event( 18 | Mock webSocket, 19 | SlackConnection slackConnection, 20 | SlackUser slackUser, 21 | Fixture fixture) 22 | { 23 | // given 24 | var connectionInfo = new ConnectionInformation 25 | { 26 | WebSocket = webSocket.Object, 27 | Users = new Dictionary 28 | { 29 | {slackUser.Id , slackUser} 30 | } 31 | }; 32 | await slackConnection.Initialise(connectionInfo); 33 | 34 | SlackChannelCreated channelCreated = null; 35 | slackConnection.OnChannelCreated += channel => 36 | { 37 | channelCreated = channel; 38 | return Task.CompletedTask; 39 | }; 40 | 41 | var inboundMessage = new ChannelCreatedMessage 42 | { 43 | Channel = new Channel 44 | { 45 | Creator = slackUser.Id, 46 | Id = fixture.Create(), 47 | Name = fixture.Create() 48 | } 49 | }; 50 | 51 | // when 52 | webSocket.Raise(x => x.OnMessage += null, null, inboundMessage); 53 | 54 | // then 55 | channelCreated.Id.ShouldBe(inboundMessage.Channel.Id); 56 | channelCreated.Name.ShouldBe(inboundMessage.Channel.Name); 57 | channelCreated.Creator.ShouldBe(slackUser); 58 | slackConnection.ConnectedHubs.ContainsKey(inboundMessage.Channel.Id).ShouldBeTrue(); 59 | } 60 | 61 | [Theory, AutoMoqData] 62 | private async Task should_not_raise_event_given_missing_data( 63 | Mock webSocket, 64 | SlackConnection slackConnection) 65 | { 66 | // given 67 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 68 | await slackConnection.Initialise(connectionInfo); 69 | 70 | SlackChannelCreated channelCreated = null; 71 | slackConnection.OnChannelCreated += channel => 72 | { 73 | channelCreated = channel; 74 | return Task.CompletedTask; 75 | }; 76 | 77 | var inboundMessage = new ChannelCreatedMessage { Channel = null }; 78 | 79 | // when 80 | webSocket.Raise(x => x.OnMessage += null, null, inboundMessage); 81 | 82 | // then 83 | channelCreated.ShouldBeNull(); 84 | slackConnection.ConnectedHubs.ShouldBeEmpty(); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/InboundMessageTests/ChannelJoinedTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Moq; 4 | using SlackConnector.Connections.Models; 5 | using SlackConnector.Connections.Sockets; 6 | using SlackConnector.Connections.Sockets.Messages.Inbound; 7 | using SlackConnector.Models; 8 | using Xunit; 9 | using Shouldly; 10 | 11 | namespace SlackConnector.Tests.Unit.SlackConnectionTests.InboundMessageTests 12 | { 13 | public class ChannelJoinedTests 14 | { 15 | [Theory, AutoMoqData] 16 | private async Task should_raise_event( 17 | Mock webSocket, 18 | SlackConnection slackConnection) 19 | { 20 | // given 21 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 22 | await slackConnection.Initialise(connectionInfo); 23 | 24 | const string hubId = "this-is-the-id"; 25 | SlackChatHub lastHub = null; 26 | slackConnection.OnChatHubJoined += hub => 27 | { 28 | lastHub = hub; 29 | return Task.CompletedTask; 30 | }; 31 | 32 | var inboundMessage = new ChannelJoinedMessage 33 | { 34 | Channel = new Channel { Id = hubId } 35 | }; 36 | 37 | // when 38 | webSocket.Raise(x => x.OnMessage += null, null, inboundMessage); 39 | 40 | // then 41 | lastHub.Id.ShouldBe(hubId); 42 | lastHub.Type.ShouldBe(SlackChatHubType.Channel); 43 | slackConnection.ConnectedHubs.ContainsKey(hubId).ShouldBeTrue(); 44 | slackConnection.ConnectedHubs[hubId].ShouldBe(lastHub); 45 | } 46 | 47 | [Theory, AutoMoqData] 48 | private async Task should_not_raise_event_given_missing_data( 49 | Mock webSocket, 50 | SlackConnection slackConnection) 51 | { 52 | // given 53 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 54 | await slackConnection.Initialise(connectionInfo); 55 | 56 | SlackChatHub lastHub = null; 57 | slackConnection.OnChatHubJoined += hub => 58 | { 59 | lastHub = hub; 60 | return Task.CompletedTask; 61 | }; 62 | 63 | var inboundMessage = new ChannelJoinedMessage { Channel = null }; 64 | 65 | // when 66 | webSocket.Raise(x => x.OnMessage += null, null, inboundMessage); 67 | 68 | // then 69 | lastHub.ShouldBeNull(); 70 | slackConnection.ConnectedHubs.ShouldBeEmpty(); 71 | } 72 | 73 | [Theory, AutoMoqData] 74 | private async Task given_exception_in_event_then_shouldnt_bubble_exception( 75 | Mock webSocket, 76 | SlackConnection slackConnection) 77 | { 78 | // given 79 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 80 | await slackConnection.Initialise(connectionInfo); 81 | 82 | const string hubId = "this-is-the-id"; 83 | slackConnection.OnChatHubJoined += hub => throw new NotImplementedException("THIS SHOULDN'T BUBBLE UP"); 84 | 85 | var inboundMessage = new ChannelJoinedMessage 86 | { 87 | Channel = new Channel { Id = hubId } 88 | }; 89 | 90 | // when & then (does not throw) 91 | webSocket.Raise(x => x.OnMessage += null, null, inboundMessage); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/InboundMessageTests/DmJoinedTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Moq; 3 | using SlackConnector.Connections.Models; 4 | using SlackConnector.Connections.Sockets; 5 | using SlackConnector.Connections.Sockets.Messages.Inbound; 6 | using SlackConnector.Models; 7 | using SlackConnector.Tests.Unit.TestExtensions; 8 | using Xunit; 9 | using Shouldly; 10 | 11 | namespace SlackConnector.Tests.Unit.SlackConnectionTests.InboundMessageTests 12 | { 13 | public class DmJoinedTests 14 | { 15 | [Theory, AutoMoqData] 16 | private async Task should_raise_event( 17 | Mock webSocket, 18 | SlackConnection slackConnection) 19 | { 20 | // given 21 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 22 | await slackConnection.Initialise(connectionInfo); 23 | 24 | SlackChatHub lastHub = null; 25 | slackConnection.OnChatHubJoined += hub => 26 | { 27 | lastHub = hub; 28 | return Task.CompletedTask; 29 | }; 30 | 31 | var inboundMessage = new DmChannelJoinedMessage 32 | { 33 | Channel = new Im 34 | { 35 | User = "test-user", 36 | Id = "channel-id", 37 | IsIm = true, 38 | IsOpen = true 39 | } 40 | }; 41 | 42 | // when 43 | webSocket.Raise(x => x.OnMessage += null, null, inboundMessage); 44 | 45 | // then 46 | slackConnection.ConnectedHubs.ContainsKey("channel-id").ShouldBeTrue(); 47 | slackConnection.ConnectedHubs["channel-id"].ShouldBe(lastHub); 48 | lastHub.ShouldLookLike(new SlackChatHub 49 | { 50 | Id = "channel-id", 51 | Name = "@test-user", 52 | Type = SlackChatHubType.DM 53 | }); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/InboundMessageTests/GroupJoinedTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Moq; 3 | using SlackConnector.Connections.Models; 4 | using SlackConnector.Connections.Sockets; 5 | using SlackConnector.Connections.Sockets.Messages.Inbound; 6 | using SlackConnector.Models; 7 | using Xunit; 8 | using Shouldly; 9 | 10 | namespace SlackConnector.Tests.Unit.SlackConnectionTests.InboundMessageTests 11 | { 12 | public class GroupJoinedTests 13 | { 14 | [Theory, AutoMoqData] 15 | private async Task should_raise_event( 16 | Mock webSocket, 17 | SlackConnection slackConnection) 18 | { 19 | // given 20 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 21 | await slackConnection.Initialise(connectionInfo); 22 | 23 | const string hubId = "this-is-the-id"; 24 | SlackChatHub lastHub = null; 25 | slackConnection.OnChatHubJoined += hub => 26 | { 27 | lastHub = hub; 28 | return Task.CompletedTask; 29 | }; 30 | 31 | var inboundMessage = new GroupJoinedMessage 32 | { 33 | Channel = new Group { Id = hubId } 34 | }; 35 | 36 | // when 37 | webSocket.Raise(x => x.OnMessage += null, null, inboundMessage); 38 | 39 | // then 40 | lastHub.Id.ShouldBe(hubId); 41 | lastHub.Type.ShouldBe(SlackChatHubType.Group); 42 | slackConnection.ConnectedHubs.ContainsKey(hubId).ShouldBeTrue(); 43 | slackConnection.ConnectedHubs[hubId].ShouldBe(lastHub); 44 | } 45 | 46 | [Theory, AutoMoqData] 47 | private async Task should_not_raise_event_given_missing_data( 48 | Mock webSocket, 49 | SlackConnection slackConnection) 50 | { 51 | // given 52 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 53 | await slackConnection.Initialise(connectionInfo); 54 | 55 | SlackChatHub lastHub = null; 56 | slackConnection.OnChatHubJoined += hub => 57 | { 58 | lastHub = hub; 59 | return Task.CompletedTask; 60 | }; 61 | 62 | var inboundMessage = new GroupJoinedMessage { Channel = null }; 63 | 64 | // when 65 | webSocket.Raise(x => x.OnMessage += null, null, inboundMessage); 66 | 67 | // then 68 | lastHub.ShouldBeNull(); 69 | slackConnection.ConnectedHubs.ShouldBeEmpty(); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/InboundMessageTests/PongTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Moq; 4 | using AutoFixture.Xunit2; 5 | using SlackConnector.Connections.Monitoring; 6 | using SlackConnector.Connections.Sockets; 7 | using SlackConnector.Connections.Sockets.Messages.Inbound; 8 | using SlackConnector.Models; 9 | using Xunit; 10 | using Shouldly; 11 | 12 | namespace SlackConnector.Tests.Unit.SlackConnectionTests.InboundMessageTests 13 | { 14 | public class PongTests 15 | { 16 | [Theory, AutoMoqData] 17 | private async Task should_raise_event( 18 | Mock webSocket, 19 | SlackConnection slackConnection) 20 | { 21 | // given 22 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 23 | await slackConnection.Initialise(connectionInfo); 24 | 25 | DateTime lastTimestamp = DateTime.MinValue; 26 | slackConnection.OnPong += timestamp => 27 | { 28 | lastTimestamp = timestamp; 29 | return Task.CompletedTask; 30 | }; 31 | 32 | var inboundMessage = new PongMessage 33 | { 34 | Timestamp = DateTime.Now 35 | }; 36 | 37 | // when 38 | webSocket.Raise(x => x.OnMessage += null, null, inboundMessage); 39 | 40 | // then 41 | lastTimestamp.ShouldBe(inboundMessage.Timestamp); 42 | } 43 | 44 | [Theory, AutoMoqData] 45 | private async Task should_pong_monitor( 46 | [Frozen]Mock monitoringFactory, 47 | Mock pingPongMonitor, 48 | Mock webSocket, 49 | SlackConnection slackConnection) 50 | { 51 | // given 52 | monitoringFactory 53 | .Setup(x => x.CreatePingPongMonitor()) 54 | .Returns(pingPongMonitor.Object); 55 | 56 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 57 | await slackConnection.Initialise(connectionInfo); 58 | 59 | var inboundMessage = new PongMessage 60 | { 61 | Timestamp = DateTime.Now 62 | }; 63 | 64 | // when 65 | webSocket.Raise(x => x.OnMessage += null, null, inboundMessage); 66 | 67 | // then 68 | pingPongMonitor.Verify(x => x.Pong(), Times.Once); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/InboundMessageTests/UserJoinedTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Moq; 4 | using SlackConnector.Connections.Models; 5 | using SlackConnector.Connections.Sockets; 6 | using SlackConnector.Connections.Sockets.Messages.Inbound; 7 | using SlackConnector.Models; 8 | using SlackConnector.Tests.Unit.TestExtensions; 9 | using Xunit; 10 | using Shouldly; 11 | 12 | namespace SlackConnector.Tests.Unit.SlackConnectionTests.InboundMessageTests 13 | { 14 | public class UserJoinedTests 15 | { 16 | [Theory, AutoMoqData] 17 | private async Task should_raise_event(Mock webSocket, SlackConnection slackConnection) 18 | { 19 | // given 20 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 21 | await slackConnection.Initialise(connectionInfo); 22 | 23 | SlackUser lastUser = null; 24 | slackConnection.OnUserJoined += user => 25 | { 26 | lastUser = user; 27 | return Task.CompletedTask; 28 | }; 29 | 30 | var inboundMessage = new UserJoinedMessage 31 | { 32 | MessageType = MessageType.Team_Join, 33 | User = new User 34 | { 35 | Id = "some-id", 36 | Name = "my-name", 37 | TimeZoneOffset = -231, 38 | IsBot = true, 39 | Profile = new Profile 40 | { 41 | Email = "some-email@mail.com" 42 | } 43 | } 44 | }; 45 | 46 | // when 47 | webSocket.Raise(x => x.OnMessage += null, null, inboundMessage); 48 | 49 | // then 50 | lastUser.ShouldLookLike(new SlackUser 51 | { 52 | Id = "some-id", 53 | Name = "my-name", 54 | TimeZoneOffset = -231, 55 | IsBot = true, 56 | Email = "some-email@mail.com" 57 | }); 58 | 59 | slackConnection.UserCache.ContainsKey(inboundMessage.User.Id).ShouldBeTrue(); 60 | slackConnection.UserCache[inboundMessage.User.Id].ShouldBe(lastUser); 61 | } 62 | 63 | [Theory, AutoMoqData] 64 | private async Task should_not_raise_event_given_missing_user_info(Mock webSocket, SlackConnection slackConnection) 65 | { 66 | // given 67 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 68 | await slackConnection.Initialise(connectionInfo); 69 | 70 | SlackUser lastUser = null; 71 | slackConnection.OnUserJoined += user => 72 | { 73 | lastUser = user; 74 | return Task.CompletedTask; 75 | }; 76 | 77 | var inboundMessage = new UserJoinedMessage 78 | { 79 | User = new User 80 | { 81 | Id = null 82 | } 83 | }; 84 | 85 | // when 86 | webSocket.Raise(x => x.OnMessage += null, null, inboundMessage); 87 | 88 | // then 89 | lastUser.ShouldBeNull(); 90 | slackConnection.UserCache.ShouldBeEmpty(); 91 | } 92 | 93 | [Theory, AutoMoqData] 94 | private async Task should_not_raise_exception(Mock webSocket, SlackConnection slackConnection) 95 | { 96 | // given 97 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 98 | await slackConnection.Initialise(connectionInfo); 99 | slackConnection.OnUserJoined += user => throw new NotImplementedException("THIS SHOULDN'T BUBBLE UP"); 100 | 101 | var inboundMessage = new UserJoinedMessage 102 | { 103 | User = new User 104 | { 105 | Id = null 106 | } 107 | }; 108 | 109 | // when & then (does not throw) 110 | webSocket.Raise(x => x.OnMessage += null, null, inboundMessage); 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/InitialiseTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Moq; 5 | using AutoFixture.Xunit2; 6 | using SlackConnector.Connections.Monitoring; 7 | using SlackConnector.Connections.Sockets; 8 | using SlackConnector.Models; 9 | using Xunit; 10 | using Shouldly; 11 | 12 | namespace SlackConnector.Tests.Unit.SlackConnectionTests 13 | { 14 | public class given_valid_connection_info 15 | { 16 | [Theory, AutoMoqData] 17 | private void should_initialise_slack_connection(SlackConnection connection) 18 | { 19 | // given 20 | var info = new ConnectionInformation 21 | { 22 | Self = new ContactDetails { Id = "self-id" }, 23 | Team = new ContactDetails { Id = "team-id" }, 24 | Users = new Dictionary { { "userid", new SlackUser() { Name = "userName" } } }, 25 | SlackChatHubs = new Dictionary { { "some-hub", new SlackChatHub() } }, 26 | WebSocket = new Mock().Object 27 | }; 28 | 29 | // when 30 | connection.Initialise(info).Wait(); 31 | 32 | // then 33 | connection.Self.ShouldBe(info.Self); 34 | connection.Team.ShouldBe(info.Team); 35 | connection.UserCache.ShouldBe(info.Users); 36 | connection.ConnectedHubs.ShouldBe(info.SlackChatHubs); 37 | connection.ConnectedSince.HasValue.ShouldBeTrue(); 38 | } 39 | 40 | [Theory, AutoMoqData] 41 | private void should_be_connected_if_websocket_is_alive( 42 | Mock webSocketClient, 43 | SlackConnection connection) 44 | { 45 | // given 46 | var info = new ConnectionInformation 47 | { 48 | WebSocket = webSocketClient.Object 49 | }; 50 | 51 | webSocketClient 52 | .Setup(x => x.IsAlive) 53 | .Returns(true); 54 | 55 | // when 56 | connection.Initialise(info).Wait(); 57 | 58 | // then 59 | connection.IsConnected.ShouldBeTrue(); 60 | } 61 | 62 | [Theory, AutoMoqData] 63 | private async Task should_initialise_ping_pong_monitor( 64 | [Frozen]Mock monitoringFactory, 65 | Mock pingPongMonitor, 66 | SlackConnection connection) 67 | { 68 | // given 69 | var info = new ConnectionInformation 70 | { 71 | WebSocket = new Mock().Object 72 | }; 73 | 74 | pingPongMonitor 75 | .Setup(x => x.StartMonitor(It.IsAny>(), It.IsAny>(), It.IsAny())) 76 | .Returns(Task.CompletedTask); 77 | 78 | monitoringFactory 79 | .Setup(x => x.CreatePingPongMonitor()) 80 | .Returns(pingPongMonitor.Object); 81 | 82 | // when 83 | await connection.Initialise(info); 84 | 85 | // then 86 | pingPongMonitor.Verify(x => x.StartMonitor(It.IsNotNull>(), It.IsNotNull>(), TimeSpan.FromMinutes(2)), Times.Once); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/JoinChannelTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Moq; 4 | using AutoFixture.Xunit2; 5 | using SlackConnector.Connections; 6 | using SlackConnector.Connections.Clients.Channel; 7 | using SlackConnector.Connections.Models; 8 | using SlackConnector.Connections.Sockets; 9 | using SlackConnector.Models; 10 | using SlackConnector.Tests.Unit.TestExtensions; 11 | using Xunit; 12 | using Shouldly; 13 | 14 | namespace SlackConnector.Tests.Unit.SlackConnectionTests 15 | { 16 | public class JoinChannelTests 17 | { 18 | [Theory, AutoMoqData] 19 | private async Task should_return_expected_slack_hub( 20 | [Frozen]Mock connectionFactory, 21 | Mock channelClient, 22 | Mock webSocket, 23 | SlackConnection slackConnection) 24 | { 25 | // given 26 | const string slackKey = "key-yay"; 27 | const string channelName = "public-channel-name"; 28 | 29 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object, SlackKey = slackKey }; 30 | await slackConnection.Initialise(connectionInfo); 31 | 32 | connectionFactory 33 | .Setup(x => x.CreateChannelClient()) 34 | .Returns(channelClient.Object); 35 | 36 | var returnChannel = new Channel { Id = "super-id", Name = "public-channel-name" }; 37 | channelClient 38 | .Setup(x => x.JoinChannel(slackKey, channelName)) 39 | .ReturnsAsync(returnChannel); 40 | 41 | // when 42 | var result = await slackConnection.JoinChannel(channelName); 43 | 44 | // then 45 | result.ShouldLookLike(new SlackChatHub 46 | { 47 | Id = returnChannel.Id, 48 | Name = returnChannel.Name, 49 | Type = SlackChatHubType.Channel 50 | }); 51 | } 52 | 53 | [Theory, AutoMoqData] 54 | private async Task should_throw_exception_given_null_channel_name( 55 | Mock webSocket, 56 | SlackConnection slackConnection) 57 | { 58 | // given 59 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 60 | await slackConnection.Initialise(connectionInfo); 61 | 62 | // when 63 | var exception = await Assert.ThrowsAsync(() => slackConnection.JoinChannel(null)); 64 | 65 | // then 66 | exception.Message.ShouldBe("Value cannot be null.\r\nParameter name: channelName"); 67 | } 68 | 69 | [Theory, AutoMoqData] 70 | private async Task should_throw_exception_given_empty_channel_name(Mock webSocket, SlackConnection slackConnection) 71 | { 72 | // given 73 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 74 | await slackConnection.Initialise(connectionInfo); 75 | 76 | // when 77 | var exception = await Assert.ThrowsAsync(() => slackConnection.JoinChannel(string.Empty)); 78 | 79 | // then 80 | exception.Message.ShouldBe("Value cannot be null.\r\nParameter name: channelName"); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/JoinDirectMessageChannelTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Moq; 4 | using AutoFixture.Xunit2; 5 | using SlackConnector.Connections; 6 | using SlackConnector.Connections.Clients.Channel; 7 | using SlackConnector.Connections.Models; 8 | using SlackConnector.Connections.Sockets; 9 | using SlackConnector.Models; 10 | using SlackConnector.Tests.Unit.TestExtensions; 11 | using Xunit; 12 | using Shouldly; 13 | 14 | namespace SlackConnector.Tests.Unit.SlackConnectionTests 15 | { 16 | public class JoinDirectMessageChannelTests 17 | { 18 | [Theory, AutoMoqData] 19 | private async Task should_return_expected_slack_hub( 20 | [Frozen]Mock connectionFactory, 21 | Mock channelClient, 22 | Mock webSocket, 23 | SlackConnection slackConnection) 24 | { 25 | // given 26 | const string slackKey = "key-yay"; 27 | const string userId = "some-id"; 28 | 29 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object, SlackKey = slackKey }; 30 | await slackConnection.Initialise(connectionInfo); 31 | 32 | connectionFactory 33 | .Setup(x => x.CreateChannelClient()) 34 | .Returns(channelClient.Object); 35 | 36 | var returnChannel = new Channel { Id = "super-id", Name = "dm-channel" }; 37 | channelClient 38 | .Setup(x => x.JoinDirectMessageChannel(slackKey, userId)) 39 | .ReturnsAsync(returnChannel); 40 | 41 | // when 42 | var result = await slackConnection.JoinDirectMessageChannel(userId); 43 | 44 | // then 45 | result.ShouldLookLike(new SlackChatHub 46 | { 47 | Id = returnChannel.Id, 48 | Name = returnChannel.Name, 49 | Type = SlackChatHubType.DM 50 | }); 51 | } 52 | 53 | [Theory, AutoMoqData] 54 | private async Task should_throw_exception_given_null_user_id( 55 | Mock webSocket, 56 | SlackConnection slackConnection) 57 | { 58 | // given 59 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 60 | await slackConnection.Initialise(connectionInfo); 61 | 62 | // when 63 | var exception = await Assert.ThrowsAsync(() => slackConnection.JoinDirectMessageChannel(null)); 64 | 65 | // then 66 | exception.Message.ShouldBe("Value cannot be null.\r\nParameter name: user"); 67 | } 68 | 69 | [Theory, AutoMoqData] 70 | private async Task should_throw_exception_given_empty_user_id(Mock webSocket, SlackConnection slackConnection) 71 | { 72 | // given 73 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 74 | await slackConnection.Initialise(connectionInfo); 75 | 76 | // when 77 | var exception = await Assert.ThrowsAsync(() => slackConnection.JoinDirectMessageChannel(string.Empty)); 78 | 79 | // then 80 | exception.Message.ShouldBe("Value cannot be null.\r\nParameter name: user"); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/PingTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Moq; 3 | using SlackConnector.Connections.Sockets; 4 | using SlackConnector.Connections.Sockets.Messages.Outbound; 5 | using SlackConnector.Models; 6 | using Xunit; 7 | 8 | namespace SlackConnector.Tests.Unit.SlackConnectionTests 9 | { 10 | public class PingTests 11 | { 12 | [Theory, AutoMoqData] 13 | private async Task should_send_ping( 14 | Mock webSocket, 15 | SlackConnection slackConnection) 16 | { 17 | // given 18 | const string slackKey = "key-yay"; 19 | 20 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object, SlackKey = slackKey }; 21 | await slackConnection.Initialise(connectionInfo); 22 | 23 | // when 24 | await slackConnection.Ping(); 25 | 26 | // then 27 | webSocket.Verify(x => x.SendMessage(It.IsAny())); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/SayTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Moq; 4 | using AutoFixture.Xunit2; 5 | using SlackConnector.Connections; 6 | using SlackConnector.Connections.Clients.Chat; 7 | using SlackConnector.Connections.Sockets; 8 | using SlackConnector.Exceptions; 9 | using SlackConnector.Models; 10 | using Xunit; 11 | using Shouldly; 12 | 13 | namespace SlackConnector.Tests.Unit.SlackConnectionTests 14 | { 15 | public class SayTests 16 | { 17 | [Theory, AutoMoqData] 18 | private async Task should_send_message( 19 | [Frozen]Mock connectionFactory, 20 | Mock chatClient, 21 | Mock webSocket, 22 | SlackConnection slackConnection) 23 | { 24 | // given 25 | const string slackKey = "key-yay"; 26 | 27 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object, SlackKey = slackKey }; 28 | await slackConnection.Initialise(connectionInfo); 29 | 30 | connectionFactory 31 | .Setup(x => x.CreateChatClient()) 32 | .Returns(chatClient.Object); 33 | 34 | var message = new BotMessage 35 | { 36 | Text = "some text", 37 | ChatHub = new SlackChatHub { Id = "channel-id" }, 38 | Attachments = new List() 39 | }; 40 | 41 | // when 42 | await slackConnection.Say(message); 43 | 44 | // then 45 | chatClient 46 | .Verify(x => x.PostMessage(slackKey, message.ChatHub.Id, message.Text, message.Attachments), Times.Once); 47 | } 48 | 49 | [Theory, AutoMoqData] 50 | private async Task should_throw_exception_given_null_chat_hub( 51 | Mock webSocket, 52 | SlackConnection slackConnection) 53 | { 54 | // given 55 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 56 | await slackConnection.Initialise(connectionInfo); 57 | 58 | // when 59 | var exception = await Assert.ThrowsAsync(() => slackConnection.Say(new BotMessage { ChatHub = null })); 60 | 61 | // then 62 | exception.Message.ShouldBe("When calling the Say() method, the message parameter must have its ChatHub property set."); 63 | } 64 | 65 | [Theory, AutoMoqData] 66 | private async Task should_throw_exception_given_empty_chat_hub_id( 67 | Mock webSocket, 68 | SlackConnection slackConnection) 69 | { 70 | // given 71 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 72 | await slackConnection.Initialise(connectionInfo); 73 | 74 | // when 75 | var exception = await Assert.ThrowsAsync(() => slackConnection.Say(new BotMessage { ChatHub = new SlackChatHub { Id = string.Empty } })); 76 | 77 | // then 78 | exception.Message.ShouldBe("When calling the Say() method, the message parameter must have its ChatHub property set."); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/SetChannelPurposeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Moq; 4 | using AutoFixture.Xunit2; 5 | using SlackConnector.Connections; 6 | using SlackConnector.Connections.Clients.Channel; 7 | using SlackConnector.Connections.Sockets; 8 | using SlackConnector.Models; 9 | using SlackConnector.Tests.Unit.TestExtensions; 10 | using Xunit; 11 | using Shouldly; 12 | 13 | namespace SlackConnector.Tests.Unit.SlackConnectionTests 14 | { 15 | public class SetChannelPurposeTests 16 | { 17 | [Theory, AutoMoqData] 18 | private async Task should_return_expected_slack_purpose( 19 | [Frozen]Mock connectionFactory, 20 | Mock channelClient, 21 | Mock webSocket, 22 | SlackConnection slackConnection) 23 | { 24 | // given 25 | const string slackKey = "key-yay"; 26 | const string channelName = "public-channel-name"; 27 | const string channelPurpose = "new purpose"; 28 | 29 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object, SlackKey = slackKey }; 30 | await slackConnection.Initialise(connectionInfo); 31 | 32 | connectionFactory 33 | .Setup(x => x.CreateChannelClient()) 34 | .Returns(channelClient.Object); 35 | 36 | channelClient 37 | .Setup(x => x.SetPurpose(slackKey, channelName, channelPurpose)) 38 | .ReturnsAsync(channelPurpose); 39 | 40 | // when 41 | var result = await slackConnection.SetChannelPurpose(channelName, channelPurpose); 42 | 43 | // then 44 | result.ShouldLookLike(new SlackPurpose 45 | { 46 | ChannelName = channelName, 47 | Purpose = channelPurpose 48 | }); 49 | } 50 | 51 | [Theory, AutoMoqData] 52 | private async Task should_throw_exception_given_null_channel_name( 53 | Mock webSocket, 54 | SlackConnection slackConnection) 55 | { 56 | // given 57 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 58 | await slackConnection.Initialise(connectionInfo); 59 | 60 | // when 61 | var exception = await Assert.ThrowsAsync(() => slackConnection.SetChannelPurpose(null, "purpose")); 62 | 63 | // then 64 | exception.Message.ShouldBe("Value cannot be null.\r\nParameter name: channelName"); 65 | } 66 | 67 | [Theory, AutoMoqData] 68 | private async Task should_throw_exception_given_empty_channel_name(Mock webSocket, SlackConnection slackConnection) 69 | { 70 | // given 71 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 72 | await slackConnection.Initialise(connectionInfo); 73 | 74 | // when 75 | var exception = await Assert.ThrowsAsync(() => slackConnection.SetChannelPurpose(string.Empty, "purpose")); 76 | 77 | // then 78 | exception.Message.ShouldBe("Value cannot be null.\r\nParameter name: channelName"); 79 | } 80 | 81 | [Theory, AutoMoqData] 82 | private async Task should_throw_exception_given_null_purpose( 83 | Mock webSocket, 84 | SlackConnection slackConnection) 85 | { 86 | // given 87 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 88 | await slackConnection.Initialise(connectionInfo); 89 | 90 | // when 91 | var exception = await Assert.ThrowsAsync(() => slackConnection.SetChannelPurpose("channel", null)); 92 | 93 | // then 94 | exception.Message.ShouldBe("Value cannot be null.\r\nParameter name: purpose"); 95 | } 96 | 97 | [Theory, AutoMoqData] 98 | private async Task should_throw_exception_given_empty_purpose(Mock webSocket, SlackConnection slackConnection) 99 | { 100 | // given 101 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object }; 102 | await slackConnection.Initialise(connectionInfo); 103 | 104 | // when 105 | var exception = await Assert.ThrowsAsync(() => slackConnection.SetChannelPurpose("channel", string.Empty)); 106 | 107 | // then 108 | exception.Message.ShouldBe("Value cannot be null.\r\nParameter name: purpose"); 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/TypingIndicatorTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Moq; 3 | using SlackConnector.Connections.Sockets; 4 | using SlackConnector.Connections.Sockets.Messages.Outbound; 5 | using SlackConnector.Models; 6 | using Xunit; 7 | 8 | namespace SlackConnector.Tests.Unit.SlackConnectionTests 9 | { 10 | public class TypingIndicatorTests 11 | { 12 | [Theory, AutoMoqData] 13 | private async Task should_send_ping( 14 | Mock webSocket, 15 | SlackConnection slackConnection) 16 | { 17 | // given 18 | const string slackKey = "key-yay"; 19 | 20 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object, SlackKey = slackKey }; 21 | await slackConnection.Initialise(connectionInfo); 22 | var chatHub = new SlackChatHub { Id = "channelz-id" }; 23 | 24 | // when 25 | await slackConnection.IndicateTyping(chatHub); 26 | 27 | // then 28 | webSocket.Verify(x => x.SendMessage(It.Is((TypingIndicatorMessage p) => p.Channel == chatHub.Id))); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/UploadFileTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using Moq; 4 | using AutoFixture.Xunit2; 5 | using SlackConnector.Connections; 6 | using SlackConnector.Connections.Clients.File; 7 | using SlackConnector.Connections.Sockets; 8 | using SlackConnector.Models; 9 | using Xunit; 10 | 11 | namespace SlackConnector.Tests.Unit.SlackConnectionTests 12 | { 13 | public class UploadFileTests 14 | { 15 | [Theory, AutoMoqData] 16 | private async Task should_upload_file_from_disk( 17 | [Frozen]Mock connectionFactory, 18 | Mock fileClient, 19 | Mock webSocket, 20 | SlackConnection slackConnection) 21 | { 22 | // given 23 | const string slackKey = "key-yay"; 24 | const string filePath = "expected-file-name"; 25 | var chatHub = new SlackChatHub { Id = "channelz-id" }; 26 | 27 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object, SlackKey = slackKey }; 28 | await slackConnection.Initialise(connectionInfo); 29 | 30 | connectionFactory 31 | .Setup(x => x.CreateFileClient()) 32 | .Returns(fileClient.Object); 33 | 34 | // when 35 | await slackConnection.Upload(chatHub, filePath); 36 | 37 | // then 38 | fileClient 39 | .Verify(x => x.PostFile(slackKey, chatHub.Id, filePath), Times.Once); 40 | } 41 | 42 | [Theory, AutoMoqData] 43 | private async Task should_upload_file_from_stream( 44 | [Frozen]Mock connectionFactory, 45 | Mock fileClient, 46 | Mock webSocket, 47 | SlackConnection slackConnection) 48 | { 49 | // given 50 | const string slackKey = "key-yay"; 51 | const string fileName = "expected-file-name"; 52 | var chatHub = new SlackChatHub { Id = "channelz-id" }; 53 | var stream = new MemoryStream(); 54 | 55 | var connectionInfo = new ConnectionInformation { WebSocket = webSocket.Object, SlackKey = slackKey }; 56 | await slackConnection.Initialise(connectionInfo); 57 | 58 | connectionFactory 59 | .Setup(x => x.CreateFileClient()) 60 | .Returns(fileClient.Object); 61 | 62 | // when 63 | await slackConnection.Upload(chatHub, stream, fileName); 64 | 65 | // then 66 | fileClient 67 | .Verify(x => x.PostFile(slackKey, chatHub.Id, stream, fileName), Times.Once); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnectionTests/WebSocketTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Moq; 4 | using SlackConnector.Connections.Sockets; 5 | using SlackConnector.Models; 6 | using Xunit; 7 | using Shouldly; 8 | 9 | namespace SlackConnector.Tests.Unit.SlackConnectionTests 10 | { 11 | public class WebSocketTests 12 | { 13 | [Theory, AutoMoqData] 14 | private async Task should_detect_disconnect( 15 | Mock webSocket, 16 | SlackConnection slackConnection) 17 | { 18 | // given 19 | bool connectionChangedValue = false; 20 | slackConnection.OnDisconnect += () => connectionChangedValue = true; 21 | 22 | var info = new ConnectionInformation { WebSocket = webSocket.Object }; 23 | await slackConnection.Initialise(info); 24 | 25 | // when 26 | webSocket.Raise(x => x.OnClose += null, new EventArgs()); 27 | 28 | // then 29 | connectionChangedValue.ShouldBeTrue(); 30 | slackConnection.IsConnected.ShouldBeFalse(); 31 | slackConnection.ConnectedSince.ShouldBeNull(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/SlackConnector.Tests.Unit.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | all 26 | runtime; build; native; contentfiles; analyzers; buildtransitive 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/Stubs/SlackConnectionFactoryStub.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using SlackConnector.Models; 3 | 4 | namespace SlackConnector.Tests.Unit.Stubs 5 | { 6 | internal class SlackConnectionFactoryStub : ISlackConnectionFactory 7 | { 8 | public ConnectionInformation Create_ConnectionInformation { get; private set; } 9 | public SlackConnectionStub Create_Value { get; set; } 10 | 11 | public Task Create(ConnectionInformation connectionInformation) 12 | { 13 | Create_ConnectionInformation = connectionInformation; 14 | return Task.FromResult(Create_Value); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/Stubs/TimerStub.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SlackConnector.Connections.Monitoring; 3 | 4 | namespace SlackConnector.Tests.Unit.Stubs 5 | { 6 | public class TimerStub : ITimer 7 | { 8 | public Action RunEvery_Action { get; private set; } 9 | public TimeSpan RunEvery_Tick { get; private set; } 10 | 11 | public void RunEvery(Action action, TimeSpan tick) 12 | { 13 | RunEvery_Action = action; 14 | RunEvery_Tick = tick; 15 | } 16 | 17 | public void Dispose() 18 | { 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/Stubs/WebSocketClientStub.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using SlackConnector.Connections.Sockets; 4 | using SlackConnector.Connections.Sockets.Messages.Inbound; 5 | using SlackConnector.Connections.Sockets.Messages.Outbound; 6 | 7 | namespace SlackConnector.Tests.Unit.Stubs 8 | { 9 | internal class WebSocketClientStub : IWebSocketClient 10 | { 11 | public bool IsAlive { get; set; } 12 | public int CurrentMessageId { get; set; } 13 | 14 | public event EventHandler OnMessage; 15 | public void RaiseOnMessage(InboundMessage message) 16 | { 17 | OnMessage.Invoke(this, message); 18 | } 19 | 20 | public event EventHandler OnClose; 21 | public void RaiseOnClose() 22 | { 23 | OnClose.Invoke(this, null); 24 | } 25 | 26 | public Task Connect(string webSocketUrl) 27 | { 28 | return Task.CompletedTask; 29 | } 30 | 31 | public Task Connect() 32 | { 33 | return Task.CompletedTask; 34 | } 35 | 36 | public BaseMessage SendMessage_Message { get; private set; } 37 | public Task SendMessage(BaseMessage message) 38 | { 39 | SendMessage_Message = message; 40 | return Task.CompletedTask; 41 | } 42 | 43 | public Task Close() 44 | { 45 | return Task.CompletedTask; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/TestExtensions/ExpectedObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using ExpectedObjects; 2 | 3 | namespace SlackConnector.Tests.Unit.TestExtensions 4 | { 5 | public static class ExpectedObjectExtensions 6 | { 7 | public static void ShouldLookLike(this T actual, T expected) 8 | { 9 | expected.ToExpectedObject().ShouldEqual(actual); 10 | } 11 | 12 | public static void ShouldLookLikePartial(this T actual, object expected) 13 | { 14 | expected.ToExpectedObject().ShouldMatch(actual); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /tests/SlackConnector.Tests.Unit/TestExtensions/ShouldExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using ExpectedObjects; 4 | using Moq; 5 | 6 | namespace SlackConnector.Tests.Unit.TestExtensions 7 | { 8 | public static class Looks 9 | { 10 | public static T Like(T obj) 11 | { 12 | var expected = obj.ToExpectedObject(); 13 | return It.Is(t => expected.Equals(t)); 14 | } 15 | 16 | public static T Like(Expression> initializer) where T : class 17 | { 18 | return It.Is(t => ShouldMatch(initializer, t)); 19 | } 20 | 21 | 22 | public static T LikePartialOf(object partial) 23 | { 24 | var expected = partial.ToExpectedObject(); 25 | 26 | return It.Is(t => ShouldMatch(expected, t)); 27 | } 28 | 29 | private static bool ShouldMatch(Expression> initializer, T o) where T : class 30 | { 31 | try 32 | { 33 | o.ShouldLookLike(initializer); 34 | } 35 | catch (Exception) 36 | { 37 | return false; 38 | } 39 | 40 | return true; 41 | } 42 | 43 | private static bool ShouldMatch(ExpectedObject expected, object o) 44 | { 45 | try 46 | { 47 | expected.ShouldMatch(o); 48 | return true; 49 | } 50 | catch (Exception) 51 | { 52 | return false; 53 | } 54 | } 55 | } 56 | } --------------------------------------------------------------------------------