пофикшен баг
Tests / frontend-test (pull_request) Successful in 43s
Tests / backend-test (pull_request) Has been cancelled

This commit is contained in:
gorbenko.pavel
2026-08-12 17:49:35 +05:00
parent a92f2e4978
commit d48a2d696e
2 changed files with 61 additions and 5 deletions
@@ -1,3 +1,4 @@
using System.Globalization;
using System.Text.Json; using System.Text.Json;
namespace Tnsu.ApprovalRoute.Application.Reminders; namespace Tnsu.ApprovalRoute.Application.Reminders;
@@ -44,19 +45,53 @@ internal sealed record ApprovalRow(
private static string? GetString(JsonElement e, string name) => private static string? GetString(JsonElement e, string name) =>
e.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.String ? v.GetString() : null; e.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.String ? v.GetString() : null;
/// <summary>
/// Целое из JSON — с поправкой на то, КАК SharePoint отдаёт Number-колонки.
///
/// Одного TryGetInt32 здесь мало: у колонки типа Number значение приходит
/// дробным («1.0», а не «1»), а TryGetInt32 на числе с десятичной точкой
/// возвращает false — и поле молча становилось null. На фронтенде этой
/// проблемы нет вовсе: в JavaScript 1.0 и 1 — одно и то же число, поэтому
/// там те же данные читались верно, а на бэкенде терялись.
///
/// Строку тоже принимаем: с odata=verbose (и у части полей) SharePoint
/// отдаёт числа строками.
/// </summary>
private static bool TryGetInt(JsonElement e, string name, out int value) private static bool TryGetInt(JsonElement e, string name, out int value)
{ {
value = 0; value = 0;
return e.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.Number && v.TryGetInt32(out value); if (!e.TryGetProperty(name, out var v)) return false;
if (v.ValueKind == JsonValueKind.Number)
{
if (v.TryGetInt32(out value)) return true;
if (!v.TryGetDouble(out var d) || d is < int.MinValue or > int.MaxValue) return false;
value = (int)Math.Round(d);
return true;
}
if (v.ValueKind != JsonValueKind.String) return false;
if (!double.TryParse(v.GetString(), NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed))
return false;
if (parsed is < int.MinValue or > int.MaxValue) return false;
value = (int)Math.Round(parsed);
return true;
} }
private static bool TryGetBool(JsonElement e, string name, out bool value) private static bool TryGetBool(JsonElement e, string name, out bool value)
{ {
value = false; value = false;
if (!e.TryGetProperty(name, out var v)) return false; if (!e.TryGetProperty(name, out var v)) return false;
if (v.ValueKind is not (JsonValueKind.True or JsonValueKind.False)) return false;
value = v.GetBoolean(); if (v.ValueKind is JsonValueKind.True or JsonValueKind.False)
return true; {
value = v.GetBoolean();
return true;
}
// Да/Нет-колонка тоже может прийти строкой («true»/«false») — см. TryGetInt.
return v.ValueKind == JsonValueKind.String && bool.TryParse(v.GetString(), out value);
} }
private static DateTimeOffset? TryGetDate(JsonElement e, string name) => private static DateTimeOffset? TryGetDate(JsonElement e, string name) =>
@@ -54,7 +54,7 @@ public sealed class ReminderServiceTests : IClassFixture<PostgresDatabaseFixture
private static object Row( private static object Row(
int id, int ticketId, int stepOrder, string status, string phase, int id, int ticketId, int stepOrder, string status, string phase,
string? date = null, string email = "approver@tnsu.kz", int? slaHours = null, bool? ask = null) => string? date = null, string email = "approver@tnsu.kz", object? slaHours = null, bool? ask = null) =>
new new
{ {
Id = id, Id = id,
@@ -207,6 +207,27 @@ public sealed class ReminderServiceTests : IClassFixture<PostgresDatabaseFixture
result.Value.ShouldHaveSingleItem().HoursOverdue.ShouldBe(5); result.Value.ShouldHaveSingleItem().HoursOverdue.ShouldBe(5);
} }
// SharePoint отдаёт Number-колонки ДРОБНЫМИ («1.0», а не «1»), и на этом
// разбор спотыкался: JsonElement.TryGetInt32 на числе с десятичной точкой
// возвращает false, поле молча становилось null и SLA строки терялся.
// На фронтенде проблемы не было — в JavaScript 1.0 и 1 неразличимы.
[Fact(DisplayName = "SLA строки читается, даже когда SharePoint отдаёт его дробным числом")]
public async Task RowSla_WrittenAsDecimalNumber_IsStillRead()
{
var listUrl = await CreateTypeWithSlaAsync("Sla decimal", slaHours: 100);
// По типу 100 часов — просрочки не было бы; в строке 1 час — просрочка 5 часов.
var service = BuildService(listUrl,
// Именно decimal: System.Text.Json сохраняет у него хвостовой ноль и
// пишет «1.0», тогда как double 1.0 сериализуется в «1» и баг не
// воспроизводит.
[Row(10, 1, 1, "На согласовании", "Согласование", slaHours: 1.0m)],
requestCreated: Now.AddHours(-6));
var result = await service.GetOverdueAsync(Now, CancellationToken.None);
result.Value.ShouldHaveSingleItem().HoursOverdue.ShouldBe(5);
}
[Fact(DisplayName = "Флаг опроса причины наследуется от типа заявки, если в строке не задан")] [Fact(DisplayName = "Флаг опроса причины наследуется от типа заявки, если в строке не задан")]
public async Task AskOverdueReason_InheritsFromRequestType() public async Task AskOverdueReason_InheritsFromRequestType()
{ {