@@ -130,8 +130,7 @@ fn warns_for_every_unmapped_field() {
130130services:
131131 s:
132132 image: x
133- network_mode: "container:other"
134- privileged: true
133+ network_mode: "bridge:custom"
135134 profiles: [debug]
136135 volumes_from:
137136 - other
@@ -141,13 +140,7 @@ services:
141140 let file = parse_str ( yaml) . unwrap ( ) ;
142141 let out = generate ( & file, "p" ) ;
143142 let joined = out. warnings . join ( "\n " ) ;
144- for needle in [
145- "network_mode" ,
146- "privileged" ,
147- "profiles" ,
148- "volumes_from" ,
149- "scale/replicas" ,
150- ] {
143+ for needle in [ "network_mode" , "profiles" , "volumes_from" , "scale/replicas" ] {
151144 assert ! ( joined. contains( needle) , "expected warning for {needle}" ) ;
152145 }
153146}
@@ -334,3 +327,123 @@ volumes:
334327 // An option with no dedicated key falls back to PodmanArgs=--opt.
335328 assert ! ( vol. contains( "PodmanArgs=--opt custom=extra" ) , "in:\n {vol}" ) ;
336329}
330+
331+ #[ test]
332+ fn healthcheck_start_interval_is_warned_and_omitted ( ) {
333+ // `start_interval` has no Quadlet/Podman equivalent: it must not emit a
334+ // `HealthStartupInterval=` (which drives an unrelated, no-op startup
335+ // healthcheck) and must instead produce a warning.
336+ let yaml = r#"
337+ services:
338+ s:
339+ image: x
340+ healthcheck:
341+ test: ["CMD", "true"]
342+ interval: 5s
343+ start_interval: 2s
344+ "# ;
345+ let file = parse_str ( yaml) . unwrap ( ) ;
346+ let out = generate ( & file, "p" ) ;
347+ let c = & unit_named ( & out, "s.container" ) . contents ;
348+ assert ! ( c. contains( "HealthInterval=5s" ) , "in:\n {c}" ) ;
349+ assert ! (
350+ !c. contains( "HealthStartupInterval" ) ,
351+ "start_interval must not emit HealthStartupInterval=; got:\n {c}"
352+ ) ;
353+ let joined = out. warnings . join ( "\n " ) ;
354+ assert ! (
355+ joined. contains( "start_interval" ) ,
356+ "start_interval must warn; got:\n {joined}"
357+ ) ;
358+ }
359+
360+ #[ test]
361+ fn dependency_unit_names_are_sanitized_in_ordering ( ) {
362+ // A dependency whose compose key sanitizes to a different stem must be
363+ // referenced by that stem in After=/Requires=, matching the generated unit.
364+ let yaml = r#"
365+ services:
366+ web:
367+ image: nginx
368+ depends_on:
369+ - "db:1"
370+ ? "db:1"
371+ : { image: postgres }
372+ "# ;
373+ let file = parse_str ( yaml) . unwrap ( ) ;
374+ let out = generate ( & file, "proj" ) ;
375+ let web = & unit_named ( & out, "web.container" ) . contents ;
376+ assert ! ( web. contains( "After=db_1.service" ) , "in:\n {web}" ) ;
377+ assert ! ( web. contains( "Requires=db_1.service" ) , "in:\n {web}" ) ;
378+ // The raw, unsanitized name must not leak into the ordering directives.
379+ assert ! ( !web. contains( "db:1.service" ) , "in:\n {web}" ) ;
380+ // The dependency's own unit really is named with the sanitized stem.
381+ unit_named ( & out, "db_1.container" ) ;
382+ }
383+
384+ #[ test]
385+ fn network_mode_service_and_container_map_to_dot_container ( ) {
386+ // `network_mode: service:X` / `container:X` reuse another container's netns,
387+ // which Quadlet expresses as `Network={X}.container`.
388+ for ( mode, target) in [ ( "service:db" , "db" ) , ( "container:sidecar" , "sidecar" ) ] {
389+ let yaml = format ! ( "services:\n s:\n image: x\n network_mode: \" {mode}\" \n " ) ;
390+ let file = parse_str ( & yaml) . unwrap ( ) ;
391+ let out = generate ( & file, "p" ) ;
392+ let c = & unit_named ( & out, "s.container" ) . contents ;
393+ assert ! (
394+ c. contains( & format!( "Network={target}.container" ) ) ,
395+ "{mode} must map to Network={target}.container; got:\n {c}"
396+ ) ;
397+ }
398+ }
399+
400+ #[ test]
401+ fn network_mode_service_target_is_sanitized ( ) {
402+ let yaml = "services:\n s:\n image: x\n network_mode: \" service:web:1\" \n " ;
403+ let file = parse_str ( yaml) . unwrap ( ) ;
404+ let out = generate ( & file, "p" ) ;
405+ let c = & unit_named ( & out, "s.container" ) . contents ;
406+ assert ! ( c. contains( "Network=web_1.container" ) , "in:\n {c}" ) ;
407+ }
408+
409+ #[ test]
410+ fn volume_and_network_units_have_no_install_section ( ) {
411+ let yaml = r#"
412+ services:
413+ s:
414+ image: x
415+ networks:
416+ net:
417+ volumes:
418+ vol:
419+ "# ;
420+ let file = parse_str ( yaml) . unwrap ( ) ;
421+ let out = generate ( & file, "p" ) ;
422+ let net = & unit_named ( & out, "net.network" ) . contents ;
423+ let vol = & unit_named ( & out, "vol.volume" ) . contents ;
424+ assert ! (
425+ !net. contains( "[Install]" ) && !net. contains( "WantedBy" ) ,
426+ ".network must carry no [Install]; got:\n {net}"
427+ ) ;
428+ assert ! (
429+ !vol. contains( "[Install]" ) && !vol. contains( "WantedBy" ) ,
430+ ".volume must carry no [Install]; got:\n {vol}"
431+ ) ;
432+ // The container unit still carries its [Install].
433+ let c = & unit_named ( & out, "s.container" ) . contents ;
434+ assert ! ( c. contains( "[Install]" ) && c. contains( "WantedBy=default.target" ) ) ;
435+ }
436+
437+ #[ test]
438+ fn privileged_maps_to_podman_arg ( ) {
439+ let yaml = "services:\n s:\n image: x\n privileged: true\n " ;
440+ let file = parse_str ( yaml) . unwrap ( ) ;
441+ let out = generate ( & file, "p" ) ;
442+ let c = & unit_named ( & out, "s.container" ) . contents ;
443+ assert ! ( c. contains( "PodmanArgs=--privileged" ) , "in:\n {c}" ) ;
444+ assert ! (
445+ !out. warnings. iter( ) . any( |w| w. contains( "privileged" ) ) ,
446+ "privileged must be mapped, not warned; got: {:?}" ,
447+ out. warnings
448+ ) ;
449+ }
0 commit comments