@@ -229,3 +229,141 @@ impl ConfigFile for ModulesConfig {
229229 changed
230230 }
231231}
232+
233+ #[ cfg( test) ]
234+ mod tests {
235+ use super :: * ;
236+
237+ fn base ( ) -> ModulesConfig {
238+ ModulesConfig :: default ( )
239+ }
240+
241+ #[ test]
242+ fn default_surface_enabled_others_disabled ( ) {
243+ let cfg = base ( ) ;
244+ assert_eq ! ( cfg. modules. get( SURFACE_MODULE_NAME ) , Some ( & true ) ) ;
245+ assert_eq ! ( cfg. modules. get( SHELL_MODULE_NAME ) , Some ( & false ) ) ;
246+ assert_eq ! ( cfg. modules. get( NET_MODULE_NAME ) , Some ( & false ) ) ;
247+ assert_eq ! ( cfg. modules. get( LAN_MODULE_NAME ) , Some ( & false ) ) ;
248+ }
249+
250+ #[ test]
251+ fn set_module_state_enables_known_module ( ) {
252+ let mut cfg = base ( ) ;
253+ cfg. set_module_state ( SHELL_MODULE_NAME , true ) . unwrap ( ) ;
254+ assert_eq ! ( cfg. modules. get( SHELL_MODULE_NAME ) , Some ( & true ) ) ;
255+ }
256+
257+ #[ test]
258+ fn set_module_state_blocks_enable_when_dep_missing ( ) {
259+ let mut cfg = base ( ) ;
260+ // lan requires net; net is disabled
261+ let err = cfg. set_module_state ( LAN_MODULE_NAME , true ) . unwrap_err ( ) ;
262+ assert ! ( err. contains( "net" ) , "error should mention missing dep: {err}" ) ;
263+ }
264+
265+ #[ test]
266+ fn set_module_state_blocks_disable_when_dependent_enabled ( ) {
267+ let mut cfg = base ( ) ;
268+ cfg. set_module_state ( NET_MODULE_NAME , true ) . unwrap ( ) ;
269+ cfg. set_module_state ( LAN_MODULE_NAME , true ) . unwrap ( ) ;
270+ let err = cfg. set_module_state ( NET_MODULE_NAME , false ) . unwrap_err ( ) ;
271+ assert ! ( err. contains( "lan" ) , "error should mention blocking dependent: {err}" ) ;
272+ }
273+
274+ #[ test]
275+ fn enable_with_requirements_transitively_enables_deps ( ) {
276+ let mut cfg = base ( ) ;
277+ cfg. enable_with_requirements ( LAN_MODULE_NAME ) . unwrap ( ) ;
278+ assert_eq ! ( cfg. modules. get( NET_MODULE_NAME ) , Some ( & true ) ) ;
279+ assert_eq ! ( cfg. modules. get( LAN_MODULE_NAME ) , Some ( & true ) ) ;
280+ }
281+
282+ #[ test]
283+ fn enable_with_requirements_remote_session_enables_net_and_lan ( ) {
284+ let mut cfg = base ( ) ;
285+ cfg. enable_with_requirements ( REMOTE_SESSION_MODULE_NAME ) . unwrap ( ) ;
286+ assert_eq ! ( cfg. modules. get( NET_MODULE_NAME ) , Some ( & true ) ) ;
287+ assert_eq ! ( cfg. modules. get( LAN_MODULE_NAME ) , Some ( & true ) ) ;
288+ assert_eq ! ( cfg. modules. get( REMOTE_SESSION_MODULE_NAME ) , Some ( & true ) ) ;
289+ }
290+
291+ #[ test]
292+ fn missing_requirements_for_lan_without_net ( ) {
293+ let cfg = base ( ) ;
294+ let missing = cfg. missing_requirements_for ( LAN_MODULE_NAME ) . unwrap ( ) ;
295+ assert ! ( missing. contains( & NET_MODULE_NAME . to_string( ) ) ) ;
296+ }
297+
298+ #[ test]
299+ fn missing_requirements_empty_when_dep_met ( ) {
300+ let mut cfg = base ( ) ;
301+ cfg. set_module_state ( NET_MODULE_NAME , true ) . unwrap ( ) ;
302+ let missing = cfg. missing_requirements_for ( LAN_MODULE_NAME ) . unwrap ( ) ;
303+ assert ! ( missing. is_empty( ) ) ;
304+ }
305+
306+ #[ test]
307+ fn missing_requirements_unknown_module_errors ( ) {
308+ let cfg = base ( ) ;
309+ assert ! ( cfg. missing_requirements_for( "does-not-exist" ) . is_err( ) ) ;
310+ }
311+
312+ #[ test]
313+ fn merge_defaults_migrates_legacy_lan_key ( ) {
314+ let mut cfg = ModulesConfig {
315+ modules : {
316+ let mut m = std:: collections:: BTreeMap :: new ( ) ;
317+ m. insert ( LEGACY_LAN_MODULE_NAME . to_string ( ) , true ) ;
318+ m
319+ } ,
320+ } ;
321+ let changed = cfg. merge_defaults ( ) ;
322+ assert ! ( changed) ;
323+ assert ! ( !cfg. modules. contains_key( LEGACY_LAN_MODULE_NAME ) ) ;
324+ assert_eq ! ( cfg. modules. get( LAN_MODULE_NAME ) , Some ( & true ) ) ;
325+ }
326+
327+ #[ test]
328+ fn merge_defaults_adds_missing_modules ( ) {
329+ let mut cfg = ModulesConfig { modules : std:: collections:: BTreeMap :: new ( ) } ;
330+ let changed = cfg. merge_defaults ( ) ;
331+ assert ! ( changed) ;
332+ for manifest in MODULE_REGISTRY {
333+ assert ! (
334+ cfg. modules. contains_key( manifest. name) ,
335+ "merge_defaults must add missing module '{}'" ,
336+ manifest. name
337+ ) ;
338+ }
339+ }
340+
341+ #[ test]
342+ fn manifest_for_known_module ( ) {
343+ let m = ModulesConfig :: manifest_for ( SHELL_MODULE_NAME ) . unwrap ( ) ;
344+ assert_eq ! ( m. name, SHELL_MODULE_NAME ) ;
345+ }
346+
347+ #[ test]
348+ fn manifest_for_unknown_returns_none ( ) {
349+ assert ! ( ModulesConfig :: manifest_for( "totally-fake" ) . is_none( ) ) ;
350+ }
351+
352+ #[ test]
353+ fn set_module_state_unknown_module_errors ( ) {
354+ let mut cfg = base ( ) ;
355+ assert ! ( cfg. set_module_state( "ghost-module" , true ) . is_err( ) ) ;
356+ }
357+
358+ #[ test]
359+ fn shell_motd_requires_shell ( ) {
360+ let mut cfg = base ( ) ;
361+ // shell-motd requires shell; shell disabled → enable should fail
362+ let err = cfg. set_module_state ( SHELL_MOTD_MODULE_NAME , true ) . unwrap_err ( ) ;
363+ assert ! ( err. contains( "shell" ) , "error should mention shell: {err}" ) ;
364+ // enable shell first, then motd should work
365+ cfg. set_module_state ( SHELL_MODULE_NAME , true ) . unwrap ( ) ;
366+ cfg. set_module_state ( SHELL_MOTD_MODULE_NAME , true ) . unwrap ( ) ;
367+ assert_eq ! ( cfg. modules. get( SHELL_MOTD_MODULE_NAME ) , Some ( & true ) ) ;
368+ }
369+ }
0 commit comments