//! Sandbox workflow: ephemeral account creation, feature restrictions, visibility rules. use crate::harness::TestHarness; use makenotwork::constants::SANDBOX_MAX_PER_IP; /// Helper: create a sandbox account via POST /sandbox. /// The client must have a CSRF token (fetched from GET /sandbox). /// Returns the response from POST /sandbox (should be a 302 redirect). async fn create_sandbox(h: &mut TestHarness) -> crate::harness::client::TestResponse { // GET /sandbox to establish session + extract CSRF token let resp = h.client.get("/sandbox").await; assert!( resp.status.is_success(), "GET /sandbox failed: {} {}", resp.status, resp.text ); // POST /sandbox to create the account (new session, CSRF regenerated) let resp = h.client.post_form("/sandbox", "").await; // Fetch a page to pick up the new CSRF token for the fresh session let _ = h.client.get("/library").await; resp } /// Look up the sandbox username from the DB (most recently created sandbox_ user). async fn sandbox_username(h: &TestHarness) -> String { sqlx::query_scalar::<_, String>( "SELECT username FROM users WHERE username LIKE 'sandbox_%' ORDER BY created_at DESC LIMIT 1", ) .fetch_one(&h.db) .await .expect("No sandbox user found") } #[tokio::test] async fn create_sandbox_account() { let mut h = TestHarness::new().await; let resp = create_sandbox(&mut h).await; assert!( resp.status.is_redirection(), "POST /sandbox should redirect, got {}", resp.status ); // Should be able to access dashboard as the sandbox user let resp = h.client.get("/dashboard").await; assert_eq!( resp.status, 200, "Dashboard should be accessible after sandbox creation" ); } #[tokio::test] async fn sandbox_blocks_restricted_endpoints() { let mut h = TestHarness::new().await; create_sandbox(&mut h).await; // Custom domains let resp = h.client.post_form("/api/domains", "domain=sandbox.example.com").await; assert_eq!(resp.status, 403, "Sandbox: POST /api/domains should be 403, got {}", resp.status); // Git repos let resp = h.client.post_json("/api/repos", r#"{"name": "test-repo"}"#).await; assert_eq!(resp.status, 403, "Sandbox: POST /api/repos should be 403, got {}", resp.status); // Imports let resp = h.client.post_json( "/api/users/me/import", r#"{"project_id": "00000000-0000-0000-0000-000000000000", "source": "generic_csv", "csv_data": "ZW1haWwKdGVzdEB0ZXN0LmNvbQo=", "column_mapping": {"email": 0}}"#, ).await; assert_eq!(resp.status, 403, "Sandbox: POST /api/users/me/import should be 403, got {}", resp.status); // Guest purchase claim let resp = h.client.post_json( "/api/purchases/claim", r#"{"claim_token": "00000000-0000-0000-0000-000000000000"}"#, ).await; assert_eq!(resp.status, 403, "Sandbox: POST /api/purchases/claim should be 403, got {}", resp.status); } #[tokio::test] async fn sandbox_content_not_visible_on_item_page() { let mut h = TestHarness::new().await; create_sandbox(&mut h).await; // Create a project let resp = h .client .post_form("/api/projects", "slug=sandbox-proj&title=Sandbox+Project") .await; assert!( resp.status.is_success(), "Create project failed: {} {}", resp.status, resp.text ); let project: serde_json::Value = resp.json(); let project_id = project["id"].as_str().unwrap(); // Publish project h.client .put_json( &format!("/api/projects/{}", project_id), r#"{"is_public": true}"#, ) .await; // Create an item let resp = h .client .post_form( &format!("/api/projects/{}/items", project_id), "title=Sandbox+Item&item_type=digital&price_cents=0", ) .await; assert!( resp.status.is_success(), "Create item failed: {} {}", resp.status, resp.text ); let item: serde_json::Value = resp.json(); let item_id = item["id"].as_str().unwrap(); // Publish the item h.client .put_form(&format!("/api/items/{}", item_id), "is_public=true") .await; // Use a second harness (unauthenticated client) to visit the item page let mut h2 = TestHarness::new().await; let resp = h2.client.get(&format!("/i/{}", item_id)).await; assert_eq!( resp.status, 404, "Sandbox item should return 404 to unauthenticated visitor, got {}", resp.status ); } #[tokio::test] async fn sandbox_rss_returns_404() { let mut h = TestHarness::new().await; create_sandbox(&mut h).await; let username = sandbox_username(&h).await; let resp = h.client.get(&format!("/u/{}/rss", username)).await; assert_eq!( resp.status, 404, "Sandbox user RSS feed should return 404, got {}", resp.status ); } #[tokio::test] #[cfg_attr(not(feature = "fast-tests"), ignore)] async fn sandbox_per_ip_cap() { let mut h = TestHarness::new().await; // Create SANDBOX_MAX_PER_IP sandboxes without logging out. // The cap counts concurrent active sessions per IP — logout deletes // the session row, which would defeat the count. for i in 0..SANDBOX_MAX_PER_IP { let resp = create_sandbox(&mut h).await; assert!( resp.status.is_redirection(), "Sandbox {} should succeed, got {}", i + 1, resp.status ); } // Try one more — should be rejected (cap reached) let resp = h.client.get("/sandbox").await; assert!(resp.status.is_success()); let resp = h.client.post_form("/sandbox", "").await; assert_eq!( resp.status, 400, "Sandbox beyond per-IP cap should return 400, got {}", resp.status ); } #[tokio::test] async fn sandbox_blog_no_email() { let mut h = TestHarness::with_mocks().await; create_sandbox(&mut h).await; // Clear any emails from sandbox creation h.mock_email.as_ref().unwrap().clear(); // Create a project let resp = h .client .post_form("/api/projects", "slug=sb-blog&title=Sandbox+Blog") .await; assert!( resp.status.is_success(), "Create project failed: {} {}", resp.status, resp.text ); let project: serde_json::Value = resp.json(); let project_id = project["id"].as_str().unwrap(); // Publish project h.client .put_json( &format!("/api/projects/{}", project_id), r#"{"is_public": true}"#, ) .await; // Create and immediately publish a blog post let resp = h .client .post_json( &format!("/api/projects/{}/blog", project_id), r#"{"title": "Sandbox Post", "body_markdown": "Hello from sandbox!", "is_published": true}"#, ) .await; assert!( resp.status.is_success(), "Create blog post failed: {} {}", resp.status, resp.text ); // No emails should have been sent (sandbox skips announcements) let count = h.mock_email.as_ref().unwrap().count(); assert_eq!( count, 0, "Sandbox blog publish should send 0 emails, got {}", count ); }