relay filter/appview bootstrap
1mod common;
2
3use chrono::Utc;
4use reqwest::StatusCode;
5use serde_json::{json, Value};
6use prism::records::{
7 NewAccount, NewRecord, CHANNEL_COLLECTION, INVITE_COLLECTION, LATTICE_COLLECTION,
8 MEMBERSHIP_COLLECTION, SHARD_COLLECTION,
9};
10
11async fn insert_test_account(pool: &sqlx::PgPool, did: &str) {
12 prism::db::upsert_account(
13 pool,
14 &NewAccount {
15 did: did.to_string(),
16 handle: did.to_string(),
17 created_at: Utc::now(),
18 },
19 )
20 .await
21 .unwrap();
22}
23
24async fn insert_test_channel(pool: &sqlx::PgPool, cid: &str, creator_did: &str, name: &str) {
25 let uri = format!("at://{}/systems.gmstn.development.channel/{}", creator_did, cid);
26 let now = Utc::now();
27 prism::db::insert_record(
28 pool,
29 &NewRecord {
30 uri,
31 cid: cid.to_string(),
32 collection: CHANNEL_COLLECTION.to_string(),
33 creator_did: creator_did.to_string(),
34 created_at: now,
35 indexed_at: now,
36 data: json!({"name": name, "topic": "test topic"}),
37 target_did: None,
38 ref_cids: vec![],
39 },
40 )
41 .await
42 .unwrap();
43}
44
45async fn insert_test_invite(
46 pool: &sqlx::PgPool,
47 cid: &str,
48 creator_did: &str,
49 channel_cid: &str,
50 recipient_did: &str,
51) {
52 let uri = format!(
53 "at://{}/systems.gmstn.development.channel.invite/{}",
54 creator_did, cid
55 );
56 let now = Utc::now();
57 prism::db::insert_record(
58 pool,
59 &NewRecord {
60 uri,
61 cid: cid.to_string(),
62 collection: INVITE_COLLECTION.to_string(),
63 creator_did: creator_did.to_string(),
64 created_at: now,
65 indexed_at: now,
66 data: json!({"recipient": recipient_did}),
67 target_did: Some(recipient_did.to_string()),
68 ref_cids: vec![channel_cid.to_string()],
69 },
70 )
71 .await
72 .unwrap();
73}
74
75async fn insert_test_membership(
76 pool: &sqlx::PgPool,
77 cid: &str,
78 recipient_did: &str,
79 channel_cid: &str,
80 invite_cid: &str,
81) {
82 let uri = format!(
83 "at://{}/systems.gmstn.development.channel.membership/{}",
84 recipient_did, cid
85 );
86 let now = Utc::now();
87 prism::db::insert_record(
88 pool,
89 &NewRecord {
90 uri,
91 cid: cid.to_string(),
92 collection: MEMBERSHIP_COLLECTION.to_string(),
93 creator_did: recipient_did.to_string(),
94 created_at: now,
95 indexed_at: now,
96 data: json!({"state": "accepted"}),
97 target_did: Some(recipient_did.to_string()),
98 ref_cids: vec![channel_cid.to_string(), invite_cid.to_string()],
99 },
100 )
101 .await
102 .unwrap();
103}
104
105async fn insert_test_lattice(pool: &sqlx::PgPool, cid: &str, creator_did: &str) {
106 let uri = format!("at://{}/systems.gmstn.development.lattice/{}", creator_did, cid);
107 let now = Utc::now();
108 prism::db::insert_record(
109 pool,
110 &NewRecord {
111 uri,
112 cid: cid.to_string(),
113 collection: LATTICE_COLLECTION.to_string(),
114 creator_did: creator_did.to_string(),
115 created_at: now,
116 indexed_at: now,
117 data: json!({"description": "test lattice"}),
118 target_did: None,
119 ref_cids: vec![],
120 },
121 )
122 .await
123 .unwrap();
124}
125
126async fn insert_test_shard(pool: &sqlx::PgPool, cid: &str, creator_did: &str) {
127 let uri = format!("at://{}/systems.gmstn.development.shard/{}", creator_did, cid);
128 let now = Utc::now();
129 prism::db::insert_record(
130 pool,
131 &NewRecord {
132 uri,
133 cid: cid.to_string(),
134 collection: SHARD_COLLECTION.to_string(),
135 creator_did: creator_did.to_string(),
136 created_at: now,
137 indexed_at: now,
138 data: json!({"description": "test shard"}),
139 target_did: None,
140 ref_cids: vec![],
141 },
142 )
143 .await
144 .unwrap();
145}
146
147#[tokio::test]
148async fn test_list_channels_returns_correct_structure() {
149 let base_url = common::base_url().await;
150 let pool = common::get_db_pool().await;
151 let client = common::client();
152
153 let test_did = "did:plc:test_channels_structure";
154 insert_test_account(&pool, test_did).await;
155 insert_test_channel(&pool, "bafychannel001", test_did, "Test Channel").await;
156
157 let res = client
158 .get(format!(
159 "{}/xrpc/systems.gmstn.development.channel.listChannels?author={}",
160 base_url, test_did
161 ))
162 .send()
163 .await
164 .unwrap();
165
166 assert_eq!(res.status(), StatusCode::OK);
167
168 let body: Value = res.json().await.unwrap();
169 assert!(body["channels"].is_array());
170
171 let channels = body["channels"].as_array().unwrap();
172 assert!(!channels.is_empty());
173
174 let channel = &channels[0];
175 assert!(channel["uri"].is_string());
176 assert!(channel["cid"].is_string());
177 assert!(channel["displayName"].is_string());
178 assert!(channel["createdAt"].is_string());
179 assert!(channel["indexedAt"].is_string());
180}
181
182#[tokio::test]
183async fn test_list_channels_empty_author() {
184 let base_url = common::base_url().await;
185 let client = common::client();
186
187 let res = client
188 .get(format!(
189 "{}/xrpc/systems.gmstn.development.channel.listChannels?author=did:plc:nonexistent",
190 base_url
191 ))
192 .send()
193 .await
194 .unwrap();
195
196 assert_eq!(res.status(), StatusCode::OK);
197
198 let body: Value = res.json().await.unwrap();
199 assert!(body["channels"].is_array());
200 assert!(body["channels"].as_array().unwrap().is_empty());
201 assert!(body["cursor"].is_null());
202}
203
204#[tokio::test]
205async fn test_list_channels_limit_bounds() {
206 let base_url = common::base_url().await;
207 let pool = common::get_db_pool().await;
208 let client = common::client();
209
210 let test_did = "did:plc:test_limit_bounds";
211 insert_test_account(&pool, test_did).await;
212
213 for i in 0..5 {
214 insert_test_channel(&pool, &format!("bafylimitchan{}", i), test_did, &format!("Channel {}", i))
215 .await;
216 }
217
218 let res = client
219 .get(format!(
220 "{}/xrpc/systems.gmstn.development.channel.listChannels?author={}&limit=2",
221 base_url, test_did
222 ))
223 .send()
224 .await
225 .unwrap();
226
227 assert_eq!(res.status(), StatusCode::OK);
228
229 let body: Value = res.json().await.unwrap();
230 let channels = body["channels"].as_array().unwrap();
231 assert_eq!(channels.len(), 2);
232 assert!(body["cursor"].is_string());
233
234 let res_over_limit = client
235 .get(format!(
236 "{}/xrpc/systems.gmstn.development.channel.listChannels?author={}&limit=150",
237 base_url, test_did
238 ))
239 .send()
240 .await
241 .unwrap();
242
243 assert_eq!(res_over_limit.status(), StatusCode::OK);
244
245 let body_over: Value = res_over_limit.json().await.unwrap();
246 let channels_over = body_over["channels"].as_array().unwrap();
247 assert!(channels_over.len() <= 100);
248}
249
250#[tokio::test]
251async fn test_list_invites_by_recipient() {
252 let base_url = common::base_url().await;
253 let pool = common::get_db_pool().await;
254 let client = common::client();
255
256 let creator_did = "did:plc:invite_creator";
257 let recipient_did = "did:plc:invite_recipient";
258 insert_test_account(&pool, creator_did).await;
259 insert_test_account(&pool, recipient_did).await;
260
261 insert_test_invite(&pool, "bafyinvite001", creator_did, "bafyinvitechan001", recipient_did).await;
262
263 let res = client
264 .get(format!(
265 "{}/xrpc/systems.gmstn.development.channel.listInvites?recipient={}",
266 base_url, recipient_did
267 ))
268 .send()
269 .await
270 .unwrap();
271
272 assert_eq!(res.status(), StatusCode::OK);
273
274 let body: Value = res.json().await.unwrap();
275 assert!(body["invites"].is_array());
276
277 let invites = body["invites"].as_array().unwrap();
278 assert!(!invites.is_empty());
279
280 let invite = &invites[0];
281 assert!(invite["uri"].is_string());
282 assert!(invite["cid"].is_string());
283 assert!(invite["channel"].is_string());
284 assert!(invite["recipient"].is_string());
285 assert!(invite["createdAt"].is_string());
286}
287
288#[tokio::test]
289async fn test_list_memberships_by_recipient() {
290 let base_url = common::base_url().await;
291 let pool = common::get_db_pool().await;
292 let client = common::client();
293
294 let creator_did = "did:plc:membership_creator";
295 let recipient_did = "did:plc:membership_recipient";
296 insert_test_account(&pool, creator_did).await;
297 insert_test_account(&pool, recipient_did).await;
298
299 insert_test_membership(&pool, "bafymem001", recipient_did, "bafymemchan001", "bafymeminvite001").await;
300
301 let res = client
302 .get(format!(
303 "{}/xrpc/systems.gmstn.development.channel.listMemberships?recipient={}",
304 base_url, recipient_did
305 ))
306 .send()
307 .await
308 .unwrap();
309
310 assert_eq!(res.status(), StatusCode::OK);
311
312 let body: Value = res.json().await.unwrap();
313 assert!(body["memberships"].is_array());
314
315 let memberships = body["memberships"].as_array().unwrap();
316 assert!(!memberships.is_empty());
317
318 let membership = &memberships[0];
319 assert!(membership["uri"].is_string());
320 assert!(membership["cid"].is_string());
321 assert!(membership["channel"].is_string());
322 assert!(membership["invite"].is_string());
323 assert!(membership["recipient"].is_string());
324 assert!(membership["state"].is_string());
325 assert!(membership["createdAt"].is_string());
326}
327
328#[tokio::test]
329async fn test_list_lattices_by_author() {
330 let base_url = common::base_url().await;
331 let pool = common::get_db_pool().await;
332 let client = common::client();
333
334 let test_did = "did:plc:lattice_author";
335 insert_test_account(&pool, test_did).await;
336 insert_test_lattice(&pool, "bafylattice001", test_did).await;
337
338 let res = client
339 .get(format!(
340 "{}/xrpc/systems.gmstn.development.lattice.listLattices?author={}",
341 base_url, test_did
342 ))
343 .send()
344 .await
345 .unwrap();
346
347 assert_eq!(res.status(), StatusCode::OK);
348
349 let body: Value = res.json().await.unwrap();
350 assert!(body["lattices"].is_array());
351
352 let lattices = body["lattices"].as_array().unwrap();
353 assert!(!lattices.is_empty());
354
355 let lattice = &lattices[0];
356 assert!(lattice["uri"].is_string());
357 assert!(lattice["cid"].is_string());
358 assert!(lattice["author"].is_string());
359 assert!(lattice["createdAt"].is_string());
360 assert!(lattice["indexedAt"].is_string());
361}
362
363#[tokio::test]
364async fn test_list_shards_by_author() {
365 let base_url = common::base_url().await;
366 let pool = common::get_db_pool().await;
367 let client = common::client();
368
369 let test_did = "did:plc:shard_author";
370 insert_test_account(&pool, test_did).await;
371 insert_test_shard(&pool, "bafyshard001", test_did).await;
372
373 let res = client
374 .get(format!(
375 "{}/xrpc/systems.gmstn.development.shard.listShards?author={}",
376 base_url, test_did
377 ))
378 .send()
379 .await
380 .unwrap();
381
382 assert_eq!(res.status(), StatusCode::OK);
383
384 let body: Value = res.json().await.unwrap();
385 assert!(body["shards"].is_array());
386
387 let shards = body["shards"].as_array().unwrap();
388 assert!(!shards.is_empty());
389
390 let shard = &shards[0];
391 assert!(shard["uri"].is_string());
392 assert!(shard["cid"].is_string());
393 assert!(shard["author"].is_string());
394 assert!(shard["createdAt"].is_string());
395 assert!(shard["indexedAt"].is_string());
396}