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")', '', '', ]; foreach ($xssPayloads as $payload) { $response = $this->postJson("/api/v1/design/models/{$this->model->id}/parameters", [ 'name' => 'test_param', 'label' => $payload, 'type' => 'NUMBER', 'default_value' => '0', 'sort_order' => 1, ]); // Check that XSS payload is not reflected in response if ($response->isSuccessful()) { $responseData = $response->json(); $this->assertStringNotContainsString('