100 lines
4.1 KiB
C#
100 lines
4.1 KiB
C#
using System.Text.Json;
|
||
using Shouldly;
|
||
using Tnsu.ApprovalRoute.Infrastructure.SharePoint;
|
||
|
||
namespace Tnsu.ApprovalRoute.IntegrationTests.Infrastructure;
|
||
|
||
/// <summary>
|
||
/// Разбор ответов SharePoint REST — единственная часть SharePointClient, которую
|
||
/// можно проверить без живого тенанта: сам HTTP-вызов неотделим от аутентификации
|
||
/// по сертификату (ClientCertificateCredential ходит в Azure AD за токеном перед
|
||
/// каждым запросом), а вот разбор уже полученного JSON — чистая функция.
|
||
///
|
||
/// Это самое непроверенное место всей интеграции: форма ответа Search REST
|
||
/// (_api/search/query) задокументирована, но подтвердить её точное соответствие
|
||
/// без реального тенанта нельзя — только этими тестами на образцах ответа.
|
||
/// </summary>
|
||
public sealed class SharePointClientParsingTests
|
||
{
|
||
private static JsonElement Parse(string json) => JsonDocument.Parse(json).RootElement;
|
||
|
||
[Fact(DisplayName = "Search: с odata=nometadata Rows/Cells приходят голыми массивами")]
|
||
public void ParseSearchListItemIds_BareArrays_ReturnsIds()
|
||
{
|
||
const string response = """
|
||
{
|
||
"PrimaryQueryResult": {
|
||
"RelevantResults": {
|
||
"RowCount": 2,
|
||
"Table": {
|
||
"Rows": [
|
||
{ "Cells": [ { "Key": "Rank", "Value": "1" }, { "Key": "ListItemID", "Value": "11" } ] },
|
||
{ "Cells": [ { "Key": "ListItemID", "Value": "27" }, { "Key": "Rank", "Value": "2" } ] }
|
||
]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
""";
|
||
|
||
var ids = SharePointClient.ParseSearchListItemIds(Parse(response));
|
||
|
||
ids.ShouldBe([11, 27]);
|
||
}
|
||
|
||
[Fact(DisplayName = "Search: без odata=nometadata Rows/Cells завёрнуты в {results: [...]}")]
|
||
public void ParseSearchListItemIds_WrappedResults_ReturnsIds()
|
||
{
|
||
const string response = """
|
||
{
|
||
"PrimaryQueryResult": {
|
||
"RelevantResults": {
|
||
"Table": {
|
||
"Rows": {
|
||
"results": [
|
||
{ "Cells": { "results": [ { "Key": "ListItemID", "Value": "42" } ] } }
|
||
]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
""";
|
||
|
||
var ids = SharePointClient.ParseSearchListItemIds(Parse(response));
|
||
|
||
ids.ShouldBe([42]);
|
||
}
|
||
|
||
[Fact(DisplayName = "Search: нет совпадений — пустой список, а не исключение")]
|
||
public void ParseSearchListItemIds_NoRows_ReturnsEmpty()
|
||
{
|
||
const string response = """{ "PrimaryQueryResult": { "RelevantResults": { "RowCount": 0, "Table": { "Rows": [] } } } } """;
|
||
|
||
SharePointClient.ParseSearchListItemIds(Parse(response)).ShouldBeEmpty();
|
||
}
|
||
|
||
[Fact(DisplayName = "Search: неожиданная форма ответа не роняет разбор")]
|
||
public void ParseSearchListItemIds_UnexpectedShape_ReturnsEmpty()
|
||
{
|
||
SharePointClient.ParseSearchListItemIds(Parse("""{ "odata.error": "что-то не так" }""")).ShouldBeEmpty();
|
||
}
|
||
|
||
[Fact(DisplayName = "RootFolder/ServerRelativeUrl разбирается для условия Path в KQL")]
|
||
public void ParseServerRelativeUrl_ReturnsValue()
|
||
{
|
||
const string response = """
|
||
{ "RootFolder": { "ServerRelativeUrl": "/sites/requests/Lists/Заявка тест — согласование" } }
|
||
""";
|
||
|
||
SharePointClient.ParseServerRelativeUrl(Parse(response))
|
||
.ShouldBe("/sites/requests/Lists/Заявка тест — согласование");
|
||
}
|
||
|
||
[Fact(DisplayName = "Список без RootFolder в ответе — null, а не исключение")]
|
||
public void ParseServerRelativeUrl_Missing_ReturnsNull()
|
||
{
|
||
SharePointClient.ParseServerRelativeUrl(Parse("{}")).ShouldBeNull();
|
||
}
|
||
}
|