diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index b1ffc4a..01076b1 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -4,18 +4,46 @@ on: push: branches: - master - - dev - release/* + - dev + pull_request: + branches: + - dev jobs: build_and_test: name: Build and test the GitHub Action - runs-on: ubuntu-latest + strategy: + matrix: + os: [ 'ubuntu-latest' ] + dotnet: [ '3.1.100' ] + + runs-on: ${{ matrix.os }} steps: - name: Checkout the repository uses: actions/checkout@v1 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v1 + with: + dotnet-version: ${{ matrix.dotnet }} + + - name: Restore NuGet packages + shell: bash + run: | + dotnet restore + + - name: Build console app + shell: bash + run: | + dotnet build + + - name: Test console app + shell: bash + run: | + dotnet test test/**/*.csproj + - name: Run the private action uses: ./ with: @@ -25,4 +53,4 @@ jobs: text: '' theme_color: '' sections: '[{ "activityImage": "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png", "activityTitle": "GitHub Action invoked", "activitySubtitle": "Event triggered by ${{ github.event.head_commit.author.name }}", "activityText": "Commit message: [${{ github.event.head_commit.message}}](${{ github.event.head_commit.url }})" }]' - actions: '' + actions: '[{ "@type": "OpenUri", "name": "View Commit", "targets": [{ "os": "default", "uri": "${{ github.event.head_commit.url }}" }] }]' diff --git a/src/GitHubActions.Teams.ConsoleApp/ActionConverter.cs b/src/GitHubActions.Teams.ConsoleApp/ActionConverter.cs new file mode 100644 index 0000000..948c7ab --- /dev/null +++ b/src/GitHubActions.Teams.ConsoleApp/ActionConverter.cs @@ -0,0 +1,46 @@ +using System; + +using MessageCardModel; +using MessageCardModel.Actions; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Aliencube.GitHubActions.Teams.ConsoleApp +{ + public class ActionConverter : JsonConverter + { + /// + public override bool CanWrite => false; + + /// + public override bool CanRead => true; + + /// + public override bool CanConvert(Type objectType) => objectType == typeof(BaseAction); + + /// + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException(); + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var jsonObject = JObject.Load(reader); + + var actionType = jsonObject.GetValue("@type").Value(); + + BaseAction target = actionType switch + { + "ActionCard" => new ActionCardAction(), + "HttpPOST" => new HttpPostAction(), + "OpenUri" => new OpenUriAction(), + + string type => throw new NotSupportedException($"Cannot deserialize action type: {type}") + }; + + serializer.Populate(jsonObject.CreateReader(), target); + + return target; + } + } +} diff --git a/src/GitHubActions.Teams.ConsoleApp/GitHubActions.Teams.ConsoleApp.csproj b/src/GitHubActions.Teams.ConsoleApp/GitHubActions.Teams.ConsoleApp.csproj index 35d0ee7..aa17d91 100644 --- a/src/GitHubActions.Teams.ConsoleApp/GitHubActions.Teams.ConsoleApp.csproj +++ b/src/GitHubActions.Teams.ConsoleApp/GitHubActions.Teams.ConsoleApp.csproj @@ -3,6 +3,7 @@ Exe netcoreapp3.1 + latest diff --git a/src/GitHubActions.Teams.ConsoleApp/IMessageHandler.cs b/src/GitHubActions.Teams.ConsoleApp/IMessageHandler.cs new file mode 100644 index 0000000..76ec876 --- /dev/null +++ b/src/GitHubActions.Teams.ConsoleApp/IMessageHandler.cs @@ -0,0 +1,36 @@ +using System.Net.Http; +using System.Threading.Tasks; + +using Newtonsoft.Json; + +namespace Aliencube.GitHubActions.Teams.ConsoleApp +{ + /// + /// This provides interfaces to the class. + /// + public interface IMessageHandler + { + /// + /// Gets the JSON serialised message. + /// + string Converted { get; } + + /// + /// Gets the incoming webhook URI to Microsoft Teams. + /// + string RequestUri { get; } + + /// + /// Builds a message in Actionable Message Format. + /// + /// instance. + /// instance. + IMessageHandler BuildMessage(Options options, JsonSerializerSettings settings); + + /// + /// Sends a message to a Microsoft Teams channel. + /// + /// instance. + Task SendMessageAsync(HttpClient client); + } +} diff --git a/src/GitHubActions.Teams.ConsoleApp/MessageHandler.cs b/src/GitHubActions.Teams.ConsoleApp/MessageHandler.cs new file mode 100644 index 0000000..b26129c --- /dev/null +++ b/src/GitHubActions.Teams.ConsoleApp/MessageHandler.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +using MessageCardModel; + +using Newtonsoft.Json; + +namespace Aliencube.GitHubActions.Teams.ConsoleApp +{ + /// + /// This represents the console app entity. + /// + public class MessageHandler : IMessageHandler + { + /// + public virtual string Converted { get; private set; } + + /// + public virtual string RequestUri { get; private set; } + + /// + public IMessageHandler BuildMessage(Options options, JsonSerializerSettings settings) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + + var card = new MessageCard() + { + Title = ParseString(options.Title), + Summary = ParseString(options.Summary), + Text = ParseString(options.Text), + ThemeColor = ParseString(options.ThemeColor), + Sections = ParseCollection
(options.Sections, settings), + Actions = ParseCollection(options.Actions, settings) + }; + + this.Converted = JsonConvert.SerializeObject(card, settings); + this.RequestUri = options.WebhookUri; + + return this; + } + + /// + public async Task SendMessageAsync(HttpClient client) + { + if (client == null) + { + throw new ArgumentNullException(nameof(client)); + } + + if (string.IsNullOrWhiteSpace(this.RequestUri)) + { + throw new InvalidOperationException("Webhook URI not ready"); + } + + var message = default(string); + var exitCode = default(int); + + using (var content = new StringContent(this.Converted, Encoding.UTF8, "application/json")) + using (var response = await client.PostAsync(this.RequestUri, content).ConfigureAwait(false)) + { + try + { + response.EnsureSuccessStatusCode(); + + message = $"Message sent: {this.Converted}"; + exitCode = 0; + } + catch (HttpRequestException ex) + { + message = $"Error: {ex.Message}"; + exitCode = (int) response.StatusCode; + } + } + + Console.WriteLine(message); + + return exitCode; + } + + private static string ParseString(string value) + { + var parsed = string.IsNullOrWhiteSpace(value) + ? null + : value; + + return parsed; + } + + private static List ParseCollection(string value, JsonSerializerSettings settings) + { + var parsed = string.IsNullOrWhiteSpace(value) + ? null + : JsonConvert.DeserializeObject>(value, settings); + + return parsed; + } + } +} diff --git a/src/GitHubActions.Teams.ConsoleApp/Program.cs b/src/GitHubActions.Teams.ConsoleApp/Program.cs index b440080..8054264 100644 --- a/src/GitHubActions.Teams.ConsoleApp/Program.cs +++ b/src/GitHubActions.Teams.ConsoleApp/Program.cs @@ -1,12 +1,10 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net.Http; -using System.Text; using CommandLine; -using MessageCardModel; - using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -17,6 +15,16 @@ namespace Aliencube.GitHubActions.Teams.ConsoleApp /// public static class Program { + /// + /// Gets or sets the instance. + /// + public static IMessageHandler MessageHandler { get; set; } = new MessageHandler(); + + /// + /// Gets or sets the instance. + /// + public static HttpClient HttpClient { get; set; } = new HttpClient(); + private static JsonSerializerSettings settings { get; } = new JsonSerializerSettings() { @@ -24,72 +32,37 @@ public static class Program ContractResolver = new DefaultContractResolver() { NamingStrategy = new CamelCaseNamingStrategy() }, NullValueHandling = NullValueHandling.Ignore, MissingMemberHandling = MissingMemberHandling.Ignore, + Converters = new List { new ActionConverter() }, }; /// /// Invokes the console app. /// /// List of arguments passed. - public static void Main(string[] args) + public static int Main(string[] args) { using (var parser = new Parser(with => { with.EnableDashDash = true; with.HelpWriter = Console.Out; })) { - parser.ParseArguments(args) - .WithParsed(options => Process(options)); - } - } - - private static void Process(Options options) - { - var card = new MessageCard() - { - Title = ParseString(options.Title), - Summary = ParseString(options.Summary), - Text = ParseString(options.Text), - ThemeColor = ParseString(options.ThemeColor), - Sections = ParseCollection
(options.Sections), - Actions = ParseCollection(options.Actions) - }; - - var converted = JsonConvert.SerializeObject(card, settings); - var message = (string)null; - var requestUri = options.WebhookUri; + var result = parser.ParseArguments(args) + .MapResult(options => OnParsed(options), errors => OnNotParsed(errors)) + ; - using (var client = new HttpClient()) - using (var content = new StringContent(converted, Encoding.UTF8, "application/json")) - using (var response = client.PostAsync(requestUri, content).Result) - { - try - { - response.EnsureSuccessStatusCode(); - - message = converted; - } - catch (HttpRequestException ex) - { - message = ex.Message; - } + return result; } - - Console.WriteLine($"Message sent: {message}"); } - private static string ParseString(string value) + private static int OnParsed(Options options) { - var parsed = string.IsNullOrWhiteSpace(value) - ? null - : value; + var result = MessageHandler.BuildMessage(options, settings) + .SendMessageAsync(HttpClient) + .Result; - return parsed; + return result; } - private static List ParseCollection(string value) + private static int OnNotParsed(IEnumerable errors) { - var parsed = string.IsNullOrWhiteSpace(value) - ? null - : JsonConvert.DeserializeObject>(value, settings); - - return parsed; + return errors.Count(); } } } diff --git a/test/GitHubActions.Teams.ConsoleApp.Tests/ActionConverterTests.cs b/test/GitHubActions.Teams.ConsoleApp.Tests/ActionConverterTests.cs new file mode 100644 index 0000000..2a0ba98 --- /dev/null +++ b/test/GitHubActions.Teams.ConsoleApp.Tests/ActionConverterTests.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; + +using Aliencube.GitHubActions.Teams.ConsoleApp; + +using FluentAssertions; + +using MessageCardModel; +using MessageCardModel.Actions; + +using Newtonsoft.Json; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GitHubActions.Teams.ConsoleApp.Tests +{ + [TestClass] + public class ActionConverterTests + { + [TestMethod] + public void Given_Type_It_Should_Inherit() + { + typeof(ActionConverter) + .Should().BeDerivedFrom(); + } + + [TestMethod] + public void Given_Value_When_WriteJson_Invoked_THen_It_Should_Throw() + { + var converter = new ActionConverter(); + + Action action = () => converter.WriteJson(null, null, null); + + action.Should().Throw(); + } + + [DataTestMethod] + [DataRow(typeof(BaseAction), true)] + [DataRow(typeof(ActionCardAction), false)] + [DataRow(typeof(HttpPostAction), false)] + [DataRow(typeof(OpenUriAction), false)] + public void Given_Type_When_CanConvert_Invoked_It_Should_Return(Type type, bool expected) + { + var converter = new ActionConverter(); + + var result = converter.CanConvert(type); + + result.Should().Be(expected); + } + + [DataTestMethod] + [DataRow("{ \"@type\": \"ActionCard\" }", typeof(ActionCardAction))] + [DataRow("{ \"@type\": \"HttpPOST\" }", typeof(HttpPostAction))] + [DataRow("{ \"@type\": \"OpenUri\" }", typeof(OpenUriAction))] + public void Given_Json_Value_When_ReadJson_Invoked_Then_It_Should_Return(string json, Type expected) + { + var serialiser = new JsonSerializer(); + var result = default(object); + + var converter = new ActionConverter(); + using (var reader = new StringReader(json)) + using (var jsonReader = new JsonTextReader(reader)) + { + result = converter.ReadJson(jsonReader, null, null, serialiser); + } + + result.Should().BeOfType(expected); + } + } +} diff --git a/test/GitHubActions.Teams.ConsoleApp.Tests/GitHubActions.Teams.ConsoleApp.Tests.csproj b/test/GitHubActions.Teams.ConsoleApp.Tests/GitHubActions.Teams.ConsoleApp.Tests.csproj new file mode 100644 index 0000000..a4f8978 --- /dev/null +++ b/test/GitHubActions.Teams.ConsoleApp.Tests/GitHubActions.Teams.ConsoleApp.Tests.csproj @@ -0,0 +1,26 @@ + + + + netcoreapp3.1 + false + latest + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + diff --git a/test/GitHubActions.Teams.ConsoleApp.Tests/MessageHandlerTests.cs b/test/GitHubActions.Teams.ConsoleApp.Tests/MessageHandlerTests.cs new file mode 100644 index 0000000..e16752b --- /dev/null +++ b/test/GitHubActions.Teams.ConsoleApp.Tests/MessageHandlerTests.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +using Aliencube.GitHubActions.Teams.ConsoleApp; + +using FluentAssertions; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Moq; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using WorldDomination.Net.Http; + +namespace GitHubActions.Teams.ConsoleApp.Tests +{ + [TestClass] + public class MessageHandlerTests + { + private static JsonSerializerSettings settings { get; } = + new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + ContractResolver = new DefaultContractResolver() { NamingStrategy = new CamelCaseNamingStrategy() }, + NullValueHandling = NullValueHandling.Ignore, + MissingMemberHandling = MissingMemberHandling.Ignore, + Converters = new List { new ActionConverter() }, + }; + + [TestMethod] + public void Given_Type_Then_It_Should_Have_Properties() + { + typeof(MessageHandler) + .Should().HaveProperty("Converted") + .Which.Should().BeVirtual() + .And.BeReadable() + .And.BeWritable() + .And.BeVirtual() + ; + + typeof(MessageHandler) + .Should().HaveProperty("RequestUri") + .Which.Should().BeVirtual() + .And.BeReadable() + .And.BeWritable() + .And.BeVirtual() + ; + } + + [TestMethod] + public void Given_Type_Then_It_Should_Have_Methods() + { + typeof(MessageHandler) + .Should().HaveMethod("BuildMessage", new[] { typeof(Options), typeof(JsonSerializerSettings) }) + ; + + typeof(MessageHandler) + .Should().HaveMethod("SendMessageAsync", new List() { typeof(HttpClient) } ) + ; + } + + [TestMethod] + public void Given_Type_Then_It_Should_Implement_Interfaces() + { + typeof(MessageHandler) + .Should().Implement() + ; + } + + [TestMethod] + public void Given_Null_Parameters_When_BuildMessage_Invoked_Then_It_Should_Throw_Exception() + { + var options = new Mock(); + var handler = new MessageHandler(); + + Action action = () => handler.BuildMessage(null, null); + action.Should().Throw(); + + action = () => handler.BuildMessage(options.Object, null); + action.Should().Throw(); + } + + [TestMethod] + public void Given_Null_When_BuildMessage_Invoked_Then_It_Should_Not_Exist() + { + var options = new Mock(); + options.SetupGet(p => p.Title).Returns("hello world"); + + var handler = new MessageHandler(); + + var result = handler.BuildMessage(options.Object, settings); + + var deserialised = JObject.Parse(handler.Converted); + + deserialised.Value("text").Should().BeNull(); + } + + [DataTestMethod] + [DataRow("hello world", "hello world")] + public void Given_Title_When_BuildMessage_Invoked_Then_It_Should_Return_Result(string input, string expected) + { + var options = new Mock(); + options.SetupGet(p => p.Title).Returns(input); + + var handler = new MessageHandler(); + + var result = handler.BuildMessage(options.Object, settings); + + var deserialised = JObject.Parse(handler.Converted); + + deserialised.Value("title").Should().Be(expected); + } + + [DataTestMethod] + [DataRow("[ { \"activityImage\": \"https://localhost/image.png\", \"activityTitle\": \"lorem\", \"activitySubtitle\": \"ipsum\", \"activityText\": \"hello world\" } ]")] + public void Given_Sections_When_BuildMessage_Invoked_Then_It_Should_Return_Result(string sections) + { + var options = new Mock(); + options.SetupGet(p => p.Sections).Returns(sections); + + var handler = new MessageHandler(); + + var result = handler.BuildMessage(options.Object, settings); + + var deserialised = JObject.Parse(handler.Converted); + + deserialised["sections"].AsJEnumerable().Should().HaveCount(1); + } + + [DataTestMethod] + [DataRow("[ { \"@type\": \"OpenUri\", \"name\": \"lorem ipsum\", \"targets\": [ { \"os\": \"default\", \"uri\": \"https://localhost\" } ] } ]")] + public void Given_Actions_When_BuildMessage_Invoked_Then_It_Should_Return_Result(string actions) + { + var options = new Mock(); + options.SetupGet(p => p.Actions).Returns(actions); + + var handler = new MessageHandler(); + + var result = handler.BuildMessage(options.Object, settings); + + var deserialised = JObject.Parse(handler.Converted); + + deserialised["potentialAction"].AsJEnumerable().Should().HaveCount(1); + } + + [TestMethod] + public void Given_Null_Parameters_When_SendMessageAsync_Invoked_Then_It_Should_Throw_Exception() + { + var handler = new MessageHandler(); + + Func func = async () => await handler.SendMessageAsync(null).ConfigureAwait(false); + func.Should().Throw(); + } + + [TestMethod] + public void Given_Null_RequestUri_Property_When_SendMessageAsync_Invoked_Then_It_Should_Throw_Exception() + { + var options = new Mock(); + + var handler = new MessageHandler(); + handler.BuildMessage(options.Object, settings); + + var httpClient = new HttpClient(); + + Func func = async () => await handler.SendMessageAsync(httpClient).ConfigureAwait(false); + func.Should().Throw() + .Which.Message.Should().BeEquivalentTo("Webhook URI not ready"); + } + + [DataTestMethod] + [DataRow("lorem ipsum", HttpStatusCode.OK, 0)] + [DataRow("lorem ipsum", HttpStatusCode.Created, 0)] + [DataRow("lorem ipsum", HttpStatusCode.Accepted, 0)] + [DataRow("lorem ipsum", HttpStatusCode.Found, (int)HttpStatusCode.Found)] + [DataRow("lorem ipsum", HttpStatusCode.BadRequest, (int)HttpStatusCode.BadRequest)] + [DataRow("lorem ipsum", HttpStatusCode.InternalServerError, (int)HttpStatusCode.InternalServerError)] + public async Task Given_HttpClient_When_SendMessageAsync_Invoked_Then_It_Should_Return_Result(string message, HttpStatusCode statusCode, int expected) + { + var options = new Mock(); + options.SetupGet(p => p.Title).Returns("hello world"); + options.SetupGet(p => p.WebhookUri).Returns("https://localhost"); + + var content = new StringContent(message); + var response = new HttpResponseMessage(statusCode) { Content = content }; + var messageOptions = new HttpMessageOptions() { HttpResponseMessage = response }; + var httpHandler = new FakeHttpMessageHandler(messageOptions); + var httpClient = new HttpClient(httpHandler); + + var handler = new MessageHandler(); + + handler.BuildMessage(options.Object, settings); + + var result = await handler.SendMessageAsync(httpClient).ConfigureAwait(false); + + result.Should().Be(expected); + } + } +} diff --git a/test/GitHubActions.Teams.ConsoleApp.Tests/OptionsTests.cs b/test/GitHubActions.Teams.ConsoleApp.Tests/OptionsTests.cs new file mode 100644 index 0000000..f308516 --- /dev/null +++ b/test/GitHubActions.Teams.ConsoleApp.Tests/OptionsTests.cs @@ -0,0 +1,125 @@ +using Aliencube.GitHubActions.Teams.ConsoleApp; + +using CommandLine; + +using FluentAssertions; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace GitHubActions.Teams.ConsoleApp.Tests +{ + [TestClass] + public class OptionsTests + { + [TestMethod] + public void Given_Type_Then_It_Should_Have_Properties() + { + typeof(Options) + .Should().HaveProperty("WebhookUri") + .Which.Should().BeReadable() + .And.BeWritable() + .And.BeVirtual() + ; + + typeof(Options) + .Should().HaveProperty("Title") + .Which.Should().BeReadable() + .And.BeWritable() + .And.BeVirtual() + ; + + typeof(Options) + .Should().HaveProperty("Summary") + .Which.Should().BeReadable() + .And.BeWritable() + .And.BeVirtual() + ; + + typeof(Options) + .Should().HaveProperty("Text") + .Which.Should().BeReadable() + .And.BeWritable() + .And.BeVirtual() + ; + + typeof(Options) + .Should().HaveProperty("ThemeColor") + .Which.Should().BeReadable() + .And.BeWritable() + .And.BeVirtual() + ; + + typeof(Options) + .Should().HaveProperty("Sections") + .Which.Should().BeReadable() + .And.BeWritable() + .And.BeVirtual() + ; + + typeof(Options) + .Should().HaveProperty("Actions") + .Which.Should().BeReadable() + .And.BeWritable() + .And.BeVirtual() + ; + } + + [TestMethod] + public void Given_Type_Then_It_Should_Have_Decorators() + { + typeof(Options) + .Should().HaveProperty("WebhookUri") + .Which.Should().BeDecoratedWith( + p => p.LongName.Equals("webhook-uri") && + p.Required == true) + ; + + typeof(Options) + .Should().HaveProperty("Title") + .Which.Should().BeDecoratedWith( + p => p.LongName.Equals("title") && + p.Required == false && + p.Default as string == string.Empty) + ; + + typeof(Options) + .Should().HaveProperty("Summary") + .Which.Should().BeDecoratedWith( + p => p.LongName.Equals("summary") && + p.Required == true) + ; + + typeof(Options) + .Should().HaveProperty("Text") + .Which.Should().BeDecoratedWith( + p => p.LongName.Equals("text") && + p.Required == false && + p.Default as string == string.Empty) + ; + + typeof(Options) + .Should().HaveProperty("ThemeColor") + .Which.Should().BeDecoratedWith( + p => p.LongName.Equals("theme-color") && + p.Required == false && + p.Default as string == string.Empty) + ; + + typeof(Options) + .Should().HaveProperty("Sections") + .Which.Should().BeDecoratedWith( + p => p.LongName.Equals("sections") && + p.Required == false && + p.Default as string == string.Empty) + ; + + typeof(Options) + .Should().HaveProperty("Actions") + .Which.Should().BeDecoratedWith( + p => p.LongName.Equals("actions") && + p.Required == false && + p.Default as string == string.Empty) + ; + } + } +} diff --git a/test/GitHubActions.Teams.ConsoleApp.Tests/ProgramTests.cs b/test/GitHubActions.Teams.ConsoleApp.Tests/ProgramTests.cs new file mode 100644 index 0000000..1097ad4 --- /dev/null +++ b/test/GitHubActions.Teams.ConsoleApp.Tests/ProgramTests.cs @@ -0,0 +1,94 @@ +using System.Linq; +using System.Net.Http; + +using Aliencube.GitHubActions.Teams.ConsoleApp; + +using FluentAssertions; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Moq; + +using Newtonsoft.Json; + +namespace GitHubActions.Teams.ConsoleApp.Tests +{ + [TestClass] + public class ProgramTests + { + [TestMethod] + public void Given_Type_Then_It_Should_Have_Properties() + { + typeof(Program) + .Should().HaveProperty("MessageHandler") + .Which.Should().BeReadable() + .And.BeWritable() + ; + + typeof(Program) + .Should().HaveProperty("HttpClient") + .Which.Should().BeReadable() + .And.BeWritable() + ; + } + + [TestMethod] + public void Given_Type_Then_It_Should_Have_Methods() + { + typeof(Program) + .Should().HaveMethod("Main", new[] { typeof(string[]) }) + ; + } + + [DataTestMethod] + [DataRow(0, 0)] + [DataRow(1, 1)] + public void Given_Valid_Args_When_Main_Invoked_Then_It_Should_Return_Result(int exitCode, int expected) + { + var handler = new Mock(); + handler.Setup(p => p.BuildMessage(It.IsAny(), It.IsAny())).Returns(handler.Object); + handler.Setup(p => p.SendMessageAsync(It.IsAny())).ReturnsAsync(exitCode); + + Program.MessageHandler = handler.Object; + + var args = new[] { + "--webhook-uri", + "https://localhosts", + "--summary", + "This is the summary", + "--sections", + "[ { \"activityTitle\": \"This is activity title\" } ]", + "--actions", + "[ { \"@type\": \"OpenUri\", \"name\": \"lorem ipsum\", \"targets\": [ { \"os\": \"default\", \"uri\": \"https://localhost\" } ] } ]" + }.ToArray(); + + var result = Program.Main(args); + + result.Should().Be(expected); + } + + [TestMethod] + public void Given_Invalid_Args_When_Main_Invoked_Then_It_Should_Return_Result() + { + var handler = new Mock(); + handler.Setup(p => p.BuildMessage(It.IsAny(), It.IsAny())).Returns(handler.Object); + handler.Setup(p => p.SendMessageAsync(It.IsAny())).ReturnsAsync(0); + + Program.MessageHandler = handler.Object; + + var args = new[] { + "--webhook-uri", + "https://localhosts", + "--summary", + "--sections", + "[ { \"activityTitle\": \"This is activity title\" } ]", + "--actions", + "[ { \"@type\": \"OpenUri\", \"name\": \"lorem ipsum\", \"targets\": [ { \"os\": \"default\", \"uri\": \"https://localhost\" } ] } ]" + }.ToArray(); + + var result = Program.Main(args); + + result.Should().BeGreaterThan(0); + } + } +}