postjevsql.git / tests / threshold.rs
1//! `jev(row, q [, threshold])` (contract *SQL surface*): true when the
2//! Noul probability is at least the threshold, taken from the argument,
3//! then `jev.threshold`, then 0.5. It is evaluated by the jev scan and
4//! shares its judgments, and the cache, with `jev_prob`.
5
6use serde_json::Value;
7use tokio_postgres::error::SqlState;
8use support::mock_jev::{MockJev, Reply};
9use support::{jev_instance, noul};
10
11/// Answers 0.3, 0.6, 0.9 and 0.5 about tickets 1 to 4.
12async fn graded() -> MockJev {
13    MockJev::start(|req| {
14        let body: Value = serde_json::from_slice(&req.body).unwrap();
15        let p = match body["state"]["id"].as_i64() {
16            Some(1) => 0.3,
17            Some(2) => 0.6,
18            Some(3) => 0.9,
19            _ => 0.5,
20        };
21        Reply::json(200, noul(p))
22    })
23    .await
24}
25
26async fn ids(client: &tokio_postgres::Client, sql: &str) -> Vec<i32> {
27    client.query(sql, &[]).await.unwrap().iter().map(|r| r.get(0)).collect()
28}
29
30#[tokio::test(flavor = "multi_thread")]
31async fn argument_then_setting_then_half() {
32    let mock = graded().await;
33    let (_pg, client) = jev_instance(&mock).await;
34    client
35        .batch_execute("INSERT INTO tickets VALUES (2, 'b'), (3, 'c'), (4, 'd')")
36        .await
37        .unwrap();
38    let urgent = "SELECT id FROM tickets t WHERE jev(t, 'Urgent?') ORDER BY id";
39
40    // Neither: 0.5, and a probability equal to it passes.
41    assert_eq!(ids(&client, urgent).await, [2, 3, 4]);
42
43    // The setting.
44    client.batch_execute("SET jev.threshold = 0.7").await.unwrap();
45    assert_eq!(ids(&client, urgent).await, [3]);
46
47    // The argument, over the setting, both lower and higher.
48    assert_eq!(ids(&client, "SELECT id FROM tickets t WHERE jev(t, 'Urgent?', 0.2) ORDER BY id").await, [1, 2, 3, 4]);
49    assert_eq!(ids(&client, "SELECT id FROM tickets t WHERE jev(t, 'Urgent?', 0.95) ORDER BY id").await, [] as [i32; 0]);
50
51    // RESET falls back to 0.5.
52    client.batch_execute("RESET jev.threshold").await.unwrap();
53    assert_eq!(ids(&client, urgent).await, [2, 3, 4]);
54
55    // In the select list, beside jev_prob, one judgment per row.
56    let rows = client
57        .query("SELECT id, jev(t, 'Urgent?', 0.55), jev_prob(t, 'Urgent?') FROM tickets t ORDER BY id", &[])
58        .await
59        .unwrap();
60    let got: Vec<(i32, bool, f64)> = rows.iter().map(|r| (r.get(0), r.get(1), r.get(2))).collect();
61    assert_eq!(got, [(1, false, 0.3), (2, true, 0.6), (3, true, 0.9), (4, false, 0.5)]);
62
63    // Every statement after the first was answered from the cache: the
64    // threshold is applied to the stored probability, not asked.
65    assert_eq!(mock.requests().len(), 4);
66}
67
68#[tokio::test(flavor = "multi_thread")]
69async fn thresholds_outside_zero_to_one_are_refused() {
70    let mock = graded().await;
71    let (_pg, client) = jev_instance(&mock).await;
72
73    let err = client.batch_execute("SET jev.threshold = 1.5").await.expect_err("refused");
74    assert_eq!(err.as_db_error().expect("an ERROR").code(), &SqlState::INVALID_PARAMETER_VALUE);
75
76    let err = client
77        .query("SELECT id FROM tickets t WHERE jev(t, 'Urgent?', 2)", &[])
78        .await
79        .expect_err("refused");
80    assert_eq!(err.as_db_error().expect("an ERROR").code(), &SqlState::INVALID_PARAMETER_VALUE);
81    assert!(mock.requests().is_empty(), "refused before sending");
82}