use serde_json::{json, Value};
use rust_store_core::command::{
plan_insert, plan_mutation, WriteLinkPolicy, ERR_WRITE_CROSS_SOURCE_PREFIX,
};
use rust_store_core::federation::plan_federated;
use rust_store_core::schema::{Location, Registry};
fn loc(source: &str, database: Option<&str>, schema: Option<&str>) -> Location {
Location {
source: source.to_string(),
database: database.map(String::from),
schema: schema.map(String::from),
}
}
fn registry_of(items: &[(Value, Location)]) -> Registry {
let mut registry = Registry::new();
registry
.register_batch(items, None)
.expect("schema 注册失败");
registry
}
fn sources_of(plan: &Value) -> Vec<Value> {
plan.get("sources")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default()
}
fn edges_of(plan: &Value) -> Vec<Value> {
plan.get("join")
.and_then(|j| j.get("edges"))
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default()
}
fn order_defn() -> Value {
json!({
"name": "Order", "collection": "orders", "timestamps": false, "idPrefix": "ord",
"fields": { "code": { "type": "string" } },
"relations": {}
})
}
fn replica_defn() -> Value {
json!({ "name": "Order", "replica": true })
}
fn user_order_items() -> Vec<(Value, Location)> {
vec![
(
json!({
"name": "User", "collection": "users", "timestamps": false, "idPrefix": "usr",
"fields": { "name": { "type": "string" } },
"relations": {
"orders": { "model": "Order", "type": "many", "localField": "_id", "foreignField": "userId" }
}
}),
loc("mongodb_main", None, None),
),
(
json!({ "name": "User", "replica": true }),
loc("mongodb_main", Some("orders_db"), None),
),
(
json!({
"name": "Order", "collection": "orders", "timestamps": false, "idPrefix": "ord",
"fields": { "userId": { "type": "string" }, "code": { "type": "string" } },
"relations": {}
}),
loc("mongodb_main", Some("orders_db"), None),
),
(
json!({ "name": "Order", "replica": true }),
loc("mongodb_main", Some("users_db"), None),
),
]
}
#[test]
fn a4_read_picks_same_db_chain_single_unit() {
let registry = registry_of(&user_order_items());
let ds = json!({ "sources": { "mongodb_main": "mongo" } });
let plan = plan_federated(
"User{name, orders{code}}",
&Default::default(),
®istry,
None,
&ds,
)
.expect("联邦规划失败");
let sources = sources_of(&plan);
assert_eq!(sources.len(), 1, "应选同 db 链路下推为单单元: {}", plan);
assert!(edges_of(&plan).is_empty(), "下推后不应有内存 join 边");
assert_eq!(sources[0]["source"], "mongodb_main");
assert_eq!(
sources[0]["database"], "orders_db",
"选中链路应为 Order 主链路所在 db"
);
}
#[test]
fn a4_read_without_common_db_minimizes_units() {
let items = vec![
(
json!({
"name": "User", "collection": "users", "timestamps": false, "idPrefix": "usr",
"fields": { "name": { "type": "string" } },
"relations": {
"orders": { "model": "Order", "type": "many", "localField": "_id", "foreignField": "userId" }
}
}),
loc("mongodb_main", None, None),
),
(
json!({ "name": "User", "replica": true }),
loc("mongodb_main", Some("users_db"), None),
),
(
json!({
"name": "Order", "collection": "orders", "timestamps": false, "idPrefix": "ord",
"fields": { "userId": { "type": "string" }, "code": { "type": "string" } },
"relations": {}
}),
loc("mongodb_main", Some("orders_db"), None),
),
(
json!({ "name": "Order", "replica": true }),
loc("mongodb_main", Some("archive_db"), None),
),
];
let registry = registry_of(&items);
let ds = json!({ "sources": { "mongodb_main": "mongo" } });
let plan = plan_federated(
"User{name, orders{code}}",
&Default::default(),
®istry,
None,
&ds,
)
.expect("联邦规划失败");
assert_eq!(
sources_of(&plan).len(),
2,
"无同 db 组合应拆 2 组: {}",
plan
);
assert_eq!(edges_of(&plan).len(), 1);
}
fn order_same_source_items() -> Vec<(Value, Location)> {
vec![
(order_defn(), loc("mongodb_main", Some("orders_db"), None)),
(
replica_defn(),
loc("mongodb_main", Some("archived_db"), None),
),
]
}
fn order_cross_source_items() -> Vec<(Value, Location)> {
vec![
(order_defn(), loc("mongodb_main", Some("orders_db"), None)),
(replica_defn(), loc("pg_main", Some("analytics"), None)),
]
}
#[test]
fn a3_same_connection_write_is_atomic() {
let registry = registry_of(&order_same_source_items());
let plan = plan_insert(
"Order",
®istry,
None,
&json!({ "_id": "o1", "code": "C1" }),
0,
"",
None,
)
.expect("同连接写应放行");
let wl = &plan["writeLinks"];
assert_eq!(wl["atomic"], true, "同连接应可同事务: {}", plan);
assert_eq!(wl["source"], "mongodb_main");
let targets = wl["targets"].as_array().expect("targets 应为数组");
assert_eq!(targets.len(), 2, "targets 应覆盖 2 落点: {}", plan);
assert!(targets.iter().all(|t| t["source"] == "mongodb_main"));
}
#[test]
fn a3_same_connection_mutation_attaches_write_links() {
let registry = registry_of(&order_same_source_items());
let out = plan_mutation(
"Order",
®istry,
None,
&json!({ "_id": "o1", "code": "C1" }),
0,
&[],
)
.expect("同连接 mutation 应放行");
let wl = &out["writeLinks"];
assert_eq!(wl["atomic"], true, "{}", out);
assert_eq!(wl["targets"].as_array().unwrap().len(), 2);
}
#[test]
fn a3_cross_connection_write_rejected_by_default() {
let registry = registry_of(&order_cross_source_items());
assert_eq!(registry.write_link_policy(), WriteLinkPolicy::Reject);
let err = plan_insert(
"Order",
®istry,
None,
&json!({ "_id": "o1", "code": "C1" }),
0,
"",
None,
)
.expect_err("跨连接写默认应拒绝");
assert!(
err.starts_with(ERR_WRITE_CROSS_SOURCE_PREFIX),
"错误前缀不正确: {err}"
);
}
#[test]
fn a3_cross_connection_primary_only_degrades_not_atomic() {
let mut registry = registry_of(&order_cross_source_items());
registry.set_write_link_policy(WriteLinkPolicy::PrimaryOnly);
let plan = plan_insert(
"Order",
®istry,
None,
&json!({ "_id": "o1", "code": "C1" }),
0,
"",
None,
)
.expect("PrimaryOnly 策略应降级放行");
assert_eq!(
plan["writeLinks"]["atomic"], false,
"从链路未同步 ⇒ 非原子: {}",
plan
);
let codes: Vec<&str> = plan["degraded"]
.as_array()
.expect("plan.degraded 应存在")
.iter()
.filter_map(|d| d["code"].as_str())
.collect();
assert!(codes.contains(&"writePrimaryOnly"), "plan = {plan}");
assert_eq!(
plan["writeLinks"]["degraded"][0]["code"], "writePrimaryOnly",
"writeLinks 内也应带降级声明: {}",
plan
);
}
#[test]
fn single_location_write_plan_shape_unchanged() {
let registry = registry_of(&[(order_defn(), loc("mongodb_main", Some("orders_db"), None))]);
let plan = plan_insert(
"Order",
®istry,
None,
&json!({ "_id": "o1", "code": "C1" }),
0,
"",
None,
)
.expect("单落点写应放行");
assert!(plan.get("writeLinks").is_none(), "plan = {plan}");
assert!(plan.get("degraded").is_none(), "plan = {plan}");
}