@@ -1375,6 +1375,101 @@ public function testCloneFormWithConfirmationEmailQuestionId(): void {
13751375 $ this ->assertEquals (11 , $ clonedForm ->getConfirmationEmailQuestionId ());
13761376 }
13771377
1378+ /**
1379+ * @param int $formId the form a copied question comes from
1380+ * @return Question the source question, with one text to recognise it by
1381+ */
1382+ private function sourceQuestionInForm (int $ formId ): Question {
1383+ return Question::fromParams ([
1384+ 'id ' => 10 ,
1385+ 'formId ' => $ formId ,
1386+ 'order ' => 1 ,
1387+ 'type ' => 'short ' ,
1388+ 'text ' => 'Source question ' ,
1389+ 'description ' => '' ,
1390+ 'isRequired ' => false ,
1391+ ]);
1392+ }
1393+
1394+ public function testCloneQuestionFromAnotherForm (): void {
1395+ $ targetForm = Form::fromParams (['id ' => 1 , 'ownerId ' => 'currentUser ' ]);
1396+ $ sourceForm = Form::fromParams (['id ' => 2 , 'ownerId ' => 'currentUser ' ]);
1397+
1398+ // Editing rights are checked on both forms: the one being added to, and the one
1399+ // the question is read out of.
1400+ $ this ->formsService ->expects ($ this ->exactly (2 ))
1401+ ->method ('getFormIfAllowed ' )
1402+ ->willReturnCallback (fn (int $ id , string $ permission ) => match ([$ id , $ permission ]) {
1403+ [1 , Constants::PERMISSION_EDIT ] => $ targetForm ,
1404+ [2 , Constants::PERMISSION_EDIT ] => $ sourceForm ,
1405+ });
1406+
1407+ $ this ->questionMapper ->method ('findById ' )->with (10 )->willReturn ($ this ->sourceQuestionInForm (2 ));
1408+ $ this ->optionMapper ->method ('findByQuestion ' )->with (10 )->willReturn ([]);
1409+ $ this ->questionMapper ->method ('findByForm ' )->with (1 )->willReturn ([
1410+ Question::fromParams (['id ' => 20 , 'formId ' => 1 , 'order ' => 3 , 'type ' => 'short ' ]),
1411+ ]);
1412+
1413+ $ inserted = null ;
1414+ $ this ->questionMapper ->expects ($ this ->once ())
1415+ ->method ('insert ' )
1416+ ->with ($ this ->callback (function (Question $ question ) use (&$ inserted ) {
1417+ $ inserted = $ question ;
1418+ return true ;
1419+ }));
1420+
1421+ $ this ->apiController ->newQuestion (1 , fromId: 10 );
1422+
1423+ // Created in the form it was copied into, not back in the one it came from.
1424+ $ this ->assertEquals (1 , $ inserted ->getFormId ());
1425+ $ this ->assertEquals (4 , $ inserted ->getOrder ());
1426+ $ this ->assertEquals ('Source question ' , $ inserted ->getText ());
1427+ }
1428+
1429+ public function testCloneQuestionIntoEmptyForm (): void {
1430+ $ targetForm = Form::fromParams (['id ' => 1 , 'ownerId ' => 'currentUser ' ]);
1431+ $ sourceForm = Form::fromParams (['id ' => 2 , 'ownerId ' => 'currentUser ' ]);
1432+ $ this ->formsService ->method ('getFormIfAllowed ' )
1433+ ->willReturnCallback (fn (int $ id ) => $ id === 1 ? $ targetForm : $ sourceForm );
1434+
1435+ $ this ->questionMapper ->method ('findById ' )->with (10 )->willReturn ($ this ->sourceQuestionInForm (2 ));
1436+ $ this ->optionMapper ->method ('findByQuestion ' )->with (10 )->willReturn ([]);
1437+ // A new form with nothing in it yet: the usual target when copying questions over.
1438+ $ this ->questionMapper ->method ('findByForm ' )->with (1 )->willReturn ([]);
1439+
1440+ $ inserted = null ;
1441+ $ this ->questionMapper ->expects ($ this ->once ())
1442+ ->method ('insert ' )
1443+ ->with ($ this ->callback (function (Question $ question ) use (&$ inserted ) {
1444+ $ inserted = $ question ;
1445+ return true ;
1446+ }));
1447+
1448+ $ this ->apiController ->newQuestion (1 , fromId: 10 );
1449+
1450+ $ this ->assertEquals (1 , $ inserted ->getFormId ());
1451+ $ this ->assertEquals (1 , $ inserted ->getOrder ());
1452+ }
1453+
1454+ public function testCloneQuestionFromFormWithoutEditRights (): void {
1455+ $ targetForm = Form::fromParams (['id ' => 1 , 'ownerId ' => 'currentUser ' ]);
1456+ $ this ->formsService ->method ('getFormIfAllowed ' )
1457+ ->willReturnCallback (function (int $ id ) use ($ targetForm ) {
1458+ if ($ id === 1 ) {
1459+ return $ targetForm ;
1460+ }
1461+ throw new NoSuchFormException ('User has no permissions to get this form ' );
1462+ });
1463+
1464+ $ this ->questionMapper ->method ('findById ' )->with (10 )->willReturn ($ this ->sourceQuestionInForm (2 ));
1465+ // Nothing may be read out of a form the user cannot edit, let alone copied.
1466+ $ this ->optionMapper ->expects ($ this ->never ())->method ('findByQuestion ' );
1467+ $ this ->questionMapper ->expects ($ this ->never ())->method ('insert ' );
1468+
1469+ $ this ->expectException (NoSuchFormException::class);
1470+ $ this ->apiController ->newQuestion (1 , fromId: 10 );
1471+ }
1472+
13781473 public function testTransferOwnerNotOwner () {
13791474 $ form = new Form ();
13801475 $ form ->setId (1 );
0 commit comments