Files
HRM-System/tests/Feature/ContactWizardHardeningTest.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);
}
}