Repository navigation
Expand file tree
/
Copy pathUnitTestExExtensions.SqlServer.cs
More file actions
166 lines (148 loc) · 11.9 KB
/
Copy pathUnitTestExExtensions.SqlServer.cs
File metadata and controls
166 lines (148 loc) · 11.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#pragma warning disable IDE0130 // Namespace does not match folder structure; by design.
namespace UnitTestEx;
#pragma warning restore IDE0130 // Namespace does not match folder structure
public static partial class UnitTestExExtensions
{
/// <summary>
/// Replaces the registered <see cref="IEventPublisher"/> with a decorator (<see cref="EventPublisherDecorator"/>) that also captures the published events for expectation assertions.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="serviceKey">The service key for the previously registered <see cref="IEventPublisher"/>.</param>
/// <param name="bypassPassThrough">Indicates whether to bypass the pass-through to the original event publisher.</param>
/// <returns>The <see cref="IServiceCollection"/> to support fluent-style method-chaining.</returns>
/// <remarks>This is a convenience method that defaults the <paramref name="serviceKey"/> to <see cref="SqlServerOutboxPublisher.DefaultServiceKey"/> where invoking the underlying <see cref="UseExpectedEventPublisher(IServiceCollection, string, bool)"/>.
/// <para>The <paramref name="bypassPassThrough"/> when set to <see langword="true"/> will bypass the pass-through to the original event publisher and leverage the <see cref="NoOpEventPublisher"/> instead.</para></remarks>
public static IServiceCollection UseExpectedSqlServerOutboxPublisher(this IServiceCollection services, string serviceKey = SqlServerOutboxPublisher.DefaultServiceKey, bool bypassPassThrough = false)
=> UseExpectedEventPublisher(services, serviceKey, bypassPassThrough);
/// <summary>
/// Replaces the registered <see cref="IEventPublisher"/> with a decorator (<see cref="EventPublisherDecorator"/>) that also captures the published events for expectation assertions; whilst also adding post-run expectations for the captured events.
/// </summary>
/// <typeparam name="TEntryPoint">The API startup <see cref="Type"/>.</typeparam>
/// <param name="tester">The <see cref="AspNetCore.ApiTester{TEntryPoint}"/>.</param>
/// <param name="serviceKey">The service key for the previously registered <see cref="IEventPublisher"/>.</param>
/// <param name="bypassPassThrough">Indicates whether to bypass the pass-through to the original event publisher.</param>
/// <param name="expectNoEvents">Indicates whether to expect no events to be published.</param>
/// <returns>The <see cref="AspNetCore.ApiTester{TEntryPoint}"/> instance to support fluent-style method-chaining.</returns>
/// <remarks>The <paramref name="expectNoEvents"/> parameter is only actioned when no explicit event expectations are defined for the underlying test; acts as a catch all.</remarks>
public static AspNetCore.ApiTester<TEntryPoint> UseExpectedSqlServerOutboxPublisher<TEntryPoint>(this AspNetCore.ApiTester<TEntryPoint> tester, string serviceKey = SqlServerOutboxPublisher.DefaultServiceKey, bool bypassPassThrough = false, bool expectNoEvents = true) where TEntryPoint : class
=> tester.ConfigureServices(services => services.UseExpectedSqlServerOutboxPublisher(serviceKey, bypassPassThrough))
.AddEventExpectationsPostRun(serviceKey, expectNoEvents);
/// <summary>
/// Execute the <see cref="SqlServerMigration"/> using the <typeparamref name="TAssembly"/> to include additional resource.
/// </summary>
/// <typeparam name="TAssembly">The <see cref="Type"/> to infer the underlying <see cref="Assembly"/>.</typeparam>
/// <param name="tester">The <see cref="TesterBase"/>.</param>
/// <param name="configureMigrationArgs">The function to further configure the <see cref="MigrationArgs"/>.</param>
/// <param name="connectionString">The database connection string configuration key.</param>
/// <remarks>Where the migration is unsuccessful then an <see cref="TestFrameworkImplementor.AssertFail(string?)"/> will be automatically issued.
/// <para>The <paramref name="connectionString"/> supports the retrieval of the value from <see cref="TesterBase.Configuration"/> where prefixed with '<c>config:</c>' or '<c>^</c>', or is wrapped with '<c>%</c>'.</para></remarks>
public static Task MigrateSqlServerDataAsync<TAssembly>(this TesterBase tester, Func<MigrationArgs, MigrationArgs>? configureMigrationArgs = null, string connectionString = "^Aspire:Microsoft:Data:SqlClient:ConnectionString")
=> MigrateSqlServerDataAsync(tester, configureMigrationArgs, connectionString, typeof(TAssembly).Assembly);
/// <summary>
/// Execute the <see cref="SqlServerMigration"/> using the <typeparamref name="TAssembly"/> to include additional resource.
/// </summary>
/// <typeparam name="TAssembly">The <see cref="Type"/> to infer the underlying <see cref="Assembly"/>.</typeparam>
/// <param name="tester">The <see cref="TesterBase"/>.</param>
/// <param name="resourceFileNames">The resource file names to include in the data loading; see <see cref="DataParserArgs.AddNamed{TAssembly}(string[])"/>.</param>
/// <param name="configureMigrationArgs">The function to further configure the <see cref="MigrationArgs"/>.</param>
/// <param name="connectionString">The database connection string configuration key.</param>
/// <remarks>Where the migration is unsuccessful then an <see cref="TestFrameworkImplementor.AssertFail(string?)"/> will be automatically issued.
/// <para>The <paramref name="connectionString"/> supports the retrieval of the value from <see cref="TesterBase.Configuration"/> where prefixed with '<c>config:</c>' or '<c>^</c>', or is wrapped with '<c>%</c>'.</para></remarks>
public static Task MigrateSqlServerDataAsync<TAssembly>(this TesterBase tester, string[] resourceFileNames, Func<MigrationArgs, MigrationArgs>? configureMigrationArgs = null, string connectionString = "^Aspire:Microsoft:Data:SqlClient:ConnectionString")
{
if (configureMigrationArgs is null)
{
return MigrateSqlServerDataAsync<TAssembly>(tester, ma =>
{
ma.DataParserArgs.AddNamed<TAssembly>(resourceFileNames);
return ma;
}, connectionString);
}
else
{
return MigrateSqlServerDataAsync<TAssembly>(tester, ma =>
{
var cma = configureMigrationArgs(ma);
cma.DataParserArgs.AddNamed<TAssembly>(resourceFileNames);
return cma;
}, connectionString);
}
}
/// <summary>
/// Execute the <see cref="SqlServerMigration"/>.
/// </summary>
/// <param name="tester">The <see cref="TesterBase"/>.</param>
/// <param name="configureMigrationArgs">The function to further configure the <see cref="MigrationArgs"/>.</param>
/// <param name="connectionString">The database connection string configuration key.</param>
/// <param name="assemblies">Zero or more assemblies to add to the migration args (see <see cref="MigrationArgsBase{TSelf}.AddAssembly(Assembly[])"/>).</param>
/// <remarks>Where the migration is unsuccessful then an <see cref="TestFrameworkImplementor.AssertFail(string?)"/> will be automatically issued.
/// <para>The <paramref name="connectionString"/> supports the retrieval of the value from <see cref="TesterBase.Configuration"/> where prefixed with '<c>config:</c>' or '<c>^</c>', or is wrapped with '<c>%</c>'.</para></remarks>
public static async Task MigrateSqlServerDataAsync(this TesterBase tester, Func<MigrationArgs, MigrationArgs>? configureMigrationArgs = null, string connectionString = "^Aspire:Microsoft:Data:SqlClient:ConnectionString", params Assembly[] assemblies)
{
// Determine the connection string and configure the migration args.
var cs = CoreEx.Abstractions.Internal.GetValueFromConfigurationWhereApplicable(connectionString.ThrowIfNullOrEmpty(), tester.ThrowIfNull().Configuration);
var ma = new MigrationArgs(_connectionStrings.TryAdd(cs, 0) ? MigrationCommand.All | MigrationCommand.ResetAndData : MigrationCommand.ResetAndData, cs);
if (configureMigrationArgs is not null)
ma = configureMigrationArgs(ma);
if (assemblies.Length > 0)
ma.AddAssembly(assemblies);
// Execute the sql-server migration.
using var m = new SqlServerMigration(ma);
var (Success, Output) = await m.MigrateAndLogAsync().ConfigureAwait(false);
if (!Success)
tester.Implementor.AssertFail("SqlServerMigration failed:" + Environment.NewLine + Output);
}
#region Aspire
/// <summary>
/// Execute the <see cref="SqlServerMigration"/> using the specified <paramref name="aspireResourceName"/> to retrieve the connection string from the distributed application.
/// </summary>
/// <typeparam name="TAssembly">The <see cref="Type"/> to infer the underlying <see cref="Assembly"/>.</typeparam>
/// <param name="app">The <see cref="DistributedApplication"/>.</param>
/// <param name="aspireResourceName">The name of the Aspire resource to retrieve the connection string for.</param>
/// <param name="resourceFileNames">The resource file names to include in the data loading; see <see cref="DataParserArgs.AddNamed{TAssembly}(string[])"/>.</param>
/// <param name="configureMigrationArgs">An optional function to configure the <see cref="MigrationArgs"/>.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public static Task MigrateSqlServerDataAsync<TAssembly>(this DistributedApplication app, string aspireResourceName, string[]? resourceFileNames = null, Func<MigrationArgs, MigrationArgs>? configureMigrationArgs = null)
{
resourceFileNames ??= [];
if (configureMigrationArgs is null)
{
return MigrateSqlServerDataAsync(app, aspireResourceName, ma =>
{
ma.DataParserArgs.AddNamed<TAssembly>(resourceFileNames);
return ma;
}, typeof(TAssembly).Assembly);
}
else
{
return MigrateSqlServerDataAsync(app, aspireResourceName, ma =>
{
var cma = configureMigrationArgs(ma);
cma.DataParserArgs.AddNamed<TAssembly>(resourceFileNames);
return cma;
}, typeof(TAssembly).Assembly);
}
}
/// <summary>
/// Execute the <see cref="SqlServerMigration"/> using the specified <paramref name="aspireResourceName"/> to retrieve the connection string from the distributed application.
/// </summary>
/// <param name="app">The <see cref="DistributedApplication"/>.</param>
/// <param name="aspireResourceName">The name of the Aspire resource to retrieve the connection string for.</param>
/// <param name="configureMigrationArgs">An optional function to configure the <see cref="MigrationArgs"/>.</param>
/// <param name="assemblies">An optional array of assemblies to include in the migration.</param>
public static async Task MigrateSqlServerDataAsync(this DistributedApplication app, string aspireResourceName, Func<MigrationArgs, MigrationArgs>? configureMigrationArgs = null, params Assembly[] assemblies)
{
var cs = (await AspireTesterBase.GetConnectionStringAsync(app.ThrowIfNull(), aspireResourceName.ThrowIfNullOrEmpty()).ConfigureAwait(false)) ?? throw new InvalidOperationException($"The '{aspireResourceName}' connection string not found.");
var ma = new MigrationArgs(MigrationCommand.All | MigrationCommand.ResetAndData, cs);
if (configureMigrationArgs is not null)
ma = configureMigrationArgs(ma);
if (assemblies.Length > 0)
ma.AddAssembly(assemblies);
// Execute the sql-server migration.
using var m = new SqlServerMigration(ma);
var (Success, Output) = await m.MigrateAndLogAsync().ConfigureAwait(false);
if (!Success)
throw new InvalidOperationException("SqlServerMigration failed:" + Environment.NewLine + Output);
}
#endregion
}