Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-simview/src/mock.rs

//! **An in-memory stand-in for the server side of the contract** — a population with
//! persons in municipalities and their records, Generate and Permutate as jobs that are
//! busy for a few polls and then done, and a log of every request it was asked. The
//! tests drive the app against it; the native window and the dev server serve it.
//!
//! The model is a SHAPE, not knut's generator: persons with a `kommun`, each owning a
//! handful of records of the population's other labels, and a permutation that moves
//! 10% of the persons to another `kommun` and stamps them with the new round. What the
//! viewer is tested on - counts, the lit round, the ego network - is all it needs.

use std::sync::{Arc, Mutex};

use crate::backend::Backend;
use crate::contract::{self, EdgeRow, Meta, NodeRow, Req, Resp};

/// Twenty municipalities - the 3D view clusters persons by these.
pub const KOMMUNER: [&str; 20] = [
    "Stockholm", "Göteborg", "Malmö", "Uppsala", "Linköping", "Örebro", "Västerås", "Helsingborg", "Norrköping",
    "Jönköping", "Umeå", "Lund", "Borås", "Huddinge", "Eskilstuna", "Nacka", "Gävle", "Södertälje", "Halmstad",
    "Karlstad",
];

/// The other labels a person owns one of (the shape of knut's model).
const RECORDS: [(&str, &str); 4] =
    [("Address", "BOR_PA"), ("TaxRecord", "DEKLARERAR"), ("Vehicle", "AGER_FORDON"), ("Loan", "HAR_LAN")];

/// The mock's whole state.
#[derive(Debug, Clone)]
pub struct MockServer {
    pub persons: usize,
    pub round: i64,
    nodes: Vec<NodeRow>,
    edges: Vec<EdgeRow>,
    /// A running job: (verb, polls left before it is done).
    job: Option<(&'static str, u32)>,
    last: Option<(String, bool, String)>,
    /// Polls a job stays busy for.
    pub job_polls: u32,
    /// Every request, in order - what a test asserts on.
    pub log: Vec<Req>,
}

impl MockServer {
    /// A generated population of `persons` persons, round 0.
    pub fn new(persons: usize) -> Self {
        let mut s =
            MockServer { persons, round: 0, nodes: Vec::new(), edges: Vec::new(), job: None, last: None, job_polls: 2, log: Vec::new() };
        s.generate();
        s
    }

    fn generate(&mut self) {
        self.round = 0;
        self.nodes.clear();
        self.edges.clear();
        // Persons take ids 0..persons, their records the ids after.
        for p in 0..self.persons {
            self.nodes.push(NodeRow {
                id: p as i64,
                label: "Person".into(),
                name: format!("person {p}"),
                kommun: Some(KOMMUNER[p % KOMMUNER.len()].into()),
                round: 0,
            });
        }
        let mut next = self.persons as i64;
        for p in 0..self.persons {
            for (label, etype) in RECORDS {
                self.nodes.push(NodeRow { id: next, label: label.into(), name: format!("{label} {next}"), kommun: None, round: 0 });
                self.edges.push(EdgeRow { src: p as i64, dst: next, etype: etype.into() });
                next += 1;
            }
        }
    }

    /// 10% of the persons move to the next municipality and carry the new round.
    fn permutate(&mut self) {
        self.round += 1;
        let r = self.round;
        let step = 10usize;
        let offset = (r as usize) % step;
        for p in (offset..self.persons).step_by(step) {
            let n = &mut self.nodes[p];
            let k = KOMMUNER.iter().position(|k| Some(*k) == n.kommun.as_deref()).unwrap_or(0);
            n.kommun = Some(KOMMUNER[(k + 1) % KOMMUNER.len()].into());
            n.round = r;
        }
    }

    /// Persons stamped with the latest round.
    pub fn changed_in_latest_round(&self) -> usize {
        self.nodes.iter().filter(|n| n.label == "Person" && n.round == self.round).count()
    }

    /// `GET /sim/api/nodes.arrow?labels=..&limit=..` (empty `labels` = every label).
    pub fn nodes_arrow(&self, labels: &[&str], limit: usize) -> Vec<u8> {
        let rows: Vec<NodeRow> =
            self.nodes.iter().filter(|n| labels.is_empty() || labels.contains(&n.label.as_str())).take(limit).cloned().collect();
        contract::encode_nodes(&rows)
    }

    /// `GET /sim/api/edges.arrow?types=..&limit=..` (empty `types` = every type).
    pub fn edges_arrow(&self, types: &[&str], limit: usize) -> Vec<u8> {
        let rows: Vec<EdgeRow> =
            self.edges.iter().filter(|e| types.is_empty() || types.contains(&e.etype.as_str())).take(limit).cloned().collect();
        contract::encode_edges(&rows)
    }

    fn power_json(&self) -> String {
        let last = self.last.as_ref().map(|(v, ok, d)| serde_json::json!({"verb": v, "ok": ok, "ago_s": 0, "detail": d}));
        let entry = match &self.job {
            Some((verb, _)) => serde_json::json!({"id": contract::TARGET, "busy": true, "verb": verb, "for_s": 1, "last": last}),
            None => serde_json::json!({"id": contract::TARGET, "busy": false, "last": last}),
        };
        serde_json::json!({"benches": [entry]}).to_string()
    }

    /// Answer one request, as the server would.
    pub fn answer(&mut self, req: &Req) -> (u16, Vec<u8>) {
        self.log.push(req.clone());
        match req {
            Req::Press(verb) => {
                if self.job.is_some() {
                    return (409, b"a job is already running for falkordb".to_vec());
                }
                self.job = Some((verb, self.job_polls));
                (202, serde_json::json!({"accepted": true, "id": contract::TARGET, "verb": verb}).to_string().into_bytes())
            }
            Req::Power => {
                if let Some((verb, left)) = self.job {
                    if left == 0 {
                        if verb == contract::VERB_PERMUTATE {
                            self.permutate();
                        } else {
                            self.generate();
                        }
                        self.job = None;
                        self.last = Some((verb.into(), true, format!("{verb} falkordb: done")));
                    } else {
                        self.job = Some((verb, left - 1));
                    }
                }
                (200, self.power_json().into_bytes())
            }
            Req::Meta => {
                let persons = self.persons as i64;
                let m = Meta { round: self.round, persons, nodes: self.nodes.len() as i64, edges: self.edges.len() as i64 };
                (200, m.to_json().into_bytes())
            }
            Req::Persons => {
                let rows: Vec<NodeRow> =
                    self.nodes.iter().filter(|n| n.label == "Person").take(contract::NODE_LIMIT).cloned().collect();
                (200, contract::encode_nodes(&rows))
            }
            Req::Ego(id) => {
                let around: std::collections::BTreeSet<i64> = self
                    .edges
                    .iter()
                    .filter(|e| e.src == *id || e.dst == *id)
                    .flat_map(|e| [e.src, e.dst])
                    .chain(std::iter::once(*id))
                    .collect();
                let rows: Vec<EdgeRow> =
                    self.edges.iter().filter(|e| around.contains(&e.src) && around.contains(&e.dst)).cloned().collect();
                (200, contract::encode_edges(&rows))
            }
            Req::NodesByIds(ids) => {
                let want: std::collections::BTreeSet<&i64> = ids.iter().collect();
                let rows: Vec<NodeRow> = self.nodes.iter().filter(|n| want.contains(&n.id)).cloned().collect();
                (200, contract::encode_nodes(&rows))
            }
        }
    }
}

/// The mock as a [`Backend`]: every request is answered at once and handed back on the
/// next [`drain`](Backend::drain), the way a fast network would.
#[derive(Clone)]
pub struct MockBackend {
    pub server: Arc<Mutex<MockServer>>,
    inbox: Arc<Mutex<Vec<Resp>>>,
}

impl MockBackend {
    pub fn new(server: MockServer) -> Self {
        Self { server: Arc::new(Mutex::new(server)), inbox: Arc::new(Mutex::new(Vec::new())) }
    }

    /// Every request the app made, in order.
    pub fn log(&self) -> Vec<Req> {
        self.server.lock().unwrap().log.clone()
    }
}

impl Backend for MockBackend {
    fn send(&self, req: Req) {
        let (status, body) = self.server.lock().unwrap().answer(&req);
        self.inbox.lock().unwrap().push(Resp { req, status, body });
    }

    fn drain(&self) -> Vec<Resp> {
        std::mem::take(&mut *self.inbox.lock().unwrap())
    }
}