@@ -188,7 +188,7 @@ public static function dockerComposerFileProvider(): iterable
188188 /**
189189 * @dataProvider dockerComposerFileProvider
190190 */
191- public function testConfigure (string $ fileName )
191+ public function testConfigure (string $ fileName ): void
192192 {
193193 $ dockerComposeFile = FLEX_TEST_DIR ."/ $ fileName " ;
194194 file_put_contents ($ dockerComposeFile , self ::ORIGINAL_CONTENT );
@@ -224,7 +224,38 @@ public function testConfigure(string $fileName)
224224 $ this ->assertEquals (self ::ORIGINAL_CONTENT , file_get_contents ($ dockerComposeFile ));
225225 }
226226
227- public function testNotConfiguredIfConfigSet ()
227+ /**
228+ * Re-configuring a recipe that only touches a non-last top-level key (e.g. `services`)
229+ * on a file that also has a `volumes:` key must not duplicate the `volumes:` line.
230+ * This is what happens during `composer recipes:install --reset --force`.
231+ */
232+ public function testReconfigureDoesNotDuplicateLaterTopLevelKey (): void
233+ {
234+ // Recipe that contributes BOTH services and volumes (sets up the file shape).
235+ $ this ->configurator ->configure ($ this ->recipeDb , self ::CONFIG_DB , $ this ->lock );
236+
237+ // Recipe that only contributes `services` (e.g. shopware/docker-dev, symfony/mailer).
238+ $ servicesOnlyRecipe = $ this ->getMockBuilder (Recipe::class)->disableOriginalConstructor ()->getMock ();
239+ $ servicesOnlyRecipe ->method ('getName ' )->willReturn ('acme/services-only ' );
240+ $ servicesOnlyConfig = [
241+ 'services ' => [
242+ 'cache: ' ,
243+ ' image: redis:alpine ' ,
244+ ],
245+ ];
246+
247+ $ this ->configurator ->configure ($ servicesOnlyRecipe , $ servicesOnlyConfig , $ this ->lock );
248+ $ afterFirstConfigure = file_get_contents (FLEX_TEST_DIR .'/compose.yaml ' );
249+
250+ // Simulate `recipes:install --reset --force` re-running configure on the marked file.
251+ $ this ->configurator ->configure ($ servicesOnlyRecipe , $ servicesOnlyConfig , $ this ->lock , ['force ' => true ]);
252+ $ afterSecondConfigure = file_get_contents (FLEX_TEST_DIR .'/compose.yaml ' );
253+
254+ $ this ->assertSame (1 , substr_count ($ afterSecondConfigure , "\nvolumes: \n" ), 'compose.yaml must contain exactly one top-level "volumes:" key ' );
255+ $ this ->assertSame ($ afterFirstConfigure , $ afterSecondConfigure , 'configure must be idempotent when re-run ' );
256+ }
257+
258+ public function testNotConfiguredIfConfigSet (): void
228259 {
229260 $ this ->package ->setExtra (['symfony ' => ['docker ' => false ]]);
230261 $ this ->configurator ->configure ($ this ->recipeDb , self ::CONFIG_DB , $ this ->lock );
@@ -235,7 +266,7 @@ public function testNotConfiguredIfConfigSet()
235266 /**
236267 * @dataProvider getInteractiveDockerPreferenceTests
237268 */
238- public function testPreferenceAskedInteractively (string $ userInput , bool $ expectedIsConfigured , bool $ expectedIsComposerJsonUpdated )
269+ public function testPreferenceAskedInteractively (string $ userInput , bool $ expectedIsConfigured , bool $ expectedIsComposerJsonUpdated ): void
239270 {
240271 $ composerJsonPath = FLEX_TEST_DIR .'/composer.json ' ;
241272 file_put_contents ($ composerJsonPath , json_encode (['name ' => 'test/app ' ]));
@@ -270,7 +301,7 @@ public function getInteractiveDockerPreferenceTests()
270301 yield 'no_forever ' => ['x ' , false , true ];
271302 }
272303
273- public function testEnvVarUsedForDockerConfirmation ()
304+ public function testEnvVarUsedForDockerConfirmation (): void
274305 {
275306 $ composerJsonPath = FLEX_TEST_DIR .'/composer.json ' ;
276307 file_put_contents ($ composerJsonPath , json_encode (['name ' => 'test/app ' ]));
@@ -290,7 +321,7 @@ public function testEnvVarUsedForDockerConfirmation()
290321 $ this ->assertTrue ($ composerJsonData ['extra ' ]['symfony ' ]['docker ' ]);
291322 }
292323
293- public function testConfigureFileWithExistingVolumes ()
324+ public function testConfigureFileWithExistingVolumes (): void
294325 {
295326 $ originalContent = self ::ORIGINAL_CONTENT .<<<'YAML'
296327
@@ -341,7 +372,7 @@ public function testConfigureFileWithExistingVolumes()
341372 );
342373 }
343374
344- public function testConfigureFileWithExistingMarks ()
375+ public function testConfigureFileWithExistingMarks (): void
345376 {
346377 $ originalContent = self ::ORIGINAL_CONTENT .<<<'YAML'
347378
@@ -435,7 +466,7 @@ public function testConfigureFileWithExistingMarks()
435466 $ this ->assertEquals (self ::ORIGINAL_CONTENT , file_get_contents ($ dockerComposeFile ));
436467 }
437468
438- public function testUnconfigureFileWithManyMarks ()
469+ public function testUnconfigureFileWithManyMarks (): void
439470 {
440471 $ originalContent = self ::ORIGINAL_CONTENT .<<<'YAML'
441472
@@ -507,7 +538,7 @@ public function testUnconfigureFileWithManyMarks()
507538 $ this ->assertStringEqualsFile ($ dockerComposeFile , $ contentWithoutDoctrine );
508539 }
509540
510- public function testConfigureMultipleFiles ()
541+ public function testConfigureMultipleFiles (): void
511542 {
512543 $ dockerComposeFile = FLEX_TEST_DIR .'/docker-compose.yml ' ;
513544 file_put_contents ($ dockerComposeFile , self ::ORIGINAL_CONTENT );
@@ -548,7 +579,7 @@ public function testConfigureMultipleFiles()
548579 $ this ->assertStringEqualsFile ($ dockerComposeOverrideFile , self ::ORIGINAL_CONTENT );
549580 }
550581
551- public function testConfigureEnvVar ()
582+ public function testConfigureEnvVar (): void
552583 {
553584 @mkdir (FLEX_TEST_DIR .'/child/ ' );
554585 $ dockerComposeFile = FLEX_TEST_DIR .'/child/docker-compose.yml ' ;
@@ -593,7 +624,7 @@ public function testConfigureEnvVar()
593624 $ this ->assertStringEqualsFile ($ dockerComposeOverrideFile , self ::ORIGINAL_CONTENT );
594625 }
595626
596- public function testConfigureFileInParentDir ()
627+ public function testConfigureFileInParentDir (): void
597628 {
598629 $ this ->configurator = new DockerComposeConfigurator (
599630 $ this ->composer ,
@@ -636,7 +667,7 @@ public function testConfigureFileInParentDir()
636667 $ this ->assertEquals (self ::ORIGINAL_CONTENT , file_get_contents ($ dockerComposeFile ));
637668 }
638669
639- public function testConfigureWithoutExistingDockerComposeFiles ()
670+ public function testConfigureWithoutExistingDockerComposeFiles (): void
640671 {
641672 $ dockerComposeFile = FLEX_TEST_DIR .'/compose.yaml ' ;
642673
@@ -672,7 +703,7 @@ public function testConfigureWithoutExistingDockerComposeFiles()
672703 $ this ->assertEquals ('' , file_get_contents ($ dockerComposeFile ));
673704 }
674705
675- public function testUpdate ()
706+ public function testUpdate (): void
676707 {
677708 $ recipe = $ this ->createMock (Recipe::class);
678709 $ recipe ->method ('getName ' )
0 commit comments