tawasuyu

tawasuyu/brahman

Rust 326TOML 123JSON 17Markdown 144606 Definitions

⎇ 72758e75ce515f355f27615671c95ecaf8c2a8bc crates/modules/nakui/core/tests/inventory.rs Rust · 173 lines · 5.3 KB History Who touched what raw

refactor(nakui-core): KCL → Nickel — kcl_wrapper reemplazado por evaluación in-process b05de24c2 · Sergio ·

1//! Inventory module integration tests. The point: prove the kernel is
2//! module-agnostic — these tests use the SAME executor code path as
3//! treasury, just pointed at a different module dir, and the conservation
4//! rule is just declarative (Stock.cantidad group_by sku_id).
5
6use std::path::{Path, PathBuf};
7
8use nakui_core::executor::{ExecError, Executor};
9use nakui_core::store::{MemoryStore, Store};
10use serde_json::{Value, json};
11use uuid::Uuid;
12
13fn workspace_root() -> PathBuf {
14    Path::new(env!("CARGO_MANIFEST_DIR"))
15        .parent()
16        .expect("workspace root above core/")
17        .to_path_buf()
18}
19
20fn inventory_module() -> PathBuf {
21    workspace_root().join("modules/inventory")
22}
23
24fn cantidad(store: &MemoryStore, id: Uuid) -> i64 {
25    store
26        .load("Stock", id)
27        .and_then(|v| v.get("cantidad").and_then(Value::as_i64))
28        .expect("stock with cantidad")
29}
30
31fn seed_stock(store: &mut MemoryStore, id: Uuid, sku: &str, cantidad: i64) {
32    store.seed(
33        "Stock",
34        id,
35        json!({
36            "id": id.to_string(),
37            "sku_id": sku,
38            "ubicacion": "test-loc",
39            "cantidad": cantidad,
40        }),
41    );
42}
43
44#[test]
45fn transfer_conserves_units_across_same_sku() {
46    let exec = Executor::load_module(inventory_module()).expect("load module");
47    let mut store = MemoryStore::new();
48
49    let a = Uuid::new_v4();
50    let b = Uuid::new_v4();
51    seed_stock(&mut store, a, "sku-X", 500);
52    seed_stock(&mut store, b, "sku-X", 100);
53
54    let ops = exec
55        .run(
56            &mut store,
57            "transferir_stock",
58            &[("source", a), ("dest", b)],
59            json!({
60                "cantidad": 150_i64,
61                "timestamp": "2026-05-04T00:00:00Z",
62                "transfer_id": Uuid::new_v4().to_string(),
63            }),
64        )
65        .expect("transfer must pass");
66
67    assert_eq!(ops.len(), 3, "2 sets + 1 create = 3 ops");
68    assert_eq!(cantidad(&store, a), 350);
69    assert_eq!(cantidad(&store, b), 250);
70    // Total preserved.
71    assert_eq!(cantidad(&store, a) + cantidad(&store, b), 600);
72}
73
74#[test]
75fn transfer_across_different_skus_is_rejected_by_conservation() {
76    // Construct a buggy synthetic morphism that mimics transfer but skips
77    // the in-script same-sku check. We do this by pointing at a fixture
78    // script that lacks the `throw if source.sku_id != dest.sku_id`.
79    //
80    // Without that fixture we can rely on the production script's `throw`
81    // to fire first — which is itself fine but proves the SCRIPT, not the
82    // KERNEL. To prove the kernel-level conservation works on inventory,
83    // see kernel_guards.rs (treasury) — that test exercises the same
84    // executor logic with Caja.saldo grouped by currency. Here we just
85    // assert the production script rejects cross-SKU.
86    let exec = Executor::load_module(inventory_module()).expect("load module");
87    let mut store = MemoryStore::new();
88
89    let a = Uuid::new_v4();
90    let c = Uuid::new_v4();
91    seed_stock(&mut store, a, "sku-X", 500);
92    seed_stock(&mut store, c, "sku-Y", 200);
93
94    let result = exec.run(
95        &mut store,
96        "transferir_stock",
97        &[("source", a), ("dest", c)],
98        json!({
99            "cantidad": 50_i64,
100            "timestamp": "2026-05-04T00:00:00Z",
101            "transfer_id": Uuid::new_v4().to_string(),
102        }),
103    );
104
105    match result {
106        Err(ExecError::Rhai(_)) => {}
107        other => panic!("expected Rhai (script throw on sku mismatch), got {:?}", other),
108    }
109    assert_eq!(cantidad(&store, a), 500);
110    assert_eq!(cantidad(&store, c), 200);
111}
112
113#[test]
114fn overdraw_transfer_blocked_by_kcl_post_check() {
115    let exec = Executor::load_module(inventory_module()).expect("load module");
116    let mut store = MemoryStore::new();
117
118    let a = Uuid::new_v4();
119    let b = Uuid::new_v4();
120    seed_stock(&mut store, a, "sku-X", 100);
121    seed_stock(&mut store, b, "sku-X", 0);
122
123    let result = exec.run(
124        &mut store,
125        "transferir_stock",
126        &[("source", a), ("dest", b)],
127        json!({
128            "cantidad": 999_i64,
129            "timestamp": "2026-05-04T00:00:00Z",
130            "transfer_id": Uuid::new_v4().to_string(),
131        }),
132    );
133
134    match result {
135        Err(ExecError::SchemaPost { role, entity, .. }) => {
136            assert_eq!(role, "source");
137            assert_eq!(entity, "Stock");
138        }
139        other => panic!("expected SchemaPost on source, got {:?}", other),
140    }
141    assert_eq!(cantidad(&store, a), 100);
142    assert_eq!(cantidad(&store, b), 0);
143}
144
145#[test]
146fn recibir_increases_stock_and_creates_movimiento() {
147    let exec = Executor::load_module(inventory_module()).expect("load module");
148    let mut store = MemoryStore::new();
149
150    let a = Uuid::new_v4();
151    seed_stock(&mut store, a, "sku-X", 100);
152
153    let mov_id = Uuid::new_v4();
154    let ops = exec
155        .run(
156            &mut store,
157            "recibir_stock",
158            &[("stock", a)],
159            json!({
160                "cantidad": 50_i64,
161                "timestamp": "2026-05-04T00:00:00Z",
162                "movimiento_id": mov_id.to_string(),
163            }),
164        )
165        .expect("recibir must pass");
166
167    assert_eq!(ops.len(), 2, "1 set + 1 create");
168    assert_eq!(cantidad(&store, a), 150);
169    assert!(
170        store.load("MovimientoStock", mov_id).is_some(),
171        "movimiento must be persisted"
172    );
173}