diff --git a/legacy_tests/register_patron.php b/legacy_tests/register_patron.php index 2535fd1..f573c01 100644 --- a/legacy_tests/register_patron.php +++ b/legacy_tests/register_patron.php @@ -58,3 +58,10 @@ $response = $ilsws->registerPatron($patron, $token, $addrNum, $options); print_r($response); + +if (isset($response['key'])) { + echo "\nRetrieving patron attributes for key {$response['key']}...\n"; + $attributes = $ilsws->getPatronAttributes($token, $response['key']); + print_r($attributes); +} + diff --git a/libilsws.yaml.sample b/libilsws.yaml.sample index 0be544e..69714f4 100644 --- a/libilsws.yaml.sample +++ b/libilsws.yaml.sample @@ -109,7 +109,7 @@ symphony: alias: 'patron_key' validation: 'i:0,99999999' profile: - validation: "v:0_MULT|1_MULT_Y|2_METRO|3_METRO_Y|PASSPORT|PASSPORT_Y" + validation: "v:0_MULT|1_MULT_Y|2_METRO|3_METRO_Y|PASSPORT|PASSPORT_Y|ONLINE" default: '0_MULT' pin: alias: 'password' @@ -137,11 +137,13 @@ symphony: type: 'address' CITY/STATE: alias: 'city_state' - validation: 'r:/^[[:word:] ]{4,128}$/' + validation: 'r:/^[[:word:] ,]{4,128}$/' default: 'Portland, OR' type: 'address' ZIP: - validation: 'v:97002|97003|97004|97005|97006|97007|97008|97009|97010|97011|97013|97014|97015|97017|97019|97022|97023|97024|97027|97028|97030|97031|97032r97034|97035|97036|97038|97041|97042|97044|97045|97049|97055|97056|97060|97062|97064|97067|97068|97070|97071|97075|97076|97077|97078|97079|97080|97086|97089|97106|97109|97113|97116|97117|97119|97123|97124|97125|97129|97133|97140|97144|97201|97202|97203|97204|97205|97206|97207|97208|97209|97210|97211|97212|97213|97214|97215|97216|97217|97218|97219|97220|97221|97222|97223|97224|97225|97227|97228|97229|97230|97231|97232|97233|97236|97238|97239|97240|97242|97250|97251|97252|97253|97254|97255|97256|97259|97266|97267|97268|97269|97271|97272|97280|97281|97282|97283|97286|97290|97291|97292|97293|97294|97296|97298|97362|97375|98601|98602|98603|98604|98605|98606|98607|98610|98613|98616|98617|98619|98620|98622|98623|98628|98629|98635|98639|98642|98648|98650|98651|98660|98661|98662|98663|98664|98665|98666|98668|98670|98671|98672|98673|98674|98675|98682|98683|98684|98685|98686|98687|99322|99356' + alias: 'postal_code' + validation: 'v:97002|97003|97004|97005|97006|97007|97008|97009|97010|97011|97013|97014|97015|97017|97019|97022|97023|97024|97027|97028|97030|97031|97032|97034|97035|97036|97038|97041|97042|97044|97045|97049|97055|97056|97060|97062|97064|97067|97068|97070|97071|97075|97076|97077|97078|97079|97080|97086|97089|97106|97109|97113|97116|97117|97119|97123|97124|97125|97129|97133|97140|97144|97201|97202|97203|97204|97205|97206|97207|97208|97209|97210|97211|97212|97213|97214|97215|97216|97217|97218|97219|97220|97221|97222|97223|97224|97225|97227|97228|97229|97230|97231|97232|97233|97236|97238|97239|97240|97242|97250|97251|97252|97253|97254|97255|97256|97259|97266|97267|97268|97269|97271|97272|97280|97281|97282|97283|97286|97290|97291|97292|97293|97294|97296|97298|97362|97375|98601|98602|98603|98604|98605|98606|98607|98610|98613|98616|98617|98619|98620|98622|98623|98628|98629|98635|98639|98642|98648|98650|98651|98660|98661|98662|98663|98664|98665|98666|98668|98670|98671|98672|98673|98674|98675|98682|98683|98684|98685|98686|98687|99322|99356' + type: 'address' EMAIL: alias: 'email' validation: 'e' @@ -236,13 +238,13 @@ symphony: type: 'address' CITY/STATE: alias: 'city_state' - validation: 'r:/^[[:word:] ]{4,128}$/' + validation: 'r:/^[[:word:] ,]{4,128}$/' default: 'Portland, OR' required: true type: 'address' ZIP: alias: 'postal_code' - validation: 'v:97002|97003|97004|97005|97006|97007|97008|97009|97010|97011|97013|97014|97015|97017|97019|97022|97023|97024|97027|97028|97030|97031|97032r97034|97035|97036|97038|97041|97042|97044|97045|97049|97055|97056|97060|97062|97064|97067|97068|97070|97071|97075|97076|97077|97078|97079|97080|97086|97089|97106|97109|97113|97116|97117|97119|97123|97124|97125|97129|97133|97140|97144|97201|97202|97203|97204|97205|97206|97207|97208|97209|97210|97211|97212|97213|97214|97215|97216|97217|97218|97219|97220|97221|97222|97223|97224|97225|97227|97228|97229|97230|97231|97232|97233|97236|97238|97239|97240|97242|97250|97251|97252|97253|97254|97255|97256|97259|97266|97267|97268|97269|97271|97272|97280|97281|97282|97283|97286|97290|97291|97292|97293|97294|97296|97298|97362|97375|98601|98602|98603|98604|98605|98606|98607|98610|98613|98616|98617|98619|98620|98622|98623|98628|98629|98635|98639|98642|98648|98650|98651|98660|98661|98662|98663|98664|98665|98666|98668|98670|98671|98672|98673|98674|98675|98682|98683|98684|98685|98686|98687|99322|99356' + validation: 'v:97002|97003|97004|97005|97006|97007|97008|97009|97010|97011|97013|97014|97015|97017|97019|97022|97023|97024|97027|97028|97030|97031|97032|97034|97035|97036|97038|97041|97042|97044|97045|97049|97055|97056|97060|97062|97064|97067|97068|97070|97071|97075|97076|97077|97078|97079|97080|97086|97089|97106|97109|97113|97116|97117|97119|97123|97124|97125|97129|97133|97140|97144|97201|97202|97203|97204|97205|97206|97207|97208|97209|97210|97211|97212|97213|97214|97215|97216|97217|97218|97219|97220|97221|97222|97223|97224|97225|97227|97228|97229|97230|97231|97232|97233|97236|97238|97239|97240|97242|97250|97251|97252|97253|97254|97255|97256|97259|97266|97267|97268|97269|97271|97272|97280|97281|97282|97283|97286|97290|97291|97292|97293|97294|97296|97298|97362|97375|98601|98602|98603|98604|98605|98606|98607|98610|98613|98616|98617|98619|98620|98622|98623|98628|98629|98635|98639|98642|98648|98650|98651|98660|98661|98662|98663|98664|98665|98666|98668|98670|98671|98672|98673|98674|98675|98682|98683|98684|98685|98686|98687|99322|99356' required: true type: 'address' EMAIL: diff --git a/src/Libilsws.php b/src/Libilsws.php index 7a057c4..0004147 100644 --- a/src/Libilsws.php +++ b/src/Libilsws.php @@ -1957,6 +1957,22 @@ private function checkAliases(array $patron, array $fields): array } } + if (empty($patron['ZIP'])) { + if (isset($patron['zip'])) { + $patron['ZIP'] = $patron['zip']; + } elseif (isset($patron['postal_code'])) { + $patron['ZIP'] = $patron['postal_code']; + } + } + + if (empty($patron['CITY/STATE'])) { + if (isset($patron['city_state'])) { + $patron['CITY/STATE'] = $patron['city_state']; + } elseif (isset($patron['city']) && isset($patron['state'])) { + $patron['CITY/STATE'] = $patron['city'] . ', ' . $patron['state']; + } + } + return $patron; } @@ -2397,7 +2413,7 @@ public function registerPatron(array $patron, ?string $token = null, ?int $addrN $this->validate('template', $template, 'r:#^([a-zA-Z0-9\-_]{1,40})(\.)(html|text)(\.)(twig)$#'); $subject = !empty($options['subject']) ? $options['subject'] : ''; - $this->validate('subject', $subject, 's:20'); + $this->validate('subject', $subject, 's:128'); $response = []; diff --git a/tests/Unit/LibilswsTest.php b/tests/Unit/LibilswsTest.php index 5fc039b..305e9bb 100644 --- a/tests/Unit/LibilswsTest.php +++ b/tests/Unit/LibilswsTest.php @@ -113,6 +113,12 @@ public function testGetPatronAttributesMapsFieldsProperly(): void 'code' => ['key' => 'CITY/STATE'], 'data' => 'Portland, OR' ] + ], + [ + 'fields' => [ + 'code' => ['key' => 'ZIP'], + 'data' => '97209' + ] ] ] ] @@ -140,6 +146,7 @@ public function testGetPatronAttributesMapsFieldsProperly(): void $this->assertEquals('johndoe@example.com', $attributes['email']); $this->assertEquals('Portland', $attributes['city']); $this->assertEquals('OR', $attributes['state']); + $this->assertEquals('97209', $attributes['zip']); $this->assertEquals('John Doe', $attributes['displayName']); } @@ -183,5 +190,94 @@ public function testGetExpirationCalculatesDatesCorrectly(): void $expectedFutureDate = date('Y-m-d', strtotime($today . " + 30 day")); $this->assertEquals($expectedFutureDate, $ilsws->getExpiration(30)); } + + public function testUpdatePatronPreservesAddressFieldsFromGetPatronAttributes(): void + { + $sampleYamlContent = file_get_contents(__DIR__ . '/../../libilsws.yaml.sample'); + $sampleYamlContent = str_replace( + ['HOSTNAME', 'PORT', 'WEBAPP_NAME'], + ['api.example.com', '443', 'symws'], + $sampleYamlContent + ); + $tempYamlPath = sys_get_temp_dir() . '/test_sample_' . bin2hex(random_bytes(8)) . '.yaml'; + file_put_contents($tempYamlPath, $sampleYamlContent); + + try { + $ilswsMock = $this->getMockBuilder(Libilsws::class) + ->setConstructorArgs([$tempYamlPath]) + ->onlyMethods(['sendGet', 'sendQuery']) + ->getMock(); + + $ilswsMock->method('sendGet')->willReturn([ + 'fields' => [ + ['name' => 'library', 'type' => 'resource', 'uri' => '/policy/library'], + ['name' => 'profile', 'type' => 'resource', 'uri' => '/policy/patronProfile'], + ['name' => 'language', 'type' => 'resource', 'uri' => '/policy/patronLanguage'], + ['name' => 'firstName', 'type' => 'string', 'min' => 1, 'max' => 40], + ['name' => 'middleName', 'type' => 'string', 'min' => 0, 'max' => 40], + ['name' => 'lastName', 'type' => 'string', 'min' => 1, 'max' => 40], + ['name' => 'birthDate', 'type' => 'date'], + ['name' => 'category01', 'type' => 'resource', 'uri' => '/policy/patronCategory01'], + ['name' => 'category02', 'type' => 'resource', 'uri' => '/policy/patronCategory02'], + ['name' => 'category03', 'type' => 'resource', 'uri' => '/policy/patronCategory03'], + ['name' => 'category05', 'type' => 'resource', 'uri' => '/policy/patronCategory05'], + ['name' => 'category06', 'type' => 'resource', 'uri' => '/policy/patronCategory06'], + ['name' => 'category11', 'type' => 'string', 'min' => 0, 'max' => 10], + ['name' => 'category12', 'type' => 'string', 'min' => 0, 'max' => 10], + ['name' => 'category13', 'type' => 'string', 'min' => 0, 'max' => 10], + ] + ]); + + $fakeToken = '12345678-1234-1234-1234-123456789012'; + $patronKey = '1234567'; + + // Input shaped like getPatronAttributes() output + $patronInput = [ + 'firstName' => 'John', + 'lastName' => 'Doe', + 'city' => 'Portland', + 'state' => 'OR', + 'zip' => '97209', + 'email' => 'johndoe@example.com', + 'telephone' => '215-534-6820', + 'category02' => 'TEXT', + 'profile' => 'ONLINE', + ]; + + // Handle sendQuery calls for updatePatron + $ilswsMock->expects($this->once()) + ->method('sendQuery') + ->willReturnCallback(function ($url, $token, $data = null, $method = 'GET') use ($patronKey) { + if (str_contains($url, '/policy/patron/description')) { + return ['fields' => []]; + } + + if (str_contains($url, "/user/patron/key/{$patronKey}") && $method === 'PUT') { + $parsedData = json_decode($data, true); + $address1 = $parsedData['fields']['address1'] ?? []; + + $zipField = current(array_filter($address1, fn($item) => ($item['fields']['code']['key'] ?? null) === 'ZIP')); + $cityStateField = current(array_filter($address1, fn($item) => ($item['fields']['code']['key'] ?? null) === 'CITY/STATE')); + + $this->assertNotEmpty($zipField, 'ZIP field should be present in address1'); + $this->assertEquals('97209', $zipField['fields']['data'] ?? null); + $this->assertNotEmpty($cityStateField, 'CITY/STATE field should be present in address1'); + $this->assertEquals('Portland, OR', $cityStateField['fields']['data'] ?? null); + + return ['resource' => '/user/patron', 'key' => $patronKey]; + } + + return []; + }); + + $response = $ilswsMock->updatePatron($patronInput, $fakeToken, $patronKey, 1); + $this->assertEquals($patronKey, $response['key']); + } finally { + if (file_exists($tempYamlPath)) { + unlink($tempYamlPath); + } + } + } } +