using System.Security.Claims; using JobTrackerApi.Controllers; using JobTrackerApi.Data; using JobTrackerApi.Models; using JobTrackerApi.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Moq; using Xunit; namespace JobTrackerApi.Tests; public sealed class CompanyCreateCompatibilityTests { [Fact] public async Task Exact_Norwegian_name_reuses_owned_company_without_overwriting_existing_fields() { await using var fixture = await Fixture.CreateAsync(); var existing = new Company { OwnerUserId = "owner-a", Name = "Ærlig Økonomi Ås", Location = "Original location", Source = "Finn", RecruiterEmail = "retained@example.test" }; fixture.Db.Companies.Add(existing); await fixture.Db.SaveChangesAsync(); var result = await fixture.Controller.Create(new(" Ærlig Økonomi Ås ", "Imported location", "LinkedIn"), default); var company = Assert.IsType(Assert.IsType(result.Result).Value); Assert.Equal(existing.Id, company.Id); Assert.Equal("owner-a", company.OwnerUserId); Assert.Equal("Ærlig Økonomi Ås", company.Name); Assert.Equal("Original location", company.Location); Assert.Equal("Finn", company.Source); Assert.Equal("retained@example.test", company.RecruiterEmail); Assert.Equal(1, await fixture.Db.Companies.IgnoreQueryFilters().CountAsync()); } [Fact] public async Task Ascii_case_insensitive_reuse_and_trimmed_input_remain_compatible() { await using var fixture = await Fixture.CreateAsync(); var existing = new Company { OwnerUserId = "owner-a", Name = "Northwind AS", Location = "Original location", Source = "Finn" }; fixture.Db.Companies.Add(existing); await fixture.Db.SaveChangesAsync(); var result = await fixture.Controller.Create(new(" nORTHWIND aS ", "Another location", "LinkedIn"), default); var company = Assert.IsType(Assert.IsType(result.Result).Value); Assert.Equal(existing.Id, company.Id); Assert.Equal("Northwind AS", company.Name); Assert.Equal("Original location", company.Location); Assert.Equal("Finn", company.Source); Assert.Equal(1, await fixture.Db.Companies.CountAsync()); } [Fact] public async Task An_identical_foreign_name_creates_a_separate_owned_company_and_preserves_source_normalization() { await using var fixture = await Fixture.CreateAsync(); var foreign = new Company { OwnerUserId = "owner-b", Name = "Ærlig AS", Location = "Private location", Source = "Private source", RecruiterEmail = "private@example.test" }; fixture.Db.Companies.Add(foreign); await fixture.Db.SaveChangesAsync(); var result = await fixture.Controller.Create(new(" Ærlig AS ", " Bergen ", "https://www.nav.no/jobs"), default); var company = Assert.IsType(Assert.IsType(result.Result).Value); Assert.NotEqual(foreign.Id, company.Id); Assert.Equal("owner-a", company.OwnerUserId); Assert.Equal("Ærlig AS", company.Name); Assert.Equal("Bergen", company.Location); Assert.Equal("NAV", company.Source); Assert.Null(company.RecruiterEmail); Assert.Equal(1, await fixture.Db.Companies.CountAsync()); Assert.Equal(2, await fixture.Db.Companies.IgnoreQueryFilters().CountAsync()); var retained = await fixture.Db.Companies.IgnoreQueryFilters().AsNoTracking().SingleAsync(value => value.Id == foreign.Id); Assert.Equal("owner-b", retained.OwnerUserId); Assert.Equal("Private location", retained.Location); Assert.Equal("Private source", retained.Source); Assert.Equal("private@example.test", retained.RecruiterEmail); } private sealed class Fixture(SqliteConnection connection, JobTrackerContext db) : IAsyncDisposable { public JobTrackerContext Db { get; } = db; public CompaniesController Controller { get; } = new(db) { ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal(new ClaimsIdentity([new Claim(ClaimTypes.NameIdentifier, "owner-a")], "test")), }, }, }; public static async Task CreateAsync() { var connection = new SqliteConnection("Data Source=:memory:"); await connection.OpenAsync(); var currentUser = new Mock(); currentUser.SetupGet(value => value.UserId).Returns("owner-a"); var db = new JobTrackerContext(new DbContextOptionsBuilder().UseSqlite(connection).Options, currentUser.Object); await db.Database.EnsureCreatedAsync(); return new Fixture(connection, db); } public async ValueTask DisposeAsync() { await Db.DisposeAsync(); await connection.DisposeAsync(); } } }