lightweight com.atproto.sync.listReposByCollection
45
fork

Configure Feed

Select the types of activity you want to include in your feed.

tests for mst::collections

phil cec729ac af4459ed

+187 -1
+3 -1
hacking.md
··· 90 90 - [ ] lenient sync1.1 91 91 - [ ] *don't* allow non-validating commits that look like sync1.1 92 92 - [ ] rachet by PDS host: be lenient if we have never seen a sync1.1-looking commit, always strict after we see one. 93 + - [ ] account status convergeance: if we receive commits from apparently-inactive accounts, should we check upstream status to make sure we're not stale? 94 + 95 + very much still todo but i'm getting tired 93 96 - [ ] multi-relay listener 94 97 - [ ] special did:web behaviour to keep reusing a stale resolution on failure 95 98 - [ ] admin view of backfill state etc 96 99 - [ ] vanity stats for optimizations, like how many in-flight repos were saved from resync due to high-water-mark firehose cursor persistence 97 - - [ ] account status convergeance: if we receive commits from apparently-inactive accounts, should we check upstream status to make sure we're not stale? 98 100 - [ ] if the upstream is a PDS (check with describeServer?) then make only accept events for DIDs that have it as their PDS 99 101 - [ ] split the keyspace: put the rbc/cbr indexes on a second keyspace with larger block size, expect hits on main keyspace 100 102
+184
src/mst/collections.rs
··· 70 70 collections.sort_unstable(); 71 71 Ok(collections) 72 72 } 73 + 74 + #[cfg(test)] 75 + mod tests { 76 + use super::*; 77 + use std::sync::Arc; 78 + 79 + use bytes::Bytes; 80 + use jacquard_common::types::string::Did; 81 + use jacquard_common::types::tid::Tid; 82 + use jacquard_repo::commit::Commit; 83 + use jacquard_repo::{BlockStore, MemoryBlockStore, Mst, car::write_car_bytes}; 84 + use repo_stream::DriverBuilder; 85 + 86 + fn nsid(s: &str) -> Nsid<'static> { 87 + s.parse().unwrap() 88 + } 89 + 90 + /// Build a `MemCar` from a list of `collection/rkey` MST keys. 91 + async fn make_mem_car(keys: &[&str]) -> MemCar { 92 + let storage = Arc::new(MemoryBlockStore::new()); 93 + let mut mst = Mst::new(storage.clone()); 94 + let dummy_cid = storage.put(b"record").await.unwrap(); 95 + for key in keys { 96 + mst = mst.add(key, dummy_cid).await.unwrap(); 97 + } 98 + let (mst_root, mut blocks) = mst.collect_blocks().await.unwrap(); 99 + let commit = Commit { 100 + did: Did::new_owned("did:web:example.com").unwrap(), 101 + version: 3, 102 + data: mst_root, 103 + rev: Tid::now_0(), 104 + prev: None, 105 + sig: Bytes::from(vec![0u8; 64]), 106 + }; 107 + let commit_cid = commit.to_cid().unwrap(); 108 + let commit_cbor = Bytes::from(commit.to_cbor().unwrap()); 109 + blocks.insert(commit_cid, commit_cbor); 110 + let car_bytes = write_car_bytes(commit_cid, blocks).await.unwrap(); 111 + DriverBuilder::new() 112 + .load_car(tokio::io::BufReader::new(std::io::Cursor::new(car_bytes))) 113 + .await 114 + .unwrap() 115 + } 116 + 117 + // --------------------------------------------------------------------------- 118 + // extract_from_slice 119 + // --------------------------------------------------------------------------- 120 + 121 + #[tokio::test] 122 + async fn slice_empty_repo() { 123 + let mut car = make_mem_car(&[]).await; 124 + let result = extract_from_slice(&mut car).unwrap(); 125 + assert!(result.is_empty()); 126 + } 127 + 128 + #[tokio::test] 129 + async fn slice_single_collection_single_record() { 130 + let mut car = make_mem_car(&["app.bsky.feed.post/abc123"]).await; 131 + let result = extract_from_slice(&mut car).unwrap(); 132 + assert_eq!(result, HashSet::from([nsid("app.bsky.feed.post")])); 133 + } 134 + 135 + #[tokio::test] 136 + async fn slice_deduplicates_within_collection() { 137 + let mut car = make_mem_car(&[ 138 + "app.bsky.feed.post/abc123", 139 + "app.bsky.feed.post/def456", 140 + "app.bsky.feed.post/ghi789", 141 + ]) 142 + .await; 143 + let result = extract_from_slice(&mut car).unwrap(); 144 + assert_eq!(result, HashSet::from([nsid("app.bsky.feed.post")])); 145 + } 146 + 147 + #[tokio::test] 148 + async fn slice_multiple_collections() { 149 + let mut car = make_mem_car(&[ 150 + "app.bsky.actor.profile/self", 151 + "app.bsky.feed.post/abc123", 152 + "app.bsky.graph.follow/abc123", 153 + ]) 154 + .await; 155 + let result = extract_from_slice(&mut car).unwrap(); 156 + assert_eq!( 157 + result, 158 + HashSet::from([ 159 + nsid("app.bsky.actor.profile"), 160 + nsid("app.bsky.feed.post"), 161 + nsid("app.bsky.graph.follow"), 162 + ]) 163 + ); 164 + } 165 + 166 + // --------------------------------------------------------------------------- 167 + // extract_from_full 168 + // --------------------------------------------------------------------------- 169 + 170 + #[tokio::test] 171 + async fn full_empty_repo() { 172 + let car = make_mem_car(&[]).await; 173 + let result = extract_from_full(car).await.unwrap(); 174 + assert!(result.is_empty()); 175 + } 176 + 177 + #[tokio::test] 178 + async fn full_single_collection_single_record() { 179 + let car = make_mem_car(&["app.bsky.feed.post/abc123"]).await; 180 + let result = extract_from_full(car).await.unwrap(); 181 + assert_eq!(result, vec![nsid("app.bsky.feed.post")]); 182 + } 183 + 184 + #[tokio::test] 185 + async fn full_deduplicates_and_sorts() { 186 + let car = make_mem_car(&[ 187 + "app.bsky.actor.profile/self", 188 + "app.bsky.feed.post/abc123", 189 + "app.bsky.feed.post/def456", 190 + "app.bsky.graph.follow/abc123", 191 + ]) 192 + .await; 193 + let result = extract_from_full(car).await.unwrap(); 194 + assert_eq!( 195 + result, 196 + vec![ 197 + nsid("app.bsky.actor.profile"), 198 + nsid("app.bsky.feed.post"), 199 + nsid("app.bsky.graph.follow"), 200 + ] 201 + ); 202 + } 203 + 204 + #[tokio::test] 205 + async fn full_prefix_collection_names() { 206 + // In the MST, '.' (0x2E) < '/' (0x2F), so `sh.tangled.repo.issue/<rkey>` 207 + // sorts before `sh.tangled.repo/<rkey>`. The seek strategy must still 208 + // produce a correctly sorted output that includes all three. 209 + let car = make_mem_car(&[ 210 + "sh.tangled.repo/self", 211 + "sh.tangled.repo.issue/abc123", 212 + "sh.tangled.repo.pull/abc123", 213 + ]) 214 + .await; 215 + let result = extract_from_full(car).await.unwrap(); 216 + assert_eq!( 217 + result, 218 + vec![ 219 + nsid("sh.tangled.repo"), 220 + nsid("sh.tangled.repo.issue"), 221 + nsid("sh.tangled.repo.pull"), 222 + ] 223 + ); 224 + } 225 + 226 + #[tokio::test] 227 + async fn full_many_collections() { 228 + let car = make_mem_car(&[ 229 + "app.bsky.actor.profile/self", 230 + "app.bsky.feed.generator/abc123", 231 + "app.bsky.feed.like/abc123", 232 + "app.bsky.feed.like/def456", 233 + "app.bsky.feed.post/abc123", 234 + "app.bsky.feed.post/def456", 235 + "app.bsky.feed.post/ghi789", 236 + "app.bsky.feed.repost/abc123", 237 + "app.bsky.graph.block/abc123", 238 + "app.bsky.graph.follow/abc123", 239 + "app.bsky.graph.follow/def456", 240 + ]) 241 + .await; 242 + let result = extract_from_full(car).await.unwrap(); 243 + assert_eq!( 244 + result, 245 + vec![ 246 + nsid("app.bsky.actor.profile"), 247 + nsid("app.bsky.feed.generator"), 248 + nsid("app.bsky.feed.like"), 249 + nsid("app.bsky.feed.post"), 250 + nsid("app.bsky.feed.repost"), 251 + nsid("app.bsky.graph.block"), 252 + nsid("app.bsky.graph.follow"), 253 + ] 254 + ); 255 + } 256 + }