use rusqlite::params;
use serde_json::Value;
use super::jev_tests::{bodies, probs, reply, run_pipeline_cfg, server_with, RULES};
use crate::core::config::{Config, LlmConfig};
use crate::core::db::Database;
const MESSAGE: &str = "zzz qqq vvv";
const PATHS: [&str; 4] = [
"Dockerfile",
"build",
"infra/acme-fin/deploy.yaml",
"services/paygate-bridge/src/settle_core.rs",
];
const PATH_PARTS: [&str; 9] = [
"Dockerfile",
"build",
"infra",
"acme-fin",
"deploy",
"services",
"paygate-bridge",
"src/",
"settle_core",
];
const PR_TITLE: &str = "Settle fix for Qirin Vossberg on ledger-core";
fn seed(db: &Database) {
let conn = db.connection();
conn.execute(
"INSERT INTO commits (sha, author_name, author_email, timestamp, message, repository) \
VALUES ('sha-ctx', 'Qirin Vossberg', 'qv@corp.test', '2024-01-01T00:00:00Z', ?1, \
'ledger-core')",
params![MESSAGE],
)
.expect("insert commit");
let id: i64 = conn
.query_row("SELECT id FROM commits WHERE sha = 'sha-ctx'", [], |r| {
r.get(0)
})
.expect("id");
for p in PATHS {
conn.execute(
"INSERT INTO files (commit_id, path, change_type) VALUES (?1, ?2, 'M')",
params![id, p],
)
.expect("insert file");
}
conn.execute(
"INSERT INTO pull_requests (pr_number, title, author, state, created_at, commit_shas) \
VALUES (3, ?1, 'qv', 'merged', '2024-01-02', '[\"sha-ctx\"]')",
params![PR_TITLE],
)
.expect("insert pr");
conn.execute(
"INSERT INTO work_items (id, source, title, status, item_type) \
VALUES ('W-9', 'jira', 'Settle drops', 'Done', 'Bug')",
[],
)
.expect("insert work item");
conn.execute(
"INSERT INTO commit_work_items (commit_sha, work_item_id, work_item_source) \
VALUES ('sha-ctx', 'W-9', 'jira')",
[],
)
.expect("link");
}
fn llm_from(yaml: &str) -> impl FnOnce(&mut Config) + '_ {
move |c: &mut Config| {
let llm: LlmConfig =
serde_yaml::from_str(yaml).unwrap_or_else(|e| panic!("llm section {yaml:?}: {e}"));
c.llm = Some(llm);
}
}
fn sent_message(body: &Value) -> String {
body["state"]["commit"]["message"]
.as_str()
.expect("message")
.to_string()
}
#[tokio::test]
async fn obfuscated_jev_context_sends_no_raw_path() {
let server = server_with(reply("CAT_1", probs("CAT_1"), 150)).await;
let yaml = "source: jev\ncontext: [paths, pr_title, issue_type]\n\
jev:\n obfuscate: true\n";
let (stats, _db) = run_pipeline_cfg(&server, &[], RULES, seed, llm_from(yaml)).await;
assert_eq!(stats.llm_usage.calls, 1);
let sent = bodies(&server).await;
assert_eq!(sent.len(), 1);
let message = sent_message(&sent[0]);
assert!(message.starts_with(MESSAGE), "{message}");
assert!(message.contains("--- commit context"), "{message}");
assert!(message.contains("Changed paths:\n- PATH_"), "{message}");
assert!(message.contains("PR title: "), "{message}");
assert!(message.contains("Issue type: Bug"), "{message}");
assert_eq!(
message.matches("\n- PATH_").count(),
PATHS.len(),
"{message}"
);
for raw in PATH_PARTS
.iter()
.chain(&["Qirin", "Vossberg", "ledger-core"])
{
assert!(!message.contains(raw), "{raw} leaked: {message}");
}
}
#[tokio::test]
async fn real_text_jev_context_is_sent_as_stored() {
let server = server_with(reply("CAT_1", probs("CAT_1"), 150)).await;
let yaml = "source: jev\ncontext: [paths, pr_title, issue_type]\n";
run_pipeline_cfg(&server, &[], RULES, seed, llm_from(yaml)).await;
let sent = bodies(&server).await;
assert_eq!(sent.len(), 1);
let expected = format!(
"{MESSAGE}\n\n--- commit context (from the repository, not the commit message) ---\n\
Changed paths:\n- Dockerfile\n- build\n- infra/acme-fin/deploy.yaml\n\
- services/paygate-bridge/src/settle_core.rs\nPR title: {PR_TITLE}\nIssue type: Bug\n\
--- end commit context ---"
);
assert_eq!(sent_message(&sent[0]), expected);
}
#[tokio::test]
async fn context_changes_the_jev_payload_key() {
let server = server_with(reply("CAT_1", probs("CAT_1"), 150)).await;
let dir = tempfile::tempdir().expect("tempdir");
let mut names = Vec::new();
for (sub, context) in [("bare", "[]"), ("ctx", "[paths]")] {
let out = dir.path().join(sub);
let yaml = format!(
"source: jev\ncontext: {context}\njev:\n payload_dump_dir: {}\n",
out.display()
);
run_pipeline_cfg(&server, &[], RULES, seed, llm_from(&yaml)).await;
let bodies: Vec<_> = std::fs::read_dir(&out)
.expect("dump dir")
.map(|e| e.expect("entry").path())
.filter(|p| !p.to_string_lossy().ends_with(".tokens.json"))
.collect();
assert_eq!(bodies.len(), 1, "{sub}: {bodies:?}");
let body: Value =
serde_json::from_slice(&std::fs::read(&bodies[0]).expect("read")).expect("json");
let has_block = sent_message(&body).contains("--- commit context");
assert_eq!(has_block, sub == "ctx", "{sub}");
names.push(bodies[0].file_name().expect("name").to_owned());
}
assert_ne!(names[0], names[1], "context and bare bodies share a key");
assert!(
bodies(&server).await.is_empty(),
"a dump run sent a request"
);
}