Skip to content

Commit 8da20e5

Browse files
committed
Updated tests
1 parent e016c8c commit 8da20e5

File tree

313 files changed

+15449
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

313 files changed

+15449
-1
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Intent Architect
2+
3+
**/.intent/*
4+
!*.application.output.log
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<OutputType>Exe</OutputType>
6+
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
7+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
8+
<NoWarn>$(NoWarn);1591</NoWarn>
9+
<Nullable>enable</Nullable>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<ProjectReference Include="..\AzureFunction.QueueStorage.Infrastructure\AzureFunction.QueueStorage.Infrastructure.csproj" />
14+
<ProjectReference Include="..\AzureFunction.QueueStorage.Application\AzureFunction.QueueStorage.Application.csproj" />
15+
<ProjectReference Include="..\AzureFunction.QueueStorage.Domain\AzureFunction.QueueStorage.Domain.csproj" />
16+
</ItemGroup>
17+
<ItemGroup>
18+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
19+
</ItemGroup>
20+
<ItemGroup>
21+
<None Update="host.json">
22+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
23+
</None>
24+
<None Update="local.settings.json">
25+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
26+
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
27+
</None>
28+
</ItemGroup>
29+
<ItemGroup>
30+
<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.23.0" />
31+
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="2.1.0" />
32+
<PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="2.0.0" />
33+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.0.2" />
34+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.OpenApi" Version="1.6.0" />
35+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues" Version="5.5.3" />
36+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.5" />
37+
</ItemGroup>
38+
39+
</Project>
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.IO;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Intent.RoslynWeaver.Attributes;
8+
using Microsoft.AspNetCore.Http;
9+
10+
[assembly: DefaultIntentManaged(Mode.Fully)]
11+
[assembly: IntentTemplate("Intent.AzureFunctions.AzureFunctionClassHelper", Version = "1.0")]
12+
13+
namespace AzureFunction.QueueStorage.Api
14+
{
15+
static class AzureFunctionHelper
16+
{
17+
private static readonly System.Text.Json.JsonSerializerOptions SerializationSettings = new() { PropertyNameCaseInsensitive = true };
18+
19+
public static async Task<T> DeserializeJsonContentAsync<T>(Stream jsonContentStream, CancellationToken cancellationToken)
20+
{
21+
return await System.Text.Json.JsonSerializer.DeserializeAsync<T>(jsonContentStream, SerializationSettings, cancellationToken) ?? throw new FormatException("Unable to deserialize JSON content.");
22+
}
23+
24+
public static T GetQueryParam<T>(string paramName, IQueryCollection query, ParseDelegate<T> parse)
25+
where T : struct
26+
{
27+
var strVal = query[paramName];
28+
if (string.IsNullOrEmpty(strVal) || !parse(strVal, out T parsed))
29+
{
30+
throw new FormatException($"Parameter '{paramName}' could not be parsed as a {typeof(T).Name}.");
31+
}
32+
33+
return parsed;
34+
}
35+
36+
public static T? GetQueryParamNullable<T>(string paramName, IQueryCollection query, ParseDelegate<T> parse)
37+
where T : struct
38+
{
39+
var strVal = query[paramName];
40+
if (string.IsNullOrEmpty(strVal))
41+
{
42+
return null;
43+
}
44+
45+
if (!parse(strVal, out T parsed))
46+
{
47+
throw new FormatException($"Parameter '{paramName}' could not be parsed as a {typeof(T).Name}.");
48+
}
49+
50+
return parsed;
51+
}
52+
53+
public static IEnumerable<T> GetQueryParamCollection<T>(string paramName, IQueryCollection query, ParseDelegate<T> parse)
54+
where T : struct
55+
{
56+
var result = new List<T>();
57+
var strVal = query[paramName];
58+
var values = strVal.ToString().Split(",");
59+
foreach (var v in values)
60+
{
61+
if (string.IsNullOrEmpty(v) || !parse(v, out T parsed))
62+
{
63+
throw new FormatException($"Parameter '{paramName}' could not be parsed as a {typeof(T).Name}.");
64+
}
65+
result.Add(parsed);
66+
}
67+
68+
return result;
69+
}
70+
71+
public static T GetHeadersParam<T>(string paramName, IHeaderDictionary headers, ParseDelegate<T> parse)
72+
where T : struct
73+
{
74+
var strVal = headers[paramName];
75+
if (string.IsNullOrEmpty(strVal) || !parse(strVal, out T parsed))
76+
{
77+
throw new FormatException($"Parameter '{paramName}' could not be parsed as a {typeof(T).Name}.");
78+
}
79+
80+
return parsed;
81+
}
82+
83+
public static T? GetHeadersParamNullable<T>(string paramName, IHeaderDictionary headers, ParseDelegate<T> parse)
84+
where T : struct
85+
{
86+
var strVal = headers[paramName];
87+
if (string.IsNullOrEmpty(strVal))
88+
{
89+
return null;
90+
}
91+
92+
if (!parse(strVal, out T parsed))
93+
{
94+
throw new FormatException($"Parameter '{paramName}' could not be parsed as a {typeof(T).Name}.");
95+
}
96+
97+
return parsed;
98+
}
99+
100+
public static IEnumerable<T> GetHeadersParamCollection<T>(string paramName, IHeaderDictionary headers, ParseDelegate<T> parse)
101+
where T : struct
102+
{
103+
var result = new List<T>();
104+
var strVal = headers[paramName];
105+
var values = strVal.ToString().Split(",");
106+
foreach (var v in values)
107+
{
108+
if (string.IsNullOrEmpty(v) || !parse(v, out T parsed))
109+
{
110+
throw new FormatException($"Parameter '{paramName}' could not be parsed as a {typeof(T).Name}.");
111+
}
112+
result.Add(parsed);
113+
}
114+
115+
return result;
116+
}
117+
118+
public static TEnum GetEnumParam<TEnum>(string paramName, string enumString)
119+
where TEnum : struct
120+
{
121+
if (!Enum.TryParse<TEnum>(enumString, true, out var enumValue))
122+
{
123+
throw new FormatException($"Parameter {paramName} has value of {enumString} which is not a valid literal value for Enum {typeof(TEnum).Name}");
124+
}
125+
126+
return enumValue;
127+
}
128+
129+
public static TEnum? GetEnumParamNullable<TEnum>(string paramName, string? enumString)
130+
where TEnum : struct
131+
{
132+
if (enumString is null)
133+
{
134+
return null;
135+
}
136+
137+
if (!Enum.TryParse<TEnum>(enumString, true, out var enumValue))
138+
{
139+
throw new FormatException($"Parameter {paramName} has value of {enumString} which is not a valid literal value for Enum {typeof(TEnum).Name}");
140+
}
141+
142+
return enumValue;
143+
}
144+
145+
public delegate bool ParseDelegate<T>(string strVal, out T parsed);
146+
}
147+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using AzureFunction.QueueStorage.Api.Services;
2+
using AzureFunction.QueueStorage.Application.Common.Interfaces;
3+
using Intent.RoslynWeaver.Attributes;
4+
using Microsoft.Extensions.Configuration;
5+
using Microsoft.Extensions.DependencyInjection;
6+
7+
[assembly: DefaultIntentManaged(Mode.Fully)]
8+
[assembly: IntentTemplate("Intent.Application.Identity.ApplicationSecurityConfiguration", Version = "1.0")]
9+
10+
namespace AzureFunction.QueueStorage.Api.Configuration
11+
{
12+
public static class ApplicationSecurityConfiguration
13+
{
14+
public static IServiceCollection ConfigureApplicationSecurity(
15+
this IServiceCollection services,
16+
IConfiguration configuration)
17+
{
18+
services.AddSingleton<ICurrentUserService, CurrentUserService>();
19+
return services;
20+
}
21+
}
22+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System.Collections.Generic;
2+
using Intent.RoslynWeaver.Attributes;
3+
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions;
4+
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
5+
using Microsoft.OpenApi.Models;
6+
7+
[assembly: DefaultIntentManaged(Mode.Fully)]
8+
[assembly: IntentTemplate("Intent.AzureFunctions.OpenApi.OpenApiConfigurationTemplate", Version = "1.0")]
9+
10+
namespace AzureFunction.QueueStorage.Api.Configuration
11+
{
12+
public class OpenApiConfigurationOptions : IOpenApiConfigurationOptions
13+
{
14+
public OpenApiConfigurationOptions()
15+
{
16+
Info = new OpenApiInfo
17+
{
18+
Title = "AzureFunction.QueueStorage API",
19+
Version = "1.0.0"
20+
};
21+
}
22+
23+
public OpenApiInfo Info { get; set; }
24+
public List<OpenApiServer> Servers { get; set; } = new();
25+
public OpenApiVersionType OpenApiVersion { get; set; } = OpenApiVersionType.V3;
26+
public List<IDocumentFilter> DocumentFilters { get; set; }
27+
public bool IncludeRequestingHostName { get; set; } = false;
28+
public bool ForceHttp { get; set; } = true;
29+
public bool ForceHttps { get; set; } = false;
30+
}
31+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System;
2+
using System.Text.Json;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using System.Transactions;
6+
using AzureFunction.QueueStorage.Application.Common.Eventing;
7+
using AzureFunction.QueueStorage.Domain.Common.Interfaces;
8+
using AzureFunction.QueueStorage.Infrastructure.Eventing;
9+
using Intent.RoslynWeaver.Attributes;
10+
using Microsoft.Azure.Functions.Worker;
11+
using Microsoft.Extensions.Logging;
12+
13+
[assembly: DefaultIntentManaged(Mode.Fully)]
14+
[assembly: IntentTemplate("Intent.AzureFunctions.AzureQueueStorage.AzureFunctionConsumer", Version = "1.0")]
15+
16+
namespace AzureFunction.QueueStorage.Api
17+
{
18+
public class CreateProductCommandConsumer
19+
{
20+
private readonly JsonSerializerOptions _serializerOptions;
21+
private readonly IAzureQueueStorageEventDispatcher _dispatcher;
22+
private readonly ILogger<CreateProductCommandConsumer> _logger;
23+
private readonly IEventBus _eventBus;
24+
private readonly IServiceProvider _serviceProvider;
25+
private readonly IUnitOfWork _unitOfWork;
26+
27+
public CreateProductCommandConsumer(IAzureQueueStorageEventDispatcher dispatcher,
28+
ILogger<CreateProductCommandConsumer> logger,
29+
IEventBus eventBus,
30+
IServiceProvider serviceProvider,
31+
IUnitOfWork unitOfWork)
32+
{
33+
_dispatcher = dispatcher;
34+
_logger = logger;
35+
_eventBus = eventBus;
36+
_serviceProvider = serviceProvider;
37+
_serializerOptions = new JsonSerializerOptions
38+
{
39+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
40+
PropertyNameCaseInsensitive = true
41+
};
42+
_unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
43+
}
44+
45+
[Function("CreateProductCommandConsumer")]
46+
public async Task Run(
47+
[QueueTrigger("cleanarchitecture-queuestorage-eventing-messages-createproductcommand", Connection = "QueueStorage:DefaultEndpoint")] AzureQueueStorageEnvelope message,
48+
CancellationToken cancellationToken)
49+
{
50+
try
51+
{
52+
// The execution is wrapped in a transaction scope to ensure that if any other
53+
// SaveChanges calls to the data source (e.g. EF Core) are called, that they are
54+
// transacted atomically. The isolation is set to ReadCommitted by default (i.e. read-
55+
// locks are released, while write-locks are maintained for the duration of the
56+
// transaction). Learn more on this approach for EF Core:
57+
// https://docs.microsoft.com/en-us/ef/core/saving/transactions#using-systemtransactions
58+
using (var transaction = new TransactionScope(TransactionScopeOption.Required,
59+
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }, TransactionScopeAsyncFlowOption.Enabled))
60+
{
61+
await _dispatcher.DispatchAsync(_serviceProvider, message, _serializerOptions, cancellationToken);
62+
63+
// By calling SaveChanges at the last point in the transaction ensures that write-
64+
// locks in the database are created and then released as quickly as possible. This
65+
// helps optimize the application to handle a higher degree of concurrency.
66+
await _unitOfWork.SaveChangesAsync(cancellationToken);
67+
68+
// Commit transaction if everything succeeds, transaction will auto-rollback when
69+
// disposed if anything failed.
70+
transaction.Complete();
71+
}
72+
await _eventBus.FlushAllAsync(cancellationToken);
73+
}
74+
catch (Exception ex)
75+
{
76+
_logger.LogError(ex, "Error processing CreateProductCommandConsumer");
77+
throw;
78+
}
79+
}
80+
}
81+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System.Configuration;
2+
using System.Linq;
3+
using AzureFunction.QueueStorage.Api.Configuration;
4+
using AzureFunction.QueueStorage.Application;
5+
using AzureFunction.QueueStorage.Infrastructure;
6+
using Intent.RoslynWeaver.Attributes;
7+
using Microsoft.Azure.Functions.Worker;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Hosting;
10+
using Microsoft.Extensions.Logging;
11+
12+
[assembly: DefaultIntentManaged(Mode.Fully)]
13+
[assembly: IntentTemplate("Intent.AzureFunctions.Isolated.Program", Version = "1.0")]
14+
15+
var host = new HostBuilder()
16+
.ConfigureFunctionsWebApplication((ctx, builder) =>
17+
{
18+
})
19+
.ConfigureServices((ctx, services) =>
20+
{
21+
var configuration = ctx.Configuration;
22+
services.AddApplicationInsightsTelemetryWorkerService();
23+
services.ConfigureFunctionsApplicationInsights();
24+
services.Configure<LoggerFilterOptions>(options =>
25+
{
26+
// The Application Insights SDK adds a default logging filter that instructs ILogger to capture only Warning and more severe logs. Application Insights requires an explicit override.
27+
// Log levels can also be configured using appsettings.json. For more information, see https://learn.microsoft.com/en-us/azure/azure-monitor/app/worker-service#ilogger-logs
28+
const string applicationInsightsLoggerProvider = "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider";
29+
var toRemove = options.Rules.FirstOrDefault(rule => rule.ProviderName == applicationInsightsLoggerProvider);
30+
31+
if (toRemove is not null)
32+
{
33+
options.Rules.Remove(toRemove);
34+
}
35+
});
36+
services.AddApplication(configuration);
37+
services.ConfigureApplicationSecurity(configuration);
38+
services.AddInfrastructure(configuration);
39+
})
40+
.Build();
41+
42+
host.Run();

0 commit comments

Comments
 (0)