test(19-01): add unit tests for AppRegistrationService and models
- AppRegistrationResult factory methods (Success/Failure/FallbackRequired) - TenantProfile.AppId null default and JSON round-trip - AppRegistrationService implements IAppRegistrationService - BuildRequiredResourceAccess structure (2 resources, 4+1 scopes, all Scope type)
This commit is contained in:
178
SharepointToolbox.Tests/Services/AppRegistrationServiceTests.cs
Normal file
178
SharepointToolbox.Tests/Services/AppRegistrationServiceTests.cs
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Moq;
|
||||||
|
using SharepointToolbox.Core.Models;
|
||||||
|
using SharepointToolbox.Services;
|
||||||
|
using AppGraphClientFactory = SharepointToolbox.Infrastructure.Auth.GraphClientFactory;
|
||||||
|
using AppMsalClientFactory = SharepointToolbox.Infrastructure.Auth.MsalClientFactory;
|
||||||
|
|
||||||
|
namespace SharepointToolbox.Tests.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unit tests for AppRegistrationResult, TenantProfile.AppId, and AppRegistrationService.
|
||||||
|
/// Graph API calls require live Entra connectivity and are marked as Integration tests.
|
||||||
|
/// Pure logic (model behaviour, BuildRequiredResourceAccess structure) is covered here.
|
||||||
|
/// </summary>
|
||||||
|
[Trait("Category", "Unit")]
|
||||||
|
public class AppRegistrationServiceTests
|
||||||
|
{
|
||||||
|
// ────────────────────────────────────────────────────────────────────────
|
||||||
|
// AppRegistrationResult — factory method tests
|
||||||
|
// ────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Success_CarriesAppId()
|
||||||
|
{
|
||||||
|
var result = AppRegistrationResult.Success("appId123");
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.False(result.IsFallback);
|
||||||
|
Assert.Equal("appId123", result.AppId);
|
||||||
|
Assert.Null(result.ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Failure_CarriesMessage()
|
||||||
|
{
|
||||||
|
var result = AppRegistrationResult.Failure("Something went wrong");
|
||||||
|
|
||||||
|
Assert.False(result.IsSuccess);
|
||||||
|
Assert.False(result.IsFallback);
|
||||||
|
Assert.Null(result.AppId);
|
||||||
|
Assert.Equal("Something went wrong", result.ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FallbackRequired_SetsFallback()
|
||||||
|
{
|
||||||
|
var result = AppRegistrationResult.FallbackRequired();
|
||||||
|
|
||||||
|
Assert.False(result.IsSuccess);
|
||||||
|
Assert.True(result.IsFallback);
|
||||||
|
Assert.Null(result.AppId);
|
||||||
|
Assert.Null(result.ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ────────────────────────────────────────────────────────────────────────
|
||||||
|
// TenantProfile.AppId — nullable field tests
|
||||||
|
// ────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AppId_DefaultsToNull()
|
||||||
|
{
|
||||||
|
var profile = new TenantProfile();
|
||||||
|
Assert.Null(profile.AppId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AppId_RoundTrips_ViaJson()
|
||||||
|
{
|
||||||
|
var profile = new TenantProfile
|
||||||
|
{
|
||||||
|
Name = "Test Tenant",
|
||||||
|
TenantUrl = "https://example.sharepoint.com",
|
||||||
|
ClientId = "client-id-abc",
|
||||||
|
AppId = "registered-app-id-xyz"
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
|
||||||
|
var json = JsonSerializer.Serialize(profile, options);
|
||||||
|
var loaded = JsonSerializer.Deserialize<TenantProfile>(json, options);
|
||||||
|
|
||||||
|
Assert.NotNull(loaded);
|
||||||
|
Assert.Equal("registered-app-id-xyz", loaded!.AppId);
|
||||||
|
Assert.Equal("Test Tenant", loaded.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AppId_Null_RoundTrips_ViaJson()
|
||||||
|
{
|
||||||
|
var profile = new TenantProfile
|
||||||
|
{
|
||||||
|
Name = "Test Tenant",
|
||||||
|
TenantUrl = "https://example.sharepoint.com",
|
||||||
|
ClientId = "client-id-abc",
|
||||||
|
AppId = null
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
|
||||||
|
var json = JsonSerializer.Serialize(profile, options);
|
||||||
|
var loaded = JsonSerializer.Deserialize<TenantProfile>(json, options);
|
||||||
|
|
||||||
|
Assert.NotNull(loaded);
|
||||||
|
Assert.Null(loaded!.AppId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ────────────────────────────────────────────────────────────────────────
|
||||||
|
// AppRegistrationService — constructor / dependency wiring
|
||||||
|
// ────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AppRegistrationService_ImplementsInterface()
|
||||||
|
{
|
||||||
|
// Verify that the concrete class satisfies the interface contract.
|
||||||
|
// We instantiate with a real MsalClientFactory (no-IO path) and mocked session manager / logger.
|
||||||
|
var msalFactory = new AppMsalClientFactory(Path.GetTempPath());
|
||||||
|
var graphFactory = new AppGraphClientFactory(msalFactory);
|
||||||
|
var sessionManagerMock = new Mock<ISessionManager>();
|
||||||
|
var loggerMock = new Microsoft.Extensions.Logging.Abstractions.NullLogger<AppRegistrationService>();
|
||||||
|
|
||||||
|
var service = new AppRegistrationService(graphFactory, msalFactory, sessionManagerMock.Object, loggerMock);
|
||||||
|
|
||||||
|
Assert.IsAssignableFrom<IAppRegistrationService>(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ────────────────────────────────────────────────────────────────────────
|
||||||
|
// BuildRequiredResourceAccess — structure verification (no live calls)
|
||||||
|
// ────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildRequiredResourceAccess_ContainsTwoResources()
|
||||||
|
{
|
||||||
|
var result = AppRegistrationService.BuildRequiredResourceAccess();
|
||||||
|
|
||||||
|
Assert.Equal(2, result.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildRequiredResourceAccess_GraphResource_HasFourScopes()
|
||||||
|
{
|
||||||
|
const string graphAppId = "00000003-0000-0000-c000-000000000000";
|
||||||
|
var result = AppRegistrationService.BuildRequiredResourceAccess();
|
||||||
|
|
||||||
|
var graphEntry = result.Single(r => r.ResourceAppId == graphAppId);
|
||||||
|
Assert.NotNull(graphEntry.ResourceAccess);
|
||||||
|
Assert.Equal(4, graphEntry.ResourceAccess!.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildRequiredResourceAccess_SharePointResource_HasOneScope()
|
||||||
|
{
|
||||||
|
const string spoAppId = "00000003-0000-0ff1-ce00-000000000000";
|
||||||
|
var result = AppRegistrationService.BuildRequiredResourceAccess();
|
||||||
|
|
||||||
|
var spoEntry = result.Single(r => r.ResourceAppId == spoAppId);
|
||||||
|
Assert.NotNull(spoEntry.ResourceAccess);
|
||||||
|
Assert.Single(spoEntry.ResourceAccess!);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildRequiredResourceAccess_AllScopes_HaveScopeType()
|
||||||
|
{
|
||||||
|
var result = AppRegistrationService.BuildRequiredResourceAccess();
|
||||||
|
|
||||||
|
var allAccess = result.SelectMany(r => r.ResourceAccess!);
|
||||||
|
Assert.All(allAccess, ra => Assert.Equal("Scope", ra.Type));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildRequiredResourceAccess_GraphResource_ContainsUserReadScope()
|
||||||
|
{
|
||||||
|
const string graphAppId = "00000003-0000-0000-c000-000000000000";
|
||||||
|
var userReadGuid = Guid.Parse("e1fe6dd8-ba31-4d61-89e7-88639da4683d"); // User.Read
|
||||||
|
var result = AppRegistrationService.BuildRequiredResourceAccess();
|
||||||
|
|
||||||
|
var graphEntry = result.Single(r => r.ResourceAppId == graphAppId);
|
||||||
|
Assert.Contains(graphEntry.ResourceAccess!, ra => ra.Id == userReadGuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user