using System.Reflection; using JobTrackerApi.Controllers; using JobTrackerApi.Data; using JobTrackerApi.Models; using JobTrackerApi.Services; using JobTrackerApi.Tests.TestSupport; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace JobTrackerApi.Tests; public sealed class BackupControllerTests { [Fact] public void Backup_controller_requires_local_authorization() { var attribute = typeof(BackupController).GetCustomAttribute(); Assert.NotNull(attribute); Assert.Equal("local", attribute!.AuthenticationSchemes); } [Fact] public void Export_controller_requires_local_authorization() { var attribute = typeof(ExportController).GetCustomAttribute(); Assert.NotNull(attribute); Assert.Equal("local", attribute!.AuthenticationSchemes); } [Fact] public async Task Encrypted_returns_file_payload_on_non_windows_platforms_too() { await using var db = CreateDb(); db.Companies.Add(new Company { Name = "Acme", OwnerUserId = "user-1" }); var job = new JobApplication { JobTitle = "Backend Developer", OwnerUserId = "user-1" }; var otherJob = new JobApplication { JobTitle = "Other tenant role", OwnerUserId = "user-2" }; db.JobApplications.AddRange(job, otherJob); await db.SaveChangesAsync(); db.EmailSendAttempts.Add(new EmailSendAttempt { Id = Guid.NewGuid(), OwnerUserId = "user-1", JobApplicationId = job.Id, Provider = "gmail", ClientRequestId = Guid.NewGuid().ToString(), PayloadHash = new string('a', 64), Status = EmailSendStatuses.Sent, ProviderMessageId = "synthetic-message-id", CreatedAtUtc = DateTime.UtcNow, CompletedAtUtc = DateTime.UtcNow, }); db.EmailDrafts.Add(new EmailDraft { Id = Guid.NewGuid(), OwnerUserId = "user-1", JobApplicationId = job.Id, Provider = "gmail", To = "recipient@example.test", Subject = "Synthetic export subject", BodyText = "Synthetic readable draft body.", ThreadId = "synthetic-thread", ClientRequestId = "00000000-0000-4000-8000-000000000123", Revision = 3, CreatedAtUtc = DateTime.UtcNow.AddMinutes(-5), UpdatedAtUtc = DateTime.UtcNow, }); db.EmailDrafts.Add(new EmailDraft { Id = Guid.NewGuid(), OwnerUserId = "user-2", JobApplicationId = otherJob.Id, Provider = "microsoft", To = "other@example.test", Subject = "Other tenant subject", BodyText = "Other tenant private body.", ClientRequestId = "00000000-0000-4000-8000-000000000456", CreatedAtUtc = DateTime.UtcNow, UpdatedAtUtc = DateTime.UtcNow, }); await db.SaveChangesAsync(); var provider = DataProtectionProvider.Create(new DirectoryInfo(Path.Combine(Path.GetTempPath(), $"jobtracker-tests-{Guid.NewGuid():N}"))); var controller = new BackupController(db, new NullLogger(), provider); var result = await controller.Encrypted(CancellationToken.None); var file = Assert.IsType(result); Assert.Equal("application/octet-stream", file.ContentType); Assert.EndsWith(".jtbackup", file.FileDownloadName); Assert.NotEmpty(file.FileContents); var protectedText = System.Text.Encoding.UTF8.GetString(file.FileContents); var json = Convert.FromBase64String(provider.CreateProtector("JobTrackerApi.Backup.v1").Unprotect(protectedText)); using var document = System.Text.Json.JsonDocument.Parse(json); var attempt = Assert.Single(document.RootElement.GetProperty("Data").GetProperty("EmailSendAttempts").EnumerateArray()); Assert.Equal("gmail", attempt.GetProperty("Provider").GetString()); Assert.Equal("sent", attempt.GetProperty("Status").GetString()); Assert.False(attempt.TryGetProperty("PayloadHash", out _)); var draft = Assert.Single(document.RootElement.GetProperty("Data").GetProperty("EmailDrafts").EnumerateArray()); Assert.Equal("recipient@example.test", draft.GetProperty("To").GetString()); Assert.Equal("Synthetic export subject", draft.GetProperty("Subject").GetString()); Assert.Equal("Synthetic readable draft body.", draft.GetProperty("BodyText").GetString()); Assert.Equal("synthetic-thread", draft.GetProperty("ThreadId").GetString()); Assert.Equal("00000000-0000-4000-8000-000000000123", draft.GetProperty("ClientRequestId").GetString()); Assert.Equal(3, draft.GetProperty("Revision").GetInt64()); } private static JobTrackerContext CreateDb() { return TestHostFactory.CreateInMemoryDb(); } }