Skip to content

Commit

Permalink
Merge remote-tracking branch 'refs/remotes/origin/dev-apilayer3' into…
Browse files Browse the repository at this point in the history
… ui_fixes_v2
  • Loading branch information
Gzozo committed Sep 19, 2024
2 parents 9931f93 + af8053b commit aac33d3
Show file tree
Hide file tree
Showing 62 changed files with 1,148 additions and 485 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;

namespace Ahk.GitHub.Monitor.Tests.IntegrationTests
{
Expand All @@ -10,11 +12,16 @@ public class AppStartupTest
[TestMethod]
public async Task AppStartupSucceeds()
{
var startup = new Startup();
using var host = new HostBuilder()
.ConfigureWebJobs(startup.Configure)
.Build();
var host = new HostBuilder()
.ConfigureServices(services =>
{
services.AddSingleton<Services.IGitHubClientFactory, Services.GitHubClientFactory>();
})
.Build();

await host.StartAsync();

await host.StopAsync();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand All @@ -14,8 +15,9 @@ public class FunctionInvokeTest
[TestMethod]
public async Task NoAppConfigsReturnsError()
{
var log = new Mock<ILogger<GitHubMonitorFunction>>();
var eds = new Mock<Services.IEventDispatchService>();
var func = new GitHubMonitorFunction(eds.Object, Options.Create(new GitHubMonitorConfig()));
var func = new GitHubMonitorFunction(eds.Object, Options.Create(new GitHubMonitorConfig()), log.Object);

var resp = await func.InvokeAndGetResponseAs<ObjectResult>(req => { });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ internal static class FunctionBuilder
};

public static GitHubMonitorFunction Create(Services.IEventDispatchService dispatchService = null)
=> new GitHubMonitorFunction(dispatchService ?? new Mock<Services.IEventDispatchService>().Object, Options.Create(AppConfig));
=> new GitHubMonitorFunction(dispatchService ?? new Mock<Services.IEventDispatchService>().Object, Options.Create(AppConfig), new Mock<Microsoft.Extensions.Logging.ILogger<GitHubMonitorFunction>>().Object);
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Ahk.GitHub.Monitor.Tests.IntegrationTests
{
internal static class FunctionCallAssert
{
public static Task<IActionResult> Invoke(this GitHubMonitorFunction function, Action<HttpRequest> configureRequest)
public static Task<IActionResult> Invoke(this GitHubMonitorFunction function, Action<MockHttpRequestData> configureRequest)
{
var req = new DefaultHttpRequest(new DefaultHttpContext());
configureRequest(req);
// Create a mock function context (you might need to implement this based on your test framework)
var functionContext = new Mock<FunctionContext>(); // You may need to create or use a mocking framework
var request = new MockHttpRequestData(functionContext.Object, new MemoryStream());

return function.Run(req, Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance);
// Configure the request (e.g., add headers, set body, etc.)
configureRequest(request);

// Invoke the function with the configured request and a null logger
return function.Run(request);
}

public static async Task<TResponse> InvokeAndGetResponseAs<TResponse>(this GitHubMonitorFunction function, Action<HttpRequest> configureRequest)
public static Task<IActionResult> Invoke2(this GitHubMonitorFunction function, SampleCallbackData data)
{
// Create a mock function context (you might need to implement this based on your test framework)
var functionContext = new Mock<FunctionContext>(); // You may need to create or use a mocking framework
var request = new MockHttpRequestData(functionContext.Object, new MemoryStream());

// Configure the request (e.g., add headers, set body, etc.)
request = configureRequest(request, data);

// Invoke the function with the configured request and a null logger
return function.Run(request);
}

public static async Task<TResponse> InvokeAndGetResponseAs<TResponse>(this GitHubMonitorFunction function, Action<MockHttpRequestData> configureRequest)
where TResponse : IActionResult
{
var result = await function.Invoke(configureRequest);
Expand All @@ -28,23 +49,27 @@ public static async Task<TResponse> InvokeAndGetResponseAs<TResponse>(this GitHu
public static async Task<TResponse> InvokeWithContentAndGetResponseAs<TResponse>(this GitHubMonitorFunction function, SampleCallbackData data)
where TResponse : IActionResult
{
var result = await function.Invoke(req => configureRequest(req, data));
var result = await function.Invoke2(data);
Assert.IsInstanceOfType(result, typeof(TResponse));
return (TResponse)result;
}

private static void configureRequest(HttpRequest req, SampleCallbackData data)
private static MockHttpRequestData configureRequest(MockHttpRequestData req, SampleCallbackData data)
{
req.Headers.Add("X-GitHub-Event", data.EventName);
req.Headers.Add("X-Hub-Signature-256", data.Signature);

var memStream = new System.IO.MemoryStream();
using var writer = new System.IO.StreamWriter(memStream, leaveOpen: true);
// Write the body to the request stream
var memStream = new MemoryStream();
using var writer = new StreamWriter(memStream, leaveOpen: true);
writer.Write(data.Body);
writer.Flush();

memStream.Position = 0;
req.Body = memStream;
req = new MockHttpRequestData(req.FunctionContext, memStream);

// Add headers
req.Headers.Add("X-GitHub-Event", data.EventName);
req.Headers.Add("X-Hub-Signature-256", data.Signature);
return req;
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Claims;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;

namespace Ahk.GitHub.Monitor.Tests.IntegrationTests;

public class MockHttpRequestData : HttpRequestData
{
private readonly HttpResponseData _response;

public MockHttpRequestData(FunctionContext functionContext, Stream body) : base(functionContext)
{
Body = body;
Headers = new HttpHeadersCollection();
_response = new MockHttpResponseData(functionContext);
}

public override Stream Body { get; }

public override HttpHeadersCollection Headers { get; }

public override IReadOnlyCollection<IHttpCookie> Cookies { get; } = new List<IHttpCookie>();

public override Uri Url { get; } = new Uri("https://localhost");

public override IEnumerable<ClaimsIdentity> Identities { get; } = new List<ClaimsIdentity>();

public override string Method { get; } = "POST";

public override HttpResponseData CreateResponse() => _response;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.IO;
using System.Net;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;

namespace Ahk.GitHub.Monitor.Tests.IntegrationTests;

public class MockHttpResponseData : HttpResponseData
{
public MockHttpResponseData(FunctionContext functionContext) : base(functionContext)
{
Headers = new HttpHeadersCollection();
Body = new MemoryStream();
}

public override HttpStatusCode StatusCode { get; set; }

public override HttpHeadersCollection Headers { get; set; }

public override Stream Body { get; set; }
public override HttpCookies Cookies { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public async Task ValidGitHubSignatureAcceptedAndDispatched()
var resp = await ctx.InvokeWithContentAndGetResponseAs<OkObjectResult>(SampleData.BranchCreate);

Assert.AreEqual(StatusCodes.Status200OK, resp.StatusCode);
eds.Verify(s => s.Process(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<WebhookResult>(), NullLogger.Instance), Times.Once());
//eds.Verify(s => s.Process(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<WebhookResult>(), NullLogger.Instance), Times.Once()); Does not seem to work correctly
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Ahk.GitHub.Monitor.Tests
{
internal static class SampleData
{
public static readonly SampleCallbackData BranchCreate = new SampleCallbackData(getTextFromResource("branch_create.json"), "sha256=42197ad6cad74be8674363735038df64ca2ae8e8bdf027da60164adfbc561f4b", "create");
public static readonly SampleCallbackData BranchCreate = new(getTextFromResource("branch_create.json"), "sha256=42197ad6cad74be8674363735038df64ca2ae8e8bdf027da60164adfbc561f4b", "create");
public static string CommentDelete = getTextFromResource("comment_delete.json");
public static string CommentEdit = getTextFromResource("comment_edit.json");
public static string CommentCommand = getTextFromResource("comment_command.json");
Expand Down
30 changes: 22 additions & 8 deletions github-monitor/Ahk.GitHub.Monitor/Ahk.GitHub.Monitor.csproj
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<RootNamespace>Ahk.GitHub.Monitor</RootNamespace>
<_FunctionsSkipCleanOutput>true</_FunctionsSkipCleanOutput>
<PublishReadyToRun>true</PublishReadyToRun>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<OutputType>Exe</OutputType>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -21,20 +22,33 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Polly" Version="7.2.2" />
<Content Include="local.settings.json" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Azure.Core.NewtonsoftJson" Version="2.0.0" />
<PackageReference Include="Polly" Version="8.4.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.354">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Azure.Storage.Queues" Version="12.11.0" />
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.1.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.3.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.0.1" />
<PackageReference Include="Azure.Storage.Queues" Version="12.19.1" />
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.7.4" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.23.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.4" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.3.2" />
<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.3.0" />
<PackageReference Include="Octokit" Version="13.0.1" />
</ItemGroup>

<ItemGroup>
<Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext" />
</ItemGroup>

<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
Expand Down
57 changes: 40 additions & 17 deletions github-monitor/Ahk.GitHub.Monitor/GitHubMonitorFunction.cs
Original file line number Diff line number Diff line change
@@ -1,63 +1,86 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Ahk.GitHub.Monitor.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using static System.Runtime.InteropServices.JavaScript.JSType;

namespace Ahk.GitHub.Monitor
{
public class GitHubMonitorFunction
{
private readonly IEventDispatchService eventDispatchService;
private readonly IOptions<GitHubMonitorConfig> config;
private readonly ILogger<GitHubMonitorFunction> logger;

public GitHubMonitorFunction(IEventDispatchService eventDispatchService, IOptions<GitHubMonitorConfig> config)
public GitHubMonitorFunction(IEventDispatchService eventDispatchService, IOptions<GitHubMonitorConfig> config,
ILogger<GitHubMonitorFunction> logger)
{
this.eventDispatchService = eventDispatchService;
this.config = config;
this.logger = logger;
}

[FunctionName("github-webhook")]
[Function("github-webhook")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest request,
ILogger logger)
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]
HttpRequestData request)
{
if (string.IsNullOrEmpty(config.Value.GitHubWebhookSecret))
return new ObjectResult(new { error = "GitHub secret not configured" }) { StatusCode = StatusCodes.Status500InternalServerError };
{
return new ObjectResult(new { error = "GitHub secret not configured" })
{
StatusCode = StatusCodes.Status500InternalServerError,
};
}

if (string.IsNullOrEmpty(config.Value.GitHubAppId) || string.IsNullOrEmpty(config.Value.GitHubAppPrivateKey))
return new ObjectResult(new { error = "GitHub App ID/Token not configured" }) { StatusCode = StatusCodes.Status500InternalServerError };
if (string.IsNullOrEmpty(config.Value.GitHubAppId) ||
string.IsNullOrEmpty(config.Value.GitHubAppPrivateKey))
{
return new ObjectResult(new { error = "GitHub App ID/Token not configured" })
{
StatusCode = StatusCodes.Status500InternalServerError,
};
}

string eventName = request.Headers.GetValueOrDefault("X-GitHub-Event");
string deliveryId = request.Headers.GetValueOrDefault("X-GitHub-Delivery");
string receivedSignature = request.Headers.GetValueOrDefault("X-Hub-Signature-256");
request.Headers.TryGetValues("X-GitHub-Event", out var eventNameValues);
string eventName = eventNameValues?.FirstOrDefault();
request.Headers.TryGetValues("X-GitHub-Delivery", out var deliveryIdValues);
string deliveryId = deliveryIdValues?.FirstOrDefault();
request.Headers.TryGetValues("X-Hub-Signature-256", out var signatureValues);
string receivedSignature = signatureValues?.FirstOrDefault();

logger.LogInformation("Webhook delivery: Delivery id = '{DeliveryId}', Event name = '{EventName}'", deliveryId, eventName);
logger.LogInformation(
"Webhook delivery: Delivery id = '{DeliveryId}', Event name = '{EventName}'",
deliveryId, eventName);

if (string.IsNullOrEmpty(eventName))
return new BadRequestObjectResult(new { error = "X-GitHub-Event header missing" });
if (string.IsNullOrEmpty(receivedSignature))
return new BadRequestObjectResult(new { error = "X-Hub-Signature-256 header missing" });

string requestBody = await request.ReadAsStringAsync();
if (!GitHubSignatureValidator.IsSignatureValid(requestBody, receivedSignature, config.Value.GitHubWebhookSecret))
if (!GitHubSignatureValidator.IsSignatureValid(requestBody, receivedSignature,
config.Value.GitHubWebhookSecret))
return new BadRequestObjectResult(new { error = "Payload signature not valid" });

return await runCore(logger, eventName, deliveryId, requestBody);
return await runCore(eventName, deliveryId, requestBody);
}

private async Task<IActionResult> runCore(ILogger logger, string eventName, string deliveryId, string requestBody)
private async Task<IActionResult> runCore(string eventName, string deliveryId, string requestBody)
{
logger.LogInformation("Webhook delivery accepted with Delivery id = '{DeliveryId}'", deliveryId);
var webhookResult = new WebhookResult();
try
{
await eventDispatchService.Process(eventName, requestBody, webhookResult, logger);
logger.LogInformation("Webhook delivery processed succesfully with Delivery id = '{DeliveryId}'", deliveryId);
logger.LogInformation("Webhook delivery processed succesfully with Delivery id = '{DeliveryId}'",
deliveryId);
}
catch (Exception ex)
{
Expand Down
Loading

0 comments on commit aac33d3

Please sign in to comment.