diff --git a/msssql-cdc.sln b/msssql-cdc.sln
index 4a52020..6e29f69 100644
--- a/msssql-cdc.sln
+++ b/msssql-cdc.sln
@@ -11,6 +11,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{5D
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "examples\Example\Example.csproj", "{7DC5DA8F-274F-4586-97F4-490E0F12AE09}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E9EECC5D-1729-4F68-B525-BD7CEE63BC05}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MsSqlCdc.Tests", "test\MsSqlCdc.Tests\MsSqlCdc.Tests.csproj", "{B9FA94EC-6E90-43CA-ACB6-D5BE12AB90AF}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -28,9 +32,14 @@ Global
{7DC5DA8F-274F-4586-97F4-490E0F12AE09}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7DC5DA8F-274F-4586-97F4-490E0F12AE09}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7DC5DA8F-274F-4586-97F4-490E0F12AE09}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B9FA94EC-6E90-43CA-ACB6-D5BE12AB90AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B9FA94EC-6E90-43CA-ACB6-D5BE12AB90AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B9FA94EC-6E90-43CA-ACB6-D5BE12AB90AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B9FA94EC-6E90-43CA-ACB6-D5BE12AB90AF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5349AA55-B231-44AA-B411-97590DB9BA49} = {69F94C69-D4A8-4347-BCF6-A28CA7A2EBA4}
{7DC5DA8F-274F-4586-97F4-490E0F12AE09} = {5DFEC30A-0E3B-42CA-A0EA-6743821DE000}
+ {B9FA94EC-6E90-43CA-ACB6-D5BE12AB90AF} = {E9EECC5D-1729-4F68-B525-BD7CEE63BC05}
EndGlobalSection
EndGlobal
diff --git a/src/MsSqlCdc/ChangeData.cs b/src/MsSqlCdc/ChangeData.cs
index 839dea8..dd24d93 100644
--- a/src/MsSqlCdc/ChangeData.cs
+++ b/src/MsSqlCdc/ChangeData.cs
@@ -2,7 +2,7 @@
namespace MsSqlCdc;
-public enum Operation : ushort
+public enum Operation
{
Delete = 1,
Insert = 2,
diff --git a/src/MsSqlCdc/DataConvert.cs b/src/MsSqlCdc/DataConvert.cs
index addfe0c..7463b1c 100644
--- a/src/MsSqlCdc/DataConvert.cs
+++ b/src/MsSqlCdc/DataConvert.cs
@@ -11,17 +11,21 @@ public static class DataConvert
///
/// Converts a a colection of columns represented as Tuple to ChangeData representation.
///
- /// List of tuples with Item1 being the name column and Item2 being the column value
+ /// List of tuples with Item1 being the name column and Item2 being the column value
/// The tablename of the column.
/// Returns the CDC column as a ChangeData record.
- public static ChangeData ConvertCdcColumn(List> column, string captureInstance)
+ ///
+ public static ChangeData ConvertCdcColumn(List> columnFields, string captureInstance)
{
- var startLsn = ConvertBinaryLsn((byte[])column[0].Item2);
- var seqVal = ConvertBinaryLsn((byte[])column[1].Item2);
- var operation = ConvertIntOperation((int)column[2].Item2);
- var updateMask = Encoding.UTF8.GetString((byte[])column[3].Item2);
+ if (columnFields.Count < 3)
+ throw new Exception($"Count of column fields should be 4 or greater, instead got '{columnFields.Count}'.");
- var body = column.Skip(4)
+ var startLsn = ConvertBinaryLsn((byte[])columnFields[0].Item2);
+ var seqVal = ConvertBinaryLsn((byte[])columnFields[1].Item2);
+ var operation = ConvertIntOperation((int)columnFields[2].Item2);
+ var updateMask = Encoding.UTF8.GetString((byte[])columnFields[3].Item2);
+
+ var body = columnFields.Skip(4)
.Aggregate(new ExpandoObject() as IDictionary,
(acc, x) => { acc[x.Item1] = x.Item2; return acc; }) as dynamic;
diff --git a/test/MsSqlCdc.Tests/DataConvertTest.cs b/test/MsSqlCdc.Tests/DataConvertTest.cs
new file mode 100644
index 0000000..c47da39
--- /dev/null
+++ b/test/MsSqlCdc.Tests/DataConvertTest.cs
@@ -0,0 +1,105 @@
+using Xunit;
+using FluentAssertions;
+using static FluentAssertions.FluentActions;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using FluentAssertions.Execution;
+
+namespace MsSqlCdc.Tests;
+
+public class DataConverTest
+{
+ [Theory]
+ [InlineData(25000L, 25002L, Operation.AfterUpdate, "ABB", 10, "Rune", 20000.00)]
+ [InlineData(25L, 25202L, Operation.BeforeUpdate, "DSDFS", 2, "Simon", 0.00)]
+ [InlineData(250000000L, 250021L, Operation.Delete, "DFS", 3, "Foo", 1000000000.00)]
+ [InlineData(0L, 2L, Operation.Insert, "DFS", 3, "John", 1000000000.00)]
+ public void ConvertCdcColumn_ShouldReturnChangeData_OnValidInput(
+ long startLsn,
+ long seqVal,
+ Operation operation,
+ string updateMask,
+ int id,
+ string name,
+ double salary)
+ {
+ var captureInstance = "dbo_Employee";
+ var columnFields = new List>
+ {
+ new Tuple("__$start_lsn", BitConverter.GetBytes(startLsn).Reverse().ToArray()),
+ new Tuple("__$seqval", BitConverter.GetBytes(seqVal).Reverse().ToArray()),
+ new Tuple("__$operation", (int)operation),
+ new Tuple("__$update_mask", Encoding.ASCII.GetBytes(updateMask)),
+ new Tuple("Id", id),
+ new Tuple("Name", name),
+ new Tuple("Salary", salary),
+ };
+
+ var changeData = new ChangeData(
+ startLsn,
+ seqVal,
+ operation,
+ updateMask,
+ "dbo_Employee",
+ new
+ {
+ Id = id,
+ Name = name,
+ Salary = salary,
+ }
+ );
+
+ var result = DataConvert.ConvertCdcColumn(columnFields, captureInstance);
+
+ using (var scope = new AssertionScope())
+ {
+ Assert.True(result.Body.Id == changeData.Body.Id);
+ Assert.True(result.Body.Name == changeData.Body.Name);
+ Assert.True(result.Body.Salary == changeData.Body.Salary);
+ result.StartLineSequenceNumber.Should().Be(changeData.StartLineSequenceNumber);
+ result.SequenceValue.Should().Be(changeData.SequenceValue);
+ result.Operation.Should().Be(changeData.Operation);
+ result.UpdateMask.Should().Be(changeData.UpdateMask);
+ }
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(3)]
+ public void ConvertCdcColumn_ShouldThrowException_OnColumnFieldsBeingLessThanFour(int columnFieldsCount)
+ {
+ var captureInstance = "dbo_Employee";
+
+ var columnFields = Enumerable.Range(0, columnFieldsCount)
+ .Select(x => new Tuple(x.ToString(), x)).ToList();
+
+ Invoking(() => DataConvert.ConvertCdcColumn(columnFields, captureInstance)).Should().Throw();
+ }
+
+ [Theory]
+ [InlineData(1, Operation.Delete)]
+ [InlineData(2, Operation.Insert)]
+ [InlineData(3, Operation.BeforeUpdate)]
+ [InlineData(4, Operation.AfterUpdate)]
+ public void ConvertIntOperation_ShouldReturnCorrectEnumConvertion(int input, Operation expected)
+ {
+ var operation = DataConvert.ConvertIntOperation(input);
+ operation.Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData(-1)]
+ [InlineData(-10)]
+ [InlineData(0)]
+ [InlineData(100)]
+ [InlineData(int.MinValue)]
+ [InlineData(int.MaxValue)]
+ public void ConvertIntOperation_ShouldThrowException_OnInvalidIntRepresentation(int input)
+ {
+ Invoking(() => DataConvert.ConvertIntOperation(input)).Should().Throw();
+ }
+}
diff --git a/test/MsSqlCdc.Tests/MsSqlCdc.Tests.csproj b/test/MsSqlCdc.Tests/MsSqlCdc.Tests.csproj
new file mode 100644
index 0000000..86171f8
--- /dev/null
+++ b/test/MsSqlCdc.Tests/MsSqlCdc.Tests.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net6.0
+ enable
+
+ false
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+