mod common;
#[cfg(test)]
mod cache_warmth {
use anytype::test_util::*;
use serial_test::serial;
use test_log::test;
#[tokio::test]
#[test_log::test]
#[serial]
async fn test_properties_cache_warmth() {
with_test_context_unit(|ctx| async move {
ctx.client.cache().clear_properties(None);
assert_eq!(
ctx.client.cache().num_properties(),
0,
"Cache should be empty initially"
);
let properties = ctx
.client
.properties(&ctx.space_id)
.list()
.await
.expect("Failed to list properties");
assert!(!properties.is_empty(), "Should have at least one property");
let cache_count = ctx.client.cache().num_properties();
assert!(
cache_count > 0,
"Cache should be populated after list (got {})",
cache_count
);
let metrics_after_list = ctx.client.http_metrics();
let first_property = properties.iter().next().unwrap();
let property = ctx
.client
.property(&ctx.space_id, &first_property.id)
.get()
.await
.expect("Failed to get property");
assert_eq!(property.id, first_property.id);
assert_eq!(property.key, first_property.key);
let metrics_after_get = ctx.client.http_metrics();
assert_eq!(
metrics_after_get.total_requests, metrics_after_list.total_requests,
"No additional HTTP requests should be made when using cached data"
);
ctx.client.cache().clear_properties(None);
})
.await
}
#[test(tokio::test)]
#[test_log::test]
#[serial]
async fn test_types_cache_warmth() {
with_test_context_unit(|ctx| async move {
ctx.client.cache().clear_types(None);
assert_eq!(
ctx.client.cache().num_types(),
0,
"Cache should be empty initially"
);
let types = ctx
.client
.types(&ctx.space_id)
.list()
.await
.expect("Failed to list types");
assert!(!types.is_empty(), "Should have at least one type");
let cache_count = ctx.client.cache().num_types();
assert!(
cache_count > 0,
"Cache should be populated after list (got {})",
cache_count
);
let metrics_after_list = ctx.client.http_metrics();
let first_type = types.iter().next().unwrap();
let typ = ctx
.client
.get_type(&ctx.space_id, &first_type.id)
.get()
.await
.expect("Failed to get type");
assert_eq!(typ.id, first_type.id);
assert_eq!(typ.key, first_type.key);
let metrics_after_get = ctx.client.http_metrics();
assert_eq!(
metrics_after_get.total_requests, metrics_after_list.total_requests,
"No additional HTTP requests should be made when using cached data"
);
ctx.client.cache().clear_types(None);
})
.await
}
#[test(tokio::test)]
#[test_log::test]
#[serial]
async fn test_spaces_cache_warmth() {
with_test_context_unit(|ctx| async move {
let spaces = ctx.client.spaces().list().await.expect("get spaces");
eprintln!("TMP200 Warmth found {} spaces", spaces.len());
ctx.client.cache().clear();
assert_eq!(
ctx.client.cache().num_spaces(),
0,
"Cache should be empty initially"
);
let spaces = ctx
.client
.spaces()
.list()
.await
.expect("Failed to list spaces");
eprintln!("TMP216 list spaces found {} spaces", spaces.len());
assert!(!spaces.is_empty(), "Should have at least one space");
let cache_count = ctx.client.cache().num_spaces();
assert!(
cache_count > 0,
"Cache should be populated after list (got {})",
cache_count
);
eprintln!("TMP226, num_spaces in cache: {cache_count}");
let metrics_after_list = ctx.client.http_metrics();
let first_space = spaces.iter().next().unwrap();
let space = ctx
.client
.space(&first_space.id)
.get()
.await
.expect("Failed to get space");
assert_eq!(space.id, first_space.id);
let metrics_after_get = ctx.client.http_metrics();
assert_eq!(
metrics_after_get.total_requests, metrics_after_list.total_requests,
"No additional HTTP requests should be made when using cached data"
);
ctx.client.cache().clear();
})
.await
}
#[test(tokio::test)]
#[test_log::test]
#[serial]
async fn test_get_auto_warms_cache() {
with_test_context_unit(|ctx| async move {
ctx.client.cache().clear_properties(None);
assert_eq!(
ctx.client.cache().num_properties(),
0,
"Cache should be empty initially"
);
let properties = ctx
.client
.properties(&ctx.space_id)
.list()
.await
.expect("Failed to list properties");
let property_id = properties.iter().next().unwrap().id.clone();
ctx.client.cache().clear_properties(None);
assert_eq!(ctx.client.cache().num_properties(), 0);
let property = ctx
.client
.property(&ctx.space_id, &property_id)
.get()
.await
.expect("Failed to get property");
assert_eq!(property.id, property_id);
assert!(
ctx.client.cache().num_properties() > 0,
"Cache should be warmed after get() with empty cache"
);
ctx.client.cache().clear_properties(None);
})
.await
}
}
mod cache_clearing {
use anytype::test_util::*;
use serial_test::serial;
use test_log::test;
#[test(tokio::test)]
#[test_log::test]
#[serial]
async fn test_clear_properties_cache_targeted() {
with_test_context_unit(|ctx| async move {
ctx.client.cache().clear_properties(None);
let properties = ctx
.client
.properties(&ctx.space_id)
.list()
.await
.expect("Failed to list properties")
.collect_all()
.await
.expect("collect-all properties");
let initial_count = ctx.client.cache().num_properties();
assert!(initial_count > 0, "Cache should be populated after list");
assert_eq!(properties.len(), initial_count);
ctx.client.cache().clear_properties(Some(&ctx.space_id));
assert_eq!(
ctx.client.cache().num_properties(),
0,
"Cache should be empty after targeted clear"
);
let properties_next = ctx
.client
.properties(&ctx.space_id)
.list()
.await
.expect("Failed to re-list properties")
.collect_all()
.await
.expect("collect properties_next");
let temp_dir = ctx.temp_dir("properties").expect("temp dir");
let file1 = temp_dir.join("properties1.json");
let file2 = temp_dir.join("properties2.json");
std::fs::write(&file1, serde_json::to_string_pretty(&properties).unwrap())
.expect("dump json1");
std::fs::write(
&file2,
serde_json::to_string_pretty(&properties_next).unwrap(),
)
.expect("dump json2");
assert_eq!(ctx.client.cache().num_properties(), properties_next.len());
assert_eq!(
properties_next.len(),
initial_count,
"Cache should be re-populated with same count"
);
ctx.client.cache().clear_properties(None);
})
.await
}
#[test(tokio::test)]
#[test_log::test]
#[serial]
async fn test_clear_properties_cache_global() {
with_test_context_unit(|ctx| async move {
ctx.client.cache().clear_properties(None);
ctx.client
.properties(&ctx.space_id)
.list()
.await
.expect("Failed to list properties");
assert!(
ctx.client.cache().num_properties() > 0,
"Cache should be populated"
);
ctx.client.cache().clear_properties(None);
assert_eq!(
ctx.client.cache().num_properties(),
0,
"Cache should be empty after global clear"
);
ctx.client.cache().clear_properties(None);
})
.await
}
#[test(tokio::test)]
#[test_log::test]
#[serial]
async fn test_clear_types_cache_targeted() {
with_test_context_unit(|ctx| async move {
ctx.client.cache().clear_types(None);
ctx.client
.types(&ctx.space_id)
.list()
.await
.expect("Failed to list types");
let initial_count = ctx.client.cache().num_types();
assert!(initial_count > 0, "Cache should be populated after list");
ctx.client.cache().clear_types(Some(&ctx.space_id));
assert_eq!(
ctx.client.cache().num_types(),
0,
"Cache should be empty after targeted clear"
);
ctx.client
.types(&ctx.space_id)
.list()
.await
.expect("Failed to re-list types");
assert_eq!(
ctx.client.cache().num_types(),
initial_count,
"Cache should be re-populated with same count"
);
ctx.client.cache().clear_types(None);
})
.await
}
#[test(tokio::test)]
#[test_log::test]
#[serial]
async fn test_cache_clear_types_global() {
with_test_context_unit(|ctx| async move {
ctx.client.cache().clear_types(None);
ctx.client
.types(&ctx.space_id)
.list()
.await
.expect("Failed to list types");
assert!(
ctx.client.cache().num_types() > 0,
"Cache should be populated"
);
ctx.client.cache().clear_types(None);
assert_eq!(
ctx.client.cache().num_types(),
0,
"Cache should be empty after global clear"
);
ctx.client.cache().clear_types(None);
})
.await
}
#[test(tokio::test)]
#[test_log::test]
#[serial]
async fn test_cache_clear_idempotent() {
with_test_context_unit(|ctx| async move {
ctx.client.cache().clear_properties(None);
ctx.client.cache().clear_properties(None);
ctx.client.cache().clear_types(None);
ctx.client.cache().clear_types(None);
ctx.client.cache().clear_spaces();
ctx.client.cache().clear_spaces();
ctx.client.cache().clear();
ctx.client.cache().clear();
assert_eq!(ctx.client.cache().num_properties(), 0);
assert_eq!(ctx.client.cache().num_types(), 0);
assert_eq!(ctx.client.cache().num_spaces(), 0);
ctx.client.cache().clear_properties(Some(&ctx.space_id));
ctx.client.cache().clear_types(Some(&ctx.space_id));
ctx.client.cache().clear();
})
.await
}
}
mod cache_disabled {
use anytype::prelude::*;
use serial_test::serial;
use test_log::test;
#[test(tokio::test)]
#[test_log::test]
#[serial]
async fn test_cache_disabled_via_config() {
let base_url = std::env::var("ANYTYPE_TEST_URL")
.unwrap_or_else(|_| "http://127.0.0.1:31012".to_string());
let space_id =
std::env::var("ANYTYPE_TEST_SPACE_ID").expect("ANYTYPE_TEST_SPACE_ID required");
let keystore = if let Ok(key_file) = std::env::var("ANYTYPE_TEST_KEY_FILE") {
format!("file:path={key_file}")
} else if let Ok(store) = std::env::var("ANYTYPE_KEYSTORE") {
store
} else {
panic!("set ANYTYPE_TEST_KEY_FILE or ANYTYPE_KEYSTORE");
};
let config = ClientConfig {
base_url: Some(base_url),
app_name: "anytype-cache-test".to_string(),
rate_limit_max_retries: 0,
disable_cache: true, keystore: Some(keystore),
keystore_service: Some("anyr".into()),
..Default::default()
};
let client = AnytypeClient::with_config(config).expect("Failed to create client");
assert_eq!(client.cache().num_properties(), 0);
assert_eq!(client.cache().num_types(), 0);
assert_eq!(client.cache().num_spaces(), 0);
let properties = client
.properties(&space_id)
.list()
.await
.expect("Failed to list properties");
assert!(!properties.is_empty(), "Should have properties");
assert_eq!(
client.cache().num_properties(),
0,
"Cache should remain empty when disabled"
);
let types = client
.types(&space_id)
.list()
.await
.expect("Failed to list types");
assert!(!types.is_empty(), "Should have types");
assert_eq!(
client.cache().num_types(),
0,
"Cache should remain empty when disabled"
);
let spaces = client.spaces().list().await.expect("Failed to list spaces");
assert!(!spaces.is_empty(), "Should have spaces");
assert_eq!(
client.cache().num_spaces(),
0,
"Cache should remain empty when disabled"
);
let first_property = properties.iter().next().unwrap();
let _property = client
.property(&space_id, &first_property.id)
.get()
.await
.expect("Failed to get property");
assert_eq!(
client.cache().num_properties(),
0,
"Cache should remain empty after get with cache disabled"
);
}
}
mod cache_isolation {
use anytype::test_util::*;
use serial_test::serial;
use test_log::test;
#[test(tokio::test)]
#[test_log::test]
#[serial]
async fn test_cache_space_isolation() {
with_test_context_unit(|ctx| async move {
ctx.client.cache().clear();
let spaces = ctx
.client
.spaces()
.list()
.await
.expect("Failed to list spaces");
if spaces.len() < 2 {
println!(
"Skipping cache isolation test - need at least 2 spaces, found {}",
spaces.len()
);
return;
}
let space_a = &ctx.space_id;
let space_b = spaces
.iter()
.find(|s| s.id != *space_a)
.map(|s| &s.id)
.expect("Failed to find second space");
ctx.client
.properties(space_a)
.list()
.await
.expect("Failed to list properties for space A");
let cache_count_after_a = ctx.client.cache().num_properties();
assert!(cache_count_after_a > 0, "Space A cache should be populated");
ctx.client
.properties(space_b)
.list()
.await
.expect("Failed to list properties for space B");
let cache_count_after_b = ctx.client.cache().num_properties();
assert!(
cache_count_after_b >= cache_count_after_a,
"Cache should include both spaces"
);
ctx.client.cache().clear_properties(Some(space_a));
assert!(
ctx.client.cache().has_properties(space_b),
"space b property cache should not be empty"
);
assert!(
!ctx.client.cache().has_properties(space_a),
"space a property cache should be empty"
);
ctx.client.cache().clear();
})
.await
}
#[test(tokio::test)]
#[test_log::test]
#[serial]
async fn test_cache_isolation_between_types() {
with_test_context_unit(|ctx| async move {
ctx.client.cache().clear();
ctx.client
.properties(&ctx.space_id)
.list()
.await
.expect("Failed to list properties");
assert!(ctx.client.cache().num_properties() > 0);
assert_eq!(ctx.client.cache().num_types(), 0);
assert_eq!(ctx.client.cache().num_spaces(), 0);
ctx.client
.types(&ctx.space_id)
.list()
.await
.expect("Failed to list types");
assert!(ctx.client.cache().num_properties() > 0);
assert!(ctx.client.cache().num_types() > 0);
assert_eq!(ctx.client.cache().num_spaces(), 0);
let spaces = ctx
.client
.spaces()
.list()
.await
.expect("Failed to list spaces");
eprintln!("(2) Found {} spaces", spaces.len());
assert!(ctx.client.cache().num_properties() > 0);
assert!(ctx.client.cache().num_types() > 0);
assert!(ctx.client.cache().num_spaces() > 0);
ctx.client.cache().clear_properties(None);
assert_eq!(ctx.client.cache().num_properties(), 0);
assert!(
ctx.client.cache().num_types() > 0,
"Types cache should be unaffected"
);
assert!(
ctx.client.cache().num_spaces() > 0,
"Spaces cache should be unaffected"
);
ctx.client.cache().clear_types(None);
assert_eq!(ctx.client.cache().num_properties(), 0);
assert_eq!(ctx.client.cache().num_types(), 0);
assert!(
ctx.client.cache().num_spaces() > 0,
"Spaces cache should be unaffected"
);
ctx.client.cache().clear();
})
.await
}
}
mod cache_query_behavior {
use anytype::test_util::*;
use serial_test::serial;
use test_log::test;
#[test(tokio::test)]
#[test_log::test]
#[serial]
async fn test_cache_introspection() {
with_test_context_unit(|ctx| async move {
ctx.client.cache().clear();
assert_eq!(ctx.client.cache().num_properties(), 0);
assert_eq!(ctx.client.cache().num_types(), 0);
assert_eq!(ctx.client.cache().num_spaces(), 0);
ctx.client
.properties(&ctx.space_id)
.list()
.await
.expect("Failed to list properties");
let prop_count = ctx.client.cache().num_properties();
assert!(prop_count > 0);
assert!(
ctx.client.cache().has_properties(&ctx.space_id),
"Should have properties for test space"
);
assert!(
!ctx.client.cache().has_properties("non-existent space"),
"Should not have properties for non-existent space"
);
ctx.client
.types(&ctx.space_id)
.list()
.await
.expect("Failed to list types");
let type_count = ctx.client.cache().num_types();
assert!(type_count > 0);
ctx.client.cache().clear();
})
.await
}
#[test(tokio::test)]
#[test_log::test]
#[serial]
async fn test_cache_returns_complete_data() {
with_test_context_unit(|ctx| async move {
ctx.client.cache().clear_properties(None);
let properties_from_list = ctx
.client
.properties(&ctx.space_id)
.list()
.await
.expect("Failed to list properties");
let first_prop = properties_from_list.iter().next().unwrap();
let property_from_cache = ctx
.client
.property(&ctx.space_id, &first_prop.id)
.get()
.await
.expect("Failed to get property from cache");
assert_eq!(property_from_cache.id, first_prop.id);
assert_eq!(property_from_cache.key, first_prop.key);
assert_eq!(property_from_cache.name, first_prop.name);
assert_eq!(property_from_cache.format(), first_prop.format());
ctx.client.cache().clear_properties(None);
})
.await
}
}