основное сделано
This commit is contained in:
+99
@@ -0,0 +1,99 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user