282 lines
9.4 KiB
PHP
282 lines
9.4 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\Inquiry;
|
|
use App\Models\Contact;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
class ContactWizardHardeningTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
public function test_can_submit_valid_contact_wizard_inquiry(): void
|
|
{
|
|
$payload = [
|
|
'company_name' => 'Acme Corporation',
|
|
'contact_name' => 'John Doe',
|
|
'email' => 'john.doe@acme.com',
|
|
'phone' => '+1555123456',
|
|
'desired_subdomain' => 'acme-test-slug',
|
|
'employee_count' => 75,
|
|
'requested_features' => ['mobile_app', 'geofence', 'payroll'],
|
|
'notes' => 'Testing wizard hardening',
|
|
];
|
|
|
|
$response = $this->postJson('/api/inquiries', $payload);
|
|
|
|
$response->assertStatus(201)
|
|
->assertJson(['success' => true]);
|
|
|
|
$this->assertDatabaseHas('inquiries', [
|
|
'email' => 'john.doe@acme.com',
|
|
'desired_subdomain' => 'acme-test-slug',
|
|
'employee_count' => 75,
|
|
]);
|
|
|
|
$this->assertDatabaseHas('contacts', [
|
|
'email' => 'john.doe@acme.com',
|
|
'status' => 'New',
|
|
]);
|
|
}
|
|
|
|
public function test_prevents_duplicate_desired_subdomains(): void
|
|
{
|
|
Inquiry::create([
|
|
'company_name' => 'Existing Company',
|
|
'contact_name' => 'Existing Contact',
|
|
'email' => 'existing@acme.com',
|
|
'desired_subdomain' => 'unique-slug',
|
|
'employee_count' => 10,
|
|
'status' => 'pending_payment_approval',
|
|
]);
|
|
|
|
$payload = [
|
|
'company_name' => 'New Company',
|
|
'contact_name' => 'New Contact',
|
|
'email' => 'new@acme.com',
|
|
'desired_subdomain' => 'unique-slug',
|
|
'employee_count' => 50,
|
|
];
|
|
|
|
$response = $this->postJson('/api/inquiries', $payload);
|
|
|
|
$response->assertStatus(422)
|
|
->assertJsonValidationErrors(['desired_subdomain']);
|
|
}
|
|
|
|
public function test_honeypot_bot_rejection(): void
|
|
{
|
|
$payload = [
|
|
'company_name' => 'Spam Bot Co',
|
|
'contact_name' => 'Bot User',
|
|
'email' => 'bot@spam.com',
|
|
'desired_subdomain' => 'bot-subdomain',
|
|
'hp_website' => 'http://spam-link.com',
|
|
];
|
|
|
|
$response = $this->postJson('/api/inquiries', $payload);
|
|
|
|
$response->assertStatus(422)
|
|
->assertJson(['success' => false, 'message' => 'Spam submission detected.']);
|
|
}
|
|
|
|
public function test_strict_module_and_bounds_validation(): void
|
|
{
|
|
$payload = [
|
|
'company_name' => 'Invalid Company',
|
|
'contact_name' => 'Invalid Contact',
|
|
'email' => 'invalid@acme.com',
|
|
'desired_subdomain' => 'invalid-subdomain',
|
|
'employee_count' => 999999, // Exceeds max 10,000
|
|
'requested_features' => ['invalid_fake_module'],
|
|
];
|
|
|
|
$response = $this->postJson('/api/inquiries', $payload);
|
|
|
|
$response->assertStatus(422)
|
|
->assertJsonValidationErrors(['employee_count', 'requested_features.0']);
|
|
}
|
|
|
|
public function test_same_user_can_resubmit_inquiry_with_same_subdomain(): void
|
|
{
|
|
Inquiry::create([
|
|
'company_name' => 'Original Company',
|
|
'contact_name' => 'Same User',
|
|
'email' => 'sameuser@acme.com',
|
|
'desired_subdomain' => 'sameuser-slug',
|
|
'employee_count' => 10,
|
|
'status' => 'pending_payment_approval',
|
|
]);
|
|
|
|
$payload = [
|
|
'company_name' => 'Updated Company',
|
|
'contact_name' => 'Same User Updated',
|
|
'email' => 'sameuser@acme.com',
|
|
'desired_subdomain' => 'sameuser-slug', // Same user reusing their requested subdomain
|
|
'employee_count' => 100,
|
|
'notes' => 'Updated notes from same user',
|
|
];
|
|
|
|
$response = $this->postJson('/api/inquiries', $payload);
|
|
|
|
$response->assertStatus(201)
|
|
->assertJson(['success' => true]);
|
|
|
|
$this->assertDatabaseHas('inquiries', [
|
|
'email' => 'sameuser@acme.com',
|
|
'company_name' => 'Updated Company',
|
|
'employee_count' => 100,
|
|
]);
|
|
}
|
|
|
|
public function test_oversized_notes_validation(): void
|
|
{
|
|
$payload = [
|
|
'company_name' => 'Long Note Co',
|
|
'contact_name' => 'Long Note User',
|
|
'email' => 'longnote@acme.com',
|
|
'desired_subdomain' => 'long-note-slug',
|
|
'notes' => str_repeat('A', 2001), // Exceeds max 2000 chars
|
|
];
|
|
|
|
$response = $this->postJson('/api/inquiries', $payload);
|
|
|
|
$response->assertStatus(422)
|
|
->assertJsonValidationErrors(['notes']);
|
|
}
|
|
|
|
public function test_creates_multiple_contact_history_entries_for_repeat_submissions(): void
|
|
{
|
|
$payload1 = [
|
|
'company_name' => 'History Co',
|
|
'contact_name' => 'Repeat User',
|
|
'email' => 'repeat.user@acme.com',
|
|
'desired_subdomain' => 'history-slug',
|
|
'notes' => 'First message',
|
|
];
|
|
|
|
$this->postJson('/api/inquiries', $payload1)->assertStatus(201);
|
|
|
|
$payload2 = [
|
|
'company_name' => 'History Co',
|
|
'contact_name' => 'Repeat User',
|
|
'email' => 'repeat.user@acme.com',
|
|
'desired_subdomain' => 'history-slug',
|
|
'notes' => 'Second message',
|
|
];
|
|
|
|
$this->postJson('/api/inquiries', $payload2)->assertStatus(201);
|
|
|
|
$this->assertEquals(2, Contact::where('email', 'repeat.user@acme.com')->count());
|
|
}
|
|
|
|
public function test_accumulated_notes_are_truncated_to_2000_chars(): void
|
|
{
|
|
Inquiry::create([
|
|
'company_name' => 'Truncate Co',
|
|
'contact_name' => 'Truncate User',
|
|
'email' => 'truncate@acme.com',
|
|
'desired_subdomain' => 'truncate-slug',
|
|
'notes' => str_repeat('A', 1500),
|
|
'status' => 'pending_payment_approval',
|
|
]);
|
|
|
|
$payload = [
|
|
'company_name' => 'Truncate Co',
|
|
'contact_name' => 'Truncate User',
|
|
'email' => 'truncate@acme.com',
|
|
'desired_subdomain' => 'truncate-slug',
|
|
'notes' => str_repeat('B', 1000),
|
|
];
|
|
|
|
$this->postJson('/api/inquiries', $payload)->assertStatus(201);
|
|
|
|
$inquiry = Inquiry::where('email', 'truncate@acme.com')->first();
|
|
$this->assertEquals(2000, mb_strlen($inquiry->notes));
|
|
}
|
|
|
|
public function test_cannot_alter_subdomain_slug_if_already_approved(): void
|
|
{
|
|
Inquiry::create([
|
|
'company_name' => 'Approved Co',
|
|
'contact_name' => 'Approved User',
|
|
'email' => 'approved@acme.com',
|
|
'desired_subdomain' => 'approved-slug',
|
|
'status' => 'approved',
|
|
]);
|
|
|
|
$payload = [
|
|
'company_name' => 'Approved Co',
|
|
'contact_name' => 'Approved User',
|
|
'email' => 'approved@acme.com',
|
|
'desired_subdomain' => 'new-unapproved-slug',
|
|
];
|
|
|
|
$response = $this->postJson('/api/inquiries', $payload);
|
|
|
|
$response->assertStatus(422)
|
|
->assertJson(['success' => false, 'message' => 'Your subdomain request has already been approved. Subdomain slug cannot be altered.']);
|
|
}
|
|
|
|
public function test_email_verification_flow(): void
|
|
{
|
|
$payload = [
|
|
'company_name' => 'Verify Co',
|
|
'contact_name' => 'Verify User',
|
|
'email' => 'verify.user@acme.com',
|
|
'desired_subdomain' => 'verify-slug',
|
|
];
|
|
|
|
$response = $this->postJson('/api/inquiries', $payload);
|
|
$response->assertStatus(201)
|
|
->assertJson(['success' => true, 'requires_verification' => true]);
|
|
|
|
$inquiry = Inquiry::where('email', 'verify.user@acme.com')->first();
|
|
$this->assertNotNull($inquiry->verification_code);
|
|
$this->assertNull($inquiry->email_verified_at);
|
|
|
|
// Invalid verification code
|
|
$verifyFail = $this->postJson('/api/inquiries/verify-email', [
|
|
'email' => 'verify.user@acme.com',
|
|
'verification_code' => '999999',
|
|
]);
|
|
$verifyFail->assertStatus(422);
|
|
|
|
// Valid verification code
|
|
$verifySuccess = $this->postJson('/api/inquiries/verify-email', [
|
|
'email' => 'verify.user@acme.com',
|
|
'verification_code' => $inquiry->verification_code,
|
|
]);
|
|
$verifySuccess->assertStatus(200)
|
|
->assertJson(['success' => true]);
|
|
|
|
$inquiry->refresh();
|
|
$this->assertNotNull($inquiry->email_verified_at);
|
|
$this->assertNull($inquiry->verification_code);
|
|
}
|
|
|
|
public function test_rate_limiting_throttles_excessive_submissions(): void
|
|
{
|
|
for ($i = 0; $i < 6; $i++) {
|
|
$response = $this->postJson('/api/inquiries', [
|
|
'company_name' => 'Company ' . $i,
|
|
'contact_name' => 'Contact ' . $i,
|
|
'email' => 'rate' . $i . '@acme.com',
|
|
'desired_subdomain' => 'rate-slug-' . $i,
|
|
]);
|
|
}
|
|
|
|
// 7th request within 1 minute should be rate-limited
|
|
$response = $this->postJson('/api/inquiries', [
|
|
'company_name' => 'Throttled Co',
|
|
'contact_name' => 'Throttled User',
|
|
'email' => 'throttled@acme.com',
|
|
'desired_subdomain' => 'throttled-slug',
|
|
]);
|
|
|
|
$response->assertStatus(429);
|
|
}
|
|
}
|