Side B implements a real feature: pseudonym-based actor identity with UUID-keyed vote dedup, replacing blind edge-merge accumulation with idempotent uuid_votes storage, plus schema version bumps, migration seeding, and updated tests across many call sites—substantive lasting design work fixing a Sybil/re-vote correctness gap. Side A is a pure module reshuffle (moving room_route and url_identity_tests into other files) with no behavioral change, offering negligible lasting value beyond cosmetic reorganization.
constitution · epochs · watch · epoch 3
c_554c5efe648b (tommy-mor) vs c_3e6a3370542e (tommy-mor)
download prompt · raw event · cmp_7f5bd0e8057b65
council reasoning
A only consolidates existing room_route helpers into paths.rs and relocates url_identity_tests with no behavior change. B redesigns vote identity: schema/event fields, uuid_votes + pseudonym map, edge rebuild/rollback for per-actor pair dedup, and projection storage—real lasting domain architecture versus pure file churn.
Side B makes a substantive architectural change to vote handling: it replaces incremental edge merges with per-actor `uuid_votes` deduplication, introduces pseudonym-to-UUID resolution and seeding, updates event/schema versions, and rebuilds rankings from stored deduplicated votes, with corresponding validation and regression tests. Side A is primarily a consolidation/refactoring that moves room-route helpers into `paths.rs`, relocates tests, and deletes a module without materially changing behavior.
sides
A — c_554c5efe648b (tommy-mor)
message
[e4908910] consolidated
diff preview
diff --git a/types/src/lib.rs b/types/src/lib.rs
index 516cf935f15fca97081b39b988da5be894c67725..ceaa574cd32b7e7eb397c9a3191fcbc037b8c606 100644
--- a/types/src/lib.rs
+++ b/types/src/lib.rs
@@ -1,6 +1,5 @@
use serde::{Deserialize, Serialize};
-pub mod room_route;
pub mod url_normalize;
pub mod paths;
pub mod timeago;
@@ -8,9 +7,9 @@ pub mod timeago;
pub use paths::{
canonicalize_item, canonicalize_tag, item_parent_path, item_path_segments, normalize_slug_ontology_storage_url,
CanonicalItemUrl, ForumThreadUrl, GardenItemUrl, RelativePath, SLUG_TILDE_ONTOLOGY_ROOT,
+ room_id_from_route_segment, room_route_segment, ROOM_SHORT_ID_LEN,
TildeHttpPathTail, TildeOntologyPath, TildePath, tilde_http_path_to_canonical,
};
-pub use room_route::{room_id_from_route_segment, room_route_segment, ROOM_SHORT_ID_LEN};
pub use url_normalize::normalize_http_identity_url;
/// Max characters returned for a garden item body unless `full=true` / `--full` (API + CLI).
@@ -661,6 +660,3 @@ pub struct VoteResponse {
pub ranking: Vec<RankRow>,
pub next: NextMoves,
}
-
-#[cfg(test)]
-mod url_identity_tests;
diff --git a/types/src/paths.rs b/types/src/paths.rs
index 98787a5fb481a1599556564bbbbd1d54dc693fc3..5c60e762c2ca74d79c74237097d5bcc02ba74af2 100644
--- a/types/src/paths.rs
+++ b/types/src/paths.rs
@@ -8,6 +8,7 @@
//! - **[`TildeHttpPathTail`]** — capture from `GET /~/*path` or `…/r/{short}{slug}/~/…` (the `*path` segment).
//! - **`-/…` wire form** — external items; see [`canonicalize_item`] dash branch.
//! - **[`GardenItemUrl`], [`ForumThreadUrl`]** — JSON / browser href surfaces.
+//! - **[`ROOM_SHORT_ID_LEN`] / [`room_route_segment`]** — `/r/{short}{slug}` vs wire `short/slug`.
use std::borrow::Borrow;
use std::fmt;
@@ -15,7 +16,6 @@ use std::ops::Deref;
use serde::{Deserialize, Serialize};
-use crate::room_route::room_route_segment;
use crate::url_normalize::{host_preserves_dash_path_case, normalize_http_identity_url};
// ---------------------------------------------------------------------------
@@ -35,6 +35,46 @@ pub fn normalize_slug_ontology_storage_url(s: &str) -> String {
}
}
+// ---------------------------------------------------------------------------
+// Private room HTTP path (`/r/{short}{slug}`; wire id remains `short/slug`)
+// ---------------------------------------------------------------------------
+
+/// Byte length of the random `short` segment in `short/slug` room ids (matches server `gen_short_id`).
+pub const ROOM_SHORT_ID_LEN: usize = 7;
+
+/// `ab12cde/my-room` → `ab12cdemy-room` for a single `/r/…` path segment.
+pub fn room_route_segment(room_id: &str) -> Option<String> {
+ let (short, slug) = room_id.split_once('/')?;
+ if short.len() != ROOM_SHORT_ID_LEN || short.is_empty() || slug.is_empty() {
+ return None;
+ }
+ if !short
+ .bytes()
+ .all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z'))
+ {
+ return None;
+ }
+ Some(format!("{short}{slug}"))
+}
+
+/// `/r/{short}{slug}` path segment → `short/slug` wire id (inverse of [`room_route_segment`]).
+pub fn room_id_from_route_segment(seg: &str) -> Option<String> {
+ if seg.len() <= ROOM_SHORT_ID_LEN {
+ return None;
+ }
+ let (short, slug) = seg.split_at(ROOM_SHORT_ID_LEN);
+ if short.is_empty() || slug.is_empty() {
+ return None;
+ }
+ if !short
+ .bytes()
+ .all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z'))
+ {
+ return None;
+ }
+ Some(format!("{short}/{slug}"))
+}
+
// ---------------------------------------------------------------------------
// Normalization (moved from server `canonical_path`)
// ---------------------------------------------------------------------------
@@ -823,4 +863,17 @@ mod tests {
Some("https://github.com/org/repo/issues")
);
}
+
+ #[test]
+ fn round_trip_room_segment() {
+ let id = "9ab12cd/my-room";
+ let seg = room_route_segment(id).unwrap();
+ assert_eq!(seg, "9ab12cdmy-room");
+ assert_eq!(room_id_from_route_segment(&seg).as_deref(), Some(id));
+ }
+
+ #[test]
+ fn too_short_room_route_segment_rejected() {
+ assert!(room_id_from_route_segment("9ab12cd").is_none());
+ }
}
diff --git a/types/src/room_route.rs b/types/src/room_route.rs
deleted file mode 100644
index 4f4780c88e30f2b28e2cfcd7aee713d39dbd1c66..0000000000000000000000000000000000000000
--- a/types/src/room_route.rs
+++ /dev/null
@@ -1,56 +0,0 @@
-//! HTTP path encoding for private rooms: `/r/{short}{slug}` (short is fixed width).
-
-/// Byte length of the random `short` segment in `short/slug` room ids.
-/// Must match room creation (`gen_short_id`) and [`super::paths`][] URL builders.
-pub const ROOM_SHORT_ID_LEN: usize = 7;
-
-/// `ab12cde/my-room` → `ab12cdemy-room` for a single `/r/…` path segment.
-pub fn room_route_segment(room_id: &str) -> Option<String> {
- let (short, slug) = room_id.split_once('/')?;
- if short.len() != ROOM_SHORT_ID_LEN || short.is_empty() || slug.is_empty() {
- return None;
- }
- if !short
- .bytes()
- .all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z'))
- {
- return None;
- }
- Some(format!("{short}{slug}"))
-}
-
-/// `/r/{short}{slug}` path segment → `short/slug` wire id (inverse of [`room_route_segment`]).
-pub fn room_id_from_route_segment(seg: &str) -> Option<String> {
- if seg.len() <= ROOM_SHORT_ID_LEN {
- return None;
- }
- let (short, slug) = seg.split_at(ROOM_SHORT_ID_LEN);
- if short.is_empty() || slug.is_empty() {
- return None;
- }
- if !short
- .bytes()
- .all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z'))
- {
- return None;
- }
- Some(format!("{short}/{slug}"))
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn round_trip_room_segment() {
- let id = "9ab12cd/my-room";
- let seg = room_route_segment(id).unwrap();
- assert_eq!(seg, "9ab12cdmy-room");
- assert_eq!(room_id_from_route_segment(&seg).as_deref(), Some(id));
- }
-
- #[test]
- fn too_short_segment_rejected() {
- assert!(room_id_from_route_segment("9ab12cd").is_none());
- }
-}
diff --git a/types/src/url_identity_tests.rs b/types/src/url_identity_tests.rs
deleted file mode 100644
index e2ff0a9a64a88049e31f20979c703777df6a9b65..0000000000000000000000000000000000000000
--- a/types/src/url_identity_tests.rs
+++ /dev/null
@@ -1,126 +0,0 @@
-//! How `url::Url` behaves as `HashMap` keys (`Eq` + `Hash`).
-//!
-//! If `ItemId::External` stores `Url`, these tests are the contract you are buying into
-//! (or the baseline before you add a custom normalization layer).
-
-use std::collections::HashMap;
-use std::hash::{Hash, Hasher};
-use url::Url;
-
-fn hash_one(url: &Url) -> u64 {
- let mut h = std::collections::hash_map::DefaultHasher::new();
- url.hash(&mut h);
- h.finish()
-}
-
-#[test]
-fn identical_parse_strings_are_eq_and_share_hash_bucket() {
- let a = Url::parse("https://example.com/path").unwrap();
- let b = Url::parse("https://example.com/path").unwrap();
- assert_eq!(a, b);
- assert_eq!(hash_one(&a), hash_one(&b));
-
- let mut m: HashMap<Url, u32> = HashMap::new();
- m.insert(a, 1);
- *m.entry(b).or_default() += 10;
- assert_eq!(m.len(), 1);
- assert_eq!(m[&Url::parse("https://example.com/path").unwrap()], 11);
-}
-
-#[test]
-fn host_is_ascii_lowercase_in_eq() {
- let lower = Url::parse("https://examplE.com/").unwrap();
- let upper = Url::parse("https://EXAMPLE.com/").unwrap();
- assert_eq!(lower, upper);
- assert_eq!(hash_one(&lower), hash_one(&upper));
-}
-
-#[test]
-fn path_space_normalizes_to_percent_encoding_so_forms_merge() {
- let encoded = Url::parse("https://example.com/a%20b").unwrap();
- let decoded = Url::parse("https://example.com/a b").unwrap();
- // Parser normalizes both to the same internal path (`/a%20b`).
- assert_eq!(encoded, decoded);
- assert_eq!(hash_one(&encoded), hash_one(&decoded));
-
- let mut m: HashMap<Url, &str> = HashMap::new();
- m.insert(encoded, "first");
- assert_eq!(m.insert(decoded, "second"), Some("first"));
- assert_eq!(m.len(), 1);
- assert_eq!(m.values().next().copied(), Some("second"));
-}
-
-#[test]
-fn encoded_slash_in_segment_stays_distinct_from_real_path_separator() {
- let encoded = Url::parse("https://example.com/a%2Fb").unwrap();
- let real_slash = Url::parse("https://example.com/a/b").unwrap();
- assert_ne!(encoded, real_slash);
- assert_ne!(hash_one(&encoded), hash_one(&real_slash));
-}
-
-#[test]
-fn trailing_slash_on_path_is_significant_for_eq() {
- let with_slash = Url::parse("https://example.com/foo/").unwrap();
- let no_slash = Url::parse("https://example.com/foo").unwrap();
- assert_ne!(with_slash, no_slash);
- assert_ne!(hash_one(&with_slash), hash_one(&no_slash));
-}
-
-#[test]
-fn default_http_port_80_is_normalized_in_representation() {
- let explicit = Url::parse("http://example.com:80/").unwrap();
- let implicit = Url::parse("http://example.com/").unwrap();
- assert_eq!(explicit, implicit);
- assert_eq!(hash_one(&explicit), hash_one(&implicit));
-}
-
-#[test]
-fn default_https_port_443_is_normalized() {
- let explicit = Url::parse("https://example.com:443/foo").unwrap();
- let implicit = Url::parse("https://example.com/foo").unwrap();
- assert_eq!(explicit, implicit);
-}
-
-#[test]
-fn non_default_port_is_part_of_identity() {
- let a = Url::parse("https://example.com:444/").unwrap();
- let b = Url::parse("https://example.com:445/").unwrap();
- assert_ne!(a, b);
-}
-
-#[test]
-fn empty_path_vs_slash_only_path_may_differ() {
- let root = Url::parse("https://example.com").unwrap();
- let slash = Url::parse("https://example.com/").unwrap();
- // Both serialize to `https://example.com/` in practice for this crate — verify.
- assert_eq!(root, slash, "document: root and trailing-slash-only merge for this parser");
-}
-
-#[test]
-fn scheme_case_is_normalized_to_lowercase() {
- let lower = Url::parse("https://example.com/").unwrap();
- let upper = Url::parse("HTTPS://example.com/").unwrap();
- assert_eq!(lower, upper);
-}
-
-#[test]
-fn fragment_is_part_of_eq_and_hash() {
- let no_frag = Url::parse("https://example.com/a").unwrap();
- let frag = Url::parse("https://example.com/a#section").unwrap();
- assert_ne!(
- no_frag, frag,
- "#fragment is included in PartialEq — anchors are different HashMap keys"
- );
- assert_ne!(hash_one(&no_frag), hash_one(&frag));
-}
-
-#[test]
-fn query_order_and_encoding_can_split_identity() {
- let a = Url::parse("https://example.com/?b=2&a=1").unwrap();
- let b = Url::parse("https://example.com/?a=1&b=2").unwrap();
- assert_ne!(a, b, "query pairs order is preserved in serialization");
-
- let plus = Url::parse("https://example.com/?q=a+b").unwrap();
- let encoded = Url::parse("https://example.com/?q=a%20b").unwrap();
- assert_ne!(plus, encoded, "space as + vs %20 — different keys unless normalized");
-}
diff --git a/types/src/url_normalize.rs b/types/src/url_normalize.rs
index d5bc913f33ad29cd0cd1c7158e28e4fefdbf5f5d..999baa4f3367b632763b44ddc77b894451207c50 100644
--- a/types/src/url_normalize.rs
+++ b/types/src/url_normalize.rs
@@ -232,3 +232,127 @@ mod tests {
);
}
}
+
+/// `url::Url` as a `HashMap` key: `Eq` / `Hash` behavior (baseline if we store external ids as `Url`).
+#[cfg(test)]
+mod url_identity_tests {
+ use std::collections::HashMap;
+ use std::hash::{Hash, Hasher};
+ use url::Url;
+
+ fn hash_one(url: &Url) -> u64 {
+ let mut h = std::collections::hash_map::DefaultHasher::new();
+ url.hash(&mut h);
+ h.finish()
+ }
+
+ #[test]
+ fn identical_parse_strings_are_eq_and_share_hash_bucket() {
+ let a = Url::parse("https://example.com/path").unwrap();
+
… preview truncated; 4,257 characters omittedB — c_3e6a3370542e (tommy-mor)
message
[e52a5895] Replace vestigial vote identity with pseudonym-based uuid dedup. Store votes in uuid_votes (not blind edge merges), derive rankings on load, and resolve actors via a pseudonyms map seeded at projection open. Co-authored-by: Cursor <cursoragent@cursor.com>
diff preview
diff --git a/server/src/bin/storage_bench.rs b/server/src/bin/storage_bench.rs
index 3d988416ad36b27a7d3dc84280cfdbdcafa43e69..cc5cd78ba1e02a1e6f92aa503677f2d329bb5993 100644
--- a/server/src/bin/storage_bench.rs
+++ b/server/src/bin/storage_bench.rs
@@ -29,14 +29,7 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
for chunk_start in (0..opts.events).step_by(opts.batch_size) {
let chunk_end = (chunk_start + opts.batch_size).min(opts.events);
let events = (chunk_start..chunk_end)
- .map(|i| Event::VoteRecorded {
- ts: i as i64,
- a: format!("item-{i}"),
- b: format!("item-{}", i + 1),
- ratio_left: 2,
- ratio_right: 1,
- scope: String::new(),
- })
+ .map(|i| Event::vote_recorded(i as i64, format!("item-{i}"), format!("item-{}", i + 1), 2, 1, ""))
.collect();
journal.append_many(events).await?;
}
diff --git a/server/src/event_log.rs b/server/src/event_log.rs
index 2c9290b6fdbf2c2ad1c0f1ffd7374b2d9cc97f36..8e5684cb1378e80c45bcf743d2fdc48402dd4dc8 100644
--- a/server/src/event_log.rs
+++ b/server/src/event_log.rs
@@ -206,14 +206,7 @@ mod tests {
.unwrap();
log.append(&sample_record(
2,
- Event::VoteRecorded {
- ts: 1,
- a: "a".into(),
- b: "b".into(),
- ratio_left: 2,
- ratio_right: 1,
- scope: String::new(),
- },
+ Event::vote_recorded(1, "a", "b", 2, 1, ""),
))
.await
.unwrap();
diff --git a/server/src/events.rs b/server/src/events.rs
index d76c3bb4277216b0d39c9422ba7a50db10a95e05..8a166d49b4f26835fbc2b58cb1f4bdbf002763b8 100644
--- a/server/src/events.rs
+++ b/server/src/events.rs
@@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
/// Schema version for JSONL log records. Bump when event semantics change.
-pub const CURRENT_LOG_SCHEMA: u32 = 1;
+pub const CURRENT_LOG_SCHEMA: u32 = 2;
/// One JSONL line: schema envelope around a payload event.
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -51,9 +51,33 @@ pub enum Event {
b: String,
ratio_left: i32,
ratio_right: i32,
- #[serde(default)]
scope: String,
+ pseudonym: String,
+ trust_weight: f64,
},
/// Register a node path in the fractal tree (no external fetch).
NodeEnsured { id: String },
}
+
+impl Event {
+ /// Construct a vote event with the default dev pseudonym (tests and benches).
+ pub fn vote_recorded(
+ ts: i64,
+ a: impl Into<String>,
+ b: impl Into<String>,
+ ratio_left: i32,
+ ratio_right: i32,
+ scope: impl Into<String>,
+ ) -> Self {
+ Self::VoteRecorded {
+ ts,
+ a: a.into(),
+ b: b.into(),
+ ratio_left,
+ ratio_right,
+ scope: scope.into(),
+ pseudonym: crate::identity::DEFAULT_PSEUDONYM.to_string(),
+ trust_weight: 1.0,
+ }
+ }
+}
diff --git a/server/src/html/vote.rs b/server/src/html/vote.rs
index 8cf2b0d4d8bd58cc51cc27c9046fa7f7728a7017..bf82aef3ad9c5f4e9e877c47dab27beb29a80b8f 100644
--- a/server/src/html/vote.rs
+++ b/server/src/html/vote.rs
@@ -336,6 +336,7 @@ pub async fn vote_page(
#[cfg(test)]
mod polarity_tests {
use super::*;
+ use crate::identity::{DEFAULT_PSEUDONYM, TEST_ACTOR_UUID};
use crate::ranking::ranked_items;
use crate::reducer::GlobalTree;
@@ -351,11 +352,11 @@ mod polarity_tests {
let right = id("right_item");
// Stored a == page left: keep order.
- let v1 = VoteData::from_recorded(1, left.as_str(), right.as_str(), 9, 1).unwrap();
+ let v1 = VoteData::from_event(1, left.as_str(), right.as_str(), 9, 1, DEFAULT_PSEUDONYM.to_string(), 1.0).unwrap();
assert_eq!(ratios_for_page(&v1, &left, &right), (9, 1));
// Stored a == page right: swap so left stays left.
- let v2 = VoteData::from_recorded(2, right.as_str(), left.as_str(), 9, 1).unwrap();
+ let v2 = VoteData::from_event(2, right.as_str(), left.as_str(), 9, 1, DEFAULT_PSEUDONYM.to_string(), 1.0).unwrap();
assert_eq!(ratios_for_page(&v2, &left, &right), (1, 9));
}
@@ -383,9 +384,9 @@ mod polarity_tests {
let right = id("right_item");
// Slider dragged left yields e.g. 9:1 with a = left item.
- let vote = VoteData::from_recorded(1, left.as_str(), right.as_str(), 9, 1).unwrap();
+ let vote = VoteData::from_event(1, left.as_str(), right.as_str(), 9, 1, DEFAULT_PSEUDONYM.to_string(), 1.0).unwrap();
let mut tree = GlobalTree::new();
- tree.apply_vote(&parent, vote);
+ tree.apply_vote(&parent, vote, TEST_ACTOR_UUID);
let group = &tree.get(&parent).unwrap().local_ranking;
let ranked = ranked_items(group);
diff --git a/server/src/identity.rs b/server/src/identity.rs
new file mode 100644
index 0000000000000000000000000000000000000000..621d505436912246ec0830ff20c3473f0c4665ce
--- /dev/null
+++ b/server/src/identity.rs
@@ -0,0 +1,35 @@
+//! Actor identity: pseudonym display names mapped to stable UUIDs for vote dedup.
+
+use durable::Db;
+
+use crate::storage_schema::{Store, StoreFields};
+
+/// Default pseudonym until session/auth UI exists.
+pub const DEFAULT_PSEUDONYM: &str = "anon";
+
+/// UUID for the default single-user dev principal.
+pub const DEFAULT_ACTOR_UUID: &str = "00000000-0000-0000-0000-000000000001";
+
+/// UUID for in-memory unit tests.
+pub const TEST_ACTOR_UUID: &str = "00000000-0000-0000-0000-000000000099";
+
+/// Resolve the trust anchor for a pseudonym (must exist in the pseudonyms map).
+pub fn resolve_actor_uuid(db: &Db, pseudonym: &str) -> Result<String, String> {
+ Store::root()
+ .pseudonyms()
+ .key(&pseudonym.to_string())
+ .get(db)
+ .map_err(|e| e.to_string())?
+ .ok_or_else(|| format!("unknown pseudonym: {pseudonym}"))
+}
+
+/// Ensure the default pseudonym → UUID mapping exists (operational seed, not event-logged).
+pub fn seed_default_pseudonym(db: &Db) -> Result<(), durable::Error> {
+ let path = Store::root()
+ .pseudonyms()
+ .key(&DEFAULT_PSEUDONYM.to_string());
+ if path.get(db)?.is_none() {
+ db.run(path.set(&DEFAULT_ACTOR_UUID.to_string()), durable::Durability::SyncWal)?;
+ }
+ Ok(())
+}
diff --git a/server/src/lib.rs b/server/src/lib.rs
index 3dfc7c8acb8ed61bb73ade63e72768e402042cc5..da6e33e3dd6d79967bbee7a2708a7d982c93b88c 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -4,6 +4,7 @@ pub mod events;
pub mod fetch;
pub mod form_template;
pub mod html;
+pub mod identity;
pub mod journal;
pub mod pair;
pub mod parser;
diff --git a/server/src/pair.rs b/server/src/pair.rs
index 2277e32edf6e0024687d6fe2b9d1a9b84c0b34c8..42a1b1eb2adf16730d34d0fe23c13d5a75d7ba27 100644
--- a/server/src/pair.rs
+++ b/server/src/pair.rs
@@ -360,8 +360,17 @@ impl PairError {
#[cfg(test)]
mod tests {
use super::*;
+ use crate::identity::{DEFAULT_PSEUDONYM, TEST_ACTOR_UUID};
use crate::reducer::{GlobalTree, VoteData};
+ fn test_vote(ts: i64, a: &str, b: &str, l: i32, r: i32) -> VoteData {
+ VoteData::from_event(ts, a, b, l, r, DEFAULT_PSEUDONYM.to_string(), 1.0).unwrap()
+ }
+
+ fn apply(tree: &mut GlobalTree, parent: &ItemId, vote: VoteData) {
+ tree.apply_vote(parent, vote, TEST_ACTOR_UUID);
+ }
+
fn seed_children(parent: &ItemId, ids: &[&str]) -> GlobalTree {
let mut tree = GlobalTree::new();
tree.ensure_path(parent);
@@ -382,22 +391,13 @@ mod tests {
#[test]
fn zero_weight_vote_leaves_pair_available_for_suggestion() {
let parent = ItemId::parse("https://reddit.com/r/rust").unwrap();
- let mut tree = seed_children(
+ let tree = seed_children(
&parent,
&[
"https://reddit.com/r/rust/a",
"https://reddit.com/r/rust/b",
],
);
- let noop = VoteData::from_recorded(
- 1,
- "https://reddit.com/r/rust/a",
- "https://reddit.com/r/rust/b",
- 0,
- 0,
- )
- .unwrap();
- tree.apply_vote(&parent, noop);
let group = tree.get(&parent).unwrap().local_ranking.clone();
let pool = children_of(&tree, &parent);
assert!(!pair_is_voted(&group, &pool[0], &pool[1]));
@@ -415,9 +415,8 @@ mod tests {
"https://reddit.com/r/rust/c",
],
);
- let vote =
- VoteData::from_recorded(1, "https://reddit.com/r/rust/a", "https://reddit.com/r/rust/b", 2, 1).unwrap();
- tree.apply_vote(&parent, vote);
+ let vote = test_vote(1, "https://reddit.com/r/rust/a", "https://reddit.com/r/rust/b", 2, 1);
+ apply(&mut tree, &parent, vote);
let group = tree.get(&parent).unwrap().local_ranking.clone();
let pool = children_of(&tree, &parent);
let (l, r) = suggest_next_pair_in_pool(&group, &pool, None).unwrap();
@@ -438,12 +437,10 @@ mod tests {
"https://reddit.com/r/rust/d",
],
);
- let ab =
- VoteData::from_recorded(1, "https://reddit.com/r/rust/a", "https://reddit.com/r/rust/b", 2, 1).unwrap();
- let cd =
- VoteData::from_recorded(2, "https://reddit.com/r/rust/c", "https://reddit.com/r/rust/d", 2, 1).unwrap();
- tree.apply_vote(&parent, ab);
- tree.apply_vote(&parent, cd);
+ let ab = test_vote(1, "https://reddit.com/r/rust/a", "https://reddit.com/r/rust/b", 2, 1);
+ let cd = test_vote(2, "https://reddit.com/r/rust/c", "https://reddit.com/r/rust/d", 2, 1);
+ apply(&mut tree, &parent, ab);
+ apply(&mut tree, &parent, cd);
let group = tree.get(&parent).unwrap().local_ranking.clone();
let pool = children_of(&tree, &parent);
let pair = suggest_next_pair_in_pool(&group, &pool, None).unwrap();
@@ -468,9 +465,8 @@ mod tests {
"https://reddit.com/r/rust/e",
],
);
- let ab =
- VoteData::from_recorded(1, "https://reddit.com/r/rust/a", "https://reddit.com/r/rust/b", 2, 1).unwrap();
- tree.apply_vote(&parent, ab);
+ let ab = test_vote(1, "https://reddit.com/r/rust/a", "https://reddit.com/r/rust/b", 2, 1);
+ apply(&mut tree, &parent, ab);
let group = tree.get(&parent).unwrap().local_ranking.clone();
let pool = children_of(&tree, &parent);
let pair = suggest_next_pair_in_pool(&group, &pool, None).unwrap();
@@ -498,9 +494,8 @@ mod tests {
"https://reddit.com/r/rust/c",
],
);
- let ab =
- VoteData::from_recorded(1, "https://reddit.com/r/rust/a", "https://reddit.com/r/rust/b", 2, 1).unwrap();
- tree.apply_vote(&parent, ab);
+ let ab = test_vote(1, "https://reddit.com/r/rust/a", "https://reddit.com/r/rust/b", 2, 1);
+ apply(&mut tree, &parent, ab);
let group = tree.get(&parent).unwrap().local_ranking.clone();
let pool = children_of(&tree, &parent);
let pair = suggest_next_pair_in_pool(&group, &pool, None).unwrap();
@@ -524,8 +519,8 @@ mod tests {
("https://reddit.com/r/rust/a", "https://reddit.com/r/rust/b", 3, 1),
("https://reddit.com/r/rust/a", "https://reddit.com/r/rust/c", 2, 1),
] {
- let v = VoteData::from_recorded(1, a, b, l, r).unwrap();
- tree.apply_vote(&parent, v);
+ let v = test_vote(1, a, b, l, r);
+ apply(&mut tree, &parent, v);
}
let group = tree.get(&parent).unwrap().local_ranking.clone();
let pool = children_of(&tree, &parent);
@@ -552,8 +547,8 @@ mod tests {
("https://reddi
… preview truncated; 38,467 characters omittedHardlinks — judgments / attempts / prompt
judgments
attempts
Prompt text is loaded only by the download route.