using JobTrackerApi.Controllers; using JobTrackerApi.Data; using JobTrackerApi.Models; using JobTrackerApi.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Moq; using Xunit; namespace JobTrackerApi.Tests; public sealed class ApplicationCalendarControllerTests { [Fact] public async Task Status_distinguishes_connection_from_calendar_consent() { await using var db = CreateDb("owner-1"); var google = new Mock(); google.Setup(service => service.GetConnectionAsync("owner-1", It.IsAny())) .ReturnsAsync(new GmailConnection { OwnerUserId = "owner-1", Scope = GmailOAuthService.CalendarScope }); var microsoft = new Mock(); microsoft.Setup(service => service.GetConnectionAsync("owner-1", It.IsAny())) .ReturnsAsync(new MicrosoftGraphConnection { OwnerUserId = "owner-1", Scope = "Mail.Read" }); var controller = CreateController(db, "owner-1", google.Object, microsoft.Object); var value = Assert.IsType(Assert.IsType((await controller.Status(default)).Result).Value); Assert.True(value.Google.Connected); Assert.True(value.Google.Writable); Assert.True(value.Microsoft.Connected); Assert.False(value.Microsoft.Writable); } [Fact] public async Task Creates_follow_up_in_selected_connected_calendar_with_stable_job_context() { await using var db = CreateDb("owner-1"); var job = await SeedJobAsync(db, "owner-1"); ExternalCalendarEventRequest? sent = null; var google = new Mock(); google.Setup(service => service.CreateCalendarEventAsync("owner-1", It.IsAny(), It.IsAny())) .Callback((_, request, _) => sent = request) .ReturnsAsync(new ExternalCalendarEventResult("event-1", "https://calendar.google.test/event-1")); var controller = CreateController(db, "owner-1", google.Object, Mock.Of()); var response = await controller.CreateEvent(job.Id, new("google", "follow-up"), default); var value = Assert.IsType(Assert.IsType(response.Result).Value); Assert.Equal("google", value.Provider); Assert.Equal("event-1", value.Id); Assert.NotNull(sent); Assert.Equal("Follow up: Backend Engineer at Acme", sent.Summary); Assert.Equal(job.FollowUpAt, sent.StartsAtUtc.UtcDateTime); Assert.False(sent.AllDay); Assert.Equal(64, sent.StableId.Length); } [Fact] public async Task Rejects_another_owners_job_without_calling_provider() { await using var db = CreateDb("owner-1"); var job = await SeedJobAsync(db, "owner-2"); var google = new Mock(MockBehavior.Strict); var controller = CreateController(db, "owner-1", google.Object, Mock.Of()); var response = await controller.CreateEvent(job.Id, new("google", "follow-up"), default); Assert.IsType(response.Result); } [Fact] public async Task Downloads_portable_all_day_deadline_event_without_provider_credentials() { await using var db = CreateDb("owner-1"); var job = await SeedJobAsync(db, "owner-1"); var controller = CreateController(db, "owner-1", Mock.Of(), Mock.Of()); var response = Assert.IsType(await controller.DownloadEvent(job.Id, "deadline", default)); var content = System.Text.Encoding.UTF8.GetString(response.FileContents); Assert.Equal("text/calendar; charset=utf-8", response.ContentType); Assert.Contains("DTSTART;VALUE=DATE:20260915", content); Assert.Contains("DTEND;VALUE=DATE:20260916", content); Assert.Contains("SUMMARY:Application deadline: Backend Engineer at Acme", content); } private static JobTrackerContext CreateDb(string owner) { var current = new Mock(); current.SetupGet(service => service.UserId).Returns(owner); return new JobTrackerContext(new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()).Options, current.Object); } private static async Task SeedJobAsync(JobTrackerContext db, string owner) { var company = new Company { OwnerUserId = owner, Name = "Acme" }; var job = new JobApplication { OwnerUserId = owner, Company = company, JobTitle = "Backend Engineer", Status = "Applied", FollowUpAt = new DateTime(2026, 9, 2, 10, 30, 0, DateTimeKind.Utc), Deadline = new DateTime(2026, 9, 15, 0, 0, 0, DateTimeKind.Utc), Location = "Oslo" }; db.JobApplications.Add(job); await db.SaveChangesAsync(); return job; } private static ApplicationCalendarController CreateController(JobTrackerContext db, string owner, IGmailOAuthService google, IMicrosoftGraphOAuthService microsoft) { var current = new Mock(); current.SetupGet(service => service.UserId).Returns(owner); return new ApplicationCalendarController(db, current.Object, google, microsoft); } }