fwmanager: test validate()'s reject paths

Board tables run validate() at compile time, where a rejection is a
build error nobody can assert on — a vacuous loop would pass every
const check silently. Call it at runtime instead and prove each reject
path fires: empty device name, empty checkpoint list, empty checkpoint
name, zero window.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Christina Quast <christina.quast@9elements.com>
Refs: 9elements/openprot#1
diff --git a/services/fwmanager/api/src/config.rs b/services/fwmanager/api/src/config.rs
index 0a946cd..43e3ed8 100644
--- a/services/fwmanager/api/src/config.rs
+++ b/services/fwmanager/api/src/config.rs
@@ -100,3 +100,74 @@
         i += 1;
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use core::time::Duration;
+
+    // Board tables run validate() at compile time, where a rejection is a
+    // build error nobody can assert on. These tests call it at runtime to
+    // prove the reject paths actually fire — a vacuous loop would pass
+    // every `const _` check silently.
+
+    const CHECKPOINT: BootCheckpoint<u8> = BootCheckpoint {
+        name: "boot-complete",
+        signal: BootSignal::GpioBootComplete(0),
+        window: Duration::from_secs(1),
+    };
+
+    const DEVICE: DeviceConfig<u8, u8> = DeviceConfig {
+        name: "dev",
+        reset_signal: 0,
+        checkpoints: &[CHECKPOINT],
+        commit_policy: CommitPolicy::Liveness,
+    };
+
+    #[test]
+    fn accepts_a_valid_table() {
+        validate(&[DEVICE]);
+    }
+
+    #[test]
+    #[should_panic(expected = "device name must not be empty")]
+    fn rejects_an_empty_device_name() {
+        validate(&[DEVICE, DeviceConfig { name: "", ..DEVICE }]);
+    }
+
+    #[test]
+    #[should_panic(expected = "at least one boot checkpoint")]
+    fn rejects_an_empty_checkpoint_list() {
+        validate(&[DeviceConfig {
+            checkpoints: &[],
+            ..DEVICE
+        }]);
+    }
+
+    #[test]
+    #[should_panic(expected = "checkpoint name must not be empty")]
+    fn rejects_an_empty_checkpoint_name() {
+        validate(&[DeviceConfig {
+            checkpoints: &[BootCheckpoint {
+                name: "",
+                ..CHECKPOINT
+            }],
+            ..DEVICE
+        }]);
+    }
+
+    #[test]
+    #[should_panic(expected = "checkpoint window must not be zero")]
+    fn rejects_a_zero_checkpoint_window() {
+        validate(&[DeviceConfig {
+            checkpoints: &[
+                CHECKPOINT,
+                BootCheckpoint {
+                    window: Duration::ZERO,
+                    ..CHECKPOINT
+                },
+            ],
+            ..DEVICE
+        }]);
+    }
+}