commit f043868a55b261553827ad3a9cfe7c41895c9280
parent aa50e41dad8884372c6c0258d256301c405f98f4
Author: triesap <tyson@radroots.org>
Date: Sun, 22 Mar 2026 16:02:41 +0000
tests: migrate identity fixtures to approved values
- add approved test-fixture dev-dependencies to identity, nostr, nostr-connect, and nostr-accounts
- replace generated and copied identity and relay literals in the relevant tests with approved fixture imports
- keep generation and invalid-shape cases only where the behavior under test depends on them
- verify targeted Nix test lanes; nix run .#contract remains blocked by no space on device
Diffstat:
12 files changed, 219 insertions(+), 161 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -2168,6 +2168,7 @@ dependencies = [
"nostr",
"radroots-events",
"radroots-runtime",
+ "radroots-test-fixtures",
"secrecy",
"serde",
"serde_json",
@@ -2227,6 +2228,7 @@ dependencies = [
"radroots-events",
"radroots-events-codec",
"radroots-identity",
+ "radroots-test-fixtures",
"reqwest",
"serde",
"serde_json",
@@ -2243,6 +2245,7 @@ dependencies = [
"radroots-nostr-ndb",
"radroots-nostr-signer",
"radroots-runtime",
+ "radroots-test-fixtures",
"serde",
"serde_json",
"tempfile",
@@ -2255,6 +2258,7 @@ name = "radroots-nostr-connect"
version = "0.1.0-alpha.1"
dependencies = [
"nostr",
+ "radroots-test-fixtures",
"serde",
"serde_json",
"thiserror 1.0.69",
diff --git a/crates/identity/Cargo.toml b/crates/identity/Cargo.toml
@@ -39,4 +39,5 @@ ts-rs = { workspace = true, optional = true }
zeroize = { workspace = true, optional = true }
[dev-dependencies]
+radroots-test-fixtures = { workspace = true }
tempfile = { workspace = true }
diff --git a/crates/identity/tests/identity.rs b/crates/identity/tests/identity.rs
@@ -7,8 +7,18 @@ use radroots_identity::{
use radroots_identity::{
RadrootsIdentityEncryptedSecretKeyOptions, RadrootsIdentityEncryptedSecretKeySecurity,
};
+use radroots_test_fixtures::{ApprovedFixtureIdentity, FIXTURE_ALICE, FIXTURE_BOB};
use std::path::PathBuf;
+fn fixture_keys(fixture: ApprovedFixtureIdentity) -> nostr::Keys {
+ let secret = nostr::SecretKey::from_hex(fixture.secret_key_hex).unwrap();
+ nostr::Keys::new(secret)
+}
+
+fn fixture_identity(fixture: ApprovedFixtureIdentity) -> RadrootsIdentity {
+ RadrootsIdentity::from_secret_key_str(fixture.secret_key_hex).unwrap()
+}
+
fn profile_with_identifier(value: &str) -> RadrootsIdentityProfile {
RadrootsIdentityProfile {
identifier: Some(value.to_string()),
@@ -18,14 +28,13 @@ fn profile_with_identifier(value: &str) -> RadrootsIdentityProfile {
fn sample_event(content: &str) -> nostr::Event {
nostr::EventBuilder::text_note(content)
- .sign_with_keys(&nostr::Keys::generate())
+ .sign_with_keys(&fixture_keys(FIXTURE_ALICE))
.unwrap()
}
#[test]
fn load_from_json_file_hex() {
- let keys = nostr::Keys::generate();
- let identity = RadrootsIdentity::new(keys.clone());
+ let identity = fixture_identity(FIXTURE_ALICE);
let json = serde_json::to_string(&identity.to_file()).unwrap();
let dir = tempfile::tempdir().unwrap();
@@ -33,13 +42,12 @@ fn load_from_json_file_hex() {
std::fs::write(&path, json).unwrap();
let loaded = RadrootsIdentity::load_from_path_auto(&path).unwrap();
- assert_eq!(loaded.public_key(), keys.public_key());
+ assert_eq!(loaded.public_key().to_hex(), FIXTURE_ALICE.public_key_hex);
}
#[test]
fn load_from_json_file_profile() {
- let keys = nostr::Keys::generate();
- let mut identity = RadrootsIdentity::new(keys.clone());
+ let mut identity = fixture_identity(FIXTURE_ALICE);
let profile = RadrootsProfile {
name: "relay-agent".to_string(),
display_name: Some("Relay Agent".to_string()),
@@ -71,8 +79,7 @@ fn load_from_json_file_profile() {
#[test]
fn load_from_text_file_hex() {
- let keys = nostr::Keys::generate();
- let identity = RadrootsIdentity::new(keys.clone());
+ let identity = fixture_identity(FIXTURE_ALICE);
let secret = identity.secret_key_hex();
let dir = tempfile::tempdir().unwrap();
@@ -80,13 +87,12 @@ fn load_from_text_file_hex() {
std::fs::write(&path, secret).unwrap();
let loaded = RadrootsIdentity::load_from_path_auto(&path).unwrap();
- assert_eq!(loaded.public_key(), keys.public_key());
+ assert_eq!(loaded.public_key().to_hex(), FIXTURE_ALICE.public_key_hex);
}
#[test]
fn load_from_text_file_nsec() {
- let keys = nostr::Keys::generate();
- let identity = RadrootsIdentity::new(keys.clone());
+ let identity = fixture_identity(FIXTURE_ALICE);
let secret = identity.secret_key_nsec();
let dir = tempfile::tempdir().unwrap();
@@ -94,13 +100,12 @@ fn load_from_text_file_nsec() {
std::fs::write(&path, secret).unwrap();
let loaded = RadrootsIdentity::load_from_path_auto(&path).unwrap();
- assert_eq!(loaded.public_key(), keys.public_key());
+ assert_eq!(loaded.public_key().to_hex(), FIXTURE_ALICE.public_key_hex);
}
#[test]
fn load_from_binary_file() {
- let keys = nostr::Keys::generate();
- let identity = RadrootsIdentity::new(keys.clone());
+ let identity = fixture_identity(FIXTURE_ALICE);
let secret = identity.secret_key_bytes();
let dir = tempfile::tempdir().unwrap();
@@ -108,7 +113,7 @@ fn load_from_binary_file() {
std::fs::write(&path, secret).unwrap();
let loaded = RadrootsIdentity::load_from_path_auto(&path).unwrap();
- assert_eq!(loaded.public_key(), keys.public_key());
+ assert_eq!(loaded.public_key().to_hex(), FIXTURE_ALICE.public_key_hex);
}
#[test]
@@ -134,8 +139,7 @@ fn load_or_generate_missing_allowed_creates_json() {
#[test]
fn load_from_json_file_public_key_npub() {
- let keys = nostr::Keys::generate();
- let identity = RadrootsIdentity::new(keys.clone());
+ let identity = fixture_identity(FIXTURE_ALICE);
let mut file = identity.to_file();
file.public_key = Some(identity.public_key_npub());
let json = serde_json::to_string(&file).unwrap();
@@ -145,16 +149,14 @@ fn load_from_json_file_public_key_npub() {
std::fs::write(&path, json).unwrap();
let loaded = RadrootsIdentity::load_from_path_auto(&path).unwrap();
- assert_eq!(loaded.public_key(), keys.public_key());
+ assert_eq!(loaded.public_key().to_hex(), FIXTURE_ALICE.public_key_hex);
}
#[test]
fn load_from_json_file_public_key_mismatch() {
- let keys = nostr::Keys::generate();
- let identity = RadrootsIdentity::new(keys);
- let other_keys = nostr::Keys::generate();
+ let identity = fixture_identity(FIXTURE_ALICE);
let mut file = identity.to_file();
- file.public_key = Some(other_keys.public_key().to_hex());
+ file.public_key = Some(FIXTURE_BOB.public_key_hex.to_string());
let json = serde_json::to_string(&file).unwrap();
let dir = tempfile::tempdir().unwrap();
@@ -167,36 +169,28 @@ fn load_from_json_file_public_key_mismatch() {
#[test]
fn identity_id_matches_public_key_hex() {
- let keys = nostr::Keys::generate();
- let identity = RadrootsIdentity::new(keys.clone());
+ let identity = fixture_identity(FIXTURE_ALICE);
let id = identity.id();
- assert_eq!(id.as_str(), keys.public_key().to_hex());
+ assert_eq!(id.as_str(), FIXTURE_ALICE.public_key_hex);
}
#[test]
fn identity_id_parses_hex_and_npub() {
- use nostr::nips::nip19::ToBech32;
-
- let keys = nostr::Keys::generate();
- let public_key = keys.public_key();
- let hex = public_key.to_hex();
- let npub = public_key.to_bech32().unwrap();
-
- let from_hex = RadrootsIdentityId::parse(hex.as_str()).unwrap();
- let from_npub = RadrootsIdentityId::parse(npub.as_str()).unwrap();
- assert_eq!(from_hex.as_str(), hex);
- assert_eq!(from_npub.as_str(), hex);
+ let from_hex = RadrootsIdentityId::parse(FIXTURE_ALICE.public_key_hex).unwrap();
+ let from_npub = RadrootsIdentityId::parse(FIXTURE_ALICE.npub).unwrap();
+ assert_eq!(from_hex.as_str(), FIXTURE_ALICE.public_key_hex);
+ assert_eq!(from_npub.as_str(), FIXTURE_ALICE.public_key_hex);
}
#[test]
fn to_public_projection_excludes_secret_key_fields() {
- let keys = nostr::Keys::generate();
- let identity = RadrootsIdentity::new(keys.clone());
+ let identity = fixture_identity(FIXTURE_ALICE);
let public = identity.to_public();
- assert_eq!(public.id.as_str(), keys.public_key().to_hex());
- assert_eq!(public.public_key_hex, keys.public_key().to_hex());
+ assert_eq!(public.id.as_str(), FIXTURE_ALICE.public_key_hex);
+ assert_eq!(public.public_key_hex, FIXTURE_ALICE.public_key_hex);
+ assert_eq!(public.public_key_npub, FIXTURE_ALICE.npub);
assert!(public.profile.is_none());
let json = serde_json::to_string(&public).unwrap();
@@ -206,9 +200,8 @@ fn to_public_projection_excludes_secret_key_fields() {
#[test]
fn identity_id_trait_paths_and_string_conversions() {
- let keys = nostr::Keys::generate();
- let public_key = keys.public_key();
- let public_key_hex = public_key.to_hex();
+ let public_key = fixture_identity(FIXTURE_ALICE).public_key();
+ let public_key_hex = FIXTURE_ALICE.public_key_hex.to_string();
let from_impl = RadrootsIdentityId::from(public_key);
assert_eq!(from_impl.as_ref(), public_key_hex);
@@ -220,9 +213,10 @@ fn identity_id_trait_paths_and_string_conversions() {
#[test]
fn identity_profile_state_mutation_paths() {
- let keys = nostr::Keys::generate();
- let mut identity =
- RadrootsIdentity::with_profile(keys.clone(), RadrootsIdentityProfile::default());
+ let mut identity = RadrootsIdentity::with_profile(
+ fixture_keys(FIXTURE_ALICE),
+ RadrootsIdentityProfile::default(),
+ );
assert!(identity.profile().is_none());
identity.set_profile(RadrootsIdentityProfile::default());
@@ -245,36 +239,42 @@ fn identity_profile_state_mutation_paths() {
identity.clear_profile();
assert!(identity.profile().is_none());
- let public_without_profile = RadrootsIdentityPublic::new(keys.public_key())
+ let public_without_profile = RadrootsIdentityPublic::new(identity.public_key())
.with_profile(RadrootsIdentityProfile::default());
assert!(public_without_profile.profile.is_none());
- let public_with_profile = RadrootsIdentityPublic::new(keys.public_key()).with_profile(profile);
+ let public_with_profile =
+ RadrootsIdentityPublic::new(identity.public_key()).with_profile(profile);
assert!(public_with_profile.profile.is_some());
}
#[test]
fn identity_accessor_paths_and_secret_formats() {
- let keys = nostr::Keys::generate();
- let identity = RadrootsIdentity::new(keys.clone());
+ let identity = fixture_identity(FIXTURE_ALICE);
- assert_eq!(identity.keys().public_key(), keys.public_key());
- assert_eq!(identity.public_key(), keys.public_key());
- assert!(identity.npub().starts_with("npub1"));
- assert!(identity.nsec().starts_with("nsec1"));
+ assert_eq!(
+ identity.keys().public_key().to_hex(),
+ FIXTURE_ALICE.public_key_hex
+ );
+ assert_eq!(identity.public_key().to_hex(), FIXTURE_ALICE.public_key_hex);
+ assert_eq!(identity.npub(), FIXTURE_ALICE.npub);
+ assert_eq!(identity.nsec(), FIXTURE_ALICE.nsec);
let file_nsec = identity.to_file_with_secret_format(RadrootsIdentitySecretKeyFormat::Nsec);
- assert!(file_nsec.secret_key.starts_with("nsec1"));
+ assert_eq!(file_nsec.secret_key, FIXTURE_ALICE.nsec);
- let from_keys: RadrootsIdentity = keys.clone().into();
+ let from_keys: RadrootsIdentity = fixture_keys(FIXTURE_ALICE).into();
let roundtrip_keys = from_keys.clone().into_keys();
- assert_eq!(roundtrip_keys.public_key(), keys.public_key());
+ assert_eq!(
+ roundtrip_keys.public_key().to_hex(),
+ FIXTURE_ALICE.public_key_hex
+ );
}
#[cfg(feature = "nip49")]
#[test]
fn encrypted_secret_key_round_trips_to_identity() {
- let identity = RadrootsIdentity::generate();
+ let identity = fixture_identity(FIXTURE_ALICE);
let encrypted = identity
.encrypt_secret_key_ncryptsec("fixture-password")
.unwrap();
@@ -291,7 +291,7 @@ fn encrypted_secret_key_options_propagate_to_output() {
use nostr::nips::nip19::FromBech32;
use nostr::nips::nip49::{EncryptedSecretKey, KeySecurity};
- let identity = RadrootsIdentity::generate();
+ let identity = fixture_identity(FIXTURE_ALICE);
let encrypted = identity
.encrypt_secret_key_ncryptsec_with_options(
"fixture-password",
@@ -309,7 +309,7 @@ fn encrypted_secret_key_options_propagate_to_output() {
#[cfg(feature = "nip49")]
#[test]
fn encrypted_secret_key_rejects_invalid_and_wrong_password_inputs() {
- let identity = RadrootsIdentity::generate();
+ let identity = fixture_identity(FIXTURE_ALICE);
let encrypted = identity
.encrypt_secret_key_ncryptsec("fixture-password")
.unwrap();
@@ -390,8 +390,7 @@ fn load_from_path_rejects_invalid_payloads() {
#[test]
fn load_from_json_file_without_public_key_succeeds() {
- let keys = nostr::Keys::generate();
- let identity = RadrootsIdentity::new(keys.clone());
+ let identity = fixture_identity(FIXTURE_ALICE);
let mut file = identity.to_file();
file.public_key = None;
let json = serde_json::to_string(&file).unwrap();
@@ -401,7 +400,7 @@ fn load_from_json_file_without_public_key_succeeds() {
std::fs::write(&path, json).unwrap();
let loaded = RadrootsIdentity::load_from_path_auto(&path).unwrap();
- assert_eq!(loaded.public_key(), keys.public_key());
+ assert_eq!(loaded.public_key().to_hex(), FIXTURE_ALICE.public_key_hex);
}
#[test]
@@ -420,8 +419,7 @@ fn load_from_json_file_rejects_invalid_secret_key_string() {
#[test]
fn load_from_json_file_rejects_invalid_public_key_value() {
- let keys = nostr::Keys::generate();
- let identity = RadrootsIdentity::new(keys);
+ let identity = fixture_identity(FIXTURE_ALICE);
let mut file = identity.to_file();
file.public_key = Some("invalid-public-key".to_string());
let json = serde_json::to_string(&file).unwrap();
@@ -436,7 +434,7 @@ fn load_from_json_file_rejects_invalid_public_key_value() {
#[test]
fn save_json_rejects_directory_target() {
- let identity = RadrootsIdentity::generate();
+ let identity = fixture_identity(FIXTURE_ALICE);
let dir = tempfile::tempdir().unwrap();
let err = identity.save_json(dir.path()).unwrap_err();
assert!(matches!(err, IdentityError::Store(_)));
@@ -447,7 +445,7 @@ fn save_json_rejects_directory_target() {
fn save_json_reports_write_failure_on_read_only_directory() {
use std::os::unix::fs::PermissionsExt;
- let identity = RadrootsIdentity::generate();
+ let identity = fixture_identity(FIXTURE_ALICE);
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("identity.json");
identity.save_json(path.as_path()).unwrap();
@@ -502,8 +500,7 @@ fn load_or_generate_uses_default_path_when_missing() {
#[test]
fn load_or_generate_prefers_existing_path() {
- let keys = nostr::Keys::generate();
- let identity = RadrootsIdentity::new(keys.clone());
+ let identity = fixture_identity(FIXTURE_ALICE);
let payload = serde_json::to_string(&identity.to_file()).unwrap();
let dir = tempfile::tempdir().unwrap();
@@ -511,12 +508,12 @@ fn load_or_generate_prefers_existing_path() {
std::fs::write(&path, payload).unwrap();
let loaded = RadrootsIdentity::load_or_generate(Some(&path), false).unwrap();
- assert_eq!(loaded.public_key(), keys.public_key());
+ assert_eq!(loaded.public_key().to_hex(), FIXTURE_ALICE.public_key_hex);
}
#[test]
fn path_ref_variants_cover_success_paths() {
- let identity = RadrootsIdentity::generate();
+ let identity = fixture_identity(FIXTURE_ALICE);
let dir = tempfile::tempdir().unwrap();
let saved_path = dir.path().join("saved.json");
@@ -562,8 +559,7 @@ fn identity_profile_is_empty_checks_metadata_and_application_handler() {
fn secret_key_hex_secret_returns_secret_string() {
use secrecy::ExposeSecret;
- let keys = nostr::Keys::generate();
- let identity = RadrootsIdentity::new(keys);
+ let identity = fixture_identity(FIXTURE_ALICE);
let secret = identity.secret_key_hex_secret();
assert_eq!(secret.expose_secret(), &identity.secret_key_hex());
}
@@ -571,8 +567,7 @@ fn secret_key_hex_secret_returns_secret_string() {
#[cfg(feature = "zeroize")]
#[test]
fn secret_key_zeroizing_bytes_matches_raw_secret() {
- let keys = nostr::Keys::generate();
- let identity = RadrootsIdentity::new(keys);
+ let identity = fixture_identity(FIXTURE_ALICE);
let raw = identity.secret_key_bytes();
let protected = identity.secret_key_bytes_zeroizing();
assert_eq!(&*protected, &raw);
diff --git a/crates/nostr-accounts/Cargo.toml b/crates/nostr-accounts/Cargo.toml
@@ -47,4 +47,5 @@ thiserror = { workspace = true }
zeroize = { workspace = true }
[dev-dependencies]
+radroots-test-fixtures = { workspace = true }
tempfile = { workspace = true }
diff --git a/crates/nostr-accounts/src/vault.rs b/crates/nostr-accounts/src/vault.rs
@@ -134,15 +134,17 @@ impl RadrootsNostrSecretVault for RadrootsNostrSecretVaultOsKeyring {
mod tests {
use super::*;
use radroots_identity::RadrootsIdentityId;
+ use radroots_test_fixtures::FIXTURE_ALICE;
use std::thread;
+ fn fixture_account_id() -> RadrootsIdentityId {
+ RadrootsIdentityId::parse(FIXTURE_ALICE.public_key_hex).expect("account id")
+ }
+
#[test]
fn memory_vault_round_trip() {
let vault = RadrootsNostrSecretVaultMemory::new();
- let account_id = RadrootsIdentityId::parse(
- "3bf0c63f0f4478a288f6b67f0429dbf7f5119d4fa7218a4c40ef1378f80f7606",
- )
- .expect("account id");
+ let account_id = fixture_account_id();
vault
.store_secret_hex(&account_id, "abc123")
.expect("store");
@@ -156,10 +158,7 @@ mod tests {
#[test]
fn memory_vault_distinguishes_present_and_missing_entries() {
let vault = RadrootsNostrSecretVaultMemory::new();
- let account_id = RadrootsIdentityId::parse(
- "3bf0c63f0f4478a288f6b67f0429dbf7f5119d4fa7218a4c40ef1378f80f7606",
- )
- .expect("account id");
+ let account_id = fixture_account_id();
assert!(
vault
@@ -184,10 +183,7 @@ mod tests {
#[test]
fn memory_vault_reports_poisoned_lock() {
let vault = RadrootsNostrSecretVaultMemory::new();
- let account_id = RadrootsIdentityId::parse(
- "3bf0c63f0f4478a288f6b67f0429dbf7f5119d4fa7218a4c40ef1378f80f7606",
- )
- .expect("account id");
+ let account_id = fixture_account_id();
let shared = vault.entries.clone();
let _ = thread::spawn(move || {
diff --git a/crates/nostr-connect/Cargo.toml b/crates/nostr-connect/Cargo.toml
@@ -19,3 +19,6 @@ serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
thiserror = { workspace = true }
url = { workspace = true }
+
+[dev-dependencies]
+radroots-test-fixtures = { workspace = true }
diff --git a/crates/nostr-connect/tests/coverage.rs b/crates/nostr-connect/tests/coverage.rs
@@ -5,21 +5,30 @@ use radroots_nostr_connect::prelude::{
RadrootsNostrConnectRequestMessage, RadrootsNostrConnectResponse,
RadrootsNostrConnectResponseEnvelope, RadrootsNostrConnectUri,
};
+use radroots_test_fixtures::{
+ APP_PRIMARY_HTTPS, CDN_PRIMARY_HTTPS, FIXTURE_ALICE, RELAY_PRIMARY_WSS, RELAY_SECONDARY_WSS,
+ RELAY_TERTIARY_WSS,
+};
use serde_json::{Value, json};
use std::str::FromStr;
fn test_public_key() -> PublicKey {
- PublicKey::parse("83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5")
- .expect("public key")
+ PublicKey::parse(FIXTURE_ALICE.public_key_hex).expect("public key")
}
fn test_keys() -> Keys {
- let secret_key =
- SecretKey::from_hex("6d5f4530cbf6a9e8f021eb409c8c5f2ee7ea123c76364b6f53c2d8a3507f7f5b")
- .expect("secret key");
+ let secret_key = SecretKey::from_hex(FIXTURE_ALICE.secret_key_hex).expect("secret key");
Keys::new(secret_key)
}
+fn encode_uri_component(value: &str) -> String {
+ url::form_urlencoded::byte_serialize(value.as_bytes()).collect()
+}
+
+fn logo_url() -> String {
+ format!("{CDN_PRIMARY_HTTPS}/logo.png")
+}
+
fn unsigned_event() -> UnsignedEvent {
serde_json::from_value(json!({
"pubkey": test_public_key().to_hex(),
@@ -156,18 +165,26 @@ fn error_method_and_permission_surfaces_cover_public_paths() {
#[test]
fn uri_surface_covers_rendering_ignored_queries_and_error_paths() {
- let bunker = RadrootsNostrConnectUri::parse(
- "bunker://83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5?relay=wss%3A%2F%2Frelay.example.com&foo=bar",
- )
+ let bunker = RadrootsNostrConnectUri::parse(&format!(
+ "bunker://{}?relay={}&foo=bar",
+ FIXTURE_ALICE.public_key_hex,
+ encode_uri_component(RELAY_PRIMARY_WSS),
+ ))
.expect("parse bunker");
let bunker_rendered = bunker.to_string();
- assert!(bunker_rendered.contains("relay=wss%3A%2F%2Frelay.example.com"));
+ assert!(bunker_rendered.contains(&format!(
+ "relay={}",
+ encode_uri_component(RELAY_PRIMARY_WSS)
+ )));
assert!(!bunker_rendered.contains("secret="));
- let minimal_client: RadrootsNostrConnectUri =
- "nostrconnect://83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5?relay=wss%3A%2F%2Frelay.example.com&secret=shared"
- .parse()
- .expect("parse minimal client");
+ let minimal_client: RadrootsNostrConnectUri = format!(
+ "nostrconnect://{}?relay={}&secret=shared",
+ FIXTURE_ALICE.public_key_hex,
+ encode_uri_component(RELAY_PRIMARY_WSS),
+ )
+ .parse()
+ .expect("parse minimal client");
let minimal_client_rendered = minimal_client.to_string();
assert!(minimal_client_rendered.contains("secret=shared"));
assert!(!minimal_client_rendered.contains("perms="));
@@ -175,15 +192,22 @@ fn uri_surface_covers_rendering_ignored_queries_and_error_paths() {
assert!(!minimal_client_rendered.contains("url="));
assert!(!minimal_client_rendered.contains("image="));
- let metadata_client = RadrootsNostrConnectUri::parse(
- "nostrconnect://83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5?relay=wss%3A%2F%2Frelay.example.com&secret=shared&perms=ping&name=myc&url=https%3A%2F%2Fexample.com&image=https%3A%2F%2Fexample.com%2Flogo.png&ignored=value",
- )
+ let metadata_client = RadrootsNostrConnectUri::parse(&format!(
+ "nostrconnect://{}?relay={}&secret=shared&perms=ping&name=myc&url={}&image={}&ignored=value",
+ FIXTURE_ALICE.public_key_hex,
+ encode_uri_component(RELAY_PRIMARY_WSS),
+ encode_uri_component(APP_PRIMARY_HTTPS),
+ encode_uri_component(&logo_url()),
+ ))
.expect("parse metadata client");
let metadata_rendered = metadata_client.to_string();
assert!(metadata_rendered.contains("perms=ping"));
assert!(metadata_rendered.contains("name=myc"));
- assert!(metadata_rendered.contains("url=https%3A%2F%2Fexample.com%2F"));
- assert!(metadata_rendered.contains("image=https%3A%2F%2Fexample.com%2Flogo.png"));
+ assert!(metadata_rendered.contains(&format!(
+ "url={}",
+ encode_uri_component(&format!("{APP_PRIMARY_HTTPS}/"))
+ )));
+ assert!(metadata_rendered.contains(&format!("image={}", encode_uri_component(&logo_url()))));
assert!(matches!(
RadrootsNostrConnectUri::parse("not a uri"),
@@ -196,21 +220,22 @@ fn uri_surface_covers_rendering_ignored_queries_and_error_paths() {
Err(RadrootsNostrConnectError::MissingPublicKey)
));
assert!(matches!(
- RadrootsNostrConnectUri::parse(
- "bunker://83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5"
- ),
+ RadrootsNostrConnectUri::parse(&format!("bunker://{}", FIXTURE_ALICE.public_key_hex)),
Err(RadrootsNostrConnectError::MissingRelay)
));
assert!(matches!(
- RadrootsNostrConnectUri::parse(
- "nostrconnect://83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5?secret=abc"
- ),
+ RadrootsNostrConnectUri::parse(&format!(
+ "nostrconnect://{}?secret=abc",
+ FIXTURE_ALICE.public_key_hex
+ )),
Err(RadrootsNostrConnectError::MissingRelay)
));
assert!(matches!(
- RadrootsNostrConnectUri::parse(
- "nostrconnect://83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5?relay=wss%3A%2F%2Frelay.example.com"
- ),
+ RadrootsNostrConnectUri::parse(&format!(
+ "nostrconnect://{}?relay={}",
+ FIXTURE_ALICE.public_key_hex,
+ encode_uri_component(RELAY_PRIMARY_WSS),
+ )),
Err(RadrootsNostrConnectError::MissingSecret)
));
assert!(matches!(
@@ -224,15 +249,18 @@ fn uri_surface_covers_rendering_ignored_queries_and_error_paths() {
Err(RadrootsNostrConnectError::InvalidPublicKey { .. })
));
assert!(matches!(
- RadrootsNostrConnectUri::parse(
- "nostrconnect://83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5?relay=http%3A%2F%2Frelay.example.com&secret=abc"
- ),
+ RadrootsNostrConnectUri::parse(&format!(
+ "nostrconnect://{}?relay=http%3A%2F%2Frelay.example.com&secret=abc",
+ FIXTURE_ALICE.public_key_hex
+ )),
Err(RadrootsNostrConnectError::InvalidRelayUrl { .. })
));
assert!(matches!(
- RadrootsNostrConnectUri::parse(
- "nostrconnect://83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5?relay=wss%3A%2F%2Frelay.example.com&secret=abc&url=not-a-url"
- ),
+ RadrootsNostrConnectUri::parse(&format!(
+ "nostrconnect://{}?relay={}&secret=abc&url=not-a-url",
+ FIXTURE_ALICE.public_key_hex,
+ encode_uri_component(RELAY_PRIMARY_WSS),
+ )),
Err(RadrootsNostrConnectError::InvalidUrl { value, .. }) if value == "not-a-url"
));
assert!(matches!(
@@ -240,21 +268,26 @@ fn uri_surface_covers_rendering_ignored_queries_and_error_paths() {
Err(RadrootsNostrConnectError::InvalidPublicKey { .. })
));
assert!(matches!(
- RadrootsNostrConnectUri::parse(
- "bunker://83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5?relay=http%3A%2F%2Frelay.example.com"
- ),
+ RadrootsNostrConnectUri::parse(&format!(
+ "bunker://{}?relay=http%3A%2F%2Frelay.example.com",
+ FIXTURE_ALICE.public_key_hex
+ )),
Err(RadrootsNostrConnectError::InvalidRelayUrl { .. })
));
assert!(matches!(
- RadrootsNostrConnectUri::parse(
- "nostrconnect://83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5?relay=wss%3A%2F%2Frelay.example.com&secret=abc&perms=sign_event%3A"
- ),
+ RadrootsNostrConnectUri::parse(&format!(
+ "nostrconnect://{}?relay={}&secret=abc&perms=sign_event%3A",
+ FIXTURE_ALICE.public_key_hex,
+ encode_uri_component(RELAY_PRIMARY_WSS),
+ )),
Err(RadrootsNostrConnectError::InvalidPermission(value)) if value == "sign_event:"
));
assert!(matches!(
- RadrootsNostrConnectUri::parse(
- "nostrconnect://83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5?relay=wss%3A%2F%2Frelay.example.com&secret=abc&image=not-a-url"
- ),
+ RadrootsNostrConnectUri::parse(&format!(
+ "nostrconnect://{}?relay={}&secret=abc&image=not-a-url",
+ FIXTURE_ALICE.public_key_hex,
+ encode_uri_component(RELAY_PRIMARY_WSS),
+ )),
Err(RadrootsNostrConnectError::InvalidUrl { value, .. }) if value == "not-a-url"
));
}
@@ -636,13 +669,13 @@ fn response_surface_covers_success_and_error_paths() {
),
(
RadrootsNostrConnectResponse::RelayList(vec![
- relay("wss://relay1.example.com"),
- relay("wss://relay2.example.com"),
+ relay(RELAY_SECONDARY_WSS),
+ relay(RELAY_TERTIARY_WSS),
]),
RadrootsNostrConnectMethod::SwitchRelays,
RadrootsNostrConnectResponse::RelayList(vec![
- relay("wss://relay1.example.com"),
- relay("wss://relay2.example.com"),
+ relay(RELAY_SECONDARY_WSS),
+ relay(RELAY_TERTIARY_WSS),
]),
),
(
@@ -761,12 +794,12 @@ fn response_surface_covers_success_and_error_paths() {
&RadrootsNostrConnectMethod::SwitchRelays,
RadrootsNostrConnectResponseEnvelope {
id: "req-switch".to_owned(),
- result: Some(json!("[\"wss://relay1.example.com\"]")),
+ result: Some(json!(format!("[\"{RELAY_SECONDARY_WSS}\"]"))),
error: None,
},
)
.expect("parse stringified relay list"),
- RadrootsNostrConnectResponse::RelayList(vec![relay("wss://relay1.example.com")])
+ RadrootsNostrConnectResponse::RelayList(vec![relay(RELAY_SECONDARY_WSS)])
);
assert!(matches!(
diff --git a/crates/nostr-connect/tests/protocol.rs b/crates/nostr-connect/tests/protocol.rs
@@ -4,24 +4,40 @@ use radroots_nostr_connect::prelude::{
RadrootsNostrConnectRequest, RadrootsNostrConnectRequestMessage, RadrootsNostrConnectResponse,
RadrootsNostrConnectResponseEnvelope, RadrootsNostrConnectUri,
};
+use radroots_test_fixtures::{
+ APP_PRIMARY_HTTPS, CDN_PRIMARY_HTTPS, FIXTURE_ALICE, RELAY_PRIMARY_WSS, RELAY_SECONDARY_WSS,
+ RELAY_TERTIARY_WSS,
+};
use serde_json::{Value, json};
fn test_public_key() -> PublicKey {
- PublicKey::parse("83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5")
- .expect("public key")
+ PublicKey::parse(FIXTURE_ALICE.public_key_hex).expect("public key")
}
fn test_keys() -> Keys {
- let secret_key =
- SecretKey::from_hex("6d5f4530cbf6a9e8f021eb409c8c5f2ee7ea123c76364b6f53c2d8a3507f7f5b")
- .expect("secret key");
+ let secret_key = SecretKey::from_hex(FIXTURE_ALICE.secret_key_hex).expect("secret key");
Keys::new(secret_key)
}
+fn encode_uri_component(value: &str) -> String {
+ url::form_urlencoded::byte_serialize(value.as_bytes()).collect()
+}
+
+fn logo_url() -> String {
+ format!("{CDN_PRIMARY_HTTPS}/logo.png")
+}
+
#[test]
fn parses_client_uri_with_current_spec_query_fields() {
- let uri = "nostrconnect://83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5?relay=wss%3A%2F%2Frelay1.example.com&relay=wss%3A%2F%2Frelay2.example.com&secret=0s8j2djs&perms=nip44_encrypt%2Csign_event%3A1059&name=My+Client&url=https%3A%2F%2Fexample.com&image=https%3A%2F%2Fexample.com%2Flogo.png";
- let parsed = RadrootsNostrConnectUri::parse(uri).expect("parse client uri");
+ let uri = format!(
+ "nostrconnect://{}?relay={}&relay={}&secret=0s8j2djs&perms=nip44_encrypt%2Csign_event%3A1059&name=My+Client&url={}&image={}",
+ FIXTURE_ALICE.public_key_hex,
+ encode_uri_component(RELAY_SECONDARY_WSS),
+ encode_uri_component(RELAY_TERTIARY_WSS),
+ encode_uri_component(APP_PRIMARY_HTTPS),
+ encode_uri_component(&logo_url()),
+ );
+ let parsed = RadrootsNostrConnectUri::parse(&uri).expect("parse client uri");
match parsed {
RadrootsNostrConnectUri::Client(client) => {
@@ -39,11 +55,11 @@ fn parses_client_uri_with_current_spec_query_fields() {
),
])
);
- assert_eq!(client.metadata.url.as_deref(), Some("https://example.com/"));
assert_eq!(
- client.metadata.image.as_deref(),
- Some("https://example.com/logo.png")
+ client.metadata.url.as_deref(),
+ Some(format!("{APP_PRIMARY_HTTPS}/").as_str())
);
+ assert_eq!(client.metadata.image.as_deref(), Some(logo_url().as_str()));
}
other => panic!("expected client uri, got {other:?}"),
}
@@ -51,8 +67,12 @@ fn parses_client_uri_with_current_spec_query_fields() {
#[test]
fn parses_bunker_uri_and_roundtrips() {
- let source = "bunker://83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5?relay=wss%3A%2F%2Frelay.example.com&secret=abcd";
- let parsed = RadrootsNostrConnectUri::parse(source).expect("parse bunker uri");
+ let source = format!(
+ "bunker://{}?relay={}&secret=abcd",
+ FIXTURE_ALICE.public_key_hex,
+ encode_uri_component(RELAY_PRIMARY_WSS),
+ );
+ let parsed = RadrootsNostrConnectUri::parse(&source).expect("parse bunker uri");
let rendered = parsed.to_string();
let reparsed = RadrootsNostrConnectUri::parse(&rendered).expect("reparse bunker uri");
assert_eq!(parsed, reparsed);
@@ -60,8 +80,12 @@ fn parses_bunker_uri_and_roundtrips() {
#[test]
fn rejects_client_uri_without_required_secret() {
- let source = "nostrconnect://83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5?relay=wss%3A%2F%2Frelay.example.com";
- assert!(RadrootsNostrConnectUri::parse(source).is_err());
+ let source = format!(
+ "nostrconnect://{}?relay={}",
+ FIXTURE_ALICE.public_key_hex,
+ encode_uri_component(RELAY_PRIMARY_WSS),
+ );
+ assert!(RadrootsNostrConnectUri::parse(&source).is_err());
}
#[test]
@@ -98,7 +122,7 @@ fn connect_request_roundtrips_requested_permissions() {
"id": "req-1",
"method": "connect",
"params": [
- "83f3b2ae6aa368e8275397b9c26cf550101d63ebaab900d19dd4a4429f5ad8f5",
+ FIXTURE_ALICE.public_key_hex,
"abcd",
"nip44_encrypt,sign_event:1059"
]
@@ -141,10 +165,7 @@ fn sign_event_request_roundtrips_unsigned_event_payload() {
fn switch_relays_response_accepts_array_or_null() {
let relays_response = RadrootsNostrConnectResponseEnvelope {
id: "req-switch".to_owned(),
- result: Some(json!([
- "wss://relay1.example.com",
- "wss://relay2.example.com"
- ])),
+ result: Some(json!([RELAY_SECONDARY_WSS, RELAY_TERTIARY_WSS])),
error: None,
};
let parsed = RadrootsNostrConnectResponse::from_envelope(
@@ -155,8 +176,8 @@ fn switch_relays_response_accepts_array_or_null() {
assert_eq!(
parsed,
RadrootsNostrConnectResponse::RelayList(vec![
- RelayUrl::parse("wss://relay1.example.com").expect("relay 1"),
- RelayUrl::parse("wss://relay2.example.com").expect("relay 2"),
+ RelayUrl::parse(RELAY_SECONDARY_WSS).expect("relay 1"),
+ RelayUrl::parse(RELAY_TERTIARY_WSS).expect("relay 2"),
])
);
diff --git a/crates/nostr/Cargo.toml b/crates/nostr/Cargo.toml
@@ -47,4 +47,5 @@ serde_json = { workspace = true }
thiserror = { workspace = true }
[dev-dependencies]
+radroots-test-fixtures = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
diff --git a/crates/nostr/src/events/mod.rs b/crates/nostr/src/events/mod.rs
@@ -40,10 +40,11 @@ pub fn radroots_nostr_build_event(
mod tests {
use super::radroots_nostr_build_event;
use crate::types::{RadrootsNostrPublicKey, RadrootsNostrTagKind};
+ use radroots_test_fixtures::FIXTURE_ALICE_PUBLIC_KEY_HEX;
#[test]
fn build_event_preserves_self_p_tag() {
- let pubkey_hex = "1bdebe7b23fccb167fc8843280b789839dfa296ae9fd86cc9769b4813d76d8a4";
+ let pubkey_hex = FIXTURE_ALICE_PUBLIC_KEY_HEX;
let pubkey = RadrootsNostrPublicKey::from_hex(pubkey_hex).expect("pubkey");
let tags = vec![
vec!["x".to_string(), "v".to_string()],
diff --git a/crates/nostr/src/nip17.rs b/crates/nostr/src/nip17.rs
@@ -214,16 +214,17 @@ where
#[cfg(all(test, feature = "nip17"))]
mod tests {
use super::*;
- use nostr::Keys;
+ use nostr::{Keys, SecretKey};
use radroots_events::message::{RadrootsMessage, RadrootsMessageRecipient};
use radroots_events::message_file::{RadrootsMessageFile, RadrootsMessageFileDimensions};
+ use radroots_test_fixtures::{FIXTURE_ALICE, FIXTURE_BOB};
fn sender_keys() -> Keys {
- Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e").unwrap()
+ Keys::new(SecretKey::from_hex(FIXTURE_ALICE.secret_key_hex).unwrap())
}
fn receiver_keys() -> Keys {
- Keys::parse("7b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e").unwrap()
+ Keys::new(SecretKey::from_hex(FIXTURE_BOB.secret_key_hex).unwrap())
}
#[tokio::test]
diff --git a/crates/nostr/tests/coverage.rs b/crates/nostr/tests/coverage.rs
@@ -31,6 +31,7 @@ use radroots_nostr::types::{
use radroots_nostr::util::{
created_at_u32_saturating, event_created_at_u32_saturating, radroots_nostr_npub_string,
};
+use radroots_test_fixtures::RELAY_PRIMARY_WSS;
fn make_keys() -> RadrootsNostrKeys {
RadrootsNostrKeys::generate()
@@ -254,7 +255,7 @@ fn tag_helpers_cover_matchers_and_resolve_paths() {
assert_eq!(matched.1, ["v1".to_string(), "v2".to_string()]);
let relays_tag = RadrootsNostrTag::from_standardized(RadrootsNostrTagStandard::Relays(vec![
- RadrootsNostrRelayUrl::parse("wss://relay.example.com").expect("relay"),
+ RadrootsNostrRelayUrl::parse(RELAY_PRIMARY_WSS).expect("relay"),
]));
assert!(radroots_nostr_tag_relays_parse(&relays_tag).is_some());
let relays_non_match =