use gen_cargo::quirks::CrateQuirk;
use gen_npm::quirks::NpmQuirk;
use gen_platform::{ComposedDispatcher, Dispatcher, DispatcherError, MergeStrategy};
use serde_json::json;
#[derive(Default)]
struct Ctx {
cargo_calls: u32,
npm_calls: u32,
}
#[derive(Default, Debug, PartialEq, Eq, Clone)]
struct Override {
tag: String,
}
fn cargo_dispatcher() -> gen_platform::SealedDispatcher<CrateQuirk, Ctx, Override> {
Dispatcher::<CrateQuirk, Ctx, Override>::new()
.with_helper("force-cfg", |_, ctx| {
ctx.cargo_calls += 1;
Override { tag: "cargo:force-cfg".into() }
})
.with_helper("fold-normal-into-build", |_, ctx| {
ctx.cargo_calls += 1;
Override { tag: "cargo:fold".into() }
})
.with_helper("substitute-source", |_, ctx| {
ctx.cargo_calls += 1;
Override { tag: "cargo:sub".into() }
})
.into_sealed()
.unwrap()
}
fn npm_dispatcher() -> gen_platform::SealedDispatcher<NpmQuirk, Ctx, Override> {
Dispatcher::<NpmQuirk, Ctx, Override>::new()
.with_helper("npm-install-flag", |_, ctx| {
ctx.npm_calls += 1;
Override { tag: "npm:flag".into() }
})
.with_helper("skip-postinstall", |_, ctx| {
ctx.npm_calls += 1;
Override { tag: "npm:skip-pi".into() }
})
.with_helper("pin-nodejs", |_, ctx| {
ctx.npm_calls += 1;
Override { tag: "npm:pin".into() }
})
.with_helper("override-registry", |_, ctx| {
ctx.npm_calls += 1;
Override { tag: "npm:reg".into() }
})
.with_helper("substitute-source", |_, ctx| {
ctx.npm_calls += 1;
Override { tag: "npm:sub".into() }
})
.into_sealed()
.unwrap()
}
#[test]
fn composition_fails_on_overlapping_kinds() {
let r = ComposedDispatcher::<Ctx, Override>::new()
.add("gen.cargo", cargo_dispatcher())
.and_then(|c| c.add("gen.npm", npm_dispatcher()));
let err = r.err().expect("overlapping kinds must error");
let DispatcherError::DuplicateHelper { kind } = err else {
panic!("wrong error variant");
};
assert_eq!(kind, "substitute-source");
}
#[test]
fn composition_succeeds_on_disjoint_kinds() {
let cargo_partial = Dispatcher::<CrateQuirk, Ctx, Override>::new()
.with_helper("force-cfg", |_, _| Override { tag: "c:fc".into() })
.with_helper("fold-normal-into-build", |_, _| Override { tag: "c:fn".into() })
.with_helper("substitute-source", |_, _| Override { tag: "c:ss".into() })
.into_sealed()
.unwrap();
let c = ComposedDispatcher::<Ctx, Override>::new()
.add("gen.cargo", cargo_partial)
.expect("single-dispatcher composition is always disjoint");
assert_eq!(c.helper_count(), 3);
let kinds = c.covered_kinds();
assert!(kinds.contains(&"force-cfg"));
assert!(kinds.contains(&"fold-normal-into-build"));
assert!(kinds.contains(&"substitute-source"));
}
#[test]
fn composed_apply_routes_to_correct_source() {
let c = ComposedDispatcher::<Ctx, Override>::new()
.add("gen.cargo", cargo_dispatcher())
.unwrap();
let variants = vec![
json!({ "kind": "force-cfg", "cfg": "x" }),
json!({ "kind": "substitute-source", "file": "a", "from": "b", "to": "c" }),
];
let mut ctx = Ctx::default();
let results = c.apply_each(&variants, &mut ctx);
assert_eq!(results.len(), 2);
assert_eq!(results[0].tag, "cargo:force-cfg");
assert_eq!(results[1].tag, "cargo:sub");
assert_eq!(ctx.cargo_calls, 2);
assert_eq!(ctx.npm_calls, 0);
}
#[test]
fn try_apply_each_surfaces_unknown_kind_typed() {
let c = ComposedDispatcher::<Ctx, Override>::new()
.add("gen.cargo", cargo_dispatcher())
.unwrap();
let variants = vec![json!({ "kind": "no-such-kind" })];
let mut ctx = Ctx::default();
let err = c.try_apply_each(&variants, &mut ctx).err().unwrap();
let DispatcherError::UnknownKind { kind } = err else {
panic!("wrong error");
};
assert_eq!(kind, "no-such-kind");
}
#[test]
fn covered_kinds_in_canonical_sorted_order() {
let c = ComposedDispatcher::<Ctx, Override>::new()
.add("gen.cargo", cargo_dispatcher())
.unwrap();
let kinds = c.covered_kinds();
let mut sorted = kinds.clone();
sorted.sort_unstable();
assert_eq!(kinds, sorted);
}
#[test]
fn sources_track_per_dispatcher_kind_contributions() {
let c = ComposedDispatcher::<Ctx, Override>::new()
.add("gen.cargo", cargo_dispatcher())
.unwrap();
let sources = c.sources();
assert_eq!(sources.len(), 1);
assert_eq!(sources[0].label, "gen.cargo");
assert_eq!(sources[0].kinds.len(), 3);
}
#[test]
fn strategy_observable_on_composition() {
let c = ComposedDispatcher::<Ctx, Override>::new()
.with_strategy(MergeStrategy::Accumulate)
.add("gen.cargo", cargo_dispatcher())
.unwrap();
assert_eq!(c.strategy(), MergeStrategy::Accumulate);
}
#[test]
fn empty_composition_returns_no_results() {
let c = ComposedDispatcher::<Ctx, Override>::new();
let mut ctx = Ctx::default();
assert_eq!(c.apply_each(&[], &mut ctx).len(), 0);
assert_eq!(c.helper_count(), 0);
assert_eq!(c.covered_kinds().len(), 0);
}
#[test]
fn variant_missing_kind_field_skipped_in_apply_each() {
let c = ComposedDispatcher::<Ctx, Override>::new()
.add("gen.cargo", cargo_dispatcher())
.unwrap();
let variants = vec![json!({ "no_kind_field": true })];
let mut ctx = Ctx::default();
let r = c.apply_each(&variants, &mut ctx);
assert_eq!(r.len(), 0);
assert_eq!(ctx.cargo_calls, 0);
}