1
use std::sync::Arc;
2

            
3
use oo7::dbus;
4
use tokio_stream::StreamExt;
5

            
6
use crate::{service::PrompterType, tests::TestServiceSetup};
7

            
8
#[tokio::test]
9
async fn create_item_plain() -> Result<(), Box<dyn std::error::Error>> {
10
    let setup = TestServiceSetup::plain_session(true).await?;
11

            
12
    // Get initial modified timestamp
13
    let initial_modified = setup.collections[0].modified().await?;
14

            
15
    // Wait to ensure timestamp will be different
16
    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
17

            
18
    // Create an item
19
    let item = setup
20
        .create_item(
21
            "Test Item",
22
            &[("application", "test-app"), ("type", "password")],
23
            "my-secret-password",
24
            false,
25
        )
26
        .await?;
27

            
28
    // Verify item exists in collection
29
    let items = setup.collections[0].items().await?;
30
    assert_eq!(items.len(), 1, "Collection should have one item");
31
    assert_eq!(items[0].inner().path(), item.inner().path());
32

            
33
    // Verify item label
34
    let label = item.label().await?;
35
    assert_eq!(label, "Test Item");
36

            
37
    // Verify modified timestamp was updated
38
    let new_modified = setup.collections[0].modified().await?;
39
    assert!(
40
        new_modified > initial_modified,
41
        "Modified timestamp should be updated after creating item"
42
    );
43

            
44
    Ok(())
45
}
46

            
47
#[tokio::test]
48
async fn create_item_encrypted() -> Result<(), Box<dyn std::error::Error>> {
49
    let setup = TestServiceSetup::encrypted_session(true).await?;
50

            
51
    // Create an encrypted item using the helper (automatically handles encryption)
52
    let item = setup
53
        .create_item(
54
            "Test Encrypted Item",
55
            &[("application", "test-app"), ("type", "encrypted-password")],
56
            "my-encrypted-secret",
57
            false,
58
        )
59
        .await?;
60

            
61
    // Verify item exists
62
    let items = setup.collections[0].items().await?;
63
    assert_eq!(items.len(), 1, "Collection should have one item");
64
    assert_eq!(items[0].inner().path(), item.inner().path());
65

            
66
    Ok(())
67
}
68

            
69
#[tokio::test]
70
async fn search_items_after_creation() -> Result<(), Box<dyn std::error::Error>> {
71
    let setup = TestServiceSetup::plain_session(true).await?;
72

            
73
    // Create two items with different attributes
74
    setup
75
        .create_item(
76
            "Firefox Password",
77
            &[("application", "firefox"), ("username", "user1")],
78
            "password1",
79
            false,
80
        )
81
        .await?;
82

            
83
    setup
84
        .create_item(
85
            "Chrome Password",
86
            &[("application", "chrome"), ("username", "user2")],
87
            "password2",
88
            false,
89
        )
90
        .await?;
91

            
92
    // Search for firefox item
93
    let firefox_attrs = &[("application", "firefox")];
94
    let firefox_items = setup.collections[0].search_items(firefox_attrs).await?;
95

            
96
    assert_eq!(firefox_items.len(), 1, "Should find one firefox item");
97

            
98
    // Search for chrome item
99
    let chrome_items = setup.collections[0]
100
        .search_items(&[("application", "chrome")])
101
        .await?;
102

            
103
    assert_eq!(chrome_items.len(), 1, "Should find one chrome item");
104

            
105
    // Search for non-existent item
106
    let nonexistent_items = setup.collections[0]
107
        .search_items(&[("application", "nonexistent")])
108
        .await?;
109

            
110
    assert_eq!(
111
        nonexistent_items.len(),
112
        0,
113
        "Should find no nonexistent items"
114
    );
115

            
116
    Ok(())
117
}
118

            
119
#[tokio::test]
120
async fn search_items_subset_matching() -> Result<(), Box<dyn std::error::Error>> {
121
    let setup = TestServiceSetup::plain_session(true).await?;
122

            
123
    // Create an item with multiple attributes (url and username)
124
    setup
125
        .create_item(
126
            "Zed Login",
127
            &[("url", "https://zed.dev"), ("username", "alice")],
128
            "my-password",
129
            false,
130
        )
131
        .await?;
132

            
133
    // Search with only the url attribute (subset of stored attributes)
134
    let results = setup.collections[0]
135
        .search_items(&[("url", "https://zed.dev")])
136
        .await?;
137

            
138
    assert_eq!(
139
        results.len(),
140
        1,
141
        "Should find item when searching with subset of its attributes"
142
    );
143

            
144
    // Search with only the username attribute (another subset)
145
    let results = setup.collections[0]
146
        .search_items(&[("username", "alice")])
147
        .await?;
148

            
149
    assert_eq!(
150
        results.len(),
151
        1,
152
        "Should find item when searching with different subset of its attributes"
153
    );
154

            
155
    // Search with both attributes (exact match)
156
    let results = setup.collections[0]
157
        .search_items(&[("url", "https://zed.dev"), ("username", "alice")])
158
        .await?;
159

            
160
    assert_eq!(
161
        results.len(),
162
        1,
163
        "Should find item when searching with all its attributes"
164
    );
165

            
166
    // Search with superset of attributes (should not match)
167
    let results = setup.collections[0]
168
        .search_items(&[
169
            ("url", "https://zed.dev"),
170
            ("username", "alice"),
171
            ("extra", "attribute"),
172
        ])
173
        .await?;
174

            
175
    assert_eq!(
176
        results.len(),
177
        0,
178
        "Should not find item when searching with superset of its attributes"
179
    );
180

            
181
    Ok(())
182
}
183

            
184
#[tokio::test]
185
async fn create_item_with_replace() -> Result<(), Box<dyn std::error::Error>> {
186
    let setup = TestServiceSetup::plain_session(true).await?;
187

            
188
    // Create first item
189
    let secret1 = oo7::Secret::text("original-password");
190
    let item1 = setup
191
        .create_item(
192
            "Test Item",
193
            &[("application", "myapp"), ("username", "user")],
194
            secret1.clone(),
195
            false,
196
        )
197
        .await?;
198

            
199
    // Verify one item exists
200
    let items = setup.collections[0].items().await?;
201
    assert_eq!(items.len(), 1, "Should have one item");
202

            
203
    // Get the secret from first item
204
    let retrieved1 = item1.secret(&setup.session).await?;
205
    assert_eq!(retrieved1.value(), secret1.as_bytes());
206

            
207
    // Create second item with same attributes and replace=true
208
    let secret2 = oo7::Secret::text("replaced-password");
209
    let item2 = setup
210
        .create_item(
211
            "Test Item",
212
            &[("application", "myapp"), ("username", "user")],
213
            secret2.clone(),
214
            true, // replace=true
215
        )
216
        .await?;
217

            
218
    // Should still have only one item (replaced)
219
    let items = setup.collections[0].items().await?;
220
    assert_eq!(items.len(), 1, "Should still have one item after replace");
221

            
222
    // Verify the new item has the updated secret
223
    let retrieved2 = item2.secret(&setup.session).await?;
224
    assert_eq!(retrieved2.value(), secret2.as_bytes());
225

            
226
    Ok(())
227
}
228

            
229
#[tokio::test]
230
async fn label_property() -> Result<(), Box<dyn std::error::Error>> {
231
    let setup = TestServiceSetup::plain_session(true).await?;
232

            
233
    // Get the Login collection via alias (don't rely on collection ordering)
234
    let login_collection = setup
235
        .service_api
236
        .read_alias("default")
237
        .await?
238
        .expect("Default collection should exist");
239

            
240
    // Get initial label (should be "Login" for default collection)
241
    let label = login_collection.label().await?;
242
    assert_eq!(label, "Login");
243

            
244
    // Get initial modified timestamp
245
    let initial_modified = login_collection.modified().await?;
246

            
247
    // Wait to ensure timestamp will be different
248
    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
249

            
250
    // Set new label
251
    login_collection.set_label("My Custom Collection").await?;
252

            
253
    // Verify new label
254
    let label = login_collection.label().await?;
255
    assert_eq!(label, "My Custom Collection");
256

            
257
    // Verify modified timestamp was updated
258
    let new_modified = login_collection.modified().await?;
259
    assert!(
260
        new_modified > initial_modified,
261
        "Modified timestamp should be updated after label change"
262
    );
263

            
264
    Ok(())
265
}
266

            
267
#[tokio::test]
268
async fn timestamps() -> Result<(), Box<dyn std::error::Error>> {
269
    let setup = TestServiceSetup::plain_session(true).await?;
270

            
271
    // Get created timestamp
272
    let created = setup.collections[0].created().await?;
273
    assert!(created.as_secs() > 0, "Created timestamp should be set");
274

            
275
    // Get modified timestamp
276
    let modified = setup.collections[0].modified().await?;
277
    assert!(modified.as_secs() > 0, "Modified timestamp should be set");
278

            
279
    // Created and modified should be close (within a second for new collection)
280
    let diff = if created > modified {
281
        created.as_secs() - modified.as_secs()
282
    } else {
283
        modified.as_secs() - created.as_secs()
284
    };
285
    assert!(diff <= 1, "Created and modified should be within 1 second");
286

            
287
    Ok(())
288
}
289

            
290
#[tokio::test]
291
async fn create_item_invalid_session() -> Result<(), Box<dyn std::error::Error>> {
292
    let setup = TestServiceSetup::plain_session(true).await?;
293

            
294
    // Create an item using the proper API
295
    let secret = oo7::Secret::text("my-secret-password");
296
    let invalid_session =
297
        dbus::api::Session::new(&setup.client_conn, "/invalid/session/path").await?;
298
    let dbus_secret = dbus::api::DBusSecret::new(Arc::new(invalid_session), secret.clone());
299

            
300
    let result = setup.collections[0]
301
        .create_item(
302
            "Test Item",
303
            &[("application", "test-app"), ("type", "password")],
304
            &dbus_secret,
305
            false,
306
            None,
307
        )
308
        .await;
309

            
310
    assert!(
311
        matches!(
312
            result,
313
            Err(oo7::dbus::Error::Service(
314
                oo7::dbus::ServiceError::NoSession(_)
315
            ))
316
        ),
317
        "Should be NoSession error"
318
    );
319

            
320
    Ok(())
321
}
322

            
323
#[tokio::test]
324
async fn item_created_signal() -> Result<(), Box<dyn std::error::Error>> {
325
    let setup = TestServiceSetup::plain_session(true).await?;
326

            
327
    // Subscribe to ItemCreated signal
328
    let signal_stream = setup.collections[0].receive_item_created().await?;
329
    tokio::pin!(signal_stream);
330

            
331
    // Create an item
332
    let item = setup
333
        .create_item("Test Item", &[("app", "test")], "test-secret", false)
334
        .await?;
335

            
336
    // Wait for signal with timeout
337
    let signal_result =
338
        tokio::time::timeout(tokio::time::Duration::from_secs(1), signal_stream.next()).await;
339

            
340
    assert!(signal_result.is_ok(), "Should receive ItemCreated signal");
341
    let signal = signal_result.unwrap();
342
    assert!(signal.is_some(), "Signal should not be None");
343

            
344
    let signal_item = signal.unwrap();
345
    assert_eq!(
346
        signal_item.inner().path().as_str(),
347
        item.inner().path().as_str(),
348
        "Signal should contain the created item path"
349
    );
350

            
351
    Ok(())
352
}
353

            
354
#[tokio::test]
355
async fn item_deleted_signal() -> Result<(), Box<dyn std::error::Error>> {
356
    let setup = TestServiceSetup::plain_session(true).await?;
357

            
358
    // Create an item
359
    let item = setup
360
        .create_item("Test Item", &[("app", "test")], "test-secret", false)
361
        .await?;
362

            
363
    let item_path = item.inner().path().to_owned();
364

            
365
    // Subscribe to ItemDeleted signal
366
    let signal_stream = setup.collections[0].receive_item_deleted().await?;
367
    tokio::pin!(signal_stream);
368

            
369
    // Delete the item
370
    item.delete(None).await?;
371

            
372
    // Wait for signal with timeout
373
    let signal_result =
374
        tokio::time::timeout(tokio::time::Duration::from_secs(1), signal_stream.next()).await;
375

            
376
    assert!(signal_result.is_ok(), "Should receive ItemDeleted signal");
377
    let signal = signal_result.unwrap();
378
    assert!(signal.is_some(), "Signal should not be None");
379

            
380
    let signal_item = signal.unwrap();
381
    assert_eq!(
382
        signal_item.as_str(),
383
        item_path.as_str(),
384
        "Signal should contain the deleted item path"
385
    );
386

            
387
    Ok(())
388
}
389

            
390
#[tokio::test]
391
async fn collection_changed_signal() -> Result<(), Box<dyn std::error::Error>> {
392
    let setup = TestServiceSetup::plain_session(true).await?;
393

            
394
    // Subscribe to CollectionChanged signal
395
    let signal_stream = setup.service_api.receive_collection_changed().await?;
396
    tokio::pin!(signal_stream);
397

            
398
    // Change the collection label
399
    setup.collections[0]
400
        .set_label("Updated Collection Label")
401
        .await?;
402

            
403
    // Wait for signal with timeout
404
    let signal_result =
405
        tokio::time::timeout(tokio::time::Duration::from_secs(1), signal_stream.next()).await;
406

            
407
    assert!(
408
        signal_result.is_ok(),
409
        "Should receive CollectionChanged signal after label change"
410
    );
411
    let signal = signal_result.unwrap();
412
    assert!(signal.is_some(), "Signal should not be None");
413

            
414
    let signal_collection = signal.unwrap();
415
    assert_eq!(
416
        signal_collection.inner().path().as_str(),
417
        setup.collections[0].inner().path().as_str(),
418
        "Signal should contain the changed collection path"
419
    );
420

            
421
    Ok(())
422
}
423

            
424
#[tokio::test]
425
async fn delete_collection() -> Result<(), Box<dyn std::error::Error>> {
426
    let setup = TestServiceSetup::plain_session(true).await?;
427

            
428
    // Create some items in the collection
429
    setup
430
        .create_item("Item 1", &[("app", "test")], "password1", false)
431
        .await?;
432

            
433
    setup
434
        .create_item("Item 2", &[("app", "test")], "password2", false)
435
        .await?;
436

            
437
    // Verify items were created
438
    let items = setup.collections[0].items().await?;
439
    assert_eq!(items.len(), 2, "Should have 2 items before deletion");
440

            
441
    // Get collection path for later verification
442
    let collection_path = setup.collections[0].inner().path().to_owned();
443

            
444
    // Verify collection exists in service
445
    let collections_before = setup.service_api.collections().await?;
446
    let initial_count = collections_before.len();
447

            
448
    // Delete the collection
449
    setup.collections[0].delete(None).await?;
450

            
451
    // Give the system a moment to process the deletion
452
    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
453

            
454
    // Verify collection is no longer in service's collection list
455
    let collections_after = setup.service_api.collections().await?;
456
    assert_eq!(
457
        collections_after.len(),
458
        initial_count - 1,
459
        "Service should have one less collection after deletion"
460
    );
461

            
462
    // Verify the specific collection is not in the list
463
    let collection_paths: Vec<_> = collections_after
464
        .iter()
465
        .map(|c| c.inner().path().as_str())
466
        .collect();
467
    assert!(
468
        !collection_paths.contains(&collection_path.as_str()),
469
        "Deleted collection should not be in service collections list"
470
    );
471

            
472
    Ok(())
473
}
474

            
475
#[tokio::test]
476
async fn collection_deleted_signal() -> Result<(), Box<dyn std::error::Error>> {
477
    let setup = TestServiceSetup::plain_session(true).await?;
478

            
479
    // Subscribe to CollectionDeleted signal
480
    let signal_stream = setup.service_api.receive_collection_deleted().await?;
481
    tokio::pin!(signal_stream);
482

            
483
    let collection_path = setup.collections[0].inner().path().to_owned();
484

            
485
    // Delete the collection
486
    setup.collections[0].delete(None).await?;
487

            
488
    // Wait for signal with timeout
489
    let signal_result =
490
        tokio::time::timeout(tokio::time::Duration::from_secs(1), signal_stream.next()).await;
491

            
492
    assert!(
493
        signal_result.is_ok(),
494
        "Should receive CollectionDeleted signal"
495
    );
496
    let signal = signal_result.unwrap();
497
    assert!(signal.is_some(), "Signal should not be None");
498

            
499
    let signal_collection = signal.unwrap();
500
    assert_eq!(
501
        signal_collection.as_str(),
502
        collection_path.as_str(),
503
        "Signal should contain the deleted collection path"
504
    );
505

            
506
    Ok(())
507
}
508

            
509
4
async fn create_item_in_locked_collection_impl(
510
    prompter_type: PrompterType,
511
) -> Result<(), Box<dyn std::error::Error>> {
512
12
    let setup = TestServiceSetup::plain_session(true).await?;
513
8
    setup.server.set_prompter_type(prompter_type).await;
514

            
515
4
    setup.lock_collection(&setup.collections[0]).await?;
516

            
517
    assert!(
518
        setup.collections[0].is_locked().await?,
519
        "Collection should be locked"
520
    );
521

            
522
4
    let secret = oo7::Secret::text("test-password");
523
12
    let item = setup
524
        .create_item(
525
            "Test Item",
526
            &[("app", "test"), ("type", "password")],
527
4
            secret.clone(),
528
            false,
529
        )
530
16
        .await?;
531

            
532
    assert!(
533
        !setup.collections[0].is_locked().await?,
534
        "Collection should be unlocked after prompt"
535
    );
536

            
537
12
    let items = setup.collections[0].items().await?;
538
    assert_eq!(items.len(), 1, "Collection should have one item");
539
    assert_eq!(
540
        items[0].inner().path(),
541
        item.inner().path(),
542
        "Created item should be in the collection"
543
    );
544

            
545
15
    let label = item.label().await?;
546
    assert_eq!(label, "Test Item", "Item should have correct label");
547

            
548
19
    let attributes = item.attributes().await?;
549
    assert_eq!(attributes.get("app"), Some(&"test".to_string()));
550
    assert_eq!(attributes.get("type"), Some(&"password".to_string()));
551

            
552
13
    let retrieved_secret = item.secret(&setup.session).await?;
553
    assert_eq!(retrieved_secret.value(), secret.as_bytes());
554

            
555
5
    Ok(())
556
}
557

            
558
#[cfg(any(feature = "gnome_native_crypto", feature = "gnome_openssl_crypto"))]
559
#[tokio::test]
560
async fn create_item_in_locked_collection_gnome() -> Result<(), Box<dyn std::error::Error>> {
561
    create_item_in_locked_collection_impl(PrompterType::GNOME).await
562
}
563

            
564
#[cfg(any(feature = "plasma_native_crypto", feature = "plasma_openssl_crypto"))]
565
#[tokio::test]
566
async fn create_item_in_locked_collection_plasma() -> Result<(), Box<dyn std::error::Error>> {
567
    create_item_in_locked_collection_impl(PrompterType::Plasma).await
568
}
569

            
570
4
async fn delete_locked_collection_with_prompt_impl(
571
    prompter_type: PrompterType,
572
) -> Result<(), Box<dyn std::error::Error>> {
573
12
    let setup = TestServiceSetup::plain_session(true).await?;
574
8
    setup.server.set_prompter_type(prompter_type).await;
575
8
    let default_collection = setup.default_collection().await?;
576

            
577
5
    setup.lock_collection(default_collection).await?;
578

            
579
    assert!(
580
        default_collection.is_locked().await?,
581
        "Collection should be locked"
582
    );
583

            
584
10
    let collection_path = default_collection.inner().path().to_owned();
585

            
586
    // Get initial collection count
587
10
    let collections_before = setup.service_api.collections().await?;
588
10
    let initial_count = collections_before.len();
589

            
590
    // Delete the locked collection
591
8
    default_collection.delete(None).await?;
592

            
593
    // Give the system a moment to process the deletion
594
9
    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
595

            
596
    // Verify collection was deleted
597
5
    let collections_after = setup.service_api.collections().await?;
598
    assert_eq!(
599
        collections_after.len(),
600
        initial_count - 1,
601
        "Collection should be deleted after prompt"
602
    );
603

            
604
    // Verify the specific collection is not in the list
605
5
    let collection_paths: Vec<_> = collections_after
606
        .iter()
607
12
        .map(|c| c.inner().path().as_str())
608
        .collect();
609
    assert!(
610
        !collection_paths.contains(&collection_path.as_str()),
611
        "Deleted collection should not be in service collections list"
612
    );
613

            
614
4
    Ok(())
615
}
616

            
617
#[cfg(any(feature = "gnome_native_crypto", feature = "gnome_openssl_crypto"))]
618
#[tokio::test]
619
async fn delete_locked_collection_with_prompt_gnome() -> Result<(), Box<dyn std::error::Error>> {
620
    delete_locked_collection_with_prompt_impl(PrompterType::GNOME).await
621
}
622

            
623
#[cfg(any(feature = "plasma_native_crypto", feature = "plasma_openssl_crypto"))]
624
#[tokio::test]
625
async fn delete_locked_collection_with_prompt_plasma() -> Result<(), Box<dyn std::error::Error>> {
626
    delete_locked_collection_with_prompt_impl(PrompterType::Plasma).await
627
}
628

            
629
38
async fn unlock_retry_impl(prompter_type: PrompterType) -> Result<(), Box<dyn std::error::Error>> {
630
12
    let setup = TestServiceSetup::plain_session(true).await?;
631
8
    setup.server.set_prompter_type(prompter_type).await;
632
8
    let default_collection = setup.default_collection().await?;
633

            
634
4
    let dbus_secret = setup.create_dbus_secret("test-secret-data")?;
635
16
    default_collection
636
4
        .create_item("Test Item", &[("app", "test")], &dbus_secret, false, None)
637
20
        .await?;
638

            
639
4
    setup.lock_collection(default_collection).await?;
640

            
641
    assert!(
642
        default_collection.is_locked().await?,
643
        "Collection should be locked"
644
    );
645

            
646
4
    setup
647
12
        .set_password_queue(vec![
648
4
            oo7::Secret::from("wrong-password"),
649
4
            oo7::Secret::from("wrong-password2"),
650
4
            oo7::Secret::from("test-password-long-enough"),
651
        ])
652
8
        .await;
653

            
654
16
    let unlocked = setup
655
        .service_api
656
4
        .unlock(&[default_collection.inner().path()], None)
657
16
        .await?;
658

            
659
    assert_eq!(unlocked.len(), 1, "Should have unlocked 1 collection");
660
    assert_eq!(
661
        unlocked[0].as_str(),
662
        default_collection.inner().path().as_str(),
663
        "Should return the collection path"
664
    );
665
    assert!(
666
        !default_collection.is_locked().await?,
667
        "Collection should be unlocked after retry with correct password"
668
    );
669

            
670
4
    Ok(())
671
}
672

            
673
#[cfg(any(feature = "gnome_native_crypto", feature = "gnome_openssl_crypto"))]
674
#[tokio::test]
675
async fn unlock_retry_gnome() -> Result<(), Box<dyn std::error::Error>> {
676
    unlock_retry_impl(PrompterType::GNOME).await
677
}
678

            
679
#[cfg(any(feature = "plasma_native_crypto", feature = "plasma_openssl_crypto"))]
680
#[tokio::test]
681
async fn unlock_retry_plasma() -> Result<(), Box<dyn std::error::Error>> {
682
    unlock_retry_impl(PrompterType::Plasma).await
683
}
684

            
685
#[tokio::test]
686
async fn locked_collection_operations() -> Result<(), Box<dyn std::error::Error>> {
687
    let setup = TestServiceSetup::plain_session(true).await?;
688

            
689
    // Verify collection is unlocked initially
690
    assert!(
691
        !setup.collections[0].is_locked().await?,
692
        "Collection should start unlocked"
693
    );
694

            
695
    // Lock the collection
696
    setup.lock_collection(&setup.collections[0]).await?;
697

            
698
    // Verify collection is now locked
699
    assert!(
700
        setup.collections[0].is_locked().await?,
701
        "Collection should be locked"
702
    );
703

            
704
    // Test 1: set_label should fail with IsLocked
705
    let result = setup.collections[0].set_label("New Label").await;
706
    assert!(
707
        matches!(result, Err(oo7::dbus::Error::ZBus(zbus::Error::FDO(_)))),
708
        "set_label should fail with IsLocked error, got: {:?}",
709
        result
710
    );
711

            
712
    // Verify read-only operations still work on locked collections
713
    assert!(
714
        setup.collections[0].label().await.is_ok(),
715
        "Should be able to read label of locked collection"
716
    );
717

            
718
    let items = setup.collections[0].items().await?;
719
    assert!(
720
        items.is_empty(),
721
        "Should be able to read items (empty) from locked collection"
722
    );
723

            
724
    Ok(())
725
}
726

            
727
#[test]
728
fn test_collection_path() {
729
    use crate::collection::collection_path;
730

            
731
    assert!(collection_path("").is_err());
732
    assert_eq!(
733
        collection_path("bär").unwrap().as_str(),
734
        "/org/freedesktop/secrets/collection/b_r"
735
    );
736
}