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);
+ }
+ }
+}