Files
zeekayandHanzo Dev b1cc108c9f gofmt
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 01:51:51 -07:00

174 lines
5.5 KiB
Go

// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build metrics
package security
import (
"strconv"
"testing"
consensusconfig "github.com/luxfi/consensus/config"
"github.com/luxfi/metric"
)
// TestMetrics_SecurityProfileGauge_Reflects_ActiveProfile proves the
// chain-wide profile gauges carry the active profile's posture after
// SetActiveProfile is called.
//
// Closes the "operator can't tell what posture the chain is on"
// audit gap: a 1 → 0 transition on profile_post_quantum_end_to_end
// is the alert primitive an operator wires to PagerDuty.
func TestMetrics_SecurityProfileGauge_Reflects_ActiveProfile(t *testing.T) {
reg := metric.NewRegistry()
metricsInstance := metric.NewWithRegistry("lux_security", reg)
m := NewMetrics(metricsInstance)
strictPQ := consensusconfig.StrictPQ()
m.SetActiveProfile(strictPQ)
// Gather and assert the gauge family for each expected metric
// carries the strict-PQ posture (1 on each headline boolean).
families, err := reg.Gather()
if err != nil {
t.Fatalf("Gather: %v", err)
}
values := flattenMetricValues(families)
expect := map[string]float64{
"lux_security_profile_post_quantum_end_to_end:profile_id=" +
strconv.Itoa(int(consensusconfig.ProfileStrictPQ)) +
",profile_name=STRICT_PQ": 1,
"lux_security_profile_nist_friendly:profile_id=" +
strconv.Itoa(int(consensusconfig.ProfileStrictPQ)): 1,
"lux_security_profile_lux_canonical:profile_id=" +
strconv.Itoa(int(consensusconfig.ProfileStrictPQ)): 1,
"lux_security_profile_unsafe_mode_enabled:profile_id=" +
strconv.Itoa(int(consensusconfig.ProfileStrictPQ)): 0,
}
for key, want := range expect {
got, ok := values[key]
if !ok {
t.Errorf("missing metric %s; got families: %v", key, values)
continue
}
if got != want {
t.Errorf("metric %s = %v; want %v", key, got, want)
}
}
}
// TestMetrics_SecurityProfileGauge_Reflects_UnsafeFork proves a
// classical-compat fork sets unsafe_mode_enabled=1, post-quantum=0,
// nist_friendly=1 (the unsafe fork keeps SHA3 but loses every
// classical-primitive Forbid bit), and lux_canonical=0.
func TestMetrics_SecurityProfileGauge_Reflects_UnsafeFork(t *testing.T) {
reg := metric.NewRegistry()
metricsInstance := metric.NewWithRegistry("lux_security", reg)
m := NewMetrics(metricsInstance)
pCopy := consensusconfig.ForkClassicalCompatUnsafeProfile
hash, err := pCopy.ComputeHash()
if err != nil {
t.Fatalf("ComputeHash: %v", err)
}
pCopy.ProfileHash = hash
m.SetActiveProfile(&pCopy)
families, err := reg.Gather()
if err != nil {
t.Fatalf("Gather: %v", err)
}
values := flattenMetricValues(families)
idLabel := strconv.Itoa(int(consensusconfig.ForkClassicalCompatUnsafeProfileID))
cases := map[string]float64{
"lux_security_profile_post_quantum_end_to_end:profile_id=" + idLabel +
",profile_name=FORK_CLASSICAL_COMPAT_UNSAFE": 0,
"lux_security_profile_lux_canonical:profile_id=" + idLabel: 0,
"lux_security_profile_unsafe_mode_enabled:profile_id=" + idLabel: 1,
"lux_security_profile_nist_friendly:profile_id=" + idLabel: 1,
}
for key, want := range cases {
got, ok := values[key]
if !ok {
t.Errorf("missing metric %s; values=%v", key, values)
continue
}
if got != want {
t.Errorf("metric %s = %v; want %v", key, got, want)
}
}
}
// TestMetrics_ObserveMempoolClassicalCredential_IncrementsCounter
// proves the per-chain classical-credentials counter wires through
// the call-site shim correctly.
func TestMetrics_ObserveMempoolClassicalCredential_IncrementsCounter(t *testing.T) {
reg := metric.NewRegistry()
metricsInstance := metric.NewWithRegistry("lux_security", reg)
m := NewMetrics(metricsInstance)
m.ObserveMempoolClassicalCredential("P")
m.ObserveMempoolClassicalCredential("P")
m.ObserveMempoolClassicalCredential("X")
families, err := reg.Gather()
if err != nil {
t.Fatalf("Gather: %v", err)
}
values := flattenMetricValues(families)
if got := values["lux_security_mempool_classical_credentials_total:chain=P"]; got != 2 {
t.Errorf("P counter = %v; want 2", got)
}
if got := values["lux_security_mempool_classical_credentials_total:chain=X"]; got != 1 {
t.Errorf("X counter = %v; want 1", got)
}
}
// TestMetrics_SetZchainProofLagEpochs_StoresValue proves the
// zchain_proof_lag_epochs gauge reflects the supplied epoch count.
func TestMetrics_SetZchainProofLagEpochs_StoresValue(t *testing.T) {
reg := metric.NewRegistry()
metricsInstance := metric.NewWithRegistry("lux_security", reg)
m := NewMetrics(metricsInstance)
m.SetZchainProofLagEpochs(7)
families, err := reg.Gather()
if err != nil {
t.Fatalf("Gather: %v", err)
}
values := flattenMetricValues(families)
got, ok := values["lux_security_zchain_proof_lag_epochs:"]
if !ok {
t.Fatalf("missing zchain_proof_lag_epochs; values=%v", values)
}
if got != 7 {
t.Errorf("zchain_proof_lag_epochs = %v; want 7", got)
}
}
// flattenMetricValues converts a Gather() result into a map keyed by
// "<metric_name>:<sorted_label_pairs>" → value. The flattening lets
// tests assert on a single canonical key per (metric, label set)
// without writing nested loops.
func flattenMetricValues(families []*metric.MetricFamily) map[string]float64 {
out := make(map[string]float64)
for _, mf := range families {
for _, m := range mf.Metrics {
labels := ""
for i, lp := range m.Labels {
if i > 0 {
labels += ","
}
labels += lp.Name + "=" + lp.Value
}
out[mf.Name+":"+labels] = m.Value.Value
}
}
return out
}