test(helpdesk): add OData injection rejection tests and meetings cache key coverage
Adds 11 tests for BcODataGateway covering getMeetingsForCustomer (empty, whitespace, not-configured), OData injection rejection for all customerNo methods (meetings, contract lines, escalation tickets, controlling), and valid input acceptance. Also adds meetingsKey format and inclusion tests for DebitorCacheControl. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -314,4 +314,102 @@ class BcODataGatewayTest extends TestCase
|
|||||||
$results = $gateway->getTicketsForControlling('10610');
|
$results = $gateway->getTicketsForControlling('10610');
|
||||||
$this->assertSame([], $results);
|
$this->assertSame([], $results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Meetings: empty / whitespace / not-configured ──────────────
|
||||||
|
|
||||||
|
public function testGetMeetingsForCustomerReturnsEmptyForEmptyNo(): void
|
||||||
|
{
|
||||||
|
$gateway = $this->createGatewayWithSettings();
|
||||||
|
$results = $gateway->getMeetingsForCustomer('');
|
||||||
|
$this->assertSame([], $results);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetMeetingsForCustomerReturnsEmptyForWhitespace(): void
|
||||||
|
{
|
||||||
|
$gateway = $this->createGatewayWithSettings();
|
||||||
|
$results = $gateway->getMeetingsForCustomer(' ');
|
||||||
|
$this->assertSame([], $results);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetMeetingsForCustomerReturnsEmptyWhenNotConfigured(): void
|
||||||
|
{
|
||||||
|
$gateway = $this->createGatewayWithSettings(false);
|
||||||
|
$results = $gateway->getMeetingsForCustomer('10610');
|
||||||
|
$this->assertSame([], $results);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── OData injection rejection on customerNo methods ────────────
|
||||||
|
|
||||||
|
public function testGetMeetingsForCustomerRejectsODataInjection(): void
|
||||||
|
{
|
||||||
|
$gateway = $this->createGatewayWithSettings();
|
||||||
|
$this->expectException(\InvalidArgumentException::class);
|
||||||
|
$gateway->getMeetingsForCustomer("10254' or '1'='1");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetContractLinesForCustomerRejectsODataInjection(): void
|
||||||
|
{
|
||||||
|
$gateway = $this->createGatewayWithSettings();
|
||||||
|
$this->expectException(\InvalidArgumentException::class);
|
||||||
|
$gateway->getContractLinesForCustomer("10254' or '1'='1");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetEscalationTicketsForCustomerRejectsODataInjection(): void
|
||||||
|
{
|
||||||
|
$gateway = $this->createGatewayWithSettings();
|
||||||
|
$this->expectException(\InvalidArgumentException::class);
|
||||||
|
$gateway->getEscalationTicketsForCustomer("10254' or '1'='1");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetTicketsForControllingRejectsODataInjection(): void
|
||||||
|
{
|
||||||
|
$gateway = $this->createGatewayWithSettings();
|
||||||
|
$this->expectException(\InvalidArgumentException::class);
|
||||||
|
$gateway->getTicketsForControlling("10254' or '1'='1");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetMeetingsForCustomerRejectsParenthesesInjection(): void
|
||||||
|
{
|
||||||
|
$gateway = $this->createGatewayWithSettings();
|
||||||
|
$this->expectException(\InvalidArgumentException::class);
|
||||||
|
$gateway->getMeetingsForCustomer("') or true or contains(Name,'");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetContractLinesForCustomerRejectsParenthesesInjection(): void
|
||||||
|
{
|
||||||
|
$gateway = $this->createGatewayWithSettings();
|
||||||
|
$this->expectException(\InvalidArgumentException::class);
|
||||||
|
$gateway->getContractLinesForCustomer("10254) or (1 eq 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Valid customerNo values still pass strict validation ───────
|
||||||
|
|
||||||
|
public function testGetMeetingsForCustomerAcceptsValidCustomerNo(): void
|
||||||
|
{
|
||||||
|
$gateway = $this->createGatewayWithSettings();
|
||||||
|
$threw = false;
|
||||||
|
try {
|
||||||
|
$gateway->getMeetingsForCustomer('10254');
|
||||||
|
} catch (\InvalidArgumentException) {
|
||||||
|
$this->fail('Valid customer number should pass strict input validation');
|
||||||
|
} catch (\RuntimeException) {
|
||||||
|
$threw = true;
|
||||||
|
}
|
||||||
|
// Either cURL failed (expected) or returned empty — both are fine
|
||||||
|
$this->assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetContractLinesForCustomerAcceptsCustomerNoWithDash(): void
|
||||||
|
{
|
||||||
|
$gateway = $this->createGatewayWithSettings();
|
||||||
|
$threw = false;
|
||||||
|
try {
|
||||||
|
$gateway->getContractLinesForCustomer('10254-A');
|
||||||
|
} catch (\InvalidArgumentException) {
|
||||||
|
$this->fail('Customer number with dash should pass strict input validation');
|
||||||
|
} catch (\RuntimeException) {
|
||||||
|
$threw = true;
|
||||||
|
}
|
||||||
|
$this->assertTrue(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,4 +71,16 @@ class DebitorCacheControlTest extends TestCase
|
|||||||
$keys = DebitorCacheControl::allDebitorCacheKeys('7', '10610');
|
$keys = DebitorCacheControl::allDebitorCacheKeys('7', '10610');
|
||||||
$this->assertContains('module.helpdesk.debitor.v2.controlling-tickets.7.10610', $keys);
|
$this->assertContains('module.helpdesk.debitor.v2.controlling-tickets.7.10610', $keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testMeetingsKeyFormat(): void
|
||||||
|
{
|
||||||
|
$key = DebitorCacheControl::meetingsKey('3', '10254');
|
||||||
|
$this->assertSame('module.helpdesk.debitor.v2.meetings.3.10254', $key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAllDebitorCacheKeysIncludesMeetingsKey(): void
|
||||||
|
{
|
||||||
|
$keys = DebitorCacheControl::allDebitorCacheKeys('3', '10254');
|
||||||
|
$this->assertContains('module.helpdesk.debitor.v2.meetings.3.10254', $keys);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user