Skip to main content

max / makenotwork

3.2 KB · 108 lines History Blame Raw
1 //! Site access gate (`ACCESS_GATE=fan_plus_or_creator`): the testnot-style
2 //! gate that restricts the whole site to creators and Fan+ members.
3
4 use crate::harness::{BuildOptions, TestHarness};
5 use makenotwork::config::AccessGate;
6 use sqlx::PgPool;
7
8 fn location(resp: &crate::harness::client::TestResponse) -> String {
9 resp.headers
10 .get("location")
11 .and_then(|v| v.to_str().ok())
12 .unwrap_or("")
13 .to_string()
14 }
15
16 /// Insert a verified user directly (the HTTP signup flow is itself gated on
17 /// testnot, so the test seeds the DB and authenticates via the exempt /login).
18 async fn seed_user(pool: &PgPool, username: &str, can_create_projects: bool) {
19 let hash = makenotwork::auth::hash_password("password123").expect("hash");
20 sqlx::query(
21 "INSERT INTO users (username, email, password_hash, email_verified, can_create_projects)
22 VALUES ($1, $2, $3, true, $4)",
23 )
24 .bind(username)
25 .bind(format!("{username}@example.com"))
26 .bind(&hash)
27 .bind(can_create_projects)
28 .execute(pool)
29 .await
30 .expect("seed user");
31 }
32
33 #[tokio::test]
34 async fn access_gate_restricts_to_fan_plus_or_creator() {
35 let mut h = TestHarness::build(BuildOptions {
36 access_gate: AccessGate::FanPlusOrCreator,
37 ..Default::default()
38 })
39 .await;
40 seed_user(&h.db, "gatecreator", true).await;
41 seed_user(&h.db, "plainfan", false).await;
42
43 // Anonymous visitor → bounced to login with the gate notice.
44 let r = h.client.get("/").await;
45 assert!(
46 r.status.is_redirection(),
47 "anon should be redirected, got {}",
48 r.status
49 );
50 assert!(
51 location(&r).starts_with("/login"),
52 "anon should land on login, got {}",
53 location(&r)
54 );
55
56 // Exempt paths stay reachable while the gate is on, or login is impossible.
57 assert_eq!(
58 h.client.get("/login").await.status.as_u16(),
59 200,
60 "login page must be reachable"
61 );
62 assert_eq!(
63 h.client.get("/health").await.status.as_u16(),
64 200,
65 "health must be reachable"
66 );
67
68 // A logged-in plain fan (no creator, no Fan+) is still blocked, the gate
69 // is stricter than ordinary auth.
70 h.login("plainfan", "password123").await;
71 let r = h.client.get("/library").await;
72 assert!(
73 r.status.is_redirection(),
74 "plain fan should be gated, got {}",
75 r.status
76 );
77 assert!(
78 location(&r).starts_with("/login"),
79 "plain fan should land on login"
80 );
81
82 // A creator passes the gate (library renders 200 for the logged-in owner).
83 h.client.post_form("/logout", "").await;
84 h.login("gatecreator", "password123").await;
85 let r = h.client.get("/library").await;
86 assert_eq!(
87 r.status.as_u16(),
88 200,
89 "creator should pass the gate: {} {}",
90 r.status,
91 r.text
92 );
93 }
94
95 #[tokio::test]
96 async fn access_gate_open_serves_public_site() {
97 // Default (Open), the public landing renders for an anonymous visitor,
98 // proving the gate is off unless explicitly enabled.
99 let mut h = TestHarness::new().await;
100 let r = h.client.get("/").await;
101 assert_eq!(
102 r.status.as_u16(),
103 200,
104 "open site should serve landing to anon: {}",
105 r.status
106 );
107 }
108