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( () => _sessionManager.GetOrCreateContextAsync(profile)); } [Fact] public async Task GetOrCreateContextAsync_EmptyTenantUrl_ThrowsArgumentException() { var profile = new TenantProfile { TenantUrl = "", ClientId = "clientId", Name = "Test" }; await Assert.ThrowsAnyAsync( () => _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( () => _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( () => _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; } }