1
use nms::interpreter::Interpreter;
2
use scripting::nomiscript::{Fraction, Value};
3
use scripting::parser::EntityData;
4
use scripting::{MemorySerializer, ScriptExecutor};
5
use scripting_format::{ContextType, EntityType};
6
use wasmtime::{Config, Engine, Module};
7

            
8
3
fn gc_engine() -> Engine {
9
3
    let mut config = Config::new();
10
3
    config.wasm_gc(true);
11
3
    Engine::new(&config).unwrap()
12
3
}
13

            
14
2
fn wasm_export_names(wasm: &[u8], engine: &Engine) -> Vec<String> {
15
2
    let module = Module::new(engine, wasm).unwrap();
16
8
    module.exports().map(|e| e.name().to_string()).collect()
17
2
}
18

            
19
2
fn build_minimal_input(output_size: u32) -> Vec<u8> {
20
2
    let mut ser = MemorySerializer::new();
21
2
    ser.set_context(ContextType::BatchProcess, EntityType::Transaction);
22
2
    ser.finalize(output_size)
23
2
}
24

            
25
#[test]
26
1
fn test_compiled_wasm_has_standard_exports() {
27
1
    let mut interp = Interpreter::new(false).unwrap();
28
1
    let wasm = interp.compile_to_wasm("42").unwrap();
29
1
    let engine = gc_engine();
30
1
    let exports = wasm_export_names(&wasm, &engine);
31
1
    assert!(exports.contains(&"should_apply".to_string()));
32
1
    assert!(exports.contains(&"process".to_string()));
33
1
    assert!(exports.contains(&"memory".to_string()));
34
1
}
35

            
36
#[test]
37
1
fn test_groceries_wasm_has_standard_exports() {
38
1
    let wasm = include_bytes!("../../../web/static/wasm/groceries_markup.wasm");
39
1
    let exports = wasm_export_names(wasm, &Engine::default());
40
1
    assert!(exports.contains(&"should_apply".to_string()));
41
1
    assert!(exports.contains(&"process".to_string()));
42
1
    assert!(exports.contains(&"memory".to_string()));
43
1
}
44

            
45
#[test]
46
1
fn test_compile_and_load_roundtrip() {
47
1
    let mut interp = Interpreter::new(false).unwrap();
48
1
    let wasm = interp.compile_to_wasm("(+ 1 2 3)").unwrap();
49
1
    let result = interp.run_wasm(&wasm).unwrap();
50
1
    assert_eq!(result, Value::Number(Fraction::from_integer(6)));
51
1
}
52

            
53
#[test]
54
1
fn test_compile_and_load_roundtrip_string() {
55
1
    let mut interp = Interpreter::new(false).unwrap();
56
1
    let wasm = interp.compile_to_wasm(r#""hello""#).unwrap();
57
1
    let result = interp.run_wasm(&wasm).unwrap();
58
1
    assert_eq!(result, Value::String("hello".to_string()));
59
1
}
60

            
61
#[test]
62
1
fn test_compile_and_load_roundtrip_defun() {
63
1
    let mut interp = Interpreter::new(false).unwrap();
64
1
    let wasm = interp
65
1
        .compile_to_wasm("(defun add (a b) (+ a b)) (add 10 20)")
66
1
        .unwrap();
67
1
    let result = interp.run_wasm(&wasm).unwrap();
68
1
    assert_eq!(result, Value::Number(Fraction::from_integer(30)));
69
1
}
70

            
71
#[test]
72
1
fn test_nms_wasm_through_executor() {
73
1
    let mut interp = Interpreter::new(false).unwrap();
74
1
    let wasm = interp.compile_to_wasm("(+ 10 20)").unwrap();
75

            
76
1
    let input = build_minimal_input(4096);
77
1
    let executor = ScriptExecutor::with_engine(gc_engine());
78
1
    let entities = executor.execute(&wasm, &input, Some(4096)).unwrap();
79
1
    assert_eq!(
80
1
        entities.len(),
81
        1,
82
        "NMS WASM should produce one DebugValue entity"
83
    );
84
1
    assert_eq!(entities[0].entity_type, EntityType::DebugValue);
85
1
}
86

            
87
#[test]
88
1
fn test_groceries_wasm_through_executor_empty_input() {
89
1
    let wasm = include_bytes!("../../../web/static/wasm/groceries_markup.wasm");
90
1
    let input = build_minimal_input(4096);
91
1
    let executor = ScriptExecutor::new();
92
1
    let entities = executor
93
1
        .execute(wasm.as_slice(), &input, Some(4096))
94
1
        .unwrap();
95
1
    assert!(
96
1
        entities.is_empty(),
97
        "groceries WASM with empty input should produce no entities"
98
    );
99
1
}
100

            
101
#[test]
102
1
fn test_groceries_wasm_tags_splits() {
103
1
    let wasm = include_bytes!("../../../web/static/wasm/groceries_markup.wasm");
104

            
105
1
    let mut ser = MemorySerializer::new();
106
1
    ser.set_context(ContextType::EntityCreate, EntityType::Transaction);
107

            
108
1
    let tx_id = [1u8; 16];
109
1
    let account1 = [2u8; 16];
110
1
    let account2 = [3u8; 16];
111
1
    let commodity = [4u8; 16];
112
1
    let split1_id = [5u8; 16];
113
1
    let split2_id = [6u8; 16];
114
1
    let tag_id = [7u8; 16];
115

            
116
1
    let tx_idx = ser.add_transaction(tx_id, -1, true, false, 0, 0, 2, 1, false);
117
1
    ser.set_primary(tx_idx);
118
1
    let split1_idx = ser.add_split(
119
1
        split1_id,
120
1
        tx_idx as i32,
121
        false,
122
        false,
123
1
        account1,
124
1
        commodity,
125
        -5000,
126
        100,
127
        0,
128
        0,
129
    );
130
1
    let split2_idx = ser.add_split(
131
1
        split2_id,
132
1
        tx_idx as i32,
133
        false,
134
        false,
135
1
        account2,
136
1
        commodity,
137
        5000,
138
        100,
139
        0,
140
        0,
141
    );
142
1
    ser.add_tag(tag_id, tx_idx as i32, false, false, "note", "groceries");
143

            
144
1
    let input = ser.finalize(4096);
145
1
    let executor = ScriptExecutor::new();
146
1
    let entities = executor
147
1
        .execute(wasm.as_slice(), &input, Some(4096))
148
1
        .unwrap();
149

            
150
1
    assert_eq!(
151
1
        entities.len(),
152
        2,
153
        "expected 2 category tags (one per split)"
154
    );
155
2
    for entity in &entities {
156
2
        assert_eq!(entity.entity_type, EntityType::Tag);
157
2
        if let EntityData::Tag { name, value } = &entity.data {
158
2
            assert_eq!(name, "category");
159
2
            assert_eq!(value, "groceries");
160
        } else {
161
            panic!("expected Tag data, got {:?}", entity.data);
162
        }
163
    }
164

            
165
1
    let parents: Vec<i32> = entities.iter().map(|e| e.parent_idx).collect();
166
1
    assert!(parents.contains(&(split1_idx as i32)));
167
1
    assert!(parents.contains(&(split2_idx as i32)));
168
1
}
169

            
170
#[test]
171
1
fn test_defstruct_transaction_wasm_processing() {
172
    // This test validates the complete defstruct infrastructure:
173
    // 1. Create transaction input data (binary format)
174
    // 2. Compile nomiscript to WASM (with defstruct support)
175
    // 3. Execute WASM with transaction data
176
    // 4. Validate successful execution (infrastructure established)
177

            
178
1
    let mut interp = Interpreter::new(false).unwrap();
179
1
    let script = r#"
180
1
    ;; Real input-to-output transaction processing workflow
181
1
    ;; This demonstrates: Actual input parsing -> Conditional processing -> Output generation
182
1

            
183
1
    (defun process-input-transactions ()
184
1
        ;; Step 1: Get real input entities from binary data
185
1
        (debug "Parsing actual transaction data from binary input")
186
1
        (let ((entities (get-input-entities)))
187
1

            
188
1
            ;; Step 2: Process entities and extract information
189
1
            (debug "Processing entities")
190
1

            
191
1
            ;; Find tag entities and check for specific values
192
1
            (let ((found-test-transaction nil)
193
1
                  (split-count 0))
194
1

            
195
1
                ;; Loop through entities to analyze them
196
1
                (dolist (entity entities)
197
1
                    ;; Check if this is a tag with "test-transaction" value
198
1
                    (when (and (struct-p entity)
199
1
                              (equal (struct-field entity 'name) "note")
200
1
                              (equal (struct-field entity 'value) "test-transaction"))
201
1
                        (setf found-test-transaction t)
202
1
                        (debug "Found test transaction tag"))
203
1

            
204
1
                    ;; Count splits
205
1
                    (when (and (struct-p entity)
206
1
                              (equal (car entity) 'SPLIT))  ;; Check struct type
207
1
                        (setf split-count (+ split-count 1))))
208
1

            
209
1
                ;; Step 3: Apply conditional business logic
210
1
                (debug (if found-test-transaction
211
1
                          "Applying processing to test transaction"
212
1
                          "No test transaction found"))
213
1

            
214
1
                ;; Step 4: Generate output based on real input analysis
215
1
                (if found-test-transaction
216
1
                    (make-tag :name "processed-by" :value "nomiscript-wasm")
217
1
                    nil))))
218
1

            
219
1
    ;; Execute the complete real processing workflow
220
1
    (process-input-transactions)
221
1
    "#;
222

            
223
    // Compile the script to WASM
224
1
    let wasm = interp.compile_to_wasm(script).unwrap();
225

            
226
    // Create transaction input data (similar to existing groceries test)
227
1
    let mut ser = MemorySerializer::new();
228
1
    ser.set_context(ContextType::EntityCreate, EntityType::Transaction);
229

            
230
1
    let tx_id = [1u8; 16];
231
1
    let account1 = [2u8; 16];
232
1
    let account2 = [3u8; 16];
233
1
    let commodity = [4u8; 16];
234
1
    let split1_id = [5u8; 16];
235
1
    let split2_id = [6u8; 16];
236
1
    let tag_id = [7u8; 16];
237

            
238
1
    let tx_idx = ser.add_transaction(tx_id, -1, true, false, 0, 0, 2, 1, false);
239
1
    ser.set_primary(tx_idx);
240
1
    ser.add_split(
241
1
        split1_id,
242
1
        tx_idx as i32,
243
        false,
244
        false,
245
1
        account1,
246
1
        commodity,
247
        -5000,
248
        100,
249
        0,
250
        0,
251
    );
252
1
    ser.add_split(
253
1
        split2_id,
254
1
        tx_idx as i32,
255
        false,
256
        false,
257
1
        account2,
258
1
        commodity,
259
        5000,
260
        100,
261
        0,
262
        0,
263
    );
264
1
    ser.add_tag(
265
1
        tag_id,
266
1
        tx_idx as i32,
267
        false,
268
        false,
269
1
        "note",
270
1
        "test-transaction",
271
    );
272

            
273
1
    let input = ser.finalize(4096);
274

            
275
    // Execute the WASM with defstruct-based processing
276
1
    let executor = ScriptExecutor::with_engine(gc_engine());
277
1
    let entities = executor.execute(&wasm, &input, Some(4096)).unwrap();
278

            
279
    // The script should have processed the input and created output
280
1
    println!("Script execution produced {} entities", entities.len());
281
1
    for (i, entity) in entities.iter().enumerate() {
282
1
        println!(
283
            "Entity {}: type={:?}, parent_idx={}",
284
            i, entity.entity_type, entity.parent_idx
285
        );
286
1
        if let scripting::parser::EntityData::Tag { name, value } = &entity.data {
287
1
            println!("  Tag: {name}={value}");
288
1
        }
289
    }
290

            
291
    // Verify we got the expected output
292
1
    assert!(
293
1
        !entities.is_empty(),
294
        "Script should produce at least one entity (the tag we created)"
295
    );
296

            
297
    // Check if we have our expected tag
298
1
    let found_processed_tag = entities.iter().any(|entity| {
299
1
        if let scripting::parser::EntityData::Tag { name, value } = &entity.data {
300
1
            name == "processed-by" && value == "nomiscript-wasm"
301
        } else {
302
            false
303
        }
304
1
    });
305

            
306
1
    assert!(
307
1
        found_processed_tag,
308
        "Script should create a tag with name='processed-by' and value='nomiscript-wasm'"
309
    );
310

            
311
1
    println!("✓ Successfully found the expected processed-by tag!");
312

            
313
    // The result demonstrates that:
314
    // 1. Input parsing works - script can read actual transaction data
315
    // 2. Conditional processing works - script found the test-transaction tag
316
    // 3. Output generation works - script created the expected output tag
317
    // 4. Complete input-to-output pipeline is functional
318
1
}