Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
using FluentAssertions;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
using Moq;
|
2026-01-22 20:47:55 +00:00
|
|
|
using RealCV.Application.Models;
|
|
|
|
|
using RealCV.Infrastructure.Services;
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
|
2026-01-22 20:47:55 +00:00
|
|
|
namespace RealCV.Tests.Services;
|
Add comprehensive unit test suite
Test project with 143 tests covering:
- TimelineAnalyserService (27 tests): gap/overlap detection, edge cases
- CVParserService (35 tests): file parsing, extension handling, API calls
- CompanyVerifierService (23 tests): verification, caching, fuzzy matching
- CVCheckService (24 tests): CRUD operations, file upload, job queuing
- ProcessCVCheckJob (34 tests): full workflow, scoring algorithm, flags
Uses xUnit, Moq, FluentAssertions, EF Core InMemory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 19:45:07 +01:00
|
|
|
|
|
|
|
|
public class TimelineAnalyserServiceTests
|
|
|
|
|
{
|
|
|
|
|
private readonly TimelineAnalyserService _sut;
|
|
|
|
|
private readonly Mock<ILogger<TimelineAnalyserService>> _loggerMock;
|
|
|
|
|
|
|
|
|
|
public TimelineAnalyserServiceTests()
|
|
|
|
|
{
|
|
|
|
|
_loggerMock = new Mock<ILogger<TimelineAnalyserService>>();
|
|
|
|
|
_sut = new TimelineAnalyserService(_loggerMock.Object);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region Empty and Single Entry Tests
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithEmptyEmploymentList_ReturnsEmptyResults()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>();
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.TotalGapMonths.Should().Be(0);
|
|
|
|
|
result.TotalOverlapMonths.Should().Be(0);
|
|
|
|
|
result.Gaps.Should().BeEmpty();
|
|
|
|
|
result.Overlaps.Should().BeEmpty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithSingleEmploymentEntry_ReturnsNoGapsOrOverlaps()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.TotalGapMonths.Should().Be(0);
|
|
|
|
|
result.TotalOverlapMonths.Should().Be(0);
|
|
|
|
|
result.Gaps.Should().BeEmpty();
|
|
|
|
|
result.Overlaps.Should().BeEmpty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Gap Detection Tests
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithTwoConsecutiveJobs_ReturnsNoGaps()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 12, 31)),
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2021, 1, 1), new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.TotalGapMonths.Should().Be(0);
|
|
|
|
|
result.Gaps.Should().BeEmpty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithThreeMonthGap_DetectsGapCorrectly()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 30)),
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2020, 10, 1), new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Gaps.Should().HaveCount(1);
|
|
|
|
|
result.Gaps[0].StartDate.Should().Be(new DateOnly(2020, 6, 30));
|
|
|
|
|
result.Gaps[0].EndDate.Should().Be(new DateOnly(2020, 10, 1));
|
|
|
|
|
result.Gaps[0].Months.Should().BeGreaterThanOrEqualTo(3);
|
|
|
|
|
result.TotalGapMonths.Should().BeGreaterThanOrEqualTo(3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithLargeGap_DetectsGapWithCorrectMonths()
|
|
|
|
|
{
|
|
|
|
|
// Arrange - 6 month gap
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 15)),
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2021, 1, 15), new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Gaps.Should().HaveCount(1);
|
|
|
|
|
result.TotalGapMonths.Should().BeGreaterThanOrEqualTo(6);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithSmallGapUnderThreeMonths_DoesNotFlagIt()
|
|
|
|
|
{
|
|
|
|
|
// Arrange - 2 month gap (should not be flagged)
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 30)),
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2020, 8, 15), new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Gaps.Should().BeEmpty();
|
|
|
|
|
result.TotalGapMonths.Should().Be(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithOneMonthGap_DoesNotFlagIt()
|
|
|
|
|
{
|
|
|
|
|
// Arrange - 1 month gap
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 30)),
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2020, 8, 1), new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Gaps.Should().BeEmpty();
|
|
|
|
|
result.TotalGapMonths.Should().Be(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Overlap Detection Tests
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithOverlappingJobsMoreThanTwoMonths_DetectsOverlap()
|
|
|
|
|
{
|
|
|
|
|
// Arrange - 4 month overlap
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 8, 31)),
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2020, 5, 1), new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Overlaps.Should().HaveCount(1);
|
|
|
|
|
result.Overlaps[0].Company1.Should().Be("Company A");
|
|
|
|
|
result.Overlaps[0].Company2.Should().Be("Company B");
|
|
|
|
|
result.Overlaps[0].Months.Should().BeGreaterThan(2);
|
|
|
|
|
result.TotalOverlapMonths.Should().BeGreaterThan(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithOneMonthTransitionOverlap_DoesNotFlagIt()
|
|
|
|
|
{
|
|
|
|
|
// Arrange - 1 month overlap (transition period, should be allowed)
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 15)),
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2020, 6, 1), new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Overlaps.Should().BeEmpty();
|
|
|
|
|
result.TotalOverlapMonths.Should().Be(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithTwoMonthTransitionOverlap_DoesNotFlagIt()
|
|
|
|
|
{
|
|
|
|
|
// Arrange - 2 month overlap (transition period, should be allowed)
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 7, 31)),
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2020, 6, 1), new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Overlaps.Should().BeEmpty();
|
|
|
|
|
result.TotalOverlapMonths.Should().Be(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithThreeMonthOverlap_DetectsOverlap()
|
|
|
|
|
{
|
|
|
|
|
// Arrange - 3 month overlap (exceeds 2 month allowed period)
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 8, 31)),
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2020, 6, 1), new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Overlaps.Should().HaveCount(1);
|
|
|
|
|
result.Overlaps[0].Months.Should().BeGreaterThan(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Mixed Scenarios Tests
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithMultipleJobsMixedGapsAndOverlaps_DetectsAll()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
// Job 1: Jan 2019 - Dec 2019
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2019, 1, 1), new DateOnly(2019, 12, 31)),
|
|
|
|
|
// Job 2: June 2020 - Dec 2020 (6 month gap after Job 1 - should be detected)
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2020, 6, 1), new DateOnly(2020, 12, 31)),
|
|
|
|
|
// Job 3: Sep 2020 - June 2021 (4 month overlap with Job 2 - should be detected)
|
|
|
|
|
CreateEmployment("Company C", new DateOnly(2020, 9, 1), new DateOnly(2021, 6, 30))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Gaps.Should().HaveCount(1);
|
|
|
|
|
result.Gaps[0].StartDate.Should().Be(new DateOnly(2019, 12, 31));
|
|
|
|
|
result.Gaps[0].EndDate.Should().Be(new DateOnly(2020, 6, 1));
|
|
|
|
|
|
|
|
|
|
result.Overlaps.Should().HaveCount(1);
|
|
|
|
|
result.Overlaps[0].Company1.Should().Be("Company B");
|
|
|
|
|
result.Overlaps[0].Company2.Should().Be("Company C");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithMultipleGaps_SumsTotalGapMonths()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2019, 1, 1), new DateOnly(2019, 6, 30)),
|
|
|
|
|
// 4 month gap
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2019, 11, 1), new DateOnly(2020, 4, 30)),
|
|
|
|
|
// 5 month gap
|
|
|
|
|
CreateEmployment("Company C", new DateOnly(2020, 10, 1), new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Gaps.Should().HaveCount(2);
|
|
|
|
|
result.TotalGapMonths.Should().BeGreaterThanOrEqualTo(8);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithMultipleOverlaps_SumsTotalOverlapMonths()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 9, 30)),
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2020, 5, 1), new DateOnly(2021, 3, 31)),
|
|
|
|
|
CreateEmployment("Company C", new DateOnly(2020, 12, 1), new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Overlaps.Should().HaveCountGreaterThan(0);
|
|
|
|
|
result.TotalOverlapMonths.Should().BeGreaterThan(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Null Date Handling Tests
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithAllNullStartDates_ReturnsEmptyResults()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", null, new DateOnly(2020, 12, 31)),
|
|
|
|
|
CreateEmployment("Company B", null, new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.TotalGapMonths.Should().Be(0);
|
|
|
|
|
result.TotalOverlapMonths.Should().Be(0);
|
|
|
|
|
result.Gaps.Should().BeEmpty();
|
|
|
|
|
result.Overlaps.Should().BeEmpty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithSomeNullStartDates_FiltersThemOut()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 30)),
|
|
|
|
|
CreateEmployment("Company B", null, new DateOnly(2020, 12, 31)),
|
|
|
|
|
CreateEmployment("Company C", new DateOnly(2021, 1, 1), new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
// Company B is filtered out, gap between A and C is 6 months
|
|
|
|
|
result.Gaps.Should().HaveCount(1);
|
|
|
|
|
result.Gaps[0].Months.Should().BeGreaterThanOrEqualTo(6);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithMixedNullDates_HandlesGracefully()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", null, null),
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2020, 1, 1), new DateOnly(2020, 12, 31)),
|
|
|
|
|
CreateEmployment("Company C", null, new DateOnly(2021, 6, 30))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert - only Company B has valid start date, so no gaps/overlaps possible
|
|
|
|
|
result.Gaps.Should().BeEmpty();
|
|
|
|
|
result.Overlaps.Should().BeEmpty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Current Job (No End Date) Tests
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithCurrentJobNoEndDate_UsesTodayAsEndDate()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 30)),
|
|
|
|
|
CreateCurrentEmployment("Company B", new DateOnly(2020, 7, 1))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Gaps.Should().BeEmpty();
|
|
|
|
|
result.TotalGapMonths.Should().Be(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithCurrentJobOverlappingPastJob_DetectsOverlap()
|
|
|
|
|
{
|
|
|
|
|
// Arrange - Current job started while still at previous job
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2024, 1, 1), new DateOnly(2024, 12, 31)),
|
|
|
|
|
CreateCurrentEmployment("Company B", new DateOnly(2024, 6, 1))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Overlaps.Should().HaveCount(1);
|
|
|
|
|
result.Overlaps[0].Company1.Should().Be("Company A");
|
|
|
|
|
result.Overlaps[0].Company2.Should().Be("Company B");
|
|
|
|
|
result.Overlaps[0].Months.Should().BeGreaterThan(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithTwoCurrentJobs_DetectsLargeOverlap()
|
|
|
|
|
{
|
|
|
|
|
// Arrange - Two concurrent current positions (unusual but valid scenario)
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateCurrentEmployment("Company A", new DateOnly(2023, 1, 1)),
|
|
|
|
|
CreateCurrentEmployment("Company B", new DateOnly(2023, 6, 1))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert - Should detect substantial overlap since both run to today
|
|
|
|
|
result.Overlaps.Should().HaveCount(1);
|
|
|
|
|
result.TotalOverlapMonths.Should().BeGreaterThan(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithGapBeforeCurrentJob_DetectsGap()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2023, 1, 1), new DateOnly(2023, 6, 30)),
|
|
|
|
|
CreateCurrentEmployment("Company B", new DateOnly(2024, 1, 1))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Gaps.Should().HaveCount(1);
|
|
|
|
|
result.Gaps[0].Months.Should().BeGreaterThanOrEqualTo(6);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Edge Cases and Ordering Tests
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithUnsortedEmployment_SortsByStartDate()
|
|
|
|
|
{
|
|
|
|
|
// Arrange - Jobs provided out of order
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company C", new DateOnly(2022, 1, 1), new DateOnly(2022, 12, 31)),
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 30)),
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2021, 1, 1), new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert - Should detect gap between A (ends June 2020) and B (starts Jan 2021)
|
|
|
|
|
result.Gaps.Should().HaveCount(1);
|
|
|
|
|
result.Gaps[0].StartDate.Should().Be(new DateOnly(2020, 6, 30));
|
|
|
|
|
result.Gaps[0].EndDate.Should().Be(new DateOnly(2021, 1, 1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithSameStartDate_HandlesCorrectly()
|
|
|
|
|
{
|
|
|
|
|
// Arrange - Two jobs starting on same date
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 12, 31)),
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2020, 1, 1), new DateOnly(2020, 8, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert - Should detect overlap
|
|
|
|
|
result.Overlaps.Should().HaveCount(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithNullList_ThrowsArgumentNullException()
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
List<EmploymentEntry>? employmentHistory = null;
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var act = () => _sut.Analyse(employmentHistory!);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
act.Should().Throw<ArgumentNullException>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithExactlyThreeMonthGap_DetectsGap()
|
|
|
|
|
{
|
|
|
|
|
// Arrange - Exactly 3 month gap (boundary condition)
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 3, 31)),
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2020, 7, 1), new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Gaps.Should().HaveCount(1);
|
|
|
|
|
result.Gaps[0].Months.Should().BeGreaterThanOrEqualTo(3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithJobEndingSameDayNextStarts_NoGap()
|
|
|
|
|
{
|
|
|
|
|
// Arrange - Seamless transition
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 6, 30)),
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2020, 6, 30), new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Gaps.Should().BeEmpty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Analyse_WithJobEndingAfterNextStarts_DetectsOverlapIfSignificant()
|
|
|
|
|
{
|
|
|
|
|
// Arrange - Job ends after next starts by more than 2 months
|
|
|
|
|
var employmentHistory = new List<EmploymentEntry>
|
|
|
|
|
{
|
|
|
|
|
CreateEmployment("Company A", new DateOnly(2020, 1, 1), new DateOnly(2020, 10, 31)),
|
|
|
|
|
CreateEmployment("Company B", new DateOnly(2020, 6, 1), new DateOnly(2021, 12, 31))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = _sut.Analyse(employmentHistory);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
result.Overlaps.Should().HaveCount(1);
|
|
|
|
|
result.Overlaps[0].Months.Should().BeGreaterThan(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Helper Methods
|
|
|
|
|
|
|
|
|
|
private static EmploymentEntry CreateEmployment(string companyName, DateOnly? startDate, DateOnly? endDate)
|
|
|
|
|
{
|
|
|
|
|
return new EmploymentEntry
|
|
|
|
|
{
|
|
|
|
|
CompanyName = companyName,
|
|
|
|
|
JobTitle = "Software Developer",
|
|
|
|
|
StartDate = startDate,
|
|
|
|
|
EndDate = endDate,
|
|
|
|
|
IsCurrent = false
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static EmploymentEntry CreateCurrentEmployment(string companyName, DateOnly startDate)
|
|
|
|
|
{
|
|
|
|
|
return new EmploymentEntry
|
|
|
|
|
{
|
|
|
|
|
CompanyName = companyName,
|
|
|
|
|
JobTitle = "Software Developer",
|
|
|
|
|
StartDate = startDate,
|
|
|
|
|
EndDate = null,
|
|
|
|
|
IsCurrent = true
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
}
|