-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathDatabaseValidationHelper.cs
More file actions
336 lines (292 loc) · 12.4 KB
/
DatabaseValidationHelper.cs
File metadata and controls
336 lines (292 loc) · 12.4 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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
using ChoETL;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Logging;
using ILogger = Microsoft.Extensions.Logging.ILogger;
using Azure.Identity;
using Azure.Core;
namespace NHS.CohortManager.SmokeTests.Helpers;
public static class DatabaseValidationHelper
{
private static readonly HashSet<string> AllowedTables = new HashSet<string>
{
"BS_COHORT_DISTRIBUTION",
"PARTICIPANT_MANAGEMENT",
"PARTICIPANT_DEMOGRAPHIC",
"EXCEPTION_MANAGEMENT"
};
private static readonly HashSet<string> AllowedFields =
[
"NHS_NUMBER",
"GIVEN_NAME",
"RULE_ID",
"RULE_DESCRIPTION"
// Add other allowed fields here
];
private static void ValidateTableName(string tableName)
{
if (!AllowedTables.Contains(tableName.ToUpper()))
{
throw new ArgumentException($"Table '{tableName}' is not in the list of allowed tables.");
}
}
private static void ValidateFieldName(string fieldName)
{
if (!AllowedFields.Contains(fieldName.ToUpper()))
{
throw new ArgumentException($"Field '{fieldName}' is not in the list of allowed fields.");
}
}
public static async Task VerifyNhsNumbersAsync(
SqlConnectionWithAuthentication sqlConnectionWithAuthentication,
string tableName,
List<string> nhsNumbers,
string recordType = null)
{
ValidateTableName(tableName);
using (var connection = await sqlConnectionWithAuthentication.GetOpenConnectionAsync())
{
foreach (var nhsNumber in nhsNumbers)
{
var isVerified = await VerifyNhsNumberAsync(connection, tableName, nhsNumber, recordType);
if (!isVerified)
{
string errorMessage = $"Verification failed: NHS number {nhsNumber} not found in {tableName} table";
if (!string.IsNullOrEmpty(recordType))
{
errorMessage += $" with record type {recordType}";
}
Assert.Fail(errorMessage);
}
}
}
}
public static async Task<bool> VerifyFieldUpdateAsync(SqlConnectionWithAuthentication sqlConnectionWithAuthentication, string tableName, string nhsNumber, string fieldName, string expectedValue, ILogger logger)
{
List<string> fieldValues = new List<string>();
ValidateTableName(tableName);
ValidateFieldName(fieldName);
using (var connection = await sqlConnectionWithAuthentication.GetOpenConnectionAsync())
{
var query = $"SELECT {fieldName} FROM {tableName} WHERE [NHS_NUMBER] = @NhsNumber";
using (var command = new SqlCommand(query, connection))
{
command.Parameters.AddWithValue("@NhsNumber", nhsNumber);
using (SqlDataReader reader = await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
var value = reader.IsDBNull(0) ? null : reader.GetValue(0);
if (value != null)
{
if (value is int intValue)
fieldValues.Add(intValue.ToString());
else
fieldValues.Add(value.ToString()!);
}
}
}
if (fieldValues.Count == 0)
{
logger.LogError($"Field {fieldName} is null for NHS number {nhsNumber} in {tableName} table.");
return false;
}
if (!fieldValues.Contains(expectedValue))
{
logger.LogError($"Field {fieldName} for NHS number {nhsNumber} does not match the expected value. Expected: {expectedValue}, Actual: {fieldValues.FirstOrDefault()}");
return false;
}
logger.LogInformation($"Field {fieldName} for NHS number {nhsNumber} successfully updated to {expectedValue}.");
return true;
}
}
}
public static async Task<bool> VerifyRecordCountAsync(string connectionString, string tableName, int expectedCount, ILogger logger, int maxRetries = 10, int delay = 1000)
{
ValidateTableName(tableName);
for (int i = 0; i < maxRetries; i++)
{
using (var connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
var query = $"SELECT COUNT(*) FROM {tableName}";
using (var command = new SqlCommand(query, connection))
{
var count = (int)await command.ExecuteScalarAsync();
if (count == expectedCount)
{
logger.LogInformation($"Database record count verified for {tableName}: {count}");
return true;
}
logger.LogInformation($"Database record count not yet updated for {tableName}, retrying... ({i + 1}/{maxRetries})");
await Task.Delay(delay);
}
}
}
logger.LogError($"Failed to verify record count for {tableName} after {maxRetries} retries.");
return false;
}
private static async Task<bool> VerifyNhsNumberAsync(
SqlConnection connection,
string tableName,
string nhsNumber,
string recordType = null)
{
int retryCount = 0;
const int maxRetries = 8;
TimeSpan delay = TimeSpan.FromSeconds(5); // Initial delay
while (retryCount < maxRetries)
{
try
{
string sql = $"SELECT 1 FROM {tableName} WHERE NHS_Number = @nhsNumber";
if (!string.IsNullOrEmpty(recordType))
{
sql += " AND RECORD_TYPE = @recordType";
}
using var command = new SqlCommand(sql, connection);
command.Parameters.AddWithValue("@nhsNumber", nhsNumber);
if (!string.IsNullOrEmpty(recordType))
{
command.Parameters.AddWithValue("@recordType", recordType);
}
var result = await command.ExecuteScalarAsync();
if (result != null)
{
return true;
}
await Task.Delay(delay);
delay *= 2; // Double the delay for the next retry attempt
retryCount++;
}
catch (Exception ex)
{
// Handle the exception and decide whether to retry
if (retryCount < maxRetries - 1)
{
// Wait for the delay before retrying
await Task.Delay(delay);
delay *= 2; // Double the delay for the next retry attempt
retryCount++;
}
else
{
throw new Exception($"Failed to verify NHS number after {maxRetries} attempts.", ex);
}
}
}
return false;
}
public static async Task<bool> VerifyFieldsMatchCsvAsync(string connectionString, string tableName, string nhsNumber, string csvFilePath, ILogger logger)
{
ValidateTableName(tableName);
var csvRecords = CsvHelperService.ReadCsv(csvFilePath);
var expectedRecord = csvRecords.FirstOrDefault(record => record["NHS Number"] == nhsNumber);
if (expectedRecord == null)
{
logger.LogError($"NHS number {nhsNumber} not found in the CSV file.");
return false;
}
using (var connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
var query = $"SELECT * FROM {tableName} WHERE [NHS_NUMBER] = @NhsNumber";
using (var command = new SqlCommand(query, connection))
{
command.Parameters.AddWithValue("@NhsNumber", nhsNumber);
using (var reader = await command.ExecuteReaderAsync())
{
if (!reader.HasRows)
{
logger.LogError($"No record found in {tableName} for NHS number {nhsNumber}.");
return false;
}
while (await reader.ReadAsync())
{
foreach (var key in expectedRecord.Keys)
{
var expectedValue = expectedRecord[key];
var actualValue = reader[key]?.ToString();
if (expectedValue != actualValue)
{
logger.LogError($"Mismatch in {key} for NHS number {nhsNumber}: expected '{expectedValue}', found '{actualValue}'.");
return false;
}
}
}
}
}
}
return true;
}
public static async Task<bool> VerifyFieldsMatchParquetAsync(string connectionString, string tableName, string nhsNumber, string parquetFilePath, ILogger logger)
{
ValidateTableName(tableName);
var parquetRecords = ReadParquetFile(parquetFilePath);
var expectedRecord = parquetRecords.FirstOrDefault(record => record["NHS Number"]?.ToString() == nhsNumber);
if (expectedRecord == null)
{
logger.LogError($"NHS number {nhsNumber} not found in the Parquet file.");
return false;
}
using (var connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
var query = $"SELECT * FROM {tableName} WHERE [NHS_NUMBER] = @NhsNumber";
using (var command = new SqlCommand(query, connection))
{
command.Parameters.AddWithValue("@NhsNumber", nhsNumber);
using (var reader = await command.ExecuteReaderAsync())
{
if (!reader.HasRows)
{
logger.LogError($"No record found in {tableName} for NHS number {nhsNumber}.");
return false;
}
while (await reader.ReadAsync())
{
foreach (var key in expectedRecord.Keys)
{
var expectedValue = expectedRecord[key]?.ToString();
var actualValue = reader[key]?.ToString();
if (expectedValue != actualValue)
{
logger.LogError($"Mismatch in {key} for NHS number {nhsNumber}: expected '{expectedValue}', found '{actualValue}'.");
return false;
}
}
}
}
}
}
return true;
}
public static async Task<int> GetNhsNumberCount(SqlConnectionWithAuthentication sqlConnectionWithAuthentication, string tableName, string nhsNumber, ILogger logger)
{
int nhsNumberCount = 0;
// Get the open connection (with token if using Managed Identity)
using (var connection = await sqlConnectionWithAuthentication.GetOpenConnectionAsync())
{
var query = $"SELECT COUNT(*) FROM {tableName} WHERE [NHS_NUMBER] = @NhsNumber";
// Create SQL command and add parameter for NHS Number
using (var command = new SqlCommand(query, connection))
{
command.Parameters.AddWithValue("@NhsNumber", nhsNumber);
// Execute the query and get the count of NHS numbers
nhsNumberCount = (int)(await command.ExecuteScalarAsync() ?? 0);
}
}
return nhsNumberCount;
}
private static List<IDictionary<string, object>> ReadParquetFile(string parquetFilePath)
{
var records = new List<IDictionary<string, object>>();
using (var reader = new ChoParquetReader(parquetFilePath))
{
foreach (var record in reader)
{
records.Add((IDictionary<string, object>)record);
}
}
return records;
}
}