From 3827b09205da5149cc132b0a3263718ca6f9729a Mon Sep 17 00:00:00 2001 From: fs Date: Mon, 13 Apr 2026 15:16:57 +0200 Subject: [PATCH] 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 --- .../Helpdesk/Service/BcODataGatewayTest.php | 98 +++++++++++++++++++ .../Service/DebitorCacheControlTest.php | 12 +++ 2 files changed, 110 insertions(+) diff --git a/modules/helpdesk/tests/Module/Helpdesk/Service/BcODataGatewayTest.php b/modules/helpdesk/tests/Module/Helpdesk/Service/BcODataGatewayTest.php index f94c3fc..1fda158 100644 --- a/modules/helpdesk/tests/Module/Helpdesk/Service/BcODataGatewayTest.php +++ b/modules/helpdesk/tests/Module/Helpdesk/Service/BcODataGatewayTest.php @@ -314,4 +314,102 @@ class BcODataGatewayTest extends TestCase $results = $gateway->getTicketsForControlling('10610'); $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); + } } diff --git a/modules/helpdesk/tests/Module/Helpdesk/Service/DebitorCacheControlTest.php b/modules/helpdesk/tests/Module/Helpdesk/Service/DebitorCacheControlTest.php index 617087e..3a9fde0 100644 --- a/modules/helpdesk/tests/Module/Helpdesk/Service/DebitorCacheControlTest.php +++ b/modules/helpdesk/tests/Module/Helpdesk/Service/DebitorCacheControlTest.php @@ -71,4 +71,16 @@ class DebitorCacheControlTest extends TestCase $keys = DebitorCacheControl::allDebitorCacheKeys('7', '10610'); $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); + } }