//! Router-level tests for the Security repo tab: it shows to people with Write on the repo, and //! only when advisories are enabled. use std::sync::Arc; use forge_core::{Role, Store, Visibility}; use topcoat::router::request::Request; use topcoat::router::{Body, Router, to_bytes}; use crate::auth::SESSION_COOKIE; /// The Security tab's link. The shell's settings menu also has a "Security" link, to /// `/settings/security`, so the text alone cannot tell the tab apart; the repo's href can. const SECURITY_TAB: &str = "href=\"/owner/proj/security\""; const CSRF: &str = "security-tab-test-csrf"; struct Site { router: Router, store: Store, _dir: tempfile::TempDir, } async fn site(advisories: bool) -> Site { let dir = tempfile::tempdir().expect("tempdir"); let mut state = crate::test_support::test_state(dir.path()).await; let mut config = crate::test_support::test_config(dir.path()); config.advisories.enabled = advisories; state.config = Arc::new(config); let store = state.store.clone(); let router = crate::test_support::app_router(state); Site { router, store, _dir: dir, } } /// Signs `user_id` in and returns the session cookie header. async fn sign_in(store: &Store, user_id: &str, token: &str) -> String { crate::test_support::create_session(store, user_id, token, CSRF).await; format!("__Host-{SESSION_COOKIE}={token}") } async fn get(site: &Site, uri: &str, cookie: Option<&str>) -> (u16, String) { let mut builder = Request::builder().method("GET").uri(uri); if let Some(cookie) = cookie { builder = builder.header("cookie", cookie); } let res = site .router .handle(builder.body(Body::from(String::new())).expect("request")) .await; let status = res.status().as_u16(); let bytes = to_bytes(res.into_body(), usize::MAX).await.expect("body"); (status, String::from_utf8_lossy(&bytes).into_owned()) } /// A public repo `owner/proj`, with an owner and a plain reader who can read it. async fn fixture(site: &Site) -> (String, String) { let owner = site .store .create_user("owner", None, "unused", false) .await .unwrap(); site.store .create_repo(&owner.id, "proj", "", Visibility::Public, "main") .await .unwrap(); let owner_cookie = sign_in(&site.store, &owner.id, "owner-token").await; let reader = site .store .create_user("reader", None, "unused", false) .await .unwrap(); let reader_cookie = sign_in(&site.store, &reader.id, "reader-token").await; (owner_cookie, reader_cookie) } #[tokio::test] async fn security_tab_shows_to_the_owner_when_advisories_are_on() { let site = site(true).await; let (owner_cookie, _) = fixture(&site).await; let (status, body) = get(&site, "/owner/proj/issues", Some(&owner_cookie)).await; assert_eq!(status, 200); assert!( body.contains(SECURITY_TAB), "owner should see the Security tab" ); assert!(body.contains("/owner/proj/security")); } #[tokio::test] async fn security_tab_shows_to_write_collaborators() { let site = site(true).await; fixture(&site).await; let writer = site .store .create_user("writer", None, "unused", false) .await .unwrap(); let repo = site .store .repo_by_owner_and_name("owner", "proj") .await .unwrap() .expect("repo"); site.store .set_collaborator(&repo.id, &writer.id, Role::Write) .await .unwrap(); let writer_cookie = sign_in(&site.store, &writer.id, "writer-token").await; let (status, body) = get(&site, "/owner/proj/issues", Some(&writer_cookie)).await; assert_eq!(status, 200); assert!( body.contains(SECURITY_TAB), "a Write collaborator should see the tab" ); } #[tokio::test] async fn security_tab_is_hidden_from_readers() { let site = site(true).await; let (_, reader_cookie) = fixture(&site).await; let (status, body) = get(&site, "/owner/proj/issues", Some(&reader_cookie)).await; assert_eq!(status, 200); assert!( !body.contains(SECURITY_TAB), "a reader must not see the tab" ); } #[tokio::test] async fn security_tab_is_hidden_when_advisories_are_off() { let site = site(false).await; let (owner_cookie, _) = fixture(&site).await; let (status, body) = get(&site, "/owner/proj/issues", Some(&owner_cookie)).await; assert_eq!(status, 200); assert!( !body.contains(SECURITY_TAB), "no tab while advisories are off" ); }