Skip to content

Commit

Permalink
merge from main
Browse files Browse the repository at this point in the history
  • Loading branch information
dncsvr committed Oct 24, 2024
2 parents bf48f63 + 4444b21 commit d29e94b
Show file tree
Hide file tree
Showing 38 changed files with 622 additions and 48 deletions.
44 changes: 44 additions & 0 deletions docs/features/reporting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Reporting

Implementations of this feature registers a singleton `IReportContext` with
which you can read raw data from database. Add this feature using
`AddReporting()` extension;

```csharp
app.Features.AddReporting(...);
```

## Fake

Adds a fake report context that allows you to return data directly from `.json`
resources.

```csharp
c => c.Fake(basePath: "Fake")
```

## Mock

Adds a mock instance of report context to be used during spec tests.

```csharp
c => c.Mock()
```

## Native SQL

Adds a report context instance that uses a `IStatelessSession` instance to
execute native SQL queries read from `.sql` resources in your project.

```csharp
c => c.NativeSql(basePath: "Queries/MySql")
```

> [!TIP]
>
> You may group your RDBMS specific queries in different folders, and use
> setting to specify which folder to use depending on environment.
>
> ```csharp
> c => c.NativeSql(basePath: Settings.Required("Reporting:NativeSql:BasePath"))
> ```
2 changes: 1 addition & 1 deletion docs/recipes/service.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Bake.New
| | With Method | |
| Communication | :white_check_mark: HTTP | :white_check_mark: Mock |
| Core | :white_check_mark: Dotnet | :white_check_mark: Mock |
| Cors(s) | :white_check_mark: Disabled | :no_entry: |
| Cors | :white_check_mark: Disabled | :no_entry: |
| Database | :white_check_mark: Sqlite | :white_check_mark: In Memory |
| Exception Handling | :white_check_mark: Default | :white_check_mark: |
| Greeting | :white_check_mark: Swagger | :no_entry: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,19 @@ public static class AssertionExtensions
public static void ShouldFail(this Spec _, string message = "") =>
throw new AssertionException(message);

[DoesNotReturn]
public static Task ShouldFailAsync(this Spec _, string message = "") =>
throw new AssertionException(message);

[DoesNotReturn]
public static void ShouldPass(this Spec _, string message = "") =>
Assert.Pass(message);

[DoesNotReturn]
public static Task ShouldPassAsync(this Spec _, string message = "")
{
Assert.Pass(message);

return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ protected override PhaseContext GetContext(AddServices phase)
services.AddSingleton(sp => _fluentConfiguration.BuildConfiguration());
services.AddSingleton(sp => sp.GetRequiredService<NHConfiguration>().BuildSessionFactory());
services.AddScoped(sp => sp.GetRequiredService<ISessionFactory>().OpenSession());
services.AddSingleton<Func<ISession>>(sp => () => sp.UsingCurrentScope().GetRequiredService<ISession>());
})
.Build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Baked.Database.InMemory;

public class InMemoryDatabaseFeature : IFeature<DatabaseConfigurator>
{
ISession? _globalSession;
IStatelessSession? _globalSession;

public void Configure(LayerConfigurator configurator)
{
Expand All @@ -28,7 +28,7 @@ public void Configure(LayerConfigurator configurator)
initializations.AddInitializer(sf =>
{
// In memory db is disposed when last connection is closed, this connection is to keep the db open
_globalSession = sf.OpenSession();
_globalSession = sf.OpenStatelessSession();
sp.GetRequiredService<Configuration>().ExportSchema(false, true, false, _globalSession.Connection);
});
Expand All @@ -48,6 +48,5 @@ public void Configure(LayerConfigurator configurator)
spec.GiveMe.TheSession().Clear();
});
});

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public void Configure(LayerConfigurator configurator)
configurator.ConfigureServiceCollection(services =>
{
services.AddFromAssembly(configurator.Context.GetGeneratedAssembly(nameof(AutoMapOrmFeature)));
services.AddScoped(sp => sp.GetRequiredService<ISessionFactory>().OpenSession());
services.AddSingleton<Func<ISession>>(sp => () => sp.UsingCurrentScope().GetRequiredService<ISession>());
services.AddScoped(typeof(IEntityContext<>), typeof(EntityContext<>));
services.AddSingleton(typeof(IQueryContext<>), typeof(QueryContext<>));
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Text.RegularExpressions;

namespace Baked.Reporting.Fake;

public record FakeData(
Dictionary<string, string?>? Parameters,
List<Dictionary<string, object?>> Result
)
{
public bool Matches(Dictionary<string, object> parameters)
{
if (Parameters is null) { return true; }

foreach (var (key, pattern) in Parameters)
{
if (!parameters.ContainsKey(key))
{
return false;
}

if (pattern is null) { continue; }
if (Regex.IsMatch($"{parameters[key]}", pattern)) { continue; }

return false;
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Baked.Reporting;
using Baked.Reporting.Fake;
using Baked.Testing;
using Microsoft.Extensions.FileProviders;

namespace Baked;

public static class FakeReportingExtensions
{
public static FakeReportingFeature Fake(this ReportingConfigurator _) =>
new();

public static IReportContext AFakeReportContext(this Stubber giveMe,
string basePath = "Fake"
) => new ReportContext(giveMe.The<IFileProvider>(), new(basePath));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Baked.Architecture;
using Microsoft.Extensions.DependencyInjection;

namespace Baked.Reporting.Fake;

public class FakeReportingFeature : IFeature<ReportingConfigurator>
{
public void Configure(LayerConfigurator configurator)
{
configurator.ConfigureServiceCollection(services =>
{
services.AddSingleton<IReportContext, ReportContext>();
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Microsoft.Extensions.FileProviders;
using Newtonsoft.Json;

namespace Baked.Reporting.Fake;

public class ReportContext(IFileProvider _fileProvider, ReportOptions _options)
: IReportContext
{
public async Task<object?[][]> Execute(string queryName, Dictionary<string, object> parameters)
{
var dataPath = $"/{Path.Join(_options.BasePath, $"{queryName}.json")}";
if (!_fileProvider.Exists(dataPath)) { throw new QueryNotFoundException(queryName); }

var dataString = await _fileProvider.ReadAsStringAsync(dataPath) ?? string.Empty;

var fakes = JsonConvert.DeserializeObject<List<FakeData>>(dataString) ?? new();
var match = fakes.FirstOrDefault(fake => fake.Matches(parameters));
if (match is null) { return []; }

return match.Result.Select(row => row.Values.ToArray()).ToArray();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Baked.Runtime;

namespace Baked.Reporting.Fake;

public record ReportOptions(Setting<string> BasePath);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Baked.Reporting;
using Baked.Reporting.Mock;

namespace Baked;

public static class MockReportingExtensions
{
public static MockReportingFeature Mock(this ReportingConfigurator _) =>
new();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Baked.Architecture;

namespace Baked.Reporting.Mock;

public class MockReportingFeature : IFeature<ReportingConfigurator>
{
public void Configure(LayerConfigurator configurator)
{
configurator.ConfigureTestConfiguration(test =>
{
test.Mocks.Add<IReportContext>(singleton: true);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Baked.Reporting;
using Baked.Reporting.NativeSql;
using Baked.Runtime;

namespace Baked;

public static class NativeSqlReportingExtensions
{
public static NativeSqlReportingFeature NativeSql(this ReportingConfigurator _,
Setting<string>? basePath = default
) => new(basePath ?? string.Empty);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Baked.Architecture;
using Baked.Runtime;
using Microsoft.Extensions.DependencyInjection;
using NHibernate;

namespace Baked.Reporting.NativeSql;

public class NativeSqlReportingFeature(Setting<string> _basePath)
: IFeature<ReportingConfigurator>
{
public void Configure(LayerConfigurator configurator)
{
configurator.ConfigureConfigurationBuilder(configuration =>
{
configuration.AddJsonAsDefault($$"""
{
"Logging": {
"LogLevel": {
"NHibernate": "None",
"NHibernate.Sql": "{{(configurator.IsDevelopment() ? "Debug" : "None")}}"
}
}
}
""");
});

configurator.ConfigureServiceCollection(services =>
{
services.AddSingleton(new ReportOptions(_basePath));
services.AddSingleton<IReportContext, ReportContext>();
services.AddScoped(sp => sp.GetRequiredService<ISessionFactory>().OpenStatelessSession());
services.AddSingleton<Func<IStatelessSession>>(sp => () => sp.UsingCurrentScope().GetRequiredService<IStatelessSession>());
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Microsoft.Extensions.FileProviders;

namespace Baked.Reporting.NativeSql;

public class ReportContext(IFileProvider _fileProvider, Func<NHibernate.IStatelessSession> _getStatelessSession, ReportOptions _options)
: IReportContext
{
public async Task<object?[][]> Execute(string queryName, Dictionary<string, object> parameters)
{
var queryPath = $"/{Path.Join(_options.BasePath, $"{queryName}.sql")}";
if (!_fileProvider.Exists(queryPath))
{
throw new QueryNotFoundException(queryName);
}

var queryString = await _fileProvider.ReadAsStringAsync(queryPath);
var query = _getStatelessSession().CreateSQLQuery(queryString);
foreach (var (name, value) in parameters)
{
query.SetParameter(name, value);
}

var result = await query.ListAsync();

return result.Cast<object[]>().ToArray();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Baked.Runtime;

namespace Baked.Reporting.NativeSql;

public record ReportOptions(Setting<string> BasePath);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Baked.Reporting;

public class ReportingConfigurator { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Baked.Architecture;
using Baked.Reporting;
using Baked.Testing;
using Moq;

namespace Baked;

public static class ReportingExtensions
{
public static void AddReporting(this List<IFeature> features, Func<ReportingConfigurator, IFeature<ReportingConfigurator>> configure) =>
features.Add(configure(new()));

public static IReportContext TheReportContext(this Mocker mockMe,
object?[][]? data = default
)
{
data ??= [];

var result = Mock.Get(mockMe.Spec.GiveMe.The<IReportContext>());

if (data is not null)
{
result
.Setup(df => df.Execute(It.IsAny<string>(), It.IsAny<Dictionary<string, object>>()))
.ReturnsAsync(data);
}

return result.Object;
}

public static void VerifyExecute(this IReportContext dataFetcher,
string? queryName = default,
(string key, object value)? parameter = default,
List<(string key, object value)>? parameters = default
)
{
parameters ??= parameter is not null ? [parameter.Value] : [];

Mock.Get(dataFetcher).Verify(
df => df.Execute(
It.Is<string>(q => queryName == null || q == queryName),
It.Is<Dictionary<string, object>>(p =>
parameters.All((kvp) => p.ContainsKey(kvp.key) && Equals(p[kvp.key], kvp.value))
)
)
);
}
}
6 changes: 6 additions & 0 deletions src/recipe/Baked.Recipe.Service/Reporting/IReportContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Baked.Reporting;

public interface IReportContext
{
Task<object?[][]> Execute(string queryName, Dictionary<string, object> parameters);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace Baked.Reporting;

public class QueryNotFoundException(string queryName)
: Exception($"No query file with '{queryName}' was found");
Loading

0 comments on commit d29e94b

Please sign in to comment.