using FluentAssertions; using Xunit; namespace BluejayInfraLint.Tests; [Trait("Category", "Unit")] public sealed class DivoomPiDeployArtifactTests { private static readonly string Root = FindRepoRoot(); private static readonly string DmRoot = Path.Combine(Root, "apps", "fc-divoom-dm-pi-device"); private static readonly string TvRoot = Path.Combine(Root, "apps", "fc-divoom-tv-pi"); public static TheoryData DmRequiredArtifacts => new() { "README.md", "hiera/edge2-divoom-dm-device.overlay.yaml", "puppet/profile/pi/service/divoom_dm_device.pp", "puppet/templates/divoom-device-registration.json.epp", "puppet/templates/flowercore-divoom-dm-agent.service.epp", }; public static TheoryData TvRequiredArtifacts => new() { "README.md", "hiera/example-divoom-tv-pi.iamworkin.lan.yaml", "puppet/profile/pi/service/divoom_tv.pp", "systemd/flowercore-divoom-tv.service", "systemd/flowercore-divoom-tv-hdmi.service", "systemd/99-flowercore-divoom-tv-hdmi.rules", "scripts/flowercore-divoom-tv-prelaunch.sh", "scripts/flowercore-divoom-tv-launch.sh", "scripts/flowercore-divoom-tv-hdmi-respond.sh", }; [Theory] [MemberData(nameof(DmRequiredArtifacts))] public void DmDeviceArtifacts_ArePresent(string relativePath) { File.Exists(Path.Combine(DmRoot, relativePath.Replace('/', Path.DirectorySeparatorChar))).Should().BeTrue(relativePath); } [Theory] [MemberData(nameof(TvRequiredArtifacts))] public void TvPiArtifacts_ArePresent(string relativePath) { File.Exists(Path.Combine(TvRoot, relativePath.Replace('/', Path.DirectorySeparatorChar))).Should().BeTrue(relativePath); } [Fact] public void DmDeviceReadme_DeclaresPuppetSystemdNotKubernetes() { var readme = ReadDm("README.md"); readme.Should().Contain("not a Kubernetes application"); readme.Should().Contain("profile::pi::service::divoom"); readme.Should().Contain("no K8s surface"); } [Fact] public void DmHieraOverlay_PreservesExistingEdge2DivoomService() { var hiera = ReadDm("hiera/edge2-divoom-dm-device.overlay.yaml"); hiera.Should().Contain("fc-pimanager:"); hiera.Should().Contain("fc-divoom:"); hiera.Should().Contain("enabled: true"); hiera.Should().Contain("profile::pi::service::divoom_dm_device::service_enabled: false"); hiera.Should().Contain("profile::pi::service::divoom_dm_device::service_ensure: 'stopped'"); } [Fact] public void DmPuppetProfile_DefaultsToStoppedDisabledService() { var profile = ReadDm("puppet/profile/pi/service/divoom_dm_device.pp"); profile.Should().Contain("Boolean $service_enabled = false"); profile.Should().Contain("Enum['running', 'stopped'] $service_ensure = 'stopped'"); profile.Should().Contain("service { $service_name:"); profile.Should().Contain("ensure => $service_ensure"); profile.Should().Contain("enable => $service_enabled"); } [Fact] public void DmPuppetProfile_DoesNotManageLiveDivoomWebUnit() { var profile = ReadDm("puppet/profile/pi/service/divoom_dm_device.pp"); profile.Should().NotContain("Service['flowercore-divoom.service']"); profile.Should().NotContain("service { 'flowercore-divoom.service'"); profile.Should().NotContain("notify => Service"); } [Fact] public void DmAgentUnit_IsSeparateAndGatedByExistingWrappers() { var unit = ReadDm("puppet/templates/flowercore-divoom-dm-agent.service.epp"); unit.Should().Contain("ConditionPathExists=<%= $divoom_install_dir %>/bt-link.sh"); unit.Should().Contain("ConditionPathExists=<%= $divoom_install_dir %>/bt-reset.sh"); unit.Should().Contain("ConditionPathExists=<%= $divoom_install_dir %>/audio-link.sh"); unit.Should().Contain("ExecStart=<%= $agent_binary_path %> --mode=Pi"); unit.Should().NotContain("flowercore-divoom.service"); } [Fact] public void DmRegistration_CarriesRenderProofAndSafetyPolicy() { var registration = ReadDm("puppet/templates/divoom-device-registration.json.epp"); registration.Should().Contain("\"candidateChannels\": <%= $bt_channels_json %>"); registration.Should().Contain("\"deviceInfoIsRenderProof\": false"); registration.Should().Contain("\"visibleRenderProofRequired\": <%= $visible_render_proof_required %>"); registration.Should().Contain("\"preserveExistingService\": \"flowercore-divoom.service\""); registration.Should().Contain("\"doNotEnableFmRadio\": true"); } [Fact] public void TvService_UsesAvaloniaHdmiSafetyGates() { var unit = ReadTv("systemd/flowercore-divoom-tv.service"); unit.Should().Contain("ConditionPathExists=/opt/flowercore/divoom-tv/FlowerCore.Divoom.Tv"); unit.Should().Contain("Environment=XDG_RUNTIME_DIR=/run/fc-divoom-tv"); unit.Should().Contain("RuntimeDirectoryMode=0700"); unit.Should().Contain("ExecStartPre=/usr/local/bin/flowercore-divoom-tv-prelaunch.sh"); unit.Should().Contain("ExecStart=/usr/local/bin/flowercore-divoom-tv-launch.sh"); unit.Should().Contain("MemoryMax=2G"); unit.Should().Contain("PrivateTmp=true"); unit.Should().NotContain("/tmp"); } [Fact] public void TvLauncher_PrefersCageAndFallsBackToDirectLaunch() { var script = ReadTv("scripts/flowercore-divoom-tv-launch.sh"); script.Should().Contain("command -v cage"); script.Should().Contain("exec cage --"); script.Should().Contain("launching FlowerCore.Divoom.Tv directly"); script.Should().Contain("--target=hdmi"); script.Should().Contain("--presentation-mode=${PRESENTATION_MODE}"); } [Fact] public void TvHotplugRule_SettlesAndRestartsRenderer() { var rule = ReadTv("systemd/99-flowercore-divoom-tv-hdmi.rules"); var responder = ReadTv("scripts/flowercore-divoom-tv-hdmi-respond.sh"); rule.Should().Contain("KERNEL==\"card?-HDMI-A-?\""); rule.Should().Contain("start flowercore-divoom-tv-hdmi.service"); responder.Should().Contain("sleep 2"); responder.Should().Contain("systemctl restart flowercore-divoom-tv.service"); } [Fact] public void TvPuppetProfile_InstallsCageAndStaticArtifacts() { var profile = ReadTv("puppet/profile/pi/service/divoom_tv.pp"); profile.Should().Contain("package { ['cage', 'libgbm1', 'libdrm2', 'libxkbcommon0', 'fonts-dejavu-core']"); profile.Should().Contain("'profile/pi/fc_divoom_tv/flowercore-divoom-tv.service'"); profile.Should().Contain("'profile/pi/fc_divoom_tv/flowercore-divoom-tv-launch.sh'"); profile.Should().Contain("profile/pi/fc_divoom_tv/99-flowercore-divoom-tv-hdmi.rules"); profile.Should().Contain("Boolean $service_enabled = false"); } [Fact] public void DivoomArtifacts_DoNotAddKubernetesWorkloads() { var allText = Directory.GetFiles(DmRoot, "*", SearchOption.AllDirectories) .Concat(Directory.GetFiles(TvRoot, "*", SearchOption.AllDirectories)) .Select(File.ReadAllText); foreach (var text in allText) { text.Should().NotContain("kind: Deployment"); text.Should().NotContain("kind: IngressRoute"); text.Should().NotContain("kind: Certificate"); text.Should().NotContain("kind: OnePasswordItem"); } } private static string ReadDm(string relativePath) => File.ReadAllText(Path.Combine(DmRoot, relativePath.Replace('/', Path.DirectorySeparatorChar))); private static string ReadTv(string relativePath) => File.ReadAllText(Path.Combine(TvRoot, relativePath.Replace('/', Path.DirectorySeparatorChar))); private static string FindRepoRoot() { var current = new DirectoryInfo(AppContext.BaseDirectory); while (current is not null) { if (Directory.Exists(Path.Combine(current.FullName, "apps")) && File.Exists(Path.Combine(current.FullName, "README.md"))) { return current.FullName; } current = current.Parent; } throw new DirectoryNotFoundException("Could not find bluejay-infra root."); } }