This commit is contained in:
2026-04-17 14:55:32 -04:00
commit bc3ac1d4c9
18017 changed files with 4371742 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
---
source_files:
- DataPRO/UnitTest/DatabaseUnitTesting/Properties/Settings.Designer.cs
generated_at: "2026-04-16T03:51:52.660365+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "f8ee505a9bbaa788"
---
# Properties
## 1. Purpose
This module defines strongly-typed application and user settings for the `DatabaseUnitTesting` project, specifically managing database connection and naming configuration used during unit testing. It leverages .NETs `ApplicationSettingsBase` to provide compile-time-safe access to configuration values—such as the test database connection string, database name, and snapshot name—enabling consistent and maintainable test environment setup without hardcoding values.
## 2. Public Interface
The class `Settings` (fully qualified: `DatabaseUnitTesting.Properties.Settings`) exposes the following members:
- **`public static Settings Default { get; }`**
A thread-safe singleton accessor for the settings instance. Returns the single shared `Settings` object used throughout the application.
- **`public string ConnectionString { get; }`**
An *application-scoped* read-only string property providing the SQL Server connection string for the unit test database. Default value:
`"Data Source=FAJITA\\DEV_SQL;Initial Catalog=DataPRO_UnitTest;Integrated Security=False;Persist Security Info=True;User ID=sa;Password=!!QQAAZZxxssww22;Connect Timeout=15;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"`
Marked with `SpecialSettingAttribute.SpecialSetting.ConnectionString`, indicating it is intended for ADO.NET connection usage.
- **`public string DBName { get; set; }`**
A *user-scoped* read-write string property for the target database name used in tests. Default value: `"DataPRO_UnitTest"`.
- **`public string DBSnapshot { get; set; }`**
A *user-scoped* read-write string property for the database snapshot name used during testing (likely for restoring state between test runs). Default value: `"DataPRO_UnitTestSnapshot"`.
## 3. Invariants
- The `ConnectionString` property is **application-scoped** and immutable at runtime—its value cannot be changed programmatically after initialization.
- `DBName` and `DBSnapshot` are **user-scoped** and mutable; changes persist only for the current user and require explicit saving (e.g., via `Settings.Default.Save()`) to be persisted across sessions.
- The `Default` property returns a synchronized (thread-safe) instance, implying safe concurrent read access.
- The `ConnectionString` value is validated at design time by the Settings Designer as a well-formed connection string, but no runtime validation is present in this file.
## 4. Dependencies
- **Depends on**:
- `System.Configuration` (for `ApplicationSettingsBase`, attributes like `ApplicationScopedSettingAttribute`, `SpecialSettingAttribute`, etc.)
- `System.Diagnostics` (for `DebuggerNonUserCodeAttribute`)
- `System.Runtime.CompilerServices` (for `CompilerGeneratedAttribute`)
- **Used by**: Any code in the `DatabaseUnitTesting` project (or referencing it) that needs to access test database configuration—e.g., test setup/teardown logic, database initialization utilities, or test harnesses that restore snapshots or connect to the test database.
## 5. Gotchas
- **Hardcoded credentials**: The default `ConnectionString` embeds a plaintext password (`User ID=sa;Password=!!QQAAZZxxssww22`). This is a security risk and should be replaced with secure credential storage (e.g., Windows Credential Manager, Azure Key Vault) in production or shared environments.
- **Assumed SQL Server instance**: The `Data Source=FAJITA\DEV_SQL` implies a specific named instance; this will fail on machines without that instance unless overridden in user settings or config.
- **`Integrated Security=False`**: Explicit use of SQL authentication (not Windows auth) may not align with security policies in some environments.
- **No validation logic**: The settings class does not validate that `DBName` or `DBSnapshot` refer to actual databases or that `DBSnapshot` is a valid snapshot name. Errors will surface only at runtime during database operations.
- **Auto-generated file**: As indicated by the `<auto-generated>` header, manual edits will be overwritten on regeneration (e.g., after modifying settings in Visual Studios designer).

View File

@@ -0,0 +1,185 @@
---
source_files:
- DataPRO/UnitTest/DatabaseUnitTesting/Utilities/ObjectsToCompare.cs
- DataPRO/UnitTest/DatabaseUnitTesting/Utilities/DatabaseAdapter.cs
- DataPRO/UnitTest/DatabaseUnitTesting/Utilities/DatabaseComparer.cs
generated_at: "2026-04-16T03:52:03.248634+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "b192c520f6919014"
---
# Utilities
## Documentation: `DatabaseUnitTesting.Utilities` Module
---
### 1. Purpose
This module provides core infrastructure for comparing database schemas and data between two SQL Server databases as part of unit testing workflows. It enables programmatic definition of object pairs (e.g., tables or views) to compare, dynamic column exclusion, and execution of set-based data difference queries using SQL `EXCEPT`. The module is internal to the unit testing framework and is not intended for general-purpose database access; its primary consumers are test runners that validate expected data consistency across environments (e.g., baseline vs. modified database states).
---
### 2. Public Interface
> **Note**: All types and members are `internal`, not `public`. They are accessible only within the `DatabaseUnitTesting` assembly.
#### `ObjectsToCompare` class
Represents a pair of database objects (e.g., tables) to compare, with optional column exclusions.
- **Constructor**
```csharp
internal ObjectsToCompare(string schema1Name, string object1Name, string schema2Name, string object2Name)
```
Initializes a comparison pair. All arguments are non-null strings identifying the schema and object name for each side of the comparison.
- **Properties**
```csharp
internal string Schema1Name { get; }
internal string Object1Name { get; }
internal string Schema2Name { get; }
internal string Object2Name { get; }
internal string Qualified1 { get; } // Returns "schema1.object1"
internal string Qualified2 { get; } // Returns "schema2.object2"
internal IEnumerable<string> ColumnsToIgnore { get; }
```
- **Methods**
```csharp
internal void AddColumnToIgnore(string columnName)
```
Adds a column name (case-insensitive) to the list of columns to exclude from comparison.
---
#### `DatabaseAdapter` class
Encapsulates low-level SQL Server operations for snapshot management and database switching.
- **Constructor**
```csharp
internal DatabaseAdapter(SqlConnection connection)
```
Wraps an existing `SqlConnection`. Automatically opens the connection if closed.
- **Methods**
```csharp
internal bool IsSnapshot(string name)
```
Returns `true` if `name` refers to a database snapshot (checks `sys.databases.source_database_id`); `false` otherwise.
```csharp
internal void UseDatabase(string databaseName)
```
Executes `USE [databaseName]` on the connection.
```csharp
internal void CreateSnapshot(string databaseName, string snapshotName)
```
Creates a snapshot of `databaseName` at a fixed path: `C:\Temp\{snapshotName}`.
⚠️ **Hardcoded path**: Assumes `C:\Temp\` exists and is writable.
```csharp
internal void DropSnapshot(string snapshotName)
```
Drops the snapshot `snapshotName`. Throws `ArgumentException` if no such snapshot exists.
---
#### `DatabaseComparer` class
Orchestrates comparison of database objects between two databases.
- **Constructor**
```csharp
internal DatabaseComparer(SqlConnection connection, string databaseOneName, string databaseTwoName)
```
Initializes the comparer. Switches the connection context to `databaseOneName` via `DatabaseAdapter.UseDatabase`.
- **Properties & Fields**
- `_connection`: Underlying `SqlConnection`.
- `_databaseAdapter`: Internal `DatabaseAdapter` instance.
- `_databaseOne`, `_databaseTwo`: Names of the two databases being compared.
- **Methods**
```csharp
internal void CleanUp()
```
Clears the list of registered object comparisons (`_objectsToCompare`).
```csharp
internal void AddObjectComparison(string schema1, string object1, string schema2, string object2)
```
Registers a new object pair for comparison by creating and adding an `ObjectsToCompare` instance.
```csharp
internal void AddColumnToIgnore(string schemaName, string objectName, string columnName)
```
Adds a column to ignore for the first object matching `(schemaName, objectName)`.
⚠️ **Assumes uniqueness**: Uses `List.Find`, so only the *first* match is updated.
```csharp
internal void AddColumnsToIgnore(string schemaName, string tableName, List<string> columnNames)
```
Adds multiple columns to ignore for the same object.
```csharp
internal string GetAllColumns(SqlTransaction transaction, ObjectsToCompare objects)
```
Queries `INFORMATION_SCHEMA.COLUMNS` for `objects.Qualified1` in `_databaseOne`.
- Validates that the object exists (throws `ArgumentException` if not).
- Removes columns listed in `objects.ColumnsToIgnore`.
- Throws `ArgumentException` if a specified column is not found or if all columns are ignored.
```csharp
internal Table RunCompare(SqlTransaction transaction, ObjectsToCompare objectComparison)
```
Executes a data comparison between `objectComparison.Qualified1` and `objectComparison.Qualified2`.
- Uses `SELECT ... EXCEPT SELECT ...` to find rows present in one side but not the other.
- Adds a `TempRowNumber` column (via `ROW_NUMBER() OVER(PARTITION BY ... ORDER BY @@SPID)`) to handle duplicates.
- Iterates over result sets (first = "In First", second = "In Second") to populate a `Table` object.
- Column names are normalized to lowercase in the resulting `Column` objects.
```csharp
internal Database GenerateDifferences(SqlTransaction transaction)
```
Compares all registered objects and returns a `Database` object containing non-empty `Table` results.
- Skips tables with zero rows in the result set.
---
### 3. Invariants
- **Object existence**: `GetAllColumns` requires that the first object (`objects.Qualified1`) exists in `_databaseOne`; otherwise, it throws `ArgumentException`.
- **Column validation**: All columns passed to `AddColumnToIgnore` must exist in the target objects column list; otherwise, `GetAllColumns` throws `ArgumentException`.
- **Non-empty comparison**: At least one column must remain after ignoring columns; otherwise, `GetAllColumns` throws `ArgumentException`.
- **Snapshot path**: `CreateSnapshot` assumes `C:\Temp\` is a valid, writable directory.
- **Transaction scope**: All comparison queries (`RunCompare`, `GetAllColumns`) require an active `SqlTransaction` passed explicitly.
- **Case sensitivity**: Column names in `Column` objects are stored in lowercase, regardless of source casing.
---
### 4. Dependencies
- **Internal dependencies**:
- `System.Data`, `System.Data.SqlClient`: For `SqlConnection`, `SqlCommand`, `SqlTransaction`, `SqlDataReader`.
- `DatabaseUnitTesting.Utilities.Results`: Uses `Database`, `Table`, `Row`, and `Column` types (not included in source).
- **External dependencies**:
- SQL Server (tested against versions supporting `sys.databases.source_database_id`, `INFORMATION_SCHEMA.COLUMNS`, and `EXCEPT`).
- File system access to `C:\Temp\` for snapshot creation.
- **Consumers** (inferred):
- Unit test classes (e.g., `Microsoft.VisualStudio.TestTools.UnitTesting`-based tests) that use `DatabaseComparer` to assert data equality.
---
### 5. Gotchas
- **Hardcoded snapshot path**: `CreateSnapshot` uses `C:\Temp\` unconditionally. This will fail on non-Windows systems or if the directory is missing/readonly.
- **Ambiguous object matching**: `AddColumnToIgnore` and `AddColumnsToIgnore` use `List.Find`, which only updates the *first* matching `ObjectsToCompare` entry. If multiple entries share the same `(schema1, object1)`, only the first will be modified.
- **Case normalization**: Column names are lowercased in `Row.AddColumn(...)`, which may cause mismatches if downstream consumers expect original casing.
- **`@@SPID` ordering**: The `ORDER BY @@SPID` in `ROW_NUMBER()` is arbitrary and non-deterministic. It is used solely to generate distinct `TempRowNumber` values for duplicate rows; it does not imply stable ordering.
- **No schema validation**: `DatabaseComparer` does not verify that `object2` exists in `_databaseTwo`; it assumes the object exists and will fail at query time if not.
- **Transaction dependency**: All public comparison methods require a `SqlTransaction`. Using them outside a transaction context will cause runtime errors.
- **No cleanup of snapshots**: `DatabaseAdapter` provides `DropSnapshot`, but no automatic cleanup is performed by `DatabaseComparer` or `ObjectsToCompare`. Tests must explicitly manage snapshot lifecycle.
None identified beyond the above.

View File

@@ -0,0 +1,143 @@
---
source_files:
- DataPRO/UnitTest/DatabaseUnitTesting/Utilities/Results/Row.cs
- DataPRO/UnitTest/DatabaseUnitTesting/Utilities/Results/Column.cs
- DataPRO/UnitTest/DatabaseUnitTesting/Utilities/Results/Database.cs
- DataPRO/UnitTest/DatabaseUnitTesting/Utilities/Results/Table.cs
- DataPRO/UnitTest/DatabaseUnitTesting/Utilities/Results/ResultSetParser.cs
- DataPRO/UnitTest/DatabaseUnitTesting/Utilities/Results/XmlFileAdapter.cs
generated_at: "2026-04-16T03:52:19.764167+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "fcd7fb2a1f464396"
---
# Results
## Documentation: Database Unit Testing Result Model
### 1. Purpose
This module provides an in-memory representation and serialization infrastructure for database query result sets, specifically designed for comparison-based unit testing of database operations. It enables capturing the structure (schema) and content (rows) of result sets from `SqlCommand` executions, storing them in structured objects (`Database`, `Table`, `Row`, `Column`), and persisting/loading them to/from XML for deterministic test assertions. The module supports equality comparison of entire result sets (including row multiplicities) and is used internally by the unit testing framework to validate expected vs actual database outputs.
### 2. Public Interface
*Note: All types are `internal` and not exposed outside the `DatabaseUnitTesting.Utilities.Results` namespace.*
- **`Row(string type)`**
Constructor. Initializes a row with a given type (e.g., `"datarow"`, `"schema"`), normalized to lowercase.
- `void AddColumn(Column column)`
Appends a column to the row; invalidates cached `KeyString`.
- `string KeyString { get; }`
Returns a canonical string representation of the row: `type + DELIMITER + col1.SortString + DELIMITER + col2.SortString + ...`. Computed lazily and cached.
- `int ColumnCount { get; }`
Returns the number of columns in the row.
- **`Column(string name, object value)`**
Constructor. Stores column name (unchanged) and value (converted via `Convert`). Precomputes and stores `SortString`.
- `static string Convert(object value)`
Converts values to stable string representations:
- `byte[]``"0x"` + hex digits (e.g., `"0x1A2B"`).
- `DateTime``"M/d/yyyy h:mm:ss tt"` (culture-invariant format), with trailing zeros/colons stripped.
- Other → `value.ToString()`.
- `string Name { get; }`
- `string Value { get; }`
- `string SortString { get; }`
Format: `name.ToLower() + DELIMITER + value`, where `DELIMITER = "\x1f;;"`.
- **`Table(string name1)` / `Table(string name1, string name2)`**
Constructor. Represents a result set table; stores `name1`/`name2` (lowercase).
- `Row Schema { get; set; }`
Schema row defining column names and types. Setting it updates `_fieldCount`.
- `void AddField(string name, string type)`
Adds a column to the schema row (using `type.ToLower()` as value). Increments `_fieldCount`.
- `void AddRow(Row row)`
Adds a data row; uses `row.KeyString` as dictionary key (counts multiplicities). Updates `_rowCount` and `_hashCode`.
- `int RowCount { get; }`
- `int FieldCount { get; }`
- `string Name1 { get; }`, `string Name2 { get; }`
- `bool LookupRow(string key, out int count)`
Tries to retrieve row count for a given `KeyString`.
- `IEnumerable<KeyValuePair<string, int>> Rows { get; }`
Enumerates all rows (as `KeyString` → count pairs).
- `override bool Equals(object other)` / `override int GetHashCode()`
Equality based on:
- `RowCount`, `FieldCount`, `Schema.KeyString`
- Matching row keys and counts (via `LookupRow`).
Hash code is sum of row `KeyString.GetHashCode()`.
- **`Database`**
Container for one or more `Table` instances.
- `int TableCount { get; }`
Total number of tables added (including duplicates).
- `IEnumerable<KeyValuePair<Table, int>> Tables { get; }`
Enumerates unique tables and their occurrence counts.
- `void AddTable(Table table)`
Adds a table; increments its count in internal dictionary and `_tableCount`. Updates `_hashCode`.
- `bool ContainsTable(Table table)`
- `int GetCount(Table table)`
- `override bool Equals(object other)` / `override int GetHashCode()`
Equality based on:
- `TableCount`, `GetHashCode()` (sum of table hash codes)
- Matching tables and counts for all entries.
- **`ResultSetParser` (static)**
Converts ADO.NET `SqlCommand` results into `Database` objects.
- `static Database Parse(SqlCommand command)`
Executes command, parses all result sets (via `NextResult()`), and returns a `Database`. Each result set becomes a `Table` named `"Result Set"`.
- `static List<string> SetFields(Table table, DataTable schema)`
Reads schema metadata (`GetSchemaTable()`) to populate `table.Schema`. Builds type strings (e.g., `"varchar(50)"`, `"decimal(10,2)"`). Returns list of column names.
- **`XmlFileAdapter` (static)**
Serializes/deserializes `Database` to/from XML.
- `static Database Read(string filename)`
Loads XML, parses `<object>` elements into `Table`s.
- `static void Write(string filename, Database diffs)`
Writes `Database` to XML with structure:
```xml
<results>
<object name1="..." name2="...">
<row type="schema">...</row>
<row type="datarow">...</row>
...
</object>
...
</results>
```
- `static List<Table> ReadTables(XmlNode xmlRoot)`
Parses `<object>` nodes into `Table`s.
- `static List<Row> ReadRows(XmlNode xmlTable)`
Parses `<row>` children of a table.
- `static void ReadColumns(XmlNode xmlRow, Row row)`
Parses `<column>` children (requires `name`/`value` attributes).
- `static void WriteTable(XmlTextWriter writer, Table table)` / `WriteRow` / `WriteColumn`
Helper methods for XML output.
- **Constants**: `LEFT = "\0L;;"`, `RIGHT = "\0R;;"` used to escape `<`/`>` in column values.
### 3. Invariants
- **Row KeyString**: Always starts with the row type, followed by `DELIMITER` (`"\x1e;;"`), then each columns `SortString` (itself containing `DELIMITER = "\x1f;;"`).
- **Column SortString**: Always `name.ToLower() + "\x1f;;" + value`, where `value` is the result of `Convert(value)`.
- **Table Schema**: Must be set before `FieldCount` is used (set via `Schema` property or `AddField`).
- **Table Equality**: Requires identical row counts, field counts, schema key strings, and identical row key counts.
- **Database Equality**: Requires identical table counts, hash codes, and per-table multiplicities.
- **XML Escaping**: `<` and `>` in column values are escaped as `"\0L;;"` and `"\0R;;"` during XML write, and unescaped during read.
- **Case Sensitivity**: Table/row/column names and types are normalized to lowercase in storage (except `Column.Name` remains original case).
### 4. Dependencies
- **Depends on**:
- `System.Data` (`SqlCommand`, `SqlDataReader`, `DataTable`, `DataRow`)
- `System.Xml` (`XmlDocument`, `XmlTextWriter`, `XmlAttribute`)
- `System.Text` (`StringBuilder`, `Encoding`)
- `System.Security` (unused in current code; possibly legacy)
- **Used by**:
- Unit test infrastructure (e.g., test runners comparing expected/actual `Database` objects via `XmlFileAdapter` or in-memory `Database` comparison).
- No other modules in this codebase are visible; usage is internal to `DatabaseUnitTesting`.
### 5. Gotchas
- **`Table.AddField` does not update `_hashCode`**: The commented-out line `// _hashCode = _hashCode + c.SortString.GetHashCode();` indicates hash code for schema changes is not tracked, violating the `GetHashCode()` contract if schema is modified after initial use. This may cause incorrect equality behavior if `Schema` is reassigned or `AddField` is called after `GetHashCode()` is used (e.g., in `Database` operations).
- **`Column.Name` is case-preserved but `SortString` uses lowercase**: `Name` retains original casing, but `SortString` uses `name.ToLower()`, which may cause unexpected ordering if case sensitivity matters.
- **`Row.KeyString` includes row type**: Two rows with identical columns but different types (e.g., `"schema"` vs `"datarow"`) will have different keys.
- **`Database` counts duplicates**: `AddTable` increments a counter for identical tables (via `Dictionary<Table, int>`), so `TableCount` may exceed unique table count.
- **XML format is rigid**: `ReadColumns` only accepts `<column>` children; any other element throws `XmlSyntaxException`.
- **`Convert(DateTime)` strips trailing zeros**: May cause loss of precision (e.g., `"12:00:00.000"` → `"12:00"`), potentially breaking equality for timestamps with sub-second precision.
- **No validation of column name/type uniqueness**: `AddField` allows duplicate column names; `Row.AddColumn` allows duplicate columns. This may lead to ambiguous schema representations.
- **`Table` equality ignores `Name1`/`Name2`**: Two tables with identical rows but different names (`name1`/`name2`) are considered equal. This may be intentional for result comparison but is non-intuitive.
- **`ResultSetParser` ignores `DBNull` values**: Columns with `DBNull` are omitted from `Row`, reducing column count and altering `KeyString`. This may cause rows with different `DBNull` patterns to be treated as identical.