use super::helpers::enable_sync_for_instance_database;
use eidetica::{
Instance,
backend::database::InMemory,
constants::GLOBAL_PERMISSION_KEY,
crdt::{Doc, doc::Value},
instance::LegacyInstanceOps,
store::{SettingsStore, Table},
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
const SERVER_KEY_NAME: &str = "CHAT_APP_SERVER";
const CLIENT_KEY_NAME: &str = "CHAT_APP_CLIENT";
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ChatMessage {
author: String,
content: String,
timestamp: i64,
}
fn has_global_permission_configured(settings: &SettingsStore) -> bool {
if let Ok(auth_settings) = settings.get_auth_settings() {
if let Ok(global_key) = auth_settings.get_key(GLOBAL_PERMISSION_KEY) {
global_key.pubkey() == GLOBAL_PERMISSION_KEY
} else {
false
}
} else {
false
}
}
#[tokio::test]
#[ignore = "Key management bug: sync handler cannot determine which admin key to use for target databases"]
async fn test_chat_app_authenticated_bootstrap() {
println!("\n🧪 TEST: Starting chat app authenticated bootstrap test");
let server_instance =
Instance::open(Box::new(InMemory::new())).expect("Failed to create test instance");
server_instance
.enable_sync()
.expect("Failed to initialize sync on server");
server_instance
.add_private_key(SERVER_KEY_NAME)
.expect("Failed to add server key");
let server_pubkey = server_instance
.get_formatted_public_key(SERVER_KEY_NAME)
.expect("Failed to get server public key");
println!("📍 Server public key: {}", server_pubkey);
let mut settings = Doc::new();
settings.set_string("name", "Test Chat Room");
let mut auth_doc = Doc::new();
auth_doc
.set_json(
SERVER_KEY_NAME,
serde_json::json!({
"pubkey": server_pubkey,
"permissions": {"Admin": 10},
"status": "Active"
}),
)
.expect("Failed to set admin auth");
auth_doc
.set_json(
"*",
serde_json::json!({
"pubkey": "*",
"permissions": {"Write": 10},
"status": "Active"
}),
)
.expect("Failed to set global auth");
settings.set_doc("auth", auth_doc);
let server_database = server_instance
.new_database(settings, SERVER_KEY_NAME)
.expect("Failed to create server database");
let room_id = server_database.root_id().clone();
println!("🏠 Created room with ID: {}", room_id);
let server_sync = server_instance.sync().expect("Server should have sync");
enable_sync_for_instance_database(&server_sync, &room_id)
.expect("Failed to enable sync for database");
{
let op = server_database
.new_transaction()
.expect("Failed to create transaction");
let store = op
.get_store::<Table<ChatMessage>>("messages")
.expect("Failed to get messages store");
let msg = ChatMessage {
author: "server_user".to_string(),
content: "Welcome to the chat room!".to_string(),
timestamp: 1234567890,
};
store.insert(msg).expect("Failed to insert message");
op.commit().expect("Failed to commit transaction");
}
let server_addr = {
server_sync
.enable_http_transport()
.expect("Failed to enable HTTP transport");
server_sync
.start_server_async("127.0.0.1:0")
.await
.expect("Failed to start server");
let addr = server_sync
.get_server_address_async()
.await
.expect("Failed to get server address");
println!("🌐 Server listening at: {}", addr);
addr
};
let client_instance =
Instance::open(Box::new(InMemory::new())).expect("Failed to create test instance");
client_instance
.enable_sync()
.expect("Failed to initialize sync on client");
client_instance
.add_private_key(CLIENT_KEY_NAME)
.expect("Failed to add client key");
let client_pubkey = client_instance
.get_formatted_public_key(CLIENT_KEY_NAME)
.expect("Failed to get client public key");
println!("📍 Client public key: {}", client_pubkey);
assert!(
client_instance.load_database(&room_id).is_err(),
"Client should not have the database initially"
);
{
let client_sync = client_instance.sync().expect("Client should have sync");
client_sync
.enable_http_transport()
.expect("Failed to enable HTTP transport");
println!("\n🔄 Client attempting authenticated bootstrap...");
let bootstrap_result = client_sync
.sync_with_peer_for_bootstrap(
&server_addr,
&room_id,
CLIENT_KEY_NAME,
eidetica::auth::Permission::Write(10),
)
.await;
match bootstrap_result {
Ok(_) => println!("✅ Bootstrap completed successfully"),
Err(e) => {
println!("❌ Bootstrap failed: {:?}", e);
panic!("Bootstrap should succeed but failed: {:?}", e);
}
}
}
tokio::time::sleep(Duration::from_millis(500)).await;
println!("\n🔍 Verifying client can load the database...");
if let Ok(tips) = client_instance.backend().get_tips(&room_id) {
println!("🔍 Client tips before loading database: {:?}", tips);
for tip_id in &tips {
if let Ok(entry) = client_instance.backend().get(tip_id) {
let parents = entry.parents().unwrap_or_default();
println!("🔍 Tip {} has parents: {:?}", tip_id, parents);
println!(
"🔍 Tip {} has settings data: {}",
tip_id,
entry.data("_settings").is_ok()
);
if let Ok(settings_data) = entry.data("_settings") {
if settings_data.contains("\"auth\"") {
println!("✅ Tip {} contains auth section", tip_id);
} else {
println!("❌ Tip {} missing auth section", tip_id);
}
}
}
}
if tips.len() == 2 {
let _tip1 = &tips[0];
let _tip2 = &tips[1];
println!("🔍 Checking ancestry between tips...");
}
}
if let Ok(settings_tips) = client_instance
.backend()
.get_store_tips(&room_id, "_settings")
{
println!("🔍 Client _settings subtree tips: {:?}", settings_tips);
for tip_id in &settings_tips {
if let Ok(entry) = client_instance.backend().get(tip_id)
&& let Ok(settings_data) = entry.data("_settings")
{
println!(
"🔍 Settings tip {} data preview: {}",
tip_id,
&settings_data[..settings_data.len().min(200)]
);
}
}
}
let signing_key = client_instance
.backend()
.get_private_key(CLIENT_KEY_NAME)
.expect("Failed to get client signing key")
.expect("Client key should exist in backend");
let client_database = match eidetica::Database::open(
client_instance.clone(),
&room_id,
signing_key,
CLIENT_KEY_NAME.to_string(),
) {
Ok(db) => {
println!("✅ Client successfully loaded database");
db
}
Err(e) => {
println!("❌ Client failed to load database: {:?}", e);
panic!("Client should be able to load database after bootstrap");
}
};
println!("\n🔐 Checking authentication setup...");
{
let settings = client_database
.get_settings()
.expect("Failed to get database settings");
if let Ok(all_settings) = settings.get_all() {
println!("🔍 Database settings: {:?}", all_settings);
}
let has_global_permission = has_global_permission_configured(&settings);
if has_global_permission {
println!("✅ Global '*' permission detected - bootstrap worked via global permission");
match settings.get("auth") {
Ok(Value::Doc(auth_node)) => {
if auth_node.get(CLIENT_KEY_NAME).is_none() {
println!(
"✅ Confirmed: Client key correctly NOT added due to global permission"
);
println!(" (but bootstrap approval worked via global '*' permission)");
} else {
println!("ℹ️ Client key found in auth settings (possibly added later)");
}
}
_ => panic!("Auth section should exist"),
}
} else {
println!("🔍 No global permission - checking for client key in auth settings");
match settings.get("auth") {
Ok(value) => {
println!("✅ Auth value found - type: {:?}", value);
if let Value::Doc(auth_node) = value {
println!("✅ Auth is a Doc");
if let Some(key_value) = auth_node.get(CLIENT_KEY_NAME) {
println!("🔍 Key value type: {:?}", key_value);
if let Value::Text(json_str) = key_value {
let key_info: serde_json::Value = serde_json::from_str(json_str)
.expect("Failed to parse key JSON");
let stored_pubkey =
key_info["pubkey"].as_str().expect("Missing pubkey in JSON");
println!("✅ Client key found with pubkey: {}", stored_pubkey);
assert_eq!(
stored_pubkey, client_pubkey,
"Stored pubkey should match client's pubkey"
);
} else if let Value::Doc(key_node) = key_value {
if let Some(stored_pubkey) = key_node.get_as::<String>("pubkey") {
println!("✅ Client key found with pubkey: {}", stored_pubkey);
assert_eq!(
stored_pubkey, client_pubkey,
"Stored pubkey should match client's pubkey"
);
} else {
panic!("Client key exists but missing pubkey field");
}
} else {
panic!("Key value is neither JSON string nor Doc: {:?}", key_value);
}
} else {
panic!("Client key NOT found in auth Doc");
}
} else {
panic!("Auth section is not a Doc: {:?}", value);
}
}
Err(e) => {
panic!("No auth section in database settings: {:?}", e);
}
}
}
}
println!("\n📖 Client reading existing messages...");
{
let op = client_database
.new_transaction()
.expect("Failed to create client transaction");
let store = op
.get_store::<Table<ChatMessage>>("messages")
.expect("Failed to get messages store");
let messages = store.search(|_| true).expect("Failed to search messages");
println!("📬 Client found {} messages", messages.len());
assert_eq!(messages.len(), 1, "Client should see the initial message");
let (_, msg) = &messages[0];
assert_eq!(msg.author, "server_user");
assert_eq!(msg.content, "Welcome to the chat room!");
}
println!("\n✍️ Client attempting to add a message...");
{
let op = match client_database.new_transaction() {
Ok(op) => {
println!("✅ Client created transaction successfully");
op
}
Err(e) => {
println!("❌ Client failed to create transaction: {:?}", e);
panic!("Client should be able to create transactions after bootstrap");
}
};
let store = op
.get_store::<Table<ChatMessage>>("messages")
.expect("Failed to get messages store");
let msg = ChatMessage {
author: "client_user".to_string(),
content: "Hello from the client!".to_string(),
timestamp: 1234567891,
};
match store.insert(msg.clone()) {
Ok(_) => println!("✅ Client successfully inserted message"),
Err(e) => {
println!("❌ Client failed to insert message: {:?}", e);
panic!("Client should be able to insert messages");
}
}
match op.commit() {
Ok(_) => println!("✅ Client successfully committed transaction"),
Err(e) => {
println!("❌ Client failed to commit transaction: {:?}", e);
panic!("Client should be able to commit transactions with global permissions");
}
}
}
println!("\n🔍 Client state before sync back:");
if let Ok(client_tips) = client_instance.backend().get_tips(&room_id) {
println!(" Client has {} tips: {:?}", client_tips.len(), client_tips);
if let Ok(entries) = client_instance.backend().get_store(&room_id, "messages") {
println!(" Client has {} entries in messages store", entries.len());
for entry in &entries {
if let Ok(data) = entry.data("messages") {
println!(" Entry {}: {}", entry.id(), &data[..data.len().min(100)]);
}
}
}
}
println!("\n🔄 Syncing client changes back to server...");
{
let client_sync = client_instance.sync().expect("Client should have sync");
client_sync
.sync_with_peer(&server_addr, Some(&room_id))
.await
.expect("Client should be able to sync to server");
}
tokio::time::sleep(Duration::from_millis(500)).await;
println!("\n📖 Server checking for client's message after bidirectional sync...");
{
let op = server_database
.new_transaction()
.expect("Failed to create server transaction");
let store = op
.get_store::<Table<ChatMessage>>("messages")
.expect("Failed to get messages store");
let messages = store.search(|_| true).expect("Failed to search messages");
println!("📬 Server found {} messages", messages.len());
for (id, msg) in &messages {
println!(
" Message {}: author={}, content={}",
id, msg.author, msg.content
);
}
assert_eq!(
messages.len(),
2,
"Server should see both messages after bidirectional sync"
);
let client_msg_found = messages
.iter()
.any(|(_, msg)| msg.author == "client_user" && msg.content == "Hello from the client!");
assert!(
client_msg_found,
"Server should see client's message after bidirectional sync"
);
println!("✅ Server successfully received client's message via bidirectional sync");
}
{
let server_sync = server_instance.sync().expect("Server should have sync");
server_sync
.stop_server_async()
.await
.expect("Failed to stop server");
}
println!("\n✅ TEST COMPLETED: Chat app authenticated bootstrap works!");
}
#[tokio::test]
async fn test_global_key_bootstrap() {
println!("\n🧪 TEST: Starting global key bootstrap test");
let server_instance =
Instance::open(Box::new(InMemory::new())).expect("Failed to create test instance");
server_instance
.enable_sync()
.expect("Failed to initialize sync on server");
server_instance
.add_private_key("admin_key")
.expect("Failed to add admin key");
let mut settings = Doc::new();
settings.set_string("name", "Public Room");
let admin_pubkey = server_instance
.get_formatted_public_key("admin_key")
.expect("Failed to get admin public key");
let mut auth_doc = Doc::new();
auth_doc
.set_json(
"*",
serde_json::json!({
"pubkey": "*",
"permissions": {"Write": 10},
"status": "Active"
}),
)
.expect("Failed to set global auth");
auth_doc
.set_json(
"admin_key",
serde_json::json!({
"pubkey": admin_pubkey,
"permissions": {"Admin": 10},
"status": "Active"
}),
)
.expect("Failed to set admin auth");
settings.set_doc("auth", auth_doc);
let server_database = server_instance
.new_database(settings, "admin_key")
.expect("Failed to create server database");
let room_id = server_database.root_id().clone();
println!("🏠 Created public room with ID: {}", room_id);
let server_sync = server_instance.sync().expect("Server should have sync");
enable_sync_for_instance_database(&server_sync, &room_id)
.expect("Failed to enable sync for database");
let server_addr = {
server_sync
.enable_http_transport()
.expect("Failed to enable HTTP transport");
server_sync
.start_server_async("127.0.0.1:0")
.await
.expect("Failed to start server");
server_sync
.get_server_address_async()
.await
.expect("Failed to get server address")
};
let client_instance =
Instance::open(Box::new(InMemory::new())).expect("Failed to create test instance");
client_instance
.enable_sync()
.expect("Failed to initialize sync on client");
client_instance
.add_private_key("*")
.expect("Failed to add client key");
{
let client_sync = client_instance.sync().expect("Client should have sync");
client_sync
.enable_http_transport()
.expect("Failed to enable HTTP transport");
println!("🔄 Client syncing with global permission...");
client_sync
.sync_with_peer(&server_addr, Some(&room_id))
.await
.expect("Sync should succeed with global permission");
}
tokio::time::sleep(Duration::from_millis(500)).await;
let signing_key = client_instance
.backend()
.get_private_key("*")
.expect("Failed to get global signing key")
.expect("Global key should exist in backend");
let client_database = eidetica::Database::open(
client_instance.clone(),
&room_id,
signing_key,
"*".to_string(),
)
.expect("Client should be able to load database");
{
let op = client_database
.new_transaction()
.expect("Should create transaction with global permission");
let store = op
.get_store::<Table<ChatMessage>>("messages")
.expect("Failed to get messages store");
let msg = ChatMessage {
author: "anonymous".to_string(),
content: "Message with global permission".to_string(),
timestamp: 1234567892,
};
store
.insert(msg)
.expect("Should insert with global permission");
op.commit().expect("Should commit with global permission");
}
println!("✅ TEST COMPLETED: Global key bootstrap works!");
{
let server_sync = server_instance.sync().expect("Server should have sync");
server_sync
.stop_server_async()
.await
.expect("Failed to stop server");
}
}
#[tokio::test]
async fn test_multiple_databases_sync() {
println!("\n🧪 TEST: Starting multiple databases sync test");
let server_instance =
Instance::open(Box::new(InMemory::new())).expect("Failed to create test instance");
server_instance
.enable_sync()
.expect("Failed to initialize sync on server");
server_instance
.add_private_key(SERVER_KEY_NAME)
.expect("Failed to add server key");
let server_pubkey = server_instance
.get_formatted_public_key(SERVER_KEY_NAME)
.expect("Failed to get server public key");
let mut room_ids = Vec::new();
for i in 1..=3 {
let mut settings = Doc::new();
settings.set_string("name", format!("Room {}", i));
let mut auth_doc = Doc::new();
auth_doc
.set_json(
SERVER_KEY_NAME,
serde_json::json!({
"pubkey": server_pubkey,
"permissions": {"Admin": 10},
"status": "Active"
}),
)
.expect("Failed to set admin auth");
auth_doc
.set_json(
"*",
serde_json::json!({
"pubkey": "*",
"permissions": {"Write": 0},
"status": "Active"
}),
)
.expect("Failed to set global wildcard permission");
settings.set_doc("auth", auth_doc);
let database = server_instance
.new_database(settings, SERVER_KEY_NAME)
.expect("Failed to create database");
room_ids.push(database.root_id().clone());
println!("🏠 Created room {} with ID: {}", i, database.root_id());
}
let server_sync = server_instance.sync().expect("Server should have sync");
for room_id in &room_ids {
enable_sync_for_instance_database(&server_sync, room_id)
.expect("Failed to enable sync for database");
}
let server_addr = {
server_sync
.enable_http_transport()
.expect("Failed to enable HTTP transport");
server_sync
.start_server_async("127.0.0.1:0")
.await
.expect("Failed to start server");
server_sync
.get_server_address_async()
.await
.expect("Failed to get server address")
};
let client_instance =
Instance::open(Box::new(InMemory::new())).expect("Failed to create test instance");
client_instance
.enable_sync()
.expect("Failed to initialize sync on client");
client_instance
.add_private_key(CLIENT_KEY_NAME)
.expect("Failed to add client key");
{
let client_sync = client_instance.sync().expect("Client should have sync");
client_sync
.enable_http_transport()
.expect("Failed to enable HTTP transport");
for (i, room_id) in room_ids.iter().enumerate() {
println!("\n🔄 Bootstrapping room {}...", i + 1);
client_sync
.sync_with_peer_for_bootstrap(
&server_addr,
room_id,
CLIENT_KEY_NAME,
eidetica::auth::Permission::Write(10),
)
.await
.unwrap_or_else(|_| panic!("Failed to bootstrap room {}", i + 1));
tokio::time::sleep(Duration::from_millis(200)).await;
}
}
for (i, room_id) in room_ids.iter().enumerate() {
let database = client_instance
.load_database(room_id)
.unwrap_or_else(|_| panic!("Failed to load room {}", i + 1));
let settings = database.get_settings().expect("Failed to get settings");
let name = settings
.get_string("name")
.expect("Failed to get room name");
assert_eq!(name, format!("Room {}", i + 1));
println!("✅ Successfully loaded {}", name);
}
println!("\n✅ TEST COMPLETED: Multiple databases sync works!");
{
let server_sync = server_instance.sync().expect("Server should have sync");
server_sync
.stop_server_async()
.await
.expect("Failed to stop server");
}
}