diff --git a/CustomDictionary.xml b/CustomDictionary.xml
index 054168f..ac4dd49 100644
--- a/CustomDictionary.xml
+++ b/CustomDictionary.xml
@@ -8,6 +8,7 @@
apis
awaitable
bitbucket
+ changeset
deployer
dequeueing
dequeues
@@ -17,12 +18,14 @@
git
instagram
kudu
+ kanban
mvc
nuget
paypal
salesforce
trello
ver
+ vsts
zapier
zendesk
你好
@@ -42,6 +45,7 @@
CRM
SSO
+ VSTS
diff --git a/WebHooks.sln b/WebHooks.sln
index 0a6860b..950d656 100644
--- a/WebHooks.sln
+++ b/WebHooks.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.24720.0
+VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Src", "Src", "{929F44D0-A040-4DC3-A22F-4C5829C05D44}"
EndProject
@@ -152,6 +152,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.WebHooks.R
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.WebHooks.Receivers.Zendesk.Test", "test\Microsoft.AspNet.WebHooks.Receivers.Zendesk.Test\Microsoft.AspNet.WebHooks.Receivers.Zendesk.Test.csproj", "{D4AE6D4C-004F-4392-9EE1-772444719449}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.WebHooks.Receivers.VSTS", "src\Microsoft.AspNet.WebHooks.Receivers.VSTS\Microsoft.AspNet.WebHooks.Receivers.VSTS.csproj", "{6B501D55-2AA3-4757-A185-1277BB881DE8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.WebHooks.Receivers.VSTS.Test", "test\Microsoft.AspNet.WebHooks.Receivers.VSTS.Test\Microsoft.AspNet.WebHooks.Receivers.VSTS.Test.csproj", "{B27CC8B5-BF38-434B-B5CE-42ACEC40624C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VstsReceiver", "samples\VstsReceiver\VstsReceiver.csproj", "{88FB9535-BBC9-48FE-AC34-4C3E9A4073F3}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
CodeAnalysis|Any CPU = CodeAnalysis|Any CPU
@@ -555,6 +561,24 @@ Global
{D4AE6D4C-004F-4392-9EE1-772444719449}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4AE6D4C-004F-4392-9EE1-772444719449}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4AE6D4C-004F-4392-9EE1-772444719449}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6B501D55-2AA3-4757-A185-1277BB881DE8}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU
+ {6B501D55-2AA3-4757-A185-1277BB881DE8}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU
+ {6B501D55-2AA3-4757-A185-1277BB881DE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6B501D55-2AA3-4757-A185-1277BB881DE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6B501D55-2AA3-4757-A185-1277BB881DE8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6B501D55-2AA3-4757-A185-1277BB881DE8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B27CC8B5-BF38-434B-B5CE-42ACEC40624C}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU
+ {B27CC8B5-BF38-434B-B5CE-42ACEC40624C}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU
+ {B27CC8B5-BF38-434B-B5CE-42ACEC40624C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B27CC8B5-BF38-434B-B5CE-42ACEC40624C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B27CC8B5-BF38-434B-B5CE-42ACEC40624C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B27CC8B5-BF38-434B-B5CE-42ACEC40624C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {88FB9535-BBC9-48FE-AC34-4C3E9A4073F3}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU
+ {88FB9535-BBC9-48FE-AC34-4C3E9A4073F3}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU
+ {88FB9535-BBC9-48FE-AC34-4C3E9A4073F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {88FB9535-BBC9-48FE-AC34-4C3E9A4073F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {88FB9535-BBC9-48FE-AC34-4C3E9A4073F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {88FB9535-BBC9-48FE-AC34-4C3E9A4073F3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -626,6 +650,9 @@ Global
{BD0AB018-7229-42BF-B607-5D46E9F1F2EA} = {E957C8D9-B4A0-488B-838F-BAB4DE080A76}
{DCD550FB-FE5E-4684-AA52-C09FE5F8FDDA} = {929F44D0-A040-4DC3-A22F-4C5829C05D44}
{D4AE6D4C-004F-4392-9EE1-772444719449} = {9575CB90-BC4B-43BB-8AEA-82C53FDA4187}
+ {6B501D55-2AA3-4757-A185-1277BB881DE8} = {929F44D0-A040-4DC3-A22F-4C5829C05D44}
+ {B27CC8B5-BF38-434B-B5CE-42ACEC40624C} = {9575CB90-BC4B-43BB-8AEA-82C53FDA4187}
+ {88FB9535-BBC9-48FE-AC34-4C3E9A4073F3} = {E957C8D9-B4A0-488B-838F-BAB4DE080A76}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
EnterpriseLibraryConfigurationToolBinariesPathV6 = packages\EnterpriseLibrary.TransientFaultHandling.6.0.1304.0\lib\portable-net45+win+wp8;packages\EnterpriseLibrary.TransientFaultHandling.Data.6.0.1304.1\lib\NET45
diff --git a/samples/VstsReceiver/App_Start/WebApiConfig.cs b/samples/VstsReceiver/App_Start/WebApiConfig.cs
new file mode 100644
index 0000000..b7f2251
--- /dev/null
+++ b/samples/VstsReceiver/App_Start/WebApiConfig.cs
@@ -0,0 +1,24 @@
+using System.Web.Http;
+
+namespace VstsReceiver
+{
+ public static class WebApiConfig
+ {
+ public static void Register(HttpConfiguration config)
+ {
+ // Web API configuration and services
+
+ // Web API routes
+ config.MapHttpAttributeRoutes();
+
+ config.Routes.MapHttpRoute(
+ name: "DefaultApi",
+ routeTemplate: "api/{controller}/{id}",
+ defaults: new { id = RouteParameter.Optional }
+ );
+
+ // Initialize Vsts WebHook receiver
+ config.InitializeReceiveVstsWebHooks();
+ }
+ }
+}
diff --git a/samples/VstsReceiver/Global.asax b/samples/VstsReceiver/Global.asax
new file mode 100644
index 0000000..d6279fa
--- /dev/null
+++ b/samples/VstsReceiver/Global.asax
@@ -0,0 +1 @@
+<%@ Application Codebehind="Global.asax.cs" Inherits="VstsReceiver.WebApiApplication" Language="C#" %>
diff --git a/samples/VstsReceiver/Global.asax.cs b/samples/VstsReceiver/Global.asax.cs
new file mode 100644
index 0000000..d7e0575
--- /dev/null
+++ b/samples/VstsReceiver/Global.asax.cs
@@ -0,0 +1,12 @@
+using System.Web.Http;
+
+namespace VstsReceiver
+{
+ public class WebApiApplication : System.Web.HttpApplication
+ {
+ protected void Application_Start()
+ {
+ GlobalConfiguration.Configure(WebApiConfig.Register);
+ }
+ }
+}
diff --git a/samples/VstsReceiver/Properties/AssemblyInfo.cs b/samples/VstsReceiver/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..0f60383
--- /dev/null
+++ b/samples/VstsReceiver/Properties/AssemblyInfo.cs
@@ -0,0 +1,34 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("VstsReceivers")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("VststReceivers")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("3f9f62dd-c365-4fec-a10e-5314d111ebf5")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Revision and Build Numbers
+// by using the '*' as shown below:
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/samples/VstsReceiver/VstsReceiver.csproj b/samples/VstsReceiver/VstsReceiver.csproj
new file mode 100644
index 0000000..62e6648
--- /dev/null
+++ b/samples/VstsReceiver/VstsReceiver.csproj
@@ -0,0 +1,161 @@
+
+
+
+
+
+
+ Debug
+ AnyCPU
+
+
+ 2.0
+ {88FB9535-BBC9-48FE-AC34-4C3E9A4073F3}
+ {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}
+ Library
+ Properties
+ VstsReceiver
+ VstsReceiver
+ v4.5.1
+ true
+
+
+
+
+
+
+
+
+
+
+ true
+ full
+ false
+ bin\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\..\bin\CodeAnalysis\Microsoft.AspNet.WebHooks.Receivers.VSTS.dll
+
+
+ ..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.0\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll
+ True
+
+
+
+
+
+ ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll
+ True
+
+
+
+
+
+
+
+
+
+ ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll
+ True
+
+
+ ..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.2\lib\net45\System.Web.Http.WebHost.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+ ..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll
+
+
+
+
+
+
+
+
+
+
+ Global.asax
+
+
+
+
+
+
+
+ Web.config
+
+
+ Web.config
+
+
+
+
+
+ {f7dd0935-6320-4efc-9464-d5a2d2b8c2f7}
+ Microsoft.AspNet.WebHooks.Common
+
+
+ {8ced31fb-32f2-4ffb-9997-452fb9728577}
+ Microsoft.AspNet.WebHooks.Receivers
+
+
+
+ 10.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+
+
+
+
+
+ True
+ True
+ 15572
+ /
+ http://localhost:50009
+ False
+ False
+
+
+ False
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/VstsReceiver/Web.Debug.config b/samples/VstsReceiver/Web.Debug.config
new file mode 100644
index 0000000..2e302f9
--- /dev/null
+++ b/samples/VstsReceiver/Web.Debug.config
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/VstsReceiver/Web.Release.config b/samples/VstsReceiver/Web.Release.config
new file mode 100644
index 0000000..c358444
--- /dev/null
+++ b/samples/VstsReceiver/Web.Release.config
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/VstsReceiver/Web.config b/samples/VstsReceiver/Web.config
new file mode 100644
index 0000000..70ef6f1
--- /dev/null
+++ b/samples/VstsReceiver/Web.config
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/VstsReceiver/WebHooks/VstsWebHookHandler.cs b/samples/VstsReceiver/WebHooks/VstsWebHookHandler.cs
new file mode 100644
index 0000000..8c35a56
--- /dev/null
+++ b/samples/VstsReceiver/WebHooks/VstsWebHookHandler.cs
@@ -0,0 +1,95 @@
+using Microsoft.AspNet.WebHooks;
+using Microsoft.AspNet.WebHooks.Payloads;
+using Newtonsoft.Json.Linq;
+using System.Threading.Tasks;
+
+namespace VstsReceiver.WebHooks
+{
+ ///
+ /// This handler processes WebHooks from Visual Studio Team Services and leverages the base handler.
+ /// For details about Visual Studio Team Services WebHooks, see https://www.visualstudio.com/en-us/get-started/integrate/service-hooks/webhooks-and-vso-vs .
+ ///
+ public class VstsWebHookHandler : VstsWebHookHandlerBase
+ {
+ ///
+ /// We use so just have to override the methods we want to process WebHooks for.
+ /// This one processes the WebHook.
+ ///
+ public override Task ExecuteAsync(WebHookHandlerContext context, BuildCompletedPayload payload)
+ {
+ return Task.FromResult(true);
+ }
+
+ ///
+ /// We use so just have to override the methods we want to process WebHooks for.
+ /// This one processes the WebHook.
+ ///
+ public override Task ExecuteAsync(WebHookHandlerContext context, TeamRoomMessagePostedPayload payload)
+ {
+ return Task.FromResult(true);
+ }
+
+ ///
+ /// We use so just have to override the methods we want to process WebHooks for.
+ /// This one processes the WebHook.
+ ///
+ public override Task ExecuteAsync(WebHookHandlerContext context, WorkItemCreatedPayload payload)
+ {
+ return Task.FromResult(true);
+ }
+
+ ///
+ /// We use so just have to override the methods we want to process WebHooks for.
+ /// This one processes the WebHook.
+ ///
+ public override Task ExecuteAsync(WebHookHandlerContext context, WorkItemCommentedOnPayload payload)
+ {
+ return Task.FromResult(true);
+ }
+
+ ///
+ /// We use so just have to override the methods we want to process WebHooks for.
+ /// This one processes the WebHook.
+ ///
+ public override Task ExecuteAsync(WebHookHandlerContext context, CodeCheckedInPayload payload)
+ {
+ return Task.FromResult(true);
+ }
+
+ ///
+ /// We use so just have to override the methods we want to process WebHooks for.
+ /// This one processes the WebHook.
+ ///
+ public override Task ExecuteAsync(WebHookHandlerContext context, WorkItemDeletedPayload payload)
+ {
+ return Task.FromResult(true);
+ }
+
+ ///
+ /// We use so just have to override the methods we want to process WebHooks for.
+ /// This one processes the WebHook.
+ ///
+ public override Task ExecuteAsync(WebHookHandlerContext context, WorkItemRestoredPayload payload)
+ {
+ return Task.FromResult(true);
+ }
+
+ ///
+ /// We use so just have to override the methods we want to process WebHooks for.
+ /// This one processes the WebHook.
+ ///
+ public override Task ExecuteAsync(WebHookHandlerContext context, WorkItemUpdatedPayload payload)
+ {
+ return Task.FromResult(true);
+ }
+
+ ///
+ /// We use so just have to override the methods we want to process WebHooks for.
+ /// This one processes the payload for unknown eventType .
+ ///
+ public override Task ExecuteAsync(WebHookHandlerContext context, JObject payload)
+ {
+ return Task.FromResult(true);
+ }
+ }
+}
diff --git a/samples/VstsReceiver/index.html b/samples/VstsReceiver/index.html
new file mode 100644
index 0000000..6853f8f
--- /dev/null
+++ b/samples/VstsReceiver/index.html
@@ -0,0 +1,27 @@
+
+
+
+ Microsoft ASP.NET WebHooks Visual Studio Team Services Receiver
+
+
+
+ Microsoft ASP.NET WebHooks Visual Studio Team Services Receiver
+
+
+ The VSTS WebHook Receiver provides support for receiving WebHooks from Visual Studio Team Services. A sample WebHook URI is:
+
+ https://<host>/api/webhooks/incoming/vsts?code=83699ec7c1d794c0c780e49a5c72972590571fd8
+
+
+ For security reasons the WebHook URI must be an https URI and contain a 'code' query parameter with the
+ same value as configured in the MS_WebHookReceiverSecret_VSTS application setting.
+ The 'code' parameter must be between 32 and 128 characters long. Optionally you can use IDs
+ to differentiate between multiple WebHooks, for example 'code0, id1=code1, id2=code2 '.
+
+
+
+ Please see Visual Studio Team Services WebHooks
+ for more information.
+
+
+
diff --git a/samples/VstsReceiver/packages.config b/samples/VstsReceiver/packages.config
new file mode 100644
index 0000000..9cc9e59
--- /dev/null
+++ b/samples/VstsReceiver/packages.config
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Extensions/HttpConfigurationExtensions.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Extensions/HttpConfigurationExtensions.cs
new file mode 100644
index 0000000..130ee0c
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Extensions/HttpConfigurationExtensions.cs
@@ -0,0 +1,30 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.ComponentModel;
+using Microsoft.AspNet.WebHooks.Config;
+
+namespace System.Web.Http
+{
+ ///
+ /// Extension methods for .
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static class HttpConfigurationExtensions
+ {
+ ///
+ /// Initializes support for receiving WebHooks generated by Visual Studio Team Services.
+ ///
+ /// A sample WebHook URI is 'https://<host>/api/webhooks/incoming/vsts/{id}?code={code} '.
+ /// For security reasons the WebHook URI must be an https URI and contain a 'code' query parameter with the
+ /// same value as configured in the 'MS_WebHookReceiverSecret_Tfs ' application setting, optionally using IDs
+ /// to differentiate between multiple WebHooks, for example 'secret0, id1=secret1, id2=secret2 '.
+ /// The 'code' parameter must be between 32 and 128 characters long.
+ ///
+ /// The current config.
+ public static void InitializeReceiveVstsWebHooks(this HttpConfiguration config)
+ {
+ WebHooksConfig.Initialize(config);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Handlers/VstsWebHookHandlerBase.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Handlers/VstsWebHookHandlerBase.cs
new file mode 100644
index 0000000..9fbdf01
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Handlers/VstsWebHookHandlerBase.cs
@@ -0,0 +1,147 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNet.WebHooks.Payloads;
+using Microsoft.AspNet.WebHooks.Properties;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.AspNet.WebHooks
+{
+ ///
+ /// Provides a base implementation which can be used to for handling Visual Studio Team Services WebHook
+ /// using strongly-typed payloads. For details about MyGet WebHooks, see https://www.visualstudio.com/en-us/get-started/integrate/service-hooks/webhooks-and-vso-vs .
+ ///
+ public abstract class VstsWebHookHandlerBase : WebHookHandler
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected VstsWebHookHandlerBase()
+ {
+ this.Receiver = VstsWebHookReceiver.ReceiverName;
+ }
+
+ ///
+ public override Task ExecuteAsync(string receiver, WebHookHandlerContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ string action = context.Actions.First();
+ JObject data = context.GetDataOrDefault();
+
+ // map eventType to corresponding payload
+ switch (action)
+ {
+ case "workitem.updated": return ExecuteAsync(context, data.ToObject());
+ case "workitem.restored": return ExecuteAsync(context, data.ToObject());
+ case "workitem.deleted": return ExecuteAsync(context, data.ToObject());
+ case "workitem.created": return ExecuteAsync(context, data.ToObject());
+ case "workitem.commented": return ExecuteAsync(context, data.ToObject());
+ case "message.posted": return ExecuteAsync(context, data.ToObject());
+ case "tfvc.checkin": return ExecuteAsync(context, data.ToObject());
+ case "build.complete": return ExecuteAsync(context, data.ToObject());
+ default:
+ string msg = string.Format(CultureInfo.CurrentCulture, VstsReceiverResources.Handler_NonMappedEventType, action);
+ context.RequestContext.Configuration.DependencyResolver.GetLogger().Warn(msg);
+ return ExecuteAsync(context, data);
+ }
+ }
+
+ ///
+ /// Executes the incoming WebHook request for event 'workitem.updated '.
+ ///
+ /// Provides context for the for further processing the incoming WebHook.
+ /// Strong-typed WebHook payload.
+ public virtual Task ExecuteAsync(WebHookHandlerContext context, WorkItemUpdatedPayload payload)
+ {
+ return Task.FromResult(true);
+ }
+
+ ///
+ /// Executes the incoming WebHook request for event 'workitem.restored '.
+ ///
+ /// Provides context for the for further processing the incoming WebHook.
+ /// Strong-typed WebHook payload.
+ public virtual Task ExecuteAsync(WebHookHandlerContext context, WorkItemRestoredPayload payload)
+ {
+ return Task.FromResult(true);
+ }
+
+ ///
+ /// Executes the incoming WebHook request for event 'workitem.deleted '.
+ ///
+ /// Provides context for the for further processing the incoming WebHook.
+ /// Strong-typed WebHook payload.
+ public virtual Task ExecuteAsync(WebHookHandlerContext context, WorkItemDeletedPayload payload)
+ {
+ return Task.FromResult(true);
+ }
+
+ ///
+ /// Executes the incoming WebHook request for event 'workitem.created '.
+ ///
+ /// Provides context for the for further processing the incoming WebHook.
+ /// Strong-typed WebHook payload.
+ public virtual Task ExecuteAsync(WebHookHandlerContext context, WorkItemCreatedPayload payload)
+ {
+ return Task.FromResult(true);
+ }
+
+ ///
+ /// Executes the incoming WebHook request for event 'workitem.commented '.
+ ///
+ /// Provides context for the for further processing the incoming WebHook.
+ /// Strong-typed WebHook payload.
+ public virtual Task ExecuteAsync(WebHookHandlerContext context, WorkItemCommentedOnPayload payload)
+ {
+ return Task.FromResult(true);
+ }
+
+ ///
+ /// Executes the incoming WebHook request for event 'message.posted '.
+ ///
+ /// Provides context for the for further processing the incoming WebHook.
+ /// Strong-typed WebHook payload.
+ public virtual Task ExecuteAsync(WebHookHandlerContext context, TeamRoomMessagePostedPayload payload)
+ {
+ return Task.FromResult(true);
+ }
+
+ ///
+ /// Executes the incoming WebHook request for event 'tfvc.checkin '.
+ ///
+ /// Provides context for the for further processing the incoming WebHook.
+ /// Strong-typed WebHook payload.
+ public virtual Task ExecuteAsync(WebHookHandlerContext context, CodeCheckedInPayload payload)
+ {
+ return Task.FromResult(true);
+ }
+
+ ///
+ /// Executes the incoming WebHook request for event 'build.complete '.
+ ///
+ /// Provides context for the for further processing the incoming WebHook.
+ /// Strong-typed WebHook payload.
+ public virtual Task ExecuteAsync(WebHookHandlerContext context, BuildCompletedPayload payload)
+ {
+ return Task.FromResult(true);
+ }
+
+ ///
+ /// Executes the incoming WebHook request for unknown event.
+ ///
+ /// Provides context for the for further processing the incoming WebHook.
+ /// JSON payload.
+ public virtual Task ExecuteAsync(WebHookHandlerContext context, JObject payload)
+ {
+ return Task.FromResult(true);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Microsoft.AspNet.WebHooks.Receivers.VSTS.csproj b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Microsoft.AspNet.WebHooks.Receivers.VSTS.csproj
new file mode 100644
index 0000000..5cf823a
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Microsoft.AspNet.WebHooks.Receivers.VSTS.csproj
@@ -0,0 +1,137 @@
+
+
+
+
+ Debug
+ AnyCPU
+
+
+ 2.0
+ {6B501D55-2AA3-4757-A185-1277BB881DE8}
+ Library
+ Properties
+ Microsoft.AspNet.WebHooks
+ Microsoft.AspNet.WebHooks.Receivers.VSTS
+ $(OutputPath)$(AssemblyName).xml
+ $(CodeAnalysis)
+ /assemblyCompareMode:StrongNameIgnoringVersion
+ ..\..\FxCop.ruleset
+ $(DefineConstants);ASPNETWEBHOOKS
+ true
+ b718ef60
+
+
+
+ ..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll
+ True
+
+
+
+
+ ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll
+ True
+
+
+ ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll
+ True
+
+
+
+
+
+
+
+
+
+
+ Properties\CommonAssemblyInfo.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ VstsReceiverResources.resx
+ True
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Designer
+
+
+
+
+
+ {f7dd0935-6320-4efc-9464-d5a2d2b8c2f7}
+ Microsoft.AspNet.WebHooks.Common
+
+
+ {8CED31FB-32F2-4FFB-9997-452FB9728577}
+ Microsoft.AspNet.WebHooks.Receivers
+
+
+
+
+ CustomDictionary.xml
+ Designer
+
+
+
+
+ ResXFileCodeGenerator
+ VstsReceiverResources.Designer.cs
+ Designer
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Microsoft.AspNet.WebHooks.Receivers.VSTS.nuspec b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Microsoft.AspNet.WebHooks.Receivers.VSTS.nuspec
new file mode 100644
index 0000000..ad16f65
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Microsoft.AspNet.WebHooks.Receivers.VSTS.nuspec
@@ -0,0 +1,20 @@
+
+
+
+ $id$
+ $symversion$
+ Microsoft ASP.NET WebHooks Receiver for Visual Studio Team Services
+ Microsoft
+ Microsoft, aspnet
+ http://www.microsoft.com/web/webpi/eula/net_library_eula_ENU.htm
+ http://go.microsoft.com/fwlink/?LinkId=690277
+ http://go.microsoft.com/fwlink/?LinkID=288859
+ true
+
+ This package provides support for receiving WebHooks from Visual Studio Team Services. For information about Visual Studio Team Services WebHooks, see "https://www.visualstudio.com/en-us/get-started/integrate/service-hooks/webhooks-and-vso-vs".
+
+
+ © Microsoft Corporation. All rights reserved.
+ Microsoft AspNet WebApi AspNetWebApi WebHooks TFS VSTS Visual Studio Team Services
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BasePayload.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BasePayload.cs
new file mode 100644
index 0000000..f1a0c63
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BasePayload.cs
@@ -0,0 +1,81 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Root object of payload sent for all types of events.
+ ///
+ /// Type of resource within payload which differs depending on 'eventType ' field
+ public abstract class BasePayload where T : BaseResource
+ {
+ ///
+ /// Gets the subscription identifier which triggered the event.
+ ///
+ [JsonProperty("subscriptionId")]
+ public string SubscriptionId { get; set; }
+
+ ///
+ /// Gets the notification identifier within subscription.
+ ///
+ [JsonProperty("notificationId")]
+ public int NotificationId { get; set; }
+
+ ///
+ /// Gets the identifier of HTTP request.
+ ///
+ [JsonProperty("id")]
+ public string Id { get; set; }
+
+ ///
+ /// Gets the type of the event.
+ ///
+ [JsonProperty("eventType")]
+ public string EventType { get; set; }
+
+ ///
+ /// Gets the publisher identifier.
+ ///
+ [JsonProperty("publisherId")]
+ public string PublisherId { get; set; }
+
+ ///
+ /// Gets the message which describes the event.
+ ///
+ [JsonProperty("message")]
+ public PayloadMessage Message { get; set; }
+
+ ///
+ /// Gets the detailed message which describes the event.
+ ///
+ [JsonProperty("detailedMessage")]
+ public PayloadMessage DetailedMessage { get; set; }
+
+ ///
+ /// Gets the resource itself - data associated with corresponding event.
+ ///
+ [JsonProperty("resource")]
+ public T Resource { get; set; }
+
+ ///
+ /// Gets the resource version.
+ ///
+ [JsonProperty("resourceVersion")]
+ public string ResourceVersion { get; set; }
+
+ ///
+ /// Gets the resource containers.
+ ///
+ [JsonProperty("resourceContainers")]
+ public PayloadResourceContainers ResourceContainers { get; set; }
+
+ ///
+ /// Gets the date when HTTP request was created.
+ ///
+ [JsonProperty("createdDate")]
+ public DateTime CreatedDate { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BaseResource.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BaseResource.cs
new file mode 100644
index 0000000..c7e08a6
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BaseResource.cs
@@ -0,0 +1,13 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Base class for resource object which describes
+ /// a specific event type.
+ ///
+ public abstract class BaseResource
+ {
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BaseWorkItemResource.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BaseWorkItemResource.cs
new file mode 100644
index 0000000..e083307
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BaseWorkItemResource.cs
@@ -0,0 +1,45 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Base class for resource object which describes WorkItem event types.
+ ///
+ /// Type which describes fields associated with this kind of WorkItem change
+ public abstract class BaseWorkItemResource : BaseResource
+ {
+ ///
+ /// Gets the identifier of WorkItem.
+ ///
+ [JsonProperty("id")]
+ public int Id { get; set; }
+
+ ///
+ /// Gets the revision number.
+ ///
+ [JsonProperty("rev")]
+ public int RevisionNumber { get; set; }
+
+ ///
+ /// Gets fields associated with the WorkItem.
+ ///
+ [JsonProperty("fields")]
+ public T Fields { get; set; }
+
+ ///
+ /// Gets links associated with the WorkItem.
+ ///
+ [JsonProperty("_links")]
+ public WorkItemLinks Links { get; set; }
+
+ ///
+ /// Gets the URL of the WorkItem.
+ ///
+ [JsonProperty("url")]
+ public Uri Url { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedDefinition.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedDefinition.cs
new file mode 100644
index 0000000..18b95af
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedDefinition.cs
@@ -0,0 +1,50 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes build definition
+ ///
+ public class BuildCompletedDefinition
+ {
+ ///
+ /// Gets the size of the batch.
+ ///
+ [JsonProperty("batchSize")]
+ public int BatchSize { get; set; }
+
+ ///
+ /// Gets the trigger type.
+ ///
+ [JsonProperty("triggerType")]
+ public string TriggerType { get; set; }
+
+ ///
+ /// Gets the trigger type.
+ ///
+ [JsonProperty("definitionType")]
+ public string DefinitionType { get; set; }
+
+ ///
+ /// Gets the identifier of the build definition.
+ ///
+ [JsonProperty("id")]
+ public int Id { get; set; }
+
+ ///
+ /// Gets the name of the build definition.
+ ///
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ ///
+ /// Gets the URL of the build definition.
+ ///
+ [JsonProperty("url")]
+ public Uri Url { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedDrop.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedDrop.cs
new file mode 100644
index 0000000..0a934af
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedDrop.cs
@@ -0,0 +1,38 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes build drop
+ ///
+ public class BuildCompletedDrop
+ {
+ ///
+ /// Gets drop location.
+ ///
+ [JsonProperty("location")]
+ public string Location { get; set; }
+
+ ///
+ /// Gets drop type.
+ ///
+ [JsonProperty("type")]
+ public string DropType { get; set; }
+
+ ///
+ /// Gets drop location URL.
+ ///
+ [JsonProperty("url")]
+ public Uri Url { get; set; }
+
+ ///
+ /// Gets drop location download URL.
+ ///
+ [JsonProperty("downloadUrl")]
+ public Uri DownloadUrl { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedLog.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedLog.cs
new file mode 100644
index 0000000..dd22659
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedLog.cs
@@ -0,0 +1,32 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes build log
+ ///
+ public class BuildCompletedLog
+ {
+ ///
+ /// Gets the log type.
+ ///
+ [JsonProperty("type")]
+ public string LogType { get; set; }
+
+ ///
+ /// Gets the log URL.
+ ///
+ [JsonProperty("url")]
+ public Uri Url { get; set; }
+
+ ///
+ /// Gets the log download URL.
+ ///
+ [JsonProperty("downloadUrl")]
+ public Uri DownloadUrl { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedPayload.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedPayload.cs
new file mode 100644
index 0000000..64af11e
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedPayload.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the entire payload of event 'build.complete '.
+ ///
+ public class BuildCompletedPayload : BasePayload
+ {
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedQueueDefinition.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedQueueDefinition.cs
new file mode 100644
index 0000000..d2c4361
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedQueueDefinition.cs
@@ -0,0 +1,38 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the queue of the build.
+ ///
+ public class BuildCompletedQueueDefinition
+ {
+ ///
+ /// Gets the type of the queue.
+ ///
+ [JsonProperty("queueType")]
+ public string QueueType { get; set; }
+
+ ///
+ /// Gets the identifier of the queue.
+ ///
+ [JsonProperty("id")]
+ public int Id { get; set; }
+
+ ///
+ /// Gets the name of the queue.
+ ///
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ ///
+ /// Gets the URL of the queue.
+ ///
+ [JsonProperty("url")]
+ public Uri Url { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedRequest.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedRequest.cs
new file mode 100644
index 0000000..4d2cf79
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedRequest.cs
@@ -0,0 +1,32 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the request of the build.
+ ///
+ public class BuildCompletedRequest
+ {
+ ///
+ /// Gets the identifier of the request.
+ ///
+ [JsonProperty("id")]
+ public int Id { get; set; }
+
+ ///
+ /// Gets the URL of the request.
+ ///
+ [JsonProperty("url")]
+ public Uri Url { get; set; }
+
+ ///
+ /// Gets the user associated with the request.
+ ///
+ [JsonProperty("requestedFor")]
+ public ResourceUser RequestedFor { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedResource.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedResource.cs
new file mode 100644
index 0000000..af75d9e
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/BuildCompletedResource.cs
@@ -0,0 +1,128 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.ObjectModel;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the resource that associated with
+ ///
+ public class BuildCompletedResource : BaseResource
+ {
+ private readonly Collection _requests = new Collection();
+
+ ///
+ /// Gets the build URI.
+ ///
+ [JsonProperty("uri")]
+ public Uri Uri { get; set; }
+
+ ///
+ /// Gets the build identifier.
+ ///
+ [JsonProperty("id")]
+ public int Id { get; set; }
+
+ ///
+ /// Gets the build number.
+ ///
+ [JsonProperty("buildNumber")]
+ public string BuildNumber { get; set; }
+
+ ///
+ /// Gets the build URL.
+ ///
+ [JsonProperty("url")]
+ public Uri Url { get; set; }
+
+ ///
+ /// Gets the start time of the build.
+ ///
+ [JsonProperty("startTime")]
+ public DateTime StartTime { get; set; }
+
+ ///
+ /// Gets the finish time of the build.
+ ///
+ [JsonProperty("finishTime")]
+ public DateTime FinishTime { get; set; }
+
+ ///
+ /// Gets the reason which triggered the build.
+ ///
+ [JsonProperty("reason")]
+ public string Reason { get; set; }
+
+ ///
+ /// Gets the outcome status of the build.
+ ///
+ [JsonProperty("status")]
+ public string Status { get; set; }
+
+ ///
+ /// Gets the build drop location.
+ ///
+ [JsonProperty("dropLocation")]
+ public string DropLocation { get; set; }
+
+ ///
+ /// Gets the build drop.
+ ///
+ [JsonProperty("drop")]
+ public BuildCompletedDrop Drop { get; set; }
+
+ ///
+ /// Gets the build log.
+ ///
+ [JsonProperty("log")]
+ public BuildCompletedLog Log { get; set; }
+
+ ///
+ /// Gets the source version for the build.
+ ///
+ [JsonProperty("sourceGetVersion")]
+ public string SourceGetVersion { get; set; }
+
+ ///
+ /// Gets the user which last changed the source.
+ ///
+ [JsonProperty("lastChangedBy")]
+ public ResourceUser LastChangedBy { get; set; }
+
+ ///
+ /// Gets value indicating whether this build retain indefinitely.
+ ///
+ [JsonProperty("retainIndefinitely")]
+ public bool RetainIndefinitely { get; set; }
+
+ ///
+ /// Gets value indicating whether this build has diagnostics.
+ ///
+ [JsonProperty("hasDiagnostics")]
+ public bool HasDiagnostics { get; set; }
+
+ ///
+ /// Gets the definition of the build.
+ ///
+ [JsonProperty("definition")]
+ public BuildCompletedDefinition Definition { get; set; }
+
+ ///
+ /// Gets the build queue.
+ ///
+ [JsonProperty("queue")]
+ public BuildCompletedQueueDefinition Queue { get; set; }
+
+ ///
+ /// Gets build requests.
+ ///
+ [JsonProperty("requests")]
+ public Collection Requests
+ {
+ get { return _requests; }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/CodeCheckedInPayload.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/CodeCheckedInPayload.cs
new file mode 100644
index 0000000..2d7ee2a
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/CodeCheckedInPayload.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the entire payload of event 'tfvc.checkin '.
+ ///
+ public class CodeCheckedInPayload : BasePayload
+ {
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/CodeCheckedInResource.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/CodeCheckedInResource.cs
new file mode 100644
index 0000000..ddcd3ba
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/CodeCheckedInResource.cs
@@ -0,0 +1,50 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the resource that associated with
+ ///
+ public class CodeCheckedInResource : BaseResource
+ {
+ ///
+ /// Gets the changeset identifier.
+ ///
+ [JsonProperty("changesetId")]
+ public int ChangesetId { get; set; }
+
+ ///
+ /// Gets the changeset URL.
+ ///
+ [JsonProperty("url")]
+ public Uri Url { get; set; }
+
+ ///
+ /// Gets the changeset author.
+ ///
+ [JsonProperty("author")]
+ public ResourceUser Author { get; set; }
+
+ ///
+ /// Gets the user that checked in the changeset.
+ ///
+ [JsonProperty("checkedInBy")]
+ public ResourceUser CheckedInBy { get; set; }
+
+ ///
+ /// Gets the changeset creation date.
+ ///
+ [JsonProperty("createdDate")]
+ public DateTime CreatedDate { get; set; }
+
+ ///
+ /// Gets the changeset comment.
+ ///
+ [JsonProperty("comment")]
+ public string Comment { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/PayloadMessage.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/PayloadMessage.cs
new file mode 100644
index 0000000..7f6d1be
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/PayloadMessage.cs
@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes payload message.
+ ///
+ public class PayloadMessage
+ {
+ ///
+ /// Gets the message in plain text.
+ ///
+ [JsonProperty("text")]
+ public string Text { get; set; }
+
+ ///
+ /// Gets the message in HTML format.
+ ///
+ [JsonProperty("html")]
+ public string Html { get; set; }
+
+ ///
+ /// Gets the message in markdown format.
+ ///
+ [JsonProperty("markdown")]
+ public string Markdown { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/PayloadResourceContainer.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/PayloadResourceContainer.cs
new file mode 100644
index 0000000..1339f93
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/PayloadResourceContainer.cs
@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes container
+ ///
+ public class PayloadResourceContainer
+ {
+ ///
+ /// Gets the identifier of container.
+ ///
+ [JsonProperty("id")]
+ public string Id { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/PayloadResourceContainers.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/PayloadResourceContainers.cs
new file mode 100644
index 0000000..058cca7
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/PayloadResourceContainers.cs
@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes containers containing the resource
+ ///
+ public class PayloadResourceContainers
+ {
+ ///
+ /// Gets the collection.
+ ///
+ [JsonProperty("collection")]
+ public PayloadResourceContainer Collection { get; set; }
+
+ ///
+ /// Gets the account.
+ ///
+ [JsonProperty("account")]
+ public PayloadResourceContainer Account { get; set; }
+
+ ///
+ /// Gets the project.
+ ///
+ [JsonProperty("project")]
+ public PayloadResourceContainer Project { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/ResourceUser.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/ResourceUser.cs
new file mode 100644
index 0000000..d9c94f5
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/ResourceUser.cs
@@ -0,0 +1,44 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes user entity
+ ///
+ public class ResourceUser
+ {
+ ///
+ /// Gets the identifier of the user.
+ ///
+ [JsonProperty("id")]
+ public string Id { get; set; }
+
+ ///
+ /// Gets the user display name.
+ ///
+ [JsonProperty("displayName")]
+ public string DisplayName { get; set; }
+
+ ///
+ /// Gets the user unique name.
+ ///
+ [JsonProperty("uniqueName")]
+ public string UniqueName { get; set; }
+
+ ///
+ /// Gets the user URL.
+ ///
+ [JsonProperty("url")]
+ public Uri Url { get; set; }
+
+ ///
+ /// Gets the user image URL.
+ ///
+ [JsonProperty("imageUrl")]
+ public Uri ImageUrl { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/TeamRoomMessagePostedPayload.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/TeamRoomMessagePostedPayload.cs
new file mode 100644
index 0000000..95abcf4
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/TeamRoomMessagePostedPayload.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the entire payload of event 'message.posted '.
+ ///
+ public class TeamRoomMessagePostedPayload : BasePayload
+ {
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/TeamRoomMessagePostedResource.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/TeamRoomMessagePostedResource.cs
new file mode 100644
index 0000000..a54ecd1
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/TeamRoomMessagePostedResource.cs
@@ -0,0 +1,50 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the resource that associated with
+ ///
+ public class TeamRoomMessagePostedResource : BaseResource
+ {
+ ///
+ /// Gets the identifier.
+ ///
+ [JsonProperty("id")]
+ public int Id { get; set; }
+
+ ///
+ /// Gets the content of the message.
+ ///
+ [JsonProperty("content")]
+ public string Content { get; set; }
+
+ ///
+ /// Gets the type of the message.
+ ///
+ [JsonProperty("messageType")]
+ public string MessageType { get; set; }
+
+ ///
+ /// Gets the posted time of the message.
+ ///
+ [JsonProperty("postedTime")]
+ public DateTime PostedTime { get; set; }
+
+ ///
+ /// Gets the room identifier where message was posted.
+ ///
+ [JsonProperty("postedRoomId")]
+ public int PostedRoomId { get; set; }
+
+ ///
+ /// Gets the user who posted the message.
+ ///
+ [JsonProperty("postedBy")]
+ public ResourceUser PostedBy { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemCommentedOnPayload.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemCommentedOnPayload.cs
new file mode 100644
index 0000000..31a4d74
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemCommentedOnPayload.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the entire payload of event 'workitem.commented '.
+ ///
+ public class WorkItemCommentedOnPayload : BasePayload
+ {
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemCommentedOnResource.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemCommentedOnResource.cs
new file mode 100644
index 0000000..28d0f7b
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemCommentedOnResource.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the resource that associated with
+ ///
+ public class WorkItemCommentedOnResource : BaseWorkItemResource
+ {
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemCreatedPayload.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemCreatedPayload.cs
new file mode 100644
index 0000000..bb54be0
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemCreatedPayload.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the entire payload of event 'workitem.created '.
+ ///
+ public class WorkItemCreatedPayload : BasePayload
+ {
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemCreatedResource.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemCreatedResource.cs
new file mode 100644
index 0000000..fdecd0f
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemCreatedResource.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the resource that associated with
+ ///
+ public class WorkItemCreatedResource : BaseWorkItemResource
+ {
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemDeletedPayload.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemDeletedPayload.cs
new file mode 100644
index 0000000..303ac73
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemDeletedPayload.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the entire payload of event 'workitem.deleted '.
+ ///
+ public class WorkItemDeletedPayload : BasePayload
+ {
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemDeletedResource.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemDeletedResource.cs
new file mode 100644
index 0000000..ec9645d
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemDeletedResource.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the resource that associated with
+ ///
+ public class WorkItemDeletedResource : BaseWorkItemResource
+ {
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemFields.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemFields.cs
new file mode 100644
index 0000000..57af3b0
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemFields.cs
@@ -0,0 +1,98 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes fields of the WorkItem
+ ///
+ public class WorkItemFields
+ {
+ ///
+ /// Gets the value of field System.AreaPath .
+ ///
+ [JsonProperty("System.AreaPath")]
+ public string SystemAreaPath { get; set; }
+
+ ///
+ /// Gets the value of field System.TeamProject .
+ ///
+ [JsonProperty("System.TeamProject")]
+ public string SystemTeamProject { get; set; }
+
+ ///
+ /// Gets the value of field System.IterationPath .
+ ///
+ [JsonProperty("System.IterationPath")]
+ public string SystemIterationPath { get; set; }
+
+ ///
+ /// Gets the value of field System.WorkItemType .
+ ///
+ [JsonProperty("System.WorkItemType")]
+ public string SystemWorkItemType { get; set; }
+
+ ///
+ /// Gets the value of field System.State .
+ ///
+ [JsonProperty("System.State")]
+ public string SystemState { get; set; }
+
+ ///
+ /// Gets the value of field System.Reason .
+ ///
+ [JsonProperty("System.Reason")]
+ public string SystemReason { get; set; }
+
+ ///
+ /// Gets the value of field System.CreatedDate .
+ ///
+ [JsonProperty("System.CreatedDate")]
+ public DateTime SystemCreatedDate { get; set; }
+
+ ///
+ /// Gets the value of field System.CreatedBy .
+ ///
+ [JsonProperty("System.CreatedBy")]
+ public string SystemCreatedBy { get; set; }
+
+ ///
+ /// Gets the value of field System.ChangedDate .
+ ///
+ [JsonProperty("System.ChangedDate")]
+ public DateTime SystemChangedDate { get; set; }
+
+ ///
+ /// Gets the value of field System.ChangedBy .
+ ///
+ [JsonProperty("System.ChangedBy")]
+ public string SystemChangedBy { get; set; }
+
+ ///
+ /// Gets the value of field System.Title .
+ ///
+ [JsonProperty("System.Title")]
+ public string SystemTitle { get; set; }
+
+ ///
+ /// Gets the value of field Microsoft.VSTS.Common.Severity .
+ ///
+ [JsonProperty("Microsoft.VSTS.Common.Severity")]
+ public string MicrosoftCommonSeverity { get; set; }
+
+ ///
+ /// Gets the value of field WEF_EB329F44FE5F4A94ACB1DA153FDF38BA_Kanban.Column .
+ ///
+ [JsonProperty("WEF_EB329F44FE5F4A94ACB1DA153FDF38BA_Kanban.Column")]
+ public string KanbanColumn { get; set; }
+
+ ///
+ /// Gets the value of field System.History .
+ ///
+ [JsonProperty("System.History")]
+ public string SystemHistory { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemLink.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemLink.cs
new file mode 100644
index 0000000..35c6f7b
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemLink.cs
@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the WorkItem's link.
+ ///
+ public class WorkItemLink
+ {
+ ///
+ /// Gets the URL of WorkItem's link.
+ ///
+ [JsonProperty("href")]
+ public string Href { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemLinks.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemLinks.cs
new file mode 100644
index 0000000..c3633cc
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemLinks.cs
@@ -0,0 +1,61 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes links of the WorkItem.
+ ///
+ public class WorkItemLinks
+ {
+ ///
+ /// Gets the link to the WorkItem itself.
+ ///
+ [JsonProperty("self")]
+ public WorkItemLink Self { get; set; }
+
+ ///
+ /// Gets the link to the parent WorkItem if exists.
+ ///
+ [JsonProperty("parent")]
+ public WorkItemLink Parent { get; set; }
+
+ ///
+ /// Gets the link to the WorkItem' updates.
+ ///
+ [JsonProperty("workItemUpdates")]
+ public WorkItemLink WorkItemUpdates { get; set; }
+
+ ///
+ /// Gets the link to the WorkItem's revisions.
+ ///
+ [JsonProperty("workItemRevisions")]
+ public WorkItemLink WorkItemRevisions { get; set; }
+
+ ///
+ /// Gets the link to the WorkItem's type.
+ ///
+ [JsonProperty("workItemType")]
+ public WorkItemLink WorkItemType { get; set; }
+
+ ///
+ /// Gets the link to the WorkItem's fields.
+ ///
+ [JsonProperty("fields")]
+ public WorkItemLink Fields { get; set; }
+
+ ///
+ /// Gets the link to the WorkItem's HTML.
+ ///
+ [JsonProperty("html")]
+ public WorkItemLink Html { get; set; }
+
+ ///
+ /// Gets the link to the WorkItem's history.
+ ///
+ [JsonProperty("workItemHistory")]
+ public WorkItemLink WorkItemHistory { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemRestoredPayload.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemRestoredPayload.cs
new file mode 100644
index 0000000..f2a5ab6
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemRestoredPayload.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the entire payload of event 'workitem.restored '.
+ ///
+ public class WorkItemRestoredPayload : BasePayload
+ {
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemRestoredResource.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemRestoredResource.cs
new file mode 100644
index 0000000..1cfbb66
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemRestoredResource.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the resource that associated with
+ ///
+ public class WorkItemRestoredResource : BaseWorkItemResource
+ {
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemUpdatedFieldValue.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemUpdatedFieldValue.cs
new file mode 100644
index 0000000..cc67bae
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemUpdatedFieldValue.cs
@@ -0,0 +1,26 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes change of specific field
+ ///
+ /// The string-type of the field that is being changed
+ public class WorkItemUpdatedFieldValue
+ {
+ ///
+ /// Gets the value of the field before the change.
+ ///
+ [JsonProperty("oldValue")]
+ public T OldValue { get; set; }
+
+ ///
+ /// Gets the value of the field after the change.
+ ///
+ [JsonProperty("newValue")]
+ public T NewValue { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemUpdatedFields.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemUpdatedFields.cs
new file mode 100644
index 0000000..5f2a3c8
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemUpdatedFields.cs
@@ -0,0 +1,68 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes fields of the WorkItem that was updated
+ ///
+ public class WorkItemUpdatedFields
+ {
+ ///
+ /// Gets the change information for the field 'System.Rev '.
+ ///
+ [JsonProperty("System.Rev")]
+ public WorkItemUpdatedFieldValue SystemRev { get; set; }
+
+ ///
+ /// Gets the change information for the field 'System.AuthorizedDate '.
+ ///
+ [JsonProperty("System.AuthorizedDate")]
+ public WorkItemUpdatedFieldValue SystemAuthorizedDate { get; set; }
+
+ ///
+ /// Gets the change information for the field 'System.RevisedDate '.
+ ///
+ [JsonProperty("System.RevisedDate")]
+ public WorkItemUpdatedFieldValue SystemRevisedDate { get; set; }
+
+ ///
+ /// Gets the change information for the field 'System.State '.
+ ///
+ [JsonProperty("System.State")]
+ public WorkItemUpdatedFieldValue SystemState { get; set; }
+
+ ///
+ /// Gets the change information for the field 'System.Reason '.
+ ///
+ [JsonProperty("System.Reason")]
+ public WorkItemUpdatedFieldValue SystemReason { get; set; }
+
+ ///
+ /// Gets the change information for the field 'System.AssignedTo '.
+ ///
+ [JsonProperty("System.AssignedTo")]
+ public WorkItemUpdatedFieldValue SystemAssignedTo { get; set; }
+
+ ///
+ /// Gets the change information for the field 'System.ChangedDate '.
+ ///
+ [JsonProperty("System.ChangedDate")]
+ public WorkItemUpdatedFieldValue SystemChangedDate { get; set; }
+
+ ///
+ /// Gets the change information for the field 'System.Watermark '.
+ ///
+ [JsonProperty("System.Watermark")]
+ public WorkItemUpdatedFieldValue SystemWatermark { get; set; }
+
+ ///
+ /// Gets the change information for the field 'Microsoft.VSTS.Common.Severity '.
+ ///
+ [JsonProperty("Microsoft.Vsts.Common.Severity")]
+ public WorkItemUpdatedFieldValue MicrosoftCommonSeverity { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemUpdatedPayload.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemUpdatedPayload.cs
new file mode 100644
index 0000000..3c1047e
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemUpdatedPayload.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the entire payload of event 'workitem.updated '.
+ ///
+ public class WorkItemUpdatedPayload : BasePayload
+ {
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemUpdatedResource.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemUpdatedResource.cs
new file mode 100644
index 0000000..4fa8c72
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemUpdatedResource.cs
@@ -0,0 +1,38 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the resource that associated with
+ ///
+ public class WorkItemUpdatedResource : BaseWorkItemResource
+ {
+ ///
+ /// Gets WorkItem identifier.
+ ///
+ [JsonProperty("workItemId")]
+ public int WorkItemId { get; set; }
+
+ ///
+ /// Gets the author of revision.
+ ///
+ [JsonProperty("revisedBy")]
+ public ResourceUser RevisedBy { get; set; }
+
+ ///
+ /// Gets the revised date.
+ ///
+ [JsonProperty("revisedDate")]
+ public DateTime RevisedDate { get; set; }
+
+ ///
+ /// Gets the revision.
+ ///
+ [JsonProperty("revision")]
+ public WorkItemUpdatedRevision Revision { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemUpdatedRevision.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemUpdatedRevision.cs
new file mode 100644
index 0000000..8e0cb24
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Payloads/WorkItemUpdatedRevision.cs
@@ -0,0 +1,38 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.AspNet.WebHooks.Payloads
+{
+ ///
+ /// Describes the revision
+ ///
+ public class WorkItemUpdatedRevision
+ {
+ ///
+ /// Gets the identifier of the revision.
+ ///
+ [JsonProperty("id")]
+ public int Id { get; set; }
+
+ ///
+ /// Gets the revision number.
+ ///
+ [JsonProperty("rev")]
+ public int Rev { get; set; }
+
+ ///
+ /// Gets the revision fields.
+ ///
+ [JsonProperty("fields")]
+ public WorkItemFields Fields { get; set; }
+
+ ///
+ /// Gets the revision URL.
+ ///
+ [JsonProperty("url")]
+ public Uri Url { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Properties/AssemblyInfo.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..b4ca08d
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Properties/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNet.WebHooks.Receivers.TFS.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Properties/VstsReceiverResources.Designer.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Properties/VstsReceiverResources.Designer.cs
new file mode 100644
index 0000000..3487f72
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Properties/VstsReceiverResources.Designer.cs
@@ -0,0 +1,81 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Microsoft.AspNet.WebHooks.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class VstsReceiverResources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal VstsReceiverResources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNet.WebHooks.Properties.VstsReceiverResources", typeof(VstsReceiverResources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The property 'eventType' contains unmapped value '{0}'..
+ ///
+ internal static string Handler_NonMappedEventType {
+ get {
+ return ResourceManager.GetString("Handler_NonMappedEventType", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No property 'eventType' was found in root of the object..
+ ///
+ internal static string Receiver_NoEventType {
+ get {
+ return ResourceManager.GetString("Receiver_NoEventType", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Properties/VstsReceiverResources.resx b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Properties/VstsReceiverResources.resx
new file mode 100644
index 0000000..5ebe328
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/Properties/VstsReceiverResources.resx
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ The property 'eventType' contains unmapped value '{0}'.
+
+
+ No property 'eventType' was found in root of the object.
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/WebHooks/VstsWebHookReceiver.cs b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/WebHooks/VstsWebHookReceiver.cs
new file mode 100644
index 0000000..9a08492
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/WebHooks/VstsWebHookReceiver.cs
@@ -0,0 +1,87 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Web.Http.Controllers;
+using Microsoft.AspNet.WebHooks.Properties;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.AspNet.WebHooks
+{
+ ///
+ /// Provides an implementation which supports WebHooks generated by Visual Studio Team Services.
+ ///
+ /// The corresponding WebHook URI is of the form 'https://<host>/api/webhooks/incoming/vsts/{id}?code={code} '.
+ /// For security reasons the WebHook URI must be an https URI and contain a 'code' query parameter with the
+ /// same value as configured in the 'MS_WebHookReceiverSecret_Tfs ' application setting, optionally using IDs
+ /// to differentiate between multiple WebHooks, for example 'secret0, id1=secret1, id2=secret2 '.
+ /// The 'code' parameter must be between 32 and 128 characters long.
+ ///
+ /// For details about Visual Studio Team Services WebHooks, see https://www.visualstudio.com/en-us/get-started/integrate/service-hooks/webhooks-and-vso-vs .
+ ///
+ public class VstsWebHookReceiver : WebHookReceiver
+ {
+ internal const string RecName = "vsts";
+ internal const string EventTypeTokenName = "eventType";
+
+ ///
+ /// Gets the receiver name for this receiver.
+ ///
+ public static string ReceiverName
+ {
+ get { return RecName; }
+ }
+
+ ///
+ public override string Name
+ {
+ get { return RecName; }
+ }
+
+ ///
+ public override async Task ReceiveAsync(string id, HttpRequestContext context, HttpRequestMessage request)
+ {
+ if (id == null)
+ {
+ throw new ArgumentNullException(nameof(id));
+ }
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+ if (request == null)
+ {
+ throw new ArgumentNullException(nameof(request));
+ }
+
+ if (request.Method != HttpMethod.Post)
+ {
+ return CreateBadMethodResponse(request);
+ }
+
+ // Ensure that we use https and have a valid code parameter
+ await EnsureValidCode(request, id);
+
+ // Read the request entity body
+ JObject jsonBody = await ReadAsJsonAsync(request);
+
+ // Read the action from body
+ JToken action;
+ string actionAsString;
+ if (!jsonBody.TryGetValue(EventTypeTokenName, out action))
+ {
+ request.GetConfiguration().DependencyResolver.GetLogger().Error(VstsReceiverResources.Receiver_NoEventType);
+ return request.CreateErrorResponse(HttpStatusCode.BadRequest, VstsReceiverResources.Receiver_NoEventType);
+ }
+ else
+ {
+ actionAsString = action.Value();
+ }
+
+ return await ExecuteWebHookAsync(id, context, request, new[] { actionAsString }, jsonBody);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/packages.config b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/packages.config
new file mode 100644
index 0000000..11b3234
--- /dev/null
+++ b/src/Microsoft.AspNet.WebHooks.Receivers.VSTS/packages.config
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/App.config b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/App.config
new file mode 100644
index 0000000..8daa28a
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/App.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Common/Extensions.cs b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Common/Extensions.cs
new file mode 100644
index 0000000..4c20f77
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Common/Extensions.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Globalization;
+
+namespace Microsoft.AspNet.WebHooks
+{
+ internal static class Extensions
+ {
+ public static DateTime ToDateTime(this string self)
+ {
+ return DateTime.Parse(self, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Handlers/VstsWebHookHandlerBaseTests.cs b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Handlers/VstsWebHookHandlerBaseTests.cs
new file mode 100644
index 0000000..6965bca
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Handlers/VstsWebHookHandlerBaseTests.cs
@@ -0,0 +1,168 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Web.Http;
+using System.Web.Http.Controllers;
+using Microsoft.AspNet.WebHooks.Payloads;
+using Moq;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Microsoft.AspNet.WebHooks.Handlers
+{
+ public class VstsWebHookHandlerBaseTests
+ {
+ private readonly Mock _handlerMock;
+ private readonly VstsWebHookHandlerBase _handler;
+
+ private WebHookHandlerContext _context;
+
+ public VstsWebHookHandlerBaseTests()
+ {
+ _handlerMock = new Mock { CallBase = true };
+ _handler = _handlerMock.Object;
+ }
+
+ [Fact]
+ public void VstsWebHookHandlerBase_SetsReceiverName()
+ {
+ Assert.Equal(VstsWebHookReceiver.ReceiverName, _handler.Receiver);
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_Dispatches_BuildCompleted()
+ {
+ // Arrange
+ _context = GetContext("Microsoft.AspNet.WebHooks.Messages.build.complete.json", "build.complete");
+
+ // Act
+ await _handler.ExecuteAsync(VstsWebHookReceiver.ReceiverName, _context);
+
+ // Assert
+ _handlerMock.Verify(h => h.ExecuteAsync(_context, It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_Dispatches_CodeCheckedIn()
+ {
+ // Arrange
+ _context = GetContext("Microsoft.AspNet.WebHooks.Messages.tfvc.checkin.json", "tfvc.checkin");
+
+ // Act
+ await _handler.ExecuteAsync(VstsWebHookReceiver.ReceiverName, _context);
+
+ // Assert
+ _handlerMock.Verify(h => h.ExecuteAsync(_context, It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_Dispatches_TeamRoomMessagePosted()
+ {
+ // Arrange
+ _context = GetContext("Microsoft.AspNet.WebHooks.Messages.message.posted.json", "message.posted");
+
+ // Act
+ await _handler.ExecuteAsync(VstsWebHookReceiver.ReceiverName, _context);
+
+ // Assert
+ _handlerMock.Verify(h => h.ExecuteAsync(_context, It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_Dispatches_WorkItemCommentedOn()
+ {
+ // Arrange
+ _context = GetContext("Microsoft.AspNet.WebHooks.Messages.workitem.commented.json", "workitem.commented");
+
+ // Act
+ await _handler.ExecuteAsync(VstsWebHookReceiver.ReceiverName, _context);
+
+ // Assert
+ _handlerMock.Verify(h => h.ExecuteAsync(_context, It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_Dispatches_WorkItemCreated()
+ {
+ // Arrange
+ _context = GetContext("Microsoft.AspNet.WebHooks.Messages.workitem.created.json", "workitem.created");
+
+ // Act
+ await _handler.ExecuteAsync(VstsWebHookReceiver.ReceiverName, _context);
+
+ // Assert
+ _handlerMock.Verify(h => h.ExecuteAsync(_context, It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_Dispatches_WorkItemDeleted()
+ {
+ // Arrange
+ _context = GetContext("Microsoft.AspNet.WebHooks.Messages.workitem.deleted.json", "workitem.deleted");
+
+ // Act
+ await _handler.ExecuteAsync(VstsWebHookReceiver.ReceiverName, _context);
+
+ // Assert
+ _handlerMock.Verify(h => h.ExecuteAsync(_context, It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_Dispatches_WorkItemRestored()
+ {
+ // Arrange
+ _context = GetContext("Microsoft.AspNet.WebHooks.Messages.workitem.restored.json", "workitem.restored");
+
+ // Act
+ await _handler.ExecuteAsync(VstsWebHookReceiver.ReceiverName, _context);
+
+ // Assert
+ _handlerMock.Verify(h => h.ExecuteAsync(_context, It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_Dispatches_WorkItemUpdated()
+ {
+ // Arrange
+ _context = GetContext("Microsoft.AspNet.WebHooks.Messages.workitem.updated.json", "workitem.updated");
+
+ // Act
+ await _handler.ExecuteAsync(VstsWebHookReceiver.ReceiverName, _context);
+
+ // Assert
+ _handlerMock.Verify(h => h.ExecuteAsync(_context, It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_Handles_UnknownEventType()
+ {
+ // Arrange
+ _context = GetContext("Microsoft.AspNet.WebHooks.Messages.bad.notMappedEventType.json", "unknown");
+
+ // Act
+ await _handler.ExecuteAsync(VstsWebHookReceiver.ReceiverName, _context);
+
+ // Assert
+ _handlerMock.Verify(h => h.ExecuteAsync(_context, It.IsAny()), Times.Once());
+ }
+
+ private static WebHookHandlerContext GetContext(string payload, string action)
+ {
+ JObject data = EmbeddedResource.ReadAsJObject(payload);
+ HttpConfiguration httpConfig = new HttpConfiguration();
+ HttpRequestContext requestContext = new HttpRequestContext { Configuration = httpConfig };
+ HttpRequestMessage request = new HttpRequestMessage();
+ request.SetRequestContext(requestContext);
+ IEnumerable actions = new[] { action };
+ return new WebHookHandlerContext(actions)
+ {
+ Data = data,
+ Request = request,
+ RequestContext = requestContext
+ };
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/bad.noEventType.json b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/bad.noEventType.json
new file mode 100644
index 0000000..a7d792e
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/bad.noEventType.json
@@ -0,0 +1,3 @@
+{
+ "subscriptionId": "00000000-0000-0000-0000-000000000000"
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/bad.notMappedEventType.json b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/bad.notMappedEventType.json
new file mode 100644
index 0000000..99b7d7d
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/bad.notMappedEventType.json
@@ -0,0 +1,5 @@
+{
+ "subscriptionId": "00000000-0000-0000-0000-000000000000",
+ "notificationId": 5,
+ "eventType": "unknownType"
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/build.complete.json b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/build.complete.json
new file mode 100644
index 0000000..8fc5f97
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/build.complete.json
@@ -0,0 +1,83 @@
+{
+ "subscriptionId": "00000000-0000-0000-0000-000000000000",
+ "notificationId": 1,
+ "id": "4a5d99d6-1c75-4e53-91b9-ee80057d4ce3",
+ "eventType": "build.complete",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Build ConsumerAddressModule_20150407.2 succeeded",
+ "html": "Build ConsumerAddressModule_20150407.2 succeeded",
+ "markdown": "Build [ConsumerAddressModule_20150407.2](https://good-company.some.ssl.host/web/build.aspx?id=5023c10b-bef3-41c3-bf53-686c4e34ee9e&builduri=vstfs%3a%2f%2f%2fBuild%2fBuild%2f3) succeeded"
+ },
+ "detailedMessage": {
+ "text": "Build ConsumerAddressModule_20150407.2 succeeded",
+ "html": "Build ConsumerAddressModule_20150407.2 succeeded",
+ "markdown": "Build [ConsumerAddressModule_20150407.2](https://good-company.some.ssl.host/web/build.aspx?id=5023c10b-bef3-41c3-bf53-686c4e34ee9e&builduri=vstfs%3a%2f%2f%2fBuild%2fBuild%2f3) succeeded"
+ },
+ "resource": {
+ "uri": "vstfs:///Build/Build/2",
+ "id": 2,
+ "buildNumber": "ConsumerAddressModule_20150407.1",
+ "url": "https://good-company.some.ssl.host/DefaultCollection/71777fbc-1cf2-4bd1-9540-128c1c71f766/_apis/build/Builds/2",
+ "startTime": "2015-04-07T18:04:06.83Z",
+ "finishTime": "2015-04-07T18:06:10.69Z",
+ "reason": "manual",
+ "status": "succeeded",
+ "dropLocation": "#/3/drop",
+ "drop": {
+ "location": "#/3/drop",
+ "type": "container",
+ "url": "https://good-company.some.ssl.host/DefaultCollection/_apis/resources/Containers/3/drop",
+ "downloadUrl": "https://good-company.some.ssl.host/DefaultCollection/_apis/resources/Containers/3/drop?api-version=1.0&$format=zip&downloadFileName=ConsumerAddressModule_20150407.1_drop"
+ },
+ "log": {
+ "type": "container",
+ "url": "https://good-company.some.ssl.host/DefaultCollection/_apis/resources/Containers/3/logs",
+ "downloadUrl": "https://good-company.some.ssl.host/_apis/resources/Containers/3/logs?api-version=1.0&$format=zip&downloadFileName=ConsumerAddressModule_20150407.1_logs"
+ },
+ "sourceGetVersion": "LG:refs/heads/master:600c52d2d5b655caa111abfd863e5a9bd304bb0e",
+ "lastChangedBy": {
+ "id": "d6245f20-2af8-44f4-9451-8107cb2767db",
+ "displayName": "John Smith",
+ "uniqueName": "fabrikamfiber16@hotmail.com",
+ "url": "https://good-company.some.ssl.host/_apis/Identities/d6245f20-2af8-44f4-9451-8107cb2767db",
+ "imageUrl": "https://good-company.some.ssl.host/DefaultCollection/_api/_common/identityImage?id=d6245f20-2af8-44f4-9451-8107cb2767db"
+ },
+ "retainIndefinitely": false,
+ "hasDiagnostics": true,
+ "definition": {
+ "batchSize": 1,
+ "triggerType": "none",
+ "definitionType": "xaml",
+ "id": 2,
+ "name": "ConsumerAddressModule",
+ "url": "https://good-company.some.ssl.host/DefaultCollection/71777fbc-1cf2-4bd1-9540-128c1c71f766/_apis/build/Definitions/2"
+ },
+ "queue": {
+ "queueType": "buildController",
+ "id": 4,
+ "name": "Hosted Build Controller",
+ "url": "https://good-company.some.ssl.host/DefaultCollection/_apis/build/Queues/4"
+ },
+ "requests": [
+ {
+ "id": 1,
+ "url": "https://good-company.some.ssl.host/DefaultCollection/71777fbc-1cf2-4bd1-9540-128c1c71f766/_apis/build/Requests/1",
+ "requestedFor": {
+ "id": "d6245f20-2af8-44f4-9451-8107cb2767db",
+ "displayName": "John Smith",
+ "uniqueName": "fabrikamfiber16@hotmail.com",
+ "url": "https://good-company.some.ssl.host/_apis/Identities/d6245f20-2af8-44f4-9451-8107cb2767db",
+ "imageUrl": "https://good-company.some.ssl.host/DefaultCollection/_api/_common/identityImage?id=d6245f20-2af8-44f4-9451-8107cb2767db"
+ }
+ }
+ ]
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": { "id": "c12d0eb8-e382-443b-9f9c-c52cba5014c2" },
+ "account": { "id": "f844ec47-a9db-4511-8281-8b63f4eaf94e" },
+ "project": { "id": "be9b3917-87e6-42a4-a549-2bc06a7a878f" }
+ },
+ "createdDate": "2016-05-02T19:00:39.5893296Z"
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/message.posted.json b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/message.posted.json
new file mode 100644
index 0000000..117b8b5
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/message.posted.json
@@ -0,0 +1,36 @@
+{
+ "subscriptionId": "00000000-0000-0000-0000-000000000000",
+ "notificationId": 3,
+ "id": "daae438c-296b-4512-b08e-571910874e9b",
+ "eventType": "message.posted",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Jamal Hartnett posted a message to Northward-Fiber-Git Team Room\r\nHello",
+ "html": "Jamal Hartnett posted a message to Northward-Fiber-Git Team Room\r\nHello",
+ "markdown": "Jamal Hartnett posted a message to Northward-Fiber-Git Team Room\r\nHello"
+ },
+ "detailedMessage": {
+ "text": "Jamal Hartnett posted a message to Northward-Fiber-Git Team Room\r\nHello",
+ "html": "Jamal Hartnett posted a message to Northward-Fiber-Git Team RoomHello
",
+ "markdown": "Jamal Hartnett posted a message to Northward-Fiber-Git Team Room\r\nHello"
+ },
+ "resource": {
+ "id": 0,
+ "content": "Hello",
+ "messageType": "normal",
+ "postedTime": "2014-05-02T19:17:13.3309587Z",
+ "postedRoomId": 1,
+ "postedBy": {
+ "id": "00067FFED5C7AF52@Live.com",
+ "displayName": "Jamal Hartnett",
+ "uniqueName": "Windows Live ID\\fabrikamfiber4@hotmail.com"
+ }
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": { "id": "c12d0eb8-e382-443b-9f9c-c52cba5014c2" },
+ "account": { "id": "f844ec47-a9db-4511-8281-8b63f4eaf94e" },
+ "project": { "id": "be9b3917-87e6-42a4-a549-2bc06a7a878f" }
+ },
+ "createdDate": "2016-05-02T19:13:40.8417653Z"
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/tfvc.checkin.json b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/tfvc.checkin.json
new file mode 100644
index 0000000..e990b4b
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/tfvc.checkin.json
@@ -0,0 +1,40 @@
+{
+ "subscriptionId": "00000000-0000-0000-0000-000000000000",
+ "notificationId": 2,
+ "id": "f9b4c23e-88dd-4516-b04d-849787304e32",
+ "eventType": "tfvc.checkin",
+ "publisherId": "tfs",
+ "message": {
+ "text": "John Smith checked in changeset 18: Dropping in new Java sample",
+ "html": "John Smith checked in changeset 18 : Dropping in new Java sample",
+ "markdown": "John Smith checked in changeset [18](https://good-company.some.ssl.host/web/cs.aspx?id=d81542e4-cdfa-4333-b082-1ae2d6c3ad16&cs=18): Dropping in new Java sample"
+ },
+ "detailedMessage": {
+ "text": "John Smith checked in changeset 18: Dropping in new Java sample",
+ "html": "John Smith checked in changeset 18 : Dropping in new Java sample",
+ "markdown": "John Smith checked in changeset [18](https://good-company.some.ssl.host/web/cs.aspx?id=d81542e4-cdfa-4333-b082-1ae2d6c3ad16&cs=18): Dropping in new Java sample"
+ },
+ "resource": {
+ "changesetId": 18,
+ "url": "https://good-company.some.ssl.host/DefaultCollection/_apis/tfvc/changesets/18",
+ "author": {
+ "id": "d6245f20-2af8-44f4-9451-8107cb2767db",
+ "displayName": "John Smith",
+ "uniqueName": "fabrikamfiber16@hotmail.com"
+ },
+ "checkedInBy": {
+ "id": "d6245f20-2af8-44f4-9451-8107cb2767db",
+ "displayName": "John Smith",
+ "uniqueName": "fabrikamfiber16@hotmail.com"
+ },
+ "createdDate": "2014-05-12T22:41:16Z",
+ "comment": "Dropping in new Java sample"
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": { "id": "c12d0eb8-e382-443b-9f9c-c52cba5014c2" },
+ "account": { "id": "f844ec47-a9db-4511-8281-8b63f4eaf94e" },
+ "project": { "id": "be9b3917-87e6-42a4-a549-2bc06a7a878f" }
+ },
+ "createdDate": "2016-05-02T19:01:11.7056821Z"
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/workitem.commented.json b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/workitem.commented.json
new file mode 100644
index 0000000..354d3fc
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/workitem.commented.json
@@ -0,0 +1,52 @@
+{
+ "subscriptionId": "00000000-0000-0000-0000-000000000000",
+ "notificationId": 4,
+ "id": "fb2617ed-60df-4518-81fa-749faa6c5cd6",
+ "eventType": "workitem.commented",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Bug #5 (Some great new idea!) commented on by Jamal Hartnett.\r\n(http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5)",
+ "html": "Bug #5 (Some great new idea!) commented on by Jamal Hartnett.",
+ "markdown": "[Bug #5](http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5) (Some great new idea!) commented on by Jamal Hartnett."
+ },
+ "detailedMessage": {
+ "text": "Bug #5 (Some great new idea!) commented on by Jamal Hartnett.\r\n(http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5)\r\nThis is a great new idea",
+ "html": "Bug #5 (Some great new idea!) commented on by Jamal Hartnett. This is a great new idea",
+ "markdown": "[Bug #5](http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5) (Some great new idea!) commented on by Jamal Hartnett.\r\nThis is a great new idea"
+ },
+ "resource": {
+ "id": 5,
+ "rev": 4,
+ "fields": {
+ "System.AreaPath": "GoodCompanyCloud",
+ "System.TeamProject": "GoodCompanyCloud",
+ "System.IterationPath": "GoodCompanyCloud\\Release 1\\Sprint 1",
+ "System.WorkItemType": "Bug",
+ "System.State": "New",
+ "System.Reason": "New defect reported",
+ "System.CreatedDate": "2014-07-15T17:42:44.663Z",
+ "System.CreatedBy": "Jamal Hartnett",
+ "System.ChangedDate": "2014-07-15T17:42:44.663Z",
+ "System.ChangedBy": "Jamal Hartnett",
+ "System.Title": "Some great new idea!",
+ "Microsoft.VSTS.Common.Severity": "3 - Medium",
+ "WEF_EB329F44FE5F4A94ACB1DA153FDF38BA_Kanban.Column": "New",
+ "System.History": "This is a great new idea"
+ },
+ "_links": {
+ "self": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5" },
+ "workItemUpdates": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/updates" },
+ "workItemRevisions": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/revisions" },
+ "workItemType": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/ea830882-2a3c-4095-a53f-972f9a376f6e/workItemTypes/Bug" },
+ "fields": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/fields" }
+ },
+ "url": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5"
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": { "id": "c12d0eb8-e382-443b-9f9c-c52cba5014c2" },
+ "account": { "id": "f844ec47-a9db-4511-8281-8b63f4eaf94e" },
+ "project": { "id": "be9b3917-87e6-42a4-a549-2bc06a7a878f" }
+ },
+ "createdDate": "2016-05-02T19:15:37.4638247Z"
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/workitem.created.json b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/workitem.created.json
new file mode 100644
index 0000000..293083b
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/workitem.created.json
@@ -0,0 +1,51 @@
+{
+ "subscriptionId": "00000000-0000-0000-0000-000000000000",
+ "notificationId": 5,
+ "id": "d2d46fb1-dba5-403c-9373-427583f19e8c",
+ "eventType": "workitem.created",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Bug #5 (Some great new idea!) created by Jamal Hartnett.\r\n(http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5)",
+ "html": "Bug #5 (Some great new idea!) created by Jamal Hartnett.",
+ "markdown": "[Bug #5](http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5) (Some great new idea!) created by Jamal Hartnett."
+ },
+ "detailedMessage": {
+ "text": "Bug #5 (Some great new idea!) created by Jamal Hartnett.\r\n(http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5)\r\n\r\n- State: New\r\n- Assigned to: \r\n- Comment: \r\n- Severity: 3 - Medium\r\n",
+ "html": "Bug #5 (Some great new idea!) created by Jamal Hartnett.\r\nState: New \r\nAssigned to: \r\nComment: \r\nSeverity: 3 - Medium ",
+ "markdown": "[Bug #5](http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5) (Some great new idea!) created by Jamal Hartnett.\r\n\r\n* State: New\r\n* Assigned to: \r\n* Comment: \r\n* Severity: 3 - Medium\r\n"
+ },
+ "resource": {
+ "id": 5,
+ "rev": 1,
+ "fields": {
+ "System.AreaPath": "GoodCompanyCloud",
+ "System.TeamProject": "GoodCompanyCloud",
+ "System.IterationPath": "GoodCompanyCloud\\Release 1\\Sprint 1",
+ "System.WorkItemType": "Bug",
+ "System.State": "New",
+ "System.Reason": "New defect reported",
+ "System.CreatedDate": "2014-07-15T17:42:44.663Z",
+ "System.CreatedBy": "Jamal Hartnett",
+ "System.ChangedDate": "2014-07-15T17:42:44.663Z",
+ "System.ChangedBy": "Jamal Hartnett",
+ "System.Title": "Some great new idea!",
+ "Microsoft.VSTS.Common.Severity": "3 - Medium",
+ "WEF_EB329F44FE5F4A94ACB1DA153FDF38BA_Kanban.Column": "New"
+ },
+ "_links": {
+ "self": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5" },
+ "workItemUpdates": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/updates" },
+ "workItemRevisions": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/revisions" },
+ "workItemType": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/ea830882-2a3c-4095-a53f-972f9a376f6e/workItemTypes/Bug" },
+ "fields": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/fields" }
+ },
+ "url": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5"
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": { "id": "c12d0eb8-e382-443b-9f9c-c52cba5014c2" },
+ "account": { "id": "f844ec47-a9db-4511-8281-8b63f4eaf94e" },
+ "project": { "id": "be9b3917-87e6-42a4-a549-2bc06a7a878f" }
+ },
+ "createdDate": "2016-05-02T19:16:25.6251162Z"
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/workitem.deleted.json b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/workitem.deleted.json
new file mode 100644
index 0000000..120b097
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/workitem.deleted.json
@@ -0,0 +1,49 @@
+{
+ "subscriptionId": "00000000-0000-0000-0000-000000000000",
+ "notificationId": 6,
+ "id": "72da0ade-0709-40ee-beb7-104287bf7e84",
+ "eventType": "workitem.deleted",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Bug #5 (Some great new idea!) deleted by Jamal Hartnett.",
+ "html": "Bug #5 (Some great new idea!) deleted by Jamal Hartnett.",
+ "markdown": "[Bug #5] (Some great new idea!) deleted by Jamal Hartnett."
+ },
+ "detailedMessage": {
+ "text": "Bug #5 (Some great new idea!) deleted by Jamal Hartnett.\r\n\r\n- State: New\r\n",
+ "html": "Bug #5 (Some great new idea!) deleted by Jamal Hartnett.",
+ "markdown": "[Bug #5] (Some great new idea!) deleted by Jamal Hartnett.\r\n\r\n* State: New\r\n"
+ },
+ "resource": {
+ "id": 5,
+ "rev": 1,
+ "fields": {
+ "System.AreaPath": "GoodCompanyCloud",
+ "System.TeamProject": "GoodCompanyCloud",
+ "System.IterationPath": "GoodCompanyCloud\\Release 1\\Sprint 1",
+ "System.WorkItemType": "Bug",
+ "System.State": "New",
+ "System.Reason": "New defect reported",
+ "System.CreatedDate": "2014-07-15T17:42:44.663Z",
+ "System.CreatedBy": "Jamal Hartnett",
+ "System.ChangedDate": "2014-07-15T17:42:44.663Z",
+ "System.ChangedBy": "Jamal Hartnett",
+ "System.Title": "Some great new idea!",
+ "Microsoft.VSTS.Common.Severity": "3 - Medium",
+ "WEF_EB329F44FE5F4A94ACB1DA153FDF38BA_Kanban.Column": "New"
+ },
+ "_links": {
+ "self": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/recyclebin/5" },
+ "workItemType": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/ea830882-2a3c-4095-a53f-972f9a376f6e/workItemTypes/Bug" },
+ "fields": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/fields" }
+ },
+ "url": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/recyclebin/5"
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": { "id": "c12d0eb8-e382-443b-9f9c-c52cba5014c2" },
+ "account": { "id": "f844ec47-a9db-4511-8281-8b63f4eaf94e" },
+ "project": { "id": "be9b3917-87e6-42a4-a549-2bc06a7a878f" }
+ },
+ "createdDate": "2016-05-02T19:17:28.3644564Z"
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/workitem.restored.json b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/workitem.restored.json
new file mode 100644
index 0000000..de83dc1
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/workitem.restored.json
@@ -0,0 +1,53 @@
+{
+ "subscriptionId": "00000000-0000-0000-0000-000000000000",
+ "notificationId": 7,
+ "id": "1ca023d6-6cff-49dd-b3d1-302b69311810",
+ "eventType": "workitem.restored",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Bug #5 (Some great new idea!) restored by Jamal Hartnett.\r\n(http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5)",
+ "html": "Bug #5 (Some great new idea!) restored by Jamal Hartnett.",
+ "markdown": "[Bug #5](http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5) (Some great new idea!) restored by Jamal Hartnett."
+ },
+ "detailedMessage": {
+ "text": "Bug #5 (Some great new idea!) restored by Jamal Hartnett.\r\n(http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5)\r\n\r\n- State: New\r\n- Severity: 3 - Medium\r\n",
+ "html": "Bug #5 (Some great new idea!) restored by Jamal Hartnett.\r\nState: New Severity: 3 - Medium ",
+ "markdown": "[Bug #5](http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5) (Some great new idea!) restored by Jamal Hartnett.\r\n\r\n* State: New\r\n* Severity: 3 - Medium\r\n"
+ },
+ "resource": {
+ "id": 5,
+ "rev": 1,
+ "fields": {
+ "System.AreaPath": "GoodCompanyCloud",
+ "System.TeamProject": "GoodCompanyCloud",
+ "System.IterationPath": "GoodCompanyCloud\\Release 1\\Sprint 1",
+ "System.WorkItemType": "Bug",
+ "System.State": "New",
+ "System.Reason": "New defect reported",
+ "System.CreatedDate": "2014-07-15T17:42:44.663Z",
+ "System.CreatedBy": "Jamal Hartnett",
+ "System.ChangedDate": "2014-07-15T17:42:44.663Z",
+ "System.ChangedBy": "Jamal Hartnett",
+ "System.Title": "Some great new idea!",
+ "Microsoft.VSTS.Common.Severity": "3 - Medium",
+ "WEF_EB329F44FE5F4A94ACB1DA153FDF38BA_Kanban.Column": "New"
+ },
+ "_links": {
+ "self": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5" },
+ "workItemUpdates": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/updates" },
+ "workItemRevisions": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/revisions" },
+ "workItemType": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/ea830882-2a3c-4095-a53f-972f9a376f6e/workItemTypes/Bug" },
+ "fields": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/fields" },
+ "html": { "href": "https://good-company.some.ssl.host/web/wi.aspx?id=d81542e4-cdfa-4333-b082-1ae2d6c3ad16&id=5" },
+ "workItemHistory": { "href": "https://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/history" }
+ },
+ "url": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5"
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": { "id": "c12d0eb8-e382-443b-9f9c-c52cba5014c2" },
+ "account": { "id": "f844ec47-a9db-4511-8281-8b63f4eaf94e" },
+ "project": { "id": "be9b3917-87e6-42a4-a549-2bc06a7a878f" }
+ },
+ "createdDate": "2016-05-02T19:18:15.5707279Z"
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/workitem.updated.json b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/workitem.updated.json
new file mode 100644
index 0000000..ec32878
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Messages/workitem.updated.json
@@ -0,0 +1,92 @@
+{
+ "subscriptionId": "00000000-0000-0000-0000-000000000000",
+ "notificationId": 8,
+ "id": "27646e0e-b520-4d2b-9411-bba7524947cd",
+ "eventType": "workitem.updated",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Bug #5 (Some great new idea!) updated by Jamal Hartnett.\r\n(http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5)",
+ "html": "Bug #5 (Some great new idea!) updated by Jamal Hartnett.",
+ "markdown": "[Bug #5](http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5) (Some great new idea!) updated by Jamal Hartnett."
+ },
+ "detailedMessage": {
+ "text": "Bug #5 (Some great new idea!) updated by Jamal Hartnett.\r\n(http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5)\r\n\r\n- New State: Approved\r\n",
+ "html": "Bug #5 (Some great new idea!) updated by Jamal Hartnett.",
+ "markdown": "[Bug #5](http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5) (Some great new idea!) updated by Jamal Hartnett.\r\n\r\n* New State: Approved\r\n"
+ },
+ "resource": {
+ "id": 2,
+ "workItemId": 0,
+ "rev": 2,
+ "revisedBy": null,
+ "revisedDate": "0001-01-01T00:00:00",
+ "fields": {
+ "System.Rev": {
+ "oldValue": "1",
+ "newValue": "2"
+ },
+ "System.AuthorizedDate": {
+ "oldValue": "2014-07-15T16:48:44.663Z",
+ "newValue": "2014-07-15T17:42:44.663Z"
+ },
+ "System.RevisedDate": {
+ "oldValue": "2014-07-15T17:42:44.663Z",
+ "newValue": "9999-01-01T00:00:00Z"
+ },
+ "System.State": {
+ "oldValue": "New",
+ "newValue": "Approved"
+ },
+ "System.Reason": {
+ "oldValue": "New defect reported",
+ "newValue": "Approved by the Product Owner"
+ },
+ "System.AssignedTo": { "newValue": "Jamal Hartnet" },
+ "System.ChangedDate": {
+ "oldValue": "2014-07-15T16:48:44.663Z",
+ "newValue": "2014-07-15T17:42:44.663Z"
+ },
+ "System.Watermark": {
+ "oldValue": "2",
+ "newValue": "5"
+ },
+ "Microsoft.VSTS.Common.Severity": {
+ "oldValue": "3 - Medium",
+ "newValue": "2 - High"
+ }
+ },
+ "_links": {
+ "self": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/updates/2" },
+ "parent": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5" },
+ "workItemUpdates": { "href": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/updates" }
+ },
+ "url": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/updates/2",
+ "revision": {
+ "id": 5,
+ "rev": 2,
+ "fields": {
+ "System.AreaPath": "GoodCompanyCloud",
+ "System.TeamProject": "GoodCompanyCloud",
+ "System.IterationPath": "GoodCompanyCloud\\Release 1\\Sprint 1",
+ "System.WorkItemType": "Bug",
+ "System.State": "New",
+ "System.Reason": "New defect reported",
+ "System.CreatedDate": "2014-07-15T16:48:44.663Z",
+ "System.CreatedBy": "Jamal Hartnett",
+ "System.ChangedDate": "2014-07-15T16:48:44.663Z",
+ "System.ChangedBy": "Jamal Hartnett",
+ "System.Title": "Some great new idea!",
+ "Microsoft.VSTS.Common.Severity": "3 - Medium",
+ "WEF_EB329F44FE5F4A94ACB1DA153FDF38BA_Kanban.Column": "New"
+ },
+ "url": "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/revisions/2"
+ }
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": { "id": "c12d0eb8-e382-443b-9f9c-c52cba5014c2" },
+ "account": { "id": "f844ec47-a9db-4511-8281-8b63f4eaf94e" },
+ "project": { "id": "be9b3917-87e6-42a4-a549-2bc06a7a878f" }
+ },
+ "createdDate": "2016-05-02T19:19:12.8836446Z"
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test.csproj b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test.csproj
new file mode 100644
index 0000000..d89c726
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test.csproj
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+ {B27CC8B5-BF38-434B-B5CE-42ACEC40624C}
+ Library
+ Properties
+ Microsoft.AspNet.WebHooks
+ Microsoft.AspNet.WebHooks.Receivers.VSTS.Test
+ ..\..\bin\$(Configuration)\Test\
+ $(CodeAnalysis)
+ /assemblyCompareMode:StrongNameIgnoringVersion
+ ..\..\FxCopTest.ruleset
+ $(DefineConstants);ASPNETWEBHOOKS
+
+
+
+
+
+ ..\..\packages\Moq.4.2.1502.0911\lib\net40\Moq.dll
+ True
+
+
+ ..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll
+ True
+
+
+
+
+
+ ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll
+ True
+
+
+ ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll
+ True
+
+
+
+
+
+
+
+ ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll
+ True
+
+
+ ..\..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll
+ True
+
+
+ ..\..\packages\xunit.extensibility.core.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll
+ True
+
+
+
+
+ Common\EmbeddedResource.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Designer
+
+
+
+
+ {f7dd0935-6320-4efc-9464-d5a2d2b8c2f7}
+ Microsoft.AspNet.WebHooks.Common
+
+
+ {6b501d55-2aa3-4757-a185-1277bb881de8}
+ Microsoft.AspNet.WebHooks.Receivers.VSTS
+
+
+ {8ced31fb-32f2-4ffb-9997-452fb9728577}
+ Microsoft.AspNet.WebHooks.Receivers
+
+
+ {cb965ed1-1e07-4374-a829-c636db09f567}
+ Microsoft.AspNet.WebHooks.Receivers.Test
+
+
+ {608b1d09-e4de-4dba-8dbf-7758003988f0}
+ Microsoft.TestUtilities
+
+
+
+
+ CustomDictionary.xml
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/BuildCompletedPayloadTests.cs b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/BuildCompletedPayloadTests.cs
new file mode 100644
index 0000000..da5ee37
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/BuildCompletedPayloadTests.cs
@@ -0,0 +1,124 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNet.WebHooks.Payloads;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Microsoft.AspNet.WebHooks
+{
+ public class BuildCompletedPayloadTests
+ {
+ [Fact]
+ public void BuildCompletedPayload_Roundtrips()
+ {
+ // Arrange
+ JObject data = EmbeddedResource.ReadAsJObject("Microsoft.AspNet.WebHooks.Messages.build.complete.json");
+
+ var expected = new BuildCompletedPayload
+ {
+ SubscriptionId = "00000000-0000-0000-0000-000000000000",
+ NotificationId = 1,
+ Id = "4a5d99d6-1c75-4e53-91b9-ee80057d4ce3",
+ EventType = "build.complete",
+ PublisherId = "tfs",
+ Message = new PayloadMessage
+ {
+ Text = "Build ConsumerAddressModule_20150407.2 succeeded",
+ Html = "Build ConsumerAddressModule_20150407.2 succeeded",
+ Markdown = "Build [ConsumerAddressModule_20150407.2](https://good-company.some.ssl.host/web/build.aspx?id=5023c10b-bef3-41c3-bf53-686c4e34ee9e&builduri=vstfs%3a%2f%2f%2fBuild%2fBuild%2f3) succeeded"
+ },
+ DetailedMessage = new PayloadMessage
+ {
+ Text = "Build ConsumerAddressModule_20150407.2 succeeded",
+ Html = "Build ConsumerAddressModule_20150407.2 succeeded",
+ Markdown = "Build [ConsumerAddressModule_20150407.2](https://good-company.some.ssl.host/web/build.aspx?id=5023c10b-bef3-41c3-bf53-686c4e34ee9e&builduri=vstfs%3a%2f%2f%2fBuild%2fBuild%2f3) succeeded"
+ },
+
+ Resource = new BuildCompletedResource
+ {
+ Uri = new Uri("vstfs:///Build/Build/2"),
+ Id = 2,
+ BuildNumber = "ConsumerAddressModule_20150407.1",
+ Url = new Uri("https://good-company.some.ssl.host/DefaultCollection/71777fbc-1cf2-4bd1-9540-128c1c71f766/_apis/build/Builds/2"),
+ StartTime = "2015-04-07T18:04:06.83Z".ToDateTime(),
+ FinishTime = "2015-04-07T18:06:10.69Z".ToDateTime(),
+ Reason = "manual",
+ Status = "succeeded",
+ DropLocation = "#/3/drop",
+ Drop = new BuildCompletedDrop
+ {
+ Location = "#/3/drop",
+ DropType = "container",
+ Url = new Uri("https://good-company.some.ssl.host/DefaultCollection/_apis/resources/Containers/3/drop"),
+ DownloadUrl = new Uri("https://good-company.some.ssl.host/DefaultCollection/_apis/resources/Containers/3/drop?api-version=1.0&$format=zip&downloadFileName=ConsumerAddressModule_20150407.1_drop")
+ },
+ Log = new BuildCompletedLog
+ {
+ LogType = "container",
+ Url = new Uri("https://good-company.some.ssl.host/DefaultCollection/_apis/resources/Containers/3/logs"),
+ DownloadUrl = new Uri("https://good-company.some.ssl.host/_apis/resources/Containers/3/logs?api-version=1.0&$format=zip&downloadFileName=ConsumerAddressModule_20150407.1_logs")
+ },
+ SourceGetVersion = "LG:refs/heads/master:600c52d2d5b655caa111abfd863e5a9bd304bb0e",
+ LastChangedBy = new ResourceUser
+ {
+ Id = "d6245f20-2af8-44f4-9451-8107cb2767db",
+ DisplayName = "John Smith",
+ UniqueName = "fabrikamfiber16@hotmail.com",
+ Url = new Uri("https://good-company.some.ssl.host/_apis/Identities/d6245f20-2af8-44f4-9451-8107cb2767db"),
+ ImageUrl = new Uri("https://good-company.some.ssl.host/DefaultCollection/_api/_common/identityImage?id=d6245f20-2af8-44f4-9451-8107cb2767db")
+ },
+ RetainIndefinitely = false,
+ HasDiagnostics = true,
+ Definition = new BuildCompletedDefinition
+ {
+ BatchSize = 1,
+ TriggerType = "none",
+ DefinitionType = "xaml",
+ Id = 2,
+ Name = "ConsumerAddressModule",
+ Url = new Uri("https://good-company.some.ssl.host/DefaultCollection/71777fbc-1cf2-4bd1-9540-128c1c71f766/_apis/build/Definitions/2")
+ },
+ Queue = new BuildCompletedQueueDefinition
+ {
+ QueueType = "buildController",
+ Id = 4,
+ Name = "Hosted Build Controller",
+ Url = new Uri("https://good-company.some.ssl.host/DefaultCollection/_apis/build/Queues/4")
+ }
+ },
+ ResourceVersion = "1.0",
+ ResourceContainers = new PayloadResourceContainers
+ {
+ Collection = new PayloadResourceContainer { Id = "c12d0eb8-e382-443b-9f9c-c52cba5014c2" },
+ Account = new PayloadResourceContainer { Id = "f844ec47-a9db-4511-8281-8b63f4eaf94e" },
+ Project = new PayloadResourceContainer { Id = "be9b3917-87e6-42a4-a549-2bc06a7a878f" }
+ },
+ CreatedDate = "2016-05-02T19:00:39.5893296Z".ToDateTime()
+ };
+ expected.Resource.Requests.Add(new BuildCompletedRequest
+ {
+ Id = 1,
+ Url = new Uri("https://good-company.some.ssl.host/DefaultCollection/71777fbc-1cf2-4bd1-9540-128c1c71f766/_apis/build/Requests/1"),
+ RequestedFor = new ResourceUser
+ {
+ Id = "d6245f20-2af8-44f4-9451-8107cb2767db",
+ DisplayName = "John Smith",
+ UniqueName = "fabrikamfiber16@hotmail.com",
+ Url = new Uri("https://good-company.some.ssl.host/_apis/Identities/d6245f20-2af8-44f4-9451-8107cb2767db"),
+ ImageUrl = new Uri("https://good-company.some.ssl.host/DefaultCollection/_api/_common/identityImage?id=d6245f20-2af8-44f4-9451-8107cb2767db")
+ }
+ });
+
+ // Act
+ var actual = data.ToObject();
+
+ // Assert
+ string expectedJson = JsonConvert.SerializeObject(expected);
+ string actualJson = JsonConvert.SerializeObject(actual);
+ Assert.Equal(expectedJson, actualJson);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/CodeCheckedInPayloadTests.cs b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/CodeCheckedInPayloadTests.cs
new file mode 100644
index 0000000..6350a96
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/CodeCheckedInPayloadTests.cs
@@ -0,0 +1,76 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNet.WebHooks.Payloads;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Microsoft.AspNet.WebHooks
+{
+ public class CodeCheckedInPayloadTests
+ {
+ [Fact]
+ public void CodeCheckedInPayload_Roundtrips()
+ {
+ // Arrange
+ JObject data = EmbeddedResource.ReadAsJObject("Microsoft.AspNet.WebHooks.Messages.tfvc.checkin.json");
+ var expected = new CodeCheckedInPayload
+ {
+ SubscriptionId = "00000000-0000-0000-0000-000000000000",
+ NotificationId = 2,
+ Id = "f9b4c23e-88dd-4516-b04d-849787304e32",
+ EventType = "tfvc.checkin",
+ PublisherId = "tfs",
+ Message = new PayloadMessage
+ {
+ Text = "John Smith checked in changeset 18: Dropping in new Java sample",
+ Html = "John Smith checked in changeset 18 : Dropping in new Java sample",
+ Markdown = "John Smith checked in changeset [18](https://good-company.some.ssl.host/web/cs.aspx?id=d81542e4-cdfa-4333-b082-1ae2d6c3ad16&cs=18): Dropping in new Java sample"
+ },
+ DetailedMessage = new PayloadMessage
+ {
+ Text = "John Smith checked in changeset 18: Dropping in new Java sample",
+ Html = "John Smith checked in changeset 18 : Dropping in new Java sample",
+ Markdown = "John Smith checked in changeset [18](https://good-company.some.ssl.host/web/cs.aspx?id=d81542e4-cdfa-4333-b082-1ae2d6c3ad16&cs=18): Dropping in new Java sample"
+ },
+ Resource = new CodeCheckedInResource
+ {
+ ChangesetId = 18,
+ Url = new Uri("https://good-company.some.ssl.host/DefaultCollection/_apis/tfvc/changesets/18"),
+ Author = new ResourceUser
+ {
+ Id = "d6245f20-2af8-44f4-9451-8107cb2767db",
+ DisplayName = "John Smith",
+ UniqueName = "fabrikamfiber16@hotmail.com"
+ },
+ CheckedInBy = new ResourceUser
+ {
+ Id = "d6245f20-2af8-44f4-9451-8107cb2767db",
+ DisplayName = "John Smith",
+ UniqueName = "fabrikamfiber16@hotmail.com"
+ },
+ CreatedDate = "2014-05-12T22:41:16Z".ToDateTime(),
+ Comment = "Dropping in new Java sample"
+ },
+ ResourceVersion = "1.0",
+ ResourceContainers = new PayloadResourceContainers
+ {
+ Collection = new PayloadResourceContainer { Id = "c12d0eb8-e382-443b-9f9c-c52cba5014c2" },
+ Account = new PayloadResourceContainer { Id = "f844ec47-a9db-4511-8281-8b63f4eaf94e" },
+ Project = new PayloadResourceContainer { Id = "be9b3917-87e6-42a4-a549-2bc06a7a878f" }
+ },
+ CreatedDate = "2016-05-02T19:01:11.7056821Z".ToDateTime()
+ };
+
+ // Act
+ var actual = data.ToObject();
+
+ // Assert
+ string expectedJson = JsonConvert.SerializeObject(expected);
+ string actualJson = JsonConvert.SerializeObject(actual);
+ Assert.Equal(expectedJson, actualJson);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/TeamRoomMessagePostedPayloadTests.cs b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/TeamRoomMessagePostedPayloadTests.cs
new file mode 100644
index 0000000..72973e9
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/TeamRoomMessagePostedPayloadTests.cs
@@ -0,0 +1,70 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNet.WebHooks.Payloads;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Microsoft.AspNet.WebHooks
+{
+ public class TeamRoomMessagePostedPayloadTests
+ {
+ [Fact]
+ public void TeamRoomMessagePostedPayload_Roundtrips()
+ {
+ // Arrange
+ JObject data = EmbeddedResource.ReadAsJObject("Microsoft.AspNet.WebHooks.Messages.message.posted.json");
+ var expected = new TeamRoomMessagePostedPayload
+ {
+ SubscriptionId = "00000000-0000-0000-0000-000000000000",
+ NotificationId = 3,
+ Id = "daae438c-296b-4512-b08e-571910874e9b",
+ EventType = "message.posted",
+ PublisherId = "tfs",
+ Message = new PayloadMessage
+ {
+ Text = "Jamal Hartnett posted a message to Northward-Fiber-Git Team Room\r\nHello",
+ Html = "Jamal Hartnett posted a message to Northward-Fiber-Git Team Room\r\nHello",
+ Markdown = "Jamal Hartnett posted a message to Northward-Fiber-Git Team Room\r\nHello"
+ },
+ DetailedMessage = new PayloadMessage
+ {
+ Text = "Jamal Hartnett posted a message to Northward-Fiber-Git Team Room\r\nHello",
+ Html = "Jamal Hartnett posted a message to Northward-Fiber-Git Team RoomHello
",
+ Markdown = "Jamal Hartnett posted a message to Northward-Fiber-Git Team Room\r\nHello"
+ },
+ Resource = new TeamRoomMessagePostedResource
+ {
+ Id = 0,
+ Content = "Hello",
+ MessageType = "normal",
+ PostedTime = "2014-05-02T19:17:13.3309587Z".ToDateTime(),
+ PostedRoomId = 1,
+ PostedBy = new ResourceUser
+ {
+ Id = "00067FFED5C7AF52@Live.com",
+ DisplayName = "Jamal Hartnett",
+ UniqueName = "Windows Live ID\\fabrikamfiber4@hotmail.com"
+ }
+ },
+ ResourceVersion = "1.0",
+ ResourceContainers = new PayloadResourceContainers
+ {
+ Collection = new PayloadResourceContainer { Id = "c12d0eb8-e382-443b-9f9c-c52cba5014c2" },
+ Account = new PayloadResourceContainer { Id = "f844ec47-a9db-4511-8281-8b63f4eaf94e" },
+ Project = new PayloadResourceContainer { Id = "be9b3917-87e6-42a4-a549-2bc06a7a878f" }
+ },
+ CreatedDate = "2016-05-02T19:13:40.8417653Z".ToDateTime()
+ };
+
+ // Act
+ var actual = data.ToObject();
+
+ // Assert
+ string expectedJson = JsonConvert.SerializeObject(expected);
+ string actualJson = JsonConvert.SerializeObject(actual);
+ Assert.Equal(expectedJson, actualJson);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/WorkItemCommentedOnPayloadTests.cs b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/WorkItemCommentedOnPayloadTests.cs
new file mode 100644
index 0000000..a7ec1fe
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/WorkItemCommentedOnPayloadTests.cs
@@ -0,0 +1,88 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNet.WebHooks.Payloads;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Microsoft.AspNet.WebHooks
+{
+ public class WorkItemCommentedOnPayloadTests
+ {
+ [Fact]
+ public void WorkItemCommentedOnPayload_Roundtrips()
+ {
+ // Arrange
+ JObject data = EmbeddedResource.ReadAsJObject("Microsoft.AspNet.WebHooks.Messages.workitem.commented.json");
+ var expected = new WorkItemCommentedOnPayload
+ {
+ SubscriptionId = "00000000-0000-0000-0000-000000000000",
+ NotificationId = 4,
+ Id = "fb2617ed-60df-4518-81fa-749faa6c5cd6",
+ EventType = "workitem.commented",
+ PublisherId = "tfs",
+ Message = new PayloadMessage
+ {
+ Text = "Bug #5 (Some great new idea!) commented on by Jamal Hartnett.\r\n(http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5)",
+ Html = "Bug #5 (Some great new idea!) commented on by Jamal Hartnett.",
+ Markdown = "[Bug #5](http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5) (Some great new idea!) commented on by Jamal Hartnett."
+ },
+ DetailedMessage = new PayloadMessage
+ {
+ Text = "Bug #5 (Some great new idea!) commented on by Jamal Hartnett.\r\n(http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5)\r\nThis is a great new idea",
+ Html = "Bug #5 (Some great new idea!) commented on by Jamal Hartnett. This is a great new idea",
+ Markdown = "[Bug #5](http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5) (Some great new idea!) commented on by Jamal Hartnett.\r\nThis is a great new idea"
+ },
+ Resource = new WorkItemCommentedOnResource
+ {
+ Id = 5,
+ RevisionNumber = 4,
+ Fields = new WorkItemFields
+ {
+ SystemAreaPath = "GoodCompanyCloud",
+ SystemTeamProject = "GoodCompanyCloud",
+ SystemIterationPath = "GoodCompanyCloud\\Release 1\\Sprint 1",
+ SystemWorkItemType = "Bug",
+ SystemState = "New",
+ SystemReason = "New defect reported",
+ SystemCreatedDate = "2014-07-15T17:42:44.663Z".ToDateTime(),
+ SystemCreatedBy = "Jamal Hartnett",
+ SystemChangedDate = "2014-07-15T17:42:44.663Z".ToDateTime(),
+ SystemChangedBy = "Jamal Hartnett",
+ SystemTitle = "Some great new idea!",
+ MicrosoftCommonSeverity = "3 - Medium",
+ KanbanColumn = "New",
+ SystemHistory = "This is a great new idea"
+ },
+ Links = new WorkItemLinks
+ {
+ Self = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5" },
+ WorkItemUpdates = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/updates" },
+ WorkItemRevisions = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/revisions" },
+ WorkItemType = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/ea830882-2a3c-4095-a53f-972f9a376f6e/workItemTypes/Bug" },
+ Fields = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/fields" }
+ },
+ Url = new Uri("http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5")
+ },
+ ResourceVersion = "1.0",
+ ResourceContainers = new PayloadResourceContainers
+ {
+ Collection = new PayloadResourceContainer { Id = "c12d0eb8-e382-443b-9f9c-c52cba5014c2" },
+ Account = new PayloadResourceContainer { Id = "f844ec47-a9db-4511-8281-8b63f4eaf94e" },
+ Project = new PayloadResourceContainer { Id = "be9b3917-87e6-42a4-a549-2bc06a7a878f" }
+ },
+ CreatedDate = "2016-05-02T19:15:37.4638247Z".ToDateTime()
+ };
+
+ // Act
+ var actual = data.ToObject();
+
+ // Assert
+ string expectedJson = JsonConvert.SerializeObject(expected);
+ string actualJson = JsonConvert.SerializeObject(actual);
+ Assert.Equal(expectedJson, actualJson);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/WorkItemCreatedPayloadTests.cs b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/WorkItemCreatedPayloadTests.cs
new file mode 100644
index 0000000..8036ea8
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/WorkItemCreatedPayloadTests.cs
@@ -0,0 +1,87 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNet.WebHooks.Payloads;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Microsoft.AspNet.WebHooks
+{
+ public class WorkItemCreatedPayloadTests
+ {
+ [Fact]
+ public void WorkItemCreatedPayload_Roundtrips()
+ {
+ // Arrange
+ JObject data = EmbeddedResource.ReadAsJObject("Microsoft.AspNet.WebHooks.Messages.workitem.created.json");
+ var expected = new WorkItemCreatedPayload
+ {
+ SubscriptionId = "00000000-0000-0000-0000-000000000000",
+ NotificationId = 5,
+ Id = "d2d46fb1-dba5-403c-9373-427583f19e8c",
+ EventType = "workitem.created",
+ PublisherId = "tfs",
+ Message = new PayloadMessage
+ {
+ Text = "Bug #5 (Some great new idea!) created by Jamal Hartnett.\r\n(http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5)",
+ Html = "Bug #5 (Some great new idea!) created by Jamal Hartnett.",
+ Markdown = "[Bug #5](http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5) (Some great new idea!) created by Jamal Hartnett."
+ },
+ DetailedMessage = new PayloadMessage
+ {
+ Text = "Bug #5 (Some great new idea!) created by Jamal Hartnett.\r\n(http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5)\r\n\r\n- State: New\r\n- Assigned to: \r\n- Comment: \r\n- Severity: 3 - Medium\r\n",
+ Html = "Bug #5 (Some great new idea!) created by Jamal Hartnett.\r\nState: New \r\nAssigned to: \r\nComment: \r\nSeverity: 3 - Medium ",
+ Markdown = "[Bug #5](http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5) (Some great new idea!) created by Jamal Hartnett.\r\n\r\n* State: New\r\n* Assigned to: \r\n* Comment: \r\n* Severity: 3 - Medium\r\n"
+ },
+ Resource = new WorkItemCreatedResource
+ {
+ Id = 5,
+ RevisionNumber = 1,
+ Fields = new WorkItemFields
+ {
+ SystemAreaPath = "GoodCompanyCloud",
+ SystemTeamProject = "GoodCompanyCloud",
+ SystemIterationPath = "GoodCompanyCloud\\Release 1\\Sprint 1",
+ SystemWorkItemType = "Bug",
+ SystemState = "New",
+ SystemReason = "New defect reported",
+ SystemCreatedDate = "2014-07-15T17:42:44.663Z".ToDateTime(),
+ SystemCreatedBy = "Jamal Hartnett",
+ SystemChangedDate = "2014-07-15T17:42:44.663Z".ToDateTime(),
+ SystemChangedBy = "Jamal Hartnett",
+ SystemTitle = "Some great new idea!",
+ MicrosoftCommonSeverity = "3 - Medium",
+ KanbanColumn = "New"
+ },
+ Links = new WorkItemLinks
+ {
+ Self = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5" },
+ WorkItemUpdates = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/updates" },
+ WorkItemRevisions = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/revisions" },
+ WorkItemType = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/ea830882-2a3c-4095-a53f-972f9a376f6e/workItemTypes/Bug" },
+ Fields = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/fields" }
+ },
+ Url = new Uri("http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5")
+ },
+ ResourceVersion = "1.0",
+ ResourceContainers = new PayloadResourceContainers
+ {
+ Collection = new PayloadResourceContainer { Id = "c12d0eb8-e382-443b-9f9c-c52cba5014c2" },
+ Account = new PayloadResourceContainer { Id = "f844ec47-a9db-4511-8281-8b63f4eaf94e" },
+ Project = new PayloadResourceContainer { Id = "be9b3917-87e6-42a4-a549-2bc06a7a878f" }
+ },
+ CreatedDate = "2016-05-02T19:16:25.6251162Z".ToDateTime()
+ };
+
+ // Act
+ var actual = data.ToObject();
+
+ // Assert
+ string expectedJson = JsonConvert.SerializeObject(expected);
+ string actualJson = JsonConvert.SerializeObject(actual);
+ Assert.Equal(expectedJson, actualJson);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/WorkItemDeletedPayloadTests.cs b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/WorkItemDeletedPayloadTests.cs
new file mode 100644
index 0000000..27bf1be
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/WorkItemDeletedPayloadTests.cs
@@ -0,0 +1,85 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNet.WebHooks.Payloads;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Microsoft.AspNet.WebHooks
+{
+ public class WorkItemDeletedPayloadTests
+ {
+ [Fact]
+ public void WorkItemDeletedPayload_Roundtrips()
+ {
+ // Arrange
+ JObject data = EmbeddedResource.ReadAsJObject("Microsoft.AspNet.WebHooks.Messages.workitem.deleted.json");
+ var expected = new WorkItemDeletedPayload
+ {
+ SubscriptionId = "00000000-0000-0000-0000-000000000000",
+ NotificationId = 6,
+ Id = "72da0ade-0709-40ee-beb7-104287bf7e84",
+ EventType = "workitem.deleted",
+ PublisherId = "tfs",
+ Message = new PayloadMessage
+ {
+ Text = "Bug #5 (Some great new idea!) deleted by Jamal Hartnett.",
+ Html = "Bug #5 (Some great new idea!) deleted by Jamal Hartnett.",
+ Markdown = "[Bug #5] (Some great new idea!) deleted by Jamal Hartnett."
+ },
+ DetailedMessage = new PayloadMessage
+ {
+ Text = "Bug #5 (Some great new idea!) deleted by Jamal Hartnett.\r\n\r\n- State: New\r\n",
+ Html = "Bug #5 (Some great new idea!) deleted by Jamal Hartnett.",
+ Markdown = "[Bug #5] (Some great new idea!) deleted by Jamal Hartnett.\r\n\r\n* State: New\r\n"
+ },
+ Resource = new WorkItemDeletedResource
+ {
+ Id = 5,
+ RevisionNumber = 1,
+ Fields = new WorkItemFields
+ {
+ SystemAreaPath = "GoodCompanyCloud",
+ SystemTeamProject = "GoodCompanyCloud",
+ SystemIterationPath = "GoodCompanyCloud\\Release 1\\Sprint 1",
+ SystemWorkItemType = "Bug",
+ SystemState = "New",
+ SystemReason = "New defect reported",
+ SystemCreatedDate = "2014-07-15T17:42:44.663Z".ToDateTime(),
+ SystemCreatedBy = "Jamal Hartnett",
+ SystemChangedDate = "2014-07-15T17:42:44.663Z".ToDateTime(),
+ SystemChangedBy = "Jamal Hartnett",
+ SystemTitle = "Some great new idea!",
+ MicrosoftCommonSeverity = "3 - Medium",
+ KanbanColumn = "New"
+ },
+ Links = new WorkItemLinks
+ {
+ Self = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/recyclebin/5" },
+ WorkItemType = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/ea830882-2a3c-4095-a53f-972f9a376f6e/workItemTypes/Bug" },
+ Fields = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/fields" }
+ },
+ Url = new Uri("http://good-company.some.ssl.host/DefaultCollection/_apis/wit/recyclebin/5")
+ },
+ ResourceVersion = "1.0",
+ ResourceContainers = new PayloadResourceContainers
+ {
+ Collection = new PayloadResourceContainer { Id = "c12d0eb8-e382-443b-9f9c-c52cba5014c2" },
+ Account = new PayloadResourceContainer { Id = "f844ec47-a9db-4511-8281-8b63f4eaf94e" },
+ Project = new PayloadResourceContainer { Id = "be9b3917-87e6-42a4-a549-2bc06a7a878f" }
+ },
+ CreatedDate = "2016-05-02T19:17:28.3644564Z".ToDateTime()
+ };
+
+ // Act
+ var actual = data.ToObject();
+
+ // Assert
+ string expectedJson = JsonConvert.SerializeObject(expected);
+ string actualJson = JsonConvert.SerializeObject(actual);
+ Assert.Equal(expectedJson, actualJson);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/WorkItemRestoredPayloadTests.cs b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/WorkItemRestoredPayloadTests.cs
new file mode 100644
index 0000000..9b534be
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/WorkItemRestoredPayloadTests.cs
@@ -0,0 +1,89 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNet.WebHooks.Payloads;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Microsoft.AspNet.WebHooks
+{
+ public class WorkItemRestoredPayloadTests
+ {
+ [Fact]
+ public void WorkItemRestoredPayload_Roundtrips()
+ {
+ // Arrange
+ JObject data = EmbeddedResource.ReadAsJObject("Microsoft.AspNet.WebHooks.Messages.workitem.restored.json");
+ var expected = new WorkItemRestoredPayload
+ {
+ SubscriptionId = "00000000-0000-0000-0000-000000000000",
+ NotificationId = 7,
+ Id = "1ca023d6-6cff-49dd-b3d1-302b69311810",
+ EventType = "workitem.restored",
+ PublisherId = "tfs",
+ Message = new PayloadMessage
+ {
+ Text = "Bug #5 (Some great new idea!) restored by Jamal Hartnett.\r\n(http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5)",
+ Html = "Bug #5 (Some great new idea!) restored by Jamal Hartnett.",
+ Markdown = "[Bug #5](http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5) (Some great new idea!) restored by Jamal Hartnett."
+ },
+ DetailedMessage = new PayloadMessage
+ {
+ Text = "Bug #5 (Some great new idea!) restored by Jamal Hartnett.\r\n(http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5)\r\n\r\n- State: New\r\n- Severity: 3 - Medium\r\n",
+ Html = "Bug #5 (Some great new idea!) restored by Jamal Hartnett.\r\nState: New Severity: 3 - Medium ",
+ Markdown = "[Bug #5](http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5) (Some great new idea!) restored by Jamal Hartnett.\r\n\r\n* State: New\r\n* Severity: 3 - Medium\r\n"
+ },
+ Resource = new WorkItemRestoredResource
+ {
+ Id = 5,
+ RevisionNumber = 1,
+ Fields = new WorkItemFields
+ {
+ SystemAreaPath = "GoodCompanyCloud",
+ SystemTeamProject = "GoodCompanyCloud",
+ SystemIterationPath = "GoodCompanyCloud\\Release 1\\Sprint 1",
+ SystemWorkItemType = "Bug",
+ SystemState = "New",
+ SystemReason = "New defect reported",
+ SystemCreatedDate = "2014-07-15T17:42:44.663Z".ToDateTime(),
+ SystemCreatedBy = "Jamal Hartnett",
+ SystemChangedDate = "2014-07-15T17:42:44.663Z".ToDateTime(),
+ SystemChangedBy = "Jamal Hartnett",
+ SystemTitle = "Some great new idea!",
+ MicrosoftCommonSeverity = "3 - Medium",
+ KanbanColumn = "New"
+ },
+ Links = new WorkItemLinks
+ {
+ Self = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5" },
+ WorkItemUpdates = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/updates" },
+ WorkItemRevisions = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/revisions" },
+ WorkItemType = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/ea830882-2a3c-4095-a53f-972f9a376f6e/workItemTypes/Bug" },
+ Fields = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/fields" },
+ Html = new WorkItemLink { Href = "https://good-company.some.ssl.host/web/wi.aspx?id=d81542e4-cdfa-4333-b082-1ae2d6c3ad16&id=5" },
+ WorkItemHistory = new WorkItemLink { Href = "https://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/history" }
+ },
+ Url = new Uri("http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5")
+ },
+ ResourceVersion = "1.0",
+ ResourceContainers = new PayloadResourceContainers
+ {
+ Collection = new PayloadResourceContainer { Id = "c12d0eb8-e382-443b-9f9c-c52cba5014c2" },
+ Account = new PayloadResourceContainer { Id = "f844ec47-a9db-4511-8281-8b63f4eaf94e" },
+ Project = new PayloadResourceContainer { Id = "be9b3917-87e6-42a4-a549-2bc06a7a878f" }
+ },
+ CreatedDate = "2016-05-02T19:18:15.5707279Z".ToDateTime()
+ };
+
+ // Act
+ var actual = data.ToObject();
+
+ // Assert
+ string expectedJson = JsonConvert.SerializeObject(expected);
+ string actualJson = JsonConvert.SerializeObject(actual);
+ Assert.Equal(expectedJson, actualJson);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/WorkItemUpdatedPayloadTests.cs b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/WorkItemUpdatedPayloadTests.cs
new file mode 100644
index 0000000..7fc037a
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Payloads/WorkItemUpdatedPayloadTests.cs
@@ -0,0 +1,141 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNet.WebHooks.Payloads;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Microsoft.AspNet.WebHooks
+{
+ public class WorkItemUpdatedPayloadTests
+ {
+ [Fact]
+ public void WorkItemUpdatedPayload_Roundtrips()
+ {
+ // Arrange
+ JObject data = EmbeddedResource.ReadAsJObject("Microsoft.AspNet.WebHooks.Messages.workitem.updated.json");
+ var expected = new WorkItemUpdatedPayload
+ {
+ SubscriptionId = "00000000-0000-0000-0000-000000000000",
+ NotificationId = 8,
+ Id = "27646e0e-b520-4d2b-9411-bba7524947cd",
+ EventType = "workitem.updated",
+ PublisherId = "tfs",
+ Message = new PayloadMessage
+ {
+ Text = "Bug #5 (Some great new idea!) updated by Jamal Hartnett.\r\n(http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5)",
+ Html = "Bug #5 (Some great new idea!) updated by Jamal Hartnett.",
+ Markdown = "[Bug #5](http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5) (Some great new idea!) updated by Jamal Hartnett."
+ },
+ DetailedMessage = new PayloadMessage
+ {
+ Text = "Bug #5 (Some great new idea!) updated by Jamal Hartnett.\r\n(http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5)\r\n\r\n- New State: Approved\r\n",
+ Html = "Bug #5 (Some great new idea!) updated by Jamal Hartnett.",
+ Markdown = "[Bug #5](http://good-company.some.ssl.host/web/wi.aspx?id=74e918bf-3376-436d-bd20-8e8c1287f465&id=5) (Some great new idea!) updated by Jamal Hartnett.\r\n\r\n* New State: Approved\r\n"
+ },
+ Resource = new WorkItemUpdatedResource
+ {
+ Id = 2,
+ WorkItemId = 0,
+ RevisionNumber = 2,
+ RevisedBy = null,
+ RevisedDate = "0001-01-01T00:00:00".ToDateTime(),
+ Fields = new WorkItemUpdatedFields
+ {
+ SystemRev = new WorkItemUpdatedFieldValue
+ {
+ OldValue = "1",
+ NewValue = "2"
+ },
+ SystemAuthorizedDate = new WorkItemUpdatedFieldValue
+ {
+ OldValue = "2014-07-15T16:48:44.663Z".ToDateTime(),
+ NewValue = "2014-07-15T17:42:44.663Z".ToDateTime()
+ },
+ SystemRevisedDate = new WorkItemUpdatedFieldValue
+ {
+ OldValue = "2014-07-15T17:42:44.663Z".ToDateTime(),
+ NewValue = "9999-01-01T00:00:00Z".ToDateTime()
+ },
+ SystemState = new WorkItemUpdatedFieldValue
+ {
+ OldValue = "New",
+ NewValue = "Approved"
+ },
+ SystemReason = new WorkItemUpdatedFieldValue
+ {
+ OldValue = "New defect reported",
+ NewValue = "Approved by the Product Owner"
+ },
+ SystemAssignedTo = new WorkItemUpdatedFieldValue
+ {
+ NewValue = "Jamal Hartnet"
+ },
+ SystemChangedDate = new WorkItemUpdatedFieldValue
+ {
+ OldValue = "2014-07-15T16:48:44.663Z".ToDateTime(),
+ NewValue = "2014-07-15T17:42:44.663Z".ToDateTime()
+ },
+ SystemWatermark = new WorkItemUpdatedFieldValue
+ {
+ OldValue = "2",
+ NewValue = "5"
+ },
+ MicrosoftCommonSeverity = new WorkItemUpdatedFieldValue
+ {
+ OldValue = "3 - Medium",
+ NewValue = "2 - High"
+ }
+ },
+ Links = new WorkItemLinks
+ {
+ Self = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/updates/2" },
+ Parent = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5" },
+ WorkItemUpdates = new WorkItemLink { Href = "http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/updates" }
+ },
+ Url = new Uri("http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/updates/2"),
+ Revision = new WorkItemUpdatedRevision
+ {
+ Id = 5,
+ Rev = 2,
+ Fields = new WorkItemFields
+ {
+ SystemAreaPath = "GoodCompanyCloud",
+ SystemTeamProject = "GoodCompanyCloud",
+ SystemIterationPath = "GoodCompanyCloud\\Release 1\\Sprint 1",
+ SystemWorkItemType = "Bug",
+ SystemState = "New",
+ SystemReason = "New defect reported",
+ SystemCreatedDate = "2014-07-15T16:48:44.663Z".ToDateTime(),
+ SystemCreatedBy = "Jamal Hartnett",
+ SystemChangedDate = "2014-07-15T16:48:44.663Z".ToDateTime(),
+ SystemChangedBy = "Jamal Hartnett",
+ SystemTitle = "Some great new idea!",
+ MicrosoftCommonSeverity = "3 - Medium",
+ KanbanColumn = "New"
+ },
+ Url = new Uri("http://good-company.some.ssl.host/DefaultCollection/_apis/wit/workItems/5/revisions/2")
+ }
+ },
+ ResourceVersion = "1.0",
+ ResourceContainers = new PayloadResourceContainers
+ {
+ Collection = new PayloadResourceContainer { Id = "c12d0eb8-e382-443b-9f9c-c52cba5014c2" },
+ Account = new PayloadResourceContainer { Id = "f844ec47-a9db-4511-8281-8b63f4eaf94e" },
+ Project = new PayloadResourceContainer { Id = "be9b3917-87e6-42a4-a549-2bc06a7a878f" }
+ },
+ CreatedDate = "2016-05-02T19:19:12.8836446Z".ToDateTime()
+ };
+
+ // Act
+ var actual = data.ToObject();
+
+ // Assert
+ string expectedJson = JsonConvert.SerializeObject(expected);
+ string actualJson = JsonConvert.SerializeObject(actual);
+ Assert.Equal(expectedJson, actualJson);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Properties/AssemblyInfo.cs b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..f63ea89
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,8 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Runtime.InteropServices;
+
+[assembly: CLSCompliant(false)]
+[assembly: ComVisible(false)]
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/WebHooks/VstsWebHookReceiverTests.cs b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/WebHooks/VstsWebHookReceiverTests.cs
new file mode 100644
index 0000000..705eccf
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/WebHooks/VstsWebHookReceiverTests.cs
@@ -0,0 +1,203 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web.Http;
+using Moq;
+using Moq.Protected;
+using Xunit;
+
+namespace Microsoft.AspNet.WebHooks
+{
+ public class VstsWebHookReceiverTests : WebHookReceiverTestsBase
+ {
+ private const string TestAction = "message.posted";
+ private const string TestId = "";
+ private const string TestSecret = "12345678901234567890123456789012";
+ private const string TestAddress = "https://some.ssl.host?code=" + TestSecret;
+ private HttpRequestMessage _postRequest;
+
+ public static TheoryData InvalidCodeQueries
+ {
+ get
+ {
+ return new TheoryData
+ {
+ string.Empty,
+ "=",
+ "==",
+ "invalid",
+ "code",
+ "code=",
+ "k1=v1;k2=v2",
+ };
+ }
+ }
+
+ [Fact]
+ public void ReceiverName_IsConsistent()
+ {
+ // Arrange
+ IWebHookReceiver rec = new VstsWebHookReceiver();
+ string expected = "vsts";
+
+ // Act
+ string actual1 = rec.Name;
+ string actual2 = VstsWebHookReceiver.ReceiverName;
+
+ // Assert
+ Assert.Equal(expected, actual1);
+ Assert.Equal(actual1, actual2);
+ }
+
+ [Fact]
+ public async Task ReceiveAsync_Throws_IfPostIsNotUsingHttps()
+ {
+ // Arrange
+ Initialize(TestSecret);
+ _postRequest.RequestUri = new Uri("http://some.no.ssl.host");
+
+ // Act
+ HttpResponseException ex = await Assert.ThrowsAsync(() => ReceiverMock.Object.ReceiveAsync(TestId, RequestContext, _postRequest));
+
+ // Assert
+ HttpError error = await ex.Response.Content.ReadAsAsync();
+ Assert.Equal("The WebHook receiver 'VstsWebHookReceiverProxy' requires HTTPS in order to be secure. Please register a WebHook URI of type 'https'.", error.Message);
+ ReceiverMock.Protected()
+ .Verify>("ExecuteWebHookAsync", Times.Never(), TestId, RequestContext, _postRequest, ItExpr.IsAny>(), ItExpr.IsAny());
+ }
+
+ [Theory]
+ [MemberData("InvalidCodeQueries")]
+ public async Task ReceiveAsync_Throws_IfPostHasNoCodeParameter(string query)
+ {
+ // Arrange
+ Initialize(TestSecret);
+ _postRequest.RequestUri = new Uri("https://some.no.ssl.host?" + query);
+
+ // Act
+ HttpResponseException ex = await Assert.ThrowsAsync(() => ReceiverMock.Object.ReceiveAsync(TestId, RequestContext, _postRequest));
+
+ // Assert
+ HttpError error = await ex.Response.Content.ReadAsAsync();
+ Assert.Equal("The WebHook verification request must contain a 'code' query parameter.", error.Message);
+ ReceiverMock.Protected()
+ .Verify>("ExecuteWebHookAsync", Times.Never(), TestId, RequestContext, _postRequest, ItExpr.IsAny>(), ItExpr.IsAny());
+ }
+
+ [Fact]
+ public async Task ReceiveAsync_Throws_IfPostHasWrongCodeParameter()
+ {
+ // Arrange
+ Initialize(TestSecret);
+ _postRequest.RequestUri = new Uri("https://some.no.ssl.host?code=invalid");
+
+ // Act
+ HttpResponseException ex = await Assert.ThrowsAsync(() => ReceiverMock.Object.ReceiveAsync(TestId, RequestContext, _postRequest));
+
+ // Assert
+ HttpError error = await ex.Response.Content.ReadAsAsync();
+ Assert.Equal("The 'code' query parameter provided in the HTTP request did not match the expected value.", error.Message);
+ ReceiverMock.Protected()
+ .Verify>("ExecuteWebHookAsync", Times.Never(), TestId, RequestContext, _postRequest, ItExpr.IsAny>(), ItExpr.IsAny());
+ }
+
+ [Theory]
+ [MemberData("ValidIdData")]
+ public async Task ReceiveAsync_Throws_IfPostIsNotJson(string id)
+ {
+ // Arrange
+ Initialize(GetConfigValue(id, TestSecret));
+ _postRequest.Content = new StringContent("Hello World!", Encoding.UTF8, "text/plain");
+
+ // Act
+ HttpResponseException ex = await Assert.ThrowsAsync(() => ReceiverMock.Object.ReceiveAsync(id, RequestContext, _postRequest));
+
+ // Assert
+ HttpError error = await ex.Response.Content.ReadAsAsync();
+ Assert.Equal("The WebHook request must contain an entity body formatted as JSON.", error.Message);
+ ReceiverMock.Protected()
+ .Verify>("ExecuteWebHookAsync", Times.Never(), id, RequestContext, _postRequest, ItExpr.IsAny>(), ItExpr.IsAny());
+ }
+
+ [Theory]
+ [MemberData("ValidIdData")]
+ public async Task ReceiveAsync_ReturnsError_IfPostHasNoEventTypeProperty(string id)
+ {
+ // Arrange
+ Initialize(GetConfigValue(id, TestSecret));
+ _postRequest.Content = CreateRequestContent("Microsoft.AspNet.WebHooks.Messages.bad.noEventType.json");
+
+ // Act
+ HttpResponseMessage actual = await ReceiverMock.Object.ReceiveAsync(id, RequestContext, _postRequest);
+
+ // Assert
+ HttpError error = await actual.Content.ReadAsAsync();
+ Assert.Equal("No property 'eventType' was found in root of the object.", error.Message);
+ ReceiverMock.Protected()
+ .Verify>("ExecuteWebHookAsync", Times.Never(), id, RequestContext, _postRequest, ItExpr.IsAny>(), ItExpr.IsAny());
+ }
+
+ [Theory]
+ [MemberData("ValidIdData")]
+ public async Task ReceiveAsync_Succeeds_IfValidPostRequest(string id)
+ {
+ // Arrange
+ Initialize(GetConfigValue(id, TestSecret));
+ List actions = new List { TestAction };
+ ReceiverMock.Protected()
+ .Setup>("ExecuteWebHookAsync", id, RequestContext, _postRequest, actions, ItExpr.IsAny())
+ .ReturnsAsync(new HttpResponseMessage())
+ .Verifiable();
+
+ // Act
+ await ReceiverMock.Object.ReceiveAsync(id, RequestContext, _postRequest);
+
+ // Assert
+ ReceiverMock.Verify();
+ }
+
+ [Theory]
+ [InlineData("GET")]
+ [InlineData("HEAD")]
+ [InlineData("PATCH")]
+ [InlineData("PUT")]
+ [InlineData("OPTIONS")]
+ public async Task ReceiveAsync_ReturnsError_IfInvalidMethod(string method)
+ {
+ // Arrange
+ Initialize(TestSecret);
+ HttpRequestMessage req = new HttpRequestMessage { Method = new HttpMethod(method) };
+ req.SetRequestContext(RequestContext);
+
+ // Act
+ HttpResponseMessage actual = await ReceiverMock.Object.ReceiveAsync(TestId, RequestContext, req);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.MethodNotAllowed, actual.StatusCode);
+ ReceiverMock.Protected()
+ .Verify>("ExecuteWebHookAsync", Times.Never(), TestId, RequestContext, req, ItExpr.IsAny>(), ItExpr.IsAny());
+ }
+
+ public override void Initialize(string config)
+ {
+ base.Initialize(config);
+ HttpConfig.InitializeReceiveVstsWebHooks();
+
+ _postRequest = new HttpRequestMessage(HttpMethod.Post, TestAddress);
+ _postRequest.SetRequestContext(RequestContext);
+ _postRequest.Content = CreateRequestContent("Microsoft.AspNet.WebHooks.Messages.message.posted.json");
+ }
+
+ private HttpContent CreateRequestContent(string payload)
+ {
+ string content = EmbeddedResource.ReadAsString(payload);
+ return new StringContent(content, Encoding.UTF8, "application/json");
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/packages.config b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/packages.config
new file mode 100644
index 0000000..3e60551
--- /dev/null
+++ b/test/Microsoft.AspNet.WebHooks.Receivers.VSTS.Test/packages.config
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/SkipStrongNames.xml b/tools/SkipStrongNames.xml
index a255609..9d95bd4 100644
--- a/tools/SkipStrongNames.xml
+++ b/tools/SkipStrongNames.xml
@@ -24,6 +24,7 @@
+
@@ -51,6 +52,7 @@
+