set('forum_bot_protection.ip', [ 'cache_ttl_minutes' => 15, 'recent_high_risk_window_hours' => 24, 'recent_high_risk_threshold' => 3, 'recent_high_risk_penalty' => 20, 'known_proxy_penalty' => 20, 'datacenter_penalty' => 25, 'tor_penalty' => 40, 'blacklist_penalty' => 100, 'known_proxies' => ['198.51.100.0/24'], 'datacenter_ranges' => ['203.0.113.0/24'], 'provider_ranges' => [ 'aws' => ['54.240.0.0/12'], ], 'tor_exit_nodes' => [], ]); Schema::dropIfExists('forum_bot_ip_blacklist'); Schema::dropIfExists('forum_bot_logs'); Schema::create('forum_bot_ip_blacklist', function (Blueprint $table): void { $table->id(); $table->string('ip_address', 45)->unique(); $table->string('reason', 255)->nullable(); $table->unsignedTinyInteger('risk_score')->default(100); $table->timestamp('expires_at')->nullable(); $table->timestamp('created_at')->nullable(); }); Schema::create('forum_bot_logs', function (Blueprint $table): void { $table->id(); $table->unsignedBigInteger('user_id')->nullable(); $table->string('ip_address', 45)->nullable(); $table->string('action', 80); $table->unsignedTinyInteger('risk_score')->default(0); $table->string('decision', 20)->default('allow'); $table->json('metadata')->nullable(); $table->timestamp('created_at')->nullable(); }); $service = app(IPReputationService::class); $proxyResult = $service->analyze('198.51.100.23'); $datacenterResult = $service->analyze('203.0.113.77'); $providerResult = $service->analyze('54.240.10.20'); expect($proxyResult['score'])->toBe(20) ->and($proxyResult['reasons'])->toContain('IP address is in the proxy watch list.') ->and($proxyResult['blocked'])->toBeFalse(); expect($datacenterResult['score'])->toBe(25) ->and($datacenterResult['reasons'])->toContain('IP address belongs to a datacenter or hosting network range.') ->and($datacenterResult['blocked'])->toBeFalse(); expect($providerResult['score'])->toBe(25) ->and($providerResult['reasons'])->toContain('IP address belongs to the configured AWS provider range.') ->and($providerResult['blocked'])->toBeFalse(); });