Skip to content

Commit f5855ed

Browse files
Migrate database tests from SQL Server to SQLite (#4820)
* Migrate database tests from SQL Server LocalDB to SQLite for CI Replace SQL Server LocalDB dependency with SQLite so database integration tests can run in CI where no SQL Server instance is available. Changes: - Add SqliteTestDb.cs to create/manage a temporary SQLite database with the same schema and seed data as the original LocalDB test database - Replace Microsoft.Data.SqlClient with Microsoft.Data.Sqlite package - Simplify WellKnownValues.cs to serve the SQLite connection string - Initialize/cleanup SQLite database in AssemblyInitialize/Cleanup - Remove #if DEBUG guards and [TestCategory("SkipOnCIServer")] attributes from SafeDataReaderTests, DataPortalTests, and DataPortalExceptionTests - Update exception message assertions for SQLite CHECK constraint errors - Remove MDF/LDF file copy references from project file https://claude.ai/code/session_0123tC2ciU58UgL8jR2d5PBt * Fix transaction rollback in DataPortal_Insert for SQLite SQLite's SqliteConnection does not automatically enlist in ambient System.Transactions.TransactionScope the way SQL Server does. Replace the [Transactional(TransactionalTypes.TransactionScope)] attribute with an explicit SQLite transaction so the first INSERT is properly rolled back when the second INSERT fails the CHECK constraint. https://claude.ai/code/session_0123tC2ciU58UgL8jR2d5PBt * Switch to System.Data.SQLite.Core for TransactionScope support Microsoft.Data.Sqlite does not support System.Transactions.TransactionScope (EnlistTransaction throws NotSupportedException). Switch to System.Data.SQLite which properly enlists in ambient TransactionScope, so the [Transactional(TransactionalTypes.TransactionScope)] attribute test remains valid and the rollback behavior is tested end-to-end through CSLA. https://claude.ai/code/session_0123tC2ciU58UgL8jR2d5PBt --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent dd10de5 commit f5855ed

File tree

8 files changed

+177
-247
lines changed

8 files changed

+177
-247
lines changed

Source/tests/Csla.test/Csla.Tests.csproj

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
<PackageReference Include="MSTest.TestAdapter" Version="3.9.1" />
5454
<PackageReference Include="MSTest.TestFramework" Version="3.9.1" />
5555
<PackageReference Include="System.Data.Common" Version="4.3.0" />
56-
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
56+
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
5757
<PackageReference Include="System.Resources.Extensions" Version="10.0.0" />
5858
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
5959
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
@@ -81,12 +81,6 @@
8181
</ItemGroup>
8282

8383
<ItemGroup>
84-
<None Update="DataPortalTestDatabase.mdf">
85-
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
86-
</None>
87-
<None Update="DataPortalTestDatabase_log.ldf">
88-
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
89-
</None>
9084
<None Update="Properties\Settings.settings">
9185
<Generator>SettingsSingleFileGenerator</Generator>
9286
<LastGenOutput>Settings.Designer.cs</LastGenOutput>

Source/tests/Csla.test/DPException/DataPortalExceptionTests.cs

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ public void Initialize()
2828
TestResults.Reinitialise();
2929
}
3030

31-
#if DEBUG
3231
[TestMethod]
33-
[TestCategory("SkipOnCIServer")]
3432
public void CheckInnerExceptionsOnSave()
3533
{
3634
IDataPortal<DataPortal.TransactionalRoot> dataPortal = _testDIContext.CreateDataPortal<DataPortal.TransactionalRoot>();
@@ -43,7 +41,6 @@ public void CheckInnerExceptionsOnSave()
4341
string baseException = string.Empty;
4442
string baseInnerException = string.Empty;
4543
string baseInnerInnerException = string.Empty;
46-
string exceptionSource = string.Empty;
4744

4845
try
4946
{
@@ -54,31 +51,23 @@ public void CheckInnerExceptionsOnSave()
5451
baseException = ex.Message;
5552
baseInnerException = ex.InnerException.Message;
5653
baseInnerInnerException = ex.InnerException.InnerException?.Message;
57-
exceptionSource = ex.InnerException.InnerException?.Source;
5854
Assert.IsNull(ex.BusinessObject, "Business object shouldn't be returned");
5955
}
6056

6157
//check base exception
6258
Assert.IsTrue(baseException.StartsWith("DataPortal.Update failed"), "Exception should start with 'DataPortal.Update failed'");
63-
Assert.IsTrue(baseException.Contains("String or binary data would be truncated."),
64-
"Exception should contain 'String or binary data would be truncated.'");
59+
Assert.IsTrue(baseException.Contains("CHECK constraint failed"),
60+
"Exception should contain 'CHECK constraint failed'");
6561
//check inner exception
6662
Assert.AreEqual("TransactionalRoot.DataPortal_Insert method call failed", baseInnerException);
67-
//check inner exception of inner exception
68-
Assert.AreEqual("String or binary data would be truncated.\r\nThe statement has been terminated.", baseInnerInnerException);
63+
//check inner exception of inner exception (SQLite CHECK constraint violation)
64+
Assert.IsTrue(baseInnerInnerException.Contains("CHECK constraint failed"),
65+
"Inner inner exception should contain 'CHECK constraint failed'");
6966

70-
//check what caused inner exception's inner exception (i.e. the root exception)
71-
#if (NETFRAMEWORK)
72-
Assert.AreEqual(".Net SqlClient Data Provider", exceptionSource);
73-
#else
74-
Assert.AreEqual("Core Microsoft SqlClient Data Provider", exceptionSource);
75-
#endif
76-
77-
//verify that the implemented method, DataPortal_OnDataPortal
67+
//verify that the implemented method, DataPortal_OnDataPortal
7868
//was called for the business object that threw the exception
7969
Assert.AreEqual("Called", TestResults.GetResult("OnDataPortalException"));
8070
}
81-
#endif
8271

8372
[TestMethod]
8473
public void CheckInnerExceptionsOnDelete()

Source/tests/Csla.test/DataPortal/DataPortalTests.cs

Lines changed: 20 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
//-----------------------------------------------------------------------
88

99
using Csla.Test.DataBinding;
10-
using Microsoft.Data.SqlClient;
10+
using System.Data.SQLite;
1111
using Csla.TestHelpers;
1212
using Microsoft.Extensions.DependencyInjection;
1313
using Csla.Configuration;
@@ -19,7 +19,6 @@
1919
using Csla.Server;
2020
using System.Security.Principal;
2121
using FluentAssertions.Execution;
22-
using System.ComponentModel; // added
2322

2423
namespace Csla.Test.DataPortal
2524
{
@@ -40,30 +39,17 @@ public void Initialize()
4039
TestResults.Reinitialise();
4140
}
4241

43-
private static string CONNECTION_STRING = WellKnownValues.DataPortalTestDatabase;
42+
private static string CONNECTION_STRING => WellKnownValues.DataPortalTestDatabase;
4443
public void ClearDataBase()
4544
{
46-
SqlConnection cn = new SqlConnection(CONNECTION_STRING);
47-
SqlCommand cm = new SqlCommand("DELETE FROM Table2", cn);
48-
49-
try
50-
{
51-
cn.Open();
52-
cm.ExecuteNonQuery();
53-
}
54-
catch (Exception)
55-
{
56-
//do nothing
57-
}
58-
finally
59-
{
60-
cn.Close();
61-
}
45+
using var cn = new SQLiteConnection(CONNECTION_STRING);
46+
cn.Open();
47+
using var cm = cn.CreateCommand();
48+
cm.CommandText = "DELETE FROM Table2";
49+
cm.ExecuteNonQuery();
6250
}
6351

64-
#if DEBUG
6552
[TestMethod]
66-
[TestCategory("SkipOnCIServer")]
6753
public void TestTransactionScopeUpdate()
6854
{
6955
IDataPortal<TransactionalRoot> dataPortal = _testDIContext.CreateDataPortal<TransactionalRoot>();
@@ -73,35 +59,17 @@ public void TestTransactionScopeUpdate()
7359
tr.LastName = "Johnson";
7460
tr.SmallColumn = "abc";
7561

76-
try
77-
{
78-
tr = tr.Save();
79-
}
80-
catch (Exception ex)
81-
{
82-
if (IsTransactionScopeEnvironmentUnavailable(ex))
83-
Assert.Inconclusive("Skipping TransactionScope test: transactional infrastructure (MSDTC / distributed transactions) not available (0x89c5010a).");
84-
throw;
85-
}
86-
87-
SqlConnection cn = new SqlConnection(CONNECTION_STRING);
88-
SqlCommand cm = new SqlCommand("SELECT * FROM Table2", cn);
62+
tr = tr.Save();
8963

90-
try
64+
using (var cn = new SQLiteConnection(CONNECTION_STRING))
9165
{
9266
cn.Open();
93-
SqlDataReader dr = cm.ExecuteReader();
67+
using var cm = cn.CreateCommand();
68+
cm.CommandText = "SELECT * FROM Table2";
69+
using var dr = cm.ExecuteReader();
9470

71+
//will have rows since no exception was thrown on the insert
9572
Assert.AreEqual(true, dr.HasRows);
96-
dr.Close();
97-
}
98-
catch (Exception)
99-
{
100-
//do nothing
101-
}
102-
finally
103-
{
104-
cn.Close();
10573
}
10674

10775
ClearDataBase();
@@ -117,47 +85,24 @@ public void TestTransactionScopeUpdate()
11785
}
11886
catch (Exception ex)
11987
{
120-
if (IsTransactionScopeEnvironmentUnavailable(ex))
121-
Assert.Inconclusive("Skipping TransactionScope test: transactional infrastructure (MSDTC / distributed transactions) not available (0x89c5010a).");
12288
Assert.IsTrue(ex.Message.StartsWith("DataPortal.Update failed"), "Invalid exception message");
12389
}
12490

125-
try
91+
//within the DataPortal_Insert method, two commands are run to insert data into
92+
//the database. Here we verify that both commands have been rolled back
93+
using (var cn = new SQLiteConnection(CONNECTION_STRING))
12694
{
12795
cn.Open();
128-
SqlDataReader dr = cm.ExecuteReader();
96+
using var cm = cn.CreateCommand();
97+
cm.CommandText = "SELECT * FROM Table2";
98+
using var dr = cm.ExecuteReader();
12999

100+
//should not have rows since both commands were rolled back
130101
Assert.AreEqual(false, dr.HasRows);
131-
dr.Close();
132-
}
133-
catch (Exception)
134-
{
135-
//do nothing
136-
}
137-
finally
138-
{
139-
cn.Close();
140102
}
141103

142104
ClearDataBase();
143105
}
144-
#endif
145-
146-
private static bool IsTransactionScopeEnvironmentUnavailable(Exception ex)
147-
{
148-
// Walk inner exceptions to find Win32Exception 0x89c5010a
149-
while (ex != null)
150-
{
151-
if (ex is Win32Exception w32)
152-
{
153-
if (w32.NativeErrorCode == unchecked((int)0x89c5010a) ||
154-
w32.Message.Contains("0x89c5010a", StringComparison.OrdinalIgnoreCase))
155-
return true;
156-
}
157-
ex = ex.InnerException;
158-
}
159-
return false;
160-
}
161106

162107
[TestMethod]
163108
public void StronglyTypedDataPortalMethods()

Source/tests/Csla.test/DataPortal/TransactionalRoot.cs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// <summary>no summary</summary>
77
//-----------------------------------------------------------------------
88

9-
using Microsoft.Data.SqlClient;
9+
using System.Data.SQLite;
1010

1111
namespace Csla.Test.DataPortal
1212
{
@@ -119,33 +119,29 @@ protected void DataPortal_Fetch(object criteria)
119119
[Transactional(TransactionalTypes.TransactionScope)]
120120
[Insert]
121121
protected void DataPortal_Insert()
122-
{
123-
SqlConnection cn = new SqlConnection(WellKnownValues.DataPortalTestDatabase);
122+
{
123+
using var cn = new SQLiteConnection(WellKnownValues.DataPortalTestDatabase);
124124
string firstName = FirstName;
125125
string lastName = LastName;
126126
string smallColumn = SmallColumn;
127127

128+
cn.Open();
129+
128130
//this command will always execute successfully
129131
//since it inserts a string less than 5 characters
130132
//into SmallColumn
131-
SqlCommand cm1 = new SqlCommand();
132-
cm1.Connection = cn;
133+
using var cm1 = cn.CreateCommand();
133134
cm1.CommandText = "INSERT INTO Table2(FirstName, LastName, SmallColumn) VALUES('Bill', 'Thompson', 'abc')";
134135

135136
//this command will throw an exception
136-
//if SmallColumn is set to a string longer than
137+
//if SmallColumn is set to a string longer than
137138
//5 characters
138-
SqlCommand cm2 = new SqlCommand();
139-
cm2.Connection = cn;
140-
//use stringbuilder
141-
cm2.CommandText = "INSERT INTO Table2(FirstName, LastName, SmallColumn) VALUES('";
142-
cm2.CommandText += firstName;
143-
cm2.CommandText += "', '" + lastName + "', '" + smallColumn + "')";
139+
using var cm2 = cn.CreateCommand();
140+
cm2.CommandText = "INSERT INTO Table2(FirstName, LastName, SmallColumn) VALUES('" +
141+
firstName + "', '" + lastName + "', '" + smallColumn + "')";
144142

145-
cn.Open();
146143
cm1.ExecuteNonQuery();
147144
cm2.ExecuteNonQuery();
148-
cn.Close();
149145

150146
TestResults.Reinitialise();
151147
TestResults.Add("TransactionalRoot", "Inserted");

0 commit comments

Comments
 (0)