//! Site access gate (`ACCESS_GATE=fan_plus_or_creator`): the testnot-style //! gate that restricts the whole site to creators and Fan+ members. use crate::harness::{BuildOptions, TestHarness}; use makenotwork::config::AccessGate; use sqlx::PgPool; fn location(resp: &crate::harness::client::TestResponse) -> String { resp.headers .get("location") .and_then(|v| v.to_str().ok()) .unwrap_or("") .to_string() } /// Insert a verified user directly (the HTTP signup flow is itself gated on /// testnot, so the test seeds the DB and authenticates via the exempt /login). async fn seed_user(pool: &PgPool, username: &str, can_create_projects: bool) { let hash = makenotwork::auth::hash_password("password123").expect("hash"); sqlx::query( "INSERT INTO users (username, email, password_hash, email_verified, can_create_projects) VALUES ($1, $2, $3, true, $4)", ) .bind(username) .bind(format!("{username}@example.com")) .bind(&hash) .bind(can_create_projects) .execute(pool) .await .expect("seed user"); } #[tokio::test] async fn access_gate_restricts_to_fan_plus_or_creator() { let mut h = TestHarness::build(BuildOptions { access_gate: AccessGate::FanPlusOrCreator, ..Default::default() }) .await; seed_user(&h.db, "gatecreator", true).await; seed_user(&h.db, "plainfan", false).await; // Anonymous visitor → bounced to login with the gate notice. let r = h.client.get("/").await; assert!(r.status.is_redirection(), "anon should be redirected, got {}", r.status); assert!(location(&r).starts_with("/login"), "anon should land on login, got {}", location(&r)); // Exempt paths stay reachable while the gate is on, or login is impossible. assert_eq!(h.client.get("/login").await.status.as_u16(), 200, "login page must be reachable"); assert_eq!(h.client.get("/health").await.status.as_u16(), 200, "health must be reachable"); // A logged-in plain fan (no creator, no Fan+) is still blocked — the gate // is stricter than ordinary auth. h.login("plainfan", "password123").await; let r = h.client.get("/library").await; assert!(r.status.is_redirection(), "plain fan should be gated, got {}", r.status); assert!(location(&r).starts_with("/login"), "plain fan should land on login"); // A creator passes the gate (library renders 200 for the logged-in owner). h.client.post_form("/logout", "").await; h.login("gatecreator", "password123").await; let r = h.client.get("/library").await; assert_eq!(r.status.as_u16(), 200, "creator should pass the gate: {} {}", r.status, r.text); } #[tokio::test] async fn access_gate_open_serves_public_site() { // Default (Open) — the public landing renders for an anonymous visitor, // proving the gate is off unless explicitly enabled. let mut h = TestHarness::new().await; let r = h.client.get("/").await; assert_eq!(r.status.as_u16(), 200, "open site should serve landing to anon: {}", r.status); }