Separated laravel app to its own folder (#540)

This commit is contained in:
Julien Nahum
2024-08-26 18:24:56 +02:00
committed by GitHub
parent 39b8df5eed
commit 5bd1dda504
546 changed files with 124 additions and 143 deletions

View File

@@ -0,0 +1,305 @@
<?php
use App\Models\Forms\Form;
use Tests\Helpers\FormSubmissionDataFactory;
it('can answer a form', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
// TODO: generate random response given a form and un-skip
})->skip('Need to finish writing a class to generated random responses');
it('can submit form if close date is in future', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace, [
'closes_at' => \Carbon\Carbon::now()->addDays(1)->toDateTimeString(),
]);
$formData = FormSubmissionDataFactory::generateSubmissionData($form);
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form submission saved.',
]);
});
it('can not submit closed form', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace, [
'closes_at' => \Carbon\Carbon::now()->subDays(1)->toDateTimeString(),
]);
$formData = FormSubmissionDataFactory::generateSubmissionData($form);
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertStatus(403);
});
it('can submit form till max submissions count is not reached at limit', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace, [
'max_submissions_count' => 3,
]);
$formData = FormSubmissionDataFactory::generateSubmissionData($form);
// Can submit form
for ($i = 1; $i <= 3; $i++) {
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form submission saved.',
]);
}
// Now, can not submit form, Because it's reached at submission limit
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertStatus(403);
});
it('can not open draft form', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace, [
'visibility' => 'draft',
]);
$this->getJson(route('forms.show', $form->slug))
->assertStatus(404);
});
it('can not submit draft form', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace, [
'visibility' => 'draft',
]);
$formData = FormSubmissionDataFactory::generateSubmissionData($form);
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertStatus(403);
});
it('can not submit visibility closed form', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace, [
'visibility' => 'closed',
]);
$formData = FormSubmissionDataFactory::generateSubmissionData($form);
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertStatus(403);
});
it('can not submit form with past dates', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$submissionData = [];
$form->properties = collect($form->properties)->map(function ($property) use (&$submissionData) {
if (in_array($property['type'], ['date'])) {
$property['disable_past_dates'] = true;
$submissionData[$property['id']] = now()->subDays(4)->format('Y-m-d');
}
return $property;
})->toArray();
$form->update();
$formData = FormSubmissionDataFactory::generateSubmissionData($form, $submissionData);
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertStatus(422)
->assertJson([
'message' => 'The Date must be a date after yesterday.',
]);
});
it('can not submit form with future dates', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$submissionData = [];
$form->properties = collect($form->properties)->map(function ($property) use (&$submissionData) {
if (in_array($property['type'], ['date'])) {
$property['disable_future_dates'] = true;
$submissionData[$property['id']] = now()->addDays(4)->format('Y-m-d');
}
return $property;
})->toArray();
$form->update();
$formData = FormSubmissionDataFactory::generateSubmissionData($form, $submissionData);
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertStatus(422)
->assertJson([
'message' => 'The Date must be a date before tomorrow.',
]);
});
it('can submit form with passed custom validation condition', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$targetField = collect($form->properties)->where('name', 'Number')->first();
$condition = [
'actions' => [],
'conditions' => [
'operatorIdentifier' => 'or',
'children' => [
[
'identifier' => $targetField['id'],
'value' => [
'operator' => 'greater_than',
'property_meta' => [
'id' => $targetField['id'],
'type' => 'number',
],
'value' => 20,
],
],
],
],
];
$submissionData = [];
$validationMessage = 'Number too low';
$form->properties = collect($form->properties)->map(function ($property) use (&$submissionData, &$condition, &$validationMessage, $targetField) {
if (in_array($property['name'], ['Name'])) {
$property['validation'] = ['error_conditions' => $condition, 'error_message' => $validationMessage];
$submissionData[$targetField['id']] = 100;
}
return $property;
})->toArray();
$form->update();
$formData = FormSubmissionDataFactory::generateSubmissionData($form, $submissionData);
$response = $this->postJson(route('forms.answer', $form->slug), $formData);
$response->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form submission saved.',
]);
});
it('can not submit form with failed custom validation condition', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$targetField = collect($form->properties)->where('name', 'Email')->first();
$condition = [
'actions' => [],
'conditions' => [
'operatorIdentifier' => 'and',
'children' => [
[
'identifier' => $targetField['id'],
'value' => [
'operator' => 'equals',
'property_meta' => [
'id' => $targetField['id'],
'type' => 'email',
],
'value' => 'test@gmail.com',
],
],
],
],
];
$submissionData = [];
$validationMessage = 'Can only use test@gmail.com';
$form->properties = collect($form->properties)->map(function ($property) use (&$submissionData, &$condition, &$validationMessage, &$targetField) {
if (in_array($property['name'], ['Name'])) {
$property['validation'] = ['error_conditions' => $condition, 'error_message' => $validationMessage];
$submissionData[$targetField['id']] = 'fail@gmail.com';
}
return $property;
})->toArray();
$form->update();
$formData = FormSubmissionDataFactory::generateSubmissionData($form, $submissionData);
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertStatus(422)
->assertJson([
'message' => $validationMessage,
]);
});
it('can validate form answer with precognition', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$properties = $form->properties;
$properties[0]['required'] = true;
$properties[3]['required'] = true;
$properties[6]['required'] = true;
$properties[9]['required'] = true;
$form->properties = $properties;
$form->update();
// Empty submission data should fail validation, with all 4 required fields
$response = $this->postJson(route('forms.answer', $form->slug), []);
$errors = $response->json()['errors'];
$this->assertEquals(sizeof($errors), 4);
$response->assertStatus(422);
// Fill in data for only Name.
$submissionData = [];
foreach ($properties as $property) {
if ($property['name'] == 'Name') {
$submissionData[$property['id']] = 'Name';
} else {
$submissionData[$property['id']] = null;
}
}
// Select only first 3 fields for precognition validation
$validateOnlyFields = [
$properties[0]['id'],
$properties[1]['id'],
$properties[2]['id']
];
$precognitionValidateOnly = implode(',', $validateOnlyFields);
// Partial submission data should pass validation for the precognition only fields.
$response = $this->withPrecognition()->withHeaders([
'Precognition-Validate-Only' => $precognitionValidateOnly
])
->postJson(route('forms.answer', $form->slug), $submissionData);
$response->assertSuccessfulPrecognition();
// Select only next fields for precognition validation
$validateOnlyFields = $validateOnlyFields = [
$properties[3]['id'],
$properties[4]['id'],
$properties[5]['id']
];
$precognitionValidateOnly = implode(',', $validateOnlyFields);
// Partial submission data should fail validation, but for only one required field specified for precognition validation.
$response = $this->withPrecognition()->withHeaders([
'Precognition-Validate-Only' => $precognitionValidateOnly
])
->postJson(route('forms.answer', $form->slug), $submissionData);
$errors = $response->json()['errors'];
$this->assertEquals(sizeof($errors), 1);
$response->assertStatus(422);
});

View File

@@ -0,0 +1,48 @@
<?php
use Illuminate\Support\Facades\Artisan;
it('check form statistic for views & submissions counts', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace, []);
// Create 10 views & submissions (in the past of 1 day so that it's cleaned)
for ($i = 1; $i <= 10; $i++) {
$submission = $form->submissions()->create();
$submission->created_at = now()->subDay();
$submission->save();
$view = $form->views()->create();
$view->created_at = now()->subDay();
$view->save();
}
// Create a submission & a view for another date
$submission = $form->submissions()->create();
$submission->created_at = now()->subDays(2);
$submission->save();
$view = $form->views()->create();
$view->created_at = now()->subDays(2);
$view->save();
// Run Command
Artisan::call('forms:database-cleanup');
// Create 5 views & submissions
for ($i = 1; $i <= 5; $i++) {
$form->views()->create();
$form->submissions()->create();
}
// Now check counters
$statistics = $form->statistics()->get();
expect($form->views_count)->toBe(16);
expect($form->submissions_count)->toBe(16);
expect($form->views()->count())->toBe(5);
expect($form->submissions()->count())->toBe(16);
expect(count($statistics))->toBe(2); // 1 per day for 2 different dates
expect($statistics[0]['date'])->toBe(now()->subDays(2)->toDateString());
expect($statistics[0]['data'])->toBe(['views' => 1, 'submissions' => 0]);
expect($statistics[1]['date'])->toBe(now()->subDay()->toDateString());
expect($statistics[1]['data'])->toBe(['views' => 10, 'submissions' => 0]);
});

View File

@@ -0,0 +1,144 @@
<?php
use App\Mail\Forms\SubmissionConfirmationMail;
use Illuminate\Support\Facades\Mail;
it('creates confirmation emails with the submitted data', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$integrationData = $this->createFormIntegration('submission_confirmation', $form->id, [
'respondent_email' => true,
'notifications_include_submission' => true,
'notification_sender' => 'Custom Sender',
'notification_subject' => 'Test subject',
'notification_body' => 'Test body',
]);
$formData = [
collect($form->properties)->first(function ($property) {
return $property['type'] == 'email';
})['id'] => 'test@test.com',
];
$event = new \App\Events\Forms\FormSubmitted($form, $formData);
$mailable = new SubmissionConfirmationMail($event, $integrationData);
$mailable->assertSeeInHtml('Test body')
->assertSeeInHtml('As a reminder, here are your answers:')
->assertSeeInHtml('You are receiving this email because you answered the form:');
});
it('creates confirmation emails without the submitted data', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$integrationData = $this->createFormIntegration('submission_confirmation', $form->id, [
'respondent_email' => true,
'notifications_include_submission' => false,
'notification_sender' => 'Custom Sender',
'notification_subject' => 'Test subject',
'notification_body' => 'Test body',
]);
$formData = [
collect($form->properties)->first(function ($property) {
return $property['type'] == 'email';
})['id'] => 'test@test.com',
];
$event = new \App\Events\Forms\FormSubmitted($form, $formData);
$mailable = new SubmissionConfirmationMail($event, $integrationData);
$mailable->assertSeeInHtml('Test body')
->assertDontSeeInHtml('As a reminder, here are your answers:')
->assertSeeInHtml('You are receiving this email because you answered the form:');
});
it('sends a confirmation email if needed', function () {
$user = $this->actingAsProUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$this->createFormIntegration('submission_confirmation', $form->id, [
'respondent_email' => true,
'notifications_include_submission' => true,
'notification_sender' => 'Custom Sender',
'notification_subject' => 'Test subject',
'notification_body' => 'Test body',
]);
$emailProperty = collect($form->properties)->first(function ($property) {
return $property['type'] == 'email';
});
$formData = [
$emailProperty['id'] => 'test@test.com',
];
Mail::fake();
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form submission saved.',
]);
Mail::assertQueued(
SubmissionConfirmationMail::class,
function (SubmissionConfirmationMail $mail) {
return $mail->hasTo('test@test.com');
}
);
});
it('does not send a confirmation email if not needed', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$emailProperty = collect($form->properties)->first(function ($property) {
return $property['type'] == 'email';
});
$formData = [
$emailProperty['id'] => 'test@test.com',
];
Mail::fake();
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form submission saved.',
]);
Mail::assertNotQueued(
SubmissionConfirmationMail::class,
function (SubmissionConfirmationMail $mail) {
return $mail->hasTo('test@test.com');
}
);
});
it('does send a confirmation email even when reply to is broken', function () {
$user = $this->actingAsProUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$integrationData = $this->createFormIntegration('submission_confirmation', $form->id, [
'respondent_email' => true,
'notifications_include_submission' => true,
'notification_sender' => 'Custom Sender',
'notification_subject' => 'Test subject',
'notification_body' => 'Test body',
'confirmation_reply_to' => ''
]);
$emailProperty = collect($form->properties)->first(function ($property) {
return $property['type'] == 'email';
});
$formData = [
$emailProperty['id'] => 'test@test.com',
];
$event = new \App\Events\Forms\FormSubmitted($form, $formData);
$mailable = new SubmissionConfirmationMail($event, $integrationData);
$mailable->assertSeeInHtml('Test body')
->assertSeeInHtml('As a reminder, here are your answers:')
->assertSeeInHtml('You are receiving this email because you answered the form:')
->assertHasReplyTo($user->email); // Even though reply to is wrong, it should use the user's email
});

View File

@@ -0,0 +1,28 @@
<?php
use Tests\Helpers\FormSubmissionDataFactory;
it('can submit form with dyanamic select option', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$selectionsPreData = [];
$form->properties = collect($form->properties)->map(function ($property) use (&$selectionsPreData) {
if (in_array($property['type'], ['select', 'multi_select'])) {
$property['allow_creation'] = true;
$selectionsPreData[$property['id']] = ($property['type'] == 'select') ? 'New single select - '.time() : ['New multi select - '.time()];
}
return $property;
})->toArray();
$form->update();
$formData = FormSubmissionDataFactory::generateSubmissionData($form, $selectionsPreData);
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form submission saved.',
]);
});

View File

@@ -0,0 +1,20 @@
<?php
it('create form with captcha and raise validation issue', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace, [
'use_captcha' => true,
]);
$this->postJson(route('forms.answer', $form->slug), [])
->assertStatus(422)
->assertJson([
'message' => 'Please complete the captcha.',
'errors' => [
'h-captcha-response' => [
'Please complete the captcha.',
],
],
]);
});

View File

@@ -0,0 +1,28 @@
<?php
it('can fetch form integration events', function () {
$user = $this->actingAsProUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$data = [
'status' => true,
'integration_id' => 'email',
'logic' => null,
'settings' => [
'notification_emails' => 'test@test.com',
'notification_reply_to' => null
]
];
$response = $this->postJson(route('open.forms.integration.create', $form->id), $data)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form Integration was created.'
]);
$this->getJson(route('open.forms.integrations.events', [$form->id, $response->json('form_integration.id')]))
->assertSuccessful()
->assertJsonCount(0);
});

View File

@@ -0,0 +1,42 @@
<?php
it('can CRUD form integration', function () {
$user = $this->actingAsProUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$data = [
'status' => true,
'integration_id' => 'email',
'logic' => null,
'settings' => [
'notification_emails' => 'test@test.com',
'notification_reply_to' => null
]
];
$response = $this->postJson(route('open.forms.integration.create', $form->id), $data)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form Integration was created.'
]);
$this->getJson(route('open.forms.integrations', $form->id))
->assertSuccessful()
->assertJsonCount(1);
$this->putJson(route('open.forms.integration.update', [$form->id, $response->json('form_integration.id')]), $data)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form Integration was updated.'
]);
$this->deleteJson(route('open.forms.integration.destroy', [$form->id, $response->json('form_integration.id')]), $data)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form Integration was deleted.'
]);
});

View File

@@ -0,0 +1,115 @@
<?php
use Illuminate\Testing\Fluent\AssertableJson;
it('create form with logic', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace, [
'properties' => [
[
'id' => 'title',
'name' => 'Name',
'type' => 'title',
'hidden' => false,
'required' => true,
'logic' => [
'conditions' => [
'operatorIdentifier' => 'and',
'children' => [
[
'identifier' => 'email',
'value' => [
'operator' => 'is_empty',
'property_meta' => [
'id' => '93ea3198-353f-440b-8dc9-2ac9a7bee124',
'type' => 'email',
],
'value' => true,
],
],
],
],
'actions' => ['make-it-optional'],
],
],
],
]);
$this->getJson(route('forms.show', $form->slug))
->assertSuccessful()
->assertJson(function (AssertableJson $json) use ($form) {
return $json->where('id', $form->id)
->where('properties', function ($values) {
return count($values[0]['logic']) > 0;
})
->etc();
});
// Should submit form
$forData = ['93ea3198-353f-440b-8dc9-2ac9a7bee124' => ''];
$this->postJson(route('forms.answer', $form->slug), $forData)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form submission saved.',
]);
});
it('create form with multi select logic', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace, [
'properties' => [
[
'id' => 'title',
'name' => 'Name',
'type' => 'title',
'hidden' => false,
'required' => false,
'logic' => [
'conditions' => [
'operatorIdentifier' => 'and',
'children' => [
[
'identifier' => 'multi_select',
'value' => [
'operator' => 'contains',
'property_meta' => [
'id' => '93ea3198-353f-440b-8dc9-2ac9a7bee124',
'type' => 'multi_select',
],
'value' => 'One',
],
],
],
],
'actions' => ['require-answer'],
],
],
],
]);
$this->getJson(route('forms.show', $form->slug))
->assertSuccessful()
->assertJson(function (AssertableJson $json) use ($form) {
return $json->where('id', $form->id)
->where('properties', function ($values) {
return count($values[0]['logic']) > 0;
})
->etc();
});
// Should submit form
$forData = ['93ea3198-353f-440b-8dc9-2ac9a7bee124' => ['One']];
$this->postJson(route('forms.answer', $form->slug), $forData)
->assertStatus(422)
->assertJson([
'message' => 'The Name field is required.',
'errors' => [
'title' => [
'The Name field is required.',
],
],
]);
});

View File

@@ -0,0 +1,96 @@
<?php
use Illuminate\Testing\Fluent\AssertableJson;
use Tests\Helpers\FormSubmissionDataFactory;
beforeEach(function () {
$this->password = '12345';
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$this->form = $this->createForm($user, $workspace, [
'password' => $this->password,
]);
$this->formData = FormSubmissionDataFactory::generateSubmissionData($this->form);
});
it('can allow form owner to access and submit form without password', function () {
// As Form Owner so can access form without password
$this->getJson(route('forms.show', $this->form->slug))
->assertSuccessful()
->assertJson(function (AssertableJson $json) {
return $json->where('id', $this->form->id)
->where('has_password', true)
->where('is_password_protected', false)
->etc();
});
// As Form Owner so can submit form without password
$this->postJson(route('forms.answer', $this->form->slug), $this->formData)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form submission saved.',
]);
});
it('can not access form without password for guest user', function () {
$this->actingAsGuest();
$this->getJson(route('forms.show', $this->form->slug))
->assertSuccessful()
->assertJson(function (AssertableJson $json) {
return $json->where('id', $this->form->id)
->where('has_password', true)
->where('is_password_protected', true)
->etc();
});
});
it('can not submit form without password for guest user', function () {
$this->actingAsGuest();
$this->postJson(route('forms.answer', $this->form->slug), $this->formData)
->assertStatus(403)
->assertJson([
'status' => 'Unauthorized',
'message' => 'Form is protected.',
]);
});
it('can not submit form with wrong password for guest user', function () {
$this->actingAsGuest();
$this->withHeaders(['form-password' => hash('sha256', 'WRONGPASSWORD')])
->postJson(route('forms.answer', $this->form->slug), $this->formData)
->assertStatus(403)
->assertJson([
'status' => 'Unauthorized',
'message' => 'Form is protected.',
]);
});
it('can access form with right password for guest user', function () {
$this->actingAsGuest();
$this->withHeaders(['form-password' => hash('sha256', $this->password)])
->getJson(route('forms.show', $this->form->slug))
->assertSuccessful()
->assertJson(function (AssertableJson $json) {
return $json->where('id', $this->form->id)
->where('has_password', true)
->where('is_password_protected', false)
->etc();
});
});
it('can submit form with right password for guest user', function () {
$this->actingAsGuest();
$this->withHeaders(['form-password' => hash('sha256', $this->password)])
->postJson(route('forms.answer', $this->form->slug), $this->formData)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form submission saved.',
]);
});

View File

@@ -0,0 +1,201 @@
<?php
use App\Rules\FormPropertyLogicRule;
it('can validate form logic rules for actions', function () {
$rules = [
'properties.*.logic' => ['array', 'nullable', new FormPropertyLogicRule()],
];
$data = [
'properties' => [
[
'id' => 'title',
'name' => 'Name',
'type' => 'title',
'hidden' => false,
'required' => false,
'logic' => [
'conditions' => null,
'actions' => [],
],
],
],
];
$validatorObj = $this->app['validator']->make($data, $rules);
$this->assertTrue($validatorObj->passes());
$data = [
'properties' => [
[
'id' => 'title',
'name' => 'Name',
'type' => 'title',
'hidden' => true,
'required' => false,
'logic' => [
'conditions' => [
'operatorIdentifier' => 'and',
'children' => [
[
'identifier' => 'title',
'value' => [
'operator' => 'equals',
'property_meta' => [
'id' => 'title',
'type' => 'text',
],
'value' => 'TEST',
],
],
],
],
'actions' => ['hide-block'],
],
],
],
];
$validatorObj = $this->app['validator']->make($data, $rules);
$this->assertFalse($validatorObj->passes());
expect($validatorObj->errors()->messages()['properties.0.logic'][0])->toBe('The logic actions for Name are not valid.');
$data = [
'properties' => [
[
'id' => 'text',
'name' => 'Custom Test',
'type' => 'nf-text',
'logic' => [
'conditions' => [
'operatorIdentifier' => 'and',
'children' => [
[
'identifier' => 'title',
'value' => [
'operator' => 'equals',
'property_meta' => [
'id' => 'title',
'type' => 'text',
],
'value' => 'TEST',
],
],
],
],
'actions' => ['require-answer'],
],
],
],
];
$validatorObj = $this->app['validator']->make($data, $rules);
$this->assertFalse($validatorObj->passes());
expect($validatorObj->errors()->messages()['properties.0.logic'][0])->toBe('The logic actions for Custom Test are not valid.');
});
it('can validate form logic rules for conditions', function () {
$rules = [
'properties.*.logic' => ['array', 'nullable', new FormPropertyLogicRule()],
];
$data = [
'properties' => [
[
'id' => 'title',
'name' => 'Name',
'type' => 'text',
'hidden' => false,
'required' => false,
'logic' => [
'conditions' => [
'operatorIdentifier' => 'and',
'children' => [
[
'identifier' => 'title',
'value' => [
'operator' => 'equals',
'property_meta' => [
'id' => 'title',
'type' => 'text',
],
'value' => 'TEST',
],
],
],
],
'actions' => ['hide-block'],
],
],
],
];
$validatorObj = $this->app['validator']->make($data, $rules);
$this->assertTrue($validatorObj->passes());
$data = [
'properties' => [
[
'id' => 'title',
'name' => 'Name',
'type' => 'text',
'hidden' => false,
'required' => false,
'logic' => [
'conditions' => [
'operatorIdentifier' => 'and',
'children' => [
[
'identifier' => 'title',
'value' => [
'operator' => 'starts_with',
'property_meta' => [
'id' => 'title',
'type' => 'text',
],
],
],
],
],
'actions' => ['hide-block'],
],
],
],
];
$validatorObj = $this->app['validator']->make($data, $rules);
$this->assertFalse($validatorObj->passes());
expect($validatorObj->errors()->messages()['properties.0.logic'][0])->toBe('The logic conditions for Name are not complete. Error detail(s): missing condition value');
$data = [
'properties' => [
[
'id' => 'title',
'name' => 'Name',
'type' => 'text',
'hidden' => false,
'required' => false,
'logic' => [
'conditions' => [
'operatorIdentifier' => null,
'children' => [
[
'identifier' => 'title',
'value' => [
'operator' => 'starts_with',
'property_meta' => [
'id' => 'title',
'type' => 'text',
],
],
],
],
],
'actions' => ['hide-block'],
],
],
],
];
$validatorObj = $this->app['validator']->make($data, $rules);
$this->assertFalse($validatorObj->passes());
expect($validatorObj->errors()->messages()['properties.0.logic'][0])->toBe('The logic conditions for Name are not complete. Error detail(s): missing operator');
});

View File

@@ -0,0 +1,67 @@
<?php
use Illuminate\Support\Facades\Artisan;
it('check formstat chart data', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace, []);
$views = [];
$submissions = [];
// Create 10 views & submissions for past days
for ($i = 1; $i <= 10; $i++) {
$date = now()->subDays($i);
$dateString = $date->format('d-m-Y');
$submission = $form->submissions()->create();
$submission->created_at = $date;
$submission->save();
$view = $form->views()->create();
$view->created_at = $date;
$view->save();
$views[$dateString] = isset($views[$dateString]) ? ($views[$dateString] + 1) : 1;
$submissions[$dateString] = isset($submissions[$dateString]) ? ($submissions[$dateString] + 1) : 1;
}
// Run Command
Artisan::call('forms:database-cleanup');
// Create 5 views & submissions
for ($i = 1; $i <= 5; $i++) {
$form->views()->create();
$form->submissions()->create();
$dateString = now()->format('d-m-Y');
$views[$dateString] = isset($views[$dateString]) ? ($views[$dateString] + 1) : 1;
$submissions[$dateString] = isset($submissions[$dateString]) ? ($submissions[$dateString] + 1) : 1;
}
// Now check chart data
$this->getJson(route('open.workspaces.form.stats', [$workspace->id, $form->id]))
->assertSuccessful()
->assertJson(function (\Illuminate\Testing\Fluent\AssertableJson $json) use ($views, $submissions) {
return $json->whereType('views', 'array')
->whereType('submissions', 'array')
->where('views', function ($values) use ($views) {
foreach ($values as $date => $count) {
if ((isset($views[$date]) && $views[$date] != $count) || (!isset($views[$date]) && $count != 0)) {
return false;
}
}
return true;
})
->where('submissions', function ($values) use ($submissions) {
foreach ($values as $date => $count) {
if ((isset($submissions[$date]) && $submissions[$date] != $count) || (!isset($submissions[$date]) && $count != 0)) {
return false;
}
}
return true;
})
->etc();
});
});

View File

@@ -0,0 +1,193 @@
<?php
use Illuminate\Testing\Fluent\AssertableJson;
it('can create a contact form', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->makeForm($user, $workspace);
$formData = new \App\Http\Resources\FormResource($form);
$response = $this->postJson(route('open.forms.store', $formData->toArray(request())))
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form created.',
]);
expect($workspace->forms()->count())->toBe(1);
$this->assertDatabaseHas('forms', [
'id' => $response->json('form.id'),
]);
});
it('can fetch forms', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$this->getJson(route('open.workspaces.forms.index', $workspace->id))
->assertSuccessful()
->assertJsonCount(3)
->assertSuccessful()
->assertJsonPath('data.0.id', $form->id)
->assertJsonPath('data.0.title', $form->title);
});
it('can fetch a form', function () {
$user = $this->actingAsProUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$response = $this->getJson(route('open.forms.show', $form->slug))
->assertSuccessful()
->assertJson([
'id' => $form->id,
'title' => $form->title
]);
});
it('can update a form', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$newFormData = $this->makeForm($user, $workspace);
$form->fill((new \App\Http\Resources\FormResource($newFormData))->toArray(request()));
$formData = (new \App\Http\Resources\FormResource($form))->toArray(request());
$this->putJson(route('open.forms.update', $form->id), $formData)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form updated.',
]);
$this->assertDatabaseHas('forms', [
'id' => $form->id,
'title' => $form->title,
'description' => $form->description,
]);
});
it('can regenerate a form url', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$newFormData = $this->makeForm($user, $workspace);
$form->update([
'title' => $newFormData->title,
]);
$form->generateSlug();
$newSlug = $form->slug;
$this->putJson(route('open.forms.regenerate-link', [$form->id, 'uuid']))
->assertSuccessful()
->assertJson(function (AssertableJson $json) {
return $json->where('type', 'success')
->where('form.slug', function ($value) {
if (!is_string($value) || (preg_match(
'/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/',
$value
) !== 1)) {
return false;
}
return true;
})
->etc();
});
$this->putJson(route('open.forms.regenerate-link', [$form->id, 'slug']))
->assertSuccessful()
->assertJson(function (AssertableJson $json) use ($newSlug) {
return $json->where('type', 'success')
->where('form.slug', function ($slug) use ($newSlug) {
return substr($slug, 0, -6) == substr($newSlug, 0, -6);
})
->etc();
});
});
it('can duplicate a form', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$response = $this->postJson(route('open.forms.duplicate', $form->id))
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form successfully duplicated. You are now editing the duplicated version of the form.',
]);
expect($user->forms()->count())->toBe(2);
expect($workspace->forms()->count())->toBe(2);
$this->assertDatabaseHas('forms', [
'id' => $response->json('new_form.id'),
'title' => 'Copy of ' . $form->title,
'description' => $form->description,
]);
});
it('can delete a form', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$this->deleteJson(route('open.forms.destroy', $form->id))
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form was deleted.',
]);
expect($user->forms()->count())->toBe(0);
expect($workspace->forms()->count())->toBe(0);
});
it('can create form with dark mode', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace, [
'dark_mode' => 'dark',
]);
$formData = (new \App\Http\Resources\FormResource($form))->toArray(request());
$this->postJson(route('open.forms.store', $formData))
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form created.',
]);
$this->getJson(route('forms.show', $form->slug))
->assertSuccessful()
->assertJson(function (AssertableJson $json) use ($form) {
return $json->where('id', $form->id)
->where('dark_mode', 'dark')
->etc();
});
});
it('can create form with custom scripts', function () {
$user = $this->actingAsTrialingUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace, [
'custom_code' => "<script>console.log('Hello')</script>",
]);
$formData = (new \App\Http\Resources\FormResource($form))->toArray(request());
$this->postJson(route('open.forms.store', $formData))
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form successfully created, but the Non-trial features you used will be disabled when sharing your form:',
'form' => ['custom_code' => null]
]);
$this->getJson(route('forms.show', $form->slug))
->assertSuccessful()->assertJson([
'id' => $form->id,
'title' => $form->title,
'custom_code' => null
]);
})->skip(true, 'Trialing custom script form cleaning disabled for now.');

View File

@@ -0,0 +1,41 @@
<?php
use Tests\Helpers\FormSubmissionDataFactory;
it('can update form with existing record', function () {
$user = $this->actingAsProUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace, [
'editable_submissions' => true,
]);
$nameProperty = collect($form->properties)->filter(function ($property) {
return $property['name'] == 'Name';
})->first();
$response = $this->postJson(route('forms.answer', $form->slug), [$nameProperty['id'] => 'Testing'])
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form submission saved.',
]);
$submissionId = $response->json('submission_id');
expect($submissionId)->toBeString();
if ($submissionId) {
$formData = FormSubmissionDataFactory::generateSubmissionData($form, ['submission_id' => $submissionId, $nameProperty['id'] => 'Testing Updated']);
$response = $this->postJson(route('forms.answer', $form->slug), $formData)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form submission saved.',
]);
$submissionId2 = $response->json('submission_id');
expect($submissionId2)->toBeString();
expect($submissionId2)->toBe($submissionId);
$response = $this->getJson(route('forms.fetchSubmission', [$form->slug, $submissionId]))
->assertSuccessful();
expect($response->json('data.'.$nameProperty['id']))->toBe('Testing Updated');
}
});

View File

@@ -0,0 +1,37 @@
<?php
it('can see form without counting view for form owner', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$this->getJson(route('forms.show', $form->slug))
->assertSuccessful()
->assertJson(function (\Illuminate\Testing\Fluent\AssertableJson $json) use ($form) {
return $json->where('id', $form->id)
->where('title', $form->title)
->whereType('properties', 'array')
->etc();
});
expect($form->views()->count())->toBe(0);
});
it('can see form and count view for guest', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$this->actingAsGuest();
$this->getJson(route('forms.show', $form->slug))
->assertSuccessful()
->assertJson(function (\Illuminate\Testing\Fluent\AssertableJson $json) use ($form) {
return $json->where('id', $form->id)
->where('title', $form->title)
->whereType('properties', 'array')
->etc();
});
expect($form->views()->count())->toBe(1);
});

View File

@@ -0,0 +1,35 @@
<?php
use Illuminate\Support\Str;
it('can hide branding on upgrade', function () {
$user = $this->actingAsUser();
// Create workspaces and forms
for ($i = 0; $i < 3; $i++) {
$workspace = $this->createUserWorkspace($user);
for ($j = 0; $j < 3; $j++) {
$this->createForm($user, $workspace);
}
}
// Forms don't have branding removed when created
$forms = $user->workspaces()->with('forms')->get()->pluck('forms')->flatten();
$forms->each(function ($form) {
$this->assertEquals($form->no_branding, false);
});
// User subscribes
$user->subscriptions()->create([
'type' => 'default',
'stripe_id' => Str::random(),
'stripe_status' => 'active',
'stripe_price' => Str::random(),
'quantity' => 1,
]);
// Forms have branding removed after subscription
$forms = $user->workspaces()->with('forms')->get()->pluck('forms')->flatten();
$forms->each(function ($form) {
$this->assertEquals($form->no_branding, true);
});
});

View File

@@ -0,0 +1,185 @@
<?php
use Tests\Helpers\FormSubmissionDataFactory;
it('can submit form with valid matrix input', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$matrixProperty = [
'id' => 'matrix_field',
'name' => 'Matrix Question',
'type' => 'matrix',
'rows' => ['Row 1', 'Row 2', 'Row 3'],
'columns' => ['Column A', 'Column B', 'Column C'],
'required' => true
];
$form->properties = array_merge($form->properties, [$matrixProperty]);
$form->update();
$submissionData = [
'matrix_field' => [
'Row 1' => 'Column A',
'Row 2' => 'Column B',
'Row 3' => 'Column C'
]
];
$formData = FormSubmissionDataFactory::generateSubmissionData($form, $submissionData);
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form submission saved.',
]);
});
it('cannot submit form with invalid matrix input', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$matrixProperty = [
'id' => 'matrix_field',
'name' => 'Matrix Question',
'type' => 'matrix',
'rows' => ['Row 1', 'Row 2', 'Row 3'],
'columns' => ['Column A', 'Column B', 'Column C'],
'required' => true
];
$form->properties = array_merge($form->properties, [$matrixProperty]);
$form->update();
$submissionData = [
'matrix_field' => [
'Row 1' => 'Column A',
'Row 2' => 'Invalid Column',
'Row 3' => 'Column C'
]
];
$formData = FormSubmissionDataFactory::generateSubmissionData($form, $submissionData);
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertStatus(422)
->assertJson([
'message' => "Invalid value 'Invalid Column' for row 'Row 2'.",
'errors' => [
'matrix_field' => [
"Invalid value 'Invalid Column' for row 'Row 2'."
]
]
]);
});
it('can submit form with optional matrix input left empty', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$matrixProperty = [
'id' => 'matrix_field',
'name' => 'Matrix Question',
'type' => 'matrix',
'rows' => ['Row 1', 'Row 2', 'Row 3'],
'columns' => ['Column A', 'Column B', 'Column C'],
'required' => false
];
$form->properties = array_merge($form->properties, [$matrixProperty]);
$form->update();
$submissionData = [
'matrix_field' => []
];
$formData = FormSubmissionDataFactory::generateSubmissionData($form, $submissionData);
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form submission saved.',
]);
});
it('cannot submit form with required matrix input left empty', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$matrixProperty = [
'id' => 'matrix_field',
'name' => 'Matrix Question',
'type' => 'matrix',
'rows' => ['Row 1', 'Row 2', 'Row 3'],
'columns' => ['Column A', 'Column B', 'Column C'],
'required' => true
];
$form->properties = array_merge($form->properties, [$matrixProperty]);
$form->update();
$submissionData = [
'matrix_field' => []
];
$formData = FormSubmissionDataFactory::generateSubmissionData($form, $submissionData);
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertStatus(422)
->assertJson([
'message' => 'The Matrix Question field is required.',
'errors' => [
'matrix_field' => [
'The Matrix Question field is required.'
]
]
]);
});
it('can validate matrix input with precognition', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$matrixProperty = [
'id' => 'matrix_field',
'name' => 'Matrix Question',
'type' => 'matrix',
'rows' => ['Row 1', 'Row 2', 'Row 3'],
'columns' => ['Column A', 'Column B', 'Column C'],
'required' => true
];
$form->properties = array_merge($form->properties, [$matrixProperty]);
$form->update();
$submissionData = [
'matrix_field' => [
'Row 1' => 'Column A',
'Row 2' => 'Invalid Column',
'Row 3' => 'Column C'
]
];
$formData = FormSubmissionDataFactory::generateSubmissionData($form, $submissionData);
$response = $this->withPrecognition()->withHeaders([
'Precognition-Validate-Only' => 'matrix_field'
])
->postJson(route('forms.answer', $form->slug), $formData);
$response->assertStatus(422)
->assertJson([
'errors' => [
'matrix_field' => [
'Invalid value \'Invalid Column\' for row \'Row 2\'.'
]
]
]);
});

View File

@@ -0,0 +1,36 @@
<?php
use App\Models\User;
use Tests\Helpers\FormSubmissionDataFactory;
it('can validate Update Workspace Select Option Job', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);
$formData = FormSubmissionDataFactory::generateSubmissionData($form);
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form submission saved.',
]);
$formData = FormSubmissionDataFactory::generateSubmissionData($form);
$this->postJson(route('forms.answer', $form->slug), $formData)
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Form submission saved.',
]);
});
it('can validate scope with active subscription', function () {
$this->createProUser();
$this->createUser();
$this->createProUser();
$this->createProUser();
$this->createUser();
expect(User::WithActiveSubscription()->count())->toBe(3);
});