Files
SGU-CredentialProvider/tests/SGU.AuthBroker.Core.Tests/SguProfileParserTests.cs
T

476 lines
20 KiB
C#

using SGU.AuthBroker.Core.Profiles;
using Xunit;
namespace SGU.AuthBroker.Core.Tests;
public sealed class SguProfileParserTests
{
[Fact]
public void ParsesOnlyTheRequiredAdministrativeFieldsFromTheInitialResponse()
{
const string html = """
<html><body>
<span id="ctl00_contenedor_decEncabezado_lblNombre">
017045 - JESÚS&nbsp;ALEJANDRO ROSALES GONZÁLEZ
</span>
<span id="ctl00_contenedor_decEncabezado_lblIndicadorValue">
SINDICALIZADO QUINCENAL (ACTIVO)
</span>
<span id="ctl00_contenedor_decEncabezado_lblCorreo">
<a href="mailto:persona@lasalle.mx">persona@lasalle.mx</a>
</span>
<div style="display:none">
<span id="ctl00_contenedor_decEncabezado_lblPuesto">
ANALISTA DE PROYECTOS
</span>
<span id="ctl00_contenedor_decEncabezado_lblDependencia">
FACULTAD DE INGENIERÍA
</span>
<span id="ctl00_contenedor_decEncabezado_lblJefeNombre">
ESTE CAMPO NO DEBE EXTRAERSE
</span>
</div>
<table id="incidencias"><tr><td>DATOS NO REQUERIDOS</td></tr></table>
</body></html>
""";
InstitutionalProfile? profile = SguProfileParser.ParseAdministrative(html, "017045");
Assert.NotNull(profile);
Assert.Equal("017045", profile.EmployeeNumber);
Assert.Equal("Jesús Alejandro Rosales González", profile.DisplayName);
Assert.Equal("persona@lasalle.mx", profile.Email);
Assert.Equal("Sindicalizado quincenal (activo)", profile.EmployeeType);
Assert.Equal("Analista de Proyectos", profile.JobTitle);
Assert.Equal("Facultad de Ingeniería", profile.Department);
}
[Fact]
public void RejectsAdministrativeMetadataForADifferentEmployeeNumber()
{
const string html = """
<span id='ctl00_contenedor_decEncabezado_lblNombre'>017045 - PERSONA INCORRECTA</span>
<span id='ctl00_contenedor_decEncabezado_lblCorreo'>incorrecta@lasalle.mx</span>
""";
Assert.Null(SguProfileParser.ParseAdministrative(html, "999999"));
}
[Fact]
public void ParsesOnlyTheSupportedProfessorPayrollHeaderFields()
{
const string html = """
<div id="ctl00_contenedor_decEncabezado_pnlSinPoP">
<span id="ctl00_contenedor_decEncabezado_lblNombre">
013473 - ALEJANDRO LARA VILLARREAL
</span>
<span id="ctl00_contenedor_decEncabezado_lblIndicadorValue">
SINDICALIZADO QUINCENAL (ACTIVO)
</span>
<span id="ctl00_contenedor_decEncabezado_lblCorreo">
alejandro.lara@lasallistas.org.mx
</span>
<img id="ctl00_contenedor_decEncabezado_imgFoto"
src="../admonPersonal/ashx/Fotografia.ashx?id=013473&amp;tp=2" />
<span id="ctl00_contenedor_decEncabezado_lblPuesto">DOCENTE</span>
<span id="ctl00_contenedor_decEncabezado_lblDependencia"></span>
<span id="ctl00_contenedor_decEncabezado_lblJefeNombre">NO EXTRAER</span>
<span id="ctl00_contenedor_decEncabezado_lblJefePuesto">NO EXTRAER</span>
</div>
""";
InstitutionalProfile? profile = SguProfileParser.ParseProfessorPayroll(html, "013473");
Assert.NotNull(profile);
Assert.Equal("013473", profile.EmployeeNumber);
Assert.Equal("Alejandro Lara Villarreal", profile.DisplayName);
Assert.Equal("alejandro.lara@lasallistas.org.mx", profile.Email);
Assert.Equal("Sindicalizado quincenal (activo)", profile.EmployeeType);
Assert.Equal("Docente", profile.JobTitle);
Assert.Null(profile.Department);
Assert.Null(profile.GivenName);
Assert.Null(profile.Surname);
Assert.Null(profile.StreetAddress);
}
[Fact]
public void RejectsProfessorPayrollMetadataForADifferentEmployeeNumber()
{
const string html = """
<span id="ctl00_contenedor_decEncabezado_lblNombre">
013473 - PERSONA INCORRECTA
</span>
<span id="ctl00_contenedor_decEncabezado_lblCorreo">
incorrecta@lasallistas.org.mx
</span>
""";
Assert.Null(SguProfileParser.ParseProfessorPayroll(html, "123456"));
}
[Fact]
public void ParsesStructuredAdministrativeNameWithoutReadingOtherPersonalData()
{
const string html = """
<html><body>
<input id="ctl00_contenedor_txtNombre" value="MARÍA DEL CARMEN" />
<input id="ctl00_contenedor_txtApaterno" value="DE LA FUENTE" />
<input id="ctl00_contenedor_txtAmaterno" value="O&#39;CONNOR" />
<input id="ctl00_contenedor_txtCURP" value="DATO-QUE-NO-DEBE-EXTRAERSE" />
<select id="ctl00_contenedor_ddlNacimientoFecha_ddlYear">
<option selected="selected">1990</option>
</select>
</body></html>
""";
InstitutionalProfile? profile = SguProfileParser.ParseAdministrativePersonal(html);
Assert.NotNull(profile);
Assert.Equal("María del Carmen", profile.GivenName);
Assert.Equal("de la Fuente O'Connor", profile.Surname);
Assert.Equal("María del Carmen de la Fuente O'Connor", profile.DisplayName);
Assert.Null(profile.EmployeeNumber);
Assert.Null(profile.Email);
}
[Theory]
[InlineData("ctl00_contenedor_ddlsexo", "ctl00$contenedor$ddlsexo", "1", InstitutionalGender.Male)]
[InlineData("alternate-id", "ctl00$contenedor$ddlsexo", "2", InstitutionalGender.Female)]
public void ParsesStaffGenderFromTheSelectedPersonalDataOption(
string id,
string name,
string selectedValue,
InstitutionalGender expected)
{
string html = $"""
<select id="{id}" name="{name}">
<option value="">Seleccione...</option>
<option value="1"{(selectedValue == "1" ? " selected=\"selected\"" : string.Empty)}>Masculino</option>
<option value="2"{(selectedValue == "2" ? " selected=\"selected\"" : string.Empty)}>Femenino</option>
</select>
""";
InstitutionalProfile? profile = SguProfileParser.ParseAdministrativePersonal(html);
Assert.NotNull(profile);
Assert.Equal(expected, profile.Gender);
}
[Theory]
[InlineData("M", InstitutionalGender.Male)]
[InlineData("f", InstitutionalGender.Female)]
public void ParsesStudentGenderFromTheInformationSpan(
string source,
InstitutionalGender expected)
{
string html = $"""
<span id="ctl00_contenedor_HistorialAlumno1_lblClaveAlumnoHP">123456</span>
<span id="ctl00_contenedor_HistorialAlumno1_lblSexoAlumnoHP">{source}</span>
""";
InstitutionalProfile? profile = SguProfileParser.ParseStudent(html, "123456");
Assert.NotNull(profile);
Assert.Equal(expected, profile.Gender);
}
[Fact]
public void IgnoresUnknownGenderValuesWithoutFailingProfileParsing()
{
const string html = """
<input id="ctl00_contenedor_txtNombre" value="PERSONA" />
<select id="ctl00_contenedor_ddlsexo">
<option selected="selected" value="9">SIN CLASIFICAR</option>
</select>
""";
InstitutionalProfile? profile = SguProfileParser.ParseAdministrativePersonal(html);
Assert.NotNull(profile);
Assert.Equal("Persona", profile.DisplayName);
Assert.Null(profile.Gender);
}
[Fact]
public void ParsesAdministrativeAddressFromInputsAndSelectedOptions()
{
const string html = """
<html><body>
<input id='ctl00_contenedor_txtCalle' value='AVENIDA DE LA UNIVERSIDAD' />
<input id='ctl00_contenedor_txtNoExt' value='123' />
<input id='ctl00_contenedor_txtNoInt' value='B-4' />
<input id='ctl00_contenedor_txtCP' value='8500' />
<select id='ctl00_contenedor_ddlEstado'>
<option value='0'>Seleccione...</option>
<option selected='selected' value='9'>CIUDAD DE MÉXICO</option>
</select>
<select id='ctl00_contenedor_ddlLocalidad'>
<option value='1'>AZCAPOTZALCO</option>
<option selected='selected' value='4'>BENITO JUÁREZ</option>
</select>
<select id='ctl00_contenedor_ddlColonia'>
<option value='10'>COLONIA DEL VALLE</option>
</select>
<input id='ctl00_contenedor_txtTelefono' value='5555555555' />
<textarea id='ctl00_contenedor_txtDirEmergencia'>NO EXTRAER</textarea>
</body></html>
""";
InstitutionalProfile? profile = SguProfileParser.ParseAdministrativeLocation(html);
Assert.NotNull(profile);
Assert.Equal(
"Avenida de la Universidad 123, Int. B-4\r\nColonia del Valle",
profile.StreetAddress);
Assert.Equal("Benito Juárez", profile.City);
Assert.Equal("Ciudad de México", profile.State);
Assert.Equal("08500", profile.PostalCode);
Assert.Null(profile.Email);
}
[Fact]
public void ResolvesAdministrativeAddressFromPageMethodIdentifiers()
{
const string html = """
<html><body>
<input id='ctl00_contenedor_txtCalle' value='RETORNO 1, SUR 16' />
<input id='ctl00_contenedor_txtNoExt' value='74' />
<input id='ctl00_contenedor_txtNoInt' value='' />
<input id='ctl00_contenedor_txtCP' value='08500' />
<select id='ctl00_contenedor_ddlEstado'>
<option selected='selected' value='0'>Seleccione...</option>
<option value='09'>CIUDAD DE MÉXICO</option>
</select>
<select id='ctl00_contenedor_ddlLocalidad'>
<option selected='selected' value='0'>Seleccione alguna localidad...</option>
</select>
<select id='ctl00_contenedor_ddlColonia'>
<option selected='selected' value='0,0'>Seleccione alguna colonia...</option>
</select>
</body></html>
""";
const string directionJson = """
{"d":[{"p_IdCP":"091263","p_IdEstado":"09","p_NombreEstado":"","p_IdMunicipio":"006","p_NombreMunicipio":"","p_NombreColonia":"","p_Cp":"08500"}]}
""";
const string localitiesJson = """
{"d":[{"ID_Estado":"09","Id_Municipio":"002","Nombre":"AZCAPOTZALCO"},{"ID_Estado":"09","Id_Municipio":"006","Nombre":"IZTACALCO"}]}
""";
const string neighborhoodsJson = """
{"d":[{"p_IdCP":"091263","p_Nombre":"AGRÍCOLA ORIENTAL","p_Cp":"08500"}]}
""";
SguAdministrativeLocationSelection? selection =
SguProfileParser.ParseAdministrativeLocationSelection(directionJson, "08500");
InstitutionalProfile? profile = SguProfileParser.ParseAdministrativeLocation(
html,
selection,
localitiesJson,
neighborhoodsJson);
Assert.NotNull(selection);
Assert.Equal("09", selection.StateId);
Assert.Equal("006", selection.MunicipalityId);
Assert.Equal("091263", selection.NeighborhoodId);
Assert.NotNull(profile);
Assert.Equal("Retorno 1, Sur 16 74\r\nAgrícola Oriental", profile.StreetAddress);
Assert.Equal("Iztacalco", profile.City);
Assert.Equal("Ciudad de México", profile.State);
Assert.Equal("08500", profile.PostalCode);
}
[Theory]
[InlineData("not-json")]
[InlineData("{\"d\":{}}")]
[InlineData("{\"d\":[]}")]
public void RejectsUnexpectedAdministrativeLocationPayloads(string json)
{
Assert.Null(SguProfileParser.ParseAdministrativeLocationSelection(json, "08500"));
}
[Fact]
public void AdministrativePagesOverlayTheVerifiedIncidentsProfile()
{
InstitutionalProfile verified = new(
EmployeeNumber: "017045",
DisplayName: "Nombre Anterior",
Email: "persona@lasalle.mx",
JobTitle: "Analista",
Department: "Ingeniería");
InstitutionalProfile personal = new(
DisplayName: "María del Carmen de la Fuente",
GivenName: "María del Carmen",
Surname: "de la Fuente",
Gender: InstitutionalGender.Female);
InstitutionalProfile location = new(
StreetAddress: "Calle Uno 10",
City: "Ciudad de México",
State: "Ciudad de México",
PostalCode: "01000");
InstitutionalProfile combined = verified.Overlay(personal).Overlay(location);
Assert.Equal("017045", combined.EmployeeNumber);
Assert.Equal("María del Carmen de la Fuente", combined.DisplayName);
Assert.Equal("María del Carmen", combined.GivenName);
Assert.Equal("de la Fuente", combined.Surname);
Assert.Equal("persona@lasalle.mx", combined.Email);
Assert.Equal("Analista", combined.JobTitle);
Assert.Equal("Ingeniería", combined.Department);
Assert.Equal("Calle Uno 10", combined.StreetAddress);
Assert.Equal("01000", combined.PostalCode);
Assert.Equal(InstitutionalGender.Female, combined.Gender);
}
[Fact]
public void ParsesTheRequiredStudentIdentityCareerAndAddressFields()
{
const string html = """
<html><body>
<div id="ctl00_contenedor_HistorialAlumno1_div1">
<span id="ctl00_contenedor_HistorialAlumno1_lblClaveAlumnoHP">123456</span>
<span id="ctl00_contenedor_HistorialAlumno1_lblNombreAlumnoHP">MARÍA DEL CARMEN</span>
<span id="ctl00_contenedor_HistorialAlumno1_lblApPatAlumnoHP">DE LA FUENTE</span>
<span id="ctl00_contenedor_HistorialAlumno1_lblApMatAlumnoHP">O'CONNOR</span>
<span id="ctl00_contenedor_HistorialAlumno1_lblCorreoAlumnoHP">
<a href="mailto:alumna@lasalle.mx">ALUMNA@LASALLE.MX</a>
</span>
<span id="ctl00_contenedor_HistorialAlumno1_lblSexoAlumnoHP">F</span>
<span id="ctl00_contenedor_HistorialAlumno1_lblCURPAlumnoHP">
DATO-SENSIBLE-QUE-NO-DEBE-EXTRAERSE
</span>
</div>
<span id="ctl00_contenedor_HistorialAlumno1_Header1_lblCarrera">
LICENCIATURA EN INGENIERÍA CIBERNÉTICA Y SISTEMAS COMPUTACIO NALES
</span>
<div id="ctl00_contenedor_HistorialAlumno1_div2">
<span id="ctl00_contenedor_HistorialAlumno1_lblDomicilioAlumnoHP">
AVENIDA DE LA UNIVERSIDAD 123
</span>
<span id="ctl00_contenedor_HistorialAlumno1_lblColoniaAlumnoHP">
COLONIA DEL VALLE
</span>
<span id="ctl00_contenedor_HistorialAlumno1_lblEstadoAlumnoHP">
CIUDAD DE MÉXICO
</span>
<span id="ctl00_contenedor_HistorialAlumno1_lblCiudadAlumnoHP">
CIUDAD DE MÉXICO
</span>
<span id="ctl00_contenedor_HistorialAlumno1_lblDeloMunAlumnoHP">
ALCALDÍA BENITO JUÁREZ
</span>
<span id="ctl00_contenedor_HistorialAlumno1_lblCPAlumnoHP">8500</span>
</div>
</body></html>
""";
InstitutionalProfile? profile = SguProfileParser.ParseStudent(html, "123456");
Assert.NotNull(profile);
Assert.Equal("123456", profile.EmployeeNumber);
Assert.Equal("María del Carmen de la Fuente O'Connor", profile.DisplayName);
Assert.Equal("María del Carmen", profile.GivenName);
Assert.Equal("de la Fuente O'Connor", profile.Surname);
Assert.Equal("alumna@lasalle.mx", profile.Email);
Assert.Equal("Alumno", profile.EmployeeType);
Assert.Equal(
"Estudiante de Ingeniería Cibernética y Sistemas Computacionales",
profile.JobTitle);
Assert.Null(profile.Department);
Assert.Equal(
"Avenida de la Universidad 123\r\nColonia del Valle\r\nAlcaldía Benito Juárez",
profile.StreetAddress);
Assert.Equal("Ciudad de México", profile.City);
Assert.Equal("Ciudad de México", profile.State);
Assert.Equal("08500", profile.PostalCode);
Assert.Equal(InstitutionalGender.Female, profile.Gender);
}
[Fact]
public void RejectsStudentMetadataForADifferentStudentNumber()
{
const string html = """
<span id="ctl00_contenedor_HistorialAlumno1_lblClaveAlumnoHP">123456</span>
<span id="ctl00_contenedor_HistorialAlumno1_lblNombreAlumnoHP">PERSONA INCORRECTA</span>
<span id="ctl00_contenedor_HistorialAlumno1_lblCorreoAlumnoHP">incorrecta@lasalle.mx</span>
""";
Assert.Null(SguProfileParser.ParseStudent(html, "654321"));
}
[Fact]
public void UsesMunicipalityAsStudentCityWhenThePortalCityIsMissing()
{
const string html = """
<span id="ctl00_contenedor_HistorialAlumno1_lblClaveAlumnoHP">123456</span>
<span id="ctl00_contenedor_HistorialAlumno1_lblDeloMunAlumnoHP">SAN PEDRO CHOLULA</span>
<span id="ctl00_contenedor_HistorialAlumno1_lblCPAlumnoHP">72760</span>
""";
InstitutionalProfile? profile = SguProfileParser.ParseStudent(html, "123456");
Assert.NotNull(profile);
Assert.Equal("San Pedro Cholula", profile.City);
Assert.Equal("San Pedro Cholula", profile.StreetAddress);
Assert.Equal("72760", profile.PostalCode);
}
[Fact]
public void ParsesTheMenuNameAsAConservativeFallback()
{
const string html = """
<span class="usuario" id="ctl00_lblNombreUsuario">
MARÍA&nbsp;&amp;&nbsp;JOSÉ
</span>
""";
InstitutionalProfile? profile = SguProfileParser.ParseMenu(html);
Assert.NotNull(profile);
Assert.Equal("María & José", profile.DisplayName);
Assert.Null(profile.Email);
Assert.Null(profile.JobTitle);
Assert.Null(profile.Department);
}
[Theory]
[InlineData("MARÍA DEL CARMEN", "María del Carmen")]
[InlineData("MIGUEL DE CERVANTES", "Miguel de Cervantes")]
[InlineData("CARLOS DE LA FUENTE", "Carlos de la Fuente")]
[InlineData("MARÍA-JOSÉ O'CONNOR", "María-José O'Connor")]
public void PreservesSpanishNameParticlesAndAccents(string source, string expected)
{
const string marker = "ctl00_lblNombreUsuario";
InstitutionalProfile? profile = SguProfileParser.ParseMenu(
$"<span id=\"{marker}\">{source}</span>");
Assert.NotNull(profile);
Assert.Equal(expected, profile.DisplayName);
}
[Fact]
public void RejectsReplacementCharactersInsteadOfWritingCorruptMetadata()
{
const string html = """
<span id="ctl00_contenedor_decEncabezado_lblNombre">017045 - JESS ROSALES</span>
<span id="ctl00_contenedor_decEncabezado_lblPuesto">ANALISTA DE INMERSIN</span>
""";
InstitutionalProfile? profile = SguProfileParser.ParseAdministrative(html, "017045");
Assert.NotNull(profile);
Assert.Null(profile.DisplayName);
Assert.Null(profile.JobTitle);
Assert.Equal("017045", profile.EmployeeNumber);
}
[Fact]
public void MissingKnownFieldsProducesNoProfile()
{
Assert.Null(SguProfileParser.ParseMenu("<html><body>Portal SGU</body></html>"));
Assert.Null(SguProfileParser.ParseAdministrative(
"<html><body>Portal SGU</body></html>",
"017045"));
}
}