The API key comes from exactly one place: TYPESAFE_API_KEY in the
server's environment, or the superuser-only jev.api_key_file. Both
set is an error, not a precedence rule that silently picks one.
8const SELECT: &str = "SELECT jev_prob(t, 'Urgent?') FROM tickets t"; 9 10fn key_file(contents: &str) -> tempfile::NamedTempFile { 11 let file = tempfile::NamedTempFile::new().unwrap(); 12 std::fs::write(file.path(), contents).unwrap(); 13 file 14} 15 16#[tokio::test(flavor = "multi_thread")] 17async fn the_key_file_is_used() { 18 let mock = MockJev::start(|_| Reply::json(200, noul(0.95))).await; 19 let file = key_file("file-key\n"); 20 let path = file.path().to_str().unwrap(); 21 let (_pg, client) = jev_instance_env(&mock, &[("jev.api_key_file", path)], &[]).await; 22 23 client.query_one(SELECT, &[]).await.expect("answered"); 24 assert_eq!(mock.requests()[0].headers["authorization"], "Bearer file-key"); 25} 26 27#[tokio::test(flavor = "multi_thread")] 28async fn two_sources_is_an_error() { 29 let mock = MockJev::start(|_| Reply::json(200, noul(0.95))).await; 30 let file = key_file("file-key"); 31 let path = file.path().to_str().unwrap(); 32 let (_pg, client) = 33 jev_instance_env(&mock, &[("jev.api_key_file", path)], &[("TYPESAFE_API_KEY", "env-key")]).await; 34 35 let err = client.query_one(SELECT, &[]).await.expect_err("ambiguous"); 36 let message = err.as_db_error().unwrap().message().to_string(); 37 assert!(message.contains("TYPESAFE_API_KEY") && message.contains("jev.api_key_file"), "{message}"); 38 assert!(!message.contains("file-key") && !message.contains("env-key"), "the key never appears: {message}"); 39 assert!(mock.requests().is_empty()); 40} 41 42#[tokio::test(flavor = "multi_thread")] 43async fn no_source_is_an_error() { 44 let mock = MockJev::start(|_| Reply::json(200, noul(0.95))).await; 45 let (_pg, client) = jev_instance_env(&mock, &[], &[]).await; 46 47 let err = client.query_one(SELECT, &[]).await.expect_err("no key"); 48 assert_eq!(err.as_db_error().unwrap().code(), &tokio_postgres::error::SqlState::INVALID_AUTHORIZATION_SPECIFICATION); 49 let message = err.as_db_error().unwrap().message().to_string(); 50 assert!(message.contains("TYPESAFE_API_KEY") && message.contains("jev.api_key_file"), "{message}"); 51} 52 53#[tokio::test(flavor = "multi_thread")] 54async fn only_a_superuser_sets_the_key_file() { 55 let mock = MockJev::start(|_| Reply::json(200, noul(0.95))).await; 56 let (_pg, client) = jev_instance_env(&mock, &[], &[("TYPESAFE_API_KEY", "k")]).await; 57 client 58 .batch_execute("CREATE ROLE app LOGIN; SET ROLE app;") 59 .await 60 .unwrap(); 61 let err = client.batch_execute("SET jev.api_key_file = '/etc/passwd'").await.expect_err("refused"); 62 assert_eq!(err.as_db_error().unwrap().code(), &tokio_postgres::error::SqlState::INSUFFICIENT_PRIVILEGE); 63}