chore: clean up codebase (#19)
Rust / build (macos-latest, stable) (push) Waiting to run
Rust / build (ubuntu-latest, stable) (push) Waiting to run
Rust / build (windows-latest, stable) (push) Waiting to run

Reviewed-on: #19
This commit was merged in pull request #19.
This commit is contained in:
2026-09-13 09:42:08 +00:00
parent 40deb9db66
commit f6b8a5e133
82 changed files with 3559 additions and 7862 deletions
-3
View File
@@ -7,6 +7,3 @@ publish.workspace = true
[dependencies]
nostr.workspace = true
serde.workspace = true
[dev-dependencies]
serde_json.workspace = true
-16
View File
@@ -6,12 +6,10 @@ use nostr::prelude::*;
/// the alias reuses the SDK type while keeping repository-specific vocabulary.
pub type RepoAddr = Coordinate;
/// Build the address of a NIP-34 repository announcement.
pub fn repo_addr(owner: PublicKey, id: impl Into<String>) -> RepoAddr {
Coordinate::new(Kind::GitRepoAnnouncement, owner).identifier(id)
}
/// Derive a repository identifier from a display name
pub fn identifier_from_name(name: &str) -> String {
name.chars()
.map(|c| {
@@ -23,17 +21,3 @@ pub fn identifier_from_name(name: &str) -> String {
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identifier_from_name_slugs_like_gitworkshop() {
assert_eq!(identifier_from_name("My Repo"), "My-Repo");
assert_eq!(identifier_from_name("my-repo"), "my-repo");
assert_eq!(identifier_from_name("Foo_Bar!"), "Foo-Bar-");
assert_eq!(identifier_from_name("a/b"), "a/b");
assert_eq!(identifier_from_name("Café"), "Caf-");
}
}
-62
View File
@@ -196,61 +196,6 @@ mod tests {
assert_eq!(labels, vec!["bug", "help-wanted"]);
}
#[test]
fn labels_ignore_unauthorized_and_misnamed_events() {
let root = root_event();
let maintainer =
keys_from_hex("0000000000000000000000000000000000000000000000000000000000000002");
let stranger =
keys_from_hex("0000000000000000000000000000000000000000000000000000000000000003");
// A stranger's label event is not authorized.
let stranger_labels = signed(
&stranger,
Kind::Label,
vec![
e_tag(&root),
Tag::parse(["L", "#t"]).expect("valid L tag"),
Tag::parse(["l", "nope", "#t"]).expect("valid l tag"),
],
200,
);
// A valid author referencing a different event.
let other_labels = signed(
&maintainer,
Kind::Label,
vec![
Tag::parse([
"e",
"2222222222222222222222222222222222222222222222222222222222222222",
])
.expect("valid e tag"),
Tag::parse(["L", "#t"]).expect("valid L tag"),
Tag::parse(["l", "nope", "#t"]).expect("valid l tag"),
],
200,
);
// A valid author without the namespace declaration.
let missing_namespace = signed(
&maintainer,
Kind::Label,
vec![
e_tag(&root),
Tag::parse(["l", "nope", "#t"]).expect("valid l tag"),
],
200,
);
assert_eq!(
labels(
&root,
&[stranger_labels, other_labels, missing_namespace],
&[maintainer.public_key()]
),
vec!["bug"]
);
}
#[test]
fn subject_override_latest_authorized_event_wins() {
let root = root_event();
@@ -300,11 +245,4 @@ mod tests {
let note = cover_note(&root, &events, &maintainers);
assert_eq!(note.map(|event| event.id), Some(newer_id));
}
#[test]
fn cover_note_none_without_valid_events() {
let root = root_event();
assert_eq!(cover_note(&root, &[], &[]), None);
}
}
-29
View File
@@ -69,35 +69,6 @@ fn percent_decode(input: &str) -> String {
mod tests {
use super::*;
#[test]
fn parses_user_repo_without_relay() {
let target = parse_clone_url(
"nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit",
)
.unwrap();
assert_eq!(
target,
CloneTarget::UserRepo {
user: "npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr".to_owned(),
relay_hint: None,
identifier: "ngit".to_owned(),
}
);
}
#[test]
fn parses_user_repo_with_relay_hint() {
let target = parse_clone_url("nostr://danconwaydev.com/relay.ngit.dev/ngit").unwrap();
assert_eq!(
target,
CloneTarget::UserRepo {
user: "danconwaydev.com".to_owned(),
relay_hint: RelayUrl::parse("relay.ngit.dev").ok(),
identifier: "ngit".to_owned(),
}
);
}
#[test]
fn decodes_percent_encoded_parts() {
let target = parse_clone_url(
-21
View File
@@ -253,13 +253,6 @@ mod tests {
Tag::parse([name, &kind.as_u16().to_string()]).expect("valid kind tag")
}
#[test]
fn root_git_kinds_are_activity() {
for kind in [Kind::GitIssue, Kind::GitPatch, Kind::GitPullRequest] {
assert!(is_git_activity(&signed(&keys(1), kind, Vec::new())));
}
}
#[test]
fn comment_activity_depends_on_the_uppercase_k_tag() {
let on_git = signed(&keys(1), Kind::Comment, vec![kind_tag("K", Kind::GitIssue)]);
@@ -307,18 +300,4 @@ mod tests {
Vec::new()
)));
}
#[test]
fn non_git_kinds_are_not_activity() {
assert!(!is_git_activity(&signed(
&keys(1),
Kind::TextNote,
Vec::new()
)));
assert!(!is_git_activity(&signed(
&keys(1),
Kind::GitPullRequestUpdate,
Vec::new(),
)));
}
}
-175
View File
@@ -44,7 +44,6 @@ impl InboxItem {
.unwrap_or_else(|| "Untitled".to_string())
}
/// Kind shown for the thread.
pub fn kind(&self) -> Option<Kind> {
self.root_kind.or_else(|| {
self.root_event
@@ -102,7 +101,6 @@ impl InboxItem {
!self.archived && !self.unread_ids.is_empty()
}
/// Recompute the unread and archived flags from `state`.
pub fn apply_state(&mut self, state: &InboxReadState) {
self.unread_ids = self
.events
@@ -468,10 +466,6 @@ mod tests {
Tag::parse(["E", &event.id.to_hex()]).expect("valid E tag")
}
fn a_tag(owner: &PublicKey, id: &str) -> Tag {
Tag::parse(["a", &format!("30617:{}:{id}", owner.to_hex())]).expect("valid a tag")
}
fn lookup(events: &[Event]) -> impl Fn(EventId) -> Option<Event> + '_ {
move |id| events.iter().find(|event| event.id == id).cloned()
}
@@ -489,18 +483,6 @@ mod tests {
)
}
#[test]
fn issue_and_pull_request_are_their_own_root() {
let events = [
issue(&keys(1), 100),
signed(&keys(1), Kind::GitPullRequest, Vec::new(), 100),
];
let lookup = lookup(&events);
for event in &events {
assert_eq!(notification_root(event, &lookup), Some(event.id));
}
}
#[test]
fn comment_resolves_to_its_uppercase_root() {
let issue = issue(&keys(1), 100);
@@ -520,17 +502,6 @@ mod tests {
);
}
#[test]
fn comment_without_root_pointer_has_no_root() {
let comment = signed(
&keys(2),
Kind::Comment,
vec![e_tag(&issue(&keys(1), 100))],
200,
);
assert_eq!(notification_root(&comment, &lookup(&[])), None);
}
#[test]
fn child_patch_resolves_to_the_root_patch() {
let root_patch = signed(&keys(1), Kind::GitPatch, Vec::new(), 100);
@@ -555,19 +526,6 @@ mod tests {
assert_eq!(notification_root(&status, &lookup(&events)), Some(issue.id));
}
#[test]
fn pull_request_update_resolves_via_uppercase_e() {
let pr = signed(&keys(1), Kind::GitPullRequest, Vec::new(), 100);
let update = signed(
&keys(2),
Kind::GitPullRequestUpdate,
vec![uppercase_e_tag(&pr)],
200,
);
let events = [pr.clone(), update.clone()];
assert_eq!(notification_root(&update, &lookup(&events)), Some(pr.id));
}
#[test]
fn nested_comment_chain_follows_to_the_root() {
let issue = issue(&keys(1), 100);
@@ -577,96 +535,6 @@ mod tests {
assert_eq!(notification_root(&nested, &lookup(&events)), Some(issue.id));
}
#[test]
fn group_excludes_self_and_sorts_groups_newest_first() {
let me = keys(1);
let issue = issue(&keys(2), 100);
let comment = signed(&keys(3), Kind::Comment, vec![uppercase_e_tag(&issue)], 300);
let other_issue = signed(
&keys(2),
Kind::GitIssue,
vec![Tag::parse(["p", &me.public_key().to_hex()]).expect("valid p tag")],
200,
);
let mine = signed(&keys(1), Kind::Comment, vec![uppercase_e_tag(&issue)], 400);
let events = [issue.clone(), comment.clone(), other_issue.clone(), mine];
let items = group(
events,
Vec::new(),
me.public_key(),
&InboxReadState::default(),
&lookup(&[]),
);
assert_eq!(items.len(), 2);
assert_eq!(items[0].root, issue.id);
// The issue itself plus the comment; the self-authored comment is out.
assert_eq!(items[0].events.len(), 2);
assert_eq!(items[1].root, other_issue.id);
}
#[test]
fn group_reports_unread_oldest_first_and_archived() {
let me = keys(1);
let issue = issue(&keys(2), 100);
let older = signed(&keys(3), Kind::Comment, vec![uppercase_e_tag(&issue)], 200);
let newer = signed(&keys(4), Kind::Comment, vec![uppercase_e_tag(&issue)], 300);
let events = [issue.clone(), older.clone(), newer.clone()];
let items = group(
events,
Vec::new(),
me.public_key(),
&InboxReadState::default(),
&lookup(&[]),
);
assert_eq!(items[0].unread_ids, vec![issue.id, older.id, newer.id]);
assert!(!items[0].archived);
assert!(items[0].is_unread());
let state = InboxReadState {
archived_before: Timestamp::from_secs(1000),
..Default::default()
};
let items = group(
[issue.clone(), older, newer],
Vec::new(),
me.public_key(),
&state,
&lookup(&[]),
);
assert!(items[0].archived);
assert!(!items[0].unread_ids.is_empty());
assert!(!items[0].is_unread());
}
#[test]
fn group_reads_root_kind_and_address_from_the_root_event() {
let me = keys(1);
let owner_keys = keys(2);
let owner = owner_keys.public_key();
let issue = signed(
&owner_keys,
Kind::GitIssue,
vec![a_tag(&owner, "my-repo")],
100,
);
let comment = signed(&keys(3), Kind::Comment, vec![uppercase_e_tag(&issue)], 200);
let events = [issue.clone(), comment];
let items = group(
events.clone(),
Vec::new(),
me.public_key(),
&InboxReadState::default(),
&lookup(&events),
);
assert_eq!(items[0].root_kind, Some(Kind::GitIssue));
assert_eq!(items[0].address, issue.tags.coordinates().next());
}
#[test]
fn group_merges_own_events_into_the_matching_thread() {
let me = keys(1);
@@ -809,47 +677,4 @@ mod tests {
state.mark_archived(&event);
assert_eq!(state.archived_ids, HashSet::from([event.id]));
}
#[test]
fn apply_state_recomputes_unread_and_archived() {
let now = Timestamp::from_secs(1_000_000_000);
let first = issue(&keys(2), now.as_secs() - 2000);
let second = issue(&keys(2), now.as_secs() - 1000);
let mut item = InboxItem {
root: first.id,
root_event: None,
root_kind: None,
address: None,
events: vec![second.clone(), first.clone()],
own_events: Vec::new(),
unread_ids: Vec::new(),
archived: false,
};
let state = InboxReadState {
read_before: first.created_at,
..Default::default()
};
item.apply_state(&state);
assert_eq!(item.unread_ids, vec![second.id]);
assert!(!item.archived);
}
#[test]
fn serde_round_trip_preserves_state() {
let first = issue(&keys(1), 100);
let second = issue(&keys(2), 200);
let state = InboxReadState {
read_before: Timestamp::from_secs(150),
read_ids: HashSet::from([second.id]),
archived_before: Timestamp::from_secs(50),
archived_ids: HashSet::from([first.id]),
};
let json = serde_json::to_string(&state).expect("serialized");
let parsed: InboxReadState = serde_json::from_str(&json).expect("deserialized");
assert_eq!(parsed, state);
}
}
-133
View File
@@ -46,7 +46,6 @@ pub struct Upstream {
}
impl Upstream {
/// Parse the `u` tag values.
fn parse(raw: &str, relay_hint: Option<&str>) -> Self {
let coordinate = raw.split('|').next().unwrap_or(raw);
let addr = coordinate
@@ -60,7 +59,6 @@ impl Upstream {
}
}
/// Text for display.
pub fn display(&self) -> String {
match &self.addr {
Some(addr) => addr.to_string(),
@@ -143,7 +141,6 @@ pub fn pull_request_patches<'a>(
series
}
/// The patch content of a pull request.
pub fn pull_request_patch<'a>(pr: &Event, patches: impl IntoIterator<Item = &'a Event>) -> String {
let patches: Vec<&'a Event> = patches.into_iter().collect();
let series = pull_request_patches(pr, patches.iter().copied());
@@ -342,7 +339,6 @@ impl Announcement {
})
}
/// The repository address of this announcement.
pub fn addr(&self) -> RepoAddr {
repo_addr(self.owner, self.id.clone())
}
@@ -409,7 +405,6 @@ mod tests {
)
}
/// Build a signed kind `30617` event from raw tag values.
fn announcement_event(tags: &[&[&str]]) -> Event {
let tags: Vec<Tag> = tags
.iter()
@@ -469,22 +464,6 @@ mod tests {
assert_eq!(announcement.hashtags, vec!["rust", "nostr"]);
}
#[test]
fn requires_d_tag() {
let event = announcement_event(&[&["name", "No id"]]);
assert!(Announcement::from_event(&event).is_none());
}
#[test]
fn ignores_other_kinds() {
let event = EventBuilder::new(Kind::GitIssue, "")
.finalize(&keys())
.expect("signed event");
assert!(Announcement::from_event(&event).is_none());
}
#[test]
fn drops_malformed_values() {
let event = announcement_event(&[
@@ -505,17 +484,6 @@ mod tests {
assert!(announcement.maintainers.is_empty());
}
#[test]
fn ignores_unknown_tags() {
let event = announcement_event(&[&["d", "my-repo"], &["t", "label"], &["subject", "n/a"]]);
let announcement = Announcement::from_event(&event).expect("parses");
assert_eq!(announcement.id, "my-repo");
assert!(announcement.name.is_none());
assert!(announcement.web.is_empty());
}
#[test]
fn parses_upstream_tag() {
let event = announcement_event(&[
@@ -553,25 +521,6 @@ mod tests {
);
}
#[test]
fn parses_git_url_upstream() {
// The `u` tag may reference a non-nostr upstream by git URL only.
// There is no repository address to navigate to.
let event = announcement_event(&[
&["d", "my-fork"],
&["u", "https://example.com/upstream.git"],
]);
let announcement = Announcement::from_event(&event).expect("parses");
let upstream = announcement.upstream.expect("parses the u tag");
assert_eq!(upstream.addr, None);
assert_eq!(
upstream.display().to_string(),
"https://example.com/upstream.git"
);
}
#[test]
fn is_fork_of_matches_the_u_tag_coordinate() {
// The base repository, announced by the `u` tag's owner.
@@ -632,17 +581,6 @@ mod tests {
assert!(fork.is_fork_of(&base, Some(base_euc)));
}
#[test]
fn is_fork_of_excludes_the_base_itself() {
let euc = "aa231c4c6a5777dc89b42207b499891a344add5c";
let event = announcement_event(&[&["d", "upstream"], &["r", euc, "euc"]]);
let base = Announcement::from_event(&event).expect("parses");
let base_addr = base.addr();
// The base announcement matches its own EUC but is not a fork of itself.
assert!(!base.is_fork_of(&base_addr, base.euc.as_deref()));
}
#[test]
fn effective_maintainers_include_owner_for_primary_repos() {
let event = announcement_event(&[&["d", "my-repo"], &["maintainers", MAINTAINER_HEX]]);
@@ -677,7 +615,6 @@ mod tests {
);
}
/// Build a signed PR event with the given tags and content.
fn pr_event(content: &str, tags: Vec<Tag>) -> Event {
EventBuilder::new(Kind::GitPullRequest, content)
.tags(tags)
@@ -685,35 +622,6 @@ mod tests {
.expect("signed event")
}
#[test]
fn pull_request_patch_prefers_linked_patch_event() {
let patch = EventBuilder::new(Kind::GitPatch, "patch-content")
.finalize(&keys())
.expect("signed event");
let pr = pr_event("description", vec![Tag::event(patch.id)]);
assert_eq!(pull_request_patch(&pr, [&patch]), "patch-content");
}
#[test]
fn pull_request_patch_falls_back_to_inline_content() {
// Older PRs carried the patch in the content and link no patch event.
let pr = pr_event("patch-inline", vec![]);
assert_eq!(pull_request_patch(&pr, [] as [&Event; 0]), "patch-inline");
}
#[test]
fn pull_request_patch_ignores_unrelated_patch_events() {
let patch = EventBuilder::new(Kind::GitPatch, "patch-content")
.finalize(&keys())
.expect("signed event");
let pr = pr_event("description", vec![]);
assert_eq!(pull_request_patch(&pr, [&patch]), "description");
}
/// Build a signed patch event with a controlled `created_at`.
fn patch_event(content: &str, tags: Vec<Tag>, created_at: u64) -> Event {
EventBuilder::new(Kind::GitPatch, content)
.tags(tags)
@@ -757,24 +665,6 @@ mod tests {
);
}
#[test]
fn pull_request_patches_ignores_unrelated_replies() {
let root = patch_event("patch-one", vec![], 100);
let other = patch_event("other-patch", vec![Tag::event(root.id)], 250);
// A patch replying to a different root is not part of the set.
let stranger = patch_event("stranger", vec![], 150);
let pr = pr_event("description", vec![Tag::event(root.id)]);
let series = pull_request_patches(&pr, [&root, &other, &stranger]);
assert_eq!(
series
.iter()
.map(|p| p.content.as_str())
.collect::<Vec<_>>(),
vec!["patch-one", "other-patch"]
);
}
#[test]
fn pull_request_patches_finds_the_set_via_the_tip_commit() {
// PRs without an `e` tag fall back to the patch producing the tip commit.
@@ -807,7 +697,6 @@ mod tests {
const COMMIT_HEX: &str = "1111111111111111111111111111111111111111";
const OTHER_ROOT_HEX: &str = "2222222222222222222222222222222222222222";
/// Build a signed event of `kind` with the given tags and `created_at`.
fn signed_at(kind: Kind, tags: Vec<Tag>, created_at: u64) -> Event {
EventBuilder::new(kind, "")
.tags(tags)
@@ -827,20 +716,6 @@ mod tests {
)
}
#[test]
fn reads_current_commit_and_branch_name() {
let pr = pr_root();
assert_eq!(current_commit_of(&pr).as_deref(), Some(COMMIT_HEX));
assert_eq!(branch_name_of(&pr).as_deref(), Some("feature/x"));
}
#[test]
fn returns_none_without_pr_tags() {
let pr = signed_at(Kind::GitPullRequest, vec![], 100);
assert_eq!(current_commit_of(&pr), None);
assert_eq!(branch_name_of(&pr), None);
}
#[test]
fn latest_update_picks_newest_revision_of_the_root() {
let root = pr_root();
@@ -886,19 +761,12 @@ mod tests {
assert!(latest_update([&stranger, &root].into_iter(), &root).is_none());
}
#[test]
fn latest_update_ignores_roots_without_revisions() {
let root = pr_root();
assert!(latest_update([&root].into_iter(), &root).is_none());
}
const OWNER_KEYS: [&str; 3] = [
"0000000000000000000000000000000000000000000000000000000000000001",
"0000000000000000000000000000000000000000000000000000000000000002",
"0000000000000000000000000000000000000000000000000000000000000003",
];
/// Build a signed kind-30617 event for `owner` with the given tags.
fn owned_announcement_event(owner: &str, tags: &[&[&str]]) -> Event {
let keys = Keys::new(SecretKey::from_hex(owner).expect("valid secret key"));
let tags: Vec<Tag> = tags
@@ -960,7 +828,6 @@ mod tests {
let user = PublicKey::from_hex(OWNER_KEYS[1]).expect("pubkey");
let forks = fork_candidates(&all, &base_addr, Some(euc), Some(user));
// The user's fork comes first, then the other author's.
let ids: Vec<&str> = forks.iter().map(|a| a.id.as_str()).collect();
assert_eq!(ids, vec!["my-fork", "their-fork"]);
}
-34
View File
@@ -55,7 +55,6 @@ mod tests {
)
}
/// Build a signed kind `30618` event from raw tag values.
fn state_event(tags: &[&[&str]]) -> Event {
let tags: Vec<Tag> = tags
.iter()
@@ -90,26 +89,6 @@ mod tests {
);
}
#[test]
fn head_without_prefix_is_ignored() {
let event = state_event(&[&["HEAD", "main"]]);
let (refs, head) = parse_state(&event);
assert!(refs.is_empty());
assert!(head.is_none());
}
#[test]
fn ignores_non_state_tags() {
let event = state_event(&[&["d", "my-repo"], &["name", "ignored"]]);
let (refs, head) = parse_state(&event);
assert!(refs.is_empty());
assert!(head.is_none());
}
#[test]
fn build_state_round_trips_through_parse() {
let refs = [
@@ -129,17 +108,4 @@ mod tests {
assert_eq!(parsed_refs, refs);
assert_eq!(head.as_deref(), Some("main"));
}
#[test]
fn build_state_omits_head_when_detached() {
let refs = [("refs/heads/main".to_owned(), COMMIT_A.to_owned())];
let event = build_state("my-repo", &refs, None)
.finalize(&keys())
.expect("signed event");
let (parsed_refs, head) = parse_state(&event);
assert_eq!(parsed_refs, refs);
assert!(head.is_none());
}
}
-62
View File
@@ -76,7 +76,6 @@ mod tests {
EventId::from_hex(ROOT_ID_HEX).expect("valid event id")
}
/// Build a signed status event with a controlled `created_at`.
fn status_event(author: &Keys, kind: Kind, root: EventId, created_at: u64) -> Event {
EventBuilder::new(kind, "")
.tags([Tag::event(root)])
@@ -102,53 +101,6 @@ mod tests {
));
}
#[test]
fn references_root_matches_uppercase_e_tag() {
let root = root_event_id();
let event = EventBuilder::new(Kind::Comment, "")
.tags([Tag::parse(["E", ROOT_ID_HEX]).expect("valid E tag")])
.finalize(&keys_from_hex(
"0000000000000000000000000000000000000000000000000000000000000001",
))
.expect("signed event");
assert!(references_root(&event, &root));
assert!(!references_root(
&event,
&EventId::from_hex(OTHER_ID_HEX).expect("valid id")
));
}
#[test]
fn references_root_false_without_e_tags() {
let event = EventBuilder::new(Kind::GitStatusOpen, "")
.finalize(&keys_from_hex(
"0000000000000000000000000000000000000000000000000000000000000001",
))
.expect("signed event");
assert!(!references_root(&event, &root_event_id()));
}
#[test]
fn defaults_to_open_without_status_events() {
let owner =
keys_from_hex("0000000000000000000000000000000000000000000000000000000000000001");
let maintainer =
keys_from_hex("0000000000000000000000000000000000000000000000000000000000000002");
let statuses: Vec<Event> = Vec::new();
assert_eq!(
resolve_status(
statuses.iter(),
&owner.public_key(),
&[maintainer.public_key()]
),
RepoStatus::Open
);
}
#[test]
fn latest_status_wins() {
let owner =
@@ -196,18 +148,4 @@ mod tests {
RepoStatus::Draft
);
}
#[test]
fn ignores_non_status_kinds() {
let owner =
keys_from_hex("0000000000000000000000000000000000000000000000000000000000000001");
let root = root_event_id();
let statuses = [status_event(&owner, Kind::GitIssue, root, 100)];
assert_eq!(
resolve_status(statuses.iter(), &owner.public_key(), &[]),
RepoStatus::Open
);
}
}