using Manager.DTOs;
using ManagerService.Data;
using ManagerService.Data.SubSection;
using ManagerService.DTOs;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace ManagerService.Tests.Controllers
{
///
/// L'export d'une configuration doit embarquer **toutes** les ressources que ses
/// sections référencent — c'est ce qui rend une visite lisible hors ligne.
///
/// Le `switch` qui les collectait était commenté depuis un moment : seules l'image
/// de la configuration, celle du loader et l'image de chaque section partaient, donc
/// une visite téléchargée n'avait ni contenus d'articles ni audios. Ces tests portent
/// sur `GetReferencedResourceIds`, à qui la collecte a été confiée : c'est elle qui
/// garantit qu'un nouveau type de section n'ouvrira pas le même trou en silence.
///
public class ConfigurationExportTests
{
private static SectionArticle ArticleWithMedia() => new SectionArticle
{
Id = "sec-article",
ConfigurationId = "cfg-1",
InstanceId = "inst-1",
Label = "Article",
Type = SectionType.Article,
Title = new List(),
Description = new List(),
ImageId = "res-image-section",
ArticleContent = new List(),
ArticleAudioIds = new List
{
new TranslationDTO { language = "FR", value = "res-audio-fr" },
new TranslationDTO { language = "NL", value = "res-audio-nl" },
},
ArticleContents = new List
{
new ContentDTO { resourceId = "res-content-1" },
new ContentDTO { resourceId = "res-content-2" },
},
};
///
/// ⚠️ Le second trou du même endroit, trouvé le 12/09. Les ressources
/// partaient bien, mais pas les champs qui les référencent :
/// Section.ToDTO() n'est pas virtuelle, et l'export appelait
/// s.ToDTO() sur une variable de type Section. Le JSON ne portait
/// donc que les champs communs — une galerie sans ses images, une carte sans ses
/// points, une maquette sans son modèle. L'export est censé être « tout le
/// contenu en un appel » ; il l'est depuis que SectionFactory.ToDTO fait
/// le sous-type réel.
///
[Theory]
[InlineData(typeof(SectionMap), typeof(MapDTO))]
[InlineData(typeof(SectionSlider), typeof(SliderDTO))]
[InlineData(typeof(SectionVideo), typeof(VideoDTO))]
[InlineData(typeof(SectionScene3D), typeof(Scene3DDTO))]
[InlineData(typeof(SectionArticle), typeof(ArticleDTO))]
public void Export_ProducesTheRealSubTypeDto(System.Type sectionType, System.Type expectedDto)
{
var section = (Section)System.Activator.CreateInstance(sectionType)!;
section.Title = new List();
section.Description = new List();
var dto = ManagerService.Services.SectionFactory.ToDTO(section);
Assert.IsType(expectedDto, dto);
Assert.IsAssignableFrom(dto);
}
[Fact]
public void Export_KeepsModel3DFields()
{
var section = new SectionScene3D
{
Id = "s1",
Title = new List(),
Description = new List(),
Model3DResourceId = "res-glb",
Model3DSource = "https://cdn/model.glb",
Points = new List
{
new GeoPoint
{
Id = 1,
Title = new List(),
Description = new List(),
Contents = new List(),
LocalTransform = new Position3D { x = 1.5f, y = 0.2f, z = -3f }
}
}
};
var dto = Assert.IsType(ManagerService.Services.SectionFactory.ToDTO(section));
Assert.Equal("res-glb", dto.model3DResourceId);
Assert.Equal("https://cdn/model.glb", dto.model3DSource);
var point = Assert.Single(dto.points);
Assert.Equal(1.5f, point.localTransform.x);
Assert.Equal(-3f, point.localTransform.z);
}
[Fact]
public void Article_ExposesItsContentAndAudioResources()
{
var ids = ArticleWithMedia().GetReferencedResourceIds().ToList();
// Sans ces trois familles, la visite hors ligne s'ouvre sans ses médias.
Assert.Contains("res-image-section", ids);
Assert.Contains("res-content-1", ids);
Assert.Contains("res-content-2", ids);
Assert.Contains("res-audio-fr", ids);
}
[Fact]
public void Article_FiltersAudioByLanguage_WhenOneIsAsked()
{
var ids = ArticleWithMedia().GetReferencedResourceIds("FR").ToList();
Assert.Contains("res-audio-fr", ids);
// Télécharger une visite en français ne doit pas embarquer les audios NL :
// c'est du poids sur le device du visiteur, pas du contenu.
Assert.DoesNotContain("res-audio-nl", ids);
}
[Fact]
public void Map_ExposesIconCategoriesAndPointResources()
{
var map = new SectionMap
{
Id = "sec-map",
ConfigurationId = "cfg-1",
InstanceId = "inst-1",
Label = "Map",
Type = SectionType.Map,
Title = new List(),
Description = new List(),
IconResourceId = "res-map-icon",
MapCategories = new List
{
new CategorieDTO { resourceDTO = new ResourceDTO { id = "res-categorie" } },
},
MapPoints = new List
{
new GeoPoint
{
Id = 1,
ImageResourceId = "res-point-image",
Contents = new List { new ContentDTO { resourceId = "res-point-content" } },
},
},
};
var ids = map.GetReferencedResourceIds().ToList();
Assert.Contains("res-map-icon", ids);
Assert.Contains("res-categorie", ids);
Assert.Contains("res-point-image", ids);
Assert.Contains("res-point-content", ids);
}
[Fact]
public void EverySectionType_ImplementsTheCollection()
{
// Le trou d'origine venait d'un `switch` : un type oublié passait dans le
// `default` sans bruit. La méthode étant abstraite sur Section, le compilateur
// impose désormais l'implémentation — ce test le constate plutôt qu'il ne
// l'espère, et échouera si quelqu'un réintroduit une collecte par switch.
var sectionType = typeof(Section);
var subTypes = sectionType.Assembly.GetTypes()
.Where(t => t.IsSubclassOf(sectionType) && !t.IsAbstract)
.ToList();
Assert.NotEmpty(subTypes);
foreach (var t in subTypes)
{
var method = t.GetMethod(nameof(Section.GetReferencedResourceIds));
Assert.NotNull(method);
Assert.Equal(t, method.DeclaringType);
}
}
}
}