test(governance): add OpenGov track configuration unit tests

Verify all 18 tracks (15 standard + 3 welati) have correct production
periods, unique IDs/names, proper origin mappings, and no leftover
test values.
This commit is contained in:
2026-02-19 00:10:21 +03:00
parent ce79d33f0d
commit 21d1bc2375
+184
View File
@@ -128,3 +128,187 @@ fn location_conversion_works() {
assert_eq!(got, expected, "{}", tc.description);
}
}
// =============================================================================
// OpenGov Track Configuration Tests
// =============================================================================
use governance::TracksInfo;
use pezkuwichain_runtime_constants::time::{DAYS, HOURS, MINUTES};
use pezpallet_referenda::TracksInfo as TracksInfoTrait;
use std::collections::HashMap;
#[test]
fn governance_tracks_total_count() {
let count = <TracksInfo as TracksInfoTrait<Balance, BlockNumber>>::tracks().count();
assert_eq!(count, 18, "Expected 18 tracks (15 standard + 3 welati), got {count}");
}
#[test]
fn governance_track_ids_are_unique() {
let mut seen = HashSet::new();
for track in <TracksInfo as TracksInfoTrait<Balance, BlockNumber>>::tracks() {
assert!(
seen.insert(track.id),
"Duplicate track ID: {}",
track.id
);
}
}
#[test]
fn governance_track_names_are_unique() {
let mut seen = HashSet::new();
for track in <TracksInfo as TracksInfoTrait<Balance, BlockNumber>>::tracks() {
let name = String::from_utf8_lossy(&track.info.name).to_string();
assert!(seen.insert(name.clone()), "Duplicate track name: {name}");
}
}
#[test]
fn governance_no_test_periods_remain() {
// Ensure no track still uses the old test values (< 1 HOURS for decision_period).
// All production decision periods should be at least 7 DAYS.
for track in <TracksInfo as TracksInfoTrait<Balance, BlockNumber>>::tracks() {
let name = String::from_utf8_lossy(&track.info.name).to_string();
assert!(
track.info.decision_period >= 7 * DAYS,
"Track '{name}' (id={}) has decision_period={} blocks, expected >= {} (7 DAYS)",
track.id,
track.info.decision_period,
7 * DAYS
);
}
}
#[test]
fn governance_production_periods_match_spec() {
// Build expected values: (track_id, prepare, decision, confirm, enact)
let expected: Vec<(u16, &str, BlockNumber, BlockNumber, BlockNumber, BlockNumber)> = vec![
(0, "root", 2 * HOURS, 28 * DAYS, 24 * HOURS, 24 * HOURS),
(1, "whitelisted_caller", 30 * MINUTES, 28 * DAYS, 10 * MINUTES, 10 * MINUTES),
(10, "staking_admin", 2 * HOURS, 14 * DAYS, 3 * HOURS, 10 * MINUTES),
(11, "treasurer", 2 * HOURS, 28 * DAYS, 3 * HOURS, 24 * HOURS),
(12, "lease_admin", 2 * HOURS, 14 * DAYS, 3 * HOURS, 10 * MINUTES),
(13, "fellowship_admin", 2 * HOURS, 14 * DAYS, 3 * HOURS, 10 * MINUTES),
(14, "general_admin", 2 * HOURS, 14 * DAYS, 3 * HOURS, 10 * MINUTES),
(15, "auction_admin", 2 * HOURS, 14 * DAYS, 3 * HOURS, 10 * MINUTES),
(20, "referendum_canceller", 2 * HOURS, 7 * DAYS, 3 * HOURS, 10 * MINUTES),
(21, "referendum_killer", 2 * HOURS, 14 * DAYS, 3 * HOURS, 10 * MINUTES),
(30, "small_tipper", 1 * MINUTES, 7 * DAYS, 10 * MINUTES, 1 * MINUTES),
(31, "big_tipper", 10 * MINUTES, 7 * DAYS, 1 * HOURS, 10 * MINUTES),
(32, "small_spender", 4 * HOURS, 28 * DAYS, 12 * HOURS, 24 * HOURS),
(33, "medium_spender", 4 * HOURS, 28 * DAYS, 24 * HOURS, 24 * HOURS),
(34, "big_spender", 4 * HOURS, 28 * DAYS, 48 * HOURS, 24 * HOURS),
(40, "welati_election", 2 * HOURS, 14 * DAYS, 12 * HOURS, 24 * HOURS),
(41, "welati_admin", 2 * HOURS, 7 * DAYS, 3 * HOURS, 10 * MINUTES),
(42, "citizenship_admin", 2 * HOURS, 14 * DAYS, 6 * HOURS, 24 * HOURS),
];
let tracks: HashMap<u16, _> = <TracksInfo as TracksInfoTrait<Balance, BlockNumber>>::tracks()
.map(|t| (t.id, t.into_owned()))
.collect();
for (id, name, prepare, decision, confirm, enact) in &expected {
let track = tracks.get(id).unwrap_or_else(|| panic!("Track id={id} '{name}' not found"));
let got_name = String::from_utf8_lossy(&track.info.name).trim_end_matches('\0').to_string();
assert_eq!(&got_name, name, "Track id={id} name mismatch");
assert_eq!(
track.info.prepare_period, *prepare,
"Track '{name}' prepare_period: got={}, expected={prepare}",
track.info.prepare_period
);
assert_eq!(
track.info.decision_period, *decision,
"Track '{name}' decision_period: got={}, expected={decision}",
track.info.decision_period
);
assert_eq!(
track.info.confirm_period, *confirm,
"Track '{name}' confirm_period: got={}, expected={confirm}",
track.info.confirm_period
);
assert_eq!(
track.info.min_enactment_period, *enact,
"Track '{name}' min_enactment_period: got={}, expected={enact}",
track.info.min_enactment_period
);
}
assert_eq!(expected.len(), tracks.len(), "Track count mismatch");
}
#[test]
fn governance_welati_tracks_exist() {
let tracks: HashMap<u16, _> = <TracksInfo as TracksInfoTrait<Balance, BlockNumber>>::tracks()
.map(|t| (t.id, t.into_owned()))
.collect();
// welati_election
let t40 = tracks.get(&40).expect("welati_election track (id=40) missing");
assert_eq!(t40.info.max_deciding, 1, "welati_election should allow only 1 deciding");
// welati_admin
let t41 = tracks.get(&41).expect("welati_admin track (id=41) missing");
assert_eq!(t41.info.max_deciding, 10);
// citizenship_admin
let t42 = tracks.get(&42).expect("citizenship_admin track (id=42) missing");
assert_eq!(t42.info.max_deciding, 10);
}
#[test]
fn governance_decision_periods_are_in_days() {
// Verify all decision periods are expressed as multiples of DAYS (not minutes)
for track in <TracksInfo as TracksInfoTrait<Balance, BlockNumber>>::tracks() {
let name = String::from_utf8_lossy(&track.info.name).to_string();
let period = track.info.decision_period;
let days = period / DAYS;
assert!(
period == days * DAYS,
"Track '{name}' decision_period ({period} blocks) is not a whole number of days"
);
assert!(
days >= 7,
"Track '{name}' decision_period is only {days} days, expected at least 7"
);
}
}
#[test]
fn governance_track_for_origin_mapping() {
use governance::pezpallet_custom_origins::Origin;
// Test that track_for() correctly maps each origin to its track ID
let origin_to_track: Vec<(Origin, u16)> = vec![
(Origin::WhitelistedCaller, 1),
(Origin::StakingAdmin, 10),
(Origin::Treasurer, 11),
(Origin::LeaseAdmin, 12),
(Origin::FellowshipAdmin, 13),
(Origin::GeneralAdmin, 14),
(Origin::AuctionAdmin, 15),
(Origin::ReferendumCanceller, 20),
(Origin::ReferendumKiller, 21),
(Origin::SmallTipper, 30),
(Origin::BigTipper, 31),
(Origin::SmallSpender, 32),
(Origin::MediumSpender, 33),
(Origin::BigSpender, 34),
(Origin::WelatiElection, 40),
(Origin::WelatiAdmin, 41),
(Origin::CitizenshipAdmin, 42),
];
for (origin, expected_id) in origin_to_track {
let pallet_origin: <RuntimeOrigin as pezframe_support::traits::OriginTrait>::PalletsOrigin =
origin.clone().into();
let result = <TracksInfo as TracksInfoTrait<Balance, BlockNumber>>::track_for(&pallet_origin);
assert_eq!(
result,
Ok(expected_id),
"Origin {:?} should map to track {expected_id}",
origin
);
}
}