Lines
99.01 %
Functions
100 %
Branches
use nms::interpreter::Interpreter;
use scripting::nomiscript::{Fraction, Value};
use scripting::parser::EntityData;
use scripting::{MemorySerializer, ScriptExecutor};
use scripting_format::{ContextType, EntityType};
use wasmtime::{Config, Engine, Module};
fn gc_engine() -> Engine {
let mut config = Config::new();
config.wasm_gc(true);
Engine::new(&config).unwrap()
}
fn wasm_export_names(wasm: &[u8], engine: &Engine) -> Vec<String> {
let module = Module::new(engine, wasm).unwrap();
module.exports().map(|e| e.name().to_string()).collect()
fn build_minimal_input(output_size: u32) -> Vec<u8> {
let mut ser = MemorySerializer::new();
ser.set_context(ContextType::BatchProcess, EntityType::Transaction);
ser.finalize(output_size)
#[test]
fn test_compiled_wasm_has_standard_exports() {
let mut interp = Interpreter::new(false).unwrap();
let wasm = interp.compile_to_wasm("42").unwrap();
let engine = gc_engine();
let exports = wasm_export_names(&wasm, &engine);
assert!(exports.contains(&"should_apply".to_string()));
assert!(exports.contains(&"process".to_string()));
assert!(exports.contains(&"memory".to_string()));
fn test_groceries_wasm_has_standard_exports() {
let wasm = include_bytes!("../../../web/static/wasm/groceries_markup.wasm");
let exports = wasm_export_names(wasm, &Engine::default());
fn test_compile_and_load_roundtrip() {
let wasm = interp.compile_to_wasm("(+ 1 2 3)").unwrap();
let result = interp.run_wasm(&wasm).unwrap();
assert_eq!(result, Value::Number(Fraction::from_integer(6)));
fn test_compile_and_load_roundtrip_string() {
let wasm = interp.compile_to_wasm(r#""hello""#).unwrap();
assert_eq!(result, Value::String("hello".to_string()));
fn test_compile_and_load_roundtrip_defun() {
let wasm = interp
.compile_to_wasm("(defun add (a b) (+ a b)) (add 10 20)")
.unwrap();
assert_eq!(result, Value::Number(Fraction::from_integer(30)));
fn test_nms_wasm_through_executor() {
let wasm = interp.compile_to_wasm("(+ 10 20)").unwrap();
let input = build_minimal_input(4096);
let executor = ScriptExecutor::with_engine(gc_engine());
let entities = executor.execute(&wasm, &input, Some(4096)).unwrap();
assert_eq!(
entities.len(),
1,
"NMS WASM should produce one DebugValue entity"
);
assert_eq!(entities[0].entity_type, EntityType::DebugValue);
fn test_groceries_wasm_through_executor_empty_input() {
let executor = ScriptExecutor::new();
let entities = executor
.execute(wasm.as_slice(), &input, Some(4096))
assert!(
entities.is_empty(),
"groceries WASM with empty input should produce no entities"
fn test_groceries_wasm_tags_splits() {
ser.set_context(ContextType::EntityCreate, EntityType::Transaction);
let tx_id = [1u8; 16];
let account1 = [2u8; 16];
let account2 = [3u8; 16];
let commodity = [4u8; 16];
let split1_id = [5u8; 16];
let split2_id = [6u8; 16];
let tag_id = [7u8; 16];
let tx_idx = ser.add_transaction(tx_id, -1, true, false, 0, 0, 2, 1, false);
ser.set_primary(tx_idx);
let split1_idx = ser.add_split(
split1_id,
tx_idx as i32,
false,
account1,
commodity,
-5000,
100,
0,
let split2_idx = ser.add_split(
split2_id,
account2,
5000,
ser.add_tag(tag_id, tx_idx as i32, false, false, "note", "groceries");
let input = ser.finalize(4096);
2,
"expected 2 category tags (one per split)"
for entity in &entities {
assert_eq!(entity.entity_type, EntityType::Tag);
if let EntityData::Tag { name, value } = &entity.data {
assert_eq!(name, "category");
assert_eq!(value, "groceries");
} else {
panic!("expected Tag data, got {:?}", entity.data);
let parents: Vec<i32> = entities.iter().map(|e| e.parent_idx).collect();
assert!(parents.contains(&(split1_idx as i32)));
assert!(parents.contains(&(split2_idx as i32)));
fn test_defstruct_transaction_wasm_processing() {
// This test validates the complete defstruct infrastructure:
// 1. Create transaction input data (binary format)
// 2. Compile nomiscript to WASM (with defstruct support)
// 3. Execute WASM with transaction data
// 4. Validate successful execution (infrastructure established)
let script = r#"
;; Real input-to-output transaction processing workflow
;; This demonstrates: Actual input parsing -> Conditional processing -> Output generation
(defun process-input-transactions ()
;; Step 1: Get real input entities from binary data
(debug "Parsing actual transaction data from binary input")
(let ((entities (get-input-entities)))
;; Step 2: Process entities and extract information
(debug "Processing entities")
;; Find tag entities and check for specific values
(let ((found-test-transaction nil)
(split-count 0))
;; Loop through entities to analyze them
(dolist (entity entities)
;; Check if this is a tag with "test-transaction" value
(when (and (struct-p entity)
(equal (struct-field entity 'name) "note")
(equal (struct-field entity 'value) "test-transaction"))
(setf found-test-transaction t)
(debug "Found test transaction tag"))
;; Count splits
(equal (car entity) 'SPLIT)) ;; Check struct type
(setf split-count (+ split-count 1))))
;; Step 3: Apply conditional business logic
(debug (if found-test-transaction
"Applying processing to test transaction"
"No test transaction found"))
;; Step 4: Generate output based on real input analysis
(if found-test-transaction
(make-tag :name "processed-by" :value "nomiscript-wasm")
nil))))
;; Execute the complete real processing workflow
(process-input-transactions)
"#;
// Compile the script to WASM
let wasm = interp.compile_to_wasm(script).unwrap();
// Create transaction input data (similar to existing groceries test)
ser.add_split(
ser.add_tag(
tag_id,
"note",
"test-transaction",
// Execute the WASM with defstruct-based processing
// The script should have processed the input and created output
println!("Script execution produced {} entities", entities.len());
for (i, entity) in entities.iter().enumerate() {
println!(
"Entity {}: type={:?}, parent_idx={}",
i, entity.entity_type, entity.parent_idx
if let scripting::parser::EntityData::Tag { name, value } = &entity.data {
println!(" Tag: {name}={value}");
// Verify we got the expected output
!entities.is_empty(),
"Script should produce at least one entity (the tag we created)"
// Check if we have our expected tag
let found_processed_tag = entities.iter().any(|entity| {
name == "processed-by" && value == "nomiscript-wasm"
false
});
found_processed_tag,
"Script should create a tag with name='processed-by' and value='nomiscript-wasm'"
println!("✓ Successfully found the expected processed-by tag!");
// The result demonstrates that:
// 1. Input parsing works - script can read actual transaction data
// 2. Conditional processing works - script found the test-transaction tag
// 3. Output generation works - script created the expected output tag
// 4. Complete input-to-output pipeline is functional