-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathTemporaryTableEntity.cs
More file actions
241 lines (211 loc) · 13.8 KB
/
TemporaryTableEntity.cs
File metadata and controls
241 lines (211 loc) · 13.8 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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
using System;
using System.Net;
using System.Threading.Tasks;
using Azure;
using Azure.Data.Tables;
using Azure.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Arcus.Testing
{
/// <summary>
/// Represents a temporary Azure Table entity that will be deleted after the instance is disposed.
/// </summary>
public class TemporaryTableEntity : IAsyncDisposable
{
private const int NotFound = 404;
private readonly TableClient _tableClient;
private readonly Type _entityType;
private readonly bool _createdByUs;
private readonly ITableEntity _entity;
private readonly ILogger _logger;
private bool _isDisposed;
private TemporaryTableEntity(
TableClient tableClient,
Type entityType,
bool createdByUs,
ITableEntity originalEntity,
ILogger logger)
{
ArgumentNullException.ThrowIfNull(tableClient);
ArgumentNullException.ThrowIfNull(entityType);
ArgumentNullException.ThrowIfNull(originalEntity);
_tableClient = tableClient;
_entityType = entityType;
_createdByUs = createdByUs;
_entity = originalEntity;
_logger = logger ?? NullLogger.Instance;
}
/// <summary>
/// Creates a temporary entity in an Azure Table.
/// </summary>
/// <typeparam name="TEntity">A custom model type that implements <see cref="ITableEntity" /> or an instance of <see cref="TableEntity" />.</typeparam>
/// <param name="accountName">The name of the Azure Storage account where the table for the entity is located.</param>
/// <param name="tableName">The name of the Azure Table where the table entity should be added.</param>
/// <param name="entity">The entity to add to the Azure Table.</param>
/// <param name="logger">The logger instance to write diagnostic information during the lifetime of the test fixture.</param>
/// <exception cref="ArgumentException">Thrown when the <paramref name="accountName"/> or the <paramref name="tableName"/> is blank.</exception>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="entity"/> is <c>null</c>.</exception>
#pragma warning disable S1133 // Will be removed in v3.0.
[Obsolete("Will be removed in v3.0, please use the " + nameof(UpsertEntityAsync) + " instead which provides exactly the same functionality", DiagnosticId = ObsoleteDefaults.DiagnosticId)]
#pragma warning restore S1133
public static Task<TemporaryTableEntity> AddIfNotExistsAsync<TEntity>(
string accountName,
string tableName,
TEntity entity,
ILogger logger)
where TEntity : class, ITableEntity
{
return UpsertEntityAsync(accountName, tableName, entity, logger);
}
/// <summary>
/// Creates a temporary entity in an Azure Table.
/// </summary>
/// <typeparam name="TEntity">A custom model type that implements <see cref="ITableEntity" /> or an instance of <see cref="TableEntity" />.</typeparam>
/// <param name="client">The client to interact with the Azure Table resource.</param>
/// <param name="entity">The entity to add to the Azure Table.</param>
/// <param name="logger">The logger instance to write diagnostic information during the lifetime of the test fixture.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="client"/> or the <paramref name="entity"/> is <c>null</c>.</exception>
#pragma warning disable S1133 // Will be removed in v3.0.
[Obsolete("Will be removed in v3.0, please use the " + nameof(UpsertEntityAsync) + " instead which provides exactly the same functionality", DiagnosticId = ObsoleteDefaults.DiagnosticId)]
#pragma warning restore S1133
public static Task<TemporaryTableEntity> AddIfNotExistsAsync<TEntity>(TableClient client, TEntity entity, ILogger logger) where TEntity : class, ITableEntity
{
return UpsertEntityAsync(client, entity, logger);
}
/// <summary>
/// Creates a new or replaces an existing entity in an Azure Table.
/// </summary>
/// <remarks>
/// Based on the entity's <see cref="ITableEntity.PartitionKey" /> and <see cref="ITableEntity.RowKey" />,
/// an existing entity will be replaced or a new entity will be created.
/// </remarks>
/// <typeparam name="TEntity">A custom model type that implements <see cref="ITableEntity" /> or an instance of <see cref="TableEntity" />.</typeparam>
/// <param name="accountName">The name of the Azure Storage account where the table for the entity is located.</param>
/// <param name="tableName">The name of the Azure Table where the table entity should be added.</param>
/// <param name="entity">The entity to add to the Azure Table.</param>
/// <param name="logger">The logger instance to write diagnostic information during the lifetime of the test fixture.</param>
/// <exception cref="ArgumentException">Thrown when the <paramref name="accountName"/> or the <paramref name="tableName"/> is blank.</exception>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="entity"/> is <c>null</c>.</exception>
/// <exception cref="RequestFailedException">Thrown when the interaction with Azure Table Storage failed.</exception>
public static Task<TemporaryTableEntity> UpsertEntityAsync<TEntity>(
string accountName,
string tableName,
TEntity entity,
ILogger logger)
where TEntity : class, ITableEntity
{
ArgumentException.ThrowIfNullOrWhiteSpace(accountName);
ArgumentException.ThrowIfNullOrWhiteSpace(tableName);
var tableEndpoint = new Uri($"https://{accountName}.table.core.windows.net");
var tableClient = new TableClient(tableEndpoint, tableName, new DefaultAzureCredential());
return UpsertEntityAsync(tableClient, entity, logger);
}
/// <summary>
/// Creates a new or replaces an existing entity in an Azure Table.
/// </summary>
/// <remarks>
/// Based on the entity's <see cref="ITableEntity.PartitionKey" /> and <see cref="ITableEntity.RowKey" />,
/// an existing entity will be replaced or a new entity will be created.
/// </remarks>
/// <typeparam name="TEntity">A custom model type that implements <see cref="ITableEntity" /> or an instance of <see cref="TableEntity" />.</typeparam>
/// <param name="client">The client to interact with the Azure Table resource.</param>
/// <param name="entity">The entity to add to the Azure Table.</param>
/// <param name="logger">The logger instance to write diagnostic information during the lifetime of the test fixture.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="client"/> or the <paramref name="entity"/> is <c>null</c>.</exception>
/// <exception cref="RequestFailedException">Thrown when the interaction with Azure Table Storage failed.</exception>
public static async Task<TemporaryTableEntity> UpsertEntityAsync<TEntity>(TableClient client, TEntity entity, ILogger logger) where TEntity : class, ITableEntity
{
ArgumentNullException.ThrowIfNull(client);
ArgumentNullException.ThrowIfNull(entity);
logger ??= NullLogger.Instance;
Type entityType = typeof(TEntity);
NullableResponse<TEntity> entityExists =
await client.GetEntityIfExistsAsync<TEntity>(entity.PartitionKey, entity.RowKey).ConfigureAwait(false);
if (entityExists.HasValue)
{
ITableEntity originalEntity = entityExists.Value;
logger.LogSetupReplaceEntity(entityType.Name, entity.RowKey, entity.PartitionKey, client.AccountName, client.Name);
using Response response = await client.UpsertEntityAsync(entity, TableUpdateMode.Replace).ConfigureAwait(false);
if (response.IsError)
{
throw new RequestFailedException(
$"[Test:Setup] Failed to replace an existing Azure Table entity '{typeof(TEntity).Name}' (rowKey: '{entity.RowKey}', partitionKey: '{entity.PartitionKey}') in table '{client.AccountName}/{client.Name}' " +
$"since the upsert operation responded with failure: {response.Status} {(HttpStatusCode) response.Status}",
new RequestFailedException(response));
}
return new TemporaryTableEntity(client, entityType, createdByUs: false, originalEntity, logger);
}
else
{
logger.LogSetupAddNewEntity(entityType.Name, entity.RowKey, entity.PartitionKey, client.AccountName, client.Name);
using Response response = await client.AddEntityAsync(entity).ConfigureAwait(false);
if (response.IsError)
{
throw new RequestFailedException(
$"[Test:Setup] Failed to add a new Azure Table entity '{typeof(TEntity).Name}' (rowKey: '{entity.RowKey}', partitionKey: '{entity.PartitionKey}') in table '{client.AccountName}/{client.Name}' " +
$"since the add operation responded with failure: {response.Status} {(HttpStatusCode) response.Status}",
new RequestFailedException(response));
}
return new TemporaryTableEntity(client, entityType, createdByUs: true, entity, logger);
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously.
/// </summary>
/// <returns>A task that represents the asynchronous dispose operation.</returns>
public async ValueTask DisposeAsync()
{
if (_isDisposed)
{
return;
}
if (_createdByUs)
{
_logger.LogTeardownDeleteEntity(_entityType.Name, _entity.RowKey, _entity.PartitionKey, _tableClient.AccountName, _tableClient.Name);
using Response response = await _tableClient.DeleteEntityAsync(_entity).ConfigureAwait(false);
if (response.IsError && response.Status != NotFound)
{
throw new RequestFailedException(
$"[Test:Teardown] Failed to delete a Azure Table entity '{_entityType.Name}' (rowKey: '{_entity.RowKey}', partitionKey: '{_entity.PartitionKey}') in table '{_tableClient.AccountName}/{_tableClient.Name}' " +
$"since the delete operation responded with failure: {response.Status} {(HttpStatusCode) response.Status}",
new RequestFailedException(response));
}
}
else
{
_logger.LogTeardownRevertEntity(_entityType.Name, _entity.RowKey, _entity.PartitionKey, _tableClient.AccountName, _tableClient.Name);
using Response response = await _tableClient.UpsertEntityAsync(_entity, TableUpdateMode.Replace).ConfigureAwait(false);
if (response.IsError)
{
throw new RequestFailedException(
$"[Test:Teardown] Failed to revert a Azure Table entity '{_entityType.Name}' (rowKey: '{_entity.RowKey}', partitionKey: '{_entity.PartitionKey}') in table '{_tableClient.AccountName}/{_tableClient.Name}' " +
$"since the upsert operation responded with failure: {response.Status} {(HttpStatusCode) response.Status}",
new RequestFailedException(response));
}
}
_isDisposed = true;
GC.SuppressFinalize(this);
}
}
internal static partial class TempTableEntityILoggerExtensions
{
private const LogLevel SetupTeardownLogLevel = LogLevel.Debug;
[LoggerMessage(
Level = SetupTeardownLogLevel,
Message = "[Test:Setup] Add new Azure Table entity '{EntityType}' (rowKey: '{RowKey}', partitionKey: '{PartitionKey}') in table '{AccountName}/{TableName}'")]
internal static partial void LogSetupAddNewEntity(this ILogger logger, string entityType, string rowKey, string partitionKey, string accountName, string tableName);
[LoggerMessage(
Level = SetupTeardownLogLevel,
Message = "[Test:Setup] Replace already existing Azure Table entity '{EntityType}' (rowKey: '{RowKey}', partitionKey: '{PartitionKey}') in table '{AccountName}/{TableName}'")]
internal static partial void LogSetupReplaceEntity(this ILogger logger, string entityType, string rowKey, string partitionKey, string accountName, string tableName);
[LoggerMessage(
Level = SetupTeardownLogLevel,
Message = "[Test:Teardown] Delete Azure Table entity '{EntityType}' (rowKey: '{RowKey}', partitionKey: '{PartitionKey}') from table '{AccountName}/{TableName}'")]
internal static partial void LogTeardownDeleteEntity(this ILogger logger, string entityType, string rowKey, string partitionKey, string accountName, string tableName);
[LoggerMessage(
Level = SetupTeardownLogLevel,
Message = "[Test:Teardown] Revert replaced Azure Table entity '{EntityType}' (rowKey: '{RowKey}', partitionKey: '{PartitionKey}') from table '{AccountName}/{TableName}'")]
internal static partial void LogTeardownRevertEntity(this ILogger logger, string entityType, string rowKey, string partitionKey, string accountName, string tableName);
}
}