diff --git a/ApnsPHP/Message/LiveActivity.php b/ApnsPHP/Message/LiveActivity.php new file mode 100644 index 0000000..324d94c --- /dev/null +++ b/ApnsPHP/Message/LiveActivity.php @@ -0,0 +1,259 @@ +|object + */ + private array|object $state; + + /** + * Attributes at the start of an activity + * @var array|object + */ + private array|object $attributes; + + /** + * Type of attributes + * @var string + */ + private string $attributes_type; + + /** + * Event for the activity + * @var LiveActivityEvent + */ + private LiveActivityEvent $event; + + /** + * Timestamp when the activity will become stale + * @var int + */ + private int $stale_timestamp; + + /** + * Timestamp when the activity will dismiss itself + * @var int + */ + private int $dismiss_timestamp; + + /** + * Constructor. + * + * @param string|null $deviceToken Recipients device token (optional). + */ + public function __construct(?string $deviceToken = null) + { + parent::__construct($deviceToken); + parent::setPushType(PushType::LiveActivity); + } + + /** + * Get the payload dictionary. + * + * @return array The payload dictionary. + */ + public function getPayloadDictionary(): array + { + $payload = parent::getPayloadDictionary(); + + $payload[self::APPLE_RESERVED_NAMESPACE]['event'] = $this->event->value; + $payload[self::APPLE_RESERVED_NAMESPACE]['timestamp'] = time(); + if (isset($this->state)) { + $payload[self::APPLE_RESERVED_NAMESPACE]['content-state'] = $this->state; + } + if (isset($this->stale_timestamp)) { + $payload[self::APPLE_RESERVED_NAMESPACE]['stale-date'] = $this->stale_timestamp; + } + if (isset($this->dismiss_timestamp)) { + $payload[self::APPLE_RESERVED_NAMESPACE]['dismissal-date'] = $this->dismiss_timestamp; + } + + if ($this->event !== LiveActivityEvent::Start) { + return $payload; + } + + if (isset($this->attributes) && isset($this->attributes_type)) { + $payload[self::APPLE_RESERVED_NAMESPACE]['attributes-type'] = $this->attributes_type; + $payload[self::APPLE_RESERVED_NAMESPACE]['attributes'] = $this->attributes; + } + + return $payload; + } + + /** + * Set a topic + * + * @throws UnexpectedValueException if the topic is invalid for a live activity. + * + * @param string $topic + * + * @return void + */ + public function setTopic(string $topic): void + { + if (!str_contains($topic, '.push-type.liveactivity')) { + throw new UnexpectedValueException("Topic '$topic' does not include '.push-type.liveactivity'!"); + } + + parent::setTopic($topic); + } + + /** + * Set a push type + * + * @throws RuntimeException Since the push type is tied to the class + * + * @param PushType $pushType + * + * @return void + */ + public function setPushType(PushType $pushType): void + { + throw new RuntimeException('Push type is enforced by the class!'); + } + + /** + * Set the event for the activity + * + * @param LiveActivityEvent $event The activity event + */ + public function setEvent(LiveActivityEvent $event): void + { + $this->event = $event; + } + + /** + * Get the event for the activity + * @return LiveActivityEvent + */ + public function getEvent(): LiveActivityEvent + { + return $this->event; + } + + /** + * Set the attributes for the start of the activity + * + * @param array|object $attributes The attributes to set + */ + public function setAttributes(array|object $attributes): void + { + $this->attributes = $attributes; + } + + /** + * Get the attributes for the start activity + * + * @return object|array + */ + public function getAttributes(): object|array + { + return $this->attributes; + } + + /** + * Set the attribute type for the start of the activity. + * + * @param string $type The attribute type + * + * @return void + */ + public function setAttributesType(string $type): void + { + $this->attributes_type = $type; + } + + /** + * Get the attribute type for the start of the activity. + * + * @return string The attribute type + */ + public function getAttributesType(): string + { + return $this->attributes_type; + } + + /** + * Set the timestamp when the information goes stale + * + * @param int $stale_timestamp The timestamp at which the information goes stale + * + * @return void + */ + public function setStaleTimestamp(int $stale_timestamp): void + { + $this->stale_timestamp = $stale_timestamp; + } + + /** + * Get the timestamp when the information goes stale + * + * @return int The timestamp at which the information goes stale + */ + public function getStaleTimestamp(): int + { + return $this->stale_timestamp; + } + + /** + * Set the timestamp when the activity dismisses itself + * + * @param int $dismiss_timestamp The timestamp at which the activity dismisses + * + * @return void + */ + public function setDismissTimestamp(int $dismiss_timestamp): void + { + $this->dismiss_timestamp = $dismiss_timestamp; + } + + /** + * Get the timestamp when the activity dismisses itself + * + * @return int The timestamp at which the activity dismisses + */ + public function getDismissTimestamp(): int + { + return $this->dismiss_timestamp; + } + + /** + * Set the content state + * + * @param array|object $state The content state to relay to the app + * + * @return void + */ + public function setContentState(array|object $state): void + { + $this->state = $state; + } + + /** + * Get the content state + * + * @return array|object The content state that should be relayed to the app + */ + public function getContentState(): object|array + { + return $this->state; + } +} diff --git a/ApnsPHP/Message/LiveActivityEvent.php b/ApnsPHP/Message/LiveActivityEvent.php new file mode 100644 index 0000000..3e6ebed --- /dev/null +++ b/ApnsPHP/Message/LiveActivityEvent.php @@ -0,0 +1,31 @@ +mock_function('time', fn() => 1731944572); + + $this->class->setTitle('Were no strangers to love'); + $this->class->setText('You know the rules, and so do I'); + $this->class->setCategory('something'); + $this->class->setThreadId('thisIsAThreadId'); + $this->class->setCustomProperty('property', 'property'); + $this->class->setCustomProperty('name', 'value'); + $this->class->setTopic('name.push-type.liveactivity'); + $this->class->setEvent(LiveActivityEvent::Start); + $this->class->setContentState([]); + $this->class->setAttributes([]); + $this->class->setAttributesType('Type'); + $this->class->setStaleTimestamp(1); + $this->class->setDismissTimestamp(2); + + $payload = [ + 'aps' => [ + 'alert' => [ + 'title' => 'Were no strangers to love', + 'body' => 'You know the rules, and so do I' + ], + 'category' => 'something', + 'thread-id' => 'thisIsAThreadId', + 'event' => 'start', + 'timestamp' => 1731944572, + 'content-state' => [], + 'stale-date' => 1, + 'dismissal-date' => 2, + 'attributes-type' => 'Type', + 'attributes' => [], + ], + 'property' => 'property', + 'name' => 'value' + ]; + + $result = $this->get_reflection_method('getPayloadDictionary') + ->invoke($this->class); + + $this->assertEquals($payload, $result); + $this->unmock_function('time'); + } + + /** + * Test that getPayloadDictionary returns an empty payload if nothing is set + * + * @covers \ApnsPHP\Message::getPayloadDictionary + */ + public function testGetPayloadDictionaryReturnsEmptyPayload(): void + { + $this->mock_function('time', fn() => 1731944572); + + $this->class->setEvent(LiveActivityEvent::Start); + + $payload = [ + 'aps' => [ + 'event' => 'start', + 'timestamp' => 1731944572 + ] + ]; + + $result = $this->get_reflection_method('getPayloadDictionary') + ->invoke($this->class); + + $this->assertEquals($payload, $result); + $this->unmock_function('time'); + } +} diff --git a/ApnsPHP/Message/Tests/LiveActivityGetTest.php b/ApnsPHP/Message/Tests/LiveActivityGetTest.php new file mode 100644 index 0000000..d39a3bd --- /dev/null +++ b/ApnsPHP/Message/Tests/LiveActivityGetTest.php @@ -0,0 +1,104 @@ +set_reflection_property_value('event', LiveActivityEvent::Start); + + $value = $this->class->getEvent(); + + $this->assertSame(LiveActivityEvent::Start, $value); + } + + /** + * Test that getAttributes() gets the activity attributes. + * + * @covers \ApnsPHP\Message\LiveActivity::getAttributes + */ + public function testGetAttributes(): void + { + $this->set_reflection_property_value('attributes', []); + + $value = $this->class->getAttributes(); + + $this->assertSame([], $value); + } + + /** + * Test that getAttributes() gets the activity attributes type. + * + * @covers \ApnsPHP\Message\LiveActivity::getAttributesType + */ + public function testGetAttributesType(): void + { + $this->set_reflection_property_value('attributes_type', 'Type'); + + $value = $this->class->getAttributesType(); + + $this->assertSame('Type', $value); + } + + /** + * Test that getStaleTime() gets the activity stale time. + * + * @covers \ApnsPHP\Message\LiveActivity::getStaleTimestamp + */ + public function testGetStaleTime(): void + { + $this->set_reflection_property_value('stale_timestamp', 1); + + $value = $this->class->getStaleTimestamp(); + + $this->assertSame(1, $value); + } + + /** + * Test that getDismissTime() gets the activity stale time. + * + * @covers \ApnsPHP\Message\LiveActivity::getDismissTimestamp + */ + public function testGetDismissTime(): void + { + $this->set_reflection_property_value('dismiss_timestamp', 1); + + $value = $this->class->getDismissTimestamp(); + + $this->assertSame(1, $value); + } + + /** + * Test that getContentState() gets the activity state. + * + * @covers \ApnsPHP\Message\LiveActivity::getContentState + */ + public function testGetContentState(): void + { + $this->set_reflection_property_value('state', []); + + $value = $this->class->getContentState(); + + $this->assertSame([], $value); + } +} diff --git a/ApnsPHP/Message/Tests/LiveActivitySetTest.php b/ApnsPHP/Message/Tests/LiveActivitySetTest.php new file mode 100644 index 0000000..84fdb3a --- /dev/null +++ b/ApnsPHP/Message/Tests/LiveActivitySetTest.php @@ -0,0 +1,156 @@ + Arguments mapped to the event name + */ + public static function eventDataProvider(): array + { + return [ + LiveActivityEvent::Start->value => [LiveActivityEvent::Start], + LiveActivityEvent::Update->value => [LiveActivityEvent::Update], + LiveActivityEvent::End->value => [LiveActivityEvent::End], + ]; + } + + /** + * Test that setPushType() fails. + * + * @covers \ApnsPHP\Message\LiveActivity::setPushType + */ + public function testSetPushTypeFails(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage("Push type is enforced by the class!"); + + $this->class->setPushType(PushType::LiveActivity); + } + + /** + * Test that setTopic() sets the activity topic correctly. + * + * @covers \ApnsPHP\Message\LiveActivity::setTopic + */ + public function testSetTopicFailsOnInvalidTopic(): void + { + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage("Topic 'test' does not include '.push-type.liveactivity'!"); + + $this->class->setTopic('test'); + + $this->assertPropertySame('topic', 'test'); + } + + /** + * Test that setTopic() sets the activity topic. + * + * @covers \ApnsPHP\Message\LiveActivity::setTopic + */ + public function testSetTopic(): void + { + $this->class->setTopic('test.push-type.liveactivity'); + + $this->assertPropertySame('topic', 'test.push-type.liveactivity'); + } + + /** + * Test that setEvent() sets the activity event. + * + * @dataProvider eventDataProvider + * + * @param LiveActivityEvent $event The event to test with + * + * @covers \ApnsPHP\Message\LiveActivity::setEvent + */ + public function testSetEvent(LiveActivityEvent $event): void + { + $this->class->setEvent($event); + + $this->assertPropertySame('event', $event); + } + + /** + * Test that setAttributes() sets the activity attributes. + * + * @covers \ApnsPHP\Message\LiveActivity::setAttributes + */ + public function testSetAttributes(): void + { + $this->class->setAttributes([]); + $this->assertPropertySame('attributes', []); + + $object = (object) []; + $this->class->setAttributes($object); + $this->assertPropertySame('attributes', $object); + } + + /** + * Test that testSetAttributesType() sets the activity attribute type. + * + * @covers \ApnsPHP\Message\LiveActivity::setAttributesType + */ + public function testSetAttributesType(): void + { + $this->class->setAttributesType('SomeType'); + $this->assertPropertySame('attributes_type', 'SomeType'); + } + + /** + * Test that setStaleTime() sets the time the activity goes stale. + * + * @covers \ApnsPHP\Message\LiveActivity::setStaleTimestamp + */ + public function testSetStaleTime(): void + { + $this->class->setStaleTimestamp(1); + $this->assertPropertySame('stale_timestamp', 1); + } + + /** + * Test that setDismissTime() sets the time the activity dismisses. + * + * @covers \ApnsPHP\Message\LiveActivity::setDismissTimestamp + */ + public function testSetDismissTime(): void + { + $this->class->setDismissTimestamp(1); + $this->assertPropertySame('dismiss_timestamp', 1); + } + + /** + * Test that setContentState() sets the time the activity dismisses. + * + * @covers \ApnsPHP\Message\LiveActivity::setContentState + */ + public function testSetContentState(): void + { + $this->class->setContentState([]); + $this->assertPropertySame('state', []); + + $object = (object) []; + $this->class->setContentState($object); + $this->assertPropertySame('state', $object); + } +} diff --git a/ApnsPHP/Message/Tests/LiveActivityTestBase.php b/ApnsPHP/Message/Tests/LiveActivityTestBase.php new file mode 100644 index 0000000..a2e7f30 --- /dev/null +++ b/ApnsPHP/Message/Tests/LiveActivityTestBase.php @@ -0,0 +1,46 @@ +class = new LiveActivity(); + $this->baseSetUp($this->class); + } + + /** + * TestCase destructor + */ + public function tearDown(): void + { + parent::tearDown(); + unset($this->class); + } +}