| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
use crate::harness::{BuildOptions, TestHarness}; |
| 11 |
|
| 12 |
|
| 13 |
fn make_repo(dir: &std::path::Path) { |
| 14 |
use crate::harness::gitfixture::{blob, commit, init_bare, tree}; |
| 15 |
use gix::objs::tree::EntryKind; |
| 16 |
|
| 17 |
let repo = init_bare(dir, "testowner", "testrepo"); |
| 18 |
let readme = blob(&repo, b"# Test Repo\n"); |
| 19 |
let root = tree(&repo, &[("README.md", readme, EntryKind::Blob)]); |
| 20 |
commit(&repo, "Initial commit", root, Vec::new()); |
| 21 |
} |
| 22 |
|
| 23 |
|
| 24 |
async fn setup(tmp: &tempfile::TempDir) -> (TestHarness, String) { |
| 25 |
make_repo(tmp.path()); |
| 26 |
let mut h = TestHarness::build(BuildOptions { |
| 27 |
git_repos_path: Some(tmp.path().to_str().unwrap().to_string()), |
| 28 |
build_trigger_token: Some("test-trigger-secret".to_string()), |
| 29 |
..Default::default() |
| 30 |
}) |
| 31 |
.await; |
| 32 |
h.signup("testowner", "testowner@example.com", "password123") |
| 33 |
.await; |
| 34 |
|
| 35 |
|
| 36 |
h.client.get("/git/testowner/testrepo").await; |
| 37 |
sqlx::query("UPDATE git_repos SET visibility = 'public' WHERE name = 'testrepo'") |
| 38 |
.execute(&h.db) |
| 39 |
.await |
| 40 |
.unwrap(); |
| 41 |
h.client.post_form("/logout", "").await; |
| 42 |
|
| 43 |
let sha = crate::harness::gitfixture::main_tip_sha(tmp.path(), "testowner", "testrepo"); |
| 44 |
(h, sha) |
| 45 |
} |
| 46 |
|
| 47 |
const ANNOTATION: &str = "the commit that broke the importer"; |
| 48 |
|
| 49 |
|
| 50 |
async fn annotate(h: &mut TestHarness, who: &str, sha: &str, body: &str) { |
| 51 |
h.signup(who, &format!("{who}@example.com"), "password123") |
| 52 |
.await; |
| 53 |
let resp = h |
| 54 |
.client |
| 55 |
.post_form( |
| 56 |
&format!("/git/testowner/testrepo/commit/{sha}/annotate"), |
| 57 |
&format!("content={}", urlencoding::encode(body)), |
| 58 |
) |
| 59 |
.await; |
| 60 |
assert_eq!(resp.status, 303, "{}", resp.text); |
| 61 |
} |
| 62 |
|
| 63 |
#[tokio::test] |
| 64 |
async fn a_reader_sees_their_own_annotation_on_the_commit_page() { |
| 65 |
let tmp = tempfile::TempDir::new().unwrap(); |
| 66 |
let (mut h, sha) = setup(&tmp).await; |
| 67 |
annotate(&mut h, "annotator", &sha, ANNOTATION).await; |
| 68 |
|
| 69 |
let page = h |
| 70 |
.client |
| 71 |
.get(&format!("/git/testowner/testrepo/commit/{sha}")) |
| 72 |
.await; |
| 73 |
assert_eq!(page.status, 200); |
| 74 |
assert!( |
| 75 |
page.text.contains("git-annotations"), |
| 76 |
"the private section is missing from the author's own view" |
| 77 |
); |
| 78 |
assert!( |
| 79 |
page.text.contains(ANNOTATION), |
| 80 |
"the author cannot read what they wrote" |
| 81 |
); |
| 82 |
|
| 83 |
|
| 84 |
|
| 85 |
let body_at = page |
| 86 |
.text |
| 87 |
.find("git-annotations") |
| 88 |
.expect("the section is present"); |
| 89 |
let form_at = page |
| 90 |
.text |
| 91 |
.find("Save annotation") |
| 92 |
.expect("the edit form is present"); |
| 93 |
assert!( |
| 94 |
page.text[body_at..form_at].contains(ANNOTATION), |
| 95 |
"the annotation is not rendered above the form" |
| 96 |
); |
| 97 |
} |
| 98 |
|
| 99 |
#[tokio::test] |
| 100 |
async fn nobody_else_sees_it_on_the_same_page() { |
| 101 |
let tmp = tempfile::TempDir::new().unwrap(); |
| 102 |
let (mut h, sha) = setup(&tmp).await; |
| 103 |
annotate(&mut h, "annotator", &sha, ANNOTATION).await; |
| 104 |
h.client.post_form("/logout", "").await; |
| 105 |
|
| 106 |
|
| 107 |
h.signup("stranger", "stranger@example.com", "password123") |
| 108 |
.await; |
| 109 |
let theirs = h |
| 110 |
.client |
| 111 |
.get(&format!("/git/testowner/testrepo/commit/{sha}")) |
| 112 |
.await; |
| 113 |
assert_eq!(theirs.status, 200); |
| 114 |
assert!( |
| 115 |
!theirs.text.contains(ANNOTATION), |
| 116 |
"another account was served somebody's private annotation" |
| 117 |
); |
| 118 |
|
| 119 |
|
| 120 |
assert!( |
| 121 |
!theirs.text.contains("git-annotation-entry"), |
| 122 |
"another account was served an annotation row that is not theirs" |
| 123 |
); |
| 124 |
h.client.post_form("/logout", "").await; |
| 125 |
|
| 126 |
let anon = h |
| 127 |
.client |
| 128 |
.get(&format!("/git/testowner/testrepo/commit/{sha}")) |
| 129 |
.await; |
| 130 |
assert_eq!(anon.status, 200); |
| 131 |
assert!( |
| 132 |
!anon.text.contains(ANNOTATION), |
| 133 |
"an anonymous request was served a private annotation" |
| 134 |
); |
| 135 |
assert!( |
| 136 |
!anon.text.contains("git-annotations"), |
| 137 |
"the private section is rendered for a viewer who has no account" |
| 138 |
); |
| 139 |
} |
| 140 |
|
| 141 |
#[tokio::test] |
| 142 |
async fn an_annotation_outlives_the_repository_it_was_written_against() { |
| 143 |
let tmp = tempfile::TempDir::new().unwrap(); |
| 144 |
let (mut h, sha) = setup(&tmp).await; |
| 145 |
annotate(&mut h, "annotator", &sha, ANNOTATION).await; |
| 146 |
|
| 147 |
|
| 148 |
|
| 149 |
sqlx::query("DELETE FROM git_repos WHERE name = 'testrepo'") |
| 150 |
.execute(&h.db) |
| 151 |
.await |
| 152 |
.unwrap(); |
| 153 |
|
| 154 |
let listing = h.client.get("/git/my-annotations").await; |
| 155 |
assert_eq!(listing.status, 200, "{}", listing.text); |
| 156 |
assert!( |
| 157 |
listing.text.contains(ANNOTATION), |
| 158 |
"the annotation was dropped with the repository it annotated" |
| 159 |
); |
| 160 |
assert!( |
| 161 |
listing.text.contains("no longer serves"), |
| 162 |
"an orphan is listed without being marked as one" |
| 163 |
); |
| 164 |
assert!( |
| 165 |
listing.text.contains("testowner/testrepo"), |
| 166 |
"the origin is still named even though it no longer answers" |
| 167 |
); |
| 168 |
|
| 169 |
|
| 170 |
|
| 171 |
let annotator = sqlx::query_scalar::<_, makenotwork::db::UserId>( |
| 172 |
"SELECT id FROM users WHERE username = 'annotator'", |
| 173 |
) |
| 174 |
.fetch_one(&h.db) |
| 175 |
.await |
| 176 |
.unwrap(); |
| 177 |
assert_eq!( |
| 178 |
makenotwork::db::git_notes::count_user_annotations(&h.db, annotator) |
| 179 |
.await |
| 180 |
.unwrap(), |
| 181 |
1, |
| 182 |
"an orphan was collected, and nothing is allowed to collect one" |
| 183 |
); |
| 184 |
} |
| 185 |
|