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 label_property() -> Result<(), Box<dyn std::error::Error>> {
10
    let setup = TestServiceSetup::plain_session(true).await?;
11

            
12
    let item = setup
13
        .create_item("Original Label", &[("app", "test")], "test-secret", false)
14
        .await?;
15

            
16
    // Get label
17
    let label = item.label().await?;
18
    assert_eq!(label, "Original Label");
19

            
20
    // Get initial modified timestamp
21
    let initial_modified = item.modified().await?;
22

            
23
    // Wait to ensure timestamp will be different
24
    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
25

            
26
    // Set label
27
    item.set_label("New Label").await?;
28

            
29
    // Verify new label
30
    let label = item.label().await?;
31
    assert_eq!(label, "New Label");
32

            
33
    // Verify modified timestamp was updated
34
    let new_modified = item.modified().await?;
35
    println!("New modified: {:?}", new_modified);
36
    assert!(
37
        new_modified > initial_modified,
38
        "Modified timestamp should be updated after label change (initial: {:?}, new: {:?})",
39
        initial_modified,
40
        new_modified
41
    );
42

            
43
    Ok(())
44
}
45

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

            
50
    let item = setup
51
        .create_item(
52
            "Test Item",
53
            &[("app", "firefox"), ("username", "user@example.com")],
54
            "test-secret",
55
            false,
56
        )
57
        .await?;
58

            
59
    // Get attributes
60
    let attrs = item.attributes().await?;
61
    assert_eq!(attrs.get("app").unwrap(), "firefox");
62
    assert_eq!(attrs.get("username").unwrap(), "user@example.com");
63

            
64
    // Get initial modified timestamp
65
    let initial_modified = item.modified().await?;
66

            
67
    // Wait to ensure timestamp will be different
68
    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
69

            
70
    // Set new attributes
71
    item.set_attributes(&[("app", "chrome"), ("username", "newuser@example.com")])
72
        .await?;
73

            
74
    // Verify new attributes
75
    let attrs = item.attributes().await?;
76
    assert_eq!(attrs.get("app").unwrap(), "chrome");
77
    assert_eq!(attrs.get("username").unwrap(), "newuser@example.com");
78

            
79
    // Verify modified timestamp was updated
80
    let new_modified = item.modified().await?;
81
    assert!(
82
        new_modified > initial_modified,
83
        "Modified timestamp should be updated after attributes change"
84
    );
85

            
86
    Ok(())
87
}
88

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

            
93
    let item = setup
94
        .create_item("Test Item", &[("app", "test")], "test-secret", false)
95
        .await?;
96

            
97
    // Get created timestamp
98
    let created = item.created().await?;
99
    assert!(created.as_secs() > 0, "Created timestamp should be set");
100

            
101
    // Get modified timestamp
102
    let modified = item.modified().await?;
103
    assert!(modified.as_secs() > 0, "Modified timestamp should be set");
104

            
105
    // Created and modified should be close (within a second for new item)
106
    let diff = if created > modified {
107
        created.as_secs() - modified.as_secs()
108
    } else {
109
        modified.as_secs() - created.as_secs()
110
    };
111
    assert!(diff <= 1, "Created and modified should be within 1 second");
112
    Ok(())
113
}
114

            
115
#[tokio::test]
116
async fn secret_retrieval_plain() -> Result<(), Box<dyn std::error::Error>> {
117
    let setup = TestServiceSetup::plain_session(true).await?;
118

            
119
    let secret = oo7::Secret::blob(b"my-secret-password");
120
    let item = setup
121
        .create_item("Test Item", &[("app", "test")], secret.clone(), false)
122
        .await?;
123

            
124
    // Retrieve secret
125
    let retrieved_secret = item.secret(&setup.session).await?;
126
    assert_eq!(retrieved_secret.value(), secret.as_bytes());
127

            
128
    // Verify content-type is preserved
129
    assert_eq!(
130
        retrieved_secret.content_type(),
131
        secret.content_type(),
132
        "Content-type should be preserved"
133
    );
134
    Ok(())
135
}
136

            
137
#[tokio::test]
138
async fn secret_retrieval_encrypted() -> Result<(), Box<dyn std::error::Error>> {
139
    let setup = TestServiceSetup::encrypted_session(true).await?;
140

            
141
    let aes_key = setup.aes_key.as_ref().unwrap();
142
    let secret = oo7::Secret::text("my-encrypted-secret");
143
    let item = setup
144
        .create_item("Test Item", &[("app", "test")], secret.clone(), false)
145
        .await?;
146

            
147
    // Retrieve secret
148
    let retrieved_secret = item.secret(&setup.session).await?;
149
    assert_eq!(
150
        retrieved_secret.decrypt(Some(&aes_key.clone()))?.as_bytes(),
151
        secret.as_bytes()
152
    );
153
    // Verify content-type is preserved
154
    assert_eq!(
155
        retrieved_secret
156
            .decrypt(Some(&aes_key.clone()))?
157
            .content_type(),
158
        secret.content_type(),
159
        "Content-type should be preserved"
160
    );
161

            
162
    Ok(())
163
}
164

            
165
#[tokio::test]
166
async fn delete_item() -> Result<(), Box<dyn std::error::Error>> {
167
    let setup = TestServiceSetup::plain_session(true).await?;
168

            
169
    let item = setup
170
        .create_item("Test Item", &[("app", "test")], "test-secret", false)
171
        .await?;
172

            
173
    // Verify item exists
174
    let items = setup.collections[0].items().await?;
175
    assert_eq!(items.len(), 1);
176

            
177
    // Delete item
178
    item.delete(None).await?;
179

            
180
    // Verify item is deleted
181
    let items = setup.collections[0].items().await?;
182
    assert_eq!(items.len(), 0, "Item should be deleted from collection");
183
    Ok(())
184
}
185

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

            
190
    let original_secret = oo7::Secret::text("original-password");
191
    let item = setup
192
        .create_item(
193
            "Test Item",
194
            &[("app", "test")],
195
            original_secret.clone(),
196
            false,
197
        )
198
        .await?;
199

            
200
    // Verify original secret
201
    let retrieved = item.secret(&setup.session).await?;
202
    assert_eq!(retrieved.value(), original_secret.as_bytes());
203
    assert_eq!(
204
        retrieved.content_type(),
205
        original_secret.content_type(),
206
        "Content-type should be preserved"
207
    );
208

            
209
    // Get initial modified timestamp
210
    let initial_modified = item.modified().await?;
211

            
212
    // Wait to ensure timestamp will be different
213
    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
214

            
215
    // Update the secret
216
    let new_secret = oo7::Secret::blob(b"new-password");
217
    let new_dbus_secret = setup.create_dbus_secret(new_secret.clone())?;
218
    item.set_secret(&new_dbus_secret).await?;
219

            
220
    // Verify updated secret
221
    let retrieved = item.secret(&setup.session).await?;
222
    assert_eq!(retrieved.value(), new_secret.as_bytes());
223
    assert_eq!(
224
        retrieved.content_type(),
225
        new_secret.content_type(),
226
        "Content-type should be preserved"
227
    );
228

            
229
    // Verify modified timestamp was updated
230
    let new_modified = item.modified().await?;
231
    assert!(
232
        new_modified > initial_modified,
233
        "Modified timestamp should be updated after secret change"
234
    );
235

            
236
    Ok(())
237
}
238

            
239
#[tokio::test]
240
async fn set_secret_encrypted() -> Result<(), Box<dyn std::error::Error>> {
241
    let setup = TestServiceSetup::encrypted_session(true).await?;
242
    let aes_key = setup.aes_key.as_ref().unwrap().clone();
243

            
244
    let original_secret = oo7::Secret::text("original-encrypted-password");
245
    let item = setup
246
        .create_item(
247
            "Test Item",
248
            &[("app", "test")],
249
            original_secret.clone(),
250
            false,
251
        )
252
        .await?;
253

            
254
    // Verify original secret
255
    let retrieved = item.secret(&setup.session).await?;
256
    assert_eq!(
257
        retrieved.decrypt(Some(&aes_key.clone()))?.as_bytes(),
258
        original_secret.as_bytes()
259
    );
260

            
261
    // Get initial modified timestamp
262
    let initial_modified = item.modified().await?;
263

            
264
    // Wait to ensure timestamp will be different
265
    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
266

            
267
    // Update the secret
268
    let new_secret = oo7::Secret::text("new-encrypted-password");
269
    let new_dbus_secret = setup.create_dbus_secret(new_secret.clone())?;
270
    item.set_secret(&new_dbus_secret).await?;
271

            
272
    // Verify updated secret
273
    let retrieved = item.secret(&setup.session).await?;
274
    assert_eq!(
275
        retrieved.decrypt(Some(&aes_key.clone()))?.as_bytes(),
276
        new_secret.as_bytes()
277
    );
278

            
279
    // Verify modified timestamp was updated
280
    let new_modified = item.modified().await?;
281
    assert!(
282
        new_modified > initial_modified,
283
        "Modified timestamp should be updated after secret change"
284
    );
285

            
286
    Ok(())
287
}
288

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

            
293
    let item = setup
294
        .create_item("Test Item", &[("app", "test")], "test-secret", false)
295
        .await?;
296

            
297
    // Try to get secret with invalid session path
298
    let invalid_session =
299
        oo7::dbus::api::Session::new(&setup.client_conn, "/invalid/session").await?;
300
    let result = item.secret(&invalid_session).await;
301

            
302
    assert!(
303
        matches!(
304
            result,
305
            Err(oo7::dbus::Error::Service(
306
                oo7::dbus::ServiceError::NoSession(_)
307
            ))
308
        ),
309
        "Should be NoSession error"
310
    );
311

            
312
    Ok(())
313
}
314

            
315
#[tokio::test]
316
async fn set_secret_invalid_session() -> Result<(), Box<dyn std::error::Error>> {
317
    let setup = TestServiceSetup::plain_session(true).await?;
318

            
319
    let item = setup
320
        .create_item("Test Item", &[("app", "test")], "test-secret", false)
321
        .await?;
322

            
323
    let new_secret = oo7::Secret::text("new-secret");
324
    let invalid_dbus_secret = dbus::api::DBusSecret::new(
325
        Arc::new(dbus::api::Session::new(&setup.client_conn, "/invalid/session").await?),
326
        new_secret,
327
    );
328

            
329
    let result = item.set_secret(&invalid_dbus_secret).await;
330

            
331
    // Should return NoSession error
332
    assert!(
333
        matches!(
334
            result,
335
            Err(oo7::dbus::Error::Service(
336
                oo7::dbus::ServiceError::NoSession(_)
337
            ))
338
        ),
339
        "Should be NoSession error"
340
    );
341

            
342
    Ok(())
343
}
344

            
345
#[tokio::test]
346
async fn item_changed_signal() -> Result<(), Box<dyn std::error::Error>> {
347
    let setup = TestServiceSetup::plain_session(true).await?;
348

            
349
    let item = setup
350
        .create_item("Test Item", &[("app", "test")], "test-secret", false)
351
        .await?;
352

            
353
    // Subscribe to ItemChanged signal
354
    let signal_stream = setup.collections[0].receive_item_changed().await?;
355
    tokio::pin!(signal_stream);
356

            
357
    // Change the label
358
    item.set_label("Updated Label").await?;
359

            
360
    // Wait for signal
361
    let signal_result =
362
        tokio::time::timeout(tokio::time::Duration::from_secs(1), signal_stream.next()).await;
363

            
364
    assert!(
365
        signal_result.is_ok(),
366
        "Should receive ItemChanged signal after label change"
367
    );
368
    let signal = signal_result.unwrap();
369
    assert!(signal.is_some(), "Signal should not be None");
370

            
371
    let signal_item = signal.unwrap();
372
    assert_eq!(
373
        signal_item.inner().path().as_str(),
374
        item.inner().path().as_str(),
375
        "Signal should contain the changed item path"
376
    );
377

            
378
    // Change attributes and verify signal again
379
    item.set_attributes(&[("app", "updated-app")]).await?;
380

            
381
    let signal_result =
382
        tokio::time::timeout(tokio::time::Duration::from_secs(1), signal_stream.next()).await;
383

            
384
    assert!(
385
        signal_result.is_ok(),
386
        "Should receive ItemChanged signal after attributes change"
387
    );
388

            
389
    // Change secret and verify signal again
390
    let new_dbus_secret = setup.create_dbus_secret("new-secret")?;
391
    item.set_secret(&new_dbus_secret).await?;
392

            
393
    let signal_result =
394
        tokio::time::timeout(tokio::time::Duration::from_secs(1), signal_stream.next()).await;
395

            
396
    assert!(
397
        signal_result.is_ok(),
398
        "Should receive ItemChanged signal after secret change"
399
    );
400

            
401
    Ok(())
402
}
403

            
404
4
async fn delete_locked_item_with_prompt_impl(
405
    prompter_type: PrompterType,
406
) -> Result<(), Box<dyn std::error::Error>> {
407
12
    let setup = TestServiceSetup::plain_session(true).await?;
408
8
    setup.server.set_prompter_type(prompter_type).await;
409
8
    let default_collection = setup.default_collection().await?;
410

            
411
4
    let dbus_secret = setup.create_dbus_secret("test-password")?;
412

            
413
16
    let item = default_collection
414
4
        .create_item("Test Item", &[("app", "test")], &dbus_secret, false, None)
415
16
        .await?;
416

            
417
12
    let items = default_collection.items().await?;
418
    assert_eq!(items.len(), 1, "Should have one item");
419

            
420
8
    setup.lock_collection(default_collection).await?;
421

            
422
    assert!(item.is_locked().await?, "Item should be locked");
423

            
424
13
    item.delete(None).await?;
425

            
426
9
    let items = default_collection.items().await?;
427
    assert_eq!(items.len(), 0, "Item should be deleted after prompt");
428

            
429
5
    Ok(())
430
}
431

            
432
#[cfg(any(feature = "gnome_native_crypto", feature = "gnome_openssl_crypto"))]
433
#[tokio::test]
434
async fn delete_locked_item_with_prompt_gnome() -> Result<(), Box<dyn std::error::Error>> {
435
    delete_locked_item_with_prompt_impl(PrompterType::GNOME).await
436
}
437

            
438
#[cfg(any(feature = "plasma_native_crypto", feature = "plasma_openssl_crypto"))]
439
#[tokio::test]
440
async fn delete_locked_item_with_prompt_plasma() -> Result<(), Box<dyn std::error::Error>> {
441
    delete_locked_item_with_prompt_impl(PrompterType::Plasma).await
442
}
443

            
444
#[tokio::test]
445
async fn locked_item_operations() -> Result<(), Box<dyn std::error::Error>> {
446
    let setup = TestServiceSetup::plain_session(true).await?;
447

            
448
    // Create an item
449
    let item = setup
450
        .create_item("Test Item", &[("app", "test")], "test-password", false)
451
        .await?;
452

            
453
    // Verify item is unlocked initially
454
    assert!(!item.is_locked().await?, "Item should start unlocked");
455

            
456
    // Lock the collection (which locks the item)
457
    setup.lock_collection(&setup.collections[0]).await?;
458

            
459
    // Verify item is now locked
460
    assert!(
461
        item.is_locked().await?,
462
        "Item should be locked after locking collection"
463
    );
464

            
465
    // Test 1: get_secret should fail with IsLocked
466
    let result = item.secret(&setup.session).await;
467
    assert!(
468
        matches!(
469
            result,
470
            Err(oo7::dbus::Error::Service(
471
                oo7::dbus::ServiceError::IsLocked(_)
472
            ))
473
        ),
474
        "get_secret should fail with IsLocked error, got: {:?}",
475
        result
476
    );
477

            
478
    // Test 2: set_secret should fail with IsLocked
479
    let new_dbus_secret = setup.create_dbus_secret("new-password")?;
480
    let result = item.set_secret(&new_dbus_secret).await;
481
    assert!(
482
        matches!(
483
            result,
484
            Err(oo7::dbus::Error::Service(
485
                oo7::dbus::ServiceError::IsLocked(_)
486
            ))
487
        ),
488
        "set_secret should fail with IsLocked error, got: {:?}",
489
        result
490
    );
491

            
492
    // Test 3: set_attributes should fail with IsLocked
493
    let result = item.set_attributes(&[("app", "new-app")]).await;
494
    assert!(
495
        matches!(result, Err(oo7::dbus::Error::ZBus(zbus::Error::FDO(_)))),
496
        "set_attributes should fail with IsLocked error, got: {:?}",
497
        result
498
    );
499

            
500
    // Test 4: set_label should fail with IsLocked
501
    let result = item.set_label("New Label").await;
502
    assert!(
503
        matches!(result, Err(oo7::dbus::Error::ZBus(zbus::Error::FDO(_)))),
504
        "set_label should fail with IsLocked error, got: {:?}",
505
        result
506
    );
507

            
508
    // Test 5: Reading properties should also fail on locked items
509
    let result = item.label().await;
510
    assert!(
511
        matches!(result, Err(oo7::dbus::Error::ZBus(zbus::Error::FDO(_)))),
512
        "label should fail on locked item, got: {:?}",
513
        result
514
    );
515

            
516
    let result = item.attributes().await;
517
    assert!(
518
        matches!(result, Err(oo7::dbus::Error::ZBus(zbus::Error::FDO(_)))),
519
        "attributes should fail on locked item, got: {:?}",
520
        result
521
    );
522

            
523
    let result = item.created().await;
524
    assert!(
525
        matches!(result, Err(oo7::dbus::Error::ZBus(zbus::Error::FDO(_)))),
526
        "created should fail on locked item, got: {:?}",
527
        result
528
    );
529

            
530
    let result = item.modified().await;
531
    assert!(
532
        matches!(result, Err(oo7::dbus::Error::ZBus(zbus::Error::FDO(_)))),
533
        "modified should fail on locked item, got: {:?}",
534
        result
535
    );
536

            
537
    Ok(())
538
}