use std::path::PathBuf;
use sofie_model::testing::{aggregate, app, area, define_area, dependency, module, route};
use sofie_model::{
App, Cadence, FeatureDeclaration, FeatureGate, Identity, Listener, MailTemplate, MenuFile,
Method, ModuleConfig, Schedule, ScreenOverride, WallClock,
};
use super::{render_module_wiring, render_wiring};
use crate::rustfmt::format_rust;
fn index() -> sofie_model::Route {
route(Method::Get, "/", "index", 4)
}
pub(super) fn has(wiring: &str, expected: &str) -> bool {
let compact = |text: &str| {
let joined: String = text.split_whitespace().collect();
joined
.replace(",)", ")")
.replace(",]", "]")
.replace(",}", "}")
.replace(",>", ">")
};
compact(wiring).contains(&compact(expected))
}
pub(super) fn formatted(app: &App) -> String {
with_forms(app, &[])
}
fn module_formatted(app: &App) -> String {
let order = app
.modules
.iter()
.find(|module| module.krate.crate_name == "order")
.unwrap();
format_rust(
&render_module_wiring(app, order),
std::path::Path::new("generated.rs"),
)
.unwrap()
}
fn with_forms(app: &App, forms: &[crate::forms::DescribedForm]) -> String {
format_rust(
&render_wiring(app, None, forms),
std::path::Path::new("generated.rs"),
)
.unwrap()
}
fn audit_listener(event: &str) -> Listener {
Listener {
file: PathBuf::from("shop/modules/order/src/listeners.rs"),
line: 3,
function: vec!["listeners".to_owned(), "audit".to_owned()],
event: vec!["crate".to_owned(), "events".to_owned(), event.to_owned()],
}
}
fn split_flag() -> FeatureDeclaration {
FeatureDeclaration {
constant: vec!["module".to_owned(), "SPLIT".to_owned()],
name: Some("order.split".to_owned()),
added: Some("2026-09-01".to_owned()),
retire_after_days: 30,
variants: false,
file: PathBuf::from("shop/modules/order/src/module.rs"),
line: 5,
}
}
#[test]
fn lists_each_module_with_its_screens_sorted_by_crate_name_and_wires_the_rest_in_the_module() {
let mut order = module("order", "shop/modules/order");
order.migrations = ["0001_a.sql", "0002_b.sql"]
.map(|file| PathBuf::from(format!("shop/modules/order/migrations/{file}")))
.to_vec();
let mut order_web = area(&order, "web", vec![index()]);
order_web.screens = vec![PathBuf::from(
"shop/modules/order/src/areas/web/screens/orders.html",
)];
order.areas.push(order_web);
order.listeners = vec![audit_listener("OrderCancelled")];
order.schedules = vec![Schedule {
file: PathBuf::from("shop/modules/order/src/jobs.rs"),
line: 5,
function: vec!["jobs".to_owned(), "expire".to_owned()],
cadence: Cadence::Interval("15m".to_owned()),
each: Some(vec!["jobs".to_owned(), "stale".to_owned()]),
}];
let mut stock = module("stock-level", "shop/modules/stock-level");
let stock_web = area(&stock, "web", vec![index()]);
stock.areas.push(stock_web);
let app = app("shop", vec![stock, order]);
let wiring = formatted(&app);
let module_wiring = module_formatted(&app);
let expected = r#"// @generated by `sofie gen`; do not edit.
use sofie::core::Result;
use sofie::db::Db;
use sofie::http::{App, Module, Screens};
use sofie::worker::Worker;
pub static MODULES: &[Module] = &[
Module {
screens: &[(
"order/web/orders",
sofie::db::embed!(
"../modules/order/src/areas/web/screens/orders.html",
compiled = "screens/order/web/orders.sft"
),
)],
..order::module::MODULE
},
stock_level::module::MODULE,
];
pub fn app(db: Db) -> Result<App> {
Ok(App::new(db)
.serving(|serving| serving.named(env!("CARGO_PKG_NAME")))
.screens(screens()?)
.modules(MODULES))
}
#[must_use]
pub fn worker(db: Db) -> Worker {
sofie::http::worker(db, MODULES)
}
const COMPONENTS: &[(&str, &str)] = &[];
fn screens() -> Result<Screens> {
sofie::http::wired_screens(MODULES, COMPONENTS)
}
"#;
assert_eq!(wiring, expected);
let expected_module = r#"// @generated by `sofie gen`; do not edit.
use sofie::http::Module;
pub(crate) const GENERATED: Module = Module {
migrations: Some(sofie::db::Migrations::new(
"order",
&[
sofie::db::embed!("migrations/0001_a.sql"),
sofie::db::embed!("migrations/0002_b.sql"),
],
)),
route_names: &[sofie::http::RouteName {
method: "GET",
path: "/",
name: "order.web.index",
}],
areas: &[|app, module| app.area::<sofie::http::Web>(module, crate::areas::web::routes::routes)],
jobs: |worker| {
worker
.on::<crate::events::OrderCancelled, _>(
"order.listeners.audit",
crate::listeners::audit,
)
.each(
"order.jobs.stale",
std::time::Duration::from_secs(900),
crate::jobs::stale,
"order.jobs.expire",
crate::jobs::expire,
)
},
..Module::new("order")
};
"#;
assert_eq!(module_wiring, expected_module);
}
#[test]
fn a_wall_clock_schedule_is_wired_with_its_time_day_and_zone_as_a_constant() {
let order = module("order", "shop/modules/order");
let schedule = Schedule {
file: PathBuf::from("shop/modules/order/src/jobs.rs"),
line: 3,
function: vec!["jobs".to_owned(), "digest".to_owned()],
cadence: Cadence::WallClock(WallClock {
daily_at: "03:00".to_owned(),
weekly_on: Some("monday".to_owned()),
zone: "Europe/Istanbul".to_owned(),
}),
each: None,
};
let entry = super::schedule_entry(&order, &schedule).to_string();
assert_eq!(
entry,
quote::quote!(.every("order.jobs.digest", const {
sofie::__needs_local_time!(sofie::worker::WallClock::weekly(
sofie::values::Weekday::constant("monday"),
sofie::values::TimeOfDay::constant("03:00"),
"Europe/Istanbul"
))
}, crate::jobs::digest))
.to_string()
);
}
#[test]
fn a_module_with_nothing_to_wire_spreads_an_empty_module() {
let app = app("shop", vec![module("order", "shop/modules/order")]);
let wiring = module_formatted(&app);
assert!(
wiring.ends_with(
"use sofie::http::Module;\n\npub(crate) const GENERATED: Module = Module::new(\"order\");\n"
),
"{wiring}"
);
}
#[test]
fn describes_each_form_by_the_input_its_route_reads_in_its_screen_s_module() {
let form = |screen: &str| crate::forms::DescribedForm {
screen: screen.to_owned(),
form: "place-order".to_owned(),
input: "order::actions::PlaceOrder".to_owned(),
};
let order = module("order", "shop/modules/order");
let wiring = with_forms(
&app("shop", vec![order]),
&[form("order/web/orders"), form("stock/web/levels")],
);
let expected = "Module { forms: &[sofie::http::Form::of::<order::actions::PlaceOrder>(\
\"order/web/orders\", \"place-order\")], ..order::module::MODULE }";
assert!(has(&wiring, expected), "missing {expected} in {wiring}");
assert!(!wiring.contains("stock/web/levels"), "{wiring}");
}
#[test]
fn hands_the_module_components_to_the_screens() {
let mut order = module("order", "shop/modules/order");
order.components = vec![PathBuf::from("shop/modules/order/web/OrderCard.html")];
let wiring = formatted(&app("shop", vec![order]));
let expected = "const COMPONENTS: &[(&str, &str)] = &[(\n \"OrderCard\",\n concat!(\n env!(\"CARGO_MANIFEST_DIR\"),\n \"/\",\n \"../modules/order/web/OrderCard.html\"\n ),\n)];\n";
assert!(wiring.contains(expected), "missing {expected} in {wiring}");
}
#[test]
fn embeds_a_module_s_sqlite_migrations_beside_its_others() {
let mut order = module("order", "shop/modules/order");
order.migrations = vec![PathBuf::from("shop/modules/order/migrations/0001_a.sql")];
order.sqlite_migrations = vec![PathBuf::from(
"shop/modules/order/migrations/sqlite/0001_a.sql",
)];
let wiring = module_formatted(&app("shop", vec![order]));
let expected = ".sqlite(&[sofie::db::embed!(\"migrations/sqlite/0001_a.sql\")])";
assert!(has(&wiring, expected), "missing {expected} in {wiring}");
}
#[test]
fn an_app_without_modules_still_compiles() {
let wiring = formatted(&app("shop", Vec::new()));
for expected in [
"pub static MODULES: &[Module] = &[];\n",
" Ok(App::new(db)\n .serving(|serving| serving.named(env!(\"CARGO_PKG_NAME\")))\n .screens(screens()?)\n .modules(MODULES))\n",
" sofie::http::worker(db, MODULES)\n}\n",
" sofie::http::wired_screens(MODULES, COMPONENTS)\n",
] {
assert!(wiring.contains(expected), "missing {expected} in {wiring}");
}
}
#[test]
fn loads_each_module_s_config_before_the_app_is_built() {
let mut order = module("order", "shop/modules/order");
order.config = Some(ModuleConfig {
type_name: "OrderConfig".to_owned(),
file: PathBuf::from("shop/modules/order/src/config.rs"),
line: 4,
declared: true,
keys: Vec::new(),
});
let wiring = formatted(&app("shop", vec![order]));
let expected = "pub fn app(db: Db) -> Result<App> {\n \
sofie::config::load::<order::config::OrderConfig>(\"order\")?;\n Ok(";
assert!(wiring.contains(expected), "missing {expected} in {wiring}");
}
fn with_identity(mut app: App) -> App {
app.app.dependencies.push(dependency("sofie-identity"));
app.identity = Some(Identity {
screens: vec!["sofie_identity/web/sign_in".to_owned()],
..Identity::default()
});
app
}
#[test]
fn lists_the_identity_as_a_module_signing_in_each_area_a_contract_defines() {
let mut order = module("order", "shop/modules/order");
let member = area(&order, "member", vec![index()]);
let mut web = area(&order, "web", vec![index()]);
web.overrides.push(ScreenOverride {
module: "sofie_identity".to_owned(),
file: PathBuf::from("shop/modules/order/src/areas/web/screens/sofie_identity/sign_in.html"),
});
order.areas = vec![member, web];
define_area(&mut order, "member");
let app = with_identity(app("shop", vec![order]));
let wiring = formatted(&app);
let module_wiring = module_formatted(&app);
let area =
"app.area::<order_contract::area::Member>(module, crate::areas::member::routes::routes)";
assert!(
has(&module_wiring, area),
"missing {area} in {module_wiring}"
);
for expected in [
"pub static MODULES: &[Module] = &[sofie_identity::module(&[\
sofie_identity::member::<order_contract::area::Member>]), Module {",
"(\"sofie_identity/web/sign_in\", sofie::db::embed!(\
\"../modules/order/src/areas/web/screens/sofie_identity/sign_in.html\", \
compiled = \"screens/sofie_identity/web/sign_in.sft\"))",
"sofie::http::wired_screens(MODULES, COMPONENTS)",
"sofie::http::worker(db, MODULES)",
] {
assert!(has(&wiring, expected), "missing {expected} in {wiring}");
}
for unwired in [
"membership",
"add_builtin",
"FORMS",
"sofie_identity::worker",
] {
assert!(!wiring.contains(unwired), "{unwired} in {wiring}");
}
}
#[test]
fn signs_members_in_to_no_public_area_a_contract_defines() {
let mut order = module("order", "shop/modules/order");
define_area(&mut order, "member");
let mut docs = order.defined_areas[0].clone();
docs.name = "docs".to_owned();
docs.type_path[2] = "Docs".to_owned();
docs.principal = vec!["Visitor".to_owned()];
docs.implements_member_area = false;
order.defined_areas.push(docs);
let wiring = formatted(&with_identity(app("shop", vec![order])));
let expected =
"sofie_identity::module(&[sofie_identity::member::<order_contract::area::Member>])";
assert!(has(&wiring, expected), "missing {expected} in {wiring}");
}
#[test]
fn registers_the_notifications_a_closing_account_erases_once_identity_has_notify() {
let registration = "sofie_identity::account_data::register(&db, std::sync::Arc::new(\
sofie_identity::account_data::Notifications::new(&db)));";
let mut app = with_identity(app("shop", vec![module("order", "shop/modules/order")]));
assert!(!has(&formatted(&app), registration));
app.identity.as_mut().unwrap().features = vec!["account-data".to_owned(), "notify".to_owned()];
let wiring = formatted(&app);
assert!(
has(&wiring, registration),
"missing {registration} in {wiring}"
);
}
#[test]
fn embeds_each_modules_mails_and_registers_them_for_the_app_and_its_worker() {
let mut order = module("order", "shop/modules/order");
order.mails = ["receipt", "sofie_identity/password_reset"]
.map(|name| MailTemplate {
key: format!("order/{name}").replace("order/sofie_identity", "sofie_identity"),
file: PathBuf::from(format!("shop/modules/order/src/mails/{name}.html")),
})
.to_vec();
let plain = formatted(&app("shop", vec![module("order", "shop/modules/order")]));
let wiring = formatted(&app("shop", vec![order]));
for expected in [
"const MAILS: &[sofie::mail::Template] = &[",
"\"order/receipt\",\n sofie::db::embed!(\n \"../modules/order/src/mails/receipt.html\",\n \
compiled = \"mails/order/receipt.sft\"\n ),",
"\"sofie_identity/password_reset\",",
"pub fn app(db: Db) -> Result<App> {\n sofie::mail::templates(&db, MAILS);\n",
"pub fn worker(db: Db) -> Worker {\n sofie::mail::templates(&db, MAILS);\n",
] {
assert!(wiring.contains(expected), "missing {expected} in {wiring}");
}
assert!(!plain.contains("mail"), "{plain}");
}
fn gate(function: &str) -> FeatureGate {
FeatureGate {
function: function.split("::").map(str::to_owned).collect(),
feature: vec!["SPLIT".to_owned()],
file: PathBuf::from("shop/modules/order/src/actions.rs"),
line: 2,
}
}
#[test]
fn gives_the_app_and_its_worker_flags_and_gates_what_a_flag_marks() {
let mut order = module("order", "shop/modules/order");
let place = route(Method::Post, "/orders", "actions::place_order", 4);
order.areas.push(area(&order, "web", vec![place, index()]));
order.listeners = vec![audit_listener("OrderPlaced")];
order.code.features.declarations = vec![split_flag()];
order.code.features.gates = vec![gate("actions::place_order"), gate("listeners::audit")];
let app = app("shop", vec![order]);
let wiring = formatted(&app);
for expected in [
"use sofie::flags::Flags;\nuse sofie::http::{App, Gates, Module, RouteFeature, Screens};\n",
"pub fn app(db: Db, flags: Flags) -> Result<App> {\n",
" .gates(\n Gates::default()\n .features(flags)\n .route_features(ROUTE_FEATURES),\n )\n",
"const ROUTE_FEATURES: &[RouteFeature] = &[RouteFeature {\n method: \"POST\",\n path: \"/orders\",\n feature: order::module::SPLIT,\n}];\n",
"pub fn worker(db: Db, flags: &Flags) -> Worker {\n",
] {
assert!(wiring.contains(expected), "missing {expected} in {wiring}");
}
let gated = "sofie::http::worker(db, MODULES).on::<order::events::OrderPlaced, _>(\
\"order.listeners.audit\", \
sofie::flags::gated(flags, order::module::SPLIT, order::listeners::audit))";
assert!(has(&wiring, gated), "missing {gated} in {wiring}");
assert!(!module_formatted(&app).contains("jobs:"));
}
#[test]
fn declares_the_settings_of_each_module_with_some_to_the_app_s_flags() {
let with_settings = |name: &str| {
let mut module = module(name, format!("shop/modules/{name}"));
if let Some(manifest) = module.manifest.as_mut() {
manifest.settings = vec!["free_shipping_above".to_owned()];
}
module
};
let mut order = with_settings("order");
order.code.features.declarations = vec![split_flag()];
let quiet = module("quiet", "shop/modules/quiet");
let wiring = formatted(&app("shop", vec![order, quiet, with_settings("stock")]));
let without_flags = formatted(&app("shop", vec![with_settings("stock")]));
let expected = "sofie::declare_settings(&flags, MODULES)?;\n";
assert!(wiring.contains(expected), "missing {expected} in {wiring}");
assert!(
!without_flags.contains("declare_settings"),
"{without_flags}"
);
}
#[test]
fn names_the_permission_each_route_of_a_permitted_area_needs() {
let mut order = module("order", "shop/modules/order");
let cancel = route(
Method::Post,
"/orders/{order}/cancel",
"actions::cancel_order",
4,
);
order.areas.push(area(&order, "member", vec![cancel]));
define_area(&mut order, "member");
order.defined_areas[0].principal = vec!["Permitted".to_owned()];
let wiring = formatted(&app("shop", vec![order]));
for expected in [
"use sofie::http::{App, Gates, Module, Permission, RoutePermission, Screens};\n",
"pub fn app(db: Db) -> Result<App> {\n",
" .gates(Gates::default().route_permissions(ROUTE_PERMISSIONS))\n",
"const ROUTE_PERMISSIONS: &[RoutePermission] = &[RoutePermission {\n method: \"POST\",\n path: \"/account/orders/{order}/cancel\",\n permission: Permission::new(\"order.order.*.cancel\"),\n}];\n",
"pub fn worker(db: Db) -> Worker {\n",
] {
assert!(wiring.contains(expected), "missing {expected} in {wiring}");
}
}
#[test]
fn adds_each_area_s_menu_with_its_routes_after_the_gates() {
let mut order = module("order", "shop/modules/order");
let mut console = area(&order, "console", vec![index()]);
console.menu = Some(MenuFile {
file: PathBuf::from("shop/modules/order/src/areas/console/menu.rs"),
declared: true,
entries: Vec::new(),
});
order.areas.push(console);
define_area(&mut order, "console");
order.defined_areas[0].principal = vec!["Permitted".to_owned()];
let app = app("shop", vec![order]);
let wiring = formatted(&app);
for expected in [
"use sofie::http::{App, Gates, Module, Permission, RoutePermission, Screens};",
".gates(Gates::default().route_permissions(ROUTE_PERMISSIONS)).screens(screens()?)\
.modules(MODULES))",
] {
assert!(has(&wiring, expected), "missing {expected} in {wiring}");
}
let menu = "app.area::<order_contract::area::Member>(module, crate::areas::console::routes::routes)\
.menu::<order_contract::area::Member>(crate::areas::console::menu::menu)";
let module_wiring = module_formatted(&app);
for expected in ["use sofie::http::{Module, Navigation};", menu] {
assert!(
has(&module_wiring, expected),
"missing {expected} in {module_wiring}"
);
}
}
#[test]
fn registers_each_task_by_module_and_function_name() {
let mut order = module("order", "shop/modules/order");
order.tasks = vec![sofie_model::Task {
file: PathBuf::from("shop/modules/order/src/tasks/totals.rs"),
line: 1,
function: vec![
"tasks".to_owned(),
"totals".to_owned(),
"backfill".to_owned(),
],
}];
let wiring = formatted(&app("shop", vec![order]));
let expected = "#[must_use]\npub fn tasks() -> sofie::Tasks {\n \
sofie::Tasks::new().task(\"order.backfill\", order::tasks::totals::backfill)\n}\n";
assert!(wiring.contains(expected), "missing {expected} in {wiring}");
assert!(!formatted(&app("shop", Vec::new())).contains("fn tasks"));
}
#[test]
fn provides_contracts_lookups_extensions_and_jobs_to_the_app_and_its_worker() {
let mut order = module("order", "shop/modules/order");
define_area(&mut order, "member");
let provision = |contract: &str, ty: &str, unit| sofie_model::Provision {
contract: contract.split("::").map(str::to_owned).collect(),
ty: ty.split("::").map(str::to_owned).collect(),
unit,
file: PathBuf::from("shop/modules/order/src/contract.rs"),
line: 3,
};
order.implementations = vec![provision(
"order_contract::Orders",
"contract::OrderContract",
false,
)];
order.extensions = vec![provision(
"mail_contract::Unsubscribing",
"extensions::Unsubscribe",
true,
)];
order.jobs = vec![sofie_model::Job {
file: PathBuf::from("shop/modules/order/src/actions.rs"),
line: 2,
function: vec!["actions".to_owned(), "send_receipt".to_owned()],
}];
let file = "shop/modules/order/src/order/mod.rs";
let mut exposed = aggregate("Order", Vec::new(), file);
exposed.exposed = vec!["total".to_owned()];
let mut mirror = aggregate("MemberCopy", Vec::new(), file);
mirror.mirror = Some(vec!["member_contract".to_owned(), "Member".to_owned()]);
order.code.aggregates = vec![exposed, mirror];
let app = app("shop", vec![order]);
let wiring = formatted(&app);
for expected in [
"pub fn app(db: Db) -> Result<App> {\n provide(&db);\n",
"pub fn worker(db: Db) -> Worker {\n provide(&db);\n sofie::http::worker(db, MODULES)\n}\n",
"fn provide(db: &Db) {\n sofie::db::provide::<dyn order_contract::Orders>(\n db,\n \
std::sync::Arc::new(order::contract::OrderContract::from(db.clone())),\n );\n \
sofie::db::provide::<dyn sofie::db::Lookup<order_contract::Order>>(\n db,\n \
std::sync::Arc::new(\n \
sofie::db::Exposed::<order::Order, order_contract::Order>::new(db.clone()),\n ),\n );\n \
sofie::db::extend::<dyn mail_contract::Unsubscribing>(\n db,\n \
std::sync::Arc::new(order::extensions::Unsubscribe),\n );\n \
sofie::db::name_job(\n db,\n \"order.actions.send_receipt\",\n \
order::actions::send_receipt,\n );\n}\n",
] {
assert!(wiring.contains(expected), "missing {expected} in {wiring}");
}
let jobs = "worker.job(\"order.actions.send_receipt\", crate::actions::send_receipt)\
.mirror::<crate::MemberCopy>(\"order.MemberCopy\")";
let module_wiring = module_formatted(&app);
assert!(
has(&module_wiring, jobs),
"missing {jobs} in {module_wiring}"
);
}
#[test]
fn loads_each_gateway_s_adapters_before_the_app_and_provides_the_chosen_one() {
let mut payment = module("payment", "shop/modules/payment");
let file = PathBuf::from("shop/modules/payment/src/gateways.rs");
let adapter = |ty: &str, is_adapter| sofie_model::GatewayAdapter {
ty: ty.split("::").map(str::to_owned).collect(),
is_adapter,
vendor: None,
settings: Vec::new(),
file: file.clone(),
line: 5,
};
payment.gateways = vec![sofie_model::Gateway {
name: "payments".to_owned(),
path: vec!["gateways".to_owned(), "Payments".to_owned()],
adapters: vec![adapter("Stripe", true), adapter("gateways::Draft", false)],
file: file.clone(),
line: 3,
}];
let compact = |text: &str| {
let joined: String = text.split_whitespace().collect();
joined
.replace(",)", ")")
.replace(",]", "]")
.replace(",>", ">")
};
let wiring = compact(&formatted(&app("shop", vec![payment])));
for expected in [
"pubfnapp(db:Db)->Result<App>{gateways()?;provide(&db);",
"fnprovide(db:&Db){sofie::client::provide::<dynpayment::gateways::Payments>(db);}",
"fngateways()->Result<()>{sofie::client::load::<dynpayment::gateways::Payments>(\"payment\",\"payments\",\
&[sofie::client::Choice::new(<payment::Stripeassofie::client::Adapter>::VENDOR,|keys|{\
Ok(std::sync::Arc::new(sofie::client::adapter::<payment::Stripe>(keys)?))})])?;Ok(())}",
] {
assert!(
wiring.contains(&compact(expected)),
"missing {expected} in {wiring}"
);
}
assert!(!wiring.contains("Draft"));
}
#[test]
fn lists_each_module_s_aggregates_with_its_migrations_for_the_schema_check() {
let mut order = module("order", "shop/modules/order");
order.migrations = vec![PathBuf::from("shop/modules/order/migrations/0001_a.sql")];
order.code.aggregates = vec![aggregate(
"Order",
Vec::new(),
"shop/modules/order/src/order/mod.rs",
)];
let wiring = module_formatted(&app("shop", vec![order]));
assert!(
has(
&wiring,
".aggregates(const { &[sofie::db::AggregateTable::of::<crate::Order>()] })"
),
"{wiring}"
);
}
#[test]
fn installs_billing_with_the_plans_a_module_declares() {
let mut account = module("account", "shop/modules/account");
account.plans = Some(PathBuf::from("shop/modules/account/src/plans.rs"));
let wiring = formatted(&app("shop", vec![account]));
for expected in [
"pub static MODULES: &[Module] = &[sofie::billing::MODULE, account::module::MODULE];",
"let billing = sofie::billing::install(&db, &account::plans::PLANS).checked()?;",
".gates(Gates::default().entitlements(billing.clone()))",
".area::<sofie::http::Web>(\"sofie_billing\", |routes| billing.routes(routes))",
"pub fn worker(db: Db) -> Worker {\n sofie::billing::install(&db, &account::plans::PLANS);\n",
] {
assert!(wiring.contains(expected), "missing {expected} in {wiring}");
}
assert!(!formatted(&app("shop", Vec::new())).contains("billing"));
}
#[test]
fn lists_the_content_pages_and_installs_their_base_url_when_the_app_depends_on_them() {
let mut app = app("shop", Vec::new());
app.app.dependencies.push(dependency("sofie-content"));
let text = formatted(&app);
for expected in [
"pub static MODULES: &[Module] = &[sofie_content::MODULE];",
"sofie_content::install(&db, sofie_content::Settings::from_environment()?);",
] {
assert!(text.contains(expected), "{expected}\n{text}");
}
}
#[test]
fn lists_the_tenancy_module_when_the_app_depends_on_them() {
let mut app = app("shop", Vec::new());
app.app.dependencies.push(dependency("sofie-tenancy"));
let text = formatted(&app);
assert!(
text.contains("pub static MODULES: &[Module] = &[sofie_tenancy::MODULE];"),
"{text}"
);
}