Files
RealCV/tests/RealCV.Tests/Services/EducationVerifierServiceTests.cs
Peter Foster fab1866fc8 feat: Detect fake UK universities using naming patterns
Add detection for institutions that follow UK university naming
conventions (e.g., "University of the Peak District") but aren't
in the recognised institutions list. These are now flagged as
"Suspicious" with a -15 point penalty instead of just "Unknown".

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 08:11:08 +00:00

443 lines
12 KiB
C#

using FluentAssertions;
using RealCV.Application.Models;
using RealCV.Infrastructure.Services;
namespace RealCV.Tests.Services;
public sealed class EducationVerifierServiceTests
{
private readonly EducationVerifierService _sut = new();
#region Unaccredited Institution Detection
[Theory]
[InlineData("Belford University")]
[InlineData("Ashwood University")]
[InlineData("Rochville University")]
[InlineData("St Regis University")]
public void Verify_UnaccreditedInstitution_ReturnsUnaccredited(string institution)
{
// Arrange
var education = new EducationEntry
{
Institution = institution,
Qualification = "PhD",
Subject = "Business",
StartDate = new DateOnly(2020, 1, 1),
EndDate = new DateOnly(2020, 6, 1)
};
// Act
var result = _sut.Verify(education);
// Assert
result.Status.Should().Be("Unaccredited");
result.IsUnaccredited.Should().BeTrue();
result.IsSuspicious.Should().BeTrue();
result.IsVerified.Should().BeFalse();
}
[Fact]
public void Verify_UnaccreditedInstitution_IncludesVerificationNotes()
{
// Arrange
var education = new EducationEntry
{
Institution = "Belford University",
Qualification = "MBA"
};
// Act
var result = _sut.Verify(education);
// Assert
result.VerificationNotes.Should().Contain("QAA/HESA register");
}
#endregion
#region Suspicious Pattern Detection
[Theory]
[InlineData("Global Online University")]
[InlineData("Premier University of Excellence")]
[InlineData("Executive Virtual University")]
public void Verify_SuspiciousPatternInstitution_ReturnsSuspicious(string institution)
{
// Arrange
var education = new EducationEntry
{
Institution = institution
};
// Act
var result = _sut.Verify(education);
// Assert
result.Status.Should().Be("Suspicious");
result.IsSuspicious.Should().BeTrue();
result.IsUnaccredited.Should().BeFalse();
result.IsVerified.Should().BeFalse();
}
[Theory]
[InlineData("University of the Peak District")]
[InlineData("University of the Cotswolds")]
[InlineData("University of the Lake District")]
[InlineData("University of the Dales")]
[InlineData("Sheffield Metropolitan University")] // Uses UK pattern but doesn't exist
public void Verify_FakeUKInstitution_ReturnsSuspicious(string institution)
{
// Arrange
var education = new EducationEntry
{
Institution = institution
};
// Act
var result = _sut.Verify(education);
// Assert
result.Status.Should().Be("Suspicious");
result.IsSuspicious.Should().BeTrue();
result.IsVerified.Should().BeFalse();
result.VerificationNotes.Should().Contain("UK university naming convention");
}
#endregion
#region UK Institution Recognition
[Theory]
[InlineData("University of Cambridge", "University of Cambridge")]
[InlineData("Cambridge", "University of Cambridge")]
[InlineData("University of Oxford", "University of Oxford")]
[InlineData("Oxford", "University of Oxford")]
[InlineData("Imperial College London", "Imperial College London")]
[InlineData("UCL", "UCL")] // UCL is directly in the recognised list
[InlineData("LSE", "LSE")] // LSE is directly in the recognised list
public void Verify_RecognisedUKInstitution_ReturnsRecognised(string input, string expectedMatch)
{
// Arrange
var education = new EducationEntry
{
Institution = input,
Qualification = "BSc",
StartDate = new DateOnly(2018, 9, 1),
EndDate = new DateOnly(2021, 6, 1)
};
// Act
var result = _sut.Verify(education);
// Assert
result.Status.Should().Be("Recognised");
result.IsVerified.Should().BeTrue();
result.IsUnaccredited.Should().BeFalse();
result.IsSuspicious.Should().BeFalse();
result.MatchedInstitution.Should().Be(expectedMatch);
}
[Fact]
public void Verify_RecognisedInstitution_IncludesVerificationNotes()
{
// Arrange
var education = new EducationEntry
{
Institution = "University of Manchester"
};
// Act
var result = _sut.Verify(education);
// Assert
result.VerificationNotes.Should().Contain("Verified UK higher education institution");
}
[Fact]
public void Verify_RecognisedInstitutionVariation_NotesMatchedName()
{
// Arrange
var education = new EducationEntry
{
Institution = "Cambridge"
};
// Act
var result = _sut.Verify(education);
// Assert
result.VerificationNotes.Should().Contain("Matched to official name");
result.MatchedInstitution.Should().Be("University of Cambridge");
}
#endregion
#region Unknown Institutions
[Fact]
public void Verify_UnknownInstitution_ReturnsUnknown()
{
// Arrange
var education = new EducationEntry
{
Institution = "University of Ljubljana",
Qualification = "BSc"
};
// Act
var result = _sut.Verify(education);
// Assert
result.Status.Should().Be("Unknown");
result.IsVerified.Should().BeFalse();
result.IsUnaccredited.Should().BeFalse();
result.IsSuspicious.Should().BeFalse();
result.VerificationNotes.Should().Contain("international institution");
}
#endregion
#region Date Plausibility
[Fact]
public void Verify_PlausibleDates_ReturnsPlausible()
{
// Arrange
var education = new EducationEntry
{
Institution = "University of Bristol",
StartDate = new DateOnly(2018, 9, 1),
EndDate = new DateOnly(2021, 6, 1)
};
// Act
var result = _sut.Verify(education);
// Assert
result.DatesArePlausible.Should().BeTrue();
result.DatePlausibilityNotes.Should().BeNull();
}
[Fact]
public void Verify_TooShortCourseDuration_ReturnsImplausible()
{
// Arrange
var education = new EducationEntry
{
Institution = "University of Bristol",
StartDate = new DateOnly(2020, 1, 1),
EndDate = new DateOnly(2020, 6, 1) // 6 months
};
// Act
var result = _sut.Verify(education);
// Assert
result.DatesArePlausible.Should().BeFalse();
result.DatePlausibilityNotes.Should().Contain("unusually short");
}
[Fact]
public void Verify_TooLongCourseDuration_ReturnsImplausible()
{
// Arrange
var education = new EducationEntry
{
Institution = "University of Bristol",
StartDate = new DateOnly(2010, 1, 1),
EndDate = new DateOnly(2020, 1, 1) // 10 years
};
// Act
var result = _sut.Verify(education);
// Assert
result.DatesArePlausible.Should().BeFalse();
result.DatePlausibilityNotes.Should().Contain("unusually long");
}
[Fact]
public void Verify_EndDateBeforeStartDate_ReturnsImplausible()
{
// Arrange
var education = new EducationEntry
{
Institution = "University of Bristol",
StartDate = new DateOnly(2021, 1, 1),
EndDate = new DateOnly(2020, 1, 1)
};
// Act
var result = _sut.Verify(education);
// Assert
result.DatesArePlausible.Should().BeFalse();
result.DatePlausibilityNotes.Should().Contain("before or equal to start date");
}
[Fact]
public void Verify_NoDates_AssumesPlausible()
{
// Arrange
var education = new EducationEntry
{
Institution = "University of Bristol"
};
// Act
var result = _sut.Verify(education);
// Assert
result.DatesArePlausible.Should().BeTrue();
}
#endregion
#region VerifyAll
[Fact]
public void VerifyAll_MultipleEducations_ReturnsResultsForEach()
{
// Arrange
var educations = new List<EducationEntry>
{
new() { Institution = "University of Cambridge" },
new() { Institution = "Belford University" },
new() { Institution = "Unknown Foreign University" }
};
// Act
var results = _sut.VerifyAll(educations);
// Assert
results.Should().HaveCount(3);
results[0].Status.Should().Be("Recognised");
results[1].Status.Should().Be("Unaccredited");
results[2].Status.Should().Be("Unknown");
}
[Fact]
public void VerifyAll_OverlappingEducation_NotesOverlap()
{
// Arrange
var educations = new List<EducationEntry>
{
new()
{
Institution = "University of Bristol",
StartDate = new DateOnly(2018, 9, 1),
EndDate = new DateOnly(2021, 6, 1)
},
new()
{
Institution = "University of Bath",
StartDate = new DateOnly(2020, 9, 1),
EndDate = new DateOnly(2023, 6, 1)
}
};
// Act
var results = _sut.VerifyAll(educations);
// Assert
results[0].DatePlausibilityNotes.Should().Contain("Overlaps with");
results[1].DatePlausibilityNotes.Should().Contain("Overlaps with");
}
[Fact]
public void VerifyAll_EmploymentBeforeGraduation_ChecksTimeline()
{
// Arrange
var educations = new List<EducationEntry>
{
new()
{
Institution = "University of Bristol",
StartDate = new DateOnly(2018, 9, 1),
EndDate = new DateOnly(2021, 6, 1)
}
};
var employment = new List<EmploymentEntry>
{
new()
{
CompanyName = "Tech Corp",
JobTitle = "Senior Developer",
StartDate = new DateOnly(2018, 1, 1) // Started before education started
}
};
// Act
var results = _sut.VerifyAll(educations, employment);
// Assert
results[0].DatesArePlausible.Should().BeFalse();
results[0].DatePlausibilityNotes.Should().Contain("months before claimed graduation");
}
[Fact]
public void VerifyAll_InternshipBeforeGraduation_AllowsTimeline()
{
// Arrange
var educations = new List<EducationEntry>
{
new()
{
Institution = "University of Bristol",
StartDate = new DateOnly(2018, 9, 1),
EndDate = new DateOnly(2021, 6, 1)
}
};
var employment = new List<EmploymentEntry>
{
new()
{
CompanyName = "Tech Corp",
JobTitle = "Software Intern",
StartDate = new DateOnly(2019, 6, 1)
}
};
// Act
var results = _sut.VerifyAll(educations, employment);
// Assert
// Should be plausible because it's an internship
results[0].DatesArePlausible.Should().BeTrue();
}
#endregion
#region Data Preservation
[Fact]
public void Verify_PreservesAllClaimedData()
{
// Arrange
var education = new EducationEntry
{
Institution = "University of Bristol",
Qualification = "BSc Computer Science",
Subject = "Computer Science",
Grade = "First Class Honours",
StartDate = new DateOnly(2018, 9, 1),
EndDate = new DateOnly(2021, 6, 1)
};
// Act
var result = _sut.Verify(education);
// Assert
result.ClaimedInstitution.Should().Be("University of Bristol");
result.ClaimedQualification.Should().Be("BSc Computer Science");
result.ClaimedSubject.Should().Be("Computer Science");
result.ClaimedStartDate.Should().Be(new DateOnly(2018, 9, 1));
result.ClaimedEndDate.Should().Be(new DateOnly(2021, 6, 1));
}
#endregion
}