use arqen::thingd::MemoryThingdBackend;
use arqen::thingd::*;
use serde_json::json;
async fn create_backend() -> Box<dyn ThingdBackend> {
Box::new(MemoryThingdBackend::new())
}
#[tokio::test]
async fn test_object_crud() {
let backend = create_backend().await;
let obj = backend
.put_object("users", "user1", json!({"name": "Alice"}))
.await
.unwrap();
assert_eq!(obj.id, "user1");
assert_eq!(obj.collection, "users");
assert_eq!(obj.data["name"], "Alice");
let fetched = backend.get_object("users", "user1").await.unwrap();
assert!(fetched.is_some());
let fetched = fetched.unwrap();
assert_eq!(fetched.data["name"], "Alice");
let updated = backend
.put_object("users", "user1", json!({"name": "Alice Smith"}))
.await
.unwrap();
assert_eq!(updated.data["name"], "Alice Smith");
let filter = ThingdFilter {
field: "name".to_string(),
operator: FilterOperator::Eq,
value: json!("Alice Smith"),
};
let results = backend.query_objects("users", Some(filter)).await.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].data["name"], "Alice Smith");
let count = backend.count_objects("users").await.unwrap();
assert_eq!(count, 1);
backend.delete_object("users", "user1").await.unwrap();
let fetched = backend.get_object("users", "user1").await.unwrap();
assert!(fetched.is_none());
}
#[tokio::test]
async fn test_batch_operations() {
let backend = create_backend().await;
let operations = vec![
ThingdOperation::Put {
collection: "users".to_string(),
id: "user1".to_string(),
data: json!({"name": "Alice"}),
},
ThingdOperation::Put {
collection: "users".to_string(),
id: "user2".to_string(),
data: json!({"name": "Bob"}),
},
ThingdOperation::Delete {
collection: "users".to_string(),
id: "user1".to_string(),
},
];
let results = backend.batch_write(operations).await.unwrap();
assert_eq!(results.len(), 3);
assert!(results[0].success);
assert!(results[1].success);
assert!(results[2].success);
let count = backend.count_objects("users").await.unwrap();
assert_eq!(count, 1);
}
#[tokio::test]
async fn test_events() {
let backend = create_backend().await;
let event1 = backend
.append_event("user_events", "user_created", json!({"user_id": "user1"}))
.await
.unwrap();
let _event2 = backend
.append_event("user_events", "user_created", json!({"user_id": "user2"}))
.await
.unwrap();
assert_eq!(event1.stream, "user_events");
assert_eq!(event1.event_type, "user_created");
let events = backend.read_events("user_events", None, 10).await.unwrap();
assert_eq!(events.len(), 2);
assert_eq!(events[0].data["user_id"], "user1");
assert_eq!(events[1].data["user_id"], "user2");
let events = backend
.read_events("user_events", Some(event1.id.clone()), 10)
.await
.unwrap();
assert_eq!(events.len(), 1);
assert_eq!(events[0].data["user_id"], "user2");
}
#[tokio::test]
async fn test_jobs() {
let backend = create_backend().await;
let job1 = backend
.push_job("email_queue", json!({"to": "alice@example.com"}), 3)
.await
.unwrap();
let job2 = backend
.push_job("email_queue", json!({"to": "bob@example.com"}), 3)
.await
.unwrap();
assert_eq!(job1.state, JobState::Queued);
assert_eq!(job2.state, JobState::Queued);
let claimed = backend
.claim_job("email_queue", "worker1", 30)
.await
.unwrap();
assert!(claimed.is_some());
let claimed = claimed.unwrap();
assert_eq!(claimed.state, JobState::Leased);
assert_eq!(claimed.attempts, 1);
backend
.complete_job("email_queue", &claimed.id)
.await
.unwrap();
let _fetched = backend
.get_object("email_queue", &claimed.id)
.await
.unwrap();
let claimed2 = backend
.claim_job("email_queue", "worker1", 30)
.await
.unwrap();
assert!(claimed2.is_some());
let claimed2 = claimed2.unwrap();
backend.nack_job("email_queue", &claimed2.id).await.unwrap();
let claimed3 = backend
.claim_job("email_queue", "worker1", 30)
.await
.unwrap();
assert!(claimed3.is_some());
let claimed3 = claimed3.unwrap();
assert_eq!(claimed3.attempts, 2);
for _ in 0..5 {
backend.nack_job("email_queue", &claimed3.id).await.unwrap();
let claimed = backend
.claim_job("email_queue", "worker1", 30)
.await
.unwrap();
if let Some(job) = claimed
&& job.state == JobState::Dead
{
break;
}
}
}
#[tokio::test]
async fn test_links() {
let backend = create_backend().await;
let link1 = backend
.create_link("user1", "post1", "authored")
.await
.unwrap();
let _link2 = backend
.create_link("user1", "post2", "liked")
.await
.unwrap();
let _link3 = backend
.create_link("user2", "post1", "authored")
.await
.unwrap();
assert_eq!(link1.source_id, "user1");
assert_eq!(link1.target_id, "post1");
assert_eq!(link1.relation, "authored");
let links = backend.get_links("user1", None).await.unwrap();
assert_eq!(links.len(), 2);
let links = backend.get_links("user1", Some("authored")).await.unwrap();
assert_eq!(links.len(), 1);
assert_eq!(links[0].target_id, "post1");
backend.delete_link(&link1.id).await.unwrap();
let links = backend.get_links("user1", None).await.unwrap();
assert_eq!(links.len(), 1);
}
#[tokio::test]
async fn test_search() {
let backend = create_backend().await;
backend
.put_object(
"users",
"user1",
json!({"name": "Alice", "email": "alice@example.com"}),
)
.await
.unwrap();
backend
.put_object(
"users",
"user2",
json!({"name": "Bob", "email": "bob@example.com"}),
)
.await
.unwrap();
backend
.put_object(
"posts",
"post1",
json!({"title": "Hello World", "content": "First post"}),
)
.await
.unwrap();
let results = backend
.search(
"Alice",
SearchOptions {
limit: 10,
offset: 0,
filters: vec![],
},
)
.await
.unwrap();
assert_eq!(results.total, 1);
assert_eq!(results.items[0].data["name"], "Alice");
let results = backend
.search(
"example",
SearchOptions {
limit: 10,
offset: 0,
filters: vec![ThingdFilter {
field: "name".to_string(),
operator: FilterOperator::Contains,
value: json!("Bob"),
}],
},
)
.await
.unwrap();
assert_eq!(results.total, 1);
assert_eq!(results.items[0].data["name"], "Bob");
}
#[tokio::test]
async fn test_reset_and_seed() {
let backend = create_backend().await;
backend.seed().await.unwrap();
let count = backend.count_objects("users").await.unwrap();
assert!(count > 0);
backend.reset().await.unwrap();
let count = backend.count_objects("users").await.unwrap();
assert_eq!(count, 0);
}
#[tokio::test]
async fn test_filter_operators() {
let backend = create_backend().await;
backend
.put_object("items", "item1", json!({"name": "Apple", "price": 1.0}))
.await
.unwrap();
backend
.put_object("items", "item2", json!({"name": "Banana", "price": 0.5}))
.await
.unwrap();
backend
.put_object("items", "item3", json!({"name": "Cherry", "price": 2.0}))
.await
.unwrap();
let filter = ThingdFilter {
field: "name".to_string(),
operator: FilterOperator::Eq,
value: json!("Banana"),
};
let results = backend.query_objects("items", Some(filter)).await.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].data["name"], "Banana");
let filter = ThingdFilter {
field: "price".to_string(),
operator: FilterOperator::Gt,
value: json!(1.0),
};
let results = backend.query_objects("items", Some(filter)).await.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].data["name"], "Cherry");
let filter = ThingdFilter {
field: "name".to_string(),
operator: FilterOperator::Contains,
value: json!("pp"),
};
let results = backend.query_objects("items", Some(filter)).await.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].data["name"], "Apple");
}