user = User::factory()->create(['tenant_id' => 1]);
$this->otherTenantUser = User::factory()->create(['tenant_id' => 2]);
// Create test model
$this->model = Model::factory()->create(['tenant_id' => 1]);
// Set required headers
$this->withHeaders([
'X-API-KEY' => config('app.api_key', 'test-api-key'),
'Accept' => 'application/json',
'Content-Type' => 'application/json',
]);
}
/** @test */
public function it_requires_api_key_for_all_endpoints()
{
// Remove API key header
$this->withHeaders([
'Accept' => 'application/json',
'Content-Type' => 'application/json',
]);
// Test various endpoints
$endpoints = [
['GET', '/api/v1/design/models'],
['GET', "/api/v1/design/models/{$this->model->id}/parameters"],
['POST', "/api/v1/design/models/{$this->model->id}/bom/resolve"],
];
foreach ($endpoints as [$method, $endpoint]) {
$response = $this->json($method, $endpoint);
$response->assertUnauthorized();
}
}
/** @test */
public function it_rejects_invalid_api_keys()
{
$this->withHeaders([
'X-API-KEY' => 'invalid-api-key',
'Accept' => 'application/json',
]);
$response = $this->getJson('/api/v1/design/models');
$response->assertUnauthorized();
}
/** @test */
public function it_requires_authentication_for_protected_routes()
{
// Test endpoints that require user authentication
$protectedEndpoints = [
['GET', "/api/v1/design/models/{$this->model->id}/parameters"],
['POST', "/api/v1/design/models/{$this->model->id}/parameters"],
['POST', "/api/v1/design/models/{$this->model->id}/bom/resolve"],
['POST', "/api/v1/design/models/{$this->model->id}/products"],
];
foreach ($protectedEndpoints as [$method, $endpoint]) {
$response = $this->json($method, $endpoint);
$response->assertUnauthorized();
}
}
/** @test */
public function it_enforces_tenant_isolation()
{
// Authenticate as user from tenant 1
Sanctum::actingAs($this->user);
// Try to access model from different tenant
$otherTenantModel = Model::factory()->create(['tenant_id' => 2]);
$response = $this->getJson("/api/v1/design/models/{$otherTenantModel->id}/parameters");
$response->assertNotFound();
}
/** @test */
public function it_prevents_sql_injection_in_parameters()
{
Sanctum::actingAs($this->user);
// Test SQL injection attempts in various inputs
$sqlInjectionPayloads = [
"'; DROP TABLE models; --",
"' UNION SELECT * FROM users --",
"1' OR '1'='1",
"",
];
foreach ($sqlInjectionPayloads as $payload) {
// Test in BOM resolution parameters
$response = $this->postJson("/api/v1/design/models/{$this->model->id}/bom/resolve", [
'parameters' => [
'W0' => $payload,
'H0' => 800,
'screen_type' => 'SCREEN',
]
]);
// Should either validate and reject, or handle safely
$this->assertTrue(
$response->status() === 422 || $response->status() === 400,
"SQL injection payload was not properly handled: {$payload}"
);
}
}
/** @test */
public function it_sanitizes_formula_expressions()
{
Sanctum::actingAs($this->user);
// Test dangerous expressions that could execute arbitrary code
$dangerousExpressions = [
'system("rm -rf /")',
'eval("malicious code")',
'exec("ls -la")',
'__import__("os").system("pwd")',
'file_get_contents("/etc/passwd")',
];
foreach ($dangerousExpressions as $expression) {
$response = $this->postJson("/api/v1/design/models/{$this->model->id}/formulas", [
'name' => 'test_formula',
'expression' => $expression,
'description' => 'Test formula',
'return_type' => 'NUMBER',
'sort_order' => 1,
]);
// Should reject dangerous expressions
$response->assertStatus(422);
$response->assertJsonValidationErrors(['expression']);
}
}
/** @test */
public function it_prevents_xss_in_user_inputs()
{
Sanctum::actingAs($this->user);
$xssPayloads = [
'',
'javascript:alert("xss")',
'
',
'