diff --git a/.changeset/add-event-source-field.md b/.changeset/add-event-source-field.md new file mode 100644 index 00000000..3af37b1a --- /dev/null +++ b/.changeset/add-event-source-field.md @@ -0,0 +1,5 @@ +--- +'@fingerprint/php-sdk': minor +--- + +Add `source` field to `Event` to identify how the event was generated (`device` or `edge`) diff --git a/.openapi-generator/FILES b/.openapi-generator/FILES index 9a63a2bc..d117e72d 100644 --- a/.openapi-generator/FILES +++ b/.openapi-generator/FILES @@ -17,6 +17,7 @@ docs/Model/EventRuleAction.md docs/Model/EventRuleActionAllow.md docs/Model/EventRuleActionBlock.md docs/Model/EventSearch.md +docs/Model/EventSource.md docs/Model/EventUpdate.md docs/Model/FontPreferences.md docs/Model/Geolocation.md @@ -82,6 +83,7 @@ src/Model/EventRuleAction.php src/Model/EventRuleActionAllow.php src/Model/EventRuleActionBlock.php src/Model/EventSearch.php +src/Model/EventSource.php src/Model/EventUpdate.php src/Model/FontPreferences.php src/Model/Geolocation.php diff --git a/.schema-version b/.schema-version index 684a941c..c03b60b0 100644 --- a/.schema-version +++ b/.schema-version @@ -1 +1 @@ -v3.5.1 \ No newline at end of file +v3.6.0 \ No newline at end of file diff --git a/README.md b/README.md index e7fb41ac..c1bffacb 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,7 @@ Class | Method | HTTP request | Description - [EventRuleActionAllow](docs/Model/EventRuleActionAllow.md) - [EventRuleActionBlock](docs/Model/EventRuleActionBlock.md) - [EventSearch](docs/Model/EventSearch.md) + - [EventSource](docs/Model/EventSource.md) - [EventUpdate](docs/Model/EventUpdate.md) - [FontPreferences](docs/Model/FontPreferences.md) - [Geolocation](docs/Model/Geolocation.md) diff --git a/docs/Model/Event.md b/docs/Model/Event.md index bed1abb8..a64b4f6d 100644 --- a/docs/Model/Event.md +++ b/docs/Model/Event.md @@ -8,6 +8,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **event_id** | **string** | Unique identifier of the user's request. The first portion of the event_id is a unix epoch milliseconds timestamp. | **timestamp** | **int** | Timestamp of the event with millisecond precision in Unix time. | +**source** | [**\Fingerprint\ServerSdk\Model\EventSource**](EventSource.md) | | [optional] **incremental_identification_status** | [**\Fingerprint\ServerSdk\Model\IncrementalIdentificationStatus**](IncrementalIdentificationStatus.md) | | [optional] **linked_id** | **string** | A customer-provided id that was sent with the request. | [optional] **environment_id** | **string** | Environment Id of the event. | [optional] diff --git a/docs/Model/EventSource.md b/docs/Model/EventSource.md new file mode 100644 index 00000000..d2025369 --- /dev/null +++ b/docs/Model/EventSource.md @@ -0,0 +1,15 @@ +# EventSource Enum + +Identifies how the event was generated. +- `device` - the event was generated by the JS agent or a mobile SDK running on an end-user device. +- `edge` - the event was generated by the Automation Intelligence API (`/edge` endpoint), analyzing a request intercepted at the edge. + + +## Values + +| Name | Value | Description | +| --- | --- | --- | +DEVICE | 'device' | | +EDGE | 'edge' | | + +[[Back to Model list]](../../README.md#documentation-for-models) [[Back to API list]](../../README.md#endpoints) [[Back to README]](../../README.md) \ No newline at end of file diff --git a/res/fingerprint-server-api.yaml b/res/fingerprint-server-api.yaml index 2bac3291..f20cd1d4 100644 --- a/res/fingerprint-server-api.yaml +++ b/res/fingerprint-server-api.yaml @@ -1164,6 +1164,19 @@ components: format: int64 examples: - 1708102555327 + EventSource: + type: string + description: > + Identifies how the event was generated. + + - `device` - the event was generated by the JS agent or a mobile SDK + running on an end-user device. + + - `edge` - the event was generated by the Automation Intelligence API + (`/edge` endpoint), analyzing a request intercepted at the edge. + enum: + - device + - edge Url: type: string examples: @@ -3070,6 +3083,12 @@ components: - android - ios - browser + source: + $ref: '#/components/schemas/EventSource' + x-platforms: + - android + - ios + - browser incremental_identification_status: $ref: '#/components/schemas/IncrementalIdentificationStatus' x-platforms: diff --git a/src/Model/Event.php b/src/Model/Event.php index f52aebe1..10fdec8f 100644 --- a/src/Model/Event.php +++ b/src/Model/Event.php @@ -65,6 +65,7 @@ class Event implements ModelInterface, \ArrayAccess, \JsonSerializable protected static array $openAPITypes = [ 'event_id' => 'string', 'timestamp' => 'int', + 'source' => '\Fingerprint\ServerSdk\Model\EventSource', 'incremental_identification_status' => '\Fingerprint\ServerSdk\Model\IncrementalIdentificationStatus', 'linked_id' => 'string', 'environment_id' => 'string', @@ -141,6 +142,7 @@ class Event implements ModelInterface, \ArrayAccess, \JsonSerializable protected static array $openAPIFormats = [ 'event_id' => null, 'timestamp' => 'int64', + 'source' => null, 'incremental_identification_status' => null, 'linked_id' => null, 'environment_id' => null, @@ -213,6 +215,7 @@ class Event implements ModelInterface, \ArrayAccess, \JsonSerializable protected static array $openAPINullables = [ 'event_id' => false, 'timestamp' => false, + 'source' => false, 'incremental_identification_status' => false, 'linked_id' => false, 'environment_id' => false, @@ -293,6 +296,7 @@ class Event implements ModelInterface, \ArrayAccess, \JsonSerializable protected static array $attributeMap = [ 'event_id' => 'event_id', 'timestamp' => 'timestamp', + 'source' => 'source', 'incremental_identification_status' => 'incremental_identification_status', 'linked_id' => 'linked_id', 'environment_id' => 'environment_id', @@ -365,6 +369,7 @@ class Event implements ModelInterface, \ArrayAccess, \JsonSerializable protected static array $setters = [ 'event_id' => 'setEventId', 'timestamp' => 'setTimestamp', + 'source' => 'setSource', 'incremental_identification_status' => 'setIncrementalIdentificationStatus', 'linked_id' => 'setLinkedId', 'environment_id' => 'setEnvironmentId', @@ -437,6 +442,7 @@ class Event implements ModelInterface, \ArrayAccess, \JsonSerializable protected static array $getters = [ 'event_id' => 'getEventId', 'timestamp' => 'getTimestamp', + 'source' => 'getSource', 'incremental_identification_status' => 'getIncrementalIdentificationStatus', 'linked_id' => 'getLinkedId', 'environment_id' => 'getEnvironmentId', @@ -518,6 +524,7 @@ public function __construct(?array $data = null) { $this->setIfExists('event_id', $data ?? [], null); $this->setIfExists('timestamp', $data ?? [], null); + $this->setIfExists('source', $data ?? [], null); $this->setIfExists('incremental_identification_status', $data ?? [], null); $this->setIfExists('linked_id', $data ?? [], null); $this->setIfExists('environment_id', $data ?? [], null); @@ -771,6 +778,28 @@ public function setTimestamp(int $timestamp): self return $this; } + /** + * Gets source. + * + */ + public function getSource(): EventSource|string|null + { + return $this->container['source']; + } + + /** + * Sets source. + * + * @param EventSource|string $source source + * + */ + public function setSource(EventSource|string $source): self + { + $this->container['source'] = $source; + + return $this; + } + /** * Gets incremental_identification_status. * diff --git a/src/Model/EventSource.php b/src/Model/EventSource.php new file mode 100644 index 00000000..bfeaa525 --- /dev/null +++ b/src/Model/EventSource.php @@ -0,0 +1,52 @@ +assertEquals(MockHelper::MOCK_EVENT_ID, $event->getEventId()); $this->assertEquals($actual->timestamp, $event->getTimestamp()); + $this->assertEquals(EventSource::DEVICE, $event->getSource()); $this->assertEquals(IncrementalIdentificationStatus::COMPLETED, $event->getIncrementalIdentificationStatus()); $this->assertEquals($actual->linked_id, $event->getLinkedId()); $this->assertEquals(null, $event->getEnvironmentId()); diff --git a/test/Model/EnumCoverageTest.php b/test/Model/EnumCoverageTest.php index 7f8be266..a26fed22 100644 --- a/test/Model/EnumCoverageTest.php +++ b/test/Model/EnumCoverageTest.php @@ -7,6 +7,7 @@ use Fingerprint\ServerSdk\Model\BotInfoIdentity; use Fingerprint\ServerSdk\Model\BotResult; use Fingerprint\ServerSdk\Model\ErrorCode; +use Fingerprint\ServerSdk\Model\EventSource; use Fingerprint\ServerSdk\Model\IncrementalIdentificationStatus; use Fingerprint\ServerSdk\Model\ProxyConfidence; use Fingerprint\ServerSdk\Model\RareDevicePercentileBucket; @@ -37,6 +38,7 @@ #[CoversClass(BotInfoIdentity::class)] #[CoversClass(BotResult::class)] #[CoversClass(ErrorCode::class)] +#[CoversClass(EventSource::class)] #[CoversClass(IncrementalIdentificationStatus::class)] #[CoversClass(ProxyConfidence::class)] #[CoversClass(RareDevicePercentileBucket::class)] @@ -103,6 +105,7 @@ public static function enumClassProvider(): array 'BotInfoIdentity' => [BotInfoIdentity::class], 'BotResult' => [BotResult::class], 'ErrorCode' => [ErrorCode::class], + 'EventSource' => [EventSource::class], 'IncrementalIdentificationStatus' => [IncrementalIdentificationStatus::class], 'ProxyConfidence' => [ProxyConfidence::class], 'RareDevicePercentileBucket' => [RareDevicePercentileBucket::class], diff --git a/test/Model/EventSourceTest.php b/test/Model/EventSourceTest.php new file mode 100644 index 00000000..9a6d43b3 --- /dev/null +++ b/test/Model/EventSourceTest.php @@ -0,0 +1,61 @@ +assertCount(2, $cases); + $this->assertContains(EventSource::DEVICE, $cases); + $this->assertContains(EventSource::EDGE, $cases); + } + + /** + * Tests that each enum case has the correct backing value. + */ + public function testEnumBackingValues(): void + { + $this->assertEquals('device', EventSource::DEVICE->value); + $this->assertEquals('edge', EventSource::EDGE->value); + } + + /** + * Tests that valid backing values can be converted to enum instances using from(). + */ + public function testFromValidValue(): void + { + $this->assertSame(EventSource::DEVICE, EventSource::from('device')); + $this->assertSame(EventSource::EDGE, EventSource::from('edge')); + } + + /** + * Tests that tryFrom() returns null for an invalid backing value. + */ + public function testTryFromInvalidValue(): void + { + $this->assertNull(EventSource::tryFrom('invalid')); + } + + /** + * Tests that the ObjectSerializer correctly deserializes a string into the enum. + */ + public function testDeserialization(): void + { + $this->assertSame(EventSource::DEVICE, ObjectSerializer::deserialize('device', EventSource::class)); + $this->assertSame(EventSource::EDGE, ObjectSerializer::deserialize('edge', EventSource::class)); + } +} diff --git a/test/Model/EventTest.php b/test/Model/EventTest.php index 1064e8f4..967f620e 100644 --- a/test/Model/EventTest.php +++ b/test/Model/EventTest.php @@ -3,6 +3,7 @@ namespace Fingerprint\ServerSdk\Test\Model; use Fingerprint\ServerSdk\Model\Event; +use Fingerprint\ServerSdk\Model\EventSource; use Fingerprint\ServerSdk\ObjectSerializer; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; @@ -173,6 +174,72 @@ public function testIsNullableSetToNull(): void $this->assertFalse($model->isNullableSetToNull('timestamp')); } + /** + * Deserializing an event should map the source values to the matching enum cases. + * + * @throws \DateMalformedStringException + */ + public function testDeserializationWithSource(): void + { + foreach ([EventSource::DEVICE, EventSource::EDGE] as $expected) { + $json = json_encode(self::EXAMPLE + ['source' => $expected->value]); + + /** @var Event $model */ + $model = ObjectSerializer::deserialize(json_decode($json), Event::class); + + $this->assertSame($expected, $model->getSource()); + } + } + + /** + * Source is optional, so an event payload without it should deserialize with a null source. + * + * @throws \DateMalformedStringException + */ + public function testDeserializationWithoutSource(): void + { + $json = json_encode(self::EXAMPLE); + + /** @var Event $model */ + $model = ObjectSerializer::deserialize(json_decode($json), Event::class); + + $this->assertNull($model->getSource()); + $this->assertTrue($model->valid()); + } + + /** + * Deserializing a JSON response with an unknown source should not throw. + * The unknown value should be preserved through deserialization and re-serialization. + * + * @throws \DateMalformedStringException + */ + public function testDeserializationWithUnknownSource(): void + { + $json = json_encode(self::EXAMPLE + ['source' => 'unknown-value']); + + /** @var Event $model */ + $model = ObjectSerializer::deserialize(json_decode($json), Event::class); + + $this->assertEquals('unknown-value', $model->getSource()); + $this->assertTrue($model->valid()); + + // Re-serialization should preserve the unknown value + $reserialized = json_encode(ObjectSerializer::sanitizeForSerialization($model)); + $decoded = json_decode($reserialized, true); + $this->assertEquals('unknown-value', $decoded['source']); + } + + /** + * setSource should accept unknown enum values without throwing. + */ + public function testSetSourceAcceptsUnknownValue(): void + { + $model = new Event(); + + $model->setSource('unknown-value'); + $this->assertEquals('unknown-value', $model->getSource()); + } + /** * Constructor with null value for a non-nullable field should still store null (default path of setIfExists). */ diff --git a/test/mocks/events/get_event_200.json b/test/mocks/events/get_event_200.json index 8c40bcd6..6d6c726e 100644 --- a/test/mocks/events/get_event_200.json +++ b/test/mocks/events/get_event_200.json @@ -3,6 +3,7 @@ "tags": {}, "timestamp": 1708102555327, "event_id": "1708102555327.NLOjmg", + "source": "device", "incremental_identification_status": "completed", "url": "https://www.example.com/login?hope{this{works[!", "ip_address": "61.127.217.15", diff --git a/test/mocks/events/get_event_200_with_unknown_field.json b/test/mocks/events/get_event_200_with_unknown_field.json index b87f4864..bdbf8715 100644 --- a/test/mocks/events/get_event_200_with_unknown_field.json +++ b/test/mocks/events/get_event_200_with_unknown_field.json @@ -3,6 +3,7 @@ "tags": {}, "timestamp": 1708102555327, "event_id": "1708102555327.NLOjmg", + "source": "device", "incremental_identification_status": "completed", "url": "https://www.example.com/login?hope{this{works[!", "ip_address": "61.127.217.15", diff --git a/test/mocks/events/get_event_ruleset_200.json b/test/mocks/events/get_event_ruleset_200.json index b312bfe5..1e5337c0 100644 --- a/test/mocks/events/get_event_ruleset_200.json +++ b/test/mocks/events/get_event_ruleset_200.json @@ -3,6 +3,7 @@ "tags": {}, "timestamp": 1708102555327, "event_id": "1708102555327.NLOjmg", + "source": "device", "incremental_identification_status": "completed", "url": "https://www.example.com/login?hope{this{works[!", "ip_address": "61.127.217.15", diff --git a/test/mocks/events/get_event_with_bot_info_200.json b/test/mocks/events/get_event_with_bot_info_200.json index 5b5427aa..b44a687a 100644 --- a/test/mocks/events/get_event_with_bot_info_200.json +++ b/test/mocks/events/get_event_with_bot_info_200.json @@ -3,6 +3,7 @@ "tags": {}, "timestamp": 1708102555327, "event_id": "1708102555327.NLOjmg", + "source": "device", "incremental_identification_status": "completed", "url": "https://www.example.com/login?hope{this{works[!", "ip_address": "61.127.217.15", diff --git a/test/mocks/events/search/get_event_search_200.json b/test/mocks/events/search/get_event_search_200.json index ca87827b..daad06b0 100644 --- a/test/mocks/events/search/get_event_search_200.json +++ b/test/mocks/events/search/get_event_search_200.json @@ -5,6 +5,7 @@ "tags": {}, "timestamp": 1708102555327, "event_id": "1708102555327.NLOjmg", + "source": "device", "incremental_identification_status": "completed", "url": "https://www.example.com/login?hope{this{works[!", "ip_address": "61.127.217.15",