|
- using Campaign_Tracker.Server.LegacyData;
- using Campaign_Tracker.Server.LegacyData.Models;
-
- namespace Campaign_Tracker.Server.Tests;
-
- public sealed class LegacyDataAccessTests
- {
- // ── AC #1 — data returned via join keys (JCode, ID, KitID) ──────────────
-
- [Fact]
- public async Task GetJurisdictionAsync_ByJCode_ReturnsMatchingRecord_AC1()
- {
- var jurisdictions = new LegacyJurisdiction[]
- {
- new("FAIR01", "Fairview Borough", "100 Main St", "Fairview, PA 16415", null, null),
- new("LAKE02", "Lake Township", "200 Lake Rd", "Lake City, PA 16423", null, null),
- };
- var sut = new InMemoryLegacyDataAccess(jurisdictions: jurisdictions);
-
- var result = await sut.GetJurisdictionAsync("FAIR01");
-
- Assert.NotNull(result);
- Assert.Equal("FAIR01", result.JCode);
- Assert.Equal("Fairview Borough", result.Name);
- }
-
- [Fact]
- public async Task GetJurisdictionAsync_UnknownJCode_ReturnsNull_AC1()
- {
- var sut = new InMemoryLegacyDataAccess(jurisdictions: []);
-
- var result = await sut.GetJurisdictionAsync("UNKNOWN");
-
- Assert.Null(result);
- }
-
- [Fact]
- public async Task GetJurisdictionAsync_IsCaseInsensitive_AC1()
- {
- var sut = new InMemoryLegacyDataAccess(
- jurisdictions: [new("FAIR01", "Fairview", null, null, null, null)]);
-
- var result = await sut.GetJurisdictionAsync("fair01");
-
- Assert.NotNull(result);
- }
-
- [Fact]
- public async Task GetContactByIdAsync_ById_ReturnsMatchingRecord_AC1()
- {
- var contacts = new LegacyContact[]
- {
- new(1, "FAIR01", "Jane Doe", "Director", "j@test.gov",
- "555-0101", null, "100 Main St", null, null, null, null, null, "Fairview", "01"),
- };
- var sut = new InMemoryLegacyDataAccess(contacts: contacts);
-
- var result = await sut.GetContactByIdAsync(1);
-
- Assert.NotNull(result);
- Assert.Equal(1, result.Id);
- Assert.Equal("Jane Doe", result.ContactName);
- }
-
- [Fact]
- public async Task GetContactsByJurisdictionAsync_ByJurisCode_ReturnsAllMatches_AC1()
- {
- var contacts = new LegacyContact[]
- {
- new(1, "FAIR01", "Jane Doe", "Director", null, null, null, null, null, null, null, null, null, null, null),
- new(2, "FAIR01", "John Smith", "Clerk", null, null, null, null, null, null, null, null, null, null, null),
- new(3, "LAKE02", "Alice Jones","Director", null, null, null, null, null, null, null, null, null, null, null),
- };
- var sut = new InMemoryLegacyDataAccess(contacts: contacts);
-
- var result = await sut.GetContactsByJurisdictionAsync("FAIR01");
-
- Assert.Equal(2, result.Count);
- Assert.All(result, c => Assert.Equal("FAIR01", c.JurisCode));
- }
-
- [Fact]
- public async Task GetKitByIdAsync_ById_ReturnsMatchingRecord_AC1()
- {
- var kits = new LegacyKit[]
- {
- new(101, "FAIR01", "JOB-001", "Inkjet", "Active", null,
- Cass: true, InkJetJob: true,
- CreatedOn: new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc),
- ExportedToSnailWorks: null, LabelsPrinted: null, OfficeCopiesAmount: null,
- InboundStid: null, OutboundStid: null),
- };
- var sut = new InMemoryLegacyDataAccess(kits: kits);
-
- var result = await sut.GetKitByIdAsync(101);
-
- Assert.NotNull(result);
- Assert.Equal(101, result.Id);
- Assert.Equal("FAIR01", result.JCode);
- }
-
- [Fact]
- public async Task GetKitsByJurisdictionAsync_ByJCode_ReturnsAllMatches_AC1()
- {
- var kits = new LegacyKit[]
- {
- new(101, "FAIR01", "JOB-001", "Inkjet", "Active", null, true, true, null, null, null, null, null, null),
- new(102, "FAIR01", "JOB-002", "OfficeCopy", "Pending", null, false, false, null, null, null, null, null, null),
- new(103, "LAKE02", "JOB-003", "Inkjet", "Active", null, true, true, null, null, null, null, null, null),
- };
- var sut = new InMemoryLegacyDataAccess(kits: kits);
-
- var result = await sut.GetKitsByJurisdictionAsync("FAIR01");
-
- Assert.Equal(2, result.Count);
- Assert.All(result, k => Assert.Equal("FAIR01", k.JCode));
- }
-
- [Fact]
- public async Task GetKitLabelsByKitAsync_ByKitId_ReturnsAllMatches_AC1()
- {
- var labels = new LegacyKitLabel[]
- {
- new(201, KitId: 101, "IMB1", "DIGITS1", "SN1", "OUTIMB1", "OUTDIGITS1", "SN2", 1),
- new(202, KitId: 101, "IMB2", "DIGITS2", "SN3", "OUTIMB2", "OUTDIGITS2", "SN4", 2),
- new(203, KitId: 102, "IMB3", "DIGITS3", "SN5", "OUTIMB3", "OUTDIGITS3", "SN6", 1),
- };
- var sut = new InMemoryLegacyDataAccess(kitLabels: labels);
-
- var result = await sut.GetKitLabelsByKitAsync(101);
-
- Assert.Equal(2, result.Count);
- Assert.All(result, l => Assert.Equal(101, l.KitId));
- }
-
- // ── AC #2 — only SELECT operations; write keywords blocked ───────────────
-
- [Fact]
- public void ILegacyDataAccess_HasNoWriteMethods_AC2()
- {
- var methods = typeof(ILegacyDataAccess).GetMethods()
- .Select(m => m.Name)
- .ToArray();
-
- var writePatterns = new[] { "Insert", "Update", "Delete", "Remove", "Modify", "Write", "Save", "Create", "Upsert" };
-
- foreach (var pattern in writePatterns)
- {
- Assert.DoesNotContain(methods,
- name => name.StartsWith(pattern, StringComparison.OrdinalIgnoreCase));
- }
- }
-
- [Theory]
- [InlineData("INSERT INTO Jurisdiction (JCode) VALUES ('X')")]
- [InlineData("UPDATE Jurisdiction SET Name = 'X' WHERE JCode = 'Y'")]
- [InlineData("DELETE FROM Jurisdiction WHERE JCode = 'X'")]
- [InlineData("DROP TABLE Jurisdiction")]
- [InlineData("ALTER TABLE Jurisdiction ADD COLUMN Foo TEXT")]
- [InlineData("EXEC sp_executesql N'DELETE FROM Kit'")]
- [InlineData("MERGE INTO Kit")]
- [InlineData("TRUNCATE TABLE Kit")]
- [InlineData("SELECT LastUpdated FROM Kit; UPDATE Kit SET Status = 'X'")]
- public void ReadOnlyCommandGuard_BlocksWriteStatements_AC2(string sql)
- {
- Assert.Throws<LegacyWriteAttemptException>(() => ReadOnlyCommandGuard.Validate(sql));
- }
-
- [Theory]
- [InlineData("SELECT * FROM Jurisdiction WHERE JCode = 'FAIR01'")]
- [InlineData("SELECT ID, Name FROM Jurisdiction")]
- [InlineData("SELECT k.ID, j.Name FROM Kit k INNER JOIN Jurisdiction j ON k.Jcode = j.JCode")]
- [InlineData("SELECT COUNT(*) FROM Contacts WHERE JURISCODE = 'FAIR01'")]
- [InlineData("SELECT [Update] FROM Contacts")]
- [InlineData("SELECT * FROM Contacts WHERE Notes = 'delete request'")]
- [InlineData("SELECT * FROM Contacts -- DELETE marker in comment")]
- public void ReadOnlyCommandGuard_AllowsSelectStatements_AC2(string sql)
- {
- var exception = Record.Exception(() => ReadOnlyCommandGuard.Validate(sql));
- Assert.Null(exception);
- }
-
- [Fact]
- public void ReadOnlyCommandGuard_BlocksEmptySql_AC2()
- {
- Assert.Throws<LegacyWriteAttemptException>(() => ReadOnlyCommandGuard.Validate(""));
- Assert.Throws<LegacyWriteAttemptException>(() => ReadOnlyCommandGuard.Validate(" "));
- }
-
- [Fact]
- public void OleDbLegacyDataAccess_IsAvailableForConfiguredLegacyDatabase_AC1()
- {
- Assert.True(typeof(ILegacyDataAccess).IsAssignableFrom(typeof(OleDbLegacyDataAccess)));
- }
-
- // ── AC #3 — results are strongly-typed domain records ───────────────────
-
- [Fact]
- public async Task GetAllJurisdictionsAsync_ReturnsStronglyTypedRecords_AC3()
- {
- var sut = new InMemoryLegacyDataAccess();
-
- var results = await sut.GetAllJurisdictionsAsync();
-
- // Strongly-typed: compile-time member access verifies type correctness.
- Assert.All(results, j =>
- {
- _ = j.JCode; // string
- _ = j.Name; // string?
- _ = j.MailingAddress;// string?
- _ = j.CityStateZip; // string?
- });
- Assert.IsAssignableFrom<IReadOnlyList<LegacyJurisdiction>>(results);
- }
-
- [Fact]
- public async Task GetKitByIdAsync_ReturnsStronglyTypedRecord_AC3()
- {
- var sut = new InMemoryLegacyDataAccess();
-
- var result = await sut.GetKitByIdAsync(101);
-
- Assert.NotNull(result);
- Assert.IsType<LegacyKit>(result);
- // Boolean fields mapped correctly from Access bit columns.
- Assert.True(result.Cass);
- Assert.True(result.InkJetJob);
- // DateTime? field mapped correctly from Access Date/Time column.
- Assert.IsType<DateTime>(result.CreatedOn);
- }
-
- [Fact]
- public async Task GetKitLabelsByKitAsync_ReturnsStronglyTypedRecords_AC3()
- {
- var sut = new InMemoryLegacyDataAccess();
-
- var results = await sut.GetKitLabelsByKitAsync(101);
-
- Assert.All(results, l =>
- {
- Assert.IsType<LegacyKitLabel>(l);
- _ = l.InBoundImb; // string?
- _ = l.OutboundImb; // string?
- _ = l.SetNumber; // double?
- });
- }
-
- // ── AC #4 — anti-corruption layer is the sole access point ───────────────
-
- [Fact]
- public void ILegacyDataAccess_IsTheOnlyPublicContract_LayerBoundaryEnforced_AC4()
- {
- // Verify that ILegacyDataAccess exists in the LegacyData namespace
- // and that InMemoryLegacyDataAccess implements it correctly.
- // The architectural constraint (no direct table access outside the layer)
- // is enforced by the interface — any external access must go through ILegacyDataAccess.
- var interfaceType = typeof(ILegacyDataAccess);
- var implType = typeof(InMemoryLegacyDataAccess);
-
- Assert.True(interfaceType.IsInterface);
- Assert.True(interfaceType.IsAssignableFrom(implType));
- Assert.Equal("Campaign_Tracker.Server.LegacyData", interfaceType.Namespace);
- }
-
- [Fact]
- public void LegacyDomainModels_AreReadOnlySealedRecords_AC4()
- {
- var modelTypes = new[]
- {
- typeof(LegacyJurisdiction),
- typeof(LegacyContact),
- typeof(LegacyKit),
- typeof(LegacyKitLabel),
- };
-
- foreach (var type in modelTypes)
- {
- Assert.True(type.IsSealed, $"{type.Name} must be sealed to prevent extension.");
- // Positional records expose init-only setters (construction-time only, not mutation).
- // AC #4 requires no post-construction mutation — verify no plain public setters exist.
- var mutableSetters = type.GetProperties()
- .Where(p => p.SetMethod is { IsPublic: true } setter
- && !setter.ReturnParameter.GetRequiredCustomModifiers()
- .Any(m => m == typeof(System.Runtime.CompilerServices.IsExternalInit)))
- .ToArray();
- Assert.Empty(mutableSetters);
- }
- }
-
- [Fact]
- public void LegacyJoinKeys_AreRequiredInDomainRecords_AC1()
- {
- Assert.False(IsNullableReference(typeof(LegacyContact).GetProperty(nameof(LegacyContact.JurisCode))!));
- Assert.False(IsNullableReference(typeof(LegacyKit).GetProperty(nameof(LegacyKit.JCode))!));
- Assert.Equal(typeof(int), typeof(LegacyKitLabel).GetProperty(nameof(LegacyKitLabel.KitId))!.PropertyType);
- }
-
- private static bool IsNullableReference(System.Reflection.PropertyInfo property) =>
- new System.Reflection.NullabilityInfoContext()
- .Create(property)
- .ReadState == System.Reflection.NullabilityState.Nullable;
- }
|