From 275d589f8f0ba3e6080df9313c320f61d419e0af Mon Sep 17 00:00:00 2001 From: Ryan Yannelli Date: Thu, 1 Jan 2026 00:46:46 -0500 Subject: [PATCH] fix: Add phpunit.xml.dist and fix Pest test configuration (#2) - Add phpunit.xml.dist to provide proper PHPUnit/Pest configuration and avoid argument parsing issues with --cache-directory option - Fix ResponseRequest validation to use strict empty string check instead of empty() which incorrectly treats '0' as empty - Include tests and CI workflow from add-pest-tests-workflow branch --- .github/workflows/tests.yml | 49 ++++ composer.json | 25 +- phpunit.xml.dist | 18 ++ src/Requests/ResponseRequest.php | 2 +- tests/Pest.php | 36 +++ tests/TestCase.php | 10 + tests/Unit/ClientTest.php | 117 ++++++++ tests/Unit/ConfigTest.php | 17 ++ tests/Unit/DTO/ClaimAppealDTOTest.php | 143 ++++++++++ tests/Unit/DTO/ERADTOTest.php | 193 ++++++++++++++ tests/Unit/DTO/EligibilityDTOTest.php | 236 +++++++++++++++++ tests/Unit/DTO/ProviderEnrollmentDTOTest.php | 249 ++++++++++++++++++ tests/Unit/Requests/ClaimRequestTest.php | 183 +++++++++++++ tests/Unit/Requests/ERARequestTest.php | 122 +++++++++ .../Unit/Requests/EligibilityRequestTest.php | 82 ++++++ tests/Unit/Requests/FileRequestTest.php | 87 ++++++ tests/Unit/Requests/PayerRequestTest.php | 65 +++++ tests/Unit/Requests/ProviderRequestTest.php | 93 +++++++ tests/Unit/Requests/ResponseRequestTest.php | 106 ++++++++ 19 files changed, 1827 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/tests.yml create mode 100644 phpunit.xml.dist create mode 100644 tests/Pest.php create mode 100644 tests/TestCase.php create mode 100644 tests/Unit/ClientTest.php create mode 100644 tests/Unit/ConfigTest.php create mode 100644 tests/Unit/DTO/ClaimAppealDTOTest.php create mode 100644 tests/Unit/DTO/ERADTOTest.php create mode 100644 tests/Unit/DTO/EligibilityDTOTest.php create mode 100644 tests/Unit/DTO/ProviderEnrollmentDTOTest.php create mode 100644 tests/Unit/Requests/ClaimRequestTest.php create mode 100644 tests/Unit/Requests/ERARequestTest.php create mode 100644 tests/Unit/Requests/EligibilityRequestTest.php create mode 100644 tests/Unit/Requests/FileRequestTest.php create mode 100644 tests/Unit/Requests/PayerRequestTest.php create mode 100644 tests/Unit/Requests/ProviderRequestTest.php create mode 100644 tests/Unit/Requests/ResponseRequestTest.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..5ce4033 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,49 @@ +name: Tests + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +jobs: + tests: + runs-on: ubuntu-latest + + strategy: + fail-fast: true + matrix: + php: [8.3] + + name: PHP ${{ matrix.php }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: json, mbstring + coverage: none + + - name: Validate composer.json + run: composer validate --strict + + - name: Get Composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache Composer dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --prefer-dist --no-interaction --no-progress + + - name: Run tests + run: composer test diff --git a/composer.json b/composer.json index 9119b93..1187d64 100644 --- a/composer.json +++ b/composer.json @@ -3,28 +3,36 @@ "description": "An unofficial PHP wrapper for the official Claim.MD API.", "type": "library", "license": "MIT", - "version": "1.0.1", "require": { "php": ">=8.2", "guzzlehttp/guzzle": "^7.9", "ext-json": "*" }, + "require-dev": { + "mockery/mockery": "^1.6", + "pestphp/pest": "^3.0" + }, "autoload": { "psr-4": { "Nextvisit\\ClaimMDWrapper\\": "src/" } }, + "autoload-dev": { + "psr-4": { + "Nextvisit\\ClaimMDWrapper\\Tests\\": "tests/" + } + }, "authors": [ { "name": "Kyle Yannelli", - "github": "kyleyannelli", "email": "kyleyannelli@gmail.com", + "homepage": "https://github.com/kyleyannelli", "role": "Developer" }, { "name": "Ryan Yannelli", - "github": "yannelli", "email": "ryanyannelli@gmail.com", + "homepage": "https://github.com/yannelli", "role": "Developer" } ], @@ -40,7 +48,14 @@ "issues": "https://github.com/Nextvisit/claim-md-php/issues" }, "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "pestphp/pest-plugin": true + } + }, + "scripts": { + "test": "pest", + "test:coverage": "pest --coverage" }, "minimum-stability": "stable" -} \ No newline at end of file +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..d7b8544 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,18 @@ + + + + + tests/Unit + + + + + src + + + diff --git a/src/Requests/ResponseRequest.php b/src/Requests/ResponseRequest.php index 5c218ec..ff941b6 100644 --- a/src/Requests/ResponseRequest.php +++ b/src/Requests/ResponseRequest.php @@ -42,7 +42,7 @@ public function __construct(private readonly Client $client) {} */ public function fetchResponses(string $responseId, ?string $claimId = null): array { - if (empty($responseId)) { + if ($responseId === '') { throw new InvalidArgumentException('ResponseID cannot be empty'); } diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..aa7eb4a --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,36 @@ +extend(Nextvisit\ClaimMDWrapper\Tests\TestCase::class)->in('Unit'); + +/* +|-------------------------------------------------------------------------- +| Expectations +|-------------------------------------------------------------------------- +| +| When you're writing tests, you often need to check that values meet certain conditions. The +| "expect()" function gives you access to a set of "expectations" methods that you can use +| to assert different things. Of course, you may extend the Expectation API at any time. +| +*/ + +/* +|-------------------------------------------------------------------------- +| Functions +|-------------------------------------------------------------------------- +| +| While Pest is very powerful out-of-the-box, you may have some testing code specific to your +| project that you don't want to repeat in every file. Here you can also expose helpers as +| global functions to help you to reduce the number of lines of code in your test files. +| +*/ diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..37d229b --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,10 @@ +toBeInstanceOf(Client::class); + }); + + it('creates a client with custom config', function () { + $config = new Config(); + $client = new Client('test-account-key', $config); + + expect($client)->toBeInstanceOf(Client::class); + }); + + it('sends a POST request with account key in form data', function () { + $container = []; + $history = Middleware::history($container); + + $mock = new MockHandler([ + new Response(200, [], json_encode(['status' => 'success'])), + ]); + + $handlerStack = HandlerStack::create($mock); + $handlerStack->push($history); + + $guzzleClient = new GuzzleClient(['handler' => $handlerStack]); + $client = new Client('test-account-key', new Config(), $guzzleClient); + + $result = $client->sendRequest('POST', '/test-endpoint', ['data' => 'value']); + + expect($result)->toBe(['status' => 'success']); + expect($container)->toHaveCount(1); + + $request = $container[0]['request']; + expect($request->getMethod())->toBe('POST'); + expect((string) $request->getUri())->toBe('/test-endpoint'); + + $body = (string) $request->getBody(); + expect($body)->toContain('AccountKey=test-account-key'); + expect($body)->toContain('data=value'); + }); + + it('sends a multipart request when isMultipart is true', function () { + $container = []; + $history = Middleware::history($container); + + $mock = new MockHandler([ + new Response(200, [], json_encode(['uploaded' => true])), + ]); + + $handlerStack = HandlerStack::create($mock); + $handlerStack->push($history); + + $guzzleClient = new GuzzleClient(['handler' => $handlerStack]); + $client = new Client('test-account-key', new Config(), $guzzleClient); + + $result = $client->sendRequest('POST', '/upload', ['file' => 'content'], true); + + expect($result)->toBe(['uploaded' => true]); + + $request = $container[0]['request']; + $contentType = $request->getHeaderLine('Content-Type'); + expect($contentType)->toContain('multipart/form-data'); + }); + + it('includes additional headers when provided', function () { + $container = []; + $history = Middleware::history($container); + + $mock = new MockHandler([ + new Response(200, [], json_encode(['data' => 'test'])), + ]); + + $handlerStack = HandlerStack::create($mock); + $handlerStack->push($history); + + $guzzleClient = new GuzzleClient(['handler' => $handlerStack]); + $client = new Client('test-account-key', new Config(), $guzzleClient); + + $result = $client->sendRequest('POST', '/test', [], false, ['X-Custom-Header' => 'custom-value']); + + $request = $container[0]['request']; + expect($request->getHeaderLine('X-Custom-Header'))->toBe('custom-value'); + }); + + it('sends request without extra data', function () { + $container = []; + $history = Middleware::history($container); + + $mock = new MockHandler([ + new Response(200, [], json_encode(['status' => 'ok'])), + ]); + + $handlerStack = HandlerStack::create($mock); + $handlerStack->push($history); + + $guzzleClient = new GuzzleClient(['handler' => $handlerStack]); + $client = new Client('test-account-key', new Config(), $guzzleClient); + + $result = $client->sendRequest('POST', '/simple'); + + expect($result)->toBe(['status' => 'ok']); + + $body = (string) $container[0]['request']->getBody(); + expect($body)->toContain('AccountKey=test-account-key'); + }); +}); diff --git a/tests/Unit/ConfigTest.php b/tests/Unit/ConfigTest.php new file mode 100644 index 0000000..e04f8d0 --- /dev/null +++ b/tests/Unit/ConfigTest.php @@ -0,0 +1,17 @@ +getBaseUri())->toBe('https://svc.claim.md/'); + }); + + it('returns a string for base URI', function () { + $config = new Config(); + + expect($config->getBaseUri())->toBeString(); + }); +}); diff --git a/tests/Unit/DTO/ClaimAppealDTOTest.php b/tests/Unit/DTO/ClaimAppealDTOTest.php new file mode 100644 index 0000000..681549f --- /dev/null +++ b/tests/Unit/DTO/ClaimAppealDTOTest.php @@ -0,0 +1,143 @@ +claimId)->toBe('12345'); + }); + + it('creates a DTO with remoteClaimId', function () { + $dto = new ClaimAppealDTO(remoteClaimId: 'REMOTE-123'); + + expect($dto->remoteClaimId)->toBe('REMOTE-123'); + }); + + it('creates a DTO with all optional fields', function () { + $dto = new ClaimAppealDTO( + claimId: '12345', + contactName: 'John Doe', + contactTitle: 'Manager', + contactEmail: 'john@example.com', + contactPhone: '555-123-4567', + contactFax: '555-987-6543', + contactAddr1: '123 Main St', + contactAddr2: 'Suite 100', + contactCity: 'Anytown', + contactState: 'CA', + contactZip: '12345' + ); + + expect($dto->contactName)->toBe('John Doe'); + expect($dto->contactTitle)->toBe('Manager'); + expect($dto->contactEmail)->toBe('john@example.com'); + expect($dto->contactPhone)->toBe('555-123-4567'); + expect($dto->contactFax)->toBe('555-987-6543'); + expect($dto->contactAddr1)->toBe('123 Main St'); + expect($dto->contactAddr2)->toBe('Suite 100'); + expect($dto->contactCity)->toBe('Anytown'); + expect($dto->contactState)->toBe('CA'); + expect($dto->contactZip)->toBe('12345'); + }); + }); + + describe('validation', function () { + it('throws exception when neither claimId nor remoteClaimId is provided', function () { + new ClaimAppealDTO(); + })->throws(InvalidArgumentException::class, 'Either claimId or remoteClaimId must be provided.'); + + it('throws exception for invalid email', function () { + new ClaimAppealDTO(claimId: '12345', contactEmail: 'invalid-email'); + })->throws(InvalidArgumentException::class, 'contactEmail must be a valid email address.'); + + it('throws exception for invalid phone number', function () { + new ClaimAppealDTO(claimId: '12345', contactPhone: 'abc-invalid'); + })->throws(InvalidArgumentException::class, 'contactPhone must be a valid phone number.'); + + it('throws exception for invalid fax number', function () { + new ClaimAppealDTO(claimId: '12345', contactFax: 'invalid!fax'); + })->throws(InvalidArgumentException::class, 'contactFax must be a valid phone number.'); + + it('throws exception for invalid state code', function () { + new ClaimAppealDTO(claimId: '12345', contactState: 'California'); + })->throws(InvalidArgumentException::class, 'contactState must be a valid two-letter state code.'); + + it('throws exception for lowercase state code', function () { + new ClaimAppealDTO(claimId: '12345', contactState: 'ca'); + })->throws(InvalidArgumentException::class, 'contactState must be a valid two-letter state code.'); + + it('accepts valid phone formats', function () { + $dto = new ClaimAppealDTO( + claimId: '12345', + contactPhone: '+1 (555) 123-4567' + ); + + expect($dto->contactPhone)->toBe('+1 (555) 123-4567'); + }); + }); + + describe('toArray', function () { + it('converts to array with correct keys', function () { + $dto = new ClaimAppealDTO( + claimId: '12345', + remoteClaimId: 'REMOTE-123', + contactName: 'John Doe', + contactEmail: 'john@example.com' + ); + + $array = $dto->toArray(); + + expect($array)->toBe([ + 'claimid' => '12345', + 'remote_claimid' => 'REMOTE-123', + 'contact_name' => 'John Doe', + 'contact_email' => 'john@example.com', + ]); + }); + + it('filters out null values', function () { + $dto = new ClaimAppealDTO(claimId: '12345'); + + $array = $dto->toArray(); + + expect($array)->toBe(['claimid' => '12345']); + expect($array)->not->toHaveKey('remote_claimid'); + expect($array)->not->toHaveKey('contact_name'); + }); + }); + + describe('fromArray', function () { + it('creates a DTO from array', function () { + $data = [ + 'claimid' => '12345', + 'remote_claimid' => 'REMOTE-123', + 'contact_name' => 'John Doe', + 'contact_email' => 'john@example.com', + 'contact_phone' => '555-123-4567', + 'contact_state' => 'NY', + ]; + + $dto = ClaimAppealDTO::fromArray($data); + + expect($dto->claimId)->toBe('12345'); + expect($dto->remoteClaimId)->toBe('REMOTE-123'); + expect($dto->contactName)->toBe('John Doe'); + expect($dto->contactEmail)->toBe('john@example.com'); + expect($dto->contactPhone)->toBe('555-123-4567'); + expect($dto->contactState)->toBe('NY'); + }); + + it('handles missing optional fields', function () { + $data = ['claimid' => '12345']; + + $dto = ClaimAppealDTO::fromArray($data); + + expect($dto->claimId)->toBe('12345'); + expect($dto->contactName)->toBeNull(); + expect($dto->contactEmail)->toBeNull(); + }); + }); +}); diff --git a/tests/Unit/DTO/ERADTOTest.php b/tests/Unit/DTO/ERADTOTest.php new file mode 100644 index 0000000..b60b73a --- /dev/null +++ b/tests/Unit/DTO/ERADTOTest.php @@ -0,0 +1,193 @@ +checkDate)->toBeNull(); + expect($dto->receivedDate)->toBeNull(); + expect($dto->checkNumber)->toBeNull(); + }); + + it('creates a DTO with all optional fields', function () { + $dto = new ERADTO( + checkDate: '01-15-2024', + receivedDate: '01-16-2024', + receivedAfterDate: '01-10-2024', + checkNumber: 'CHK123', + checkAmount: '1500.00', + payerId: 'PAYER123', + npi: '1234567890', + taxId: '12-3456789', + newOnly: '1', + eraId: 'ERA123', + page: '1' + ); + + expect($dto->checkDate)->toBe('01-15-2024'); + expect($dto->receivedDate)->toBe('01-16-2024'); + expect($dto->receivedAfterDate)->toBe('01-10-2024'); + expect($dto->checkNumber)->toBe('CHK123'); + expect($dto->checkAmount)->toBe('1500.00'); + expect($dto->payerId)->toBe('PAYER123'); + expect($dto->npi)->toBe('1234567890'); + expect($dto->taxId)->toBe('12-3456789'); + expect($dto->newOnly)->toBe('1'); + expect($dto->eraId)->toBe('ERA123'); + expect($dto->page)->toBe('1'); + }); + }); + + describe('validation', function () { + it('throws exception for invalid checkDate format', function () { + new ERADTO(checkDate: '2024-01-15'); + })->throws(InvalidArgumentException::class, "checkDate must be in mm-dd-yyyy format or 'today'/'yesterday'"); + + it('throws exception for invalid receivedDate format', function () { + new ERADTO(receivedDate: '15-01-2024'); + })->throws(InvalidArgumentException::class, "receivedDate must be in mm-dd-yyyy format or 'today'/'yesterday'"); + + it('throws exception for invalid receivedAfterDate format', function () { + new ERADTO(receivedAfterDate: 'invalid-date'); + })->throws(InvalidArgumentException::class, "receivedAfterDate must be in mm-dd-yyyy format or 'today'/'yesterday'"); + + it('accepts today for receivedDate', function () { + $dto = new ERADTO(receivedDate: 'today'); + + expect($dto->receivedDate)->toBe('today'); + }); + + it('accepts yesterday for receivedDate', function () { + $dto = new ERADTO(receivedDate: 'yesterday'); + + expect($dto->receivedDate)->toBe('yesterday'); + }); + + it('accepts Today in mixed case for receivedDate', function () { + $dto = new ERADTO(receivedDate: 'Today'); + + expect($dto->receivedDate)->toBe('Today'); + }); + + it('accepts yesterday for receivedAfterDate', function () { + $dto = new ERADTO(receivedAfterDate: 'yesterday'); + + expect($dto->receivedAfterDate)->toBe('yesterday'); + }); + + it('throws exception for invalid newOnly value', function () { + new ERADTO(newOnly: '2'); + })->throws(InvalidArgumentException::class, "newOnly must be either '1' (true) or '0' (false)"); + + it('throws exception for non-string newOnly value', function () { + new ERADTO(newOnly: 'yes'); + })->throws(InvalidArgumentException::class, "newOnly must be either '1' (true) or '0' (false)"); + + it('accepts 0 for newOnly', function () { + $dto = new ERADTO(newOnly: '0'); + + expect($dto->newOnly)->toBe('0'); + }); + + it('accepts 1 for newOnly', function () { + $dto = new ERADTO(newOnly: '1'); + + expect($dto->newOnly)->toBe('1'); + }); + + it('accepts valid mm-dd-yyyy date format', function () { + $dto = new ERADTO(checkDate: '12-31-2024'); + + expect($dto->checkDate)->toBe('12-31-2024'); + }); + }); + + describe('toArray', function () { + it('converts to array with correct keys', function () { + $dto = new ERADTO( + checkDate: '01-15-2024', + checkNumber: 'CHK123', + payerId: 'PAYER123' + ); + + $array = $dto->toArray(); + + expect($array)->toBe([ + 'CheckDate' => '01-15-2024', + 'CheckNumber' => 'CHK123', + 'PayerID' => 'PAYER123', + ]); + }); + + it('returns empty array when no fields are set', function () { + $dto = new ERADTO(); + + $array = $dto->toArray(); + + expect($array)->toBe([]); + }); + + it('filters out null values', function () { + $dto = new ERADTO(eraId: 'ERA123'); + + $array = $dto->toArray(); + + expect($array)->toBe(['ERAID' => 'ERA123']); + expect($array)->not->toHaveKey('CheckDate'); + expect($array)->not->toHaveKey('CheckNumber'); + }); + }); + + describe('fromArray', function () { + it('creates a DTO from array', function () { + $data = [ + 'CheckDate' => '01-15-2024', + 'ReceivedDate' => 'today', + 'CheckNumber' => 'CHK123', + 'CheckAmount' => '1500.00', + 'PayerID' => 'PAYER123', + 'NPI' => '1234567890', + 'TaxID' => '12-3456789', + 'NewOnly' => '1', + 'ERAID' => 'ERA123', + 'Page' => '1', + ]; + + $dto = ERADTO::fromArray($data); + + expect($dto->checkDate)->toBe('01-15-2024'); + expect($dto->receivedDate)->toBe('today'); + expect($dto->checkNumber)->toBe('CHK123'); + expect($dto->checkAmount)->toBe('1500.00'); + expect($dto->payerId)->toBe('PAYER123'); + expect($dto->npi)->toBe('1234567890'); + expect($dto->taxId)->toBe('12-3456789'); + expect($dto->newOnly)->toBe('1'); + expect($dto->eraId)->toBe('ERA123'); + expect($dto->page)->toBe('1'); + }); + + it('handles empty array', function () { + $dto = ERADTO::fromArray([]); + + expect($dto->checkDate)->toBeNull(); + expect($dto->eraId)->toBeNull(); + }); + + it('handles partial data', function () { + $data = [ + 'PayerID' => 'PAYER123', + 'ERAID' => 'ERA456', + ]; + + $dto = ERADTO::fromArray($data); + + expect($dto->payerId)->toBe('PAYER123'); + expect($dto->eraId)->toBe('ERA456'); + expect($dto->checkDate)->toBeNull(); + }); + }); +}); diff --git a/tests/Unit/DTO/EligibilityDTOTest.php b/tests/Unit/DTO/EligibilityDTOTest.php new file mode 100644 index 0000000..034fb12 --- /dev/null +++ b/tests/Unit/DTO/EligibilityDTOTest.php @@ -0,0 +1,236 @@ +validData = [ + 'insLastName' => 'Doe', + 'insFirstName' => 'John', + 'payerId' => 'PAYER123', + 'patientRelationship' => '18', + 'serviceDate' => '20240115', + 'providerNpi' => '1234567890', + 'providerTaxId' => '12-3456789', + ]; + }); + + describe('construction', function () { + it('creates a DTO with required fields', function () { + $dto = new EligibilityDTO(...$this->validData); + + expect($dto->insLastName)->toBe('Doe'); + expect($dto->insFirstName)->toBe('John'); + expect($dto->payerId)->toBe('PAYER123'); + expect($dto->patientRelationship)->toBe('18'); + expect($dto->serviceDate)->toBe('20240115'); + expect($dto->providerNpi)->toBe('1234567890'); + expect($dto->providerTaxId)->toBe('12-3456789'); + }); + + it('creates a DTO with optional fields', function () { + $dto = new EligibilityDTO( + ...$this->validData, + insMiddleName: 'M', + insSex: 'M', + patLastName: 'Smith', + patFirstName: 'Jane', + patSex: 'F', + provTaxIdType: 'E' + ); + + expect($dto->insMiddleName)->toBe('M'); + expect($dto->insSex)->toBe('M'); + expect($dto->patLastName)->toBe('Smith'); + expect($dto->patFirstName)->toBe('Jane'); + expect($dto->patSex)->toBe('F'); + expect($dto->provTaxIdType)->toBe('E'); + }); + }); + + describe('validation', function () { + it('throws exception for missing required field', function () { + new EligibilityDTO( + insLastName: '', + insFirstName: 'John', + payerId: 'PAYER123', + patientRelationship: '18', + serviceDate: '20240115', + providerNpi: '1234567890', + providerTaxId: '12-3456789' + ); + })->throws(InvalidArgumentException::class, 'insLastName is required'); + + it('throws exception for invalid service date format', function () { + new EligibilityDTO( + insLastName: 'Doe', + insFirstName: 'John', + payerId: 'PAYER123', + patientRelationship: '18', + serviceDate: '2024-01-15', + providerNpi: '1234567890', + providerTaxId: '12-3456789' + ); + })->throws(InvalidArgumentException::class, 'serviceDate must be in yyyymmdd format'); + + it('throws exception for invalid patient relationship', function () { + new EligibilityDTO( + insLastName: 'Doe', + insFirstName: 'John', + payerId: 'PAYER123', + patientRelationship: 'XX', + serviceDate: '20240115', + providerNpi: '1234567890', + providerTaxId: '12-3456789' + ); + })->throws(InvalidArgumentException::class, "patientRelationship must be either '18' or 'G8'"); + + it('accepts G8 as patient relationship', function () { + $dto = new EligibilityDTO( + ...[...$this->validData, 'patientRelationship' => 'G8'] + ); + + expect($dto->patientRelationship)->toBe('G8'); + }); + + it('throws exception for invalid insSex', function () { + new EligibilityDTO( + ...$this->validData, + insSex: 'X' + ); + })->throws(InvalidArgumentException::class, "insSex must be either 'M' or 'F'"); + + it('throws exception for invalid patSex', function () { + new EligibilityDTO( + ...$this->validData, + patSex: 'Other' + ); + })->throws(InvalidArgumentException::class, "patSex must be either 'M' or 'F'"); + + it('throws exception for invalid provTaxIdType', function () { + new EligibilityDTO( + ...$this->validData, + provTaxIdType: 'X' + ); + })->throws(InvalidArgumentException::class, "provTaxIdType must be either 'E' or 'S'"); + + it('accepts E as provTaxIdType', function () { + $dto = new EligibilityDTO(...$this->validData, provTaxIdType: 'E'); + expect($dto->provTaxIdType)->toBe('E'); + }); + + it('accepts S as provTaxIdType', function () { + $dto = new EligibilityDTO(...$this->validData, provTaxIdType: 'S'); + expect($dto->provTaxIdType)->toBe('S'); + }); + + it('throws exception for invalid insDob format', function () { + new EligibilityDTO( + ...$this->validData, + insDob: '01-15-1990' + ); + })->throws(InvalidArgumentException::class, 'insDob must be in yyyymmdd format'); + + it('throws exception for invalid patDob format', function () { + new EligibilityDTO( + ...$this->validData, + patDob: '1990/01/15' + ); + })->throws(InvalidArgumentException::class, 'patDob must be in yyyymmdd format'); + }); + + describe('toArray', function () { + it('converts to array with correct keys', function () { + $dto = new EligibilityDTO(...$this->validData); + + $array = $dto->toArray(); + + expect($array)->toBe([ + 'ins_name_l' => 'Doe', + 'ins_name_f' => 'John', + 'payerid' => 'PAYER123', + 'pat_rel' => '18', + 'fdos' => '20240115', + 'prov_npi' => '1234567890', + 'prov_taxid' => '12-3456789', + ]); + }); + + it('includes optional fields when set', function () { + $dto = new EligibilityDTO( + ...$this->validData, + insMiddleName: 'M', + insSex: 'M' + ); + + $array = $dto->toArray(); + + expect($array)->toHaveKey('ins_name_m'); + expect($array['ins_name_m'])->toBe('M'); + expect($array)->toHaveKey('ins_sex'); + expect($array['ins_sex'])->toBe('M'); + }); + + it('filters out null values', function () { + $dto = new EligibilityDTO(...$this->validData); + + $array = $dto->toArray(); + + expect($array)->not->toHaveKey('ins_name_m'); + expect($array)->not->toHaveKey('ins_sex'); + }); + }); + + describe('fromArray', function () { + it('creates a DTO from array', function () { + $data = [ + 'ins_name_l' => 'Doe', + 'ins_name_f' => 'John', + 'payerid' => 'PAYER123', + 'pat_rel' => '18', + 'fdos' => '20240115', + 'prov_npi' => '1234567890', + 'prov_taxid' => '12-3456789', + ]; + + $dto = EligibilityDTO::fromArray($data); + + expect($dto->insLastName)->toBe('Doe'); + expect($dto->insFirstName)->toBe('John'); + expect($dto->payerId)->toBe('PAYER123'); + }); + + it('throws exception for missing required field in fromArray', function () { + $data = [ + 'ins_name_l' => 'Doe', + 'ins_name_f' => 'John', + // Missing payerid + 'pat_rel' => '18', + 'fdos' => '20240115', + 'prov_npi' => '1234567890', + 'prov_taxid' => '12-3456789', + ]; + + EligibilityDTO::fromArray($data); + })->throws(InvalidArgumentException::class, 'Missing required field: payerid'); + + it('includes optional fields from array', function () { + $data = [ + 'ins_name_l' => 'Doe', + 'ins_name_f' => 'John', + 'payerid' => 'PAYER123', + 'pat_rel' => '18', + 'fdos' => '20240115', + 'prov_npi' => '1234567890', + 'prov_taxid' => '12-3456789', + 'ins_sex' => 'M', + 'pat_name_l' => 'Smith', + ]; + + $dto = EligibilityDTO::fromArray($data); + + expect($dto->insSex)->toBe('M'); + expect($dto->patLastName)->toBe('Smith'); + }); + }); +}); diff --git a/tests/Unit/DTO/ProviderEnrollmentDTOTest.php b/tests/Unit/DTO/ProviderEnrollmentDTOTest.php new file mode 100644 index 0000000..a2a2cd2 --- /dev/null +++ b/tests/Unit/DTO/ProviderEnrollmentDTOTest.php @@ -0,0 +1,249 @@ +payerId)->toBe('PAYER123'); + expect($dto->enrollType)->toBe('era'); + expect($dto->provTaxId)->toBe('12-3456789'); + expect($dto->provNpi)->toBe('1234567890'); + }); + + it('creates a DTO with name fields when no NPI', function () { + $dto = new ProviderEnrollmentDTO( + payerId: 'PAYER123', + enrollType: '1500', + provTaxId: '12-3456789', + provNameLast: 'Smith', + provNameFirst: 'John' + ); + + expect($dto->provNameLast)->toBe('Smith'); + expect($dto->provNameFirst)->toBe('John'); + }); + + it('creates a DTO with all optional fields', function () { + $dto = new ProviderEnrollmentDTO( + payerId: 'PAYER123', + enrollType: 'era', + provTaxId: '12-3456789', + provNpi: '1234567890', + provNameLast: 'Smith', + provNameFirst: 'John', + provNameMiddle: 'M', + contact: 'Jane Doe', + contactTitle: 'Manager', + contactEmail: 'jane@example.com', + contactPhone: '555-123-4567', + contactFax: '555-987-6543', + provId: 'PROV123', + provAddr1: '123 Main St', + provAddr2: 'Suite 100', + provCity: 'Anytown', + provState: 'CA', + provZip: '12345' + ); + + expect($dto->contact)->toBe('Jane Doe'); + expect($dto->contactTitle)->toBe('Manager'); + expect($dto->contactEmail)->toBe('jane@example.com'); + expect($dto->provAddr1)->toBe('123 Main St'); + expect($dto->provState)->toBe('CA'); + }); + }); + + describe('validation', function () { + it('throws exception for empty payerId', function () { + new ProviderEnrollmentDTO( + payerId: '', + enrollType: 'era', + provTaxId: '12-3456789', + provNpi: '1234567890' + ); + })->throws(InvalidArgumentException::class, 'payerId is required.'); + + it('throws exception for empty enrollType', function () { + new ProviderEnrollmentDTO( + payerId: 'PAYER123', + enrollType: '', + provTaxId: '12-3456789', + provNpi: '1234567890' + ); + })->throws(InvalidArgumentException::class, 'enrollType is required.'); + + it('throws exception for empty provTaxId', function () { + new ProviderEnrollmentDTO( + payerId: 'PAYER123', + enrollType: 'era', + provTaxId: '', + provNpi: '1234567890' + ); + })->throws(InvalidArgumentException::class, 'provTaxId is required.'); + + it('throws exception for invalid enrollType', function () { + new ProviderEnrollmentDTO( + payerId: 'PAYER123', + enrollType: 'invalid', + provTaxId: '12-3456789', + provNpi: '1234567890' + ); + })->throws(InvalidArgumentException::class, 'enrollType must be one of: era, 1500, ub, elig, attach'); + + it('accepts all valid enrollType values', function () { + $validTypes = ['era', '1500', 'ub', 'elig', 'attach']; + + foreach ($validTypes as $type) { + $dto = new ProviderEnrollmentDTO( + payerId: 'PAYER123', + enrollType: $type, + provTaxId: '12-3456789', + provNpi: '1234567890' + ); + + expect($dto->enrollType)->toBe($type); + } + }); + + it('throws exception when NPI not provided and provNameLast is missing', function () { + new ProviderEnrollmentDTO( + payerId: 'PAYER123', + enrollType: 'era', + provTaxId: '12-3456789' + ); + })->throws(InvalidArgumentException::class, 'provNameLast is required when provNpi is not provided.'); + + it('throws exception when NPI not provided and provNameFirst is missing for individual', function () { + new ProviderEnrollmentDTO( + payerId: 'PAYER123', + enrollType: 'era', + provTaxId: '12-3456789', + provNameLast: 'Smith' + ); + })->throws(InvalidArgumentException::class, 'provNameFirst is required when provNpi is not provided and the provider is an individual.'); + + it('throws exception for invalid email', function () { + new ProviderEnrollmentDTO( + payerId: 'PAYER123', + enrollType: 'era', + provTaxId: '12-3456789', + provNpi: '1234567890', + contactEmail: 'invalid-email' + ); + })->throws(InvalidArgumentException::class, 'contactEmail must be a valid email address.'); + + it('throws exception for invalid state code', function () { + new ProviderEnrollmentDTO( + payerId: 'PAYER123', + enrollType: 'era', + provTaxId: '12-3456789', + provNpi: '1234567890', + provState: 'California' + ); + })->throws(InvalidArgumentException::class, 'provState must be a valid two-letter state code.'); + + it('throws exception for lowercase state code', function () { + new ProviderEnrollmentDTO( + payerId: 'PAYER123', + enrollType: 'era', + provTaxId: '12-3456789', + provNpi: '1234567890', + provState: 'ca' + ); + })->throws(InvalidArgumentException::class, 'provState must be a valid two-letter state code.'); + }); + + describe('toArray', function () { + it('converts to array with correct keys', function () { + $dto = new ProviderEnrollmentDTO( + payerId: 'PAYER123', + enrollType: 'era', + provTaxId: '12-3456789', + provNpi: '1234567890', + contact: 'Jane Doe', + contactEmail: 'jane@example.com' + ); + + $array = $dto->toArray(); + + expect($array)->toBe([ + 'payerid' => 'PAYER123', + 'enroll_type' => 'era', + 'prov_taxid' => '12-3456789', + 'prov_npi' => '1234567890', + 'contact' => 'Jane Doe', + 'contact_email' => 'jane@example.com', + ]); + }); + + it('filters out null values', function () { + $dto = new ProviderEnrollmentDTO( + payerId: 'PAYER123', + enrollType: '1500', + provTaxId: '12-3456789', + provNpi: '1234567890' + ); + + $array = $dto->toArray(); + + expect($array)->not->toHaveKey('prov_name_l'); + expect($array)->not->toHaveKey('contact_email'); + }); + }); + + describe('fromArray', function () { + it('creates a DTO from array', function () { + $data = [ + 'payerid' => 'PAYER123', + 'enroll_type' => 'era', + 'prov_taxid' => '12-3456789', + 'prov_npi' => '1234567890', + 'contact' => 'Jane Doe', + 'contact_email' => 'jane@example.com', + 'prov_state' => 'NY', + ]; + + $dto = ProviderEnrollmentDTO::fromArray($data); + + expect($dto->payerId)->toBe('PAYER123'); + expect($dto->enrollType)->toBe('era'); + expect($dto->provTaxId)->toBe('12-3456789'); + expect($dto->provNpi)->toBe('1234567890'); + expect($dto->contact)->toBe('Jane Doe'); + expect($dto->contactEmail)->toBe('jane@example.com'); + expect($dto->provState)->toBe('NY'); + }); + + it('throws exception for missing required field payerid', function () { + ProviderEnrollmentDTO::fromArray([ + 'enroll_type' => 'era', + 'prov_taxid' => '12-3456789', + 'prov_npi' => '1234567890', + ]); + })->throws(InvalidArgumentException::class, 'payerid is required.'); + + it('throws exception for missing required field enroll_type', function () { + ProviderEnrollmentDTO::fromArray([ + 'payerid' => 'PAYER123', + 'prov_taxid' => '12-3456789', + 'prov_npi' => '1234567890', + ]); + })->throws(InvalidArgumentException::class, 'enroll_type is required.'); + + it('throws exception for missing required field prov_taxid', function () { + ProviderEnrollmentDTO::fromArray([ + 'payerid' => 'PAYER123', + 'enroll_type' => 'era', + 'prov_npi' => '1234567890', + ]); + })->throws(InvalidArgumentException::class, 'prov_taxid is required.'); + }); +}); diff --git a/tests/Unit/Requests/ClaimRequestTest.php b/tests/Unit/Requests/ClaimRequestTest.php new file mode 100644 index 0000000..5d45cc9 --- /dev/null +++ b/tests/Unit/Requests/ClaimRequestTest.php @@ -0,0 +1,183 @@ +mockClient = Mockery::mock(Client::class); + $this->claimRequest = new ClaimRequest($this->mockClient); + }); + + afterEach(function () { + Mockery::close(); + }); + + describe('archive', function () { + it('sends archive request with claim ID', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/archive/', ['claimid' => 'CLAIM123']) + ->andReturn(['status' => 'archived']); + + $result = $this->claimRequest->archive('CLAIM123'); + + expect($result)->toBe(['status' => 'archived']); + }); + }); + + describe('listModifications', function () { + it('sends request with no filters', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/modify/', []) + ->andReturn(['modifications' => []]); + + $result = $this->claimRequest->listModifications(); + + expect($result)->toBe(['modifications' => []]); + }); + + it('sends request with modId filter', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/modify/', ['ModID' => 'MOD123']) + ->andReturn(['modifications' => ['mod1']]); + + $result = $this->claimRequest->listModifications(modId: 'MOD123'); + + expect($result)->toBe(['modifications' => ['mod1']]); + }); + + it('sends request with claimMdId filter', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/modify/', ['ClaimMD_ID' => 'CMD123']) + ->andReturn(['modifications' => []]); + + $result = $this->claimRequest->listModifications(claimMdId: 'CMD123'); + + expect($result)->toBe(['modifications' => []]); + }); + + it('sends request with field filter', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/modify/', ['Field' => 'status']) + ->andReturn(['modifications' => []]); + + $result = $this->claimRequest->listModifications(field: 'status'); + + expect($result)->toBe(['modifications' => []]); + }); + + it('sends request with all filters', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/modify/', ['ModID' => 'MOD123', 'ClaimMD_ID' => 'CMD123', 'Field' => 'status']) + ->andReturn(['modifications' => []]); + + $result = $this->claimRequest->listModifications('MOD123', 'CMD123', 'status'); + + expect($result)->toBe(['modifications' => []]); + }); + }); + + describe('appeal', function () { + it('sends appeal request with ClaimAppealDTO', function () { + $dto = new ClaimAppealDTO( + claimId: 'CLAIM123', + contactName: 'John Doe', + contactEmail: 'john@example.com' + ); + + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/appeal/', [ + 'claimid' => 'CLAIM123', + 'contact_name' => 'John Doe', + 'contact_email' => 'john@example.com', + ]) + ->andReturn(['status' => 'appealed']); + + $result = $this->claimRequest->appeal($dto); + + expect($result)->toBe(['status' => 'appealed']); + }); + + it('sends appeal request with array', function () { + $data = [ + 'claimid' => 'CLAIM123', + 'contact_name' => 'Jane Doe', + ]; + + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/appeal/', $data) + ->andReturn(['status' => 'appealed']); + + $result = $this->claimRequest->appeal($data); + + expect($result)->toBe(['status' => 'appealed']); + }); + }); + + describe('notes', function () { + it('sends request with no filters', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/notes/', []) + ->andReturn(['notes' => []]); + + $result = $this->claimRequest->notes(); + + expect($result)->toBe(['notes' => []]); + }); + + it('sends request with noteId filter', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/notes/', ['NoteID' => 'NOTE123']) + ->andReturn(['notes' => ['note1']]); + + $result = $this->claimRequest->notes(noteId: 'NOTE123'); + + expect($result)->toBe(['notes' => ['note1']]); + }); + + it('sends request with claimMdId filter', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/notes/', ['ClaimMD_ID' => 'CMD123']) + ->andReturn(['notes' => []]); + + $result = $this->claimRequest->notes(claimMdId: 'CMD123'); + + expect($result)->toBe(['notes' => []]); + }); + + it('sends request with both filters', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/notes/', ['ClaimMD_ID' => 'CMD123', 'NoteID' => 'NOTE123']) + ->andReturn(['notes' => []]); + + $result = $this->claimRequest->notes('NOTE123', 'CMD123'); + + expect($result)->toBe(['notes' => []]); + }); + }); +}); diff --git a/tests/Unit/Requests/ERARequestTest.php b/tests/Unit/Requests/ERARequestTest.php new file mode 100644 index 0000000..bb304fe --- /dev/null +++ b/tests/Unit/Requests/ERARequestTest.php @@ -0,0 +1,122 @@ +mockClient = Mockery::mock(Client::class); + $this->eraRequest = new ERARequest($this->mockClient); + }); + + afterEach(function () { + Mockery::close(); + }); + + describe('getList', function () { + it('sends request with empty data when no params provided', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/eralist/') + ->andReturn(['data' => []]); + + $result = $this->eraRequest->getList(); + + expect($result)->toBe(['data' => []]); + }); + + it('sends request with ERADTO data', function () { + $dto = new ERADTO(payerId: 'PAYER123', newOnly: '1'); + + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/eralist/', ['PayerID' => 'PAYER123', 'NewOnly' => '1']) + ->andReturn(['data' => ['era1', 'era2']]); + + $result = $this->eraRequest->getList($dto); + + expect($result)->toBe(['data' => ['era1', 'era2']]); + }); + + it('sends request with array data', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/eralist/', ['PayerID' => 'TEST123']) + ->andReturn(['data' => []]); + + $result = $this->eraRequest->getList(['PayerID' => 'TEST123']); + + expect($result)->toBe(['data' => []]); + }); + + it('handles null parameter', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/eralist/') + ->andReturn(['status' => 'ok']); + + $result = $this->eraRequest->getList(null); + + expect($result)->toBe(['status' => 'ok']); + }); + }); + + describe('getJson', function () { + it('sends request with era ID', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/eradata/', ['eraid' => 'ERA123']) + ->andReturn(['json_data' => 'test']); + + $result = $this->eraRequest->getJson('ERA123'); + + expect($result)->toBe(['json_data' => 'test']); + }); + }); + + describe('getPDF', function () { + it('sends request with era ID only', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/erapdf/', ['eraid' => 'ERA123']) + ->andReturn(['pdf' => 'base64data']); + + $result = $this->eraRequest->getPDF('ERA123'); + + expect($result)->toBe(['pdf' => 'base64data']); + }); + + it('sends request with era ID and PCN', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/erapdf/', ['eraid' => 'ERA123', 'pcn' => 'PCN456']) + ->andReturn(['pdf' => 'base64data']); + + $result = $this->eraRequest->getPDF('ERA123', 'PCN456'); + + expect($result)->toBe(['pdf' => 'base64data']); + }); + }); + + describe('get835', function () { + it('sends request with era ID', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/era835/', ['eraid' => 'ERA123']) + ->andReturn(['835_data' => 'test']); + + $result = $this->eraRequest->get835('ERA123'); + + expect($result)->toBe(['835_data' => 'test']); + }); + }); +}); diff --git a/tests/Unit/Requests/EligibilityRequestTest.php b/tests/Unit/Requests/EligibilityRequestTest.php new file mode 100644 index 0000000..21d765e --- /dev/null +++ b/tests/Unit/Requests/EligibilityRequestTest.php @@ -0,0 +1,82 @@ +mockClient = Mockery::mock(Client::class); + $this->eligibilityRequest = new EligibilityRequest($this->mockClient); + }); + + afterEach(function () { + Mockery::close(); + }); + + describe('checkEligibilityJSON', function () { + it('sends request with EligibilityDTO', function () { + $dto = new EligibilityDTO( + insLastName: 'Doe', + insFirstName: 'John', + payerId: 'PAYER123', + patientRelationship: '18', + serviceDate: '20240115', + providerNpi: '1234567890', + providerTaxId: '12-3456789' + ); + + $expectedData = [ + 'ins_name_l' => 'Doe', + 'ins_name_f' => 'John', + 'payerid' => 'PAYER123', + 'pat_rel' => '18', + 'fdos' => '20240115', + 'prov_npi' => '1234567890', + 'prov_taxid' => '12-3456789', + ]; + + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/eligdata/', $expectedData) + ->andReturn(['eligible' => true]); + + $result = $this->eligibilityRequest->checkEligibilityJSON($dto); + + expect($result)->toBe(['eligible' => true]); + }); + + it('sends request with array data', function () { + $data = [ + 'ins_name_l' => 'Smith', + 'ins_name_f' => 'Jane', + 'payerid' => 'PAYER456', + ]; + + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/eligdata/', $data) + ->andReturn(['eligible' => false]); + + $result = $this->eligibilityRequest->checkEligibilityJSON($data); + + expect($result)->toBe(['eligible' => false]); + }); + }); + + describe('checkEligibility270271', function () { + it('throws exception when file is not a resource', function () { + $this->eligibilityRequest->checkEligibility270271('not a resource'); + })->throws(InvalidArgumentException::class, 'File must be a valid resource'); + + it('throws exception for array input', function () { + $this->eligibilityRequest->checkEligibility270271(['data']); + })->throws(InvalidArgumentException::class, 'File must be a valid resource'); + + it('throws exception for null input', function () { + $this->eligibilityRequest->checkEligibility270271(null); + })->throws(InvalidArgumentException::class, 'File must be a valid resource'); + }); +}); diff --git a/tests/Unit/Requests/FileRequestTest.php b/tests/Unit/Requests/FileRequestTest.php new file mode 100644 index 0000000..0049c29 --- /dev/null +++ b/tests/Unit/Requests/FileRequestTest.php @@ -0,0 +1,87 @@ +mockClient = Mockery::mock(Client::class); + $this->fileRequest = new FileRequest($this->mockClient); + }); + + afterEach(function () { + Mockery::close(); + }); + + describe('getUploadList', function () { + it('sends request with no parameters', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/uploadlist', []) + ->andReturn(['uploads' => []]); + + $result = $this->fileRequest->getUploadList(); + + expect($result)->toBe(['uploads' => []]); + }); + + it('sends request with page parameter', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/uploadlist', ['Page' => 2]) + ->andReturn(['uploads' => [], 'page' => 2]); + + $result = $this->fileRequest->getUploadList(page: 2); + + expect($result)->toBe(['uploads' => [], 'page' => 2]); + }); + + it('sends request with uploadDate parameter', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/uploadlist', ['UploadDate' => '2024-01-15']) + ->andReturn(['uploads' => []]); + + $result = $this->fileRequest->getUploadList(uploadDate: '2024-01-15'); + + expect($result)->toBe(['uploads' => []]); + }); + + it('sends request with both parameters', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/uploadlist', ['Page' => 1, 'UploadDate' => '2024-01-15']) + ->andReturn(['uploads' => []]); + + $result = $this->fileRequest->getUploadList(1, '2024-01-15'); + + expect($result)->toBe(['uploads' => []]); + }); + + it('throws exception for invalid date format', function () { + $this->fileRequest->getUploadList(uploadDate: '01-15-2024'); + })->throws(InvalidArgumentException::class, 'Upload date must be in the format yyyy-mm-dd'); + + it('throws exception for invalid date format with slashes', function () { + $this->fileRequest->getUploadList(uploadDate: '2024/01/15'); + })->throws(InvalidArgumentException::class, 'Upload date must be in the format yyyy-mm-dd'); + }); + + describe('upload', function () { + it('throws exception when file is not a resource', function () { + $this->fileRequest->upload('not a resource'); + })->throws(InvalidArgumentException::class, 'Invalid file provided. Must be a resource.'); + + it('throws exception for array input', function () { + $this->fileRequest->upload(['data']); + })->throws(InvalidArgumentException::class, 'Invalid file provided. Must be a resource.'); + + it('throws exception for null input', function () { + $this->fileRequest->upload(null); + })->throws(InvalidArgumentException::class, 'Invalid file provided. Must be a resource.'); + }); +}); diff --git a/tests/Unit/Requests/PayerRequestTest.php b/tests/Unit/Requests/PayerRequestTest.php new file mode 100644 index 0000000..c988895 --- /dev/null +++ b/tests/Unit/Requests/PayerRequestTest.php @@ -0,0 +1,65 @@ +mockClient = Mockery::mock(Client::class); + $this->payerRequest = new PayerRequest($this->mockClient); + }); + + afterEach(function () { + Mockery::close(); + }); + + describe('listPayer', function () { + it('sends request with no filters', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/payerlist/', []) + ->andReturn(['payers' => []]); + + $result = $this->payerRequest->listPayer(); + + expect($result)->toBe(['payers' => []]); + }); + + it('sends request with payerId filter', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/payerlist/', ['payerid' => 'PAYER123']) + ->andReturn(['payers' => [['id' => 'PAYER123', 'name' => 'Test Payer']]]); + + $result = $this->payerRequest->listPayer(payerId: 'PAYER123'); + + expect($result)->toBe(['payers' => [['id' => 'PAYER123', 'name' => 'Test Payer']]]); + }); + + it('sends request with payerName filter', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/payerlist/', ['payer_name' => 'Blue Cross']) + ->andReturn(['payers' => []]); + + $result = $this->payerRequest->listPayer(payerName: 'Blue Cross'); + + expect($result)->toBe(['payers' => []]); + }); + + it('sends request with both filters', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/payerlist/', ['payerid' => 'BCBS', 'payer_name' => 'Blue Cross']) + ->andReturn(['payers' => []]); + + $result = $this->payerRequest->listPayer('BCBS', 'Blue Cross'); + + expect($result)->toBe(['payers' => []]); + }); + }); +}); diff --git a/tests/Unit/Requests/ProviderRequestTest.php b/tests/Unit/Requests/ProviderRequestTest.php new file mode 100644 index 0000000..9d962a7 --- /dev/null +++ b/tests/Unit/Requests/ProviderRequestTest.php @@ -0,0 +1,93 @@ +mockClient = Mockery::mock(Client::class); + $this->providerRequest = new ProviderRequest($this->mockClient); + }); + + afterEach(function () { + Mockery::close(); + }); + + describe('enroll', function () { + it('sends request with ProviderEnrollmentDTO', function () { + $dto = new ProviderEnrollmentDTO( + payerId: 'PAYER123', + enrollType: 'era', + provTaxId: '12-3456789', + provNpi: '1234567890', + contact: 'John Doe', + contactEmail: 'john@example.com' + ); + + $expectedData = [ + 'payerid' => 'PAYER123', + 'enroll_type' => 'era', + 'prov_taxid' => '12-3456789', + 'prov_npi' => '1234567890', + 'contact' => 'John Doe', + 'contact_email' => 'john@example.com', + ]; + + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/enroll/', $expectedData) + ->andReturn(['status' => 'enrolled']); + + $result = $this->providerRequest->enroll($dto); + + expect($result)->toBe(['status' => 'enrolled']); + }); + + it('sends request with array data', function () { + $data = [ + 'payerid' => 'PAYER456', + 'enroll_type' => '1500', + 'prov_taxid' => '98-7654321', + 'prov_npi' => '0987654321', + ]; + + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/enroll/', $data) + ->andReturn(['status' => 'pending']); + + $result = $this->providerRequest->enroll($data); + + expect($result)->toBe(['status' => 'pending']); + }); + + it('sends request with minimal DTO', function () { + $dto = new ProviderEnrollmentDTO( + payerId: 'PAYER123', + enrollType: 'elig', + provTaxId: '12-3456789', + provNpi: '1234567890' + ); + + $expectedData = [ + 'payerid' => 'PAYER123', + 'enroll_type' => 'elig', + 'prov_taxid' => '12-3456789', + 'prov_npi' => '1234567890', + ]; + + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/enroll/', $expectedData) + ->andReturn(['status' => 'enrolled']); + + $result = $this->providerRequest->enroll($dto); + + expect($result)->toBe(['status' => 'enrolled']); + }); + }); +}); diff --git a/tests/Unit/Requests/ResponseRequestTest.php b/tests/Unit/Requests/ResponseRequestTest.php new file mode 100644 index 0000000..e1f10ba --- /dev/null +++ b/tests/Unit/Requests/ResponseRequestTest.php @@ -0,0 +1,106 @@ +mockClient = Mockery::mock(Client::class); + $this->responseRequest = new ResponseRequest($this->mockClient); + }); + + afterEach(function () { + Mockery::close(); + }); + + describe('fetchResponses', function () { + it('sends request with response ID', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/response/', ['ResponseID' => '0']) + ->andReturn(['responses' => [], 'last_responseid' => '100']); + + $result = $this->responseRequest->fetchResponses('0'); + + expect($result)->toBe(['responses' => [], 'last_responseid' => '100']); + }); + + it('sends request with response ID and claim ID', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/response/', ['ResponseID' => '50', 'ClaimID' => 'CLAIM123']) + ->andReturn(['responses' => []]); + + $result = $this->responseRequest->fetchResponses('50', 'CLAIM123'); + + expect($result)->toBe(['responses' => []]); + }); + + it('throws exception for empty response ID', function () { + $this->responseRequest->fetchResponses(''); + })->throws(InvalidArgumentException::class, 'ResponseID cannot be empty'); + }); + + describe('fetchAllResponses', function () { + it('returns a generator', function () { + $generator = $this->responseRequest->fetchAllResponses(); + + expect($generator)->toBeInstanceOf(Generator::class); + }); + + it('yields pages until last_responseid is null', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/response/', ['ResponseID' => '0']) + ->andReturn(['responses' => ['page1'], 'last_responseid' => '100']); + + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/response/', ['ResponseID' => '100']) + ->andReturn(['responses' => ['page2'], 'last_responseid' => '200']); + + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/response/', ['ResponseID' => '200']) + ->andReturn(['responses' => ['page3'], 'last_responseid' => null]); + + $generator = $this->responseRequest->fetchAllResponses(); + $results = iterator_to_array($generator); + + expect($results)->toHaveCount(3); + expect($results[0]['responses'])->toBe(['page1']); + expect($results[1]['responses'])->toBe(['page2']); + expect($results[2]['responses'])->toBe(['page3']); + }); + + it('passes claim ID to each request', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/response/', ['ResponseID' => '0', 'ClaimID' => 'CLAIM123']) + ->andReturn(['responses' => [], 'last_responseid' => null]); + + $generator = $this->responseRequest->fetchAllResponses('CLAIM123'); + iterator_to_array($generator); + }); + + it('handles single page response', function () { + $this->mockClient + ->shouldReceive('sendRequest') + ->once() + ->with('POST', '/services/response/', ['ResponseID' => '0']) + ->andReturn(['responses' => ['only_page'], 'last_responseid' => null]); + + $generator = $this->responseRequest->fetchAllResponses(); + $results = iterator_to_array($generator); + + expect($results)->toHaveCount(1); + expect($results[0]['responses'])->toBe(['only_page']); + }); + }); +});