Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,16 @@ jobs:
uses: php-actions/phpunit@v4
with:
php_version: ${{matrix.php-version}}
php_extensions: json zip xdebug
php_extensions: json zlib xdebug
configuration: phpunit.xml.dist
coverage_text: true
coverage_clover: coverage.xml
env:
XDEBUG_MODE: coverage
XDEBUG_MODE: coverage

- name: Enforce coverage threshold
if: matrix.php-version == '8.2'
uses: ericsizemore/phpunit-coverage-check-action@2.0.0
with:
clover_file: 'coverage.xml'
threshold: '100'
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"@analyse"
],
"test": "vendor/bin/phpunit --testdox",
"test-coverage": "@test --coverage-text coverage"
"test-coverage": "@test --coverage-text"
},
"autoload": {
"psr-4": {
Expand Down
32 changes: 32 additions & 0 deletions tests/Feature/SendNotificationsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,38 @@ public function send_notifications_exposes_push_errors_for_each_request_error():
$this->assertEquals(PushErrorCode::PushTooManyExperienceIds, $result->errors->get(0)->code);
}

#[Test]
public function send_notifications_exposes_unauthorized_push_error_for_invalid_credentials(): void
{
$this->mockClient->addResponse(
MockResponse::make(
body: [
'errors' => [
[
'code' => 'UNAUTHORIZED',
'message' => 'Invalid credentials',
],
],
],
status: 401,
headers: ['Content-Type' => 'application/json'],
),
);

$messages = new PushMessageCollection(
new PushMessage(to: new PushToken('ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]'), title: 'Test Notification'),
);

$result = $this->service->sendNotifications($messages);

$this->mockClient->assertSentCount(1, SendNotificationsRequest::class);

$this->assertTrue($result->hasErrors());
$this->assertCount(1, $result->errors);
$this->assertEquals(PushErrorCode::Unauthorized, $result->errors->get(0)->code);
$this->assertCount(0, $result->tickets);
}

// Helpers ----

protected function generatePushMessage(): PushMessage
Expand Down
76 changes: 76 additions & 0 deletions tests/Unit/PushMessage/PushMessageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Dru1x\ExpoPush\PushMessage\RichContent;
use Dru1x\ExpoPush\PushToken\PushToken;
use Dru1x\ExpoPush\PushToken\PushTokenCollection;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use stdClass;
Expand Down Expand Up @@ -210,6 +211,81 @@ public function from_array_with_dictionary_returns_instance(): void
$this->assertSame('test-tag', $message->tag);
}

#[Test]
public function from_array_throws_exception_when_to_is_missing(): void
{
$this->expectException(InvalidArgumentException::class);

PushMessage::fromArray([
'title' => 'Test Notification',
]);
}

#[Test]
public function from_array_throws_exception_when_to_is_not_array_or_string(): void
Comment thread
dru1x marked this conversation as resolved.
{
$this->expectException(InvalidArgumentException::class);

PushMessage::fromArray([
'to' => 123,
]);
}

#[Test]
public function from_array_throws_exception_when_rich_content_is_not_array(): void
{
$this->expectException(TypeError::class);

PushMessage::fromArray([
'to' => 'ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]',
'richContent' => 'not-an-array',
]);
}

#[Test]
public function from_array_converts_priority_to_enum(): void
{
$message = PushMessage::fromArray([
'to' => 'ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]',
'priority' => 'high',
]);

$this->assertSame(Priority::High, $message->priority);
}

#[Test]
public function from_array_throws_exception_when_priority_is_not_valid(): void
{
$this->expectException(TypeError::class);

PushMessage::fromArray([
'to' => 'ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]',
'priority' => [],
]);
}

#[Test]
public function from_array_converts_interruption_level_to_enum(): void
{
$message = PushMessage::fromArray([
'to' => 'ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]',
'interruptionLevel' => 'critical',
]);

$this->assertSame(InterruptionLevel::Critical, $message->interruptionLevel);
}

#[Test]
public function from_array_throws_exception_when_interruption_level_is_not_valid(): void
{
$this->expectException(TypeError::class);

PushMessage::fromArray([
'to' => 'ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]',
'interruptionLevel' => [],
]);
}

#[Test]
public function from_json_returns_instance(): void
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Dru1x\ExpoPush\Tests\Unit\PushTicket;
namespace Dru1x\ExpoPush\Tests\Unit\PushReceipt;

use Dru1x\ExpoPush\PushReceipt\SuccessfulPushReceipt;
use Dru1x\ExpoPush\Support\PushStatus;
Expand Down
97 changes: 97 additions & 0 deletions tests/Unit/PushToken/PushTokenCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use ArrayIterator;
use Dru1x\ExpoPush\PushToken\PushToken;
use Dru1x\ExpoPush\PushToken\PushTokenCollection;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Traversable;
Expand Down Expand Up @@ -131,6 +132,102 @@ public function count_returns_correct_push_token_count(): void
$this->assertCount(3, $collection);
}

#[Test]
public function shift_with_negative_count_throws_exception(): void
{
$collection = new PushTokenCollection(
new PushToken('ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]'),
);

$this->expectException(InvalidArgumentException::class);

$collection->shift(-1);
}

#[Test]
public function shift_with_zero_count_returns_empty_collection_without_mutating_source(): void
{
$token1 = new PushToken('ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]');
$token2 = new PushToken('ExponentPushToken[yyyyyyyyyyyyyyyyyyyyyy]');
$token3 = new PushToken('ExponentPushToken[zzzzzzzzzzzzzzzzzzzzzz]');

$collection = new PushTokenCollection($token1, $token2, $token3);

$shifted = $collection->shift(0);

$this->assertTrue($shifted->isEmpty());

$this->assertCount(3, $collection);
$this->assertEquals($token1, $collection->get(0));
$this->assertEquals($token2, $collection->get(1));
$this->assertEquals($token3, $collection->get(2));
}

#[Test]
public function shift_from_empty_collection_returns_empty_collection(): void
{
$collection = new PushTokenCollection();

$shifted = $collection->shift();

$this->assertTrue($shifted->isEmpty());
}

#[Test]
public function shift_with_count_greater_than_available_returns_all_items(): void
{
$token1 = new PushToken('ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]');
$token2 = new PushToken('ExponentPushToken[yyyyyyyyyyyyyyyyyyyyyy]');

$collection = new PushTokenCollection($token1, $token2);

$shifted = $collection->shift(5);

$this->assertCount(2, $shifted);
$this->assertEquals($token1, $shifted->get(0));
$this->assertEquals($token2, $shifted->get(1));

$this->assertTrue($collection->isEmpty());
}

#[Test]
public function shift_removes_and_returns_leading_items_leaving_remainder_in_source(): void
{
$token1 = new PushToken('ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]');
$token2 = new PushToken('ExponentPushToken[yyyyyyyyyyyyyyyyyyyyyy]');
$token3 = new PushToken('ExponentPushToken[zzzzzzzzzzzzzzzzzzzzzz]');
$token4 = new PushToken('ExponentPushToken[aaaaaaaaaaaaaaaaaaaaaa]');

$collection = new PushTokenCollection($token1, $token2, $token3, $token4);

$shifted = $collection->shift(2);

$this->assertCount(2, $shifted);
$this->assertEquals($token1, $shifted->get(0));
$this->assertEquals($token2, $shifted->get(1));

$this->assertCount(2, $collection);
$this->assertEquals($token3, $collection->get(0));
$this->assertEquals($token4, $collection->get(1));
}

#[Test]
public function shift_with_default_count_removes_single_item(): void
{
$token1 = new PushToken('ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]');
$token2 = new PushToken('ExponentPushToken[yyyyyyyyyyyyyyyyyyyyyy]');

$collection = new PushTokenCollection($token1, $token2);

$shifted = $collection->shift();

$this->assertCount(1, $shifted);
$this->assertEquals($token1, $shifted->get(0));

$this->assertCount(1, $collection);
$this->assertEquals($token2, $collection->get(0));
}

#[Test]
public function chunk_returns_correctly_sized_chunks(): void
{
Expand Down
47 changes: 47 additions & 0 deletions tests/Unit/Request/GetReceiptsRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Dru1x\ExpoPush\PushReceipt\PushReceiptIdCollection;
use Dru1x\ExpoPush\Request\GetReceiptsRequest;
use OverflowException;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Saloon\Config;
Expand Down Expand Up @@ -167,6 +168,52 @@ public function create_dto_from_response_maps_unrecognized_error_code_to_unknown
$this->assertEquals(PushReceiptErrorCode::Unknown, $receipt->details->error);
}

#[Test]
#[DataProvider('knownErrorCodeProvider')]
public function create_dto_from_response_maps_known_error_code(string $expoErrorString, PushReceiptErrorCode $expectedCode): void
{
$request = new GetReceiptsRequest(
new PushReceiptIdCollection(),
);

$this->mockClient->addResponses([
GetReceiptsRequest::class => MockResponse::make(
body: [
'data' => [
'ZZZZZZZZ-ZZZZ-ZZZZ-ZZZZ-ZZZZZZZZZZZZ' => [
'status' => 'error',
'message' => '"ExponentPushToken[zzzzzzzzzzzzzzzzzzzzzz]" is not a registered push notification recipient',
'details' => [
'error' => $expoErrorString,
'expoPushToken' => 'ExponentPushToken[zzzzzzzzzzzzzzzzzzzzzz]',
],
],
],
],
headers: ['Content-Type' => 'application/json'],
),
]);

$dto = $request->createDtoFromResponse(
$this->connector->send($request),
);

$receipt = $dto->get(0);
$this->assertInstanceOf(FailedPushReceipt::class, $receipt);
$this->assertEquals($expectedCode, $receipt->details->error);
}

public static function knownErrorCodeProvider(): array
{
return [
'DeviceNotRegistered' => ['DeviceNotRegistered', PushReceiptErrorCode::DeviceNotRegistered],
'InvalidCredentials' => ['InvalidCredentials', PushReceiptErrorCode::InvalidCredentials],
'MessageRateExceeded' => ['MessageRateExceeded', PushReceiptErrorCode::MessageRateExceeded],
'MessageTooBig' => ['MessageTooBig', PushReceiptErrorCode::MessageTooBig],
'MismatchSenderId' => ['MismatchSenderId', PushReceiptErrorCode::MismatchSenderId],
];
}

#[Test]
public function create_dto_from_response_throws_exception_when_body_cannot_be_decoded(): void
{
Expand Down
19 changes: 19 additions & 0 deletions tests/Unit/Support/CollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ public function can_check_if_a_collection_contains_a_given_value(mixed $item, bo
);
}

#[Test]
#[DataProvider('doesntContainProvider')]
public function can_check_if_a_collection_doesnt_contain_a_given_value(mixed $item, bool $expected): void
{
$this->assertSame(
$expected,
Collection::make(1, 2)->doesntContain($item),
);
}

#[Test]
#[DataProvider('getProvider')]
public function can_retrieve_a_collection_value_by_its_key(int|string $key, mixed $value): void
Expand Down Expand Up @@ -226,6 +236,15 @@ public static function containsProvider(): array
];
}

public static function doesntContainProvider(): array
{
return [
'does' => [2, false],
'doesnt' => [3, true],
'doesnt loose' => ['2', true],
];
}

public static function getProvider(): array
{
return [
Expand Down
Loading