use super::*; #[test] fn concurrent_enqueues_preserve_system_and_revalidated_user_identity() { let directory = Directory::new(); let mut project = Project::load(&fixture().with_file_name("auth")).unwrap(); for source in &mut project.sources { let text = if source.path.ends_with("app/jobs.dever") { include_str!("../../../fixtures/job-tasks.dever").into() } else if source.path.ends_with("api/admin/jobs.dever") { source .text .replace("post job_submit =", "long post job_submit =") .replace("cmd job_submit", "long cmd job_submit") } else { continue; }; *source = dever::syntax::source::Source::new(source.id, source.path.clone(), text); } let program = project.check().unwrap(); let binary = build::build( &program, &project.sources, "main", &directory.0.join("tasks"), ) .unwrap(); let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let port = listener.local_addr().unwrap().port(); drop(listener); std::fs::create_dir(directory.0.join("config")).unwrap(); let mut settings = serde_json::json!({ "http":{"host":"127.0.0.1","port":port}, "sites":{"admin":{"path":"admin","auth":"session","hosts":["fixture.test"],"origin":"https://fixture.test"}}, "database":{"default":{"type":"sqlite","path":"data/account.db","max_connections":8}}, "runtime":{"mode":"api","shutdown_ms":1000}, "auth":{"providers":{"session":{"verify":"demo.account.verify","jwtSecret":KEY,"ttlSeconds":60,"cookie":"session"}}}, "job":{"workers":1,"poll_ms":10,"lease_ms":2000} }); std::fs::write( directory.0.join("config/setting.json"), settings.to_string(), ) .unwrap(); let run = |command: &str, input: &str| { let result = execute(&binary, command, input); assert!( result.status.success(), "{}", String::from_utf8_lossy(&result.stderr) ); serde_json::from_slice::(&result.stdout).unwrap()["data"].clone() }; run( "demo.account.seed", r#"{"subject":"user","session":"valid-session"}"#, ); run("dever.auth.owner", r#"{"site":"admin","user_id":1}"#); let mut server = Server(Command::new(&binary).spawn().unwrap()); let deadline = Instant::now() + Duration::from_secs(5); while TcpStream::connect(("127.0.0.1", port)).is_err() { assert!(server.0.try_wait().unwrap().is_none()); assert!(Instant::now() < deadline); std::thread::sleep(Duration::from_millis(10)); } let bearer = format!( "Authorization: Bearer {}\r\nContent-Type: application/json\r\n", token("user", "valid-session") ); let forged = request( port, "POST", "/demo/account/admin/jobs/job_submit", "fixture.test", &bearer, r#"{"subject":"forged","key":"forged","execution":{"kind":"System"}}"#, ); assert_eq!(forged.0, 400, "{}", forged.1); let submit = |subject: &str| { let (status, body) = request( port, "POST", "/demo/account/admin/jobs/job_submit", "fixture.test", &bearer, &serde_json::json!({"subject":subject,"key":subject}).to_string(), ); assert_eq!(status, 200, "{body}"); }; // payload 的 subject 与登录用户不同,不能用它替换请求建立的可信执行者。 submit("other-person"); run( "demo.account.job_submit", r#"{"subject":"system","key":"system"}"#, ); settings["runtime"]["mode"] = "worker".into(); let worker_config = directory.0.join("worker.json"); std::fs::write(&worker_config, settings.to_string()).unwrap(); let start = || { Server( Command::new(&binary) .arg("--config") .arg(&worker_config) .spawn() .unwrap(), ) }; let until = |worker: &mut Server, expected: i64| { let deadline = Instant::now() + Duration::from_secs(10); loop { let actual = run("demo.account.job_count", "{}"); if actual == expected { break; } assert!(worker.0.try_wait().unwrap().is_none()); assert!( Instant::now() < deadline, "concurrent Job did not complete: expected {expected}, got {actual}" ); std::thread::sleep(Duration::from_millis(10)); } }; let mut worker = start(); until(&mut worker, 13); assert!( Command::new("sh") .args(["-c", "kill -TERM \"$1\"", "--", &worker.0.id().to_string()]) .status() .unwrap() .success() ); assert!(worker.0.wait().unwrap().success()); submit("revoked"); run("demo.account.revoke", r#"{"id":1}"#); run( "demo.account.job_submit", r#"{"subject":"system-next","key":"system-next"}"#, ); let mut worker = start(); until(&mut worker, 19); std::thread::sleep(Duration::from_millis(100)); assert_eq!( run("demo.account.job_count", "{}"), 19, "revoked User was upgraded to System" ); }