Add OpenCorporates collector for org targets, gated on an API token
Closes the last coverage gap for org (person remains intentionally unaddressed — no good free/non-scraping option exists). Unlike the other 8 sources, OpenCorporates now requires an API token even on its free tier (confirmed: keyless request gets 401). RunnerRegistry::has() checks services.opencorporates.token is configured before exposing the source as runnable, so it degrades to "not yet integrated" in the UI rather than a broken Run button, until OPENCORPORATES_API_TOKEN is set. Also fixes a real bug hit while building this: a RunnerRegistry::REQUIRES class constant mapping source key to a closure crashed on file load (PHP class constants can't hold closures) — inlined the one check instead of building an abstraction for a single case. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
86f73c46b2
commit
dc4388eabf
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Osint;
|
||||
|
||||
use App\Models\Target;
|
||||
use App\Osint\Runners\OpenCorporatesRunner;
|
||||
use App\Osint\RunnerException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Tests\TestCase;
|
||||
|
||||
class OpenCorporatesRunnerTest extends TestCase
|
||||
{
|
||||
public function test_throws_when_no_token_configured(): void
|
||||
{
|
||||
config(['services.opencorporates.token' => null]);
|
||||
|
||||
$target = new Target(['type' => 'org', 'value' => 'Acme Inc']);
|
||||
|
||||
$this->expectException(RunnerException::class);
|
||||
$this->expectExceptionMessage('API token not configured');
|
||||
|
||||
(new OpenCorporatesRunner)->run($target);
|
||||
}
|
||||
|
||||
public function test_parses_search_results_into_matches(): void
|
||||
{
|
||||
config(['services.opencorporates.token' => 'test-token']);
|
||||
|
||||
Http::fake([
|
||||
'api.opencorporates.com/*' => Http::response([
|
||||
'results' => [
|
||||
'companies' => [
|
||||
[
|
||||
'company' => [
|
||||
'name' => 'Acme Inc',
|
||||
'jurisdiction_code' => 'us_de',
|
||||
'current_status' => 'Active',
|
||||
'opencorporates_url' => 'https://opencorporates.com/companies/us_de/123',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]),
|
||||
]);
|
||||
|
||||
$target = new Target(['type' => 'org', 'value' => 'Acme Inc']);
|
||||
|
||||
$result = (new OpenCorporatesRunner)->run($target);
|
||||
|
||||
$this->assertCount(1, $result->raw['matches']);
|
||||
$this->assertSame('Acme Inc (us_de, Active)', $result->raw['matches'][0]['name']);
|
||||
$this->assertSame('https://opencorporates.com/companies/us_de/123', $result->raw['matches'][0]['url']);
|
||||
$this->assertStringContainsString('Found 1 company', $result->summary);
|
||||
}
|
||||
|
||||
public function test_reports_no_companies_found(): void
|
||||
{
|
||||
config(['services.opencorporates.token' => 'test-token']);
|
||||
|
||||
Http::fake([
|
||||
'api.opencorporates.com/*' => Http::response(['results' => ['companies' => []]]),
|
||||
]);
|
||||
|
||||
$target = new Target(['type' => 'org', 'value' => 'Nonexistent Org XYZ']);
|
||||
|
||||
$result = (new OpenCorporatesRunner)->run($target);
|
||||
|
||||
$this->assertSame([], $result->raw['matches']);
|
||||
$this->assertStringContainsString('No companies found', $result->summary);
|
||||
}
|
||||
|
||||
public function test_rejects_non_org_targets(): void
|
||||
{
|
||||
config(['services.opencorporates.token' => 'test-token']);
|
||||
|
||||
$target = new Target(['type' => 'domain', 'value' => 'example.com']);
|
||||
|
||||
$this->expectException(RunnerException::class);
|
||||
|
||||
(new OpenCorporatesRunner)->run($target);
|
||||
}
|
||||
|
||||
public function test_throws_on_http_failure(): void
|
||||
{
|
||||
config(['services.opencorporates.token' => 'test-token']);
|
||||
|
||||
Http::fake(['api.opencorporates.com/*' => Http::response('', 401)]);
|
||||
|
||||
$target = new Target(['type' => 'org', 'value' => 'Acme Inc']);
|
||||
|
||||
$this->expectException(RunnerException::class);
|
||||
|
||||
(new OpenCorporatesRunner)->run($target);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Osint;
|
||||
|
||||
use App\Osint\RunnerRegistry;
|
||||
use Tests\TestCase;
|
||||
|
||||
class RunnerRegistryTest extends TestCase
|
||||
{
|
||||
public function test_opencorporates_is_hidden_without_a_configured_token(): void
|
||||
{
|
||||
config(['services.opencorporates.token' => null]);
|
||||
|
||||
$this->assertFalse((new RunnerRegistry)->has('opencorporates'));
|
||||
}
|
||||
|
||||
public function test_opencorporates_is_available_once_a_token_is_configured(): void
|
||||
{
|
||||
config(['services.opencorporates.token' => 'test-token']);
|
||||
|
||||
$this->assertTrue((new RunnerRegistry)->has('opencorporates'));
|
||||
}
|
||||
|
||||
public function test_unconditional_runners_are_always_available(): void
|
||||
{
|
||||
$this->assertTrue((new RunnerRegistry)->has('sherlock'));
|
||||
$this->assertTrue((new RunnerRegistry)->has('dns'));
|
||||
}
|
||||
|
||||
public function test_unknown_source_key_is_never_available(): void
|
||||
{
|
||||
$this->assertFalse((new RunnerRegistry)->has('not-a-real-source'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user