пофикшен баг
This commit is contained in:
@@ -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) =>
|
||||||
|
|||||||
+22
-1
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user