853ID, EMail oder Passwort falsch'; $parser = $this->getParserInstance(); $response = $parser->parseXmlString(ApiClient::TYPE_NOTIFICATION, $content); $this->assertEquals(853, $response->getCode()); $this->assertEquals('ID, EMail oder Passwort falsch', $response->getMessage()); $this->assertFalse($response->isSuccessful()); } public function testParseSuccessfulProfileResponse(): void { $content = 'Adressdaten141747224526FrommeBjörnHerrM16.04.1972D119447Emilienstraße 5742853RemscheidDmail@fromme.orgFalse02191-4615837'; $parser = $this->getParserInstance(); $response = $parser->parseXmlString(ApiClient::TYPE_CUSTOMER_DATA, $content); $this->assertInstanceOf(ProfileResponse::class, $response); } public function testParseSuccessfulCrmAttributesResponse(): void { $content = 'SelektionCRM141747224526'; $parser = $this->getParserInstance(); $response = $parser->parseXmlString(ApiClient::TYPE_CUSTOMER_DATA, $content); $this->assertInstanceOf(CrmAttributesResponse::class, $response); $this->assertCount(3, $response->getAttributeGroups()); $this->assertTrue($response->isTeamer()); } /** * BusPro always returns the full attribute tree and expresses the roles a person holds * through the "auswahl" flag, so a revoked role arrives as a selection set to False, * never as a missing group. The demotion path in BpnAuthenticator relies on that: it * treats "no roles" as a revocation only when attribute groups are present. */ public function testParseCrmAttributesOfAUserHoldingEveryRole(): void { $parser = $this->getParserInstance(); $response = $parser->parseXmlString(ApiClient::TYPE_CUSTOMER_DATA, $this->loadFixture('crm_attributes_granted.xml')); $this->assertInstanceOf(CrmAttributesResponse::class, $response); $this->assertTrue($response->isTeamer()); $this->assertTrue($response->isAdmin()); $this->assertTrue($response->isManager()); $this->assertTrue($response->isHouseManager()); $this->assertSame(['DKS'], $response->getHotelCodes()); // "Preisrechner Admin" is matched by id, never by its label $this->assertCount(3, $response->getAttributeGroups()); } public function testParseCrmAttributesOfAUserWhoseRolesWereRevoked(): void { $parser = $this->getParserInstance(); $response = $parser->parseXmlString(ApiClient::TYPE_CUSTOMER_DATA, $this->loadFixture('crm_attributes_revoked.xml')); $this->assertInstanceOf(CrmAttributesResponse::class, $response); $this->assertFalse($response->isTeamer()); $this->assertFalse($response->isAdmin()); $this->assertFalse($response->isManager()); $this->assertFalse($response->isHouseManager()); $this->assertSame([], $response->getHotelCodes()); // the groups still arrive, so this is a revocation and not a degraded response $this->assertCount(3, $response->getAttributeGroups()); } /** * The house of a "Hausleitung" selection comes from the configured id map, never from the * label: an id that is not mapped grants nothing. That is also what keeps the houses * commented out in bpn_crm_house_manager_ids from letting someone in who would then see * no assignments and no dispositions at all. */ public function testParseCrmAttributesIgnoresAnUnmappedHouseManagerSelection(): void { $content = 'SelektionCRM141747224526'; $parser = $this->getParserInstance(); $response = $parser->parseXmlString(ApiClient::TYPE_CUSTOMER_DATA, $content); $this->assertInstanceOf(CrmAttributesResponse::class, $response); $this->assertTrue($response->isTeamer()); $this->assertFalse($response->isHouseManager()); $this->assertSame([], $response->getHotelCodes()); } public function testParseCrmAttributesOfAnEmptyResponse(): void { $content = 'SelektionCRM141747224526'; $parser = $this->getParserInstance(); $response = $parser->parseXmlString(ApiClient::TYPE_CUSTOMER_DATA, $content); // indistinguishable from a revocation by the roles alone, which is why the empty // group set is what the demotion path checks $this->assertInstanceOf(CrmAttributesResponse::class, $response); $this->assertFalse($response->isTeamer()); $this->assertSame([], $response->getAttributeGroups()); $this->assertSame([], $response->toArray()); } public function testParseSuccessfulProfileUpdateResponseWithoutAddressData(): void { $content = 'Adressdaten_Ändern141747224526<änderung>True'; $parser = $this->getParserInstance(); $response = $parser->parseXmlString(ApiClient::TYPE_CUSTOMER_DATA, $content); $this->assertInstanceOf(ProfileUpdateResponse::class, $response); $this->assertSame(141747, $response->getAddressId()); $this->assertSame(224526, $response->getPersonId()); $this->assertTrue($response->isUpdated()); } public function testParseSuccessfulPickupsResponse(): void { $content = 'KarlsruheA5 - Autohof BruchsalBUSFalseTrueTrueBonnBahnhofBUSFalseTrueTrue'; $parser = $this->getParserInstance(); $response = $parser->parseXmlString(ApiClient::TYPE_BASE_DATA_PICKUPS, $content); $this->assertInstanceOf(BaseDataResponse::class, $response); $pickups = $response->getItems(); $this->assertCount(2, $pickups); $pickup = reset($pickups); $this->assertInstanceOf(Pickup::class, $pickup); $this->assertEquals(1, $pickup->getId()); $this->assertEquals(1, $pickup->getBusProId()); $this->assertEquals('298', $pickup->getCode()); $this->assertEquals('Karlsruhe', $pickup->getCity()); $this->assertEquals('A5 - Autohof Bruchsal', $pickup->getStreet()); } public function testParseSuccessfulHotelsResponse(): void { $content = 'Berggasthof HöchstenMusterortMusterstr. 12303444/19100GaststätteTrueBrauereigasthof Zum LöwenMusterortMusterstr. 12303437/11133GaststätteTrue'; $parser = $this->getParserInstance(); $response = $parser->parseXmlString(ApiClient::TYPE_BASE_DATA_HOTELS, $content); $this->assertInstanceOf(BaseDataResponse::class, $response); $hotels = $response->getItems(); $this->assertCount(2, $hotels); $hotel = reset($hotels); $this->assertInstanceOf(Hotel::class, $hotel); $this->assertEquals(1, $hotel->getId()); $this->assertEquals(202003, $hotel->getBusProId()); $this->assertEquals('HÖCHST', $hotel->getCode()); $this->assertEquals('Berggasthof Höchsten', $hotel->getName()); $this->assertEquals('Musterort', $hotel->getCity()); $this->assertEquals('Musterstr. 123', $hotel->getStreet()); $this->assertEquals('03444/19100', $hotel->getPhone()); $this->assertEquals('Gaststätte', $hotel->getType()); } private function loadFixture(string $filename): string { return file_get_contents(__DIR__.'/../Resources/'.$filename); } private function getParserInstance(): ResponseParser { return new ResponseParser([ 'bpn_crm_id_admin' => 1292, 'bpn_crm_id_manager' => 1293, 'bpn_crm_id_teamer' => 1070, // Deliberately a small excerpt of the configured map: the fixtures only carry // these three houses, and 1301 is here to be a mapped id no fixture selects. 'bpn_crm_house_manager_ids' => [ 1299 => 'SSL', 1301 => 'MVK', 1304 => 'DKS', 1305 => 'DGS', ], ]); } }