/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::collections::VecDeque; use std::sync::Arc; use net::indexeddb::engines::{KvsEngine, KvsOperation, KvsTransaction, SqliteEngine}; use net::indexeddb::idb_thread::IndexedDBDescription; use net::resource_thread::CoreResourceThreadPool; use net_traits::indexeddb_thread::{ AsyncOperation, AsyncReadOnlyOperation, AsyncReadWriteOperation, CreateObjectResult, IndexedDBKeyRange, IndexedDBKeyType, IndexedDBTxnMode, KeyPath, PutItemResult, }; use serde::{Deserialize, Serialize}; use servo_url::ImmutableOrigin; use url::Host; fn test_origin() -> ImmutableOrigin { ImmutableOrigin::Tuple( "test_origin".to_string(), Host::Domain("localhost".to_string()), 80, ) } fn get_pool() -> Arc { Arc::new(CoreResourceThreadPool::new(1, "test".to_string())) } #[test] fn test_cycle() { let base_dir = tempfile::tempdir().expect("Failed to create temp dir"); let thread_pool = get_pool(); // Test create let _ = SqliteEngine::new( base_dir.path(), &IndexedDBDescription { name: "test_db".to_string(), origin: test_origin(), }, thread_pool.clone(), ) .unwrap(); // Test open let db = SqliteEngine::new( base_dir.path(), &IndexedDBDescription { name: "test_db".to_string(), origin: test_origin(), }, thread_pool.clone(), ) .unwrap(); let version = db.version().expect("Failed to get version"); assert_eq!(version, 0); db.set_version(5).unwrap(); let new_version = db.version().expect("Failed to get new version"); assert_eq!(new_version, 5); db.delete_database().expect("Failed to delete database"); } #[test] fn test_create_store() { let base_dir = tempfile::tempdir().expect("Failed to create temp dir"); let thread_pool = get_pool(); let db = SqliteEngine::new( base_dir.path(), &IndexedDBDescription { name: "test_db".to_string(), origin: test_origin(), }, thread_pool, ) .unwrap(); let store_name = "test_store"; let result = db.create_store(store_name, None, true); assert!(result.is_ok()); let create_result = result.unwrap(); assert_eq!(create_result, CreateObjectResult::Created); // Try to create the same store again let result = db.create_store(store_name, None, false); assert!(result.is_ok()); let create_result = result.unwrap(); assert_eq!(create_result, CreateObjectResult::AlreadyExists); // Ensure store was not overwritten assert!(db.has_key_generator(store_name)); } #[test] fn test_create_store_empty_name() { let base_dir = tempfile::tempdir().expect("Failed to create temp dir"); let thread_pool = get_pool(); let db = SqliteEngine::new( base_dir.path(), &IndexedDBDescription { name: "test_db".to_string(), origin: test_origin(), }, thread_pool, ) .unwrap(); let store_name = ""; let result = db.create_store(store_name, None, true); assert!(result.is_ok()); let create_result = result.unwrap(); assert_eq!(create_result, CreateObjectResult::Created); } #[test] fn test_injection() { let base_dir = tempfile::tempdir().expect("Failed to create temp dir"); let thread_pool = get_pool(); let db = SqliteEngine::new( base_dir.path(), &IndexedDBDescription { name: "test_db".to_string(), origin: test_origin(), }, thread_pool, ) .unwrap(); // Create a normal store let store_name1 = "test_store"; let result = db.create_store(store_name1, None, true); assert!(result.is_ok()); let create_result = result.unwrap(); assert_eq!(create_result, CreateObjectResult::Created); // Injection let store_name2 = "' OR 1=1 -- -"; let result = db.create_store(store_name2, None, false); assert!(result.is_ok()); let create_result = result.unwrap(); assert_eq!(create_result, CreateObjectResult::Created); } #[test] fn test_key_path() { let base_dir = tempfile::tempdir().expect("Failed to create temp dir"); let thread_pool = get_pool(); let db = SqliteEngine::new( base_dir.path(), &IndexedDBDescription { name: "test_db".to_string(), origin: test_origin(), }, thread_pool, ) .unwrap(); let store_name = "test_store"; let result = db.create_store(store_name, Some(KeyPath::String("test".to_string())), true); assert!(result.is_ok()); assert_eq!( db.key_path(store_name), Some(KeyPath::String("test".to_string())) ); } #[test] fn test_delete_store() { let base_dir = tempfile::tempdir().expect("Failed to create temp dir"); let thread_pool = get_pool(); let db = SqliteEngine::new( base_dir.path(), &IndexedDBDescription { name: "test_db".to_string(), origin: test_origin(), }, thread_pool, ) .unwrap(); db.create_store("test_store", None, false) .expect("Failed to create store"); // Delete the store db.delete_store("test_store") .expect("Failed to delete store"); // Try to delete the same store again let result = db.delete_store("test_store"); assert!(result.is_err()); // Try to delete a non-existing store let result = db.delete_store("test_store"); // Should work as per spec assert!(result.is_err()); } #[test] fn test_async_operations() { fn get_channel() -> ( ipc_channel::ipc::IpcSender, ipc_channel::ipc::IpcReceiver, ) where T: for<'de> Deserialize<'de> + Serialize, { ipc_channel::ipc::channel().unwrap() } let base_dir = tempfile::tempdir().expect("Failed to create temp dir"); let thread_pool = get_pool(); let db = SqliteEngine::new( base_dir.path(), &IndexedDBDescription { name: "test_db".to_string(), origin: test_origin(), }, thread_pool, ) .unwrap(); let store_name = "test_store"; db.create_store(store_name, None, false) .expect("Failed to create store"); let put = get_channel(); let put2 = get_channel(); let put3 = get_channel(); let put_dup = get_channel(); let get_item_some = get_channel(); let get_item_none = get_channel(); let get_all_items = get_channel(); let count = get_channel(); let remove = get_channel(); let clear = get_channel(); let rx = db.process_transaction(KvsTransaction { mode: IndexedDBTxnMode::Readwrite, requests: VecDeque::from(vec![ KvsOperation { store_name: store_name.to_owned(), operation: AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem { sender: put.0, key: Some(IndexedDBKeyType::Number(1.0)), value: vec![1, 2, 3], should_overwrite: false, }), }, KvsOperation { store_name: store_name.to_owned(), operation: AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem { sender: put2.0, key: Some(IndexedDBKeyType::String("2.0".to_string())), value: vec![4, 5, 6], should_overwrite: false, }), }, KvsOperation { store_name: store_name.to_owned(), operation: AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem { sender: put3.0, key: Some(IndexedDBKeyType::Array(vec![ IndexedDBKeyType::String("3".to_string()), IndexedDBKeyType::Number(0.0), ])), value: vec![7, 8, 9], should_overwrite: false, }), }, // Try to put a duplicate key without overwrite KvsOperation { store_name: store_name.to_owned(), operation: AsyncOperation::ReadWrite(AsyncReadWriteOperation::PutItem { sender: put_dup.0, key: Some(IndexedDBKeyType::Number(1.0)), value: vec![10, 11, 12], should_overwrite: false, }), }, KvsOperation { store_name: store_name.to_owned(), operation: AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetItem { sender: get_item_some.0, key_range: IndexedDBKeyRange::only(IndexedDBKeyType::Number(1.0)), }), }, KvsOperation { store_name: store_name.to_owned(), operation: AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetItem { sender: get_item_none.0, key_range: IndexedDBKeyRange::only(IndexedDBKeyType::Number(5.0)), }), }, KvsOperation { store_name: store_name.to_owned(), operation: AsyncOperation::ReadOnly(AsyncReadOnlyOperation::GetAllItems { sender: get_all_items.0, key_range: IndexedDBKeyRange::lower_bound(IndexedDBKeyType::Number(0.0), false), count: None, }), }, KvsOperation { store_name: store_name.to_owned(), operation: AsyncOperation::ReadOnly(AsyncReadOnlyOperation::Count { sender: count.0, key_range: IndexedDBKeyRange::only(IndexedDBKeyType::Number(1.0)), }), }, KvsOperation { store_name: store_name.to_owned(), operation: AsyncOperation::ReadWrite(AsyncReadWriteOperation::RemoveItem { sender: remove.0, key: IndexedDBKeyType::Number(1.0), }), }, KvsOperation { store_name: store_name.to_owned(), operation: AsyncOperation::ReadWrite(AsyncReadWriteOperation::Clear(clear.0)), }, ]), }); let _ = rx.blocking_recv().unwrap(); put.1.recv().unwrap().unwrap(); put2.1.recv().unwrap().unwrap(); put3.1.recv().unwrap().unwrap(); let err = put_dup.1.recv().unwrap().unwrap(); assert_eq!(err, PutItemResult::CannotOverwrite); let get_result = get_item_some.1.recv().unwrap(); let value = get_result.unwrap(); assert_eq!(value, Some(vec![1, 2, 3])); let get_result = get_item_none.1.recv().unwrap(); let value = get_result.unwrap(); assert_eq!(value, None); let all_items = get_all_items.1.recv().unwrap().unwrap(); assert_eq!(all_items.len(), 3); // Check that all three items are present assert!(all_items.contains(&vec![1, 2, 3])); assert!(all_items.contains(&vec![4, 5, 6])); assert!(all_items.contains(&vec![7, 8, 9])); let amount = count.1.recv().unwrap().unwrap(); assert_eq!(amount, 1); remove.1.recv().unwrap().unwrap(); clear.1.recv().unwrap().unwrap(); }