587 lines
24 KiB
C#
587 lines
24 KiB
C#
using Manager.Services;
|
|
using ManagerService.Controllers;
|
|
using ManagerService.Data;
|
|
using ManagerService.DTOs;
|
|
using ManagerService.Services;
|
|
using ManagerService.Tests.Infrastructure;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Xunit;
|
|
|
|
namespace ManagerService.Tests.Controllers
|
|
{
|
|
public class ResourceControllerTests
|
|
{
|
|
private static ResourceController BuildController(
|
|
MyInfoMateDbContext db,
|
|
FakeHttpClientFactory httpClientFactory = null,
|
|
FakeBlobService blobService = null)
|
|
{
|
|
var cfg = FakeMongoConfig.Create();
|
|
var controller = new ResourceController(
|
|
NullLogger<ResourceController>.Instance,
|
|
new ResourceDatabaseService(cfg),
|
|
new SectionDatabaseService(cfg),
|
|
new ConfigurationDatabaseService(cfg),
|
|
db,
|
|
httpClientFactory ?? new FakeHttpClientFactory(),
|
|
blobService ?? new FakeBlobService(),
|
|
new ResourceUsageService(db, new MemoryCache(new MemoryCacheOptions())));
|
|
FakeUser.SetUser(controller, FakeUser.Create("Manager.contenteditor", "inst-test"));
|
|
return controller;
|
|
}
|
|
|
|
// ── GET ──────────────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void Get_FiltersToInstance()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.AddRange(
|
|
new Resource { Id = "r1", InstanceId = "inst-test", Label = "A", Type = ResourceType.Image },
|
|
new Resource { Id = "r2", InstanceId = "other-inst", Label = "B", Type = ResourceType.Image }
|
|
);
|
|
db.SaveChanges();
|
|
|
|
var result = BuildController(db).Get("inst-test", new List<ResourceType>());
|
|
|
|
var ok = Assert.IsType<OkObjectResult>(result);
|
|
var resources = (ok.Value as System.Collections.IEnumerable)!.Cast<object>().ToList();
|
|
Assert.Single(resources);
|
|
}
|
|
|
|
[Fact]
|
|
public void Get_WithTypeFilter_FiltersType()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.AddRange(
|
|
new Resource { Id = "r1", InstanceId = "inst-test", Label = "A", Type = ResourceType.Image },
|
|
new Resource { Id = "r2", InstanceId = "inst-test", Label = "B", Type = ResourceType.Video }
|
|
);
|
|
db.SaveChanges();
|
|
|
|
var result = BuildController(db).Get("inst-test", new List<ResourceType> { ResourceType.Image });
|
|
|
|
var ok = Assert.IsType<OkObjectResult>(result);
|
|
var resources = (ok.Value as System.Collections.IEnumerable)!.Cast<object>().ToList();
|
|
Assert.Single(resources);
|
|
}
|
|
|
|
// ── CREATE ───────────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void Create_FileType_SetsStoragePathAndSizeBytes()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
|
|
var result = BuildController(db).Create(new ResourceDTO
|
|
{
|
|
instanceId = "inst-test",
|
|
label = "brochure",
|
|
type = ResourceType.PDF,
|
|
sizeBytes = 12345
|
|
});
|
|
|
|
Assert.IsType<OkObjectResult>(result);
|
|
var created = db.Resources.First();
|
|
Assert.Equal($"pictures/inst-test/{created.Id}", created.StoragePath);
|
|
Assert.Equal(12345, created.SizeBytes);
|
|
}
|
|
|
|
[Fact]
|
|
public void Create_UrlType_LeavesStoragePathAndSizeBytesEmpty()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
|
|
var result = BuildController(db).Create(new ResourceDTO
|
|
{
|
|
instanceId = "inst-test",
|
|
label = "wikipedia",
|
|
type = ResourceType.ImageUrl,
|
|
url = "https://example.org/photo.jpg",
|
|
sizeBytes = 12345
|
|
});
|
|
|
|
Assert.IsType<OkObjectResult>(result);
|
|
var created = db.Resources.First();
|
|
Assert.Null(created.StoragePath);
|
|
Assert.Equal(0, created.SizeBytes);
|
|
}
|
|
|
|
// ── UPDATE ───────────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void Update_ExistingResource_UpdatesLabel()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.Add(new Resource { Id = "r1", InstanceId = "inst-test", Label = "Old", Type = ResourceType.Image });
|
|
db.SaveChanges();
|
|
|
|
var result = BuildController(db).Update(new ResourceDTO { id = "r1", label = "New Label" });
|
|
|
|
Assert.IsType<OkObjectResult>(result);
|
|
Assert.Equal("New Label", db.Resources.First().Label);
|
|
}
|
|
|
|
// D2 : `dateUpdate` est le seul signal de fraîcheur dont dispose la visite hors
|
|
// ligne. S'il cessait d'être exposé, le device garderait indéfiniment ses fichiers
|
|
// périmés — sans erreur, sans trace. D'où ces deux tests plutôt qu'une relecture.
|
|
[Fact]
|
|
public void ToDTO_ExposesDateUpdate()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.Add(new Resource { Id = "r1", InstanceId = "inst-test", Label = "Img", Type = ResourceType.Image });
|
|
db.SaveChanges();
|
|
|
|
var dto = db.Resources.First().ToDTO();
|
|
|
|
Assert.NotEqual(default, dto.dateUpdate);
|
|
Assert.Equal(db.Resources.First().DateUpdate, dto.dateUpdate);
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_AdvancesDateUpdate()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.Add(new Resource { Id = "r1", InstanceId = "inst-test", Label = "Old", Type = ResourceType.Image });
|
|
db.SaveChanges();
|
|
var before = db.Resources.First().DateUpdate;
|
|
|
|
var result = BuildController(db).Update(new ResourceDTO { id = "r1", label = "New Label" });
|
|
|
|
var dto = Assert.IsType<OkObjectResult>(result).Value as ResourceDTO;
|
|
Assert.True(dto.dateUpdate > before);
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_UnknownResource_Returns404()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
|
|
var result = BuildController(db).Update(new ResourceDTO { id = "unknown", label = "X" });
|
|
|
|
Assert.IsType<NotFoundObjectResult>(result);
|
|
}
|
|
|
|
// ── DELETE ───────────────────────────────────────────────────────────
|
|
|
|
// La suppression d'une ressource utilisée est refusée depuis la Médiathèque V1 :
|
|
// la suite de Delete déréférence la ressource partout où elle sert, ce qui viderait
|
|
// des contenus en silence. Ces deux tests figeaient l'ancien comportement.
|
|
[Fact]
|
|
public void Delete_ResourceUsedByConfiguration_Returns409()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.Add(new Resource { Id = "r1", InstanceId = "inst-test", Label = "Img", Type = ResourceType.Image });
|
|
db.Configurations.Add(new Configuration { Id = "c1", InstanceId = "inst-test", Label = "C", ImageId = "r1", Title = new List<TranslationDTO>() });
|
|
db.SaveChanges();
|
|
|
|
var result = BuildController(db).Delete("r1").GetAwaiter().GetResult();
|
|
|
|
Assert.IsType<ConflictObjectResult>(result);
|
|
Assert.Equal("r1", db.Configurations.First().ImageId);
|
|
Assert.Equal(1, db.Resources.Count());
|
|
}
|
|
|
|
[Fact]
|
|
public void Delete_ResourceUsedBySection_Returns409()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.Add(new Resource { Id = "r1", InstanceId = "inst-test", Label = "Img", Type = ResourceType.Image });
|
|
db.Configurations.Add(new Configuration { Id = "c1", InstanceId = "inst-test", Label = "C", Title = new List<TranslationDTO>() });
|
|
var section = TestSection.Article("s1", "inst-test", "S", "c1");
|
|
section.ImageId = "r1";
|
|
db.Sections.Add(section);
|
|
db.SaveChanges();
|
|
|
|
var result = BuildController(db).Delete("r1").GetAwaiter().GetResult();
|
|
|
|
Assert.IsType<ConflictObjectResult>(result);
|
|
Assert.Equal("r1", db.Sections.First().ImageId);
|
|
}
|
|
|
|
[Fact]
|
|
public void DeleteBulk_DeletesFreeResources_AndRefusesUsedOnes()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.AddRange(
|
|
new Resource { Id = "r-used", InstanceId = "inst-test", Label = "Utilisée", Type = ResourceType.Image },
|
|
new Resource { Id = "r-free", InstanceId = "inst-test", Label = "Libre", Type = ResourceType.Image });
|
|
db.Configurations.Add(new Configuration { Id = "c1", InstanceId = "inst-test", Label = "C", ImageId = "r-used", Title = new List<TranslationDTO>() });
|
|
db.SaveChanges();
|
|
|
|
var result = BuildController(db)
|
|
.DeleteBulk(new ResourceBulkDeleteRequestDTO { ids = new List<string> { "r-used", "r-free" } })
|
|
.GetAwaiter().GetResult();
|
|
|
|
var report = Assert.IsType<ResourceBulkDeleteReportDTO>(Assert.IsType<OkObjectResult>(result).Value);
|
|
Assert.Equal(new[] { "r-free" }, report.deleted);
|
|
Assert.Equal("r-used", Assert.Single(report.refused).id);
|
|
Assert.Equal(1, Assert.Single(report.refused).usageCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void Delete_UnknownResource_Returns404()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
|
|
var result = BuildController(db).Delete("unknown").GetAwaiter().GetResult();
|
|
|
|
Assert.IsType<NotFoundObjectResult>(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void Delete_ExistingResource_Returns202()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.Add(new Resource { Id = "r1", InstanceId = "inst-test", Label = "A", Type = ResourceType.Image });
|
|
db.SaveChanges();
|
|
|
|
var result = BuildController(db).Delete("r1").GetAwaiter().GetResult();
|
|
|
|
var obj = Assert.IsType<ObjectResult>(result);
|
|
Assert.Equal(202, obj.StatusCode);
|
|
Assert.Equal(0, db.Resources.Count());
|
|
}
|
|
|
|
// ── QUOTA & BLOB (C3) ────────────────────────────────────────────────
|
|
|
|
private static void SeedPlan(MyInfoMateDbContext db, long planQuota, long instanceQuota = 0)
|
|
{
|
|
db.SubscriptionPlans.Add(new SubscriptionPlan
|
|
{
|
|
Id = "plan-test", Name = "Test", StorageQuotaBytes = planQuota
|
|
});
|
|
db.Instances.Add(new Instance
|
|
{
|
|
Id = "inst-test", Name = "Test", SubscriptionPlanId = "plan-test",
|
|
StorageQuotaBytes = instanceQuota
|
|
});
|
|
db.SaveChanges();
|
|
}
|
|
|
|
[Fact]
|
|
public void Create_OverQuota_Returns413()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
SeedPlan(db, planQuota: 1000);
|
|
db.Resources.Add(new Resource
|
|
{
|
|
Id = "r0", InstanceId = "inst-test", Label = "Déjà là",
|
|
Type = ResourceType.Image, SizeBytes = 900
|
|
});
|
|
db.SaveChanges();
|
|
|
|
var result = BuildController(db).Create(new ResourceDTO
|
|
{
|
|
instanceId = "inst-test", label = "Trop gros",
|
|
type = ResourceType.Image, sizeBytes = 200
|
|
});
|
|
|
|
Assert.Equal(413, result.StatusCode);
|
|
Assert.Equal(1, db.Resources.Count());
|
|
}
|
|
|
|
[Fact]
|
|
public void Create_UrlType_IgnoresQuota()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
SeedPlan(db, planQuota: 1000);
|
|
db.Resources.Add(new Resource
|
|
{
|
|
Id = "r0", InstanceId = "inst-test", Label = "Déjà là",
|
|
Type = ResourceType.Image, SizeBytes = 1000
|
|
});
|
|
db.SaveChanges();
|
|
|
|
// Un lien externe n'occupe rien dans le bucket : il ne doit pas être refusé
|
|
// pour un quota de stockage, même sur une instance pleine.
|
|
var result = BuildController(db).Create(new ResourceDTO
|
|
{
|
|
instanceId = "inst-test", label = "Lien",
|
|
type = ResourceType.ImageUrl, url = "https://ailleurs/img.png"
|
|
});
|
|
|
|
Assert.Equal(200, result.StatusCode);
|
|
}
|
|
|
|
/// <summary>
|
|
/// La divergence que StorageQuota ferme : la surcharge portée par l'instance
|
|
/// l'emporte sur le plan. Avant, le pré-vol lisait le plan et bloquait à 1 000
|
|
/// alors que l'écran de quota annonçait 10 000.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Create_InstanceOverridesPlanQuota()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
SeedPlan(db, planQuota: 1000, instanceQuota: 10000);
|
|
db.Resources.Add(new Resource
|
|
{
|
|
Id = "r0", InstanceId = "inst-test", Label = "Déjà là",
|
|
Type = ResourceType.Image, SizeBytes = 900
|
|
});
|
|
db.SaveChanges();
|
|
|
|
var result = BuildController(db).Create(new ResourceDTO
|
|
{
|
|
instanceId = "inst-test", label = "Dans le quota surchargé",
|
|
type = ResourceType.Image, sizeBytes = 200
|
|
});
|
|
|
|
Assert.Equal(200, result.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public void Delete_RemovesBlobBeforeRow()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.Add(new Resource
|
|
{
|
|
Id = "r1", InstanceId = "inst-test", Label = "A", Type = ResourceType.Image,
|
|
StoragePath = "pictures/inst-test/r1"
|
|
});
|
|
db.SaveChanges();
|
|
|
|
var blob = new FakeBlobService();
|
|
var result = BuildController(db, blobService: blob).Delete("r1").GetAwaiter().GetResult();
|
|
|
|
Assert.Equal(202, result.StatusCode);
|
|
Assert.Equal(new[] { "pictures/inst-test/r1" }, blob.DeletedPaths);
|
|
Assert.Equal(0, db.Resources.Count());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Le cœur du choix : un échec de bucket conserve la ligne. Une ligne encore
|
|
/// listée se rattrape ; un blob dont plus aucune ligne ne porte le chemin est
|
|
/// facturé sans que rien ne le désigne.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Delete_BlobFailure_KeepsRowAndReturns502()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.Add(new Resource
|
|
{
|
|
Id = "r1", InstanceId = "inst-test", Label = "A", Type = ResourceType.Image,
|
|
StoragePath = "pictures/inst-test/r1"
|
|
});
|
|
db.SaveChanges();
|
|
|
|
var blob = new FakeBlobService(BlobDeleteOutcome.Failed);
|
|
var result = BuildController(db, blobService: blob).Delete("r1").GetAwaiter().GetResult();
|
|
|
|
Assert.Equal(502, result.StatusCode);
|
|
Assert.Equal(1, db.Resources.Count());
|
|
}
|
|
|
|
[Fact]
|
|
public void Delete_MissingStoragePath_IsRebuiltFromTheDeterministicPath()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.Add(new Resource
|
|
{
|
|
Id = "r1", InstanceId = "inst-test", Label = "Avant le backfill", Type = ResourceType.Image
|
|
});
|
|
db.SaveChanges();
|
|
|
|
var blob = new FakeBlobService();
|
|
BuildController(db, blobService: blob).Delete("r1").GetAwaiter().GetResult();
|
|
|
|
Assert.Equal(new[] { "pictures/inst-test/r1" }, blob.DeletedPaths);
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_TypeBecomesFile_FillsStoragePath()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.Add(new Resource
|
|
{
|
|
Id = "r1", InstanceId = "inst-test", Label = "A", Type = ResourceType.ImageUrl,
|
|
Url = "https://ailleurs/img.png"
|
|
});
|
|
db.SaveChanges();
|
|
|
|
BuildController(db).Update(new ResourceDTO
|
|
{
|
|
id = "r1", type = ResourceType.Image, sizeBytes = 256
|
|
});
|
|
|
|
var resource = db.Resources.Single();
|
|
Assert.Equal("pictures/inst-test/r1", resource.StoragePath);
|
|
Assert.Equal(256, resource.SizeBytes);
|
|
}
|
|
|
|
[Fact]
|
|
public void Update_TypeBecomesUrl_ClearsStorageColumns()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.Add(new Resource
|
|
{
|
|
Id = "r1", InstanceId = "inst-test", Label = "A", Type = ResourceType.Image,
|
|
StoragePath = "pictures/inst-test/r1", SizeBytes = 4096
|
|
});
|
|
db.SaveChanges();
|
|
|
|
BuildController(db).Update(new ResourceDTO { id = "r1", type = ResourceType.ImageUrl });
|
|
|
|
var resource = db.Resources.Single();
|
|
Assert.Null(resource.StoragePath);
|
|
Assert.Equal(0, resource.SizeBytes);
|
|
}
|
|
|
|
// ── BACKFILL STORAGE (C2) ────────────────────────────────────────────
|
|
|
|
private static ResourceBackfillReportDTO RunBackfill(
|
|
ResourceController controller, bool dryRun = false, string instanceId = null)
|
|
{
|
|
var result = controller.BackfillStorage(dryRun, instanceId).GetAwaiter().GetResult();
|
|
var ok = Assert.IsType<OkObjectResult>(result);
|
|
return Assert.IsType<ResourceBackfillReportDTO>(ok.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Backfill_FillsStoragePathAndSizeBytes()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.Add(new Resource
|
|
{
|
|
Id = "r1", InstanceId = "inst-test", Label = "A",
|
|
Type = ResourceType.Image, Url = "https://bucket/r1"
|
|
});
|
|
db.SaveChanges();
|
|
|
|
var http = new FakeHttpClientFactory(new Dictionary<string, long> { ["https://bucket/r1"] = 2048 });
|
|
var report = RunBackfill(BuildController(db, http));
|
|
|
|
var resource = db.Resources.Single();
|
|
Assert.Equal("pictures/inst-test/r1", resource.StoragePath);
|
|
Assert.Equal(2048, resource.SizeBytes);
|
|
Assert.Equal(1, report.StoragePathFilled);
|
|
Assert.Equal(1, report.SizeBytesFilled);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Le point du lien L10 : un backfill qui écrirait quand même laisserait le quota
|
|
/// de C3 porter sur des zéros. Une ligne non sondable doit ressortir nommée.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Backfill_UnreachableBlob_IsReportedNotCountedAsZero()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.Add(new Resource
|
|
{
|
|
Id = "r1", InstanceId = "inst-test", Label = "Absente",
|
|
Type = ResourceType.Image, Url = "https://bucket/disparue"
|
|
});
|
|
db.SaveChanges();
|
|
|
|
var report = RunBackfill(BuildController(db, new FakeHttpClientFactory()));
|
|
|
|
Assert.Equal(0, report.SizeBytesFilled);
|
|
Assert.Single(report.Unsized);
|
|
Assert.Contains("r1", report.Unsized[0]);
|
|
// Le chemin, lui, est déterministe : il se calcule même sans blob joignable.
|
|
Assert.Equal("pictures/inst-test/r1", db.Resources.Single().StoragePath);
|
|
}
|
|
|
|
[Fact]
|
|
public void Backfill_FileTypeWithoutUrl_IsListedAsOrphan()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.Add(new Resource
|
|
{
|
|
Id = "r1", InstanceId = "inst-test", Label = "Sans URL", Type = ResourceType.PDF
|
|
});
|
|
db.SaveChanges();
|
|
|
|
var report = RunBackfill(BuildController(db));
|
|
|
|
Assert.Single(report.Orphans);
|
|
Assert.Empty(report.Unsized);
|
|
}
|
|
|
|
[Fact]
|
|
public void Backfill_UrlType_KeepsNoPathAndNoSize()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.Add(new Resource
|
|
{
|
|
Id = "r1", InstanceId = "inst-test", Label = "Lien", Type = ResourceType.ImageUrl,
|
|
Url = "https://ailleurs/img.png", StoragePath = "pictures/inst-test/r1", SizeBytes = 999
|
|
});
|
|
db.SaveChanges();
|
|
|
|
var report = RunBackfill(BuildController(db));
|
|
|
|
var resource = db.Resources.Single();
|
|
Assert.Null(resource.StoragePath);
|
|
Assert.Equal(0, resource.SizeBytes);
|
|
Assert.Equal(1, report.UrlTypesNormalised);
|
|
}
|
|
|
|
[Fact]
|
|
public void Backfill_AlreadyCompleteRow_IsLeftAlone()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.Add(new Resource
|
|
{
|
|
Id = "r1", InstanceId = "inst-test", Label = "A", Type = ResourceType.Image,
|
|
Url = "https://bucket/r1", StoragePath = "pictures/inst-test/r1", SizeBytes = 512
|
|
});
|
|
db.SaveChanges();
|
|
|
|
var http = new FakeHttpClientFactory(new Dictionary<string, long> { ["https://bucket/r1"] = 999999 });
|
|
var report = RunBackfill(BuildController(db, http));
|
|
|
|
// La taille en base a été écrite par la création, qui connaissait le fichier :
|
|
// la resonder n'apporterait rien et écraserait une valeur plus sûre.
|
|
Assert.Equal(512, db.Resources.Single().SizeBytes);
|
|
Assert.Equal(1, report.AlreadyComplete);
|
|
Assert.Equal(0, report.SizeBytesFilled);
|
|
}
|
|
|
|
[Fact]
|
|
public void Backfill_DryRun_WritesNothing()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.Add(new Resource
|
|
{
|
|
Id = "r1", InstanceId = "inst-test", Label = "A",
|
|
Type = ResourceType.Image, Url = "https://bucket/r1"
|
|
});
|
|
db.SaveChanges();
|
|
|
|
var http = new FakeHttpClientFactory(new Dictionary<string, long> { ["https://bucket/r1"] = 2048 });
|
|
var report = RunBackfill(BuildController(db, http), dryRun: true);
|
|
|
|
Assert.True(report.DryRun);
|
|
Assert.Equal(1, report.StoragePathFilled);
|
|
|
|
// Sans vider le ChangeTracker, l'entité modifiée en mémoire répondrait la
|
|
// nouvelle valeur alors que rien n'a été enregistré : l'assertion serait creuse.
|
|
db.ChangeTracker.Clear();
|
|
var reread = db.Resources.Single();
|
|
Assert.Null(reread.StoragePath);
|
|
Assert.Equal(0, reread.SizeBytes);
|
|
}
|
|
|
|
[Fact]
|
|
public void Backfill_ScopedToInstance_IgnoresOthers()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Resources.AddRange(
|
|
new Resource { Id = "r1", InstanceId = "inst-test", Label = "A", Type = ResourceType.Image },
|
|
new Resource { Id = "r2", InstanceId = "other-inst", Label = "B", Type = ResourceType.Image }
|
|
);
|
|
db.SaveChanges();
|
|
|
|
var report = RunBackfill(BuildController(db), instanceId: "inst-test");
|
|
|
|
Assert.Equal(1, report.Examined);
|
|
Assert.Null(db.Resources.Single(r => r.Id == "r2").StoragePath);
|
|
}
|
|
}
|
|
}
|