diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c416ff5..55ff30b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,27 @@ jobs: - name: Run unit tests run: vendor/bin/phpunit --testsuite unit + phpcs: + name: phpcs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + coverage: none + tools: composer:v2 + + - name: Install dependencies + uses: ramsey/composer-install@v3 + with: + composer-options: '--no-interaction --no-progress --prefer-dist' + + - name: Run phpcs + run: vendor/bin/phpcs --standard=vendor/rentpost/coding-standard-php/phpcs.xml src/ test/ + # Integration tests hit the real TransUnion ShareAble sandbox API and require # credentials. They run only on manual maintainer trigger so PR validation # never burns sandbox quota and so secrets are never exposed to fork PRs. diff --git a/src/Client.php b/src/Client.php index 3c2e3d5..a628705 100644 --- a/src/Client.php +++ b/src/Client.php @@ -447,9 +447,10 @@ public function addRenterToScreeningRequest( ScreeningRequestRenter $screeningRequestRenter, ): ScreeningRequestRenter { + $renterId = $screeningRequestRenter->getRenterId(); $response = $this->requestJson( 'POST', - "ScreeningRequests/$screeningRequestId/Renters/{$screeningRequestRenter->getRenterId()}/ScreeningRequestRenters", + "ScreeningRequests/$screeningRequestId/Renters/$renterId/ScreeningRequestRenters", $screeningRequestRenter->toArray(), // No clue on this - docs don't say it's needed, but it is ); diff --git a/src/ClientException.php b/src/ClientException.php index e8bbf19..a4af81a 100644 --- a/src/ClientException.php +++ b/src/ClientException.php @@ -7,4 +7,5 @@ class ClientException extends \Exception { + // Marker exception for ShareAble client errors. } diff --git a/src/ClientInterface.php b/src/ClientInterface.php index fa2d33b..3b22e76 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -4,7 +4,6 @@ namespace Rentpost\TUShareable; -use Rentpost\TUShareable\Model\Attestation; use Rentpost\TUShareable\Model\AttestationGroup; use Rentpost\TUShareable\Model\Bundle; use Rentpost\TUShareable\Model\CultureCode; diff --git a/src/Model/Address.php b/src/Model/Address.php index f7785b1..f998e77 100644 --- a/src/Model/Address.php +++ b/src/Model/Address.php @@ -6,6 +6,9 @@ use Symfony\Component\Validator\Constraints as Assert; +// addressLine1..4 are TU API field names; renaming would break the wire contract. +// phpcs:disable Zend.NamingConventions.ValidVariableName.ContainsNumbers + /** * Class that represents an address. */ @@ -15,34 +18,44 @@ class Address use Validate; + private const ADDRESS_PATTERN = '/^[a-zA-Z0-9 #()&.,\'\-_\+~\/\*]*$/'; + private const ADDRESS_PATTERN_MESSAGE = 'Address field must only contain letters, numbers, spaces, hashes, ' + . 'parentheses, ampersands, commas, periods, single quotes, hyphens, underscores, pluses, tildes, ' + . 'forward slashes and asterisks.'; + + + /** @param array $data */ + public static function fromArray(array $data): self + { + return new self( + $data['addressLine1'], + $data['addressLine2'] ?? null, + $data['addressLine3'] ?? null, + $data['addressLine4'] ?? null, + $data['locality'], + $data['region'], + $data['postalCode'], + $data['country'], + ); + } + + public function __construct( #[Assert\NotBlank] #[Assert\Length(min: 1, max: 50)] - #[Assert\Regex( - pattern: '/^[a-zA-Z0-9 #()&.,\'\-_\+~\/\*]*$/', - message: 'Address field must only contain letters, numbers, spaces, hashes, parentheses, ampersands, commas, periods, single quotes, hyphens, underscores, pluses, tildes, forward slashes and asterisks.', - )] + #[Assert\Regex(pattern: self::ADDRESS_PATTERN, message: self::ADDRESS_PATTERN_MESSAGE)] private string $addressLine1, #[Assert\Length(max: 100)] - #[Assert\Regex( - pattern: '/^[a-zA-Z0-9 #()&.,\'\-_\+~\/\*]*$/', - message: 'Address field must only contain letters, numbers, spaces, hashes, parentheses, ampersands, commas, periods, single quotes, hyphens, underscores, pluses, tildes, forward slashes and asterisks.', - )] + #[Assert\Regex(pattern: self::ADDRESS_PATTERN, message: self::ADDRESS_PATTERN_MESSAGE)] private ?string $addressLine2, #[Assert\Length(max: 100)] - #[Assert\Regex( - pattern: '/^[a-zA-Z0-9 #()&.,\'\-_\+~\/\*]*$/', - message: 'Address field must only contain letters, numbers, spaces, hashes, parentheses, ampersands, commas, periods, single quotes, hyphens, underscores, pluses, tildes, forward slashes and asterisks.', - )] + #[Assert\Regex(pattern: self::ADDRESS_PATTERN, message: self::ADDRESS_PATTERN_MESSAGE)] private ?string $addressLine3, #[Assert\Length(max: 100)] - #[Assert\Regex( - pattern: '/^[a-zA-Z0-9 #()&.,\'\-_\+~\/\*]*$/', - message: 'Address field must only contain letters, numbers, spaces, hashes, parentheses, ampersands, commas, periods, single quotes, hyphens, underscores, pluses, tildes, forward slashes and asterisks.', - )] + #[Assert\Regex(pattern: self::ADDRESS_PATTERN, message: self::ADDRESS_PATTERN_MESSAGE)] private ?string $addressLine4, #[Assert\NotBlank] @@ -253,20 +266,4 @@ public function toArray(): array return $array; } - - - /** @param array $data */ - public static function fromArray(array $data): self - { - return new self( - $data['addressLine1'], - $data['addressLine2'] ?? null, - $data['addressLine3'] ?? null, - $data['addressLine4'] ?? null, - $data['locality'], - $data['region'], - $data['postalCode'], - $data['country'], - ); - } } diff --git a/src/Model/Attestation.php b/src/Model/Attestation.php index e255329..43942be 100644 --- a/src/Model/Attestation.php +++ b/src/Model/Attestation.php @@ -13,6 +13,20 @@ class Attestation use Validate; + /** @param array $data */ + public static function fromArray(array $data): self + { + return new self( + $data['attestationId'], + $data['attestationTypeId'], + $data['name'], + $data['legalText'], + $data['affirmativeRequired'], + $data['additionalInformation'], + ); + } + + public function __construct( private int $attestationId, private int $attestationTypeId, @@ -73,18 +87,4 @@ public function toArray(): array 'additionalInformation' => $this->additionalInformation, ]; } - - - /** @param array $data */ - public static function fromArray(array $data): self - { - return new self( - $data['attestationId'], - $data['attestationTypeId'], - $data['name'], - $data['legalText'], - $data['affirmativeRequired'], - $data['additionalInformation'], - ); - } } diff --git a/src/Model/AttestationGroup.php b/src/Model/AttestationGroup.php index c5c9a19..7e321e8 100644 --- a/src/Model/AttestationGroup.php +++ b/src/Model/AttestationGroup.php @@ -19,6 +19,27 @@ class AttestationGroup private ?array $attestationResponses = null; + /** @param array|null> $data */ + public static function fromArray(array $data): self + { + $attestationGroupId = $data['attestationGroupId']; + + $attestations = []; + foreach (($data['attestations'] ?? []) as $attestation) { + $attestations[] = new Attestation( + $attestation['attestationId'], + $attestation['attestationTypeId'], + $attestation['name'] ?? null, + $attestation['legalText'] ?? null, + $attestation['affirmativeRequired'], + $attestation['additionalInformation'] ?? null, + ); + } + + return new self($attestationGroupId, $attestations); + } + + public function __construct( private readonly int $attestationGroupId, @@ -55,27 +76,6 @@ public function addAttestationResponse(int $attestationId, bool $isAffirmative): } - /** @param array|null> $data */ - public static function fromArray(array $data): self - { - $attestationGroupId = $data['attestationGroupId']; - - $attestations = []; - foreach (($data['attestations'] ?? []) as $attestation) { - $attestations[] = new Attestation( - $attestation['attestationId'], - $attestation['attestationTypeId'], - $attestation['name'] ?? null, - $attestation['legalText'] ?? null, - $attestation['affirmativeRequired'], - $attestation['additionalInformation'] ?? null, - ); - } - - return new self($attestationGroupId, $attestations); - } - - /** @return array> */ public function toArray(): array { diff --git a/src/Model/Bundle.php b/src/Model/Bundle.php index fcd96be..6d0e684 100644 --- a/src/Model/Bundle.php +++ b/src/Model/Bundle.php @@ -15,6 +15,16 @@ class Bundle use Validate; + /** @param array $data */ + public static function fromArray(array $data): self + { + return new self( + $data['bundleId'], + $data['name'], + ); + } + + public function __construct( #[Assert\NotBlank] private int $bundleId, @@ -45,14 +55,4 @@ public function toArray(): array 'name' => $this->name, ]; } - - - /** @param array $data */ - public static function fromArray(array $data): self - { - return new self( - $data['bundleId'], - $data['name'], - ); - } } diff --git a/src/Model/EmploymentStatus.php b/src/Model/EmploymentStatus.php index f6d10a9..167052d 100644 --- a/src/Model/EmploymentStatus.php +++ b/src/Model/EmploymentStatus.php @@ -8,8 +8,10 @@ enum EmploymentStatus: string { + use From; + case NotEmployed = 'NotEmployed'; case Employed = 'Employed'; case SelfEmployed = 'SelfEmployed'; diff --git a/src/Model/Exam.php b/src/Model/Exam.php index d0a8989..f644525 100644 --- a/src/Model/Exam.php +++ b/src/Model/Exam.php @@ -15,6 +15,28 @@ class Exam private ?string $externalReferenceNumber = null; + /** @param array $data */ + public static function fromArray(array $data): self + { + $exam = new self($data['examId'], $data['result']); + $exam->setExternalReferenceNumber($data['setExternalReferenceNumber'] ?? null); + + foreach ($data['authenticationQuestions'] as $qInfo) { + $question = new ExamQuestion($qInfo['questionKeyName'], $qInfo['questionDisplayName'], $qInfo['type']); + + foreach ($qInfo['choices'] as $choice) { + $answer = new ExamQuestionAnswer($choice['choiceKeyName'], $choice['choiceDisplayName']); + + $question->addChoice($answer); + } + + $exam->addQuestion($question); + } + + return $exam; + } + + public function __construct(private int $examId, private string $result) {} @@ -55,26 +77,4 @@ public function setExternalReferenceNumber(?string $val): void { $this->externalReferenceNumber = $val; } - - - /** @param array $data */ - public static function fromArray(array $data): self - { - $exam = new self($data['examId'], $data['result']); - $exam->setExternalReferenceNumber($data['setExternalReferenceNumber'] ?? null); - - foreach ($data['authenticationQuestions'] as $qInfo) { - $question = new ExamQuestion($qInfo['questionKeyName'], $qInfo['questionDisplayName'], $qInfo['type']); - - foreach ($qInfo['choices'] as $choice) { - $answer = new ExamQuestionAnswer($choice['choiceKeyName'], $choice['choiceDisplayName']); - - $question->addChoice($answer); - } - - $exam->addQuestion($question); - } - - return $exam; - } } diff --git a/src/Model/Landlord.php b/src/Model/Landlord.php index be2b0ad..16a811a 100644 --- a/src/Model/Landlord.php +++ b/src/Model/Landlord.php @@ -18,6 +18,25 @@ class Landlord private ?int $landlordId = null; + /** @param array $data */ + public static function fromArray(array $data): self + { + $landlord = new self( + new Email($data['emailAddress']), + $data['firstName'], + $data['lastName'], + new Phone($data['phoneNumber'], $data['phoneType']), + $data['businessName'] ?? null, + Address::fromArray($data['businessAddress']), + boolval($data['acceptedTermsAndConditions']), + ); + + $landlord->setLandlordId($data['landlordId'] ?? null); + + return $landlord; + } + + public function __construct( #[Assert\NotBlank] private Email $emailAddress, @@ -167,23 +186,4 @@ public function toArray(): array return $array; } - - - /** @param array $data */ - public static function fromArray(array $data): self - { - $landlord = new self( - new Email($data['emailAddress']), - $data['firstName'], - $data['lastName'], - new Phone($data['phoneNumber'], $data['phoneType']), - $data['businessName'] ?? null, - Address::fromArray($data['businessAddress']), - boolval($data['acceptedTermsAndConditions']), - ); - - $landlord->setLandlordId($data['landlordId'] ?? null); - - return $landlord; - } } diff --git a/src/Model/Person.php b/src/Model/Person.php index 39554b3..c30830e 100644 --- a/src/Model/Person.php +++ b/src/Model/Person.php @@ -18,6 +18,30 @@ class Person private ?int $personId = null; + /** @param array $data */ + public static function fromArray(array $data): self + { + $ssn = $data['socialSecurityNumber'] ?? null; + $dob = $data['dateOfBirth'] ?? null; + + $person = new self( + new Email($data['emailAddress']), + $data['firstName'], + $data['middleName'] ?? null, + $data['lastName'], + new Phone($data['phoneNumber'], $data['phoneType']), + $ssn ? new SocialSecurityNumber($ssn) : null, + $dob ? new Date($dob) : null, + Address::fromArray($data['homeAddress']), + boolval($data['acceptedTermsAndConditions']), + ); + + $person->setPersonId($data['personId'] ?? null); + + return $person; + } + + public function __construct( #[Assert\NotBlank] private Email $emailAddress, @@ -202,28 +226,4 @@ public function toArray(): array return $array; } - - - /** @param array $data */ - public static function fromArray(array $data): self - { - $ssn = $data['socialSecurityNumber'] ?? null; - $dob = $data['dateOfBirth'] ?? null; - - $person = new self( - new Email($data['emailAddress']), - $data['firstName'], - $data['middleName'] ?? null, - $data['lastName'], - new Phone($data['phoneNumber'], $data['phoneType']), - $ssn ? new SocialSecurityNumber($ssn) : null, - $dob ? new Date($dob) : null, - Address::fromArray($data['homeAddress']), - boolval($data['acceptedTermsAndConditions']), - ); - - $person->setPersonId($data['personId'] ?? null); - - return $person; - } } diff --git a/src/Model/Property.php b/src/Model/Property.php index 091c41e..aef1b89 100644 --- a/src/Model/Property.php +++ b/src/Model/Property.php @@ -19,6 +19,26 @@ class Property private bool $isActive = true; + /** @param array $data */ + public static function fromArray(array $data): self + { + $property = new self( + $data['propertyName'], + new Money((string)$data['rent']), + new Money((string)$data['deposit']), + Address::fromArray($data), + boolval($data['bankruptcyCheck']), + $data['bankruptcyTimeFrame'], + $data['incomeToRentRatio'], + ); + + $property->setPropertyId($data['propertyId'] ?? null); + $property->setIsActive(boolval($data['isActive'])); + + return $property; + } + + /** * Constructor * @@ -177,24 +197,4 @@ public function toArray(): array return $array; } - - - /** @param array $data */ - public static function fromArray(array $data): self - { - $property = new self( - $data['propertyName'], - new Money((string)$data['rent']), - new Money((string)$data['deposit']), - Address::fromArray($data), - boolval($data['bankruptcyCheck']), - $data['bankruptcyTimeFrame'], - $data['incomeToRentRatio'], - ); - - $property->setPropertyId($data['propertyId'] ?? null); - $property->setIsActive(boolval($data['isActive'])); - - return $property; - } } diff --git a/src/Model/Renter.php b/src/Model/Renter.php index b8562f4..39a5501 100644 --- a/src/Model/Renter.php +++ b/src/Model/Renter.php @@ -15,6 +15,28 @@ class Renter use Validate; + /** @param array $data */ + public static function fromArray(array $data): self + { + $msex = $data['multiShareExpirationDate'] ?? null; + + $renter = new self( + Person::fromArray($data), + new Money((string)$data['income']), + $data['incomeFrequency'], + new Money((string)$data['otherIncome']), + $data['otherIncomeFrequency'], + new Money((string)$data['assets']), + EmploymentStatus::from($data['employmentStatus']), + $msex ? new Date($msex) : null, + ); + + $renter->setRenterId($data['renterId'] ?? null); + + return $renter; + } + + public function __construct( #[Assert\NotBlank] private Person $person, @@ -166,26 +188,4 @@ public function toArray(): array return $array; } - - - /** @param array $data */ - public static function fromArray(array $data): self - { - $msex = $data['multiShareExpirationDate'] ?? null; - - $renter = new self( - Person::fromArray($data), - new Money((string)$data['income']), - $data['incomeFrequency'], - new Money((string)$data['otherIncome']), - $data['otherIncomeFrequency'], - new Money((string)$data['assets']), - EmploymentStatus::from($data['employmentStatus']), - $msex ? new Date($msex) : null, - ); - - $renter->setRenterId($data['renterId'] ?? null); - - return $renter; - } } diff --git a/src/Model/RenterRole.php b/src/Model/RenterRole.php index eeeac9c..340cbdd 100644 --- a/src/Model/RenterRole.php +++ b/src/Model/RenterRole.php @@ -8,8 +8,10 @@ enum RenterRole: string { + use From; + case Applicant = 'Applicant'; case CoSigner = 'CoSigner'; } diff --git a/src/Model/RenterStatus.php b/src/Model/RenterStatus.php index b0f9509..eeb088f 100644 --- a/src/Model/RenterStatus.php +++ b/src/Model/RenterStatus.php @@ -8,8 +8,10 @@ enum RenterStatus: string { + use From; + case IdentityVerificationPending = 'IdentityVerificationPending'; case ScreeningRequestCanceled = 'ScreeningRequestCanceled'; case ReadyForReportRequest = 'ReadyForReportRequest'; diff --git a/src/Model/ReportType.php b/src/Model/ReportType.php index 7a1f4d8..405e4ac 100644 --- a/src/Model/ReportType.php +++ b/src/Model/ReportType.php @@ -6,6 +6,7 @@ enum ReportType: string { + case Html = 'html'; case Json = 'json'; } diff --git a/src/Model/Reports.php b/src/Model/Reports.php index 93ac46c..ab410b4 100644 --- a/src/Model/Reports.php +++ b/src/Model/Reports.php @@ -14,6 +14,22 @@ class Reports private array $reports = []; + /** @param array $data */ + public static function fromArray(array $data): self + { + $reports = new self($data['reportsExpireNumberOfDays']); + + foreach ($data['reportResponseModelDetails'] as $reportInfo) { + $reports->addReport(new Report( + $reportInfo['providerName'], + $reportInfo['reportData'], + )); + } + + return $reports; + } + + public function __construct(private int $reportsExpireNumberOfDays) {} @@ -37,20 +53,4 @@ public function getReportsExpireNumberOfDays(): int { return $this->reportsExpireNumberOfDays; } - - - /** @param array $data */ - public static function fromArray(array $data): self - { - $reports = new self($data['reportsExpireNumberOfDays']); - - foreach ($data['reportResponseModelDetails'] as $reportInfo) { - $reports->addReport(new Report( - $reportInfo['providerName'], - $reportInfo['reportData'], - )); - } - - return $reports; - } } diff --git a/src/Model/RequestedProduct.php b/src/Model/RequestedProduct.php index 5b0dc39..bffe2d1 100644 --- a/src/Model/RequestedProduct.php +++ b/src/Model/RequestedProduct.php @@ -8,8 +8,10 @@ enum RequestedProduct: string { + use From; + case All = 'all'; case None = 'none'; diff --git a/src/Model/ScreeningRequest.php b/src/Model/ScreeningRequest.php index 5e9a732..284e640 100644 --- a/src/Model/ScreeningRequest.php +++ b/src/Model/ScreeningRequest.php @@ -21,6 +21,29 @@ class ScreeningRequest private array $screeningRequestRenters = []; + /** @param array $data */ + public static function fromArray(array $data): self + { + $request = new self( + $data['landlordId'], + $data['propertyId'], + $data['initialBundleId'], + $data['createdOn'] ? new Date(substr($data['createdOn'], 0, 10)) : null, + $data['modifiedOn'] ? new Date(substr($data['modifiedOn'], 0, 10)) : null, + $data['propertyName'] ?? null, + $data['propertySummaryAddress'] ?? null, + ); + + $request->setScreeningRequestId($data['screeningRequestId'] ?? null); + + foreach ($data['screeningRequestRenters'] as $renterInfo) { + $request->addScreeningRequestRenter(ScreeningRequestRenter::fromArray($renterInfo)); + } + + return $request; + } + + public function __construct( #[Assert\NotBlank] private readonly int $landlordId, @@ -179,27 +202,4 @@ public function toArray(): array return $array; } - - - /** @param array $data */ - public static function fromArray(array $data): self - { - $request = new self( - $data['landlordId'], - $data['propertyId'], - $data['initialBundleId'], - $data['createdOn'] ? new Date(substr($data['createdOn'], 0, 10)) : null, - $data['modifiedOn'] ? new Date(substr($data['modifiedOn'], 0, 10)) : null, - $data['propertyName'] ?? null, - $data['propertySummaryAddress'] ?? null, - ); - - $request->setScreeningRequestId($data['screeningRequestId'] ?? null); - - foreach ($data['screeningRequestRenters'] as $renterInfo) { - $request->addScreeningRequestRenter(ScreeningRequestRenter::fromArray($renterInfo)); - } - - return $request; - } } diff --git a/src/Model/ScreeningRequestRenter.php b/src/Model/ScreeningRequestRenter.php index c50b892..e1ffe35 100644 --- a/src/Model/ScreeningRequestRenter.php +++ b/src/Model/ScreeningRequestRenter.php @@ -18,6 +18,29 @@ class ScreeningRequestRenter private ?int $screeningRequestRenterId = null; + /** @param array $data */ + public static function fromArray(array $data): self + { + $renter = new self( + $data['landlordId'], + $data['renterId'], + $data['bundleId'], + RenterRole::from($data['renterRole']), + RenterStatus::from($data['renterStatus']), + $data['createdOn'] ? new Date(substr($data['createdOn'], 0, 10)) : null, + $data['modifiedOn'] ? new Date(substr($data['modifiedOn'], 0, 10)) : null, + $data['renterFirstName'] ?? null, + $data['renterLastName'] ?? null, + $data['renterMiddleName'] ?? null, + $data['reportsExpireNumberOfDays'] ?? null, + ); + + $renter->setScreeningRequestRenterId($data['screeningRequestRenterId'] ?? null); + + return $renter; + } + + public function __construct( #[Assert\NotBlank] private int $landlordId, @@ -212,27 +235,4 @@ public function toArray(): array return $array; } - - - /** @param array $data */ - public static function fromArray(array $data): self - { - $renter = new self( - $data['landlordId'], - $data['renterId'], - $data['bundleId'], - RenterRole::from($data['renterRole']), - RenterStatus::from($data['renterStatus']), - $data['createdOn'] ? new Date(substr($data['createdOn'], 0, 10)) : null, - $data['modifiedOn'] ? new Date(substr($data['modifiedOn'], 0, 10)) : null, - $data['renterFirstName'] ?? null, - $data['renterLastName'] ?? null, - $data['renterMiddleName'] ?? null, - $data['reportsExpireNumberOfDays'] ?? null, - ); - - $renter->setScreeningRequestRenterId($data['screeningRequestRenterId'] ?? null); - - return $renter; - } } diff --git a/test/Integration/IntegrationTest.php b/test/Integration/IntegrationTest.php index c97359a..301541d 100644 --- a/test/Integration/IntegrationTest.php +++ b/test/Integration/IntegrationTest.php @@ -482,20 +482,6 @@ public function testGetScreeningRequestsForRenter(ScreeningRequestRenter $screen } - #[Depends('testAddRenterToScreeningRequest')] - public function testCancelScreeningRequestForRenter( - ScreeningRequestRenter $screeningRequestRenter, - ): void - { - self::$client->cancelScreeningRequestForRenter($screeningRequestRenter->getScreeningRequestRenterId()); - - // We don't need to assert anything because if the request - // fails an exception is thrown. But we have to assert something - // to disable phpunit warning. - $this->assertTrue(true); - } - - #[Depends('testUpdateRenter')] #[Depends('testAddRenterToScreeningRequest')] public function testCreateExam(Renter $renter, ScreeningRequestRenter $screeningRequestRenter): Exam @@ -547,10 +533,11 @@ public function testAnswerExam( #[Depends('testUpdateRenter')] #[Depends('testAddRenterToScreeningRequest')] #[Depends('testAnswerExam')] + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter public function testValidateRenterForScreeningRequest( Renter $renter, ScreeningRequestRenter $screeningRequestRenter, - Exam $exam, + Exam $exam, // required by PHPUnit #[Depends] chain even though unused here ): string { $status = self::$client->validateRenterForScreeningRequest( @@ -568,10 +555,11 @@ public function testValidateRenterForScreeningRequest( #[Depends('testUpdateRenter')] #[Depends('testAddRenterToScreeningRequest')] #[Depends('testValidateRenterForScreeningRequest')] + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter public function testCreateReport( Renter $renter, ScreeningRequestRenter $screeningRequestRenter, - string $validationResult, + string $validationResult, // required by PHPUnit #[Depends] chain even though unused here ): int { self::$client->createReport($screeningRequestRenter->getScreeningRequestRenterId(), $renter); @@ -596,7 +584,7 @@ public function testGetReportsForLandlord( // Normally we execute this only after receiving a notification from the service // But we can just sleep until they should be finished - while ((time() - $reportRequestTime) < 60) { + while (time() - $reportRequestTime < 60) { sleep(1); } @@ -634,7 +622,7 @@ public function testGetReportsForRenter( // Normally we execute this only after receiving a notification from the service // But we can just sleep until they should be finished - while ((time() - $reportRequestTime) < 60) { + while (time() - $reportRequestTime < 60) { sleep(1); } @@ -659,4 +647,67 @@ public function testGetReportsForRenter( $this->assertGreaterThan(1_024, strlen($report->getReportData())); } } + + + /** + * Cancellation can't reuse the main screening request: by the time the report + * chain finishes, TU treats it as terminal and rejects cancel with + * `ScreeningRequestCannotCancel`. And running before reports breaks + * testCreateExam with `IDVNotPossible`. So this test stands up its own + * fresh, never-completed screening request, adds the renter to it, and + * cancels that one. + */ + #[Depends('testUpdateLandlord')] + #[Depends('testUpdateProperty')] + #[Depends('testUpdateRenter')] + #[Depends('testGetReportsForRenter')] + public function testCancelScreeningRequestForRenter( + Landlord $landlord, + Property $property, + Renter $renter, + ): void + { + $attestationGroup = self::$client->getAttestationsForProperty( + $landlord->getLandlordId(), + $property->getPropertyId(), + ); + + foreach ($attestationGroup->getAttestations() as $attestation) { + $attestationGroup->addAttestationResponse($attestation->getAttestationId(), true); + } + + $request = new ScreeningRequest( + landlordId: $landlord->getLandlordId(), + propertyId: $property->getPropertyId(), + initialBundleId: 1_004, + attestationGroup: $attestationGroup, + ); + + self::$client->createScreeningRequest($request); + + $screeningRequestRenter = new ScreeningRequestRenter( + $landlord->getLandlordId(), + $renter->getRenterId(), + 1_004, + RenterRole::Applicant, + null, + null, + null, + $renter->getPerson()->getFirstName(), + $renter->getPerson()->getLastName(), + $renter->getPerson()->getMiddleName(), + ); + + self::$client->addRenterToScreeningRequest( + $request->getScreeningRequestId(), + $screeningRequestRenter, + ); + + self::$client->cancelScreeningRequestForRenter( + $screeningRequestRenter->getScreeningRequestRenterId(), + ); + + // No assertion needed; an exception would be thrown on failure. + $this->assertTrue(true); + } } diff --git a/test/Unit/Model/AddressTest.php b/test/Unit/Model/AddressTest.php index ae6d2d3..091d9ae 100644 --- a/test/Unit/Model/AddressTest.php +++ b/test/Unit/Model/AddressTest.php @@ -12,6 +12,70 @@ class AddressTest extends TestCase { + private const INVALID_CHARS_MESSAGE = 'Address field must only contain letters, numbers, spaces, hashes, ' + . 'parentheses, ampersands, commas, periods, single quotes, hyphens, underscores, pluses, tildes, ' + . 'forward slashes and asterisks.'; + + + /** + * @return array> + */ + public static function validationProvider(): array + { + $tooLongAddress = 'omNScSZL7pBjZYmgGPcFmGbGVs9sJf110IeSSeh5bPSXgb0YkfX'; + $tooLongLocality = 'too-long-locality-value-here'; + + return [ + // addressLine1 missing + [ + ['', '', '', '', 'City', 'FL', '12345'], + 'addressLine1', + 'This value is too short. It should have 1 character or more.', + ], + // addressLine1 too long + [ + [$tooLongAddress, '', '', '', 'City', 'FL', '12345'], + 'addressLine1', + 'This value is too long. It should have 50 characters or less.', + ], + // locality missing + [ + ['Street 1', '', '', '', '', 'FL', '12345'], + 'locality', + 'This value is too short. It should have 2 characters or more.', + ], + // locality too long + [ + ['Street 1', '', '', '', $tooLongLocality, 'FL', '12345'], + 'locality', + 'This value is too long. It should have 27 characters or less.', + ], + // invalid state + [ + ['Street 1', '', '', '', 'City', 'XX', '12345'], + 'region', + 'The value is not a valid US state.', + ], + // invalid characters + [ + ['Invalid character "', '', '', '', 'St Pete', 'FL', '12345'], + 'region', + self::INVALID_CHARS_MESSAGE, + ], + [ + ['Invalid character $', '', '', '', 'St Pete', 'FL', '12345'], + 'region', + self::INVALID_CHARS_MESSAGE, + ], + [ + ['Invalid character @', '', '', '', 'St Pete', 'FL', '12345'], + 'region', + self::INVALID_CHARS_MESSAGE, + ], + ]; + } + + public function testConstructorAndGetters(): void { $addr = new Address( @@ -46,30 +110,6 @@ public function testConstructorAndGetters(): void } - /** - * @return array> - */ - public static function validationProvider(): array - { - return [ - // addressLine1 missing - [ ['', '', '', '', 'City', 'FL', '12345'], 'addressLine1', 'This value is too short. It should have 1 character or more.' ], - // addressLine1 too long - [ ['omNScSZL7pBjZYmgGPcFmGbGVs9sJf110IeSSeh5bPSXgb0YkfX', '', '', '', 'City', 'FL', '12345'], 'addressLine1', 'This value is too long. It should have 50 characters or less.' ], - // locality missing - [ ['Street 1', '', '', '', '', 'FL', '12345'], 'locality', 'This value is too short. It should have 2 characters or more.' ], - // locality too long - [ ['Street 1', '', '', '', 'too-long-locality-value-here', 'FL', '12345'], 'locality', 'This value is too long. It should have 27 characters or less.' ], - // invalid state - [ ['Street 1', '', '', '', 'City', 'XX', '12345'], 'region', 'The value is not a valid US state.' ], - // invalid characters - [ ['Invalid character "', '', '', '', 'St Pete', 'FL', '12345'], 'region', 'Address field must only contain letters, numbers, spaces, hashes, parentheses, ampersands, commas, periods, single quotes, hyphens, underscores, pluses, tildes, forward slashes and asterisks.' ], - [ ['Invalid character $', '', '', '', 'St Pete', 'FL', '12345'], 'region', 'Address field must only contain letters, numbers, spaces, hashes, parentheses, ampersands, commas, periods, single quotes, hyphens, underscores, pluses, tildes, forward slashes and asterisks.' ], - [ ['Invalid character @', '', '', '', 'St Pete', 'FL', '12345'], 'region', 'Address field must only contain letters, numbers, spaces, hashes, parentheses, ampersands, commas, periods, single quotes, hyphens, underscores, pluses, tildes, forward slashes and asterisks.' ], - ]; - } - - /** * @param string[] $values */ diff --git a/test/Unit/Model/DateTest.php b/test/Unit/Model/DateTest.php index cb28399..ef4e98f 100644 --- a/test/Unit/Model/DateTest.php +++ b/test/Unit/Model/DateTest.php @@ -12,14 +12,6 @@ class DateTest extends TestCase { - public function testConstructorAndGetters(): void - { - $date = new Date('2022-01-01'); - - $this->assertSame('2022-01-01', $date->getValue()); - } - - /** * @return array> */ @@ -33,6 +25,14 @@ public static function validationProvider(): array } + public function testConstructorAndGetters(): void + { + $date = new Date('2022-01-01'); + + $this->assertSame('2022-01-01', $date->getValue()); + } + + #[DataProvider('validationProvider')] public function testValidationErrors(string $value): void { diff --git a/test/Unit/Model/EmailTest.php b/test/Unit/Model/EmailTest.php index cabada4..774f00b 100644 --- a/test/Unit/Model/EmailTest.php +++ b/test/Unit/Model/EmailTest.php @@ -12,14 +12,6 @@ class EmailTest extends TestCase { - public function testConstructorAndGetters(): void - { - $email = new Email('test@example.com'); - - $this->assertSame('test@example.com', $email->getValue()); - } - - /** * @return array> */ @@ -32,6 +24,14 @@ public static function validationProvider(): array } + public function testConstructorAndGetters(): void + { + $email = new Email('test@example.com'); + + $this->assertSame('test@example.com', $email->getValue()); + } + + #[DataProvider('validationProvider')] public function testValidationErrors(string $address, string $errorMessage): void { diff --git a/test/Unit/Model/LandlordTest.php b/test/Unit/Model/LandlordTest.php index e72f817..2edb20a 100644 --- a/test/Unit/Model/LandlordTest.php +++ b/test/Unit/Model/LandlordTest.php @@ -41,6 +41,22 @@ protected function makeObject( } + /** + * @return array> + */ + public static function validationProvider(): array + { + return [ + // No first name + [ '', 'Last', 'Business', true, 'This value should not be blank.' ], + // No last name + [ 'First', '', 'Business', true, 'This value should not be blank.' ], + // Not accepted terms + [ 'First', 'Last', 'Business', false, 'Terms and conditions need to be accepted.' ], + ]; + } + + public function testConstructorAndGetters(): void { $landlord = $this->makeObject('First', 'Last', 'Business', true); @@ -77,22 +93,6 @@ public function testConstructorAndGetters(): void } - /** - * @return array> - */ - public static function validationProvider(): array - { - return [ - // No first name - [ '', 'Last', 'Business', true, 'This value should not be blank.' ], - // No last name - [ 'First', '', 'Business', true, 'This value should not be blank.' ], - // Not accepted terms - [ 'First', 'Last', 'Business', false, 'Terms and conditions need to be accepted.' ], - ]; - } - - #[DataProvider('validationProvider')] public function testValidationErrors( string $firstName, diff --git a/test/Unit/Model/MoneyTest.php b/test/Unit/Model/MoneyTest.php index 559d370..e390307 100644 --- a/test/Unit/Model/MoneyTest.php +++ b/test/Unit/Model/MoneyTest.php @@ -27,15 +27,6 @@ public static function validProvider(): array } - #[DataProvider('validProvider')] - public function testValidValues(string $value): void - { - $money = new Money($value); - - $this->assertSame($value, $money->getValue()); - } - - /** @return array> */ public static function invalidProvider(): array { @@ -50,6 +41,15 @@ public static function invalidProvider(): array } + #[DataProvider('validProvider')] + public function testValidValues(string $value): void + { + $money = new Money($value); + + $this->assertSame($value, $money->getValue()); + } + + #[DataProvider('invalidProvider')] public function testInvalidValues(string $value): void { diff --git a/test/Unit/Model/PersonTest.php b/test/Unit/Model/PersonTest.php index 43aa843..5fb9850 100644 --- a/test/Unit/Model/PersonTest.php +++ b/test/Unit/Model/PersonTest.php @@ -47,6 +47,20 @@ protected function makeObject( } + /** @return array> */ + public static function validationProvider(): array + { + return [ + // No first name + [ '', 'Middle', 'Last', true, 'This value should not be blank.' ], + // No last name + [ 'First', 'Middle', '', true, 'This value should not be blank.' ], + // Not accepted terms + [ 'First', 'Middle', 'Last', false, 'Terms and conditions need to be accepted.' ], + ]; + } + + public function testConstructorAndGetters(): void { $person = $this->makeObject('First', 'Middle', 'Last', true); @@ -87,20 +101,6 @@ public function testConstructorAndGetters(): void } - /** @return array> */ - public static function validationProvider(): array - { - return [ - // No first name - [ '', 'Middle', 'Last', true, 'This value should not be blank.' ], - // No last name - [ 'First', 'Middle', '', true, 'This value should not be blank.' ], - // Not accepted terms - [ 'First', 'Middle', 'Last', false, 'Terms and conditions need to be accepted.' ], - ]; - } - - #[DataProvider('validationProvider')] public function testValidationErrors( string $firstName, diff --git a/test/Unit/Model/PhoneTest.php b/test/Unit/Model/PhoneTest.php index 47d38f7..499ea23 100644 --- a/test/Unit/Model/PhoneTest.php +++ b/test/Unit/Model/PhoneTest.php @@ -12,20 +12,6 @@ class PhoneTest extends TestCase { - public function testConstructorAndGetters(): void - { - $phone = new Phone('0123456789', 'Home'); - - $this->assertSame('0123456789', $phone->getNumber()); - $this->assertSame('Home', $phone->getType()); - - $this->assertSame([ - 'phoneNumber' => '0123456789', - 'phoneType' => 'Home', - ], $phone->toArray()); - } - - /** @return array> */ public static function validationProvider(): array { @@ -41,6 +27,20 @@ public static function validationProvider(): array } + public function testConstructorAndGetters(): void + { + $phone = new Phone('0123456789', 'Home'); + + $this->assertSame('0123456789', $phone->getNumber()); + $this->assertSame('Home', $phone->getType()); + + $this->assertSame([ + 'phoneNumber' => '0123456789', + 'phoneType' => 'Home', + ], $phone->toArray()); + } + + #[DataProvider('validationProvider')] public function testValidationErrors(string $phone, string $type, string $errorMessage): void { diff --git a/test/Unit/Model/PropertyTest.php b/test/Unit/Model/PropertyTest.php index 4d24835..d9206c6 100644 --- a/test/Unit/Model/PropertyTest.php +++ b/test/Unit/Model/PropertyTest.php @@ -40,6 +40,21 @@ protected function makeObject( } + /** @return array> */ + public static function validationProvider(): array + { + return [ + // No name + [ '', 'This value should not be blank.' ], + // Too long name + [ + 'C1bj6YXjJbGyARqKRljur7mXTUWe1uyWMqECWdCICEWDUv169qU66CT4gztMc9AiRWelsynyT1jMnPsuCz9MfErN9S3XigeDIbJZn', + 'This value is too long. It should have 100 characters or less.', + ], + ]; + } + + public function testConstructorAndGetters(): void { $property = $this->makeObject('Apartment', true, 20, 90); @@ -75,21 +90,6 @@ public function testConstructorAndGetters(): void } - /** @return array> */ - public static function validationProvider(): array - { - return [ - // No name - [ '', 'This value should not be blank.' ], - // Too long name - [ - 'C1bj6YXjJbGyARqKRljur7mXTUWe1uyWMqECWdCICEWDUv169qU66CT4gztMc9AiRWelsynyT1jMnPsuCz9MfErN9S3XigeDIbJZn', - 'This value is too long. It should have 100 characters or less.', - ], - ]; - } - - #[DataProvider('validationProvider')] public function testValidationErrors(string $propertyName, string $errorMessage): void { diff --git a/test/Unit/Model/RenterTest.php b/test/Unit/Model/RenterTest.php index 8e7b101..f1b807a 100644 --- a/test/Unit/Model/RenterTest.php +++ b/test/Unit/Model/RenterTest.php @@ -65,6 +65,36 @@ protected function makeObject( } + /** @return array[] */ + public static function validationProvider(): array + { + return [ + // Invalid income frequency + [ + '1000', + 'Invalid', + '5000', + 'PerYear', + '12000', + EmploymentStatus::SelfEmployed, + null, + 'The value you selected is not a valid choice.', + 'incomeFrequency',], + // Invalid other income frequency + [ + '1000', + 'PerMonth', + '5000', + 'Invalid', + '12000', + EmploymentStatus::SelfEmployed, + null, + 'The value you selected is not a valid choice.', + 'otherIncomeFrequency',], + ]; + } + + public function testConstructorAndGetters(): void { $renter = $this->makeObject( @@ -119,20 +149,6 @@ public function testConstructorAndGetters(): void } - /** @return array[] */ - public static function validationProvider(): array - { - return [ - // Invalid income frequency - ['1000', 'Invalid', '5000', 'PerYear', '12000', EmploymentStatus::SelfEmployed, null, - 'The value you selected is not a valid choice.', 'incomeFrequency'], - // Invalid other income frequency - ['1000', 'PerMonth', '5000', 'Invalid', '12000', EmploymentStatus::SelfEmployed, null, - 'The value you selected is not a valid choice.', 'otherIncomeFrequency'], - ]; - } - - #[DataProvider('validationProvider')] public function testValidationErrors( string $income, diff --git a/test/Unit/Model/SocialSecurityNumberTest.php b/test/Unit/Model/SocialSecurityNumberTest.php index bbb2be6..2b3756a 100644 --- a/test/Unit/Model/SocialSecurityNumberTest.php +++ b/test/Unit/Model/SocialSecurityNumberTest.php @@ -12,14 +12,6 @@ class SocialSecurityNumberTest extends TestCase { - public function testConstructorAndGetters(): void - { - $ssn = new SocialSecurityNumber('123456789'); - - $this->assertSame('123456789', $ssn->getValue()); - } - - /** @return array>*/ public static function validationProvider(): array { @@ -33,6 +25,14 @@ public static function validationProvider(): array } + public function testConstructorAndGetters(): void + { + $ssn = new SocialSecurityNumber('123456789'); + + $this->assertSame('123456789', $ssn->getValue()); + } + + #[DataProvider('validationProvider')] public function testValidationErrors(string $ssn, string $errorMessage): void { diff --git a/test/Unit/WebhookParserTest.php b/test/Unit/WebhookParserTest.php index b3b6b79..5638a70 100644 --- a/test/Unit/WebhookParserTest.php +++ b/test/Unit/WebhookParserTest.php @@ -14,6 +14,22 @@ class WebhookParserTest extends TestCase { + protected function createMockRequest(string $data): RequestInterface + { + $stream = $this->createStub(StreamInterface::class); + + $stream->method('getContents') + ->willReturn($data); + + $request = $this->createStub(RequestInterface::class); + + $request->method('getBody') + ->willReturn($stream); + + return $request; + } + + public function testParseReportDelivery(): void { $data = json_encode([ @@ -46,20 +62,4 @@ public function testParseAuthenticationStatus(): void $this->assertSame(12_345, $result->getScreeningRequestRenterId()); $this->assertSame('Passed', $result->getManualAuthenticationStatus()); } - - - protected function createMockRequest(string $data): RequestInterface - { - $stream = $this->createStub(StreamInterface::class); - - $stream->method('getContents') - ->willReturn($data); - - $request = $this->createStub(RequestInterface::class); - - $request->method('getBody') - ->willReturn($stream); - - return $request; - } }