pasta_core 0.1.21

Pasta Core - Language-independent DSL parsing and registry layer
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
//! SceneTable テスト (#[path] パターン)
//!
//! Phase A: registry/scene_table.rs のインラインテストを分離
//! 例外理由: SceneTableの全privateフィールド(labels, prefix_index, cache, random_selector, shuffle_enabled)
//! への直接アクセスが構造的に必要なため、#[path]パターンで分離

use super::*;
use crate::registry::random::MockRandomSelector;

fn create_test_scene_info(id: usize, name: &str, fn_name: &str) -> SceneInfo {
    SceneInfo {
        id: SceneId(id),
        name: name.to_string(),
        scope: SceneScope::Global,
        attributes: HashMap::new(),
        fn_name: fn_name.to_string(),
        parent: None,
    }
}

#[test]
fn test_resolve_scene_id_basic() {
    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = SceneTable {
        labels: vec![create_test_scene_info(0, "test", "test_1::__start__")],
        prefix_index: {
            let mut map = RadixMap::new();
            map.insert(b"test_1::__start__", vec![SceneId(0)]);
            map
        },
        cache: HashMap::new(),
        random_selector: selector,
        shuffle_enabled: false,
    };

    let result = table.resolve_scene_id("test", &HashMap::new());
    assert!(result.is_ok());
    assert_eq!(result.unwrap(), SceneId(0));
}

// ======================================================================
// Tests for collect_scene_candidates (Task 5.1)
// ======================================================================

fn create_test_local_scene_info(
    id: usize,
    name: &str,
    fn_name: &str,
    parent: &str,
) -> SceneInfo {
    SceneInfo {
        id: SceneId(id),
        name: name.to_string(),
        scope: SceneScope::Local,
        attributes: HashMap::new(),
        fn_name: fn_name.to_string(),
        parent: Some(parent.to_string()),
    }
}

#[test]
fn test_collect_scene_candidates_local_search() {
    // Test: Local search should find scenes with :module:prefix pattern
    let selector = Box::new(MockRandomSelector::new(vec![]));
    let mut table = SceneTable::new(selector);

    // Add local scene with :parent:local key format
    table.labels.push(create_test_local_scene_info(
        0,
        "選択肢",
        "会話_1::選択肢_1",
        "会話_1",
    ));
    table
        .prefix_index
        .insert(":会話_1:選択肢".as_bytes(), vec![SceneId(0)]);

    // Search from 会話_1 module
    let result = table.collect_scene_candidates("会話_1", "選択肢");
    assert!(result.is_ok());
    let candidates = result.unwrap();
    assert_eq!(candidates.len(), 1);
    assert_eq!(candidates[0], SceneId(0));
}

#[test]
fn test_collect_scene_candidates_global_search() {
    // Test: Global search (empty module_name) should find scenes without : prefix
    let selector = Box::new(MockRandomSelector::new(vec![]));
    let mut table = SceneTable::new(selector);

    // Add global scene with simple key
    table.labels.push(create_test_scene_info(0, "挨拶", "挨拶"));
    table
        .prefix_index
        .insert("挨拶".as_bytes(), vec![SceneId(0)]);

    // Search with empty module_name - should get global
    let result = table.collect_scene_candidates("", "挨拶");
    assert!(result.is_ok());
    let candidates = result.unwrap();
    assert_eq!(candidates.len(), 1);
    assert_eq!(candidates[0], SceneId(0));
}

#[test]
fn test_collect_scene_candidates_local_only() {
    // Test: Local search only - no global fallback
    let selector = Box::new(MockRandomSelector::new(vec![]));
    let mut table = SceneTable::new(selector);

    // Add global scene
    table.labels.push(create_test_scene_info(0, "挨拶", "挨拶"));
    table
        .prefix_index
        .insert("挨拶".as_bytes(), vec![SceneId(0)]);

    // Add local scene with same prefix
    table.labels.push(create_test_local_scene_info(
        1,
        "挨拶",
        "会話_1::挨拶_1",
        "会話_1",
    ));
    table
        .prefix_index
        .insert(":会話_1:挨拶".as_bytes(), vec![SceneId(1)]);

    // Search from 会話_1 module - should get ONLY local
    let result = table.collect_scene_candidates("会話_1", "挨拶");
    assert!(result.is_ok());
    let candidates = result.unwrap();
    assert_eq!(candidates.len(), 1);
    assert!(candidates.contains(&SceneId(1))); // local only
    assert!(!candidates.contains(&SceneId(0))); // global NOT included
}

#[test]
fn test_collect_scene_candidates_local_not_found_no_fallback() {
    // Test: No local found, should NOT fall back to global - return error
    let selector = Box::new(MockRandomSelector::new(vec![]));
    let mut table = SceneTable::new(selector);

    // Add global scene only
    table.labels.push(create_test_scene_info(0, "挨拶", "挨拶"));
    table
        .prefix_index
        .insert("挨拶".as_bytes(), vec![SceneId(0)]);

    // Search from 会話_1 module - no local, should NOT fall back to global
    let result = table.collect_scene_candidates("会話_1", "挨拶");
    assert!(result.is_err());
    match result {
        Err(SceneTableError::SceneNotFound { scene }) => assert_eq!(scene, "挨拶"),
        _ => panic!("Expected SceneNotFound error"),
    }
}

#[test]
fn test_collect_scene_candidates_global_via_empty_module_name() {
    // Test: Empty module_name should search global only
    let selector = Box::new(MockRandomSelector::new(vec![]));
    let mut table = SceneTable::new(selector);

    // Add global scene
    table.labels.push(create_test_scene_info(0, "挨拶", "挨拶"));
    table
        .prefix_index
        .insert("挨拶".as_bytes(), vec![SceneId(0)]);

    // Search with empty module_name - should get global
    let result = table.collect_scene_candidates("", "挨拶");
    assert!(result.is_ok());
    let candidates = result.unwrap();
    assert_eq!(candidates.len(), 1);
    assert!(candidates.contains(&SceneId(0)));
}

#[test]
fn test_collect_scene_candidates_prefix_match() {
    // Test: Prefix matching should work correctly
    let selector = Box::new(MockRandomSelector::new(vec![]));
    let mut table = SceneTable::new(selector);

    // Add scenes with common prefix
    table
        .labels
        .push(create_test_scene_info(0, "挨拶_朝", "挨拶_朝"));
    table
        .labels
        .push(create_test_scene_info(1, "挨拶_昼", "挨拶_昼"));
    table
        .labels
        .push(create_test_scene_info(2, "挨拶_夜", "挨拶_夜"));

    // Insert with proper UTF-8 bytes
    table
        .prefix_index
        .insert("挨拶_朝".as_bytes(), vec![SceneId(0)]);
    table
        .prefix_index
        .insert("挨拶_昼".as_bytes(), vec![SceneId(1)]);
    table
        .prefix_index
        .insert("挨拶_夜".as_bytes(), vec![SceneId(2)]);

    // Prefix search for "挨拶" should find all three
    let result = table.collect_scene_candidates("", "挨拶");
    assert!(result.is_ok());
    let candidates = result.unwrap();
    assert_eq!(candidates.len(), 3);
}

#[test]
fn test_collect_scene_candidates_not_found() {
    // Test: Should return error when no candidates found
    let selector = Box::new(MockRandomSelector::new(vec![]));
    let table = SceneTable::new(selector);

    let result = table.collect_scene_candidates("会話_1", "存在しないシーン");
    assert!(result.is_err());
    match result {
        Err(SceneTableError::SceneNotFound { scene }) => {
            assert_eq!(scene, "存在しないシーン");
        }
        _ => panic!("Expected SceneNotFound error"),
    }
}

#[test]
fn test_collect_scene_candidates_empty_prefix_error() {
    // Test: Empty prefix should return error
    let selector = Box::new(MockRandomSelector::new(vec![]));
    let table = SceneTable::new(selector);

    let result = table.collect_scene_candidates("会話_1", "");
    assert!(result.is_err());
    match result {
        Err(SceneTableError::InvalidScene { scene }) => {
            assert_eq!(scene, "");
        }
        _ => panic!("Expected InvalidScene error"),
    }
}

#[test]
fn test_collect_scene_candidates_exclude_local_from_global() {
    // Test: Global search should exclude local keys (starting with :)
    let selector = Box::new(MockRandomSelector::new(vec![]));
    let mut table = SceneTable::new(selector);

    // Add a local scene
    table.labels.push(create_test_local_scene_info(
        0,
        "選択肢",
        "他モジュール::選択肢_1",
        "他モジュール",
    ));
    table
        .prefix_index
        .insert(":他モジュール:選択肢".as_bytes(), vec![SceneId(0)]);

    // Search from different module - should NOT find the local scene of another module
    let result = table.collect_scene_candidates("会話_1", "選択肢");
    assert!(result.is_err()); // No candidates
}

// ======================================================================
// Tests for fn_name_to_search_key and prefix_index conversion (Task 5.3)
// ======================================================================

#[test]
fn test_fn_name_to_search_key_local_scene() {
    // Local scene: "会話_1::選択肢_1" → ":会話_1:選択肢_1"
    let result = SceneTable::fn_name_to_search_key("会話_1::選択肢_1", true);
    assert_eq!(result, ":会話_1:選択肢_1");
}

#[test]
fn test_fn_name_to_search_key_global_scene() {
    // Global scene: "会話_1::__start__" → "会話_1"
    let result = SceneTable::fn_name_to_search_key("会話_1::__start__", false);
    assert_eq!(result, "会話_1");
}

#[test]
fn test_fn_name_to_search_key_global_scene_no_suffix() {
    // Global scene without ::__start__: "挨拶" → "挨拶"
    let result = SceneTable::fn_name_to_search_key("挨拶", false);
    assert_eq!(result, "挨拶");
}

#[test]
fn test_from_scene_registry_key_conversion() {
    use crate::registry::SceneRegistry;

    // Create a registry with global and local scenes
    let mut registry = SceneRegistry::new();
    let (_, counter) = registry.register_global("会話", HashMap::new());
    registry.register_local("選択肢", "会話", counter, 1, HashMap::new());

    let selector = Box::new(MockRandomSelector::new(vec![]));
    let table = SceneTable::from_scene_registry(registry, selector).unwrap();

    // Verify search key conversion works with collect_scene_candidates
    // Global scene search
    let global_result = table.collect_scene_candidates("", "会話");
    assert!(global_result.is_ok());
    assert_eq!(global_result.unwrap().len(), 1);

    // Local scene search (from 会話_1 module)
    let local_result = table.collect_scene_candidates("会話_1", "選択肢");
    assert!(local_result.is_ok());
    assert_eq!(local_result.unwrap().len(), 1);

    // Local scene should not be found from different module
    let cross_module_result = table.collect_scene_candidates("他のモジュール", "選択肢");
    assert!(cross_module_result.is_err());
}

// ======================================================================
// Tests for resolve_scene_id_unified (Task 5.2)
// ======================================================================

#[test]
fn test_resolve_scene_id_unified_local_scene() {
    use crate::registry::SceneRegistry;

    let mut registry = SceneRegistry::new();
    let (_, counter) = registry.register_global("会話", HashMap::new());
    registry.register_local("選択肢", "会話", counter, 1, HashMap::new());

    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = SceneTable::from_scene_registry(registry, selector).unwrap();
    table.set_shuffle_enabled(false);

    // Resolve local scene from parent module
    let result = table.resolve_scene_id_unified("会話_1", "選択肢", &HashMap::new());
    assert!(result.is_ok());
    let scene_id = result.unwrap();
    let scene = table.get_scene(scene_id).unwrap();
    assert!(scene.name.contains("選択肢"));
}

#[test]
fn test_resolve_scene_id_unified_global_scene() {
    use crate::registry::SceneRegistry;

    let mut registry = SceneRegistry::new();
    registry.register_global("挨拶", HashMap::new());

    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = SceneTable::from_scene_registry(registry, selector).unwrap();
    table.set_shuffle_enabled(false);

    // Resolve global scene - must use empty module_name for global search
    let result = table.resolve_scene_id_unified("", "挨拶", &HashMap::new());
    assert!(result.is_ok());
}

#[test]
fn test_resolve_scene_id_unified_global_scene_no_fallback() {
    use crate::registry::SceneRegistry;

    let mut registry = SceneRegistry::new();
    registry.register_global("挨拶", HashMap::new());

    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = SceneTable::from_scene_registry(registry, selector).unwrap();
    table.set_shuffle_enabled(false);

    // Try to resolve global scene from a module - should fail (no fallback)
    let result = table.resolve_scene_id_unified("任意のモジュール", "挨拶", &HashMap::new());
    assert!(result.is_err());
}

#[test]
fn test_resolve_scene_id_unified_local_found() {
    use crate::registry::SceneRegistry;

    let mut registry = SceneRegistry::new();
    // Global scene "挨拶" (should not be found when searching from module)
    registry.register_global("挨拶", HashMap::new());
    // Local scene "挨拶" in module 会話_1
    let (_, counter) = registry.register_global("会話", HashMap::new());
    registry.register_local("挨拶", "会話", counter, 1, HashMap::new());

    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = SceneTable::from_scene_registry(registry, selector).unwrap();
    table.set_shuffle_enabled(false);

    // Resolve from 会話_1 - should find ONLY local (no fallback to global)
    let result = table.resolve_scene_id_unified("会話_1", "挨拶", &HashMap::new());
    assert!(result.is_ok());
    let scene_id = result.unwrap();
    let scene = table.get_scene(scene_id).unwrap();
    // Should be local scene (has parent)
    assert!(scene.parent.is_some());

    // Call again - should succeed due to cycling reset (1 local candidate cycles)
    let result2 = table.resolve_scene_id_unified("会話_1", "挨拶", &HashMap::new());
    assert!(result2.is_ok()); // Cycling reset returns the same scene
    let scene_id2 = result2.unwrap();
    let scene2 = table.get_scene(scene_id2).unwrap();
    assert!(scene2.parent.is_some()); // Still a local scene
}

// ======================================================================
// Tests for cycling reset (Task 3.1〜3.4)
// ======================================================================

#[test]
fn test_resolve_scene_id_cycling() {
    // Task 3.1: 3件のシーンを登録、4回目の呼び出しが成功することを検証
    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = SceneTable {
        labels: vec![
            create_test_scene_info(0, "OnTalk1", "OnTalk"),
            create_test_scene_info(1, "OnTalk2", "OnTalk"),
            create_test_scene_info(2, "OnTalk3", "OnTalk"),
        ],
        prefix_index: {
            let mut map = RadixMap::new();
            map.insert(b"OnTalk", vec![SceneId(0), SceneId(1), SceneId(2)]);
            map
        },
        cache: HashMap::new(),
        random_selector: selector,
        shuffle_enabled: false,
    };

    // 1回目〜3回目: 候補を順番に消費
    let r1 = table.resolve_scene_id("OnTalk", &HashMap::new());
    assert!(r1.is_ok());
    let r2 = table.resolve_scene_id("OnTalk", &HashMap::new());
    assert!(r2.is_ok());
    let r3 = table.resolve_scene_id("OnTalk", &HashMap::new());
    assert!(r3.is_ok());

    // 4回目: 循環リセットが発生し、成功すること
    let r4 = table.resolve_scene_id("OnTalk", &HashMap::new());
    assert!(
        r4.is_ok(),
        "4回目の呼び出しが失敗: 循環リセットが動作していない"
    );

    // 返却されたSceneIdが候補のいずれかであること
    let id = r4.unwrap();
    assert!(
        id == SceneId(0) || id == SceneId(1) || id == SceneId(2),
        "返却されたSceneId {:?} が候補に含まれていない",
        id
    );

    // さらに続行できることも検証(10回以上)
    for i in 5..=12 {
        let r = table.resolve_scene_id("OnTalk", &HashMap::new());
        assert!(r.is_ok(), "{}回目の呼び出しが失敗", i);
    }
}

#[test]
fn test_resolve_scene_id_cycling_reshuffles() {
    // Task 3.2: シャッフル有効時のリセット後再シャッフル検証
    // DefaultRandomSelectorを使用して実際のシャッフルが発生することを確認
    use crate::registry::random::DefaultRandomSelector;

    let selector = Box::new(DefaultRandomSelector::with_seed(42));
    let mut table = SceneTable {
        labels: vec![
            create_test_scene_info(0, "OnTalk1", "OnTalk"),
            create_test_scene_info(1, "OnTalk2", "OnTalk"),
            create_test_scene_info(2, "OnTalk3", "OnTalk"),
        ],
        prefix_index: {
            let mut map = RadixMap::new();
            map.insert(b"OnTalk", vec![SceneId(0), SceneId(1), SceneId(2)]);
            map
        },
        cache: HashMap::new(),
        random_selector: selector,
        shuffle_enabled: true,
    };

    // 1周目の順序を記録
    let mut first_cycle = Vec::new();
    for _ in 0..3 {
        let r = table.resolve_scene_id("OnTalk", &HashMap::new());
        first_cycle.push(r.unwrap());
    }

    // 2周目(リセット後)の順序を記録
    let mut second_cycle = Vec::new();
    for _ in 0..3 {
        let r = table.resolve_scene_id("OnTalk", &HashMap::new());
        assert!(r.is_ok());
        second_cycle.push(r.unwrap());
    }

    // 同じ候補セットだが、シャッフルにより順序が異なる可能性がある
    let first_set: std::collections::HashSet<_> = first_cycle.iter().collect();
    let second_set: std::collections::HashSet<_> = second_cycle.iter().collect();
    assert_eq!(first_set, second_set, "候補セットが変化している");
}

#[test]
fn test_resolve_scene_id_cycling_preserves_candidates() {
    // Task 3.3: リセット前後で候補リスト(ID集合)が保持されることを検証
    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = SceneTable {
        labels: vec![
            create_test_scene_info(0, "OnTalk1", "OnTalk"),
            create_test_scene_info(1, "OnTalk2", "OnTalk"),
            create_test_scene_info(2, "OnTalk3", "OnTalk"),
        ],
        prefix_index: {
            let mut map = RadixMap::new();
            map.insert(b"OnTalk", vec![SceneId(0), SceneId(1), SceneId(2)]);
            map
        },
        cache: HashMap::new(),
        random_selector: selector,
        shuffle_enabled: false,
    };

    // 1周目: 全候補を収集
    let mut first_cycle: std::collections::HashSet<SceneId> = std::collections::HashSet::new();
    for _ in 0..3 {
        first_cycle.insert(table.resolve_scene_id("OnTalk", &HashMap::new()).unwrap());
    }
    assert_eq!(first_cycle.len(), 3);

    // 2周目: リセット後の全候補を収集
    let mut second_cycle: std::collections::HashSet<SceneId> = std::collections::HashSet::new();
    for _ in 0..3 {
        second_cycle.insert(table.resolve_scene_id("OnTalk", &HashMap::new()).unwrap());
    }
    assert_eq!(second_cycle.len(), 3);

    // 候補セットが同一であること
    assert_eq!(
        first_cycle, second_cycle,
        "リセット後に候補リストが変化している"
    );
}

#[test]
fn test_resolve_scene_id_unified_cycling() {
    // Task 3.4: unified版での循環リセット動作検証
    use crate::registry::SceneRegistry;

    let mut registry = SceneRegistry::new();
    let (_, counter) = registry.register_global("会話", HashMap::new());
    registry.register_local("選択肢A", "会話", counter, 1, HashMap::new());
    registry.register_local("選択肢B", "会話", counter, 2, HashMap::new());

    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = SceneTable::from_scene_registry(registry, selector).unwrap();
    table.set_shuffle_enabled(false);

    // 1周目: 2件のローカル候補を消費
    let r1 = table.resolve_scene_id_unified("会話_1", "選択肢", &HashMap::new());
    assert!(r1.is_ok());
    let r2 = table.resolve_scene_id_unified("会話_1", "選択肢", &HashMap::new());
    assert!(r2.is_ok());

    // 3回目: 循環リセットが発生し、成功すること
    let r3 = table.resolve_scene_id_unified("会話_1", "選択肢", &HashMap::new());
    assert!(
        r3.is_ok(),
        "unified版で3回目の呼び出しが失敗: 循環リセットが動作していない"
    );

    // 返却されたSceneIdが有効なローカルシーンであること
    let scene = table.get_scene(r3.unwrap()).unwrap();
    assert!(scene.parent.is_some(), "返却されたシーンがローカルでない");
}

#[test]
fn test_resolve_scene_id_unified_cache_key_includes_module() {
    use crate::registry::SceneRegistry;

    // Create registry with same-named local scenes in different modules
    let mut registry = SceneRegistry::new();
    let (_, counter1) = registry.register_global("会話A", HashMap::new());
    registry.register_local("選択肢", "会話A", counter1, 1, HashMap::new());
    let (_, counter2) = registry.register_global("会話B", HashMap::new());
    registry.register_local("選択肢", "会話B", counter2, 1, HashMap::new());

    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = SceneTable::from_scene_registry(registry, selector).unwrap();
    table.set_shuffle_enabled(false);

    // Resolve from 会話A_1
    let result_a = table.resolve_scene_id_unified("会話A_1", "選択肢", &HashMap::new());
    assert!(result_a.is_ok());

    // Resolve from 会話B_1 - should use different cache key
    let result_b = table.resolve_scene_id_unified("会話B_1", "選択肢", &HashMap::new());
    assert!(result_b.is_ok());

    // Both should succeed (different cache keys)
    // The scenes should be different
    let scene_a = table.get_scene(result_a.unwrap()).unwrap();
    let scene_b = table.get_scene(result_b.unwrap()).unwrap();
    assert_ne!(scene_a.fn_name, scene_b.fn_name);
}