From 5fe9e94993513e2bac74388b97cd95c13ceec4dd Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 14 May 2026 14:15:06 -0500 Subject: [PATCH] Tighten Pi signage HDMI settle coverage --- .../systemd/99-flowercore-signage-hdmi.rules | 5 +- .../tests/display_capability.bats | 22 ++++++ .../tests/identity_bootstrap.bats | 64 +++++++++++++++++ .../tests/systemd_kiosk_wrapper.bats | 68 +++++++++++++++++++ .../PiSignagePlayerArtifactTests.cs | 7 +- 5 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 apps/fc-signage-pi-player/tests/display_capability.bats create mode 100644 apps/fc-signage-pi-player/tests/identity_bootstrap.bats create mode 100644 apps/fc-signage-pi-player/tests/systemd_kiosk_wrapper.bats diff --git a/apps/fc-signage-pi-player/systemd/99-flowercore-signage-hdmi.rules b/apps/fc-signage-pi-player/systemd/99-flowercore-signage-hdmi.rules index e227d62..f670e18 100644 --- a/apps/fc-signage-pi-player/systemd/99-flowercore-signage-hdmi.rules +++ b/apps/fc-signage-pi-player/systemd/99-flowercore-signage-hdmi.rules @@ -1,3 +1,2 @@ -# Restart kiosk and redeclare capabilities when HDMI connect/disconnect changes DRM state. -SUBSYSTEM=="drm", KERNEL=="card?-HDMI-A-?", ACTION=="change", RUN+="/usr/bin/systemctl restart flowercore-signage-player-pi.service" -SUBSYSTEM=="drm", KERNEL=="card?-HDMI-A-?", ACTION=="change", RUN+="/usr/bin/systemctl start flowercore-signage-detect-display.service" +# Settle DRM for 2s before restarting Chromium, then redeclare capabilities. +SUBSYSTEM=="drm", KERNEL=="card?-HDMI-A-?", ACTION=="change", RUN+="/usr/bin/systemctl start flowercore-signage-player-pi-hdmi.service" diff --git a/apps/fc-signage-pi-player/tests/display_capability.bats b/apps/fc-signage-pi-player/tests/display_capability.bats new file mode 100644 index 0000000..4c378e2 --- /dev/null +++ b/apps/fc-signage-pi-player/tests/display_capability.bats @@ -0,0 +1,22 @@ +#!/usr/bin/env bats + +setup() { + APP_ROOT="$(cd "$BATS_TEST_DIRNAME/.." && pwd)" + DETECT="$APP_ROOT/scripts/fc-signage-detect-display" +} + +@test "display detection emits graceful disconnected profile when no hdmi connector is present" { + script="$(cat "$DETECT")" + [[ "$script" == *"displayConnected: false"* ]] + [[ "$script" == *"No HDMI display detected"* ]] +} + +@test "display detection parses edid, falls back to kmsprint, and logs endpoint failures locally" { + script="$(cat "$DETECT")" + [[ "$script" == *"edid-decode"* ]] + [[ "$script" == *"HDR (Static|Dynamic) Metadata Block"* ]] + [[ "$script" == *"kmsprint"* ]] + [[ "$script" == *"/api/v1/nodes/\${NODE_ID}/capabilities"* ]] + [[ "$script" == *"/api/v1/displays/\${NODE_ID}/capability-profile"* ]] + [[ "$script" == *"capabilities.log"* ]] +} diff --git a/apps/fc-signage-pi-player/tests/identity_bootstrap.bats b/apps/fc-signage-pi-player/tests/identity_bootstrap.bats new file mode 100644 index 0000000..c52102b --- /dev/null +++ b/apps/fc-signage-pi-player/tests/identity_bootstrap.bats @@ -0,0 +1,64 @@ +#!/usr/bin/env bats + +setup() { + APP_ROOT="$(cd "$BATS_TEST_DIRNAME/.." && pwd)" + BOOTSTRAP="$APP_ROOT/scripts/flowercore-signage-bootstrap.sh" + RENEW="$APP_ROOT/scripts/flowercore-signage-renew-cert.sh" +} + +@test "bootstrap is idempotent when node is already enrolled" { + script="$(cat "$BOOTSTRAP")" + [[ "$script" == *'[[ -s "$NODE_JSON" && -s "$CERT_DIR/client.p12" ]]'* ]] + [[ "$script" == *"already enrolled"* ]] + [[ "$script" == *"exit 0"* ]] +} + +@test "bootstrap generates a stable node uuid and machine id" { + script="$(cat "$BOOTSTRAP")" + [[ "$script" == *"uuidgen"* ]] + [[ "$script" == *"nodeUuid"* ]] + [[ "$script" == *"machineId"* ]] + [[ "$script" == *"cut -c1-16"* ]] +} + +@test "bootstrap posts to the canonical register endpoint" { + grep -q '/api/v1/nodes/register' "$BOOTSTRAP" + grep -q '"linux-arm64-pi"' "$BOOTSTRAP" +} + +@test "bootstrap retries registration once for first-call races" { + script="$(cat "$BOOTSTRAP")" + [[ "$script" == *"for attempt in 1 2"* ]] + [[ "$script" == *"register attempt \$attempt returned"* ]] + [[ "$script" == *"sleep 5"* ]] +} + +@test "bootstrap supports setup-code approval with manual polling fallback" { + script="$(cat "$BOOTSTRAP")" + [[ "$script" == *"signage-setup-code"* ]] + [[ "$script" == *"approve-via-setup-code"* ]] + [[ "$script" == *"+ 1800"* ]] + [[ "$script" == *"sleep 15"* ]] +} + +@test "bootstrap generates an ecdsa p256 csr for the signage pi subject" { + script="$(cat "$BOOTSTRAP")" + [[ "$script" == *"ecparam -genkey -name prime256v1"* ]] + [[ "$script" == *'/CN=${NODE_ID}/O=FlowerCore/OU=SignagePlayer-Pi'* ]] +} + +@test "bootstrap writes pkcs12 bundle with restrictive permissions" { + script="$(cat "$BOOTSTRAP")" + [[ "$script" == *"openssl pkcs12 -export"* ]] + [[ "$script" == *"client.p12.pass"* ]] + [[ "$script" == *"chmod 0640"* ]] + [[ "$script" == *"chmod 0600"* ]] +} + +@test "renewal only calls renew endpoint inside the thirty-day window and swaps atomically" { + script="$(cat "$RENEW")" + [[ "$script" == *'-checkend $((30*24*3600))'* ]] + [[ "$script" == *"/api/v1/nodes/\${NODE_ID}/renew"* ]] + [[ "$script" == *"client.key.new"* ]] + [[ "$script" == *'mv "$CERT_DIR/client.p12.new" "$CERT_DIR/client.p12"'* ]] +} diff --git a/apps/fc-signage-pi-player/tests/systemd_kiosk_wrapper.bats b/apps/fc-signage-pi-player/tests/systemd_kiosk_wrapper.bats new file mode 100644 index 0000000..658518b --- /dev/null +++ b/apps/fc-signage-pi-player/tests/systemd_kiosk_wrapper.bats @@ -0,0 +1,68 @@ +#!/usr/bin/env bats + +setup() { + APP_ROOT="$(cd "$BATS_TEST_DIRNAME/.." && pwd)" +} + +@test "player unit exists" { + [ -f "$APP_ROOT/systemd/flowercore-signage-player-pi.service" ] +} + +@test "player unit uses simple chromium service with restart backoff" { + unit="$(cat "$APP_ROOT/systemd/flowercore-signage-player-pi.service")" + [[ "$unit" == *"Type=simple"* ]] + [[ "$unit" == *"Restart=always"* ]] + [[ "$unit" == *"RestartSec=10s"* ]] + [[ "$unit" == *"StartLimitBurst=5"* ]] + [[ "$unit" == *"StartLimitIntervalSec=300s"* ]] +} + +@test "player unit caps chromium memory at two gigabytes" { + grep -q '^MemoryMax=2G$' "$APP_ROOT/systemd/flowercore-signage-player-pi.service" + grep -q '^MemoryHigh=1500M$' "$APP_ROOT/systemd/flowercore-signage-player-pi.service" +} + +@test "player unit condition-gates startup on identity and p12 certificate" { + grep -q '^ConditionPathExists=/etc/flowercore/signage-node.json$' "$APP_ROOT/systemd/flowercore-signage-player-pi.service" + grep -q '^ConditionPathExists=/etc/fc-signage-player/client.p12$' "$APP_ROOT/systemd/flowercore-signage-player-pi.service" +} + +@test "player unit runs prelaunch checks before chromium" { + grep -q '^ExecStartPre=/usr/local/bin/flowercore-signage-prelaunch.sh$' "$APP_ROOT/systemd/flowercore-signage-player-pi.service" + grep -q '^ExecStart=/usr/local/bin/flowercore-signage-launch.sh$' "$APP_ROOT/systemd/flowercore-signage-player-pi.service" +} + +@test "hdmi udev rule routes through the two-second settle service" { + rule="$(cat "$APP_ROOT/systemd/99-flowercore-signage-hdmi.rules")" + [[ "$rule" == *'KERNEL=="card?-HDMI-A-?"'* ]] + [[ "$rule" == *"systemctl start flowercore-signage-player-pi-hdmi.service"* ]] + [[ "$rule" != *"systemctl restart flowercore-signage-player-pi.service"* ]] +} + +@test "hdmi responder settles, declares display, then restarts chromium" { + responder="$(cat "$APP_ROOT/scripts/flowercore-signage-hdmi-respond.sh")" + [[ "$responder" == *"sleep 2"* ]] + [[ "$responder" == *"systemctl start flowercore-signage-detect-display.service"* ]] + [[ "$responder" == *"systemctl restart flowercore-signage-player-pi.service"* ]] +} + +@test "chromium policy json is valid and disables credential prompts" { + command -v jq >/dev/null || skip "jq not installed" + jq -e '.AutofillAddressEnabled == false and .AutofillCreditCardEnabled == false and .PasswordManagerEnabled == false' \ + "$APP_ROOT/chromium-policies/flowercore-signage.json" >/dev/null +} + +@test "launch script tries embed URL and logs bare-player fallback" { + launch="$(cat "$APP_ROOT/scripts/flowercore-signage-launch.sh")" + [[ "$launch" == *'/player/${NODE_ID}/embed?token=${CERT_THUMB}'* ]] + [[ "$launch" == *"url-divergence.log"* ]] + [[ "$launch" == *'/player/${NODE_ID}?token=${CERT_THUMB}'* ]] +} + +@test "prelaunch script validates required node and cert files" { + prelaunch="$(cat "$APP_ROOT/scripts/flowercore-signage-prelaunch.sh")" + [[ "$prelaunch" == *"/etc/flowercore/signage-node.json"* ]] + [[ "$prelaunch" == *"/etc/fc-signage-player/client.p12"* ]] + [[ "$prelaunch" == *"/etc/fc-signage-player/client.p12.pass"* ]] + [[ "$prelaunch" == *"exit 1"* ]] +} diff --git a/tests/bluejay-infra-lint/PiSignagePlayerArtifactTests.cs b/tests/bluejay-infra-lint/PiSignagePlayerArtifactTests.cs index 403f11a..0827117 100644 --- a/tests/bluejay-infra-lint/PiSignagePlayerArtifactTests.cs +++ b/tests/bluejay-infra-lint/PiSignagePlayerArtifactTests.cs @@ -174,10 +174,13 @@ public sealed class PiSignagePlayerArtifactTests public void HdmiRule_RestartsPlayerAndRunsCapabilityDetection() { var rule = Read("systemd/99-flowercore-signage-hdmi.rules"); + var responder = Read("scripts/flowercore-signage-hdmi-respond.sh"); rule.Should().Contain("KERNEL==\"card?-HDMI-A-?\""); - rule.Should().Contain("restart flowercore-signage-player-pi.service"); - rule.Should().Contain("start flowercore-signage-detect-display.service"); + rule.Should().Contain("start flowercore-signage-player-pi-hdmi.service"); + responder.Should().Contain("sleep 2"); + responder.Should().Contain("start flowercore-signage-detect-display.service"); + responder.Should().Contain("restart flowercore-signage-player-pi.service"); } [Fact]