Spaces:
No application file
No application file
declare(strict_types=1); | |
namespace Mautic\FormBundle\Tests\Controller\Api; | |
use Mautic\CoreBundle\Test\MauticMysqlTestCase; | |
use Mautic\FormBundle\Entity\Submission; | |
use Mautic\LeadBundle\Entity\Company; | |
use PHPUnit\Framework\Assert; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\HttpFoundation\Response; | |
final class FormApiControllerFunctionalTest extends MauticMysqlTestCase | |
{ | |
protected $useCleanupRollback = false; | |
private const TEST_PAYLOAD = [ | |
'name' => 'API form', | |
'description' => 'Form created via API test', | |
'formType' => 'standalone', | |
'isPublished' => true, | |
'fields' => [ | |
[ | |
'label' => 'Email', | |
'type' => 'text', | |
'alias' => 'email', | |
'mappedObject' => 'contact', | |
'mappedField' => 'email', | |
'showLabel' => true, | |
'isRequired' => true, | |
], | |
[ | |
'label' => 'Number', | |
'type' => 'number', | |
'alias' => 'number', | |
'leadField' => 'points', // @deprecated Setting leadField, no mappedField or mappedObject (BC). | |
], | |
[ | |
'label' => 'Company', | |
'type' => 'text', | |
'alias' => 'company', | |
'leadField' => 'company', // @deprecated Setting leadField, no mappedField or mappedObject (BC). | |
], | |
[ | |
'label' => 'Company Phone', | |
'type' => 'tel', | |
'alias' => 'phone', | |
'leadField' => 'companyphone', // @deprecated Setting leadField, no mappedField or mappedObject (BC). | |
], | |
[ | |
'label' => 'Country', | |
'type' => 'country', | |
'alias' => 'country', | |
'mappedObject' => 'contact', | |
'mappedField' => 'country', | |
], | |
[ | |
'label' => 'Multiselect', | |
'type' => 'select', | |
'alias' => 'multiselect', | |
'properties' => [ | |
'syncList' => 0, | |
'multiple' => 1, | |
'list' => [ | |
'list' => [ | |
[ | |
'label' => 'One', | |
'value' => 'one', | |
], | |
[ | |
'label' => 'Two', | |
'value' => 'two', | |
], | |
], | |
], | |
], | |
], | |
[ | |
'label' => 'Submit', | |
'type' => 'button', | |
], | |
], | |
'actions' => [ | |
], | |
'postAction' => 'return', | |
]; | |
public function testFormWorkflow(): void | |
{ | |
$payload = [ | |
'name' => 'Form API test', | |
'formType' => 'standalone', | |
'isPublished' => true, | |
'description' => 'Functional API test', | |
'fields' => [ | |
[ | |
'label' => 'Email', | |
'alias' => 'email', | |
'type' => 'text', | |
'leadField' => 'email', | |
], | |
], | |
'postAction' => 'return', | |
]; | |
// Create: | |
$this->client->request('POST', '/api/forms/new', $payload); | |
$clientResponse = $this->client->getResponse(); | |
$response = json_decode($clientResponse->getContent(), true); | |
if (!empty($response['errors'][0])) { | |
$this->fail($response['errors'][0]['code'].': '.$response['errors'][0]['message']); | |
} | |
$this->assertSame(Response::HTTP_CREATED, $clientResponse->getStatusCode(), 'Return code must be 201.'); | |
$formId = $response['form']['id']; | |
$this->assertGreaterThan(0, $formId); | |
$this->assertEquals($payload['name'], $response['form']['name']); | |
$this->assertEquals($payload['formType'], $response['form']['formType']); | |
$this->assertEquals($payload['isPublished'], $response['form']['isPublished']); | |
$this->assertEquals($payload['description'], $response['form']['description']); | |
$this->assertIsArray($response['form']['fields']); | |
$this->assertCount(count($payload['fields']), $response['form']['fields']); | |
for ($i = 0; $i < count($payload['fields']); ++$i) { | |
$this->assertEquals($payload['fields'][$i]['label'], $response['form']['fields'][$i]['label']); | |
$this->assertEquals($payload['fields'][$i]['alias'], $response['form']['fields'][$i]['alias']); | |
$this->assertEquals($payload['fields'][$i]['type'], $response['form']['fields'][$i]['type']); | |
$this->assertEquals($payload['fields'][$i]['leadField'], $response['form']['fields'][$i]['leadField']); | |
} | |
// Edit: | |
$this->client->request('PATCH', "/api/forms/{$formId}/edit", ['name' => 'Form API renamed']); | |
$clientResponse = $this->client->getResponse(); | |
$response = json_decode($clientResponse->getContent(), true); | |
$this->assertSame(Response::HTTP_OK, $clientResponse->getStatusCode()); | |
$this->assertSame($formId, $response['form']['id'], 'ID of the created form does not match with the edited one.'); | |
$this->assertEquals('Form API renamed', $response['form']['name']); | |
$this->assertEquals($payload['formType'], $response['form']['formType']); | |
$this->assertEquals($payload['isPublished'], $response['form']['isPublished']); | |
$this->assertEquals($payload['description'], $response['form']['description']); | |
$this->assertIsArray($response['form']['fields']); | |
$this->assertCount(count($payload['fields']), $response['form']['fields']); | |
for ($i = 0; $i < count($payload['fields']); ++$i) { | |
$this->assertEquals($payload['fields'][$i]['label'], $response['form']['fields'][$i]['label']); | |
$this->assertEquals($payload['fields'][$i]['alias'], $response['form']['fields'][$i]['alias']); | |
$this->assertEquals($payload['fields'][$i]['type'], $response['form']['fields'][$i]['type']); | |
$this->assertEquals($payload['fields'][$i]['leadField'], $response['form']['fields'][$i]['leadField']); | |
} | |
} | |
public function testSingleFormWorkflow(): void | |
{ | |
$payload = self::TEST_PAYLOAD; | |
$fieldCount = count($payload['fields']); | |
$this->client->request(Request::METHOD_POST, '/api/forms/new', $payload); | |
$clientResponse = $this->client->getResponse(); | |
$response = json_decode($clientResponse->getContent(), true); | |
Assert::assertTrue(isset($response['form']['id']), $clientResponse->getContent()); | |
$formId = $response['form']['id']; | |
$this->assertSame(Response::HTTP_CREATED, $clientResponse->getStatusCode(), $clientResponse->getContent()); | |
$this->assertGreaterThan(0, $formId); | |
$this->assertEquals($payload['name'], $response['form']['name']); | |
$this->assertEquals($payload['description'], $response['form']['description']); | |
$this->assertEquals($payload['formType'], $response['form']['formType']); | |
$this->assertNotEmpty($response['form']['cachedHtml']); | |
$this->assertCount($fieldCount, $response['form']['fields']); | |
$this->assertEquals($payload['fields'][0]['label'], $response['form']['fields'][0]['label']); | |
$this->assertEquals($payload['fields'][0]['type'], $response['form']['fields'][0]['type']); | |
$this->assertEquals($payload['fields'][0]['mappedObject'], $response['form']['fields'][0]['mappedObject']); | |
$this->assertEquals($payload['fields'][0]['mappedField'], $response['form']['fields'][0]['mappedField']); | |
$this->assertEquals( | |
$payload['fields'][0]['mappedField'], | |
$response['form']['fields'][0]['leadField'] | |
); // @deprecated leadField was replaced by mappedField. Check for BC. | |
$this->assertEquals($payload['fields'][0]['showLabel'], $response['form']['fields'][0]['showLabel']); | |
$this->assertEquals($payload['fields'][0]['isRequired'], $response['form']['fields'][0]['isRequired']); | |
$this->assertEquals($payload['fields'][1]['label'], $response['form']['fields'][1]['label']); | |
$this->assertEquals($payload['fields'][1]['type'], $response['form']['fields'][1]['type']); | |
$this->assertEquals('contact', $response['form']['fields'][1]['mappedObject']); | |
$this->assertEquals('points', $response['form']['fields'][1]['mappedField']); | |
$this->assertEquals( | |
$payload['fields'][1]['leadField'], | |
$response['form']['fields'][1]['leadField'] | |
); // @deprecated leadField was replaced by mappedField. Check for BC. | |
$this->assertTrue($response['form']['fields'][1]['showLabel']); | |
$this->assertFalse($response['form']['fields'][1]['isRequired']); | |
$this->assertEquals($payload['fields'][2]['label'], $response['form']['fields'][2]['label']); | |
$this->assertEquals($payload['fields'][2]['type'], $response['form']['fields'][2]['type']); | |
$this->assertEquals('contact', $response['form']['fields'][2]['mappedObject']); | |
$this->assertEquals('company', $response['form']['fields'][2]['mappedField']); | |
$this->assertEquals( | |
$payload['fields'][2]['leadField'], | |
$response['form']['fields'][2]['leadField'] | |
); // @deprecated leadField was replaced by mappedField. Check for BC. | |
$this->assertEquals($payload['fields'][3]['label'], $response['form']['fields'][3]['label']); | |
$this->assertEquals($payload['fields'][3]['type'], $response['form']['fields'][3]['type']); | |
$this->assertEquals('company', $response['form']['fields'][3]['mappedObject']); | |
$this->assertEquals('companyphone', $response['form']['fields'][3]['mappedField']); | |
$this->assertEquals( | |
$payload['fields'][3]['leadField'], | |
$response['form']['fields'][3]['leadField'] | |
); // @deprecated leadField was replaced by mappedField. Check for BC. | |
// Edit PATCH: | |
$patchPayload = [ | |
'name' => 'API form renamed', | |
'fields' => [ | |
[ | |
'label' => 'State', | |
'type' => 'select', | |
'alias' => 'state', | |
'mappedObject' => 'contact', | |
'mappedField' => 'state', | |
'parent' => $response['form']['fields'][4]['id'], | |
'conditions' => [ | |
'expr' => 'in', | |
'any' => 1, | |
'values' => [], | |
], | |
'properties' => [ | |
'syncList' => 1, | |
'multiple' => 0, | |
], | |
], | |
], | |
]; | |
$this->client->request(Request::METHOD_PATCH, "/api/forms/{$formId}/edit", $patchPayload); | |
$clientResponse = $this->client->getResponse(); | |
$response = json_decode($clientResponse->getContent(), true); | |
$fieldCount = $fieldCount + 1; | |
$this->assertSame(Response::HTTP_OK, $clientResponse->getStatusCode(), $clientResponse->getContent()); | |
$this->assertSame($formId, $response['form']['id']); | |
$this->assertEquals('API form renamed', $response['form']['name']); | |
$this->assertEquals($payload['description'], $response['form']['description']); | |
$this->assertCount($fieldCount, $response['form']['fields']); | |
$this->assertEquals($payload['formType'], $response['form']['formType']); | |
$this->assertNotEmpty($response['form']['cachedHtml']); | |
// Edit PUT: | |
$payload['description'] .= ' renamed'; | |
$payload['fields'] = []; // Set fields to an empty array as it would duplicate all fields. | |
$payload['postAction'] = 'return'; // Must be present for PUT as all empty values are being cleared. | |
$this->client->request(Request::METHOD_PUT, "/api/forms/{$formId}/edit", $payload); | |
$clientResponse = $this->client->getResponse(); | |
$response = json_decode($clientResponse->getContent(), true); | |
$this->assertSame(Response::HTTP_OK, $clientResponse->getStatusCode(), $clientResponse->getContent()); | |
$this->assertSame($formId, $response['form']['id']); | |
$this->assertEquals($payload['name'], $response['form']['name']); | |
$this->assertEquals('Form created via API test renamed', $response['form']['description']); | |
$this->assertCount($fieldCount, $response['form']['fields']); | |
$this->assertEquals($payload['formType'], $response['form']['formType']); | |
$this->assertNotEmpty($response['form']['cachedHtml']); | |
// Get: | |
$this->client->request(Request::METHOD_GET, "/api/forms/{$formId}"); | |
$clientResponse = $this->client->getResponse(); | |
$response = json_decode($clientResponse->getContent(), true); | |
$this->assertSame(Response::HTTP_OK, $clientResponse->getStatusCode(), $clientResponse->getContent()); | |
$this->assertSame($formId, $response['form']['id']); | |
$this->assertEquals($payload['name'], $response['form']['name']); | |
$this->assertEquals($payload['description'], $response['form']['description']); | |
$this->assertCount($fieldCount, $response['form']['fields']); | |
$this->assertEquals($payload['formType'], $response['form']['formType']); | |
$this->assertNotEmpty($response['form']['cachedHtml']); | |
// Submit the form: | |
$crawler = $this->client->request(Request::METHOD_GET, "/form/{$formId}"); | |
$formCrawler = $crawler->filter('form[id=mauticform_apiform]'); | |
$this->assertSame(1, $formCrawler->count()); | |
$form = $formCrawler->form(); | |
$form->setValues([ | |
'mauticform[email]' => '[email protected]', | |
'mauticform[number]' => '123', | |
'mauticform[company]' => 'Doe Corp', | |
'mauticform[phone]' => '+420444555666', | |
'mauticform[country]' => 'Czech Republic', | |
'mauticform[state]' => 'Plzeňský kraj', | |
'mauticform[multiselect]' => ['two'], | |
]); | |
$this->client->submit($form); | |
// Ensure the submission was created properly. | |
$submissions = $this->em->getRepository(Submission::class)->findAll(); | |
Assert::assertCount(1, $submissions); | |
/** @var Submission $submission */ | |
$submission = $submissions[0]; | |
Assert::assertSame([ | |
'email' => '[email protected]', | |
'number' => 123.0, | |
'company' => 'Doe Corp', | |
'phone' => '+420444555666', | |
'country' => 'Czech Republic', | |
'multiselect' => 'two', | |
'state' => 'Plzeňský kraj', | |
], $submission->getResults()); | |
// A contact should be created by the submission. | |
$contact = $submission->getLead(); | |
Assert::assertSame('[email protected]', $contact->getEmail()); | |
Assert::assertSame('Czech Republic', $contact->getCountry()); | |
Assert::assertSame('Plzeňský kraj', $contact->getState()); | |
Assert::assertSame(123, $contact->getPoints()); | |
Assert::assertSame('Doe Corp', $contact->getCompany()); | |
$companies = $this->em->getRepository(Company::class)->findAll(); | |
Assert::assertCount(1, $companies); | |
// A company should be created by the submission. | |
/** @var Company $company */ | |
$company = $companies[0]; | |
Assert::assertSame('Doe Corp', $company->getName()); | |
Assert::assertSame('+420444555666', $company->getPhone()); | |
// The previous request changes user to anonymous. We have to configure API again. | |
$this->setUpSymfony($this->configParams); | |
// Delete: | |
$this->client->request(Request::METHOD_DELETE, "/api/forms/{$formId}/delete"); | |
$clientResponse = $this->client->getResponse(); | |
$response = json_decode($clientResponse->getContent(), true); | |
$this->assertSame(Response::HTTP_OK, $clientResponse->getStatusCode(), $clientResponse->getContent()); | |
$this->assertNull($response['form']['id']); | |
$this->assertEquals($payload['name'], $response['form']['name']); | |
$this->assertEquals($payload['description'], $response['form']['description']); | |
$this->assertCount($fieldCount, $response['form']['fields']); | |
$this->assertEquals($payload['formType'], $response['form']['formType']); | |
$this->assertNotEmpty($response['form']['cachedHtml']); | |
// Get (ensure that the form is gone): | |
$this->client->request(Request::METHOD_GET, "/api/forms/{$formId}"); | |
$clientResponse = $this->client->getResponse(); | |
$response = json_decode($clientResponse->getContent(), true); | |
$this->assertSame(Response::HTTP_NOT_FOUND, $clientResponse->getStatusCode(), $clientResponse->getContent()); | |
$this->assertSame(Response::HTTP_NOT_FOUND, $response['errors'][0]['code']); | |
} | |
public function testFormWithChangeTagsAction(): void | |
{ | |
// Create tag: | |
$tag1Payload = ['tag' => 'add this']; | |
$tag2Payload = ['tag' => 'remove this']; | |
$this->client->request('POST', '/api/tags/new', $tag1Payload); | |
$clientResponse = $this->client->getResponse(); | |
$response = json_decode($clientResponse->getContent(), true); | |
$tag1Id = $response['tag']['id']; | |
$this->client->request('POST', '/api/tags/new', $tag2Payload); | |
$clientResponse = $this->client->getResponse(); | |
$response = json_decode($clientResponse->getContent(), true); | |
$tag2Id = $response['tag']['id']; | |
$payload = [ | |
'name' => 'Form API test', | |
'formType' => 'standalone', | |
'isPublished' => true, | |
'description' => 'Functional API test', | |
'fields' => [ | |
[ | |
'label' => 'lab', | |
'alias' => 'email', | |
'type' => 'text', | |
'leadField' => 'email', | |
], | |
], | |
'actions' => [ | |
[ | |
'name' => 'Add tags to contact', | |
'description' => 'action description', | |
'type' => 'lead.changetags', | |
'order' => 1, | |
'properties' => [ | |
'add_tags' => [$tag1Id], | |
'remove_tags' => [$tag2Id], | |
], | |
], | |
], | |
'postAction' => 'return', | |
]; | |
// Create form with lead.changetags action: | |
$this->client->request('POST', '/api/forms/new', $payload); | |
$clientResponse = $this->client->getResponse(); | |
$response = json_decode($clientResponse->getContent(), true); | |
if (!empty($response['errors'][0])) { | |
$this->fail($response['errors'][0]['code'].': '.$response['errors'][0]['message']); | |
} | |
$this->assertSame(Response::HTTP_CREATED, $clientResponse->getStatusCode(), 'Return code must be 201.'); | |
$formId = $response['form']['id']; | |
$this->assertGreaterThan(0, $formId); | |
$this->assertEquals($payload['name'], $response['form']['name']); | |
$this->assertEquals($payload['formType'], $response['form']['formType']); | |
$this->assertEquals($payload['isPublished'], $response['form']['isPublished']); | |
$this->assertEquals($payload['description'], $response['form']['description']); | |
$this->assertIsArray($response['form']['fields']); | |
$this->assertCount(count($payload['fields']), $response['form']['fields']); | |
for ($i = 0; $i < count($payload['fields']); ++$i) { | |
$this->assertEquals($payload['fields'][$i]['label'], $response['form']['fields'][$i]['label']); | |
$this->assertEquals($payload['fields'][$i]['alias'], $response['form']['fields'][$i]['alias']); | |
$this->assertEquals($payload['fields'][$i]['type'], $response['form']['fields'][$i]['type']); | |
$this->assertEquals($payload['fields'][$i]['leadField'], $response['form']['fields'][$i]['leadField']); | |
} | |
$this->assertIsArray($response['form']['actions']); | |
$this->assertCount(count($payload['actions']), $response['form']['actions']); | |
$this->assertEquals($payload['actions'][0]['name'], $response['form']['actions'][0]['name']); | |
$this->assertEquals($payload['actions'][0]['description'], $response['form']['actions'][0]['description']); | |
$this->assertEquals($payload['actions'][0]['type'], $response['form']['actions'][0]['type']); | |
$this->assertEquals($payload['actions'][0]['order'], $response['form']['actions'][0]['order']); | |
$this->assertIsArray($response['form']['actions'][0]['properties']['add_tags']); | |
$this->assertIsArray($response['form']['actions'][0]['properties']['remove_tags']); | |
$this->assertEquals($tag1Payload['tag'], $response['form']['actions'][0]['properties']['add_tags'][0]); | |
$this->assertEquals($tag2Payload['tag'], $response['form']['actions'][0]['properties']['remove_tags'][0]); | |
} | |
public function testFormWithDuplicateFieldAliases(): void | |
{ | |
// Create form | |
$payload = [ | |
'name' => 'Form API test', | |
'formType' => 'standalone', | |
'isPublished' => true, | |
'description' => 'Functional API test', | |
'fields' => [ | |
[ | |
'label' => 'Email', | |
'alias' => 'email', | |
'type' => 'text', | |
'leadField' => 'email', | |
], | |
], | |
'postAction' => 'return', | |
]; | |
$this->client->request(Request::METHOD_POST, '/api/forms/new', $payload); | |
$clientResponse = $this->client->getResponse(); | |
$response = json_decode($clientResponse->getContent(), true); | |
$lastValidFormId = $response['form']['id']; | |
$this->assertGreaterThan(0, $lastValidFormId); | |
$this->assertSame(Response::HTTP_CREATED, $clientResponse->getStatusCode(), 'Return code must be 201.'); | |
// Get the last correctly saved form | |
$this->client->request(Request::METHOD_GET, '/api/forms/'.$lastValidFormId); | |
$clientResponse = $this->client->getResponse(); | |
$response = json_decode($clientResponse->getContent(), true); | |
$this->assertIsArray($response['form']); | |
$this->assertCount(1, $response); | |
$this->assertEquals($lastValidFormId, $response['form']['id']); | |
// Try to update invalid, non-existent form | |
$longAlias = 'very_long_field_alias_12345'; | |
$invalidPayload = [ | |
'name' => 'Form API test', | |
'formType' => 'standalone', | |
'isPublished' => true, | |
'description' => 'Functional API test', | |
'fields' => [ | |
[ | |
'label' => 'test1', | |
'alias' => 'very_long_field_alias_12345', | |
'type' => 'text', | |
], | |
[ | |
'label' => 'test2', | |
'alias' => 'very_long_field_alias_123456', | |
'type' => 'text', | |
], | |
], | |
]; | |
$this->client->request(Request::METHOD_PUT, '/api/forms/123/edit', $invalidPayload); | |
$clientResponse = $this->client->getResponse(); | |
$response = json_decode($clientResponse->getContent(), true); | |
$validationMessage = 'Another field is already using this alias: %alias%. Please choose another or leave blank to have it autogenerated.'; | |
$expectedMessage = str_replace('%alias%', substr($longAlias, 0, 25), $validationMessage); | |
$this->assertNotEmpty($response['errors'], 'No errors were returned when trying to save an invalid form'); | |
$this->assertSame(Response::HTTP_BAD_REQUEST, $response['errors'][0]['code'], 'Return code must be 400.'); | |
$this->assertSame(Response::HTTP_BAD_REQUEST, $clientResponse->getStatusCode(), 'Return code must be 400.'); | |
$this->assertSame($expectedMessage, $response['errors'][0]['message'], 'Returned message is different than expected'); | |
} | |
public function testFormWithInvalidField(): void | |
{ | |
$payload = [ | |
'name' => 'Form API test', | |
'formType' => 'standalone', | |
'isPublished' => true, | |
'description' => 'Functional API test', | |
'fields' => [ | |
[ | |
'label' => 'test1', | |
'alias' => 'test1', | |
'type' => 'text', | |
], | |
[ | |
'label' => 'test2', | |
'id' => 123, | |
'alias' => 'test2', | |
'type' => 'invalidField', | |
], | |
], | |
]; | |
$this->client->request(Request::METHOD_PUT, '/api/forms/123/edit', $payload); | |
$response = $this->client->getResponse(); | |
$responseContent = json_decode($response->getContent()); | |
$this->assertNotEmpty($responseContent->errors, 'No errors were returned when trying to save an invalid form'); | |
$this->assertSame('Form Field ID 123 not found', $responseContent->errors[0]->message); | |
$this->assertSame(Response::HTTP_NOT_FOUND, $response->getStatusCode(), 'Return code must be 404.'); | |
} | |
} | |