1
use std::sync::Arc;
2

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

            
6
use crate::tests::{TestServiceSetup, gnome_prompter_test, plasma_prompter_test};
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 secret = oo7::Secret::text("test-secret");
13
    let dbus_secret = dbus::api::DBusSecret::new(setup.session, secret);
14

            
15
    let item = setup.collections[0]
16
        .create_item(
17
            "Original Label",
18
            &[("app", "test")],
19
            &dbus_secret,
20
            false,
21
            None,
22
        )
23
        .await?;
24

            
25
    // Get label
26
    let label = item.label().await?;
27
    assert_eq!(label, "Original Label");
28

            
29
    // Get initial modified timestamp
30
    let initial_modified = item.modified().await?;
31

            
32
    // Wait to ensure timestamp will be different
33
    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
34

            
35
    // Set label
36
    item.set_label("New Label").await?;
37

            
38
    // Verify new label
39
    let label = item.label().await?;
40
    assert_eq!(label, "New Label");
41

            
42
    // Verify modified timestamp was updated
43
    let new_modified = item.modified().await?;
44
    println!("New modified: {:?}", new_modified);
45
    assert!(
46
        new_modified > initial_modified,
47
        "Modified timestamp should be updated after label change (initial: {:?}, new: {:?})",
48
        initial_modified,
49
        new_modified
50
    );
51

            
52
    Ok(())
53
}
54

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

            
59
    let secret = oo7::Secret::text("test-secret");
60
    let dbus_secret = dbus::api::DBusSecret::new(setup.session, secret);
61

            
62
    let item = setup.collections[0]
63
        .create_item(
64
            "Test Item",
65
            &[("app", "firefox"), ("username", "user@example.com")],
66
            &dbus_secret,
67
            false,
68
            None,
69
        )
70
        .await?;
71

            
72
    // Get attributes
73
    let attrs = item.attributes().await?;
74
    assert_eq!(attrs.get("app").unwrap(), "firefox");
75
    assert_eq!(attrs.get("username").unwrap(), "user@example.com");
76

            
77
    // Get initial modified timestamp
78
    let initial_modified = item.modified().await?;
79

            
80
    // Wait to ensure timestamp will be different
81
    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
82

            
83
    // Set new attributes
84
    item.set_attributes(&[("app", "chrome"), ("username", "newuser@example.com")])
85
        .await?;
86

            
87
    // Verify new attributes
88
    let attrs = item.attributes().await?;
89
    assert_eq!(attrs.get("app").unwrap(), "chrome");
90
    assert_eq!(attrs.get("username").unwrap(), "newuser@example.com");
91

            
92
    // Verify modified timestamp was updated
93
    let new_modified = item.modified().await?;
94
    assert!(
95
        new_modified > initial_modified,
96
        "Modified timestamp should be updated after attributes change"
97
    );
98

            
99
    Ok(())
100
}
101

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

            
106
    let collections = setup.service_api.collections().await?;
107
    let secret = oo7::Secret::text("test-secret");
108
    let dbus_secret = dbus::api::DBusSecret::new(setup.session, secret);
109

            
110
    let item = collections[0]
111
        .create_item("Test Item", &[("app", "test")], &dbus_secret, false, None)
112
        .await?;
113

            
114
    // Get created timestamp
115
    let created = item.created().await?;
116
    assert!(created.as_secs() > 0, "Created timestamp should be set");
117

            
118
    // Get modified timestamp
119
    let modified = item.modified().await?;
120
    assert!(modified.as_secs() > 0, "Modified timestamp should be set");
121

            
122
    // Created and modified should be close (within a second for new item)
123
    let diff = if created > modified {
124
        created.as_secs() - modified.as_secs()
125
    } else {
126
        modified.as_secs() - created.as_secs()
127
    };
128
    assert!(diff <= 1, "Created and modified should be within 1 second");
129
    Ok(())
130
}
131

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

            
136
    let secret = oo7::Secret::blob(b"my-secret-password");
137
    let dbus_secret = dbus::api::DBusSecret::new(Arc::clone(&setup.session), secret.clone());
138

            
139
    let item = setup.collections[0]
140
        .create_item("Test Item", &[("app", "test")], &dbus_secret, false, None)
141
        .await?;
142

            
143
    // Retrieve secret
144
    let retrieved_secret = item.secret(&setup.session).await?;
145
    assert_eq!(retrieved_secret.value(), secret.as_bytes());
146

            
147
    // Verify content-type is preserved
148
    assert_eq!(
149
        retrieved_secret.content_type(),
150
        secret.content_type(),
151
        "Content-type should be preserved"
152
    );
153
    Ok(())
154
}
155

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

            
160
    let aes_key = setup.aes_key.as_ref().unwrap();
161
    let secret = oo7::Secret::text("my-encrypted-secret");
162
    let dbus_secret =
163
        dbus::api::DBusSecret::new_encrypted(Arc::clone(&setup.session), secret.clone(), aes_key)?;
164

            
165
    let item = setup.collections[0]
166
        .create_item("Test Item", &[("app", "test")], &dbus_secret, false, None)
167
        .await?;
168

            
169
    // Retrieve secret
170
    let retrieved_secret = item.secret(&setup.session).await?;
171
    assert_eq!(
172
        retrieved_secret.decrypt(Some(&aes_key.clone()))?.as_bytes(),
173
        secret.as_bytes()
174
    );
175
    // Verify content-type is preserved
176
    assert_eq!(
177
        retrieved_secret
178
            .decrypt(Some(&aes_key.clone()))?
179
            .content_type(),
180
        secret.content_type(),
181
        "Content-type should be preserved"
182
    );
183

            
184
    Ok(())
185
}
186

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

            
191
    let secret = oo7::Secret::text("test-secret");
192
    let dbus_secret = dbus::api::DBusSecret::new(setup.session, secret);
193

            
194
    let item = setup.collections[0]
195
        .create_item("Test Item", &[("app", "test")], &dbus_secret, false, None)
196
        .await?;
197

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

            
202
    // Delete item
203
    item.delete(None).await?;
204

            
205
    // Verify item is deleted
206
    let items = setup.collections[0].items().await?;
207
    assert_eq!(items.len(), 0, "Item should be deleted from collection");
208
    Ok(())
209
}
210

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

            
215
    let original_secret = oo7::Secret::text("original-password");
216
    let dbus_secret =
217
        dbus::api::DBusSecret::new(Arc::clone(&setup.session), original_secret.clone());
218

            
219
    let item = setup.collections[0]
220
        .create_item("Test Item", &[("app", "test")], &dbus_secret, false, None)
221
        .await?;
222

            
223
    // Verify original secret
224
    let retrieved = item.secret(&setup.session).await?;
225
    assert_eq!(retrieved.value(), original_secret.as_bytes());
226
    assert_eq!(
227
        retrieved.content_type(),
228
        original_secret.content_type(),
229
        "Content-type should be preserved"
230
    );
231

            
232
    // Get initial modified timestamp
233
    let initial_modified = item.modified().await?;
234

            
235
    // Wait to ensure timestamp will be different
236
    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
237

            
238
    // Update the secret
239
    let new_secret = oo7::Secret::blob(b"new-password");
240
    let new_dbus_secret =
241
        dbus::api::DBusSecret::new(Arc::clone(&setup.session), new_secret.clone());
242
    item.set_secret(&new_dbus_secret).await?;
243

            
244
    // Verify updated secret
245
    let retrieved = item.secret(&setup.session).await?;
246
    assert_eq!(retrieved.value(), new_secret.as_bytes());
247
    assert_eq!(
248
        retrieved.content_type(),
249
        new_secret.content_type(),
250
        "Content-type should be preserved"
251
    );
252

            
253
    // Verify modified timestamp was updated
254
    let new_modified = item.modified().await?;
255
    assert!(
256
        new_modified > initial_modified,
257
        "Modified timestamp should be updated after secret change"
258
    );
259

            
260
    Ok(())
261
}
262

            
263
#[tokio::test]
264
async fn set_secret_encrypted() -> Result<(), Box<dyn std::error::Error>> {
265
    let setup = TestServiceSetup::encrypted_session(true).await?;
266
    let aes_key = setup.aes_key.unwrap();
267

            
268
    let original_secret = oo7::Secret::text("original-encrypted-password");
269
    let dbus_secret = dbus::api::DBusSecret::new_encrypted(
270
        Arc::clone(&setup.session),
271
        original_secret.clone(),
272
        &aes_key,
273
    )?;
274

            
275
    let item = setup.collections[0]
276
        .create_item("Test Item", &[("app", "test")], &dbus_secret, false, None)
277
        .await?;
278

            
279
    // Verify original secret
280
    let retrieved = item.secret(&setup.session).await?;
281
    assert_eq!(
282
        retrieved.decrypt(Some(&aes_key.clone()))?.as_bytes(),
283
        original_secret.as_bytes()
284
    );
285

            
286
    // Get initial modified timestamp
287
    let initial_modified = item.modified().await?;
288

            
289
    // Wait to ensure timestamp will be different
290
    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
291

            
292
    // Update the secret
293
    let new_secret = oo7::Secret::text("new-encrypted-password");
294
    let new_dbus_secret = dbus::api::DBusSecret::new_encrypted(
295
        Arc::clone(&setup.session),
296
        new_secret.clone(),
297
        &aes_key,
298
    )?;
299
    item.set_secret(&new_dbus_secret).await?;
300

            
301
    // Verify updated secret
302
    let retrieved = item.secret(&setup.session).await?;
303
    assert_eq!(
304
        retrieved.decrypt(Some(&aes_key.clone()))?.as_bytes(),
305
        new_secret.as_bytes()
306
    );
307

            
308
    // Verify modified timestamp was updated
309
    let new_modified = item.modified().await?;
310
    assert!(
311
        new_modified > initial_modified,
312
        "Modified timestamp should be updated after secret change"
313
    );
314

            
315
    Ok(())
316
}
317

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

            
322
    let secret = oo7::Secret::text("test-secret");
323
    let dbus_secret = dbus::api::DBusSecret::new(setup.session, secret);
324

            
325
    let item = setup.collections[0]
326
        .create_item("Test Item", &[("app", "test")], &dbus_secret, false, None)
327
        .await?;
328

            
329
    // Try to get secret with invalid session path
330
    let invalid_session =
331
        oo7::dbus::api::Session::new(&setup.client_conn, "/invalid/session").await?;
332
    let result = item.secret(&invalid_session).await;
333

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

            
344
    Ok(())
345
}
346

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

            
351
    let secret = oo7::Secret::text("test-secret");
352
    let dbus_secret = dbus::api::DBusSecret::new(Arc::clone(&setup.session), secret);
353

            
354
    let item = setup.collections[0]
355
        .create_item("Test Item", &[("app", "test")], &dbus_secret, false, None)
356
        .await?;
357

            
358
    let new_secret = oo7::Secret::text("new-secret");
359
    let invalid_dbus_secret = dbus::api::DBusSecret::new(
360
        Arc::new(dbus::api::Session::new(&setup.client_conn, "/invalid/session").await?),
361
        new_secret,
362
    );
363

            
364
    let result = item.set_secret(&invalid_dbus_secret).await;
365

            
366
    // Should return NoSession error
367
    assert!(
368
        matches!(
369
            result,
370
            Err(oo7::dbus::Error::Service(
371
                oo7::dbus::ServiceError::NoSession(_)
372
            ))
373
        ),
374
        "Should be NoSession error"
375
    );
376

            
377
    Ok(())
378
}
379

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

            
384
    let secret = oo7::Secret::text("test-secret");
385
    let dbus_secret = dbus::api::DBusSecret::new(Arc::clone(&setup.session), secret);
386

            
387
    let item = setup.collections[0]
388
        .create_item("Test Item", &[("app", "test")], &dbus_secret, false, None)
389
        .await?;
390

            
391
    // Subscribe to ItemChanged signal
392
    let signal_stream = setup.collections[0].receive_item_changed().await?;
393
    tokio::pin!(signal_stream);
394

            
395
    // Change the label
396
    item.set_label("Updated Label").await?;
397

            
398
    // Wait for signal
399
    let signal_result =
400
        tokio::time::timeout(tokio::time::Duration::from_secs(1), signal_stream.next()).await;
401

            
402
    assert!(
403
        signal_result.is_ok(),
404
        "Should receive ItemChanged signal after label change"
405
    );
406
    let signal = signal_result.unwrap();
407
    assert!(signal.is_some(), "Signal should not be None");
408

            
409
    let signal_item = signal.unwrap();
410
    assert_eq!(
411
        signal_item.inner().path().as_str(),
412
        item.inner().path().as_str(),
413
        "Signal should contain the changed item path"
414
    );
415

            
416
    // Change attributes and verify signal again
417
    item.set_attributes(&[("app", "updated-app")]).await?;
418

            
419
    let signal_result =
420
        tokio::time::timeout(tokio::time::Duration::from_secs(1), signal_stream.next()).await;
421

            
422
    assert!(
423
        signal_result.is_ok(),
424
        "Should receive ItemChanged signal after attributes change"
425
    );
426

            
427
    // Change secret and verify signal again
428
    let new_secret = oo7::Secret::text("new-secret");
429
    let new_dbus_secret = dbus::api::DBusSecret::new(Arc::clone(&setup.session), new_secret);
430
    item.set_secret(&new_dbus_secret).await?;
431

            
432
    let signal_result =
433
        tokio::time::timeout(tokio::time::Duration::from_secs(1), signal_stream.next()).await;
434

            
435
    assert!(
436
        signal_result.is_ok(),
437
        "Should receive ItemChanged signal after secret change"
438
    );
439

            
440
    Ok(())
441
}
442

            
443
gnome_prompter_test!(
444
    delete_locked_item_with_prompt_gnome,
445
    delete_locked_item_with_prompt
446
);
447
plasma_prompter_test!(
448
    delete_locked_item_with_prompt_plasma,
449
    delete_locked_item_with_prompt
450
);
451

            
452
20
async fn delete_locked_item_with_prompt() -> Result<(), Box<dyn std::error::Error>> {
453
6
    let setup = TestServiceSetup::plain_session(true).await?;
454
6
    let default_collection = setup.default_collection().await?;
455

            
456
2
    let secret = oo7::Secret::text("test-password");
457
2
    let dbus_secret = dbus::api::DBusSecret::new(Arc::clone(&setup.session), secret.clone());
458

            
459
8
    let item = default_collection
460
2
        .create_item("Test Item", &[("app", "test")], &dbus_secret, false, None)
461
8
        .await?;
462

            
463
6
    let items = default_collection.items().await?;
464
    assert_eq!(items.len(), 1, "Should have one item");
465

            
466
8
    let collection = setup
467
        .server
468
4
        .collection_from_path(default_collection.inner().path())
469
6
        .await
470
        .expect("Collection should exist");
471
8
    collection
472
4
        .set_locked(true, setup.keyring_secret.clone())
473
6
        .await?;
474

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

            
477
6
    item.delete(None).await?;
478

            
479
4
    let items = default_collection.items().await?;
480
    assert_eq!(items.len(), 0, "Item should be deleted after prompt");
481

            
482
2
    Ok(())
483
}
484

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

            
489
    // Create an item
490
    let secret = oo7::Secret::text("test-password");
491
    let dbus_secret = dbus::api::DBusSecret::new(Arc::clone(&setup.session), secret.clone());
492

            
493
    let item = setup.collections[0]
494
        .create_item("Test Item", &[("app", "test")], &dbus_secret, false, None)
495
        .await?;
496

            
497
    // Verify item is unlocked initially
498
    assert!(!item.is_locked().await?, "Item should start unlocked");
499

            
500
    // Lock the collection (which locks the item)
501
    let collection = setup
502
        .server
503
        .collection_from_path(setup.collections[0].inner().path())
504
        .await
505
        .expect("Collection should exist");
506
    collection
507
        .set_locked(true, setup.keyring_secret.clone())
508
        .await?;
509

            
510
    // Verify item is now locked
511
    assert!(
512
        item.is_locked().await?,
513
        "Item should be locked after locking collection"
514
    );
515

            
516
    // Test 1: get_secret should fail with IsLocked
517
    let result = item.secret(&setup.session).await;
518
    assert!(
519
        matches!(
520
            result,
521
            Err(oo7::dbus::Error::Service(
522
                oo7::dbus::ServiceError::IsLocked(_)
523
            ))
524
        ),
525
        "get_secret should fail with IsLocked error, got: {:?}",
526
        result
527
    );
528

            
529
    // Test 2: set_secret should fail with IsLocked
530
    let new_secret = oo7::Secret::text("new-password");
531
    let new_dbus_secret = dbus::api::DBusSecret::new(Arc::clone(&setup.session), new_secret);
532
    let result = item.set_secret(&new_dbus_secret).await;
533
    assert!(
534
        matches!(
535
            result,
536
            Err(oo7::dbus::Error::Service(
537
                oo7::dbus::ServiceError::IsLocked(_)
538
            ))
539
        ),
540
        "set_secret should fail with IsLocked error, got: {:?}",
541
        result
542
    );
543

            
544
    // Test 3: set_attributes should fail with IsLocked
545
    let result = item.set_attributes(&[("app", "new-app")]).await;
546
    assert!(
547
        matches!(result, Err(oo7::dbus::Error::ZBus(zbus::Error::FDO(_)))),
548
        "set_attributes should fail with IsLocked error, got: {:?}",
549
        result
550
    );
551

            
552
    // Test 4: set_label should fail with IsLocked
553
    let result = item.set_label("New Label").await;
554
    assert!(
555
        matches!(result, Err(oo7::dbus::Error::ZBus(zbus::Error::FDO(_)))),
556
        "set_label should fail with IsLocked error, got: {:?}",
557
        result
558
    );
559

            
560
    // Test 5: Reading properties should also fail on locked items
561
    let result = item.label().await;
562
    assert!(
563
        matches!(result, Err(oo7::dbus::Error::ZBus(zbus::Error::FDO(_)))),
564
        "label should fail on locked item, got: {:?}",
565
        result
566
    );
567

            
568
    let result = item.attributes().await;
569
    assert!(
570
        matches!(result, Err(oo7::dbus::Error::ZBus(zbus::Error::FDO(_)))),
571
        "attributes should fail on locked item, got: {:?}",
572
        result
573
    );
574

            
575
    let result = item.created().await;
576
    assert!(
577
        matches!(result, Err(oo7::dbus::Error::ZBus(zbus::Error::FDO(_)))),
578
        "created should fail on locked item, got: {:?}",
579
        result
580
    );
581

            
582
    let result = item.modified().await;
583
    assert!(
584
        matches!(result, Err(oo7::dbus::Error::ZBus(zbus::Error::FDO(_)))),
585
        "modified should fail on locked item, got: {:?}",
586
        result
587
    );
588

            
589
    Ok(())
590
}