90 lines
5.9 KiB
C#
90 lines
5.9 KiB
C#
using Xunit;
|
|
using System.Net;
|
|
using System.Text;
|
|
using JobTrackerApi.Controllers;
|
|
using JobTrackerApi.Data;
|
|
using JobTrackerApi.Models;
|
|
using JobTrackerApi.Services;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Moq;
|
|
|
|
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<OkObjectResult>(result.Result);
|
|
var job = Assert.Single(Assert.IsAssignableFrom<IReadOnlyList<DiscoveredJob>>(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<string, string?> { ["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<OkObjectResult>(result.Result);
|
|
var job = Assert.Single(Assert.IsAssignableFrom<IReadOnlyList<DiscoveredJob>>(ok.Value));
|
|
Assert.Equal("1", job.Id);
|
|
Assert.Equal("Current title", job.Title);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Saved_search_tracks_new_seen_and_dismissed_results_per_owner()
|
|
{
|
|
var feed = """{"next_url":"","items":[{"date_modified":"2026-08-30T10:00:00Z","_feed_entry":{"uuid":"job-1","status":"ACTIVE","title":"Backend Developer","businessName":"Acme","municipal":"OSLO"}}]}""";
|
|
var client = new HttpClient(new Handler(_ => feed));
|
|
var configuration = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?> { ["NavJobs:Token"] = "configured-test-token" }).Build();
|
|
var current = new Mock<ICurrentUserService>();
|
|
current.SetupGet(x => x.UserId).Returns("owner-1");
|
|
await using var db = new JobTrackerContext(new DbContextOptionsBuilder<JobTrackerContext>().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options, current.Object);
|
|
var controller = new JobDiscoveryController(new ClientFactory(client), configuration, new MemoryCache(new MemoryCacheOptions()), db, current.Object);
|
|
|
|
var created = Assert.IsType<JobDiscoveryController.SavedSearchDto>(Assert.IsType<CreatedAtActionResult>((await controller.CreateSavedSearch(new("Backend", "backend", "oslo"), default)).Result).Value);
|
|
var first = Assert.IsType<JobDiscoveryController.SavedSearchRunDto>(Assert.IsType<OkObjectResult>((await controller.RunSavedSearch(created.Id, default)).Result).Value);
|
|
Assert.True(Assert.Single(first.Jobs).IsNew);
|
|
|
|
var second = Assert.IsType<JobDiscoveryController.SavedSearchRunDto>(Assert.IsType<OkObjectResult>((await controller.RunSavedSearch(created.Id, default)).Result).Value);
|
|
Assert.False(Assert.Single(second.Jobs).IsNew);
|
|
Assert.IsType<NoContentResult>(await controller.DismissSavedSearchResult(created.Id, "job-1", new(true), default));
|
|
var listed = Assert.IsAssignableFrom<IReadOnlyList<JobDiscoveryController.SavedSearchDto>>(Assert.IsType<OkObjectResult>((await controller.ListSavedSearches(default)).Result).Value);
|
|
Assert.Equal(1, Assert.Single(listed).DismissedCount);
|
|
var paused = Assert.IsType<JobDiscoveryController.SavedSearchDto>(Assert.IsType<OkObjectResult>((await controller.UpdateSavedSearch(created.Id, new(false), default)).Result).Value);
|
|
Assert.False(paused.IsActive);
|
|
}
|
|
|
|
private sealed class ClientFactory(HttpClient client) : IHttpClientFactory
|
|
{
|
|
public HttpClient CreateClient(string name) => client;
|
|
}
|
|
|
|
private sealed class Handler(Func<HttpRequestMessage, string> response) : HttpMessageHandler
|
|
{
|
|
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) =>
|
|
Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(response(request), Encoding.UTF8, "application/json") });
|
|
}
|
|
}
|