feat(welcome): flavour loader + validation
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
using System.Text.Json;
|
||||
namespace SilverOS.Welcome.Core.Flavours;
|
||||
|
||||
public sealed class FlavourValidationException(string message) : Exception(message);
|
||||
|
||||
public sealed class FlavourLoader : IFlavourLoader
|
||||
{
|
||||
public IReadOnlyList<FlavourManifest> Load(string directory)
|
||||
{
|
||||
var list = new List<FlavourManifest>();
|
||||
foreach (var file in Directory.EnumerateFiles(directory, "*.json").OrderBy(f => f))
|
||||
{
|
||||
FlavourManifest m;
|
||||
try { m = JsonSerializer.Deserialize<FlavourManifest>(File.ReadAllText(file), FlavourManifest.JsonOptions)
|
||||
?? throw new FlavourValidationException($"{Path.GetFileName(file)}: empty"); }
|
||||
catch (JsonException ex) { throw new FlavourValidationException($"{Path.GetFileName(file)}: {ex.Message}"); }
|
||||
if (string.IsNullOrWhiteSpace(m.Id)) throw new FlavourValidationException($"{Path.GetFileName(file)}: missing id");
|
||||
if (m.Hardening.Modules.Count == 0) throw new FlavourValidationException($"{Path.GetFileName(file)}: no hardening modules");
|
||||
list.Add(m);
|
||||
}
|
||||
if (list.Count == 0) throw new FlavourValidationException("no flavour manifests found");
|
||||
if (list.Count(m => m.IsDefault) != 1) throw new FlavourValidationException("exactly one flavour must be isDefault");
|
||||
return list.OrderByDescending(m => m.IsDefault).ThenBy(m => m.Label).ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
namespace SilverOS.Welcome.Core.Flavours;
|
||||
public interface IFlavourLoader { IReadOnlyList<FlavourManifest> Load(string directory); }
|
||||
@@ -0,0 +1,38 @@
|
||||
using SilverOS.Welcome.Core.Flavours;
|
||||
using Xunit;
|
||||
|
||||
public class FlavourLoaderTests
|
||||
{
|
||||
private static string WriteTemp(params (string name, string json)[] files)
|
||||
{
|
||||
var dir = Directory.CreateTempSubdirectory("flav").FullName;
|
||||
foreach (var (name, json) in files) File.WriteAllText(Path.Combine(dir, name), json);
|
||||
return dir;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Loads_all_manifests_sorted_with_default_first()
|
||||
{
|
||||
var dir = WriteTemp(
|
||||
("privacy-max.json", """{ "id":"privacy-max","label":"Privacy-Max","hardening":{"modules":["00"]} }"""),
|
||||
("daily-driver.json", """{ "id":"daily-driver","label":"Daily-Driver","isDefault":true,"hardening":{"modules":["00"]} }"""));
|
||||
var loaded = new FlavourLoader().Load(dir);
|
||||
Assert.Equal(2, loaded.Count);
|
||||
Assert.Equal("daily-driver", loaded[0].Id); // default first
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Throws_when_a_manifest_has_no_id()
|
||||
{
|
||||
var dir = WriteTemp(("bad.json", """{ "label":"No Id","hardening":{"modules":["00"]} }"""));
|
||||
var ex = Assert.Throws<FlavourValidationException>(() => new FlavourLoader().Load(dir));
|
||||
Assert.Contains("bad.json", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Throws_when_no_default_flavour_present()
|
||||
{
|
||||
var dir = WriteTemp(("a.json", """{ "id":"a","label":"A","hardening":{"modules":["00"]} }"""));
|
||||
Assert.Throws<FlavourValidationException>(() => new FlavourLoader().Load(dir));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user