99 lines
4.5 KiB
C#
99 lines
4.5 KiB
C#
using JobTrackerApi.Data;
|
|
using JobTrackerApi.Models;
|
|
using JobTrackerApi.Services;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace JobTrackerApi.Tests;
|
|
|
|
public sealed class SavedJobSearchAlertTests
|
|
{
|
|
[Fact]
|
|
public async Task Worker_establishes_baseline_then_notifies_only_for_later_results()
|
|
{
|
|
var clock = new MutableTimeProvider(new DateTimeOffset(2026, 8, 31, 8, 0, 0, TimeSpan.Zero));
|
|
var discovery = new FakeDiscovery(clock, "job-1");
|
|
var configuration = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?>
|
|
{
|
|
["Workers:SavedJobSearchAlertsEnabled"] = "true",
|
|
["JobDiscovery:SavedSearchIntervalHours"] = "6"
|
|
}).Build();
|
|
var services = new ServiceCollection();
|
|
var databaseName = Guid.NewGuid().ToString();
|
|
services.AddLogging();
|
|
services.AddHttpContextAccessor();
|
|
services.AddSingleton<IConfiguration>(configuration);
|
|
services.AddSingleton<TimeProvider>(clock);
|
|
services.AddSingleton<IJobDiscoverySearchService>(discovery);
|
|
services.AddScoped<CurrentUserService>();
|
|
services.AddScoped<ICurrentUserService>(provider => provider.GetRequiredService<CurrentUserService>());
|
|
services.AddDbContext<JobTrackerContext>((_, options) => options.UseInMemoryDatabase(databaseName));
|
|
services.AddScoped<SavedJobSearchRunner>();
|
|
services.AddSingleton<BackgroundTenantRunner>();
|
|
await using var provider = services.BuildServiceProvider();
|
|
|
|
await using (var scope = provider.CreateAsyncScope())
|
|
{
|
|
var db = scope.ServiceProvider.GetRequiredService<JobTrackerContext>();
|
|
db.SavedJobSearches.Add(new SavedJobSearch
|
|
{
|
|
OwnerUserId = "owner-without-jobs",
|
|
Name = "Backend Oslo",
|
|
Query = "backend",
|
|
Location = "Oslo",
|
|
CreatedAtUtc = clock.GetUtcNow(),
|
|
UpdatedAtUtc = clock.GetUtcNow()
|
|
});
|
|
await db.SaveChangesAsync();
|
|
}
|
|
|
|
var worker = new SavedJobSearchAlertHostedService(
|
|
provider.GetRequiredService<BackgroundTenantRunner>(),
|
|
configuration,
|
|
NullLogger<SavedJobSearchAlertHostedService>.Instance,
|
|
Mock.Of<IStartupReadiness>(),
|
|
clock);
|
|
|
|
Assert.Equal(new BackgroundWorkerRunResult(true, 1, 1, 0), await worker.RunOnceAsync(default));
|
|
await using (var scope = provider.CreateAsyncScope())
|
|
{
|
|
var db = scope.ServiceProvider.GetRequiredService<JobTrackerContext>();
|
|
Assert.Empty(await db.UserNotifications.IgnoreQueryFilters().ToListAsync());
|
|
Assert.Single(await db.SavedJobSearchResults.IgnoreQueryFilters().ToListAsync());
|
|
}
|
|
|
|
clock.SetUtcNow(clock.GetUtcNow().AddHours(7));
|
|
discovery.Ids.Add("job-2");
|
|
Assert.Equal(new BackgroundWorkerRunResult(true, 1, 1, 0), await worker.RunOnceAsync(default));
|
|
|
|
await using var verification = provider.CreateAsyncScope();
|
|
var notification = Assert.Single(await verification.ServiceProvider.GetRequiredService<JobTrackerContext>()
|
|
.UserNotifications.IgnoreQueryFilters().AsNoTracking().ToListAsync());
|
|
Assert.Equal("owner-without-jobs", notification.OwnerUserId);
|
|
Assert.Equal("saved_search_results", notification.Kind);
|
|
Assert.Equal("/discover", notification.LinkPath);
|
|
Assert.Contains("Backend Oslo", notification.Message);
|
|
}
|
|
|
|
private sealed class FakeDiscovery(MutableTimeProvider clock, params string[] ids) : IJobDiscoverySearchService
|
|
{
|
|
public List<string> Ids { get; } = [.. ids];
|
|
|
|
public Task<IReadOnlyList<DiscoveredJob>> SearchAsync(string? query, string? location, CancellationToken cancellationToken) =>
|
|
Task.FromResult<IReadOnlyList<DiscoveredJob>>(Ids.Select(id => new DiscoveredJob(
|
|
id, "Backend Developer", "Acme", "Oslo", clock.GetUtcNow(), null,
|
|
$"https://example.test/{id}", "nav", "NAV Arbeidsplassen", "searched", clock.GetUtcNow(), "NO")).ToList());
|
|
}
|
|
|
|
private sealed class MutableTimeProvider(DateTimeOffset now) : TimeProvider
|
|
{
|
|
private DateTimeOffset _now = now;
|
|
public override DateTimeOffset GetUtcNow() => _now;
|
|
public void SetUtcNow(DateTimeOffset value) => _now = value;
|
|
}
|
|
}
|