using System.IO; using System.Text; using System.Text.Json; using SharepointToolbox.Core.Models; using SharepointToolbox.Infrastructure.Persistence; using SharepointToolbox.Services; namespace SharepointToolbox.Tests.Services; [Trait("Category", "Unit")] public class ProfileServiceTests : IDisposable { private readonly string _tempFile; public ProfileServiceTests() { _tempFile = Path.GetTempFileName(); // Ensure the file doesn't exist so tests start clean File.Delete(_tempFile); } public void Dispose() { if (File.Exists(_tempFile)) File.Delete(_tempFile); if (File.Exists(_tempFile + ".tmp")) File.Delete(_tempFile + ".tmp"); } private ProfileRepository CreateRepository() => new(_tempFile); private ProfileService CreateService() => new(CreateRepository()); [Fact] public async Task SaveAndLoad_RoundTrips_Profiles() { var repo = CreateRepository(); var profiles = new List { new() { Name = "Contoso", TenantUrl = "https://contoso.sharepoint.com", ClientId = "client-id-1" }, new() { Name = "Fabrikam", TenantUrl = "https://fabrikam.sharepoint.com", ClientId = "client-id-2" } }; await repo.SaveAsync(profiles); var loaded = await repo.LoadAsync(); Assert.Equal(2, loaded.Count); Assert.Equal("Contoso", loaded[0].Name); Assert.Equal("https://contoso.sharepoint.com", loaded[0].TenantUrl); Assert.Equal("client-id-1", loaded[0].ClientId); Assert.Equal("Fabrikam", loaded[1].Name); } [Fact] public async Task LoadAsync_MissingFile_ReturnsEmptyList() { var repo = CreateRepository(); var result = await repo.LoadAsync(); Assert.Empty(result); } [Fact] public async Task LoadAsync_CorruptJson_ThrowsInvalidDataException() { await File.WriteAllTextAsync(_tempFile, "{ not valid json !!!", System.Text.Encoding.UTF8); var repo = CreateRepository(); await Assert.ThrowsAsync(() => repo.LoadAsync()); } [Fact] public async Task SaveAsync_ConcurrentCalls_DoNotCorruptFile() { var repo = CreateRepository(); var tasks = new List(); for (int i = 0; i < 10; i++) { var idx = i; tasks.Add(Task.Run(async () => { var profiles = new List { new() { Name = $"Profile{idx}", TenantUrl = $"https://tenant{idx}.sharepoint.com", ClientId = $"cid-{idx}" } }; await repo.SaveAsync(profiles); })); } await Task.WhenAll(tasks); // After all concurrent writes, file should be valid JSON (not corrupt) var loaded = await repo.LoadAsync(); Assert.NotNull(loaded); Assert.Single(loaded); // last write wins, but exactly 1 item } [Fact] public async Task AddProfileAsync_PersistsNewProfile() { var service = CreateService(); var profile = new TenantProfile { Name = "TestTenant", TenantUrl = "https://test.sharepoint.com", ClientId = "test-cid" }; await service.AddProfileAsync(profile); var profiles = await service.GetProfilesAsync(); Assert.Single(profiles); Assert.Equal("TestTenant", profiles[0].Name); } [Fact] public async Task RenameProfileAsync_ChangesName_AndPersists() { var service = CreateService(); await service.AddProfileAsync(new TenantProfile { Name = "OldName", TenantUrl = "https://test.sharepoint.com", ClientId = "cid" }); await service.RenameProfileAsync("OldName", "NewName"); var profiles = await service.GetProfilesAsync(); Assert.Single(profiles); Assert.Equal("NewName", profiles[0].Name); } [Fact] public async Task RenameProfileAsync_ProfileNotFound_ThrowsKeyNotFoundException() { var service = CreateService(); await Assert.ThrowsAsync(() => service.RenameProfileAsync("NonExistent", "NewName")); } [Fact] public async Task DeleteProfileAsync_RemovesProfile() { var service = CreateService(); await service.AddProfileAsync(new TenantProfile { Name = "ToDelete", TenantUrl = "https://test.sharepoint.com", ClientId = "cid" }); await service.DeleteProfileAsync("ToDelete"); var profiles = await service.GetProfilesAsync(); Assert.Empty(profiles); } [Fact] public async Task DeleteProfileAsync_ProfileNotFound_ThrowsKeyNotFoundException() { var service = CreateService(); await Assert.ThrowsAsync(() => service.DeleteProfileAsync("NonExistent")); } [Fact] public async Task UpdateProfileAsync_UpdatesExistingProfile_AndPersists() { var service = CreateService(); var profile = new TenantProfile { Name = "UpdateMe", TenantUrl = "https://update.sharepoint.com", ClientId = "cid-update" }; await service.AddProfileAsync(profile); // Mutate — set a ClientLogo to simulate logo update profile.ClientLogo = new SharepointToolbox.Core.Models.LogoData { Base64 = "abc==", MimeType = "image/png" }; await service.UpdateProfileAsync(profile); var profiles = await service.GetProfilesAsync(); Assert.Single(profiles); Assert.NotNull(profiles[0].ClientLogo); Assert.Equal("abc==", profiles[0].ClientLogo!.Base64); } [Fact] public async Task UpdateProfileAsync_ProfileNotFound_ThrowsKeyNotFoundException() { var service = CreateService(); var profile = new TenantProfile { Name = "NonExistent", TenantUrl = "https://x.sharepoint.com", ClientId = "cid" }; await Assert.ThrowsAsync(() => service.UpdateProfileAsync(profile)); } [Fact] public async Task SaveAsync_JsonOutput_UsesProfilesRootKey() { var repo = CreateRepository(); var profiles = new List { new() { Name = "Test", TenantUrl = "https://test.sharepoint.com", ClientId = "cid" } }; await repo.SaveAsync(profiles); var json = await File.ReadAllTextAsync(_tempFile); using var doc = JsonDocument.Parse(json); Assert.True(doc.RootElement.TryGetProperty("profiles", out var profilesElement), "Root JSON object must contain 'profiles' key (camelCase)"); Assert.Equal(JsonValueKind.Array, profilesElement.ValueKind); var first = profilesElement.EnumerateArray().First(); Assert.True(first.TryGetProperty("name", out _), "Profile must have 'name' (camelCase)"); Assert.True(first.TryGetProperty("tenantUrl", out _), "Profile must have 'tenantUrl' (camelCase)"); Assert.True(first.TryGetProperty("clientId", out _), "Profile must have 'clientId' (camelCase)"); } }