Files
Sharepoint-Toolbox/SharepointToolbox.Tests/Auth/SessionManagerTests.cs
Dev 158aab96b2 feat(01-04): SessionManager singleton holding all ClientContext instances
- SessionManager owns all ClientContexts; callers must not store references
- IsAuthenticated(tenantUrl) returns false before auth, true after GetOrCreateContextAsync
- ClearSessionAsync disposes ClientContext and removes state (idempotent for unknown tenants)
- GetOrCreateContextAsync validates null/empty TenantUrl and ClientId (ArgumentException)
- MsalClientFactory.GetCacheHelper() added — exposes helper for PnP tokenCacheCallback wiring
- 8 unit tests pass, 1 interactive-login test skipped (integration-only)
2026-04-02 12:25:01 +02:00

104 lines
4.0 KiB
C#

using System;
using System.IO;
using System.Threading.Tasks;
using SharepointToolbox.Core.Models;
using SharepointToolbox.Infrastructure.Auth;
using SharepointToolbox.Services;
using Xunit;
namespace SharepointToolbox.Tests.Auth;
[Trait("Category", "Unit")]
public class SessionManagerTests : IDisposable
{
private readonly string _tempCacheDir;
private readonly MsalClientFactory _factory;
private readonly SessionManager _sessionManager;
public SessionManagerTests()
{
_tempCacheDir = Path.Combine(Path.GetTempPath(), "SessionManagerTests_" + Guid.NewGuid());
Directory.CreateDirectory(_tempCacheDir);
_factory = new MsalClientFactory(_tempCacheDir);
_sessionManager = new SessionManager(_factory);
}
public void Dispose()
{
try { Directory.Delete(_tempCacheDir, recursive: true); } catch { /* best effort */ }
}
// ── IsAuthenticated ──────────────────────────────────────────────────────
[Fact]
public void IsAuthenticated_BeforeAnyAuth_ReturnsFalse()
{
Assert.False(_sessionManager.IsAuthenticated("https://contoso.sharepoint.com"));
}
[Fact]
public void IsAuthenticated_NormalizesTrailingSlash()
{
Assert.False(_sessionManager.IsAuthenticated("https://contoso.sharepoint.com/"));
Assert.False(_sessionManager.IsAuthenticated("https://contoso.sharepoint.com"));
}
// ── ClearSessionAsync ────────────────────────────────────────────────────
[Fact]
public async Task ClearSessionAsync_UnknownTenantUrl_DoesNotThrow()
{
// Must be idempotent — no exception for tenants that were never authenticated
await _sessionManager.ClearSessionAsync("https://unknown.sharepoint.com");
}
[Fact]
public async Task ClearSessionAsync_MultipleCalls_DoNotThrow()
{
await _sessionManager.ClearSessionAsync("https://contoso.sharepoint.com");
await _sessionManager.ClearSessionAsync("https://contoso.sharepoint.com");
}
// ── Argument validation ──────────────────────────────────────────────────
[Fact]
public async Task GetOrCreateContextAsync_NullTenantUrl_ThrowsArgumentException()
{
var profile = new TenantProfile { TenantUrl = null!, ClientId = "clientId", Name = "Test" };
await Assert.ThrowsAnyAsync<ArgumentException>(
() => _sessionManager.GetOrCreateContextAsync(profile));
}
[Fact]
public async Task GetOrCreateContextAsync_EmptyTenantUrl_ThrowsArgumentException()
{
var profile = new TenantProfile { TenantUrl = "", ClientId = "clientId", Name = "Test" };
await Assert.ThrowsAnyAsync<ArgumentException>(
() => _sessionManager.GetOrCreateContextAsync(profile));
}
[Fact]
public async Task GetOrCreateContextAsync_NullClientId_ThrowsArgumentException()
{
var profile = new TenantProfile { TenantUrl = "https://contoso.sharepoint.com", ClientId = null!, Name = "Test" };
await Assert.ThrowsAnyAsync<ArgumentException>(
() => _sessionManager.GetOrCreateContextAsync(profile));
}
[Fact]
public async Task GetOrCreateContextAsync_EmptyClientId_ThrowsArgumentException()
{
var profile = new TenantProfile { TenantUrl = "https://contoso.sharepoint.com", ClientId = "", Name = "Test" };
await Assert.ThrowsAnyAsync<ArgumentException>(
() => _sessionManager.GetOrCreateContextAsync(profile));
}
// ── Interactive login test (skipped — requires MSAL interactive flow) ────
[Fact(Skip = "Requires interactive MSAL — integration test only")]
public Task GetOrCreateContextAsync_CreatesContext()
{
return Task.CompletedTask;
}
}