|
| 1 | +// Tests for commands/channels.rs — split into a sibling file to keep |
| 2 | +// channels.rs under the per-file line cap. |
| 3 | + |
| 4 | +use super::*; |
| 5 | +use nostr::{EventBuilder, Keys, Kind, Tag}; |
| 6 | + |
| 7 | +/// Build a signed event for testing with the given kind, content, and tags. |
| 8 | +fn ev(kind: u16, content: &str, tags: Vec<Vec<&str>>) -> nostr::Event { |
| 9 | + let keys = Keys::generate(); |
| 10 | + let parsed: Vec<Tag> = tags |
| 11 | + .into_iter() |
| 12 | + .map(|t| Tag::parse(t).expect("parse tag")) |
| 13 | + .collect(); |
| 14 | + EventBuilder::new(Kind::from_u16(kind), content) |
| 15 | + .tags(parsed) |
| 16 | + .sign_with_keys(&keys) |
| 17 | + .expect("sign") |
| 18 | +} |
| 19 | + |
| 20 | +// A 64-hex pubkey (nostr p-tags require 32-byte hex). |
| 21 | +const PK_A: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; |
| 22 | +const PK_B: &str = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; |
| 23 | +const PK_C: &str = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"; |
| 24 | + |
| 25 | +#[test] |
| 26 | +fn counts_unique_p_tags_per_channel() { |
| 27 | + let e1 = ev( |
| 28 | + 39002, |
| 29 | + "", |
| 30 | + vec![ |
| 31 | + vec!["d", "chan-1"], |
| 32 | + vec!["p", PK_A, "", "member"], |
| 33 | + vec!["p", PK_B, "", "admin"], |
| 34 | + ], |
| 35 | + ); |
| 36 | + let e2 = ev( |
| 37 | + 39002, |
| 38 | + "", |
| 39 | + vec![vec!["d", "chan-2"], vec!["p", PK_C, "", "member"]], |
| 40 | + ); |
| 41 | + |
| 42 | + let counts = count_members_by_channel(&[e1, e2]); |
| 43 | + assert_eq!(counts.get("chan-1"), Some(&2)); |
| 44 | + assert_eq!(counts.get("chan-2"), Some(&1)); |
| 45 | + assert_eq!(counts.len(), 2); |
| 46 | +} |
| 47 | + |
| 48 | +#[test] |
| 49 | +fn dedupes_repeated_pubkeys() { |
| 50 | + let e = ev( |
| 51 | + 39002, |
| 52 | + "", |
| 53 | + vec![ |
| 54 | + vec!["d", "chan-1"], |
| 55 | + vec!["p", PK_A, "", "member"], |
| 56 | + vec!["p", PK_A, "", "admin"], // duplicate pubkey, different role |
| 57 | + vec!["p", PK_B, "", "member"], |
| 58 | + ], |
| 59 | + ); |
| 60 | + let counts = count_members_by_channel(&[e]); |
| 61 | + assert_eq!(counts.get("chan-1"), Some(&2)); |
| 62 | +} |
| 63 | + |
| 64 | +#[test] |
| 65 | +fn skips_event_without_d_tag() { |
| 66 | + let e = ev(39002, "", vec![vec!["p", PK_A, "", "member"]]); |
| 67 | + let counts = count_members_by_channel(&[e]); |
| 68 | + assert!(counts.is_empty()); |
| 69 | +} |
| 70 | + |
| 71 | +#[test] |
| 72 | +fn zero_member_channel_is_recorded() { |
| 73 | + // A channel with a members event but no p-tags should report 0, |
| 74 | + // not be absent from the map (the caller relies on `get` returning |
| 75 | + // `Some(0)` to overwrite a default). |
| 76 | + let e = ev(39002, "", vec![vec!["d", "chan-1"]]); |
| 77 | + let counts = count_members_by_channel(&[e]); |
| 78 | + assert_eq!(counts.get("chan-1"), Some(&0)); |
| 79 | +} |
| 80 | + |
| 81 | +#[test] |
| 82 | +fn empty_input_yields_empty_map() { |
| 83 | + let counts = count_members_by_channel(&[]); |
| 84 | + assert!(counts.is_empty()); |
| 85 | +} |
0 commit comments