#![allow(clippy::unwrap_used)]
use std::collections::HashMap;
use fraiseql_core::schema::SourceDefinition;
use fraiseql_functions::{FunctionModule, RuntimeType};
use super::{schedulable, source_host_config_from, sources_enabled_from};
use crate::server_config::SourcesConfig;
fn module(name: &str) -> FunctionModule {
FunctionModule::from_source(name.to_string(), String::new(), RuntimeType::Deno)
}
fn registry() -> HashMap<String, FunctionModule> {
HashMap::from([("pollOrders".to_string(), module("pollOrders"))])
}
#[test]
fn schedulable_keeps_every_enabled_source_and_skips_a_disabled_one() {
let sources = vec![
SourceDefinition::new("orders", "*/5 * * * *", "pollOrders"),
SourceDefinition::new("disabled", "*/5 * * * *", "unloaded").disabled(),
];
let kept = schedulable(&sources, ®istry()).unwrap();
let names: Vec<&str> = kept.iter().map(|(source, _, _)| source.name.as_str()).collect();
assert_eq!(names, ["orders"]);
assert_eq!(kept[0].2.expression, "*/5 * * * *");
}
#[test]
fn an_enabled_source_with_no_loaded_connector_is_an_error() {
let sources = vec![SourceDefinition::new(
"invoices",
"*/5 * * * *",
"pollInvoices",
)];
let message = schedulable(&sources, ®istry()).err().unwrap().to_string();
assert!(
message.contains("\"invoices\"") && message.contains("\"pollInvoices\""),
"{message}"
);
}
#[test]
fn an_enabled_source_with_an_invalid_schedule_is_an_error() {
let sources = vec![SourceDefinition::new("orders", "61 * * * *", "pollOrders")];
let message = schedulable(&sources, ®istry()).err().unwrap().to_string();
assert!(message.contains("\"orders\"") && message.contains("61 * * * *"), "{message}");
}
#[test]
fn enabled_resolves_env_over_config() {
let on = SourcesConfig {
enabled: true,
..SourcesConfig::default()
};
let off = SourcesConfig {
enabled: false,
..SourcesConfig::default()
};
assert!(sources_enabled_from(&on, |_| None));
assert!(!sources_enabled_from(&off, |_| None));
assert!(!sources_enabled_from(&on, |_| Some("false".to_string())));
assert!(!sources_enabled_from(&on, |_| Some("OFF".to_string())));
assert!(sources_enabled_from(&off, |_| Some("true".to_string())));
assert!(sources_enabled_from(&off, |_| Some("1".to_string())));
}
#[test]
fn host_config_allowlist_resolves_env_over_config() {
let config = SourcesConfig {
enabled: true,
allowed_domains: vec!["from-toml.example".to_string()],
..SourcesConfig::default()
};
let host = source_host_config_from(&config, |_| None);
assert_eq!(host.allowed_domains, vec!["from-toml.example".to_string()]);
let host = source_host_config_from(&config, |key| {
(key == "FRAISEQL_SOURCES_ALLOWED_DOMAINS").then(|| " a.example, b.example ".to_string())
});
assert_eq!(host.allowed_domains, vec!["a.example".to_string(), "b.example".to_string()]);
let empty = SourcesConfig::default();
assert!(source_host_config_from(&empty, |_| None).allowed_domains.is_empty());
}
#[test]
fn host_config_env_var_allowlist_resolves_env_over_config() {
let config = SourcesConfig {
enabled: true,
allowed_env_vars: vec!["QONTO_API_KEY".to_string()],
..SourcesConfig::default()
};
let host = source_host_config_from(&config, |_| None);
assert!(host.allowed_env_vars.contains("QONTO_API_KEY"));
let host = source_host_config_from(&config, |key| {
(key == "FRAISEQL_SOURCES_ALLOWED_ENV_VARS")
.then(|| " LLM_API_KEY, MAIL_API_KEY ".to_string())
});
assert!(host.allowed_env_vars.contains("LLM_API_KEY"));
assert!(host.allowed_env_vars.contains("MAIL_API_KEY"));
assert!(!host.allowed_env_vars.contains("QONTO_API_KEY"), "env replaces, not merges");
assert!(
source_host_config_from(&SourcesConfig::default(), |_| None)
.allowed_env_vars
.is_empty()
);
}
#[test]
fn a_declared_cursor_override_is_the_key_the_poller_advances() {
let declared = SourceDefinition {
name: "orders_v2".to_string(),
schedule: "*/5 * * * *".to_string(),
cursor: Some("orders".to_string()),
function: "connector".to_string(),
enabled: true,
options: serde_json::Value::Null,
run_as: None,
};
assert_eq!(
declared.cursor_name(),
"orders",
"cursor_name() must prefer the declared override"
);
let no_override = SourceDefinition {
cursor: None,
..declared
};
assert_eq!(
no_override.cursor_name(),
"orders_v2",
"with no override the cursor key falls back to the source name"
);
}