1use std::{cell::RefCell, path::PathBuf, rc::Rc};
2
3use collections::HashSet;
4use gpui::{Entity, TestAppContext};
5use serde_json::json;
6use settings::SettingsStore;
7use util::path;
8
9use crate::{FakeFs, Project};
10
11use project::{trusted_worktrees::*, worktree_store::WorktreeStore};
12
13fn init_test(cx: &mut TestAppContext) {
14 cx.update(|cx| {
15 if cx.try_global::<SettingsStore>().is_none() {
16 let settings_store = SettingsStore::test(cx);
17 cx.set_global(settings_store);
18 }
19 if cx.try_global::<TrustedWorktrees>().is_some() {
20 cx.remove_global::<TrustedWorktrees>();
21 }
22 });
23}
24
25fn init_trust_global(
26 worktree_store: Entity<WorktreeStore>,
27 cx: &mut TestAppContext,
28) -> Entity<TrustedWorktreesStore> {
29 cx.update(|cx| {
30 init(DbTrustedPaths::default(), cx);
31 track_worktree_trust(worktree_store, None, None, None, cx);
32 TrustedWorktrees::try_get_global(cx).expect("global should be set")
33 })
34}
35
36#[gpui::test]
37async fn test_single_worktree_trust(cx: &mut TestAppContext) {
38 init_test(cx);
39
40 let fs = FakeFs::new(cx.executor());
41 fs.insert_tree(path!("/root"), json!({ "main.rs": "fn main() {}" }))
42 .await;
43
44 let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
45 let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
46 let worktree_id = worktree_store.read_with(cx, |store, cx| {
47 store.worktrees().next().unwrap().read(cx).id()
48 });
49
50 let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
51
52 let events: Rc<RefCell<Vec<TrustedWorktreesEvent>>> = Rc::default();
53 cx.update({
54 let events = events.clone();
55 |cx| {
56 cx.subscribe(&trusted_worktrees, move |_, event, _| {
57 events.borrow_mut().push(match event {
58 TrustedWorktreesEvent::Trusted(host, paths) => {
59 TrustedWorktreesEvent::Trusted(host.clone(), paths.clone())
60 }
61 TrustedWorktreesEvent::Restricted(host, paths) => {
62 TrustedWorktreesEvent::Restricted(host.clone(), paths.clone())
63 }
64 });
65 })
66 }
67 })
68 .detach();
69
70 let can_trust = trusted_worktrees.update(cx, |store, cx| {
71 store.can_trust(&worktree_store, worktree_id, cx)
72 });
73 assert!(!can_trust, "worktree should be restricted by default");
74
75 {
76 let events = events.borrow();
77 assert_eq!(events.len(), 1);
78 match &events[0] {
79 TrustedWorktreesEvent::Restricted(event_worktree_store, paths) => {
80 assert_eq!(event_worktree_store, &worktree_store.downgrade());
81 assert!(paths.contains(&PathTrust::Worktree(worktree_id)));
82 }
83 _ => panic!("expected Restricted event"),
84 }
85 }
86
87 let has_restricted = trusted_worktrees.read_with(cx, |store, cx| {
88 store.has_restricted_worktrees(&worktree_store, cx)
89 });
90 assert!(has_restricted, "should have restricted worktrees");
91
92 let restricted = trusted_worktrees.read_with(cx, |trusted_worktrees, cx| {
93 trusted_worktrees.restricted_worktrees(&worktree_store, cx)
94 });
95 assert!(restricted.iter().any(|(id, _)| *id == worktree_id));
96
97 events.borrow_mut().clear();
98
99 let can_trust_again = trusted_worktrees.update(cx, |store, cx| {
100 store.can_trust(&worktree_store, worktree_id, cx)
101 });
102 assert!(!can_trust_again, "worktree should still be restricted");
103 assert!(
104 events.borrow().is_empty(),
105 "no duplicate Restricted event on repeated can_trust"
106 );
107
108 trusted_worktrees.update(cx, |store, cx| {
109 store.trust(
110 &worktree_store,
111 HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
112 cx,
113 );
114 });
115
116 {
117 let events = events.borrow();
118 assert_eq!(events.len(), 1);
119 match &events[0] {
120 TrustedWorktreesEvent::Trusted(event_worktree_store, paths) => {
121 assert_eq!(event_worktree_store, &worktree_store.downgrade());
122 assert!(paths.contains(&PathTrust::Worktree(worktree_id)));
123 }
124 _ => panic!("expected Trusted event"),
125 }
126 }
127
128 let can_trust_after = trusted_worktrees.update(cx, |store, cx| {
129 store.can_trust(&worktree_store, worktree_id, cx)
130 });
131 assert!(can_trust_after, "worktree should be trusted after trust()");
132
133 let has_restricted_after = trusted_worktrees.read_with(cx, |store, cx| {
134 store.has_restricted_worktrees(&worktree_store, cx)
135 });
136 assert!(
137 !has_restricted_after,
138 "should have no restricted worktrees after trust"
139 );
140
141 let restricted_after = trusted_worktrees.read_with(cx, |trusted_worktrees, cx| {
142 trusted_worktrees.restricted_worktrees(&worktree_store, cx)
143 });
144 assert!(
145 restricted_after.is_empty(),
146 "restricted set should be empty"
147 );
148}
149
150#[gpui::test]
151async fn test_single_file_worktree_trust(cx: &mut TestAppContext) {
152 init_test(cx);
153
154 let fs = FakeFs::new(cx.executor());
155 fs.insert_tree(path!("/root"), json!({ "foo.rs": "fn foo() {}" }))
156 .await;
157
158 let project = Project::test(fs, [path!("/root/foo.rs").as_ref()], cx).await;
159 let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
160 let worktree_id = worktree_store.read_with(cx, |store, cx| {
161 let worktree = store.worktrees().next().unwrap();
162 let worktree = worktree.read(cx);
163 assert!(worktree.is_single_file(), "expected single-file worktree");
164 worktree.id()
165 });
166
167 let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
168
169 let events: Rc<RefCell<Vec<TrustedWorktreesEvent>>> = Rc::default();
170 cx.update({
171 let events = events.clone();
172 |cx| {
173 cx.subscribe(&trusted_worktrees, move |_, event, _| {
174 events.borrow_mut().push(match event {
175 TrustedWorktreesEvent::Trusted(host, paths) => {
176 TrustedWorktreesEvent::Trusted(host.clone(), paths.clone())
177 }
178 TrustedWorktreesEvent::Restricted(host, paths) => {
179 TrustedWorktreesEvent::Restricted(host.clone(), paths.clone())
180 }
181 });
182 })
183 }
184 })
185 .detach();
186
187 let can_trust = trusted_worktrees.update(cx, |store, cx| {
188 store.can_trust(&worktree_store, worktree_id, cx)
189 });
190 assert!(
191 !can_trust,
192 "single-file worktree should be restricted by default"
193 );
194
195 {
196 let events = events.borrow();
197 assert_eq!(events.len(), 1);
198 match &events[0] {
199 TrustedWorktreesEvent::Restricted(event_worktree_store, paths) => {
200 assert_eq!(event_worktree_store, &worktree_store.downgrade());
201 assert!(paths.contains(&PathTrust::Worktree(worktree_id)));
202 }
203 _ => panic!("expected Restricted event"),
204 }
205 }
206
207 events.borrow_mut().clear();
208
209 trusted_worktrees.update(cx, |store, cx| {
210 store.trust(
211 &worktree_store,
212 HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
213 cx,
214 );
215 });
216
217 {
218 let events = events.borrow();
219 assert_eq!(events.len(), 1);
220 match &events[0] {
221 TrustedWorktreesEvent::Trusted(event_worktree_store, paths) => {
222 assert_eq!(event_worktree_store, &worktree_store.downgrade());
223 assert!(paths.contains(&PathTrust::Worktree(worktree_id)));
224 }
225 _ => panic!("expected Trusted event"),
226 }
227 }
228
229 let can_trust_after = trusted_worktrees.update(cx, |store, cx| {
230 store.can_trust(&worktree_store, worktree_id, cx)
231 });
232 assert!(
233 can_trust_after,
234 "single-file worktree should be trusted after trust()"
235 );
236}
237
238#[gpui::test]
239async fn test_multiple_single_file_worktrees_trust_one(cx: &mut TestAppContext) {
240 init_test(cx);
241
242 let fs = FakeFs::new(cx.executor());
243 fs.insert_tree(
244 path!("/root"),
245 json!({
246 "a.rs": "fn a() {}",
247 "b.rs": "fn b() {}",
248 "c.rs": "fn c() {}"
249 }),
250 )
251 .await;
252
253 let project = Project::test(
254 fs,
255 [
256 path!("/root/a.rs").as_ref(),
257 path!("/root/b.rs").as_ref(),
258 path!("/root/c.rs").as_ref(),
259 ],
260 cx,
261 )
262 .await;
263 let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
264 let worktree_ids: Vec<_> = worktree_store.read_with(cx, |store, cx| {
265 store
266 .worktrees()
267 .map(|worktree| {
268 let worktree = worktree.read(cx);
269 assert!(worktree.is_single_file());
270 worktree.id()
271 })
272 .collect()
273 });
274 assert_eq!(worktree_ids.len(), 3);
275
276 let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
277
278 for &worktree_id in &worktree_ids {
279 let can_trust = trusted_worktrees.update(cx, |store, cx| {
280 store.can_trust(&worktree_store, worktree_id, cx)
281 });
282 assert!(
283 !can_trust,
284 "worktree {worktree_id:?} should be restricted initially"
285 );
286 }
287
288 trusted_worktrees.update(cx, |store, cx| {
289 store.trust(
290 &worktree_store,
291 HashSet::from_iter([PathTrust::Worktree(worktree_ids[1])]),
292 cx,
293 );
294 });
295
296 let can_trust_0 = trusted_worktrees.update(cx, |store, cx| {
297 store.can_trust(&worktree_store, worktree_ids[0], cx)
298 });
299 let can_trust_1 = trusted_worktrees.update(cx, |store, cx| {
300 store.can_trust(&worktree_store, worktree_ids[1], cx)
301 });
302 let can_trust_2 = trusted_worktrees.update(cx, |store, cx| {
303 store.can_trust(&worktree_store, worktree_ids[2], cx)
304 });
305
306 assert!(!can_trust_0, "worktree 0 should still be restricted");
307 assert!(can_trust_1, "worktree 1 should be trusted");
308 assert!(!can_trust_2, "worktree 2 should still be restricted");
309}
310
311#[gpui::test]
312async fn test_two_directory_worktrees_separate_trust(cx: &mut TestAppContext) {
313 init_test(cx);
314
315 let fs = FakeFs::new(cx.executor());
316 fs.insert_tree(
317 path!("/projects"),
318 json!({
319 "project_a": { "main.rs": "fn main() {}" },
320 "project_b": { "lib.rs": "pub fn lib() {}" }
321 }),
322 )
323 .await;
324
325 let project = Project::test(
326 fs,
327 [
328 path!("/projects/project_a").as_ref(),
329 path!("/projects/project_b").as_ref(),
330 ],
331 cx,
332 )
333 .await;
334 let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
335 let worktree_ids: Vec<_> = worktree_store.read_with(cx, |store, cx| {
336 store
337 .worktrees()
338 .map(|worktree| {
339 let worktree = worktree.read(cx);
340 assert!(!worktree.is_single_file());
341 worktree.id()
342 })
343 .collect()
344 });
345 assert_eq!(worktree_ids.len(), 2);
346
347 let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
348
349 let can_trust_a = trusted_worktrees.update(cx, |store, cx| {
350 store.can_trust(&worktree_store, worktree_ids[0], cx)
351 });
352 let can_trust_b = trusted_worktrees.update(cx, |store, cx| {
353 store.can_trust(&worktree_store, worktree_ids[1], cx)
354 });
355 assert!(!can_trust_a, "project_a should be restricted initially");
356 assert!(!can_trust_b, "project_b should be restricted initially");
357
358 trusted_worktrees.update(cx, |store, cx| {
359 store.trust(
360 &worktree_store,
361 HashSet::from_iter([PathTrust::Worktree(worktree_ids[0])]),
362 cx,
363 );
364 });
365
366 let can_trust_a = trusted_worktrees.update(cx, |store, cx| {
367 store.can_trust(&worktree_store, worktree_ids[0], cx)
368 });
369 let can_trust_b = trusted_worktrees.update(cx, |store, cx| {
370 store.can_trust(&worktree_store, worktree_ids[1], cx)
371 });
372 assert!(can_trust_a, "project_a should be trusted after trust()");
373 assert!(!can_trust_b, "project_b should still be restricted");
374
375 trusted_worktrees.update(cx, |store, cx| {
376 store.trust(
377 &worktree_store,
378 HashSet::from_iter([PathTrust::Worktree(worktree_ids[1])]),
379 cx,
380 );
381 });
382
383 let can_trust_a = trusted_worktrees.update(cx, |store, cx| {
384 store.can_trust(&worktree_store, worktree_ids[0], cx)
385 });
386 let can_trust_b = trusted_worktrees.update(cx, |store, cx| {
387 store.can_trust(&worktree_store, worktree_ids[1], cx)
388 });
389 assert!(can_trust_a, "project_a should remain trusted");
390 assert!(can_trust_b, "project_b should now be trusted");
391}
392
393#[gpui::test]
394async fn test_directory_worktree_trust_enables_single_file(cx: &mut TestAppContext) {
395 init_test(cx);
396
397 let fs = FakeFs::new(cx.executor());
398 fs.insert_tree(
399 path!("/"),
400 json!({
401 "project": { "main.rs": "fn main() {}" },
402 "standalone.rs": "fn standalone() {}"
403 }),
404 )
405 .await;
406
407 let project = Project::test(
408 fs,
409 [path!("/project").as_ref(), path!("/standalone.rs").as_ref()],
410 cx,
411 )
412 .await;
413 let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
414 let (dir_worktree_id, file_worktree_id) = worktree_store.read_with(cx, |store, cx| {
415 let worktrees: Vec<_> = store.worktrees().collect();
416 assert_eq!(worktrees.len(), 2);
417 let (dir_worktree, file_worktree) = if worktrees[0].read(cx).is_single_file() {
418 (&worktrees[1], &worktrees[0])
419 } else {
420 (&worktrees[0], &worktrees[1])
421 };
422 assert!(!dir_worktree.read(cx).is_single_file());
423 assert!(file_worktree.read(cx).is_single_file());
424 (dir_worktree.read(cx).id(), file_worktree.read(cx).id())
425 });
426
427 let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
428
429 let can_trust_file = trusted_worktrees.update(cx, |store, cx| {
430 store.can_trust(&worktree_store, file_worktree_id, cx)
431 });
432 assert!(
433 !can_trust_file,
434 "single-file worktree should be restricted initially"
435 );
436
437 let can_trust_directory = trusted_worktrees.update(cx, |store, cx| {
438 store.can_trust(&worktree_store, dir_worktree_id, cx)
439 });
440 assert!(
441 !can_trust_directory,
442 "directory worktree should be restricted initially"
443 );
444
445 trusted_worktrees.update(cx, |store, cx| {
446 store.trust(
447 &worktree_store,
448 HashSet::from_iter([PathTrust::Worktree(dir_worktree_id)]),
449 cx,
450 );
451 });
452
453 let can_trust_dir = trusted_worktrees.update(cx, |store, cx| {
454 store.can_trust(&worktree_store, dir_worktree_id, cx)
455 });
456 let can_trust_file_after = trusted_worktrees.update(cx, |store, cx| {
457 store.can_trust(&worktree_store, file_worktree_id, cx)
458 });
459 assert!(can_trust_dir, "directory worktree should be trusted");
460 assert!(
461 can_trust_file_after,
462 "single-file worktree should be trusted after directory worktree trust"
463 );
464}
465
466#[gpui::test]
467async fn test_parent_path_trust_enables_single_file(cx: &mut TestAppContext) {
468 init_test(cx);
469
470 let fs = FakeFs::new(cx.executor());
471 fs.insert_tree(
472 path!("/"),
473 json!({
474 "project": { "main.rs": "fn main() {}" },
475 "standalone.rs": "fn standalone() {}"
476 }),
477 )
478 .await;
479
480 let project = Project::test(
481 fs,
482 [path!("/project").as_ref(), path!("/standalone.rs").as_ref()],
483 cx,
484 )
485 .await;
486 let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
487 let (dir_worktree_id, file_worktree_id) = worktree_store.read_with(cx, |store, cx| {
488 let worktrees: Vec<_> = store.worktrees().collect();
489 assert_eq!(worktrees.len(), 2);
490 let (dir_worktree, file_worktree) = if worktrees[0].read(cx).is_single_file() {
491 (&worktrees[1], &worktrees[0])
492 } else {
493 (&worktrees[0], &worktrees[1])
494 };
495 assert!(!dir_worktree.read(cx).is_single_file());
496 assert!(file_worktree.read(cx).is_single_file());
497 (dir_worktree.read(cx).id(), file_worktree.read(cx).id())
498 });
499
500 let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
501
502 let can_trust_file = trusted_worktrees.update(cx, |store, cx| {
503 store.can_trust(&worktree_store, file_worktree_id, cx)
504 });
505 assert!(
506 !can_trust_file,
507 "single-file worktree should be restricted initially"
508 );
509
510 let can_trust_directory = trusted_worktrees.update(cx, |store, cx| {
511 store.can_trust(&worktree_store, dir_worktree_id, cx)
512 });
513 assert!(
514 !can_trust_directory,
515 "directory worktree should be restricted initially"
516 );
517
518 trusted_worktrees.update(cx, |store, cx| {
519 store.trust(
520 &worktree_store,
521 HashSet::from_iter([PathTrust::AbsPath(PathBuf::from(path!("/project")))]),
522 cx,
523 );
524 });
525
526 let can_trust_dir = trusted_worktrees.update(cx, |store, cx| {
527 store.can_trust(&worktree_store, dir_worktree_id, cx)
528 });
529 let can_trust_file_after = trusted_worktrees.update(cx, |store, cx| {
530 store.can_trust(&worktree_store, file_worktree_id, cx)
531 });
532 assert!(
533 can_trust_dir,
534 "directory worktree should be trusted after its parent is trusted"
535 );
536 assert!(
537 can_trust_file_after,
538 "single-file worktree should be trusted after directory worktree trust via its parent directory trust"
539 );
540}
541
542#[gpui::test]
543async fn test_abs_path_trust_covers_multiple_worktrees(cx: &mut TestAppContext) {
544 init_test(cx);
545
546 let fs = FakeFs::new(cx.executor());
547 fs.insert_tree(
548 path!("/root"),
549 json!({
550 "project_a": { "main.rs": "fn main() {}" },
551 "project_b": { "lib.rs": "pub fn lib() {}" }
552 }),
553 )
554 .await;
555
556 let project = Project::test(
557 fs,
558 [
559 path!("/root/project_a").as_ref(),
560 path!("/root/project_b").as_ref(),
561 ],
562 cx,
563 )
564 .await;
565 let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
566 let worktree_ids: Vec<_> = worktree_store.read_with(cx, |store, cx| {
567 store
568 .worktrees()
569 .map(|worktree| worktree.read(cx).id())
570 .collect()
571 });
572 assert_eq!(worktree_ids.len(), 2);
573
574 let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
575
576 for &worktree_id in &worktree_ids {
577 let can_trust = trusted_worktrees.update(cx, |store, cx| {
578 store.can_trust(&worktree_store, worktree_id, cx)
579 });
580 assert!(!can_trust, "worktree should be restricted initially");
581 }
582
583 trusted_worktrees.update(cx, |store, cx| {
584 store.trust(
585 &worktree_store,
586 HashSet::from_iter([PathTrust::AbsPath(PathBuf::from(path!("/root")))]),
587 cx,
588 );
589 });
590
591 for &worktree_id in &worktree_ids {
592 let can_trust = trusted_worktrees.update(cx, |store, cx| {
593 store.can_trust(&worktree_store, worktree_id, cx)
594 });
595 assert!(
596 can_trust,
597 "worktree should be trusted after parent path trust"
598 );
599 }
600}
601
602#[gpui::test]
603async fn test_auto_trust_all(cx: &mut TestAppContext) {
604 init_test(cx);
605
606 let fs = FakeFs::new(cx.executor());
607 fs.insert_tree(
608 path!("/"),
609 json!({
610 "project_a": { "main.rs": "fn main() {}" },
611 "project_b": { "lib.rs": "pub fn lib() {}" },
612 "single.rs": "fn single() {}"
613 }),
614 )
615 .await;
616
617 let project = Project::test(
618 fs,
619 [
620 path!("/project_a").as_ref(),
621 path!("/project_b").as_ref(),
622 path!("/single.rs").as_ref(),
623 ],
624 cx,
625 )
626 .await;
627 let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
628 let worktree_ids: Vec<_> = worktree_store.read_with(cx, |store, cx| {
629 store
630 .worktrees()
631 .map(|worktree| worktree.read(cx).id())
632 .collect()
633 });
634 assert_eq!(worktree_ids.len(), 3);
635
636 let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
637
638 let events: Rc<RefCell<Vec<TrustedWorktreesEvent>>> = Rc::default();
639 cx.update({
640 let events = events.clone();
641 |cx| {
642 cx.subscribe(&trusted_worktrees, move |_, event, _| {
643 events.borrow_mut().push(match event {
644 TrustedWorktreesEvent::Trusted(host, paths) => {
645 TrustedWorktreesEvent::Trusted(host.clone(), paths.clone())
646 }
647 TrustedWorktreesEvent::Restricted(host, paths) => {
648 TrustedWorktreesEvent::Restricted(host.clone(), paths.clone())
649 }
650 });
651 })
652 }
653 })
654 .detach();
655
656 for &worktree_id in &worktree_ids {
657 let can_trust = trusted_worktrees.update(cx, |store, cx| {
658 store.can_trust(&worktree_store, worktree_id, cx)
659 });
660 assert!(!can_trust, "worktree should be restricted initially");
661 }
662
663 let has_restricted = trusted_worktrees.read_with(cx, |store, cx| {
664 store.has_restricted_worktrees(&worktree_store, cx)
665 });
666 assert!(has_restricted, "should have restricted worktrees");
667
668 events.borrow_mut().clear();
669
670 trusted_worktrees.update(cx, |store, cx| {
671 store.auto_trust_all(cx);
672 });
673
674 for &worktree_id in &worktree_ids {
675 let can_trust = trusted_worktrees.update(cx, |store, cx| {
676 store.can_trust(&worktree_store, worktree_id, cx)
677 });
678 assert!(
679 can_trust,
680 "worktree {worktree_id:?} should be trusted after auto_trust_all"
681 );
682 }
683
684 let has_restricted_after = trusted_worktrees.read_with(cx, |store, cx| {
685 store.has_restricted_worktrees(&worktree_store, cx)
686 });
687 assert!(
688 !has_restricted_after,
689 "should have no restricted worktrees after auto_trust_all"
690 );
691
692 let trusted_event_count = events
693 .borrow()
694 .iter()
695 .filter(|e| matches!(e, TrustedWorktreesEvent::Trusted(..)))
696 .count();
697 assert!(
698 trusted_event_count > 0,
699 "should have emitted Trusted events"
700 );
701}
702
703#[gpui::test]
704async fn test_trust_restrict_trust_cycle(cx: &mut TestAppContext) {
705 init_test(cx);
706
707 let fs = FakeFs::new(cx.executor());
708 fs.insert_tree(path!("/root"), json!({ "main.rs": "fn main() {}" }))
709 .await;
710
711 let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
712 let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
713 let worktree_id = worktree_store.read_with(cx, |store, cx| {
714 store.worktrees().next().unwrap().read(cx).id()
715 });
716
717 let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
718
719 let events: Rc<RefCell<Vec<TrustedWorktreesEvent>>> = Rc::default();
720 cx.update({
721 let events = events.clone();
722 |cx| {
723 cx.subscribe(&trusted_worktrees, move |_, event, _| {
724 events.borrow_mut().push(match event {
725 TrustedWorktreesEvent::Trusted(host, paths) => {
726 TrustedWorktreesEvent::Trusted(host.clone(), paths.clone())
727 }
728 TrustedWorktreesEvent::Restricted(host, paths) => {
729 TrustedWorktreesEvent::Restricted(host.clone(), paths.clone())
730 }
731 });
732 })
733 }
734 })
735 .detach();
736
737 let can_trust = trusted_worktrees.update(cx, |store, cx| {
738 store.can_trust(&worktree_store, worktree_id, cx)
739 });
740 assert!(!can_trust, "should be restricted initially");
741 assert_eq!(events.borrow().len(), 1);
742 events.borrow_mut().clear();
743
744 trusted_worktrees.update(cx, |store, cx| {
745 store.trust(
746 &worktree_store,
747 HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
748 cx,
749 );
750 });
751 let can_trust = trusted_worktrees.update(cx, |store, cx| {
752 store.can_trust(&worktree_store, worktree_id, cx)
753 });
754 assert!(can_trust, "should be trusted after trust()");
755 assert_eq!(events.borrow().len(), 1);
756 assert!(matches!(
757 &events.borrow()[0],
758 TrustedWorktreesEvent::Trusted(..)
759 ));
760 events.borrow_mut().clear();
761
762 trusted_worktrees.update(cx, |store, cx| {
763 store.restrict(
764 worktree_store.downgrade(),
765 HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
766 cx,
767 );
768 });
769 let can_trust = trusted_worktrees.update(cx, |store, cx| {
770 store.can_trust(&worktree_store, worktree_id, cx)
771 });
772 assert!(!can_trust, "should be restricted after restrict()");
773 assert_eq!(events.borrow().len(), 1);
774 assert!(matches!(
775 &events.borrow()[0],
776 TrustedWorktreesEvent::Restricted(..)
777 ));
778
779 let has_restricted = trusted_worktrees.read_with(cx, |store, cx| {
780 store.has_restricted_worktrees(&worktree_store, cx)
781 });
782 assert!(has_restricted);
783 events.borrow_mut().clear();
784
785 trusted_worktrees.update(cx, |store, cx| {
786 store.trust(
787 &worktree_store,
788 HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
789 cx,
790 );
791 });
792 let can_trust = trusted_worktrees.update(cx, |store, cx| {
793 store.can_trust(&worktree_store, worktree_id, cx)
794 });
795 assert!(can_trust, "should be trusted again after second trust()");
796 assert_eq!(events.borrow().len(), 1);
797 assert!(matches!(
798 &events.borrow()[0],
799 TrustedWorktreesEvent::Trusted(..)
800 ));
801
802 let has_restricted = trusted_worktrees.read_with(cx, |store, cx| {
803 store.has_restricted_worktrees(&worktree_store, cx)
804 });
805 assert!(!has_restricted);
806}
807
808#[gpui::test]
809async fn test_multi_host_trust_isolation(cx: &mut TestAppContext) {
810 init_test(cx);
811
812 let fs = FakeFs::new(cx.executor());
813 fs.insert_tree(
814 path!("/"),
815 json!({
816 "local_project": { "main.rs": "fn main() {}" },
817 "remote_project": { "lib.rs": "pub fn lib() {}" }
818 }),
819 )
820 .await;
821
822 let project = Project::test(
823 fs,
824 [
825 path!("/local_project").as_ref(),
826 path!("/remote_project").as_ref(),
827 ],
828 cx,
829 )
830 .await;
831 let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
832 let worktree_ids: Vec<_> = worktree_store.read_with(cx, |store, cx| {
833 store
834 .worktrees()
835 .map(|worktree| worktree.read(cx).id())
836 .collect()
837 });
838 assert_eq!(worktree_ids.len(), 2);
839 let local_worktree = worktree_ids[0];
840 let _remote_worktree = worktree_ids[1];
841
842 let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
843
844 let can_trust_local = trusted_worktrees.update(cx, |store, cx| {
845 store.can_trust(&worktree_store, local_worktree, cx)
846 });
847 assert!(!can_trust_local, "local worktree restricted on host_a");
848
849 trusted_worktrees.update(cx, |store, cx| {
850 store.trust(
851 &worktree_store,
852 HashSet::from_iter([PathTrust::Worktree(local_worktree)]),
853 cx,
854 );
855 });
856
857 let can_trust_local_after = trusted_worktrees.update(cx, |store, cx| {
858 store.can_trust(&worktree_store, local_worktree, cx)
859 });
860 assert!(
861 can_trust_local_after,
862 "local worktree should be trusted on local host"
863 );
864}
865
866#[gpui::test]
867async fn test_invisible_worktree_stores_do_not_affect_trust(cx: &mut TestAppContext) {
868 init_test(cx);
869
870 let fs = FakeFs::new(cx.executor());
871 fs.insert_tree(
872 path!("/"),
873 json!({
874 "visible": { "main.rs": "fn main() {}" },
875 "other": { "a.rs": "fn other() {}" },
876 "invisible": { "b.rs": "fn invisible() {}" }
877 }),
878 )
879 .await;
880
881 let project = Project::test(fs, [path!("/visible").as_ref()], cx).await;
882 let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
883 let visible_worktree_id = worktree_store.read_with(cx, |store, cx| {
884 store
885 .worktrees()
886 .find(|worktree| worktree.read(cx).root_dir().unwrap().ends_with("visible"))
887 .expect("visible worktree")
888 .read(cx)
889 .id()
890 });
891 let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
892
893 let events: Rc<RefCell<Vec<TrustedWorktreesEvent>>> = Rc::default();
894 cx.update({
895 let events = events.clone();
896 |cx| {
897 cx.subscribe(&trusted_worktrees, move |_, event, _| {
898 events.borrow_mut().push(match event {
899 TrustedWorktreesEvent::Trusted(host, paths) => {
900 TrustedWorktreesEvent::Trusted(host.clone(), paths.clone())
901 }
902 TrustedWorktreesEvent::Restricted(host, paths) => {
903 TrustedWorktreesEvent::Restricted(host.clone(), paths.clone())
904 }
905 });
906 })
907 }
908 })
909 .detach();
910
911 assert!(
912 !trusted_worktrees.update(cx, |store, cx| {
913 store.can_trust(&worktree_store, visible_worktree_id, cx)
914 }),
915 "visible worktree should be restricted initially"
916 );
917 assert_eq!(
918 HashSet::from_iter([(visible_worktree_id)]),
919 trusted_worktrees.read_with(cx, |store, _| {
920 store.restricted_worktrees_for_store(&worktree_store)
921 }),
922 "only visible worktree should be restricted",
923 );
924
925 let (new_visible_worktree, new_invisible_worktree) =
926 worktree_store.update(cx, |worktree_store, cx| {
927 let new_visible_worktree = worktree_store.create_worktree("/other", true, cx);
928 let new_invisible_worktree = worktree_store.create_worktree("/invisible", false, cx);
929 (new_visible_worktree, new_invisible_worktree)
930 });
931 let (new_visible_worktree, new_invisible_worktree) = (
932 new_visible_worktree.await.unwrap(),
933 new_invisible_worktree.await.unwrap(),
934 );
935
936 let new_visible_worktree_id =
937 new_visible_worktree.read_with(cx, |new_visible_worktree, _| new_visible_worktree.id());
938 assert!(
939 !trusted_worktrees.update(cx, |store, cx| {
940 store.can_trust(&worktree_store, new_visible_worktree_id, cx)
941 }),
942 "new visible worktree should be restricted initially",
943 );
944 assert!(
945 trusted_worktrees.update(cx, |store, cx| {
946 store.can_trust(&worktree_store, new_invisible_worktree.read(cx).id(), cx)
947 }),
948 "invisible worktree should be skipped",
949 );
950 assert_eq!(
951 HashSet::from_iter([visible_worktree_id, new_visible_worktree_id]),
952 trusted_worktrees.read_with(cx, |store, _| {
953 store.restricted_worktrees_for_store(&worktree_store)
954 }),
955 "only visible worktrees should be restricted"
956 );
957}