using Xunit; using System.Net; using System.Text; using JobTrackerApi.Controllers; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; namespace JobTrackerApi.Tests; public sealed class JobDiscoveryControllerTests { [Fact] public async Task Search_filters_recent_active_nav_jobs() { const string token = "eyJ.test.token"; var feed = """{"next_url":"","items":[{"date_modified":"2026-07-30T10:00:00Z","_feed_entry":{"uuid":"1","status":"ACTIVE","title":"Backend Developer","businessName":"Acme","municipal":"OSLO","applicationDue":"2026-08-15T23:59:59Z"}},{"date_modified":"2026-07-30T11:00:00Z","_feed_entry":{"uuid":"2","status":"ACTIVE","title":"Nurse","businessName":"Hospital","municipal":"BERGEN"}}]}"""; var client = new HttpClient(new Handler(request => request.RequestUri!.AbsolutePath.EndsWith("publicToken") ? token : feed)); var controller = new JobDiscoveryController(new ClientFactory(client), new ConfigurationBuilder().Build(), new MemoryCache(new MemoryCacheOptions())); var result = await controller.Search("backend", "oslo", CancellationToken.None); var ok = Assert.IsType(result.Result); var job = Assert.Single(Assert.IsAssignableFrom>(ok.Value)); Assert.Equal("Backend Developer", job.Title); Assert.Equal("nav", job.Source); Assert.Equal("NAV Arbeidsplassen", job.SourceName); Assert.Equal("searched", job.AcquisitionType); Assert.Equal(new DateTimeOffset(2026, 8, 15, 23, 59, 59, TimeSpan.Zero), job.Deadline); Assert.True(job.RetrievedAt <= DateTimeOffset.UtcNow); Assert.Equal("NO", job.CountryCode); } [Fact] public async Task Search_keeps_latest_active_event_and_removes_inactive_duplicates() { var feed = """{"next_url":"","items":[{"date_modified":"2026-07-30T09:00:00Z","_feed_entry":{"uuid":"1","status":"ACTIVE","title":"Old title"}},{"date_modified":"2026-07-30T10:00:00Z","_feed_entry":{"uuid":"1","status":"ACTIVE","title":"Current title"}},{"date_modified":"2026-07-30T10:00:00Z","_feed_entry":{"uuid":"2","status":"ACTIVE","title":"Withdrawn"}},{"date_modified":"2026-07-30T11:00:00Z","_feed_entry":{"uuid":"2","status":"INACTIVE","title":"Withdrawn"}}]}"""; var client = new HttpClient(new Handler(_ => feed)); var configuration = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary { ["NavJobs:Token"] = "configured-test-token" }).Build(); var controller = new JobDiscoveryController(new ClientFactory(client), configuration, new MemoryCache(new MemoryCacheOptions())); var result = await controller.Search(null, null, CancellationToken.None); var ok = Assert.IsType(result.Result); var job = Assert.Single(Assert.IsAssignableFrom>(ok.Value)); Assert.Equal("1", job.Id); Assert.Equal("Current title", job.Title); } private sealed class ClientFactory(HttpClient client) : IHttpClientFactory { public HttpClient CreateClient(string name) => client; } private sealed class Handler(Func response) : HttpMessageHandler { protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(response(request), Encoding.UTF8, "application/json") }); } }