init
This commit is contained in:
7
tests/Feature/ExampleTest.php
Normal file
7
tests/Feature/ExampleTest.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
test('the application returns a successful response', function () {
|
||||
$response = $this->get('/');
|
||||
|
||||
$response->assertStatus(200);
|
||||
});
|
||||
128
tests/Feature/Feature/ContactEnquiryTest.php
Normal file
128
tests/Feature/Feature/ContactEnquiryTest.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\ResponseStatus;
|
||||
use App\Models\Website;
|
||||
|
||||
use function Pest\Laravel\assertDatabaseHas;
|
||||
use function Pest\Laravel\postJson;
|
||||
|
||||
beforeEach(function (): void {
|
||||
$this->website = Website::factory()->create([
|
||||
'domain' => 'example.com',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$this->token = $this->website->createToken('test')->plainTextToken;
|
||||
|
||||
$this->headers = [
|
||||
'Authorization' => "Bearer {$this->token}",
|
||||
'Origin' => 'https://example.com',
|
||||
];
|
||||
});
|
||||
|
||||
test('requires authentication', function (): void {
|
||||
postJson('/api/contact', [
|
||||
'registration_number' => 'AB12CDE',
|
||||
'contact_data' => ['name' => 'John Doe'],
|
||||
])->assertUnauthorized();
|
||||
});
|
||||
|
||||
test('requires registration number', function (): void {
|
||||
postJson('/api/contact', [
|
||||
'contact_data' => ['name' => 'John Doe'],
|
||||
], $this->headers)
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['registration_number']);
|
||||
});
|
||||
|
||||
test('rejects invalid registration number format', function (): void {
|
||||
postJson('/api/contact', [
|
||||
'registration_number' => 'ovidiu',
|
||||
'contact_data' => ['name' => 'John Doe'],
|
||||
], $this->headers)
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['registration_number']);
|
||||
});
|
||||
|
||||
test('requires contact data', function (): void {
|
||||
postJson('/api/contact', [
|
||||
'registration_number' => 'AB12CDE',
|
||||
], $this->headers)
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['contact_data']);
|
||||
});
|
||||
|
||||
test('stores contact submission and returns success', function (): void {
|
||||
postJson('/api/contact', [
|
||||
'registration_number' => 'AB12CDE',
|
||||
'contact_data' => [
|
||||
'name' => 'John Doe',
|
||||
'email' => 'john@example.com',
|
||||
'phone' => '07700900000',
|
||||
'address' => '1 High Street',
|
||||
'message' => 'Please call me back.',
|
||||
],
|
||||
], $this->headers)
|
||||
->assertSuccessful()
|
||||
->assertJson(['success' => true]);
|
||||
|
||||
assertDatabaseHas('api_requests', [
|
||||
'website_id' => $this->website->id,
|
||||
'registration_number' => 'AB12CDE',
|
||||
'response_status' => ResponseStatus::ContactSubmitted->value,
|
||||
]);
|
||||
});
|
||||
|
||||
test('requires at least email or phone in contact data', function (): void {
|
||||
postJson('/api/contact', [
|
||||
'registration_number' => 'AB12CDE',
|
||||
'contact_data' => ['name' => 'John Doe'],
|
||||
], $this->headers)
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['contact_data.email', 'contact_data.phone']);
|
||||
});
|
||||
|
||||
test('accepts contact data with email only', function (): void {
|
||||
postJson('/api/contact', [
|
||||
'registration_number' => 'AB12CDE',
|
||||
'contact_data' => ['email' => 'john@example.com'],
|
||||
], $this->headers)->assertSuccessful();
|
||||
});
|
||||
|
||||
test('accepts contact data with phone only', function (): void {
|
||||
postJson('/api/contact', [
|
||||
'registration_number' => 'AB12CDE',
|
||||
'contact_data' => ['phone' => '07700900000'],
|
||||
], $this->headers)->assertSuccessful();
|
||||
});
|
||||
|
||||
test('normalises registration number before storing', function (): void {
|
||||
postJson('/api/contact', [
|
||||
'registration_number' => 'ab12 cde',
|
||||
'contact_data' => ['email' => 'john@example.com', 'phone' => '07700900000'],
|
||||
], $this->headers)->assertSuccessful();
|
||||
|
||||
assertDatabaseHas('api_requests', [
|
||||
'registration_number' => 'AB12CDE',
|
||||
'response_status' => ResponseStatus::ContactSubmitted->value,
|
||||
]);
|
||||
});
|
||||
|
||||
test('inactive website cannot submit contact', function (): void {
|
||||
$this->website->update(['is_active' => false]);
|
||||
|
||||
postJson('/api/contact', [
|
||||
'registration_number' => 'AB12CDE',
|
||||
'contact_data' => ['name' => 'John Doe'],
|
||||
], $this->headers)->assertForbidden();
|
||||
});
|
||||
|
||||
test('rejects request from wrong origin', function (): void {
|
||||
postJson('/api/contact', [
|
||||
'registration_number' => 'AB12CDE',
|
||||
'contact_data' => ['name' => 'John Doe'],
|
||||
], [
|
||||
'Authorization' => "Bearer {$this->token}",
|
||||
'Origin' => 'https://malicious-site.com',
|
||||
])->assertForbidden();
|
||||
});
|
||||
250
tests/Feature/Feature/VehicleEnquiryTest.php
Normal file
250
tests/Feature/Feature/VehicleEnquiryTest.php
Normal file
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\ResponseStatus;
|
||||
use App\Models\VehicleDataSource;
|
||||
use App\Models\VehicleRecord;
|
||||
use App\Models\Website;
|
||||
use App\Services\DvlaService;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
use function Pest\Laravel\assertDatabaseHas;
|
||||
use function Pest\Laravel\postJson;
|
||||
|
||||
beforeEach(function (): void {
|
||||
$tier = \App\Models\Tier::factory()->create([
|
||||
'allowed_fields' => ['registrationNumber', 'make', 'colour'],
|
||||
]);
|
||||
|
||||
$this->website = Website::factory()->create([
|
||||
'domain' => 'example.com',
|
||||
'tier_id' => $tier->id,
|
||||
'cache_hit_rate_limit' => 100,
|
||||
'external_api_rate_limit' => 10,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$this->token = $this->website->createToken('test')->plainTextToken;
|
||||
|
||||
$this->headers = [
|
||||
'Authorization' => "Bearer {$this->token}",
|
||||
'Origin' => 'https://example.com',
|
||||
];
|
||||
});
|
||||
|
||||
test('requires authentication', function (): void {
|
||||
postJson('/api/vehicle-enquiry', [
|
||||
'registration_number' => 'ABC123',
|
||||
])->assertUnauthorized();
|
||||
});
|
||||
|
||||
test('requires registration number', function (): void {
|
||||
postJson('/api/vehicle-enquiry', [], $this->headers)
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['registration_number']);
|
||||
});
|
||||
|
||||
test('returns cached vehicle data when found', function (): void {
|
||||
$vehicle = VehicleRecord::create([
|
||||
'registration_number' => 'ABC123',
|
||||
'data' => [
|
||||
'registrationNumber' => 'ABC123',
|
||||
'make' => 'ROVER',
|
||||
'colour' => 'BLUE',
|
||||
'fuelType' => 'PETROL',
|
||||
],
|
||||
]);
|
||||
|
||||
$response = postJson('/api/vehicle-enquiry', [
|
||||
'registration_number' => 'abc123',
|
||||
], $this->headers);
|
||||
|
||||
$response->assertSuccessful()
|
||||
->assertJson([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'registrationNumber' => 'ABC123',
|
||||
'make' => 'ROVER',
|
||||
'colour' => 'BLUE',
|
||||
],
|
||||
]);
|
||||
|
||||
assertDatabaseHas('api_requests', [
|
||||
'website_id' => $this->website->id,
|
||||
'registration_number' => 'ABC123',
|
||||
'response_status' => ResponseStatus::CacheHit->value,
|
||||
]);
|
||||
});
|
||||
|
||||
test('filters response by tier fields', function (): void {
|
||||
VehicleRecord::create([
|
||||
'registration_number' => 'ABC123',
|
||||
'data' => [
|
||||
'registrationNumber' => 'ABC123',
|
||||
'make' => 'ROVER',
|
||||
'colour' => 'BLUE',
|
||||
'fuelType' => 'PETROL',
|
||||
'engineCapacity' => 2494,
|
||||
],
|
||||
]);
|
||||
|
||||
$response = postJson('/api/vehicle-enquiry', [
|
||||
'registration_number' => 'ABC123',
|
||||
], $this->headers);
|
||||
|
||||
$response->assertSuccessful()
|
||||
->assertJson([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'registrationNumber' => 'ABC123',
|
||||
'make' => 'ROVER',
|
||||
'colour' => 'BLUE',
|
||||
],
|
||||
])->assertJsonMissing(['fuelType', 'engineCapacity']);
|
||||
});
|
||||
|
||||
test('fetches from dvla when not cached', function (): void {
|
||||
Http::fake([
|
||||
'driver-vehicle-licensing.api.gov.uk/*' => Http::response([
|
||||
'registrationNumber' => 'XYZ789',
|
||||
'make' => 'FORD',
|
||||
'colour' => 'RED',
|
||||
'fuelType' => 'DIESEL',
|
||||
], 200),
|
||||
]);
|
||||
|
||||
$response = postJson('/api/vehicle-enquiry', [
|
||||
'registration_number' => 'XYZ789',
|
||||
'contact_data' => [
|
||||
'name' => 'John Doe',
|
||||
'email' => 'john@example.com',
|
||||
],
|
||||
], $this->headers);
|
||||
|
||||
$response->assertSuccessful()
|
||||
->assertJson([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'registrationNumber' => 'XYZ789',
|
||||
'make' => 'FORD',
|
||||
'colour' => 'RED',
|
||||
],
|
||||
]);
|
||||
|
||||
assertDatabaseHas('vehicle_records', [
|
||||
'registration_number' => 'XYZ789',
|
||||
]);
|
||||
|
||||
assertDatabaseHas('vehicle_data_sources', [
|
||||
'source_name' => 'dvla',
|
||||
]);
|
||||
|
||||
assertDatabaseHas('api_requests', [
|
||||
'website_id' => $this->website->id,
|
||||
'registration_number' => 'XYZ789',
|
||||
'response_status' => ResponseStatus::ApiFetched->value,
|
||||
]);
|
||||
});
|
||||
|
||||
test('returns 404 when vehicle not found in dvla', function (): void {
|
||||
Http::fake([
|
||||
'driver-vehicle-licensing.api.gov.uk/*' => Http::response([], 404),
|
||||
]);
|
||||
|
||||
$response = postJson('/api/vehicle-enquiry', [
|
||||
'registration_number' => 'ZZ99ZZZ',
|
||||
], $this->headers);
|
||||
|
||||
$response->assertNotFound()
|
||||
->assertJson([
|
||||
'message' => 'Vehicle not found',
|
||||
]);
|
||||
|
||||
assertDatabaseHas('api_requests', [
|
||||
'response_status' => ResponseStatus::NotFound->value,
|
||||
]);
|
||||
});
|
||||
|
||||
test('respects cache hit rate limit', function (): void {
|
||||
$this->website->update(['cache_hit_rate_limit' => 2]);
|
||||
|
||||
VehicleRecord::create([
|
||||
'registration_number' => 'ABC123',
|
||||
'data' => ['registrationNumber' => 'ABC123', 'make' => 'ROVER'],
|
||||
]);
|
||||
|
||||
postJson('/api/vehicle-enquiry', ['registration_number' => 'ABC123'], $this->headers)->assertSuccessful();
|
||||
|
||||
postJson('/api/vehicle-enquiry', ['registration_number' => 'ABC123'], $this->headers)->assertSuccessful();
|
||||
|
||||
postJson('/api/vehicle-enquiry', ['registration_number' => 'ABC123'], $this->headers)->assertStatus(429)
|
||||
->assertJson([
|
||||
'message' => 'Rate limit exceeded',
|
||||
]);
|
||||
});
|
||||
|
||||
test('respects external api rate limit', function (): void {
|
||||
$this->website->update(['external_api_rate_limit' => 1]);
|
||||
|
||||
Http::fake([
|
||||
'driver-vehicle-licensing.api.gov.uk/*' => Http::response([
|
||||
'registrationNumber' => 'XYZ789',
|
||||
'make' => 'FORD',
|
||||
], 200),
|
||||
]);
|
||||
|
||||
postJson('/api/vehicle-enquiry', ['registration_number' => 'NEW1'], $this->headers)->assertSuccessful();
|
||||
|
||||
postJson('/api/vehicle-enquiry', ['registration_number' => 'NEW2'], $this->headers)->assertStatus(429)
|
||||
->assertJson([
|
||||
'message' => 'External API rate limit exceeded',
|
||||
]);
|
||||
});
|
||||
|
||||
test('inactive website cannot make requests', function (): void {
|
||||
$this->website->update(['is_active' => false]);
|
||||
|
||||
postJson('/api/vehicle-enquiry', ['registration_number' => 'ABC123'], $this->headers)->assertForbidden()
|
||||
->assertJson([
|
||||
'message' => 'Account inactive',
|
||||
]);
|
||||
});
|
||||
|
||||
test('rejects request when origin does not match website domain', function (): void {
|
||||
postJson('/api/vehicle-enquiry', ['registration_number' => 'ABC123'], [
|
||||
'Authorization' => "Bearer {$this->token}",
|
||||
'Origin' => 'https://malicious-site.com',
|
||||
])->assertForbidden()
|
||||
->assertJson(['message' => 'Forbidden']);
|
||||
});
|
||||
|
||||
test('rejects request when no origin or referer header is present', function (): void {
|
||||
postJson('/api/vehicle-enquiry', ['registration_number' => 'ABC123'], [
|
||||
'Authorization' => "Bearer {$this->token}",
|
||||
])->assertForbidden()
|
||||
->assertJson(['message' => 'Forbidden']);
|
||||
});
|
||||
|
||||
test('accepts request with referer header matching website domain', function (): void {
|
||||
VehicleRecord::create([
|
||||
'registration_number' => 'ABC123',
|
||||
'data' => ['registrationNumber' => 'ABC123', 'make' => 'ROVER', 'colour' => 'BLUE'],
|
||||
]);
|
||||
|
||||
postJson('/api/vehicle-enquiry', ['registration_number' => 'ABC123'], [
|
||||
'Authorization' => "Bearer {$this->token}",
|
||||
'Referer' => 'https://example.com/some/page',
|
||||
])->assertSuccessful();
|
||||
});
|
||||
|
||||
test('bypass_rate_limit website skips origin validation', function (): void {
|
||||
$this->website->update(['bypass_rate_limit' => true]);
|
||||
|
||||
VehicleRecord::create([
|
||||
'registration_number' => 'ABC123',
|
||||
'data' => ['registrationNumber' => 'ABC123', 'make' => 'ROVER', 'colour' => 'BLUE'],
|
||||
]);
|
||||
|
||||
postJson('/api/vehicle-enquiry', ['registration_number' => 'ABC123'], [
|
||||
'Authorization' => "Bearer {$this->token}",
|
||||
])->assertSuccessful();
|
||||
});
|
||||
Reference in New Issue
Block a user