diff --git a/NEW/src/Utils/JdeScoping.ConfigManager/Services/DiffService.cs b/NEW/src/Utils/JdeScoping.ConfigManager/Services/DiffService.cs new file mode 100644 index 0000000..b5974a5 --- /dev/null +++ b/NEW/src/Utils/JdeScoping.ConfigManager/Services/DiffService.cs @@ -0,0 +1,65 @@ +using DiffPlex; +using DiffPlex.DiffBuilder; +using DiffPlex.DiffBuilder.Model; + +namespace JdeScoping.ConfigManager.Services; + +/// +/// Service for generating diffs between text content. +/// +public class DiffService : IDiffService +{ + public DiffResult GenerateDiff(string original, string modified) + { + var diffBuilder = new InlineDiffBuilder(new Differ()); + var diff = diffBuilder.BuildDiffModel(original, modified); + + var lines = new List(); + int oldLineNum = 1; + int newLineNum = 1; + int insertions = 0; + int deletions = 0; + + foreach (var line in diff.Lines) + { + var diffLine = new DiffLine + { + Text = line.Text, + Type = line.Type switch + { + ChangeType.Inserted => DiffLineType.Added, + ChangeType.Deleted => DiffLineType.Removed, + _ => DiffLineType.Unchanged + }, + OldLineNumber = line.Type == ChangeType.Inserted ? null : oldLineNum, + NewLineNumber = line.Type == ChangeType.Deleted ? null : newLineNum + }; + + lines.Add(diffLine); + + switch (line.Type) + { + case ChangeType.Inserted: + newLineNum++; + insertions++; + break; + case ChangeType.Deleted: + oldLineNum++; + deletions++; + break; + default: + oldLineNum++; + newLineNum++; + break; + } + } + + return new DiffResult + { + HasChanges = insertions > 0 || deletions > 0, + Lines = lines, + Insertions = insertions, + Deletions = deletions + }; + } +} diff --git a/NEW/src/Utils/JdeScoping.ConfigManager/Services/IDiffService.cs b/NEW/src/Utils/JdeScoping.ConfigManager/Services/IDiffService.cs new file mode 100644 index 0000000..502edaa --- /dev/null +++ b/NEW/src/Utils/JdeScoping.ConfigManager/Services/IDiffService.cs @@ -0,0 +1,38 @@ +namespace JdeScoping.ConfigManager.Services; + +/// +/// Represents a line in a diff output. +/// +public class DiffLine +{ + public required int? OldLineNumber { get; init; } + public required int? NewLineNumber { get; init; } + public required string Text { get; init; } + public required DiffLineType Type { get; init; } +} + +public enum DiffLineType +{ + Unchanged, + Added, + Removed +} + +/// +/// Result of a diff operation. +/// +public class DiffResult +{ + public bool HasChanges { get; init; } + public List Lines { get; init; } = []; + public int Insertions { get; init; } + public int Deletions { get; init; } +} + +/// +/// Service for generating diffs between text content. +/// +public interface IDiffService +{ + DiffResult GenerateDiff(string original, string modified); +} diff --git a/NEW/tests/JdeScoping.ConfigManager.Tests/Services/DiffServiceTests.cs b/NEW/tests/JdeScoping.ConfigManager.Tests/Services/DiffServiceTests.cs new file mode 100644 index 0000000..cba412f --- /dev/null +++ b/NEW/tests/JdeScoping.ConfigManager.Tests/Services/DiffServiceTests.cs @@ -0,0 +1,173 @@ +using JdeScoping.ConfigManager.Services; + +namespace JdeScoping.ConfigManager.Tests.Services; + +public class DiffServiceTests +{ + private readonly DiffService _sut; + + public DiffServiceTests() + { + _sut = new DiffService(); + } + + [Fact] + public void GenerateDiff_WithNoChanges_ReturnsEmptyDiff() + { + // Arrange + var original = "line1\nline2\nline3"; + var modified = "line1\nline2\nline3"; + + // Act + var result = _sut.GenerateDiff(original, modified); + + // Assert + result.HasChanges.ShouldBeFalse(); + } + + [Fact] + public void GenerateDiff_WithChanges_ReturnsDiffLines() + { + // Arrange + var original = "line1\nline2\nline3"; + var modified = "line1\nmodified\nline3"; + + // Act + var result = _sut.GenerateDiff(original, modified); + + // Assert + result.HasChanges.ShouldBeTrue(); + result.Lines.ShouldNotBeEmpty(); + } + + [Fact] + public void GenerateDiff_WithAddedLine_ReportsInsertion() + { + // Arrange + var original = "line1\nline2"; + var modified = "line1\nline2\nline3"; + + // Act + var result = _sut.GenerateDiff(original, modified); + + // Assert + result.HasChanges.ShouldBeTrue(); + result.Insertions.ShouldBe(1); + result.Deletions.ShouldBe(0); + result.Lines.ShouldContain(l => l.Type == DiffLineType.Added && l.Text == "line3"); + } + + [Fact] + public void GenerateDiff_WithRemovedLine_ReportsDeletion() + { + // Arrange + var original = "line1\nline2\nline3"; + var modified = "line1\nline3"; + + // Act + var result = _sut.GenerateDiff(original, modified); + + // Assert + result.HasChanges.ShouldBeTrue(); + result.Deletions.ShouldBe(1); + result.Lines.ShouldContain(l => l.Type == DiffLineType.Removed && l.Text == "line2"); + } + + [Fact] + public void GenerateDiff_WithModifiedLine_ReportsAdditionAndDeletion() + { + // Arrange + var original = "line1\noriginal\nline3"; + var modified = "line1\nchanged\nline3"; + + // Act + var result = _sut.GenerateDiff(original, modified); + + // Assert + result.HasChanges.ShouldBeTrue(); + result.Insertions.ShouldBeGreaterThan(0); + result.Deletions.ShouldBeGreaterThan(0); + } + + [Fact] + public void GenerateDiff_WithEmptyOriginal_ReportsAllAsInsertions() + { + // Arrange + var original = ""; + var modified = "line1\nline2"; + + // Act + var result = _sut.GenerateDiff(original, modified); + + // Assert + result.HasChanges.ShouldBeTrue(); + result.Insertions.ShouldBeGreaterThan(0); + result.Deletions.ShouldBe(0); + } + + [Fact] + public void GenerateDiff_WithEmptyModified_ReportsAllAsDeletions() + { + // Arrange + var original = "line1\nline2"; + var modified = ""; + + // Act + var result = _sut.GenerateDiff(original, modified); + + // Assert + result.HasChanges.ShouldBeTrue(); + result.Insertions.ShouldBe(0); + result.Deletions.ShouldBeGreaterThan(0); + } + + [Fact] + public void GenerateDiff_LineNumbers_AreCorrectForUnchangedLines() + { + // Arrange + var original = "line1\nline2\nline3"; + var modified = "line1\nline2\nline3"; + + // Act + var result = _sut.GenerateDiff(original, modified); + + // Assert + var firstLine = result.Lines.First(); + firstLine.OldLineNumber.ShouldBe(1); + firstLine.NewLineNumber.ShouldBe(1); + } + + [Fact] + public void GenerateDiff_AddedLine_HasNullOldLineNumber() + { + // Arrange + var original = "line1"; + var modified = "line1\nline2"; + + // Act + var result = _sut.GenerateDiff(original, modified); + + // Assert + var addedLine = result.Lines.FirstOrDefault(l => l.Type == DiffLineType.Added); + addedLine.ShouldNotBeNull(); + addedLine.OldLineNumber.ShouldBeNull(); + addedLine.NewLineNumber.ShouldNotBeNull(); + } + + [Fact] + public void GenerateDiff_RemovedLine_HasNullNewLineNumber() + { + // Arrange + var original = "line1\nline2"; + var modified = "line1"; + + // Act + var result = _sut.GenerateDiff(original, modified); + + // Assert + var removedLine = result.Lines.FirstOrDefault(l => l.Type == DiffLineType.Removed); + removedLine.ShouldNotBeNull(); + removedLine.OldLineNumber.ShouldNotBeNull(); + removedLine.NewLineNumber.ShouldBeNull(); + } +}