mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-08 03:23:24 +00:00
Consent expiry is now per kind of data: renewing text no longer renews an expired image permission. A connection edit keeps models found by a test at the new address, and drops only the ones saved for the old one.
109 lines
3.9 KiB
Rust
109 lines
3.9 KiB
Rust
//! u1: the rules for a custom (unlisted, OpenAI-shaped) connection: a named key header, and what
|
|
//! a stored file may carry. The form is `ConnectionDraft`; these tests go through its public API.
|
|
|
|
use craft_ai_core::connection::ConnectionError;
|
|
use craft_ai_core::{Billing, Connection, ConnectionDraft, Field, KeyChange, Locality};
|
|
|
|
fn custom(endpoint: &str, header: &str) -> ConnectionDraft {
|
|
let mut d = ConnectionDraft::custom();
|
|
d.label = "Studio gateway".to_owned();
|
|
d.endpoint = endpoint.to_owned();
|
|
d.key = "k-123".to_owned();
|
|
d.key_header = header.to_owned();
|
|
d
|
|
}
|
|
|
|
#[test]
|
|
fn the_custom_form_starts_from_the_other_preset_with_billing_unknown() {
|
|
let d = ConnectionDraft::custom();
|
|
assert!(d.is_custom());
|
|
assert_eq!(d.provider, "openai-compatible");
|
|
assert_eq!(d.billing, Billing::Unknown);
|
|
assert!(d.uses_key);
|
|
assert!(d.key_header.is_empty());
|
|
assert!(d.models.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn without_a_header_name_the_key_goes_as_a_bearer_token() {
|
|
let p = custom("https://gw.example.com/ai/v7", "").prepare(&[]).unwrap();
|
|
assert_eq!(p.connection.key_header, None);
|
|
let json = serde_json::to_string(&p.connection).unwrap();
|
|
assert!(!json.contains("key_header"), "{json}");
|
|
}
|
|
|
|
#[test]
|
|
fn a_named_header_is_kept_saved_and_edited_back() {
|
|
let p = custom("https://gw.example.com/ai/v7", " X-Api-Key ").prepare(&[]).unwrap();
|
|
assert_eq!(p.connection.key_header.as_deref(), Some("X-Api-Key"));
|
|
assert_eq!(p.connection.endpoint.as_deref(), Some("https://gw.example.com/ai/v7"));
|
|
assert_eq!(p.connection.locality, Locality::Remote);
|
|
assert!(matches!(p.key, KeyChange::Set(_)));
|
|
|
|
let again: Connection = serde_json::from_str(&serde_json::to_string(&p.connection).unwrap()).unwrap();
|
|
assert_eq!(again, p.connection);
|
|
let edit = ConnectionDraft::from_connection(&again);
|
|
assert_eq!(edit.key_header, "X-Api-Key");
|
|
let kept = edit.prepare(std::slice::from_ref(&again)).unwrap();
|
|
assert_eq!(kept.connection.key_header.as_deref(), Some("X-Api-Key"));
|
|
assert!(matches!(kept.key, KeyChange::Keep));
|
|
}
|
|
|
|
#[test]
|
|
fn header_names_that_could_break_the_request_are_refused() {
|
|
let long = "a".repeat(65);
|
|
for bad in [
|
|
"host",
|
|
"Content-Length",
|
|
"content-type",
|
|
"transfer-encoding",
|
|
"connection",
|
|
"cookie",
|
|
"proxy-authorization",
|
|
"proxy-anything",
|
|
"x api key",
|
|
"x:api",
|
|
"x-api\r\nhost",
|
|
"x-clé",
|
|
"authorization",
|
|
"Authorization",
|
|
long.as_str(),
|
|
] {
|
|
let e = custom("https://gw.example.com/v1", bad).prepare(&[]).expect_err(bad);
|
|
assert_eq!(e.field, Field::KeyHeader, "{bad:?}");
|
|
assert!(!e.message.contains("k-123"), "{bad:?}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn ordinary_key_header_names_are_accepted() {
|
|
for ok in ["x-api-key", "api-key", "X-Goog-Api-Key", "apikey", "x_token"] {
|
|
custom("https://gw.example.com/v1", ok).prepare(&[]).unwrap_or_else(|e| panic!("{ok}: {e}"));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_header_name_without_a_key_is_refused() {
|
|
let mut d = custom("http://127.0.0.1:9000/v1", "x-api-key");
|
|
d.uses_key = false;
|
|
d.key.clear();
|
|
assert_eq!(d.prepare(&[]).unwrap_err().field, Field::KeyHeader);
|
|
}
|
|
|
|
#[test]
|
|
fn a_stored_file_with_a_bad_header_name_is_refused_on_validate() {
|
|
let mut c = custom("https://gw.example.com/v1", "x-api-key").prepare(&[]).unwrap().connection;
|
|
assert!(c.validate().is_ok());
|
|
c.key_header = Some("host".to_owned());
|
|
assert!(matches!(c.validate(), Err(ConnectionError::KeyHeader(_))));
|
|
c.key_header = Some("x-api-key".to_owned());
|
|
c.auth = craft_ai_core::Auth::None;
|
|
assert!(matches!(c.validate(), Err(ConnectionError::KeyHeader(_))));
|
|
}
|
|
|
|
#[test]
|
|
fn plain_http_to_a_remote_host_is_still_refused_for_a_custom_address() {
|
|
let e = custom("http://gw.example.com/v1", "x-api-key").prepare(&[]).unwrap_err();
|
|
assert_eq!(e.field, Field::Address);
|
|
}
|