From a309e4339aa38000dbca670c37c2fd0f681f22bf Mon Sep 17 00:00:00 2001 From: Andreas Grill <1265844+andreasgrill@users.noreply.github.com> Date: Thu, 5 Feb 2026 18:38:07 +0100 Subject: [PATCH 1/2] build: fix testcontainers compatibility with Docker Desktop 29.x Update testcontainers from to 2.0.2 to fix Docker Desktop 29.x compatibility issue. Docker Engine 29 requires minimum API version 1.44, which older testcontainers versions don't support properly. Co-Authored-By: Claude Opus 4.5 --- xesar-connect/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xesar-connect/build.gradle.kts b/xesar-connect/build.gradle.kts index a47d139c..3b1d6a3a 100644 --- a/xesar-connect/build.gradle.kts +++ b/xesar-connect/build.gradle.kts @@ -33,6 +33,7 @@ val logbackVersion: String = "1.5.17" val pahoVersion: String = "1.2.5" val mockkVersion: String = "1.13.17" val kotestTestcontainersVersion: String = "2.0.2" +val testcontainersVersion: String = "2.0.2" dependencies { implementation("org.eclipse.paho:org.eclipse.paho.client.mqttv3:$pahoVersion") @@ -52,6 +53,7 @@ dependencies { testImplementation( "io.kotest.extensions:kotest-extensions-testcontainers:$kotestTestcontainersVersion" ) + testImplementation("org.testcontainers:testcontainers:$testcontainersVersion") } tasks.test { useJUnitPlatform() } From 360a6a0ebb23c5735d30bd384263086ac10ff202 Mon Sep 17 00:00:00 2001 From: Andreas Grill <1265844+andreasgrill@users.noreply.github.com> Date: Thu, 5 Feb 2026 18:35:22 +0100 Subject: [PATCH 2/2] feat: add commands and events for smartphone revoke, unassign person, and self service mode Co-Authored-By: Claude Opus 4.6 --- README.md | 4 +- .../com/open200/xesar/connect/Topics.kt | 54 ++++ .../XesarConnectIdentificationMediumExt.kt | 19 ++ .../extension/XesarConnectSmartphoneExt.kt | 71 +++++ .../command/ConfirmSmartphoneRevokeMapi.kt | 21 ++ .../command/ConfirmSmartphoneUpdateMapi.kt | 21 ++ .../messages/command/RevokeSmartphoneMapi.kt | 19 ++ .../command/UnassignPersonFromMediumMapi.kt | 19 ++ ...zationProfileMetadataDefinitionsUpdated.kt | 19 ++ ...allationPointMetadataDefinitionsUpdated.kt | 19 ++ .../event/MediumMetadataDefinitionsUpdated.kt | 18 ++ .../connect/messages/event/MediumRevoked.kt | 32 ++ .../event/MobileRegistrationStateUpdated.kt | 17 ++ .../event/PersonMetadataDefinitionsUpdated.kt | 18 ++ .../messages/event/SmartphoneLocked.kt | 32 ++ .../event/SmartphoneRevokeConfirmed.kt | 17 ++ .../messages/event/SmartphoneRevokePending.kt | 18 ++ .../event/SmartphoneUpdateConfirmed.kt | 17 ++ .../messages/event/SmartphoneUpdatePending.kt | 70 +++++ .../event/ZoneMetadataDefinitionsUpdated.kt | 18 ++ ...dataDefinitionsUpdatedSerializationTest.kt | 146 ++++++++++ ...gistrationStateUpdatedSerializationTest.kt | 42 +++ .../SelfServiceModeEventsSerializationTest.kt | 184 ++++++++++++ .../it/command/ConfirmSmartphoneRevokeTest.kt | 92 ++++++ .../it/command/ConfirmSmartphoneUpdateTest.kt | 92 ++++++ .../it/command/RevokeSmartphoneTest.kt | 85 ++++++ .../command/UnassignPersonFromMediumTest.kt | 86 ++++++ .../EventMetadataDefinitionsUpdatedTest.kt | 273 ++++++++++++++++++ ...EventMobileRegistrationStateUpdatedTest.kt | 75 +++++ .../it/event/EventSelfServiceModeTest.kt | 184 ++++++++++++ 30 files changed, 1780 insertions(+), 2 deletions(-) create mode 100644 xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/ConfirmSmartphoneRevokeMapi.kt create mode 100644 xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/ConfirmSmartphoneUpdateMapi.kt create mode 100644 xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/RevokeSmartphoneMapi.kt create mode 100644 xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/UnassignPersonFromMediumMapi.kt create mode 100644 xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/AuthorizationProfileMetadataDefinitionsUpdated.kt create mode 100644 xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/InstallationPointMetadataDefinitionsUpdated.kt create mode 100644 xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/MediumMetadataDefinitionsUpdated.kt create mode 100644 xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/MediumRevoked.kt create mode 100644 xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/MobileRegistrationStateUpdated.kt create mode 100644 xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/PersonMetadataDefinitionsUpdated.kt create mode 100644 xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneLocked.kt create mode 100644 xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneRevokeConfirmed.kt create mode 100644 xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneRevokePending.kt create mode 100644 xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneUpdateConfirmed.kt create mode 100644 xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneUpdatePending.kt create mode 100644 xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/ZoneMetadataDefinitionsUpdated.kt create mode 100644 xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/event/MetadataDefinitionsUpdatedSerializationTest.kt create mode 100644 xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/event/MobileRegistrationStateUpdatedSerializationTest.kt create mode 100644 xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/event/SelfServiceModeEventsSerializationTest.kt create mode 100644 xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/ConfirmSmartphoneRevokeTest.kt create mode 100644 xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/ConfirmSmartphoneUpdateTest.kt create mode 100644 xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/RevokeSmartphoneTest.kt create mode 100644 xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/UnassignPersonFromMediumTest.kt create mode 100644 xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/event/EventMetadataDefinitionsUpdatedTest.kt create mode 100644 xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/event/EventMobileRegistrationStateUpdatedTest.kt create mode 100644 xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/event/EventSelfServiceModeTest.kt diff --git a/README.md b/README.md index e481b667..725ed034 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,12 @@ You can add Xesar-Connect to your project by including it as a dependency in you ## Compatibility -Xesar-Connect is tested with EVVA's Xesar version 3.1 with Mqtt API version 1.2.1 see [EVVA Xesar Mqtt API](https://integrations.api.xesar.evva.com/) +Xesar-Connect is compatible with EVVA's Xesar version 3.3 with Mqtt API version 1.37.0 see [EVVA Xesar Mqtt API](https://integrations.api.xesar.evva.com/) ```kotlin dependencies { - implementation("com.open200:xesar-connect:1.0.0") + implementation("com.open200:xesar-connect:2.2.0") } ``` diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/Topics.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/Topics.kt index 46f77998..7c72c263 100644 --- a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/Topics.kt +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/Topics.kt @@ -190,6 +190,44 @@ class Topics(vararg val topics: String) { /** MQTT topic string for the "SmartphoneAuthorizationsResent" event */ val SMARTPHONE_AUTHORIZATIONS_RESENT = "xs3/1/ces/SmartphoneAuthorizationsResent" + /** MQTT topic string for the "MediumRevoked" event */ + val MEDIUM_REVOKED = "xs3/1/ces/MediumRevoked" + + /** MQTT topic string for the "MobileRegistrationStateUpdated" event */ + val MOBILE_REGISTRATION_STATE_UPDATED = "xs3/1/ces/MobileRegistrationStateUpdated" + + /** MQTT topic string for the "AuthorizationProfileMetadataDefinitionsUpdated" event */ + val AUTHORIZATION_PROFILE_METADATA_DEFINITIONS_UPDATED = + "xs3/1/ces/AuthorizationProfileMetadataDefinitionsUpdated" + + /** MQTT topic string for the "InstallationPointMetadataDefinitionsUpdated" event */ + val INSTALLATION_POINT_METADATA_DEFINITIONS_UPDATED = + "xs3/1/ces/InstallationPointMetadataDefinitionsUpdated" + + /** MQTT topic string for the "MediumMetadataDefinitionsUpdated" event */ + val MEDIUM_METADATA_DEFINITIONS_UPDATED = "xs3/1/ces/MediumMetadataDefinitionsUpdated" + + /** MQTT topic string for the "PersonMetadataDefinitionsUpdated" event */ + val PERSON_METADATA_DEFINITIONS_UPDATED = "xs3/1/ces/PersonMetadataDefinitionsUpdated" + + /** MQTT topic string for the "ZoneMetadataDefinitionsUpdated" event */ + val ZONE_METADATA_DEFINITIONS_UPDATED = "xs3/1/ces/ZoneMetadataDefinitionsUpdated" + + /** MQTT topic string for the "SmartphoneLocked" event (Self Service Mode) */ + val SMARTPHONE_LOCKED = "xs3/1/mss/ces/SmartphoneLocked" + + /** MQTT topic string for the "SmartphoneRevokePending" event (Self Service Mode) */ + val SMARTPHONE_REVOKE_PENDING = "xs3/1/mss/ces/SmartphoneRevokePending" + + /** MQTT topic string for the "SmartphoneRevokeConfirmed" event (Self Service Mode) */ + val SMARTPHONE_REVOKE_CONFIRMED = "xs3/1/mss/ces/SmartphoneRevokeConfirmed" + + /** MQTT topic string for the "SmartphoneUpdatePending" event (Self Service Mode) */ + val SMARTPHONE_UPDATE_PENDING = "xs3/1/mss/ces/SmartphoneUpdatePending" + + /** MQTT topic string for the "SmartphoneUpdateConfirmed" event (Self Service Mode) */ + val SMARTPHONE_UPDATE_CONFIRMED = "xs3/1/mss/ces/SmartphoneUpdateConfirmed" + /** * Generates the MQTT topic string which emits errors for previously received queries or * commands. @@ -491,6 +529,22 @@ class Topics(vararg val topics: String) { /** MQTT topic string for the "ChangeAuthorizationProfileMetadataValueMapi" command. */ val CHANGE_AUTHORIZATION_PROFILE_METADATA_VALUE = "xs3/1/cmd/ChangeAuthorizationProfileMetadataValueMapi" + + /** MQTT topic string for the "RevokeSmartphoneMapi" command. */ + val REVOKE_SMARTPHONE = "xs3/1/cmd/RevokeSmartphoneMapi" + + /** MQTT topic string for the "UnassignPersonFromMediumMapi" command. */ + val UNASSIGN_PERSON_FROM_MEDIUM = "xs3/1/cmd/UnassignPersonFromMediumMapi" + + /** + * MQTT topic string for the "ConfirmSmartphoneRevokeMapi" command (Self Service Mode). + */ + val CONFIRM_SMARTPHONE_REVOKE = "xs3/1/mss/cmd/ConfirmSmartphoneRevokeMapi" + + /** + * MQTT topic string for the "ConfirmSmartphoneUpdateMapi" command (Self Service Mode). + */ + val CONFIRM_SMARTPHONE_UPDATE = "xs3/1/mss/cmd/ConfirmSmartphoneUpdateMapi" } } diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/extension/XesarConnectIdentificationMediumExt.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/extension/XesarConnectIdentificationMediumExt.kt index c305ebb3..3e0f41ea 100644 --- a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/extension/XesarConnectIdentificationMediumExt.kt +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/extension/XesarConnectIdentificationMediumExt.kt @@ -527,3 +527,22 @@ fun XesarConnect.queryStreamIdentificationMedium( ): Flow { return queryStream(IdentificationMedium.QUERY_RESOURCE, params, requestConfig) } + +/** + * Unassigns a person from a medium asynchronously. + * + * @param mediumId The ID of the medium to unassign the person from. + * @param requestConfig The request configuration (optional). + */ +suspend fun XesarConnect.unassignPersonFromMediumAsync( + mediumId: UUID, + requestConfig: XesarConnect.RequestConfig = buildRequestConfig(), +): SingleEventResult { + return sendCommandAsync( + Topics.Command.UNASSIGN_PERSON_FROM_MEDIUM, + Topics.Event.MEDIUM_PERSON_CHANGED, + true, + UnassignPersonFromMediumMapi(config.uuidGenerator.generateId(), mediumId, token), + requestConfig, + ) +} diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/extension/XesarConnectSmartphoneExt.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/extension/XesarConnectSmartphoneExt.kt index f9624f53..6df01fa6 100644 --- a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/extension/XesarConnectSmartphoneExt.kt +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/extension/XesarConnectSmartphoneExt.kt @@ -217,3 +217,74 @@ suspend fun XesarConnect.resendSmartphoneAuthorizationsAsync( requestConfig, ) } + +/** + * Revokes a smartphone medium asynchronously. + * + * @param id The id of the smartphone medium. + * @param requestConfig The request configuration (optional). + */ +suspend fun XesarConnect.revokeSmartphoneAsync( + id: UUID, + requestConfig: XesarConnect.RequestConfig = buildRequestConfig(), +): SingleEventResult { + return sendCommandAsync( + Topics.Command.REVOKE_SMARTPHONE, + Topics.Event.MEDIUM_REVOKED, + true, + RevokeSmartphoneMapi(config.uuidGenerator.generateId(), id, token), + requestConfig, + ) +} + +/** + * Confirms a smartphone revocation in Self Service Mode asynchronously. + * + * @param mediumId The id of the smartphone medium. + * @param transactionId The id of the transaction. + * @param requestConfig The request configuration (optional). + */ +suspend fun XesarConnect.confirmSmartphoneRevokeAsync( + mediumId: UUID, + transactionId: UUID, + requestConfig: XesarConnect.RequestConfig = buildRequestConfig(), +): SingleEventResult { + return sendCommandAsync( + Topics.Command.CONFIRM_SMARTPHONE_REVOKE, + Topics.Event.SMARTPHONE_REVOKE_CONFIRMED, + true, + ConfirmSmartphoneRevokeMapi( + config.uuidGenerator.generateId(), + mediumId, + transactionId, + token, + ), + requestConfig, + ) +} + +/** + * Confirms a smartphone update in Self Service Mode asynchronously. + * + * @param mediumId The id of the smartphone medium. + * @param transactionId The id of the transaction. + * @param requestConfig The request configuration (optional). + */ +suspend fun XesarConnect.confirmSmartphoneUpdateAsync( + mediumId: UUID, + transactionId: UUID, + requestConfig: XesarConnect.RequestConfig = buildRequestConfig(), +): SingleEventResult { + return sendCommandAsync( + Topics.Command.CONFIRM_SMARTPHONE_UPDATE, + Topics.Event.SMARTPHONE_UPDATE_CONFIRMED, + true, + ConfirmSmartphoneUpdateMapi( + config.uuidGenerator.generateId(), + mediumId, + transactionId, + token, + ), + requestConfig, + ) +} diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/ConfirmSmartphoneRevokeMapi.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/ConfirmSmartphoneRevokeMapi.kt new file mode 100644 index 00000000..0e481b6f --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/ConfirmSmartphoneRevokeMapi.kt @@ -0,0 +1,21 @@ +package com.open200.xesar.connect.messages.command + +import com.open200.xesar.connect.utils.UUIDSerializer +import java.util.* +import kotlinx.serialization.Serializable + +/** + * Represents a command POJO to confirm a smartphone revocation in Self Service Mode. + * + * @param commandId The id of the command. + * @param mediumId The id of the smartphone medium. + * @param transactionId The id of the transaction. + * @param token The token of the command. + */ +@Serializable +data class ConfirmSmartphoneRevokeMapi( + override val commandId: @Serializable(with = UUIDSerializer::class) UUID, + val mediumId: @Serializable(with = UUIDSerializer::class) UUID, + val transactionId: @Serializable(with = UUIDSerializer::class) UUID, + val token: String, +) : Command diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/ConfirmSmartphoneUpdateMapi.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/ConfirmSmartphoneUpdateMapi.kt new file mode 100644 index 00000000..0d5ed9c4 --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/ConfirmSmartphoneUpdateMapi.kt @@ -0,0 +1,21 @@ +package com.open200.xesar.connect.messages.command + +import com.open200.xesar.connect.utils.UUIDSerializer +import java.util.* +import kotlinx.serialization.Serializable + +/** + * Represents a command POJO to confirm a smartphone update in Self Service Mode. + * + * @param commandId The id of the command. + * @param mediumId The id of the smartphone medium. + * @param transactionId The id of the transaction. + * @param token The token of the command. + */ +@Serializable +data class ConfirmSmartphoneUpdateMapi( + override val commandId: @Serializable(with = UUIDSerializer::class) UUID, + val mediumId: @Serializable(with = UUIDSerializer::class) UUID, + val transactionId: @Serializable(with = UUIDSerializer::class) UUID, + val token: String, +) : Command diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/RevokeSmartphoneMapi.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/RevokeSmartphoneMapi.kt new file mode 100644 index 00000000..1afc498f --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/RevokeSmartphoneMapi.kt @@ -0,0 +1,19 @@ +package com.open200.xesar.connect.messages.command + +import com.open200.xesar.connect.utils.UUIDSerializer +import java.util.* +import kotlinx.serialization.Serializable + +/** + * Represents a command POJO to revoke a smartphone medium. + * + * @param commandId The id of the command. + * @param id The id of the smartphone medium. + * @param token The token of the command. + */ +@Serializable +data class RevokeSmartphoneMapi( + override val commandId: @Serializable(with = UUIDSerializer::class) UUID, + val id: @Serializable(with = UUIDSerializer::class) UUID, + val token: String, +) : Command diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/UnassignPersonFromMediumMapi.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/UnassignPersonFromMediumMapi.kt new file mode 100644 index 00000000..86c96bf7 --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/command/UnassignPersonFromMediumMapi.kt @@ -0,0 +1,19 @@ +package com.open200.xesar.connect.messages.command + +import com.open200.xesar.connect.utils.UUIDSerializer +import java.util.* +import kotlinx.serialization.Serializable + +/** + * Represents a command POJO to unassign a person from a medium. + * + * @param commandId The id of the command. + * @param mediumId The id of the medium. + * @param token The token of the command. + */ +@Serializable +data class UnassignPersonFromMediumMapi( + override val commandId: @Serializable(with = UUIDSerializer::class) UUID, + val mediumId: @Serializable(with = UUIDSerializer::class) UUID, + val token: String, +) : Command diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/AuthorizationProfileMetadataDefinitionsUpdated.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/AuthorizationProfileMetadataDefinitionsUpdated.kt new file mode 100644 index 00000000..cfa185dc --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/AuthorizationProfileMetadataDefinitionsUpdated.kt @@ -0,0 +1,19 @@ +package com.open200.xesar.connect.messages.event + +import com.open200.xesar.connect.messages.EntityMetadata +import com.open200.xesar.connect.utils.UUIDSerializer +import java.util.* +import kotlinx.serialization.Serializable + +/** + * Represents an event POJO as a response of a command to change of an authorization profile + * metadata definition. + * + * @param entityMetadata Contains the information of the metadata definitions. + * @param id The id of the updated authorization profile. + */ +@Serializable +data class AuthorizationProfileMetadataDefinitionsUpdated( + val entityMetadata: List, + @Serializable(with = UUIDSerializer::class) val id: UUID, +) : Event diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/InstallationPointMetadataDefinitionsUpdated.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/InstallationPointMetadataDefinitionsUpdated.kt new file mode 100644 index 00000000..f91a4af6 --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/InstallationPointMetadataDefinitionsUpdated.kt @@ -0,0 +1,19 @@ +package com.open200.xesar.connect.messages.event + +import com.open200.xesar.connect.messages.EntityMetadata +import com.open200.xesar.connect.utils.UUIDSerializer +import java.util.* +import kotlinx.serialization.Serializable + +/** + * Represents an event POJO as a response of a command to change of an installation point metadata + * definition. + * + * @param entityMetadata Contains the information of the metadata definitions. + * @param id The id of the updated installation point. + */ +@Serializable +data class InstallationPointMetadataDefinitionsUpdated( + val entityMetadata: List, + @Serializable(with = UUIDSerializer::class) val id: UUID, +) : Event diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/MediumMetadataDefinitionsUpdated.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/MediumMetadataDefinitionsUpdated.kt new file mode 100644 index 00000000..170f9e1d --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/MediumMetadataDefinitionsUpdated.kt @@ -0,0 +1,18 @@ +package com.open200.xesar.connect.messages.event + +import com.open200.xesar.connect.messages.EntityMetadata +import com.open200.xesar.connect.utils.UUIDSerializer +import java.util.* +import kotlinx.serialization.Serializable + +/** + * Represents an event POJO as a response of a command to change of a medium metadata definition. + * + * @param entityMetadata Contains the information of the metadata definitions. + * @param id The id of the updated medium. + */ +@Serializable +data class MediumMetadataDefinitionsUpdated( + val entityMetadata: List, + @Serializable(with = UUIDSerializer::class) val id: UUID, +) : Event diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/MediumRevoked.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/MediumRevoked.kt new file mode 100644 index 00000000..a0602040 --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/MediumRevoked.kt @@ -0,0 +1,32 @@ +package com.open200.xesar.connect.messages.event + +import com.open200.xesar.connect.utils.LocalDateTimeSerializer +import com.open200.xesar.connect.utils.UUIDSerializer +import java.time.LocalDateTime +import java.util.* +import kotlinx.serialization.Serializable + +/** + * Represents an event POJO indicating a medium was revoked. + * + * @param aggregateId The id of the medium. + * @param installationPoints The installation points of the medium. + * @param mediumIdentifier The identifier of the medium. + * @param authorizationProfileId The authorization profile id of the medium. + * @param individualAuthorizations The individual authorizations of the medium. + * @param validityEnd The validity end of the medium. + * @param zones The zones of the medium. + * @param hasMasterKeyAccess Whether the medium has master key access. + */ +@Serializable +data class MediumRevoked( + val aggregateId: @Serializable(with = UUIDSerializer::class) UUID, + val installationPoints: List<@Serializable(with = UUIDSerializer::class) UUID> = emptyList(), + val mediumIdentifier: Long, + val authorizationProfileId: @Serializable(with = UUIDSerializer::class) UUID? = null, + val individualAuthorizations: List<@Serializable(with = UUIDSerializer::class) UUID> = + emptyList(), + val validityEnd: @Serializable(with = LocalDateTimeSerializer::class) LocalDateTime? = null, + val zones: List<@Serializable(with = UUIDSerializer::class) UUID> = emptyList(), + val hasMasterKeyAccess: Boolean, +) : Event diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/MobileRegistrationStateUpdated.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/MobileRegistrationStateUpdated.kt new file mode 100644 index 00000000..c1d0dc1e --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/MobileRegistrationStateUpdated.kt @@ -0,0 +1,17 @@ +package com.open200.xesar.connect.messages.event + +import com.open200.xesar.connect.utils.UUIDSerializer +import java.util.* +import kotlinx.serialization.Serializable + +/** + * Represents an event POJO indicating the mobile registration state was updated. + * + * @param id The id of the smartphone medium. + * @param registrationState The new registration state of the smartphone medium. + */ +@Serializable +data class MobileRegistrationStateUpdated( + @Serializable(with = UUIDSerializer::class) val id: UUID, + val registrationState: String? = null, +) : Event diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/PersonMetadataDefinitionsUpdated.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/PersonMetadataDefinitionsUpdated.kt new file mode 100644 index 00000000..d6314d9d --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/PersonMetadataDefinitionsUpdated.kt @@ -0,0 +1,18 @@ +package com.open200.xesar.connect.messages.event + +import com.open200.xesar.connect.messages.EntityMetadata +import com.open200.xesar.connect.utils.UUIDSerializer +import java.util.* +import kotlinx.serialization.Serializable + +/** + * Represents an event POJO as a response of a command to change of a person metadata definition. + * + * @param entityMetadata Contains the information of the metadata definitions. + * @param id The id of the updated person. + */ +@Serializable +data class PersonMetadataDefinitionsUpdated( + val entityMetadata: List, + @Serializable(with = UUIDSerializer::class) val id: UUID, +) : Event diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneLocked.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneLocked.kt new file mode 100644 index 00000000..5897e419 --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneLocked.kt @@ -0,0 +1,32 @@ +package com.open200.xesar.connect.messages.event + +import com.open200.xesar.connect.utils.LocalDateTimeSerializer +import com.open200.xesar.connect.utils.UUIDSerializer +import java.time.LocalDateTime +import java.util.* +import kotlinx.serialization.Serializable + +/** + * Represents an event POJO indicating a smartphone was locked in Self Service Mode. + * + * @param aggregateId The id of the smartphone medium. + * @param installationPoints The installation points of the smartphone medium. + * @param mediumIdentifier The identifier of the smartphone medium. + * @param authorizationProfileId The authorization profile id of the smartphone medium. + * @param individualAuthorizations The individual authorizations of the smartphone medium. + * @param validityEnd The validity end of the smartphone medium. + * @param zones The zones of the smartphone medium. + * @param hasMasterKeyAccess Whether the smartphone medium has master key access. + */ +@Serializable +data class SmartphoneLocked( + val aggregateId: @Serializable(with = UUIDSerializer::class) UUID, + val installationPoints: List<@Serializable(with = UUIDSerializer::class) UUID> = emptyList(), + val mediumIdentifier: Long, + val authorizationProfileId: @Serializable(with = UUIDSerializer::class) UUID? = null, + val individualAuthorizations: List<@Serializable(with = UUIDSerializer::class) UUID> = + emptyList(), + val validityEnd: @Serializable(with = LocalDateTimeSerializer::class) LocalDateTime? = null, + val zones: List<@Serializable(with = UUIDSerializer::class) UUID> = emptyList(), + val hasMasterKeyAccess: Boolean, +) : Event diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneRevokeConfirmed.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneRevokeConfirmed.kt new file mode 100644 index 00000000..6b2ec96b --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneRevokeConfirmed.kt @@ -0,0 +1,17 @@ +package com.open200.xesar.connect.messages.event + +import com.open200.xesar.connect.utils.UUIDSerializer +import java.util.* +import kotlinx.serialization.Serializable + +/** + * Represents an event POJO indicating a smartphone revoke was confirmed in Self Service Mode. + * + * @param mediumId The id of the smartphone medium. + * @param transactionId The id of the transaction. + */ +@Serializable +data class SmartphoneRevokeConfirmed( + @Serializable(with = UUIDSerializer::class) val mediumId: UUID, + @Serializable(with = UUIDSerializer::class) val transactionId: UUID, +) : Event diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneRevokePending.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneRevokePending.kt new file mode 100644 index 00000000..76c121bc --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneRevokePending.kt @@ -0,0 +1,18 @@ +package com.open200.xesar.connect.messages.event + +import com.open200.xesar.connect.utils.UUIDSerializer +import java.util.* +import kotlinx.serialization.Serializable + +/** + * Represents an event POJO indicating a smartphone revoke is pending confirmation in Self Service + * Mode. + * + * @param mediumId The id of the smartphone medium. + * @param transactionId The id of the transaction. + */ +@Serializable +data class SmartphoneRevokePending( + @Serializable(with = UUIDSerializer::class) val mediumId: UUID, + @Serializable(with = UUIDSerializer::class) val transactionId: UUID, +) : Event diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneUpdateConfirmed.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneUpdateConfirmed.kt new file mode 100644 index 00000000..f005273e --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneUpdateConfirmed.kt @@ -0,0 +1,17 @@ +package com.open200.xesar.connect.messages.event + +import com.open200.xesar.connect.utils.UUIDSerializer +import java.util.* +import kotlinx.serialization.Serializable + +/** + * Represents an event POJO indicating a smartphone update was confirmed in Self Service Mode. + * + * @param mediumId The id of the smartphone medium. + * @param transactionId The id of the transaction. + */ +@Serializable +data class SmartphoneUpdateConfirmed( + @Serializable(with = UUIDSerializer::class) val mediumId: UUID, + @Serializable(with = UUIDSerializer::class) val transactionId: UUID, +) : Event diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneUpdatePending.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneUpdatePending.kt new file mode 100644 index 00000000..1b23a307 --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/SmartphoneUpdatePending.kt @@ -0,0 +1,70 @@ +package com.open200.xesar.connect.messages.event + +import com.open200.xesar.connect.utils.OffsetDateTimeSerializer +import com.open200.xesar.connect.utils.UUIDSerializer +import java.time.OffsetDateTime +import java.util.* +import kotlinx.serialization.Serializable + +/** + * Represents an event POJO indicating a smartphone update is pending confirmation in Self Service + * Mode. + * + * @param masterKey Whether the medium is a master key. + * @param mediumDataFrame The hex encoded access data for the medium. + * @param metadata Container for extra descriptive data. + * @param officeMode If this medium is allowed to toggle office mode. + * @param t The document type (optional). + * @param transactionId The unique identifier for the transaction. + * @param ts The ISO8601 timestamp of when this resource was requested. + * @param validFrom The ISO8601 time from when the medium is valid. + * @param validUntil The ISO8601 time up until the medium is valid. + * @param version The version of this specification. + * @param xsId A SHA256 hash of the installation ID. + * @param xsMOBDK The Mobile Device Key (MOBDK). Only present for Smartphone media (optional). + * @param xsMOBGID The Mobile Group Identifier (MOBGID). Only present for Smartphone media + * (optional). + * @param xsMediumId The unique identifier of the identification medium. + * @param xsMobileId The unique identifier of the Smartphone (optional). + */ +@Serializable +data class SmartphoneUpdatePending( + val masterKey: Boolean, + val mediumDataFrame: String, + val metadata: Metadata, + val officeMode: Boolean, + val t: String? = null, + @Serializable(with = UUIDSerializer::class) val transactionId: UUID, + @Serializable(with = OffsetDateTimeSerializer::class) val ts: OffsetDateTime, + @Serializable(with = OffsetDateTimeSerializer::class) val validFrom: OffsetDateTime, + @Serializable(with = OffsetDateTimeSerializer::class) val validUntil: OffsetDateTime, + val version: Int, + val xsId: String, + val xsMOBDK: String? = null, + val xsMOBGID: String? = null, + @Serializable(with = UUIDSerializer::class) val xsMediumId: UUID, + @Serializable(with = UUIDSerializer::class) val xsMobileId: UUID? = null, +) : Event { + + /** + * Represents the container for extra descriptive data. + * + * @param accessPoints The list of access points the medium is authorized to disengage + * (optional). + */ + @Serializable data class Metadata(val accessPoints: List? = null) + + /** + * Represents a single access point within the system. + * + * @param accessDescription Description of the access point (optional). + * @param bleMac The Bluetooth Low Energy (BLE) MAC address of the EVVA component (optional). + * @param name The name of the EVVA component. + */ + @Serializable + data class AccessPoint( + val accessDescription: String? = null, + val bleMac: String? = null, + val name: String, + ) +} diff --git a/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/ZoneMetadataDefinitionsUpdated.kt b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/ZoneMetadataDefinitionsUpdated.kt new file mode 100644 index 00000000..a9c1542b --- /dev/null +++ b/xesar-connect/src/main/kotlin/com/open200/xesar/connect/messages/event/ZoneMetadataDefinitionsUpdated.kt @@ -0,0 +1,18 @@ +package com.open200.xesar.connect.messages.event + +import com.open200.xesar.connect.messages.EntityMetadata +import com.open200.xesar.connect.utils.UUIDSerializer +import java.util.* +import kotlinx.serialization.Serializable + +/** + * Represents an event POJO as a response of a command to change of a zone metadata definition. + * + * @param entityMetadata Contains the information of the metadata definitions. + * @param id The id of the updated zone. + */ +@Serializable +data class ZoneMetadataDefinitionsUpdated( + val entityMetadata: List, + @Serializable(with = UUIDSerializer::class) val id: UUID, +) : Event diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/event/MetadataDefinitionsUpdatedSerializationTest.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/event/MetadataDefinitionsUpdatedSerializationTest.kt new file mode 100644 index 00000000..b4b7c32c --- /dev/null +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/event/MetadataDefinitionsUpdatedSerializationTest.kt @@ -0,0 +1,146 @@ +package com.open200.xesar.connect.encodingDecoding.event + +import com.open200.xesar.connect.messages.EntityMetadata +import com.open200.xesar.connect.messages.event.* +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.equals.shouldBeEqual +import java.util.* + +class MetadataDefinitionsUpdatedSerializationTest : + FunSpec({ + val entityId = UUID.fromString("43edc7cf-80ab-4486-86db-41cda2c7a2cd") + val commandId = UUID.fromString("faf3d0c4-1281-40ae-89d7-5c541d77a757") + val metadataId = UUID.fromString("a4b319e0-1281-40ae-89d7-5c541d77a757") + + val entityMetadata = + listOf(EntityMetadata(id = metadataId, name = "testMetadata", value = "testValue")) + + val eventJsonSuffix = + "\"event\":{\"entityMetadata\":[{\"id\":" + + "\"a4b319e0-1281-40ae-89d7-5c541d77a757\"," + + "\"name\":\"testMetadata\",\"value\":\"testValue\"}]," + + "\"id\":\"43edc7cf-80ab-4486-86db-41cda2c7a2cd\"}}" + + val fullJson = "{\"commandId\":\"faf3d0c4-1281-40ae-89d7-5c541d77a757\",$eventJsonSuffix" + + test("serialize AuthorizationProfileMetadataDefinitionsUpdated") { + val apiEvent = + ApiEvent( + commandId, + AuthorizationProfileMetadataDefinitionsUpdated( + entityMetadata = entityMetadata, + id = entityId, + ), + ) + + encodeEvent(apiEvent).shouldBeEqual(fullJson) + } + + test("deserialize AuthorizationProfileMetadataDefinitionsUpdated") { + decodeEvent(fullJson) + .shouldBeEqual( + ApiEvent( + commandId, + AuthorizationProfileMetadataDefinitionsUpdated( + entityMetadata = entityMetadata, + id = entityId, + ), + ) + ) + } + + test("serialize InstallationPointMetadataDefinitionsUpdated") { + val apiEvent = + ApiEvent( + commandId, + InstallationPointMetadataDefinitionsUpdated( + entityMetadata = entityMetadata, + id = entityId, + ), + ) + + encodeEvent(apiEvent).shouldBeEqual(fullJson) + } + + test("deserialize InstallationPointMetadataDefinitionsUpdated") { + decodeEvent(fullJson) + .shouldBeEqual( + ApiEvent( + commandId, + InstallationPointMetadataDefinitionsUpdated( + entityMetadata = entityMetadata, + id = entityId, + ), + ) + ) + } + + test("serialize MediumMetadataDefinitionsUpdated") { + val apiEvent = + ApiEvent( + commandId, + MediumMetadataDefinitionsUpdated(entityMetadata = entityMetadata, id = entityId), + ) + + encodeEvent(apiEvent).shouldBeEqual(fullJson) + } + + test("deserialize MediumMetadataDefinitionsUpdated") { + decodeEvent(fullJson) + .shouldBeEqual( + ApiEvent( + commandId, + MediumMetadataDefinitionsUpdated( + entityMetadata = entityMetadata, + id = entityId, + ), + ) + ) + } + + test("serialize PersonMetadataDefinitionsUpdated") { + val apiEvent = + ApiEvent( + commandId, + PersonMetadataDefinitionsUpdated(entityMetadata = entityMetadata, id = entityId), + ) + + encodeEvent(apiEvent).shouldBeEqual(fullJson) + } + + test("deserialize PersonMetadataDefinitionsUpdated") { + decodeEvent(fullJson) + .shouldBeEqual( + ApiEvent( + commandId, + PersonMetadataDefinitionsUpdated( + entityMetadata = entityMetadata, + id = entityId, + ), + ) + ) + } + + test("serialize ZoneMetadataDefinitionsUpdated") { + val apiEvent = + ApiEvent( + commandId, + ZoneMetadataDefinitionsUpdated(entityMetadata = entityMetadata, id = entityId), + ) + + encodeEvent(apiEvent).shouldBeEqual(fullJson) + } + + test("deserialize ZoneMetadataDefinitionsUpdated") { + decodeEvent(fullJson) + .shouldBeEqual( + ApiEvent( + commandId, + ZoneMetadataDefinitionsUpdated( + entityMetadata = entityMetadata, + id = entityId, + ), + ) + ) + } + }) diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/event/MobileRegistrationStateUpdatedSerializationTest.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/event/MobileRegistrationStateUpdatedSerializationTest.kt new file mode 100644 index 00000000..982e6666 --- /dev/null +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/event/MobileRegistrationStateUpdatedSerializationTest.kt @@ -0,0 +1,42 @@ +package com.open200.xesar.connect.encodingDecoding.event + +import com.open200.xesar.connect.messages.event.* +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.equals.shouldBeEqual +import java.util.* + +class MobileRegistrationStateUpdatedSerializationTest : + FunSpec({ + val id = UUID.fromString("43edc7cf-80ab-4486-86db-41cda2c7a2cd") + val commandId = UUID.fromString("faf3d0c4-1281-40ae-89d7-5c541d77a757") + + test("serialize MobileRegistrationStateUpdated") { + val apiEvent = + ApiEvent( + commandId, + MobileRegistrationStateUpdated(id = id, registrationState = "REGISTERED"), + ) + + encodeEvent(apiEvent) + .shouldBeEqual( + "{\"commandId\":\"faf3d0c4-1281-40ae-89d7-5c541d77a757\"," + + "\"event\":{\"id\":\"43edc7cf-80ab-4486-86db-41cda2c7a2cd\"," + + "\"registrationState\":\"REGISTERED\"}}" + ) + } + + test("deserialize MobileRegistrationStateUpdated") { + val text = + "{\"commandId\":\"faf3d0c4-1281-40ae-89d7-5c541d77a757\"," + + "\"event\":{\"id\":\"43edc7cf-80ab-4486-86db-41cda2c7a2cd\"," + + "\"registrationState\":\"REGISTERED\"}}" + + decodeEvent(text) + .shouldBeEqual( + ApiEvent( + commandId, + MobileRegistrationStateUpdated(id = id, registrationState = "REGISTERED"), + ) + ) + } + }) diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/event/SelfServiceModeEventsSerializationTest.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/event/SelfServiceModeEventsSerializationTest.kt new file mode 100644 index 00000000..5748d07f --- /dev/null +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/encodingDecoding/event/SelfServiceModeEventsSerializationTest.kt @@ -0,0 +1,184 @@ +package com.open200.xesar.connect.encodingDecoding.event + +import com.open200.xesar.connect.messages.event.* +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.equals.shouldBeEqual +import java.time.OffsetDateTime +import java.util.* + +class SelfServiceModeEventsSerializationTest : + FunSpec({ + val commandId = UUID.fromString("faf3d0c4-1281-40ae-89d7-5c541d77a757") + val smartphoneId = UUID.fromString("43edc7cf-80ab-4486-86db-41cda2c7a2cd") + val transactionId = UUID.fromString("1e4a12b3-3c5f-4a6e-9b7d-8f0e1d2c3b4a") + + test("serialize SmartphoneLocked") { + val apiEvent = + ApiEvent( + commandId, + SmartphoneLocked( + aggregateId = smartphoneId, + mediumIdentifier = 123L, + hasMasterKeyAccess = true, + ), + ) + + encodeEvent(apiEvent) + .shouldBeEqual( + "{\"commandId\":\"faf3d0c4-1281-40ae-89d7-5c541d77a757\"," + + "\"event\":{\"aggregateId\":\"43edc7cf-80ab-4486-86db-41cda2c7a2cd\"," + + "\"installationPoints\":[]," + + "\"mediumIdentifier\":123," + + "\"authorizationProfileId\":null," + + "\"individualAuthorizations\":[]," + + "\"validityEnd\":null," + + "\"zones\":[]," + + "\"hasMasterKeyAccess\":true}}" + ) + } + + test("deserialize SmartphoneLocked") { + val text = + "{\"commandId\":\"faf3d0c4-1281-40ae-89d7-5c541d77a757\"," + + "\"event\":{\"aggregateId\":\"43edc7cf-80ab-4486-86db-41cda2c7a2cd\"," + + "\"installationPoints\":[]," + + "\"mediumIdentifier\":123," + + "\"individualAuthorizations\":[]," + + "\"zones\":[]," + + "\"hasMasterKeyAccess\":true}}" + + val result = decodeEvent(text) + + result.event.aggregateId.shouldBeEqual(smartphoneId) + result.event.mediumIdentifier.shouldBeEqual(123L) + result.event.hasMasterKeyAccess.shouldBeEqual(true) + } + + test("serialize SmartphoneRevokePending") { + val apiEvent = + ApiEvent( + commandId, + SmartphoneRevokePending(mediumId = smartphoneId, transactionId = transactionId), + ) + + encodeEvent(apiEvent) + .shouldBeEqual( + "{\"commandId\":\"faf3d0c4-1281-40ae-89d7-5c541d77a757\"," + + "\"event\":{\"mediumId\":\"43edc7cf-80ab-4486-86db-41cda2c7a2cd\"," + + "\"transactionId\":\"1e4a12b3-3c5f-4a6e-9b7d-8f0e1d2c3b4a\"}}" + ) + } + + test("deserialize SmartphoneRevokePending") { + val text = + "{\"commandId\":\"faf3d0c4-1281-40ae-89d7-5c541d77a757\"," + + "\"event\":{\"mediumId\":\"43edc7cf-80ab-4486-86db-41cda2c7a2cd\"," + + "\"transactionId\":\"1e4a12b3-3c5f-4a6e-9b7d-8f0e1d2c3b4a\"}}" + + decodeEvent(text) + .shouldBeEqual( + ApiEvent( + commandId, + SmartphoneRevokePending( + mediumId = smartphoneId, + transactionId = transactionId, + ), + ) + ) + } + + test("serialize SmartphoneUpdatePending") { + val xsMediumId = UUID.fromString("ab3d5e7f-1234-5678-9abc-def012345678") + val xsMobileId = UUID.fromString("cd4e6f8a-2345-6789-abcd-ef0123456789") + val ts = OffsetDateTime.parse("2023-06-01T10:00:00+02:00") + val validFrom = OffsetDateTime.parse("2023-06-01T00:00:00+02:00") + val validUntil = OffsetDateTime.parse("2024-06-01T00:00:00+02:00") + + val apiEvent = + ApiEvent( + commandId, + SmartphoneUpdatePending( + masterKey = false, + mediumDataFrame = "AABBCCDD", + metadata = + SmartphoneUpdatePending.Metadata( + accessPoints = + listOf( + SmartphoneUpdatePending.AccessPoint( + accessDescription = "Main entrance", + bleMac = "AA:BB:CC:DD:EE:FF", + name = "Front Door", + ) + ) + ), + officeMode = true, + t = "smartphone", + transactionId = transactionId, + ts = ts, + validFrom = validFrom, + validUntil = validUntil, + version = 1, + xsId = "abc123hash", + xsMOBDK = "mobdkValue", + xsMOBGID = "mobgidValue", + xsMediumId = xsMediumId, + xsMobileId = xsMobileId, + ), + ) + + encodeEvent(apiEvent) + .shouldBeEqual( + "{\"commandId\":\"faf3d0c4-1281-40ae-89d7-5c541d77a757\"," + + "\"event\":{\"masterKey\":false," + + "\"mediumDataFrame\":\"AABBCCDD\"," + + "\"metadata\":{\"accessPoints\":[{\"accessDescription\":\"Main entrance\",\"bleMac\":\"AA:BB:CC:DD:EE:FF\",\"name\":\"Front Door\"}]}," + + "\"officeMode\":true," + + "\"t\":\"smartphone\"," + + "\"transactionId\":\"1e4a12b3-3c5f-4a6e-9b7d-8f0e1d2c3b4a\"," + + "\"ts\":\"2023-06-01T10:00:00+02:00\"," + + "\"validFrom\":\"2023-06-01T00:00:00+02:00\"," + + "\"validUntil\":\"2024-06-01T00:00:00+02:00\"," + + "\"version\":1," + + "\"xsId\":\"abc123hash\"," + + "\"xsMOBDK\":\"mobdkValue\"," + + "\"xsMOBGID\":\"mobgidValue\"," + + "\"xsMediumId\":\"ab3d5e7f-1234-5678-9abc-def012345678\"," + + "\"xsMobileId\":\"cd4e6f8a-2345-6789-abcd-ef0123456789\"}}" + ) + } + + test("deserialize SmartphoneUpdatePending") { + val xsMediumId = UUID.fromString("ab3d5e7f-1234-5678-9abc-def012345678") + val ts = OffsetDateTime.parse("2023-06-01T10:00:00+02:00") + val validFrom = OffsetDateTime.parse("2023-06-01T00:00:00+02:00") + val validUntil = OffsetDateTime.parse("2024-06-01T00:00:00+02:00") + + val text = + "{\"commandId\":\"faf3d0c4-1281-40ae-89d7-5c541d77a757\"," + + "\"event\":{\"masterKey\":false," + + "\"mediumDataFrame\":\"AABBCCDD\"," + + "\"metadata\":{\"accessPoints\":[{\"name\":\"Front Door\"}]}," + + "\"officeMode\":true," + + "\"transactionId\":\"1e4a12b3-3c5f-4a6e-9b7d-8f0e1d2c3b4a\"," + + "\"ts\":\"2023-06-01T10:00:00+02:00\"," + + "\"validFrom\":\"2023-06-01T00:00:00+02:00\"," + + "\"validUntil\":\"2024-06-01T00:00:00+02:00\"," + + "\"version\":1," + + "\"xsId\":\"abc123hash\"," + + "\"xsMediumId\":\"ab3d5e7f-1234-5678-9abc-def012345678\"}}" + + val result = decodeEvent(text) + + result.event.masterKey.shouldBeEqual(false) + result.event.mediumDataFrame.shouldBeEqual("AABBCCDD") + result.event.officeMode.shouldBeEqual(true) + result.event.transactionId.shouldBeEqual(transactionId) + result.event.ts.shouldBeEqual(ts) + result.event.validFrom.shouldBeEqual(validFrom) + result.event.validUntil.shouldBeEqual(validUntil) + result.event.version.shouldBeEqual(1) + result.event.xsId.shouldBeEqual("abc123hash") + result.event.xsMediumId.shouldBeEqual(xsMediumId) + result.event.metadata.accessPoints!![0].name.shouldBeEqual("Front Door") + } + }) diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/ConfirmSmartphoneRevokeTest.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/ConfirmSmartphoneRevokeTest.kt new file mode 100644 index 00000000..0d6ea12d --- /dev/null +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/ConfirmSmartphoneRevokeTest.kt @@ -0,0 +1,92 @@ +package com.open200.xesar.connect.it.command + +import com.open200.xesar.connect.Topics +import com.open200.xesar.connect.XesarConnect +import com.open200.xesar.connect.XesarMqttClient +import com.open200.xesar.connect.extension.confirmSmartphoneRevokeAsync +import com.open200.xesar.connect.it.MosquittoContainer +import com.open200.xesar.connect.messages.event.ApiEvent +import com.open200.xesar.connect.messages.event.SmartphoneRevokeConfirmed +import com.open200.xesar.connect.messages.event.encodeEvent +import io.kotest.common.runBlocking +import io.kotest.core.spec.style.FunSpec +import io.kotest.extensions.testcontainers.perProject +import io.kotest.matchers.equals.shouldBeEqual +import io.mockk.coEvery +import java.util.UUID +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.launch + +class ConfirmSmartphoneRevokeTest : + FunSpec({ + val container = MosquittoContainer.container() + val config = MosquittoContainer.config(container) + listener(container.perProject()) + + test("confirm smartphone revoke in Self Service Mode") { + coEvery { config.uuidGenerator.generateId() } + .returns(UUID.fromString("00000000-1281-40ae-89d7-5c541d77a757")) + + val smartphoneId = UUID.fromString("43edc7cf-80ab-4486-86db-41cda2c7a2cd") + val transactionId = UUID.fromString("1e4a12b3-3c5f-4a6e-9b7d-8f0e1d2c3b4a") + + runBlocking { + val simulatedBackendReady = CompletableDeferred() + val commandReceived = CompletableDeferred() + + launch { + XesarMqttClient.connectAsync(config).await().use { client -> + client.subscribeAsync(arrayOf(Topics.ALL_TOPICS)).await() + + client.onMessage = { topic, payload -> + when (topic) { + Topics.Command.CONFIRM_SMARTPHONE_REVOKE -> { + commandReceived.complete(payload.decodeToString()) + } + } + } + + simulatedBackendReady.complete(Unit) + + val commandContent = commandReceived.await() + + commandContent.shouldBeEqual( + "{\"commandId\":\"00000000-1281-40ae-89d7-5c541d77a757\"," + + "\"mediumId\":\"43edc7cf-80ab-4486-86db-41cda2c7a2cd\"," + + "\"transactionId\":\"1e4a12b3-3c5f-4a6e-9b7d-8f0e1d2c3b4a\"," + + "\"token\":\"JDJhJDEwJDFSNEljZ2FaRUNXUXBTQ25XN05KbE9qRzFHQ1VjMzkvWTBVcFpZb1M4Vmt0dnJYZ0tJVFBx\"}" + ) + + val apiEvent = + ApiEvent( + UUID.fromString("00000000-1281-40ae-89d7-5c541d77a757"), + SmartphoneRevokeConfirmed( + mediumId = smartphoneId, + transactionId = transactionId, + ), + ) + + client + .publishAsync( + Topics.Event.SMARTPHONE_REVOKE_CONFIRMED, + encodeEvent(apiEvent), + ) + .await() + } + } + + launch { + simulatedBackendReady.await() + + val api = XesarConnect.connectAndLoginAsync(config).await() + api.subscribeAsync(Topics(Topics.Event.SMARTPHONE_REVOKE_CONFIRMED)).await() + + val result = + api.confirmSmartphoneRevokeAsync(smartphoneId, transactionId).await() + + result.mediumId.shouldBeEqual(smartphoneId) + result.transactionId.shouldBeEqual(transactionId) + } + } + } + }) diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/ConfirmSmartphoneUpdateTest.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/ConfirmSmartphoneUpdateTest.kt new file mode 100644 index 00000000..7f87b490 --- /dev/null +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/ConfirmSmartphoneUpdateTest.kt @@ -0,0 +1,92 @@ +package com.open200.xesar.connect.it.command + +import com.open200.xesar.connect.Topics +import com.open200.xesar.connect.XesarConnect +import com.open200.xesar.connect.XesarMqttClient +import com.open200.xesar.connect.extension.confirmSmartphoneUpdateAsync +import com.open200.xesar.connect.it.MosquittoContainer +import com.open200.xesar.connect.messages.event.ApiEvent +import com.open200.xesar.connect.messages.event.SmartphoneUpdateConfirmed +import com.open200.xesar.connect.messages.event.encodeEvent +import io.kotest.common.runBlocking +import io.kotest.core.spec.style.FunSpec +import io.kotest.extensions.testcontainers.perProject +import io.kotest.matchers.equals.shouldBeEqual +import io.mockk.coEvery +import java.util.UUID +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.launch + +class ConfirmSmartphoneUpdateTest : + FunSpec({ + val container = MosquittoContainer.container() + val config = MosquittoContainer.config(container) + listener(container.perProject()) + + test("confirm smartphone update in Self Service Mode") { + coEvery { config.uuidGenerator.generateId() } + .returns(UUID.fromString("00000000-1281-40ae-89d7-5c541d77a757")) + + val smartphoneId = UUID.fromString("43edc7cf-80ab-4486-86db-41cda2c7a2cd") + val transactionId = UUID.fromString("1e4a12b3-3c5f-4a6e-9b7d-8f0e1d2c3b4a") + + runBlocking { + val simulatedBackendReady = CompletableDeferred() + val commandReceived = CompletableDeferred() + + launch { + XesarMqttClient.connectAsync(config).await().use { client -> + client.subscribeAsync(arrayOf(Topics.ALL_TOPICS)).await() + + client.onMessage = { topic, payload -> + when (topic) { + Topics.Command.CONFIRM_SMARTPHONE_UPDATE -> { + commandReceived.complete(payload.decodeToString()) + } + } + } + + simulatedBackendReady.complete(Unit) + + val commandContent = commandReceived.await() + + commandContent.shouldBeEqual( + "{\"commandId\":\"00000000-1281-40ae-89d7-5c541d77a757\"," + + "\"mediumId\":\"43edc7cf-80ab-4486-86db-41cda2c7a2cd\"," + + "\"transactionId\":\"1e4a12b3-3c5f-4a6e-9b7d-8f0e1d2c3b4a\"," + + "\"token\":\"JDJhJDEwJDFSNEljZ2FaRUNXUXBTQ25XN05KbE9qRzFHQ1VjMzkvWTBVcFpZb1M4Vmt0dnJYZ0tJVFBx\"}" + ) + + val apiEvent = + ApiEvent( + UUID.fromString("00000000-1281-40ae-89d7-5c541d77a757"), + SmartphoneUpdateConfirmed( + mediumId = smartphoneId, + transactionId = transactionId, + ), + ) + + client + .publishAsync( + Topics.Event.SMARTPHONE_UPDATE_CONFIRMED, + encodeEvent(apiEvent), + ) + .await() + } + } + + launch { + simulatedBackendReady.await() + + val api = XesarConnect.connectAndLoginAsync(config).await() + api.subscribeAsync(Topics(Topics.Event.SMARTPHONE_UPDATE_CONFIRMED)).await() + + val result = + api.confirmSmartphoneUpdateAsync(smartphoneId, transactionId).await() + + result.mediumId.shouldBeEqual(smartphoneId) + result.transactionId.shouldBeEqual(transactionId) + } + } + } + }) diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/RevokeSmartphoneTest.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/RevokeSmartphoneTest.kt new file mode 100644 index 00000000..3f106dac --- /dev/null +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/RevokeSmartphoneTest.kt @@ -0,0 +1,85 @@ +package com.open200.xesar.connect.it.command + +import com.open200.xesar.connect.Topics +import com.open200.xesar.connect.XesarConnect +import com.open200.xesar.connect.XesarMqttClient +import com.open200.xesar.connect.extension.revokeSmartphoneAsync +import com.open200.xesar.connect.it.MosquittoContainer +import com.open200.xesar.connect.messages.event.ApiEvent +import com.open200.xesar.connect.messages.event.MediumRevoked +import com.open200.xesar.connect.messages.event.encodeEvent +import io.kotest.common.runBlocking +import io.kotest.core.spec.style.FunSpec +import io.kotest.extensions.testcontainers.perProject +import io.kotest.matchers.equals.shouldBeEqual +import io.mockk.coEvery +import java.util.UUID +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.launch + +class RevokeSmartphoneTest : + FunSpec({ + val container = MosquittoContainer.container() + val config = MosquittoContainer.config(container) + listener(container.perProject()) + + test("revoke smartphone medium") { + coEvery { config.uuidGenerator.generateId() } + .returns(UUID.fromString("00000000-1281-40ae-89d7-5c541d77a757")) + + val smartphoneId = UUID.fromString("43edc7cf-80ab-4486-86db-41cda2c7a2cd") + + runBlocking { + val simulatedBackendReady = CompletableDeferred() + val commandReceived = CompletableDeferred() + + launch { + XesarMqttClient.connectAsync(config).await().use { client -> + client.subscribeAsync(arrayOf(Topics.ALL_TOPICS)).await() + + client.onMessage = { topic, payload -> + when (topic) { + Topics.Command.REVOKE_SMARTPHONE -> { + commandReceived.complete(payload.decodeToString()) + } + } + } + + simulatedBackendReady.complete(Unit) + + val commandContent = commandReceived.await() + + commandContent.shouldBeEqual( + "{\"commandId\":\"00000000-1281-40ae-89d7-5c541d77a757\",\"id\":\"43edc7cf-80ab-4486-86db-41cda2c7a2cd\",\"token\":\"JDJhJDEwJDFSNEljZ2FaRUNXUXBTQ25XN05KbE9qRzFHQ1VjMzkvWTBVcFpZb1M4Vmt0dnJYZ0tJVFBx\"}" + ) + + val apiEvent = + ApiEvent( + UUID.fromString("00000000-1281-40ae-89d7-5c541d77a757"), + MediumRevoked( + aggregateId = smartphoneId, + mediumIdentifier = 123456L, + hasMasterKeyAccess = false, + ), + ) + + client + .publishAsync(Topics.Event.MEDIUM_REVOKED, encodeEvent(apiEvent)) + .await() + } + } + + launch { + simulatedBackendReady.await() + + val api = XesarConnect.connectAndLoginAsync(config).await() + api.subscribeAsync(Topics(Topics.Event.MEDIUM_REVOKED)).await() + + val result = api.revokeSmartphoneAsync(smartphoneId).await() + + result.aggregateId.shouldBeEqual(smartphoneId) + result.mediumIdentifier.shouldBeEqual(123456L) + } + } + } + }) diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/UnassignPersonFromMediumTest.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/UnassignPersonFromMediumTest.kt new file mode 100644 index 00000000..69847d5d --- /dev/null +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/command/UnassignPersonFromMediumTest.kt @@ -0,0 +1,86 @@ +package com.open200.xesar.connect.it.command + +import com.open200.xesar.connect.Topics +import com.open200.xesar.connect.XesarConnect +import com.open200.xesar.connect.XesarMqttClient +import com.open200.xesar.connect.extension.unassignPersonFromMediumAsync +import com.open200.xesar.connect.it.MosquittoContainer +import com.open200.xesar.connect.messages.event.ApiEvent +import com.open200.xesar.connect.messages.event.MediumPersonChanged +import com.open200.xesar.connect.messages.event.encodeEvent +import io.kotest.common.runBlocking +import io.kotest.core.spec.style.FunSpec +import io.kotest.extensions.testcontainers.perProject +import io.kotest.matchers.equals.shouldBeEqual +import io.kotest.matchers.nulls.shouldBeNull +import io.mockk.coEvery +import java.util.UUID +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.launch + +class UnassignPersonFromMediumTest : + FunSpec({ + val container = MosquittoContainer.container() + val config = MosquittoContainer.config(container) + listener(container.perProject()) + + test("unassign person from medium") { + coEvery { config.uuidGenerator.generateId() } + .returns(UUID.fromString("00000000-1281-40ae-89d7-5c541d77a757")) + + val mediumId = UUID.fromString("43edc7cf-80ab-4486-86db-41cda2c7a2cd") + + runBlocking { + val simulatedBackendReady = CompletableDeferred() + val commandReceived = CompletableDeferred() + + launch { + XesarMqttClient.connectAsync(config).await().use { client -> + client.subscribeAsync(arrayOf(Topics.ALL_TOPICS)).await() + + client.onMessage = { topic, payload -> + when (topic) { + Topics.Command.UNASSIGN_PERSON_FROM_MEDIUM -> { + commandReceived.complete(payload.decodeToString()) + } + } + } + + simulatedBackendReady.complete(Unit) + + val commandContent = commandReceived.await() + + commandContent.shouldBeEqual( + "{\"commandId\":\"00000000-1281-40ae-89d7-5c541d77a757\",\"mediumId\":\"43edc7cf-80ab-4486-86db-41cda2c7a2cd\",\"token\":\"JDJhJDEwJDFSNEljZ2FaRUNXUXBTQ25XN05KbE9qRzFHQ1VjMzkvWTBVcFpZb1M4Vmt0dnJYZ0tJVFBx\"}" + ) + + val apiEvent = + ApiEvent( + UUID.fromString("00000000-1281-40ae-89d7-5c541d77a757"), + MediumPersonChanged( + oldPersonId = null, + newPersonId = null, + id = mediumId, + ), + ) + + client + .publishAsync(Topics.Event.MEDIUM_PERSON_CHANGED, encodeEvent(apiEvent)) + .await() + } + } + + launch { + simulatedBackendReady.await() + + val api = XesarConnect.connectAndLoginAsync(config).await() + api.subscribeAsync(Topics(Topics.Event.MEDIUM_PERSON_CHANGED)).await() + + val result = api.unassignPersonFromMediumAsync(mediumId).await() + + result.id.shouldBeEqual(mediumId) + result.newPersonId.shouldBeNull() + } + } + } + }) diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/event/EventMetadataDefinitionsUpdatedTest.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/event/EventMetadataDefinitionsUpdatedTest.kt new file mode 100644 index 00000000..d63ec05b --- /dev/null +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/event/EventMetadataDefinitionsUpdatedTest.kt @@ -0,0 +1,273 @@ +package com.open200.xesar.connect.it.event + +import com.open200.xesar.connect.Topics +import com.open200.xesar.connect.XesarConnect +import com.open200.xesar.connect.XesarMqttClient +import com.open200.xesar.connect.filters.TopicFilter +import com.open200.xesar.connect.it.MosquittoContainer +import com.open200.xesar.connect.messages.EntityMetadata +import com.open200.xesar.connect.messages.event.* +import io.kotest.core.spec.style.FunSpec +import io.kotest.extensions.testcontainers.perProject +import io.kotest.matchers.equals.shouldBeEqual +import java.util.UUID +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeout + +class EventMetadataDefinitionsUpdatedTest : + FunSpec({ + val container = MosquittoContainer.container() + val config = MosquittoContainer.config(container) + listener(container.perProject()) + + val entityId = UUID.fromString("43edc7cf-80ab-4486-86db-41cda2c7a2cd") + val metadataId = UUID.fromString("a4b319e0-1281-40ae-89d7-5c541d77a757") + val commandId = UUID.fromString("00000000-1281-40ae-89d7-5c541d77a757") + + val entityMetadata = + listOf(EntityMetadata(id = metadataId, name = "testMetadata", value = "testValue")) + + test("should receive AuthorizationProfileMetadataDefinitionsUpdated event") { + withTimeout(5000) { + val clientReady = CompletableDeferred() + val eventReceived = + CompletableDeferred>() + + launch { + val api = XesarConnect.connectAndLoginAsync(config).await() + api.subscribeAsync( + Topics(Topics.Event.AUTHORIZATION_PROFILE_METADATA_DEFINITIONS_UPDATED) + ) + .await() + + api.onEvent( + TopicFilter(Topics.Event.AUTHORIZATION_PROFILE_METADATA_DEFINITIONS_UPDATED) + ) { event -> + eventReceived.complete(event) + } + + clientReady.complete(Unit) + + val result = eventReceived.await() + result.event.id.shouldBeEqual(entityId) + result.event.entityMetadata.shouldBeEqual(entityMetadata) + } + + launch { + clientReady.await() + + XesarMqttClient.connectAsync(config).await().use { client -> + val apiEvent = + ApiEvent( + commandId, + AuthorizationProfileMetadataDefinitionsUpdated( + entityMetadata = entityMetadata, + id = entityId, + ), + ) + + client + .publishAsync( + Topics.Event.AUTHORIZATION_PROFILE_METADATA_DEFINITIONS_UPDATED, + encodeEvent(apiEvent), + ) + .await() + } + } + } + } + + test("should receive InstallationPointMetadataDefinitionsUpdated event") { + withTimeout(5000) { + val clientReady = CompletableDeferred() + val eventReceived = + CompletableDeferred>() + + launch { + val api = XesarConnect.connectAndLoginAsync(config).await() + api.subscribeAsync( + Topics(Topics.Event.INSTALLATION_POINT_METADATA_DEFINITIONS_UPDATED) + ) + .await() + + api.onEvent( + TopicFilter(Topics.Event.INSTALLATION_POINT_METADATA_DEFINITIONS_UPDATED) + ) { event -> + eventReceived.complete(event) + } + + clientReady.complete(Unit) + + val result = eventReceived.await() + result.event.id.shouldBeEqual(entityId) + result.event.entityMetadata.shouldBeEqual(entityMetadata) + } + + launch { + clientReady.await() + + XesarMqttClient.connectAsync(config).await().use { client -> + val apiEvent = + ApiEvent( + commandId, + InstallationPointMetadataDefinitionsUpdated( + entityMetadata = entityMetadata, + id = entityId, + ), + ) + + client + .publishAsync( + Topics.Event.INSTALLATION_POINT_METADATA_DEFINITIONS_UPDATED, + encodeEvent(apiEvent), + ) + .await() + } + } + } + } + + test("should receive MediumMetadataDefinitionsUpdated event") { + withTimeout(5000) { + val clientReady = CompletableDeferred() + val eventReceived = + CompletableDeferred>() + + launch { + val api = XesarConnect.connectAndLoginAsync(config).await() + api.subscribeAsync(Topics(Topics.Event.MEDIUM_METADATA_DEFINITIONS_UPDATED)) + .await() + + api.onEvent( + TopicFilter(Topics.Event.MEDIUM_METADATA_DEFINITIONS_UPDATED) + ) { event -> + eventReceived.complete(event) + } + + clientReady.complete(Unit) + + val result = eventReceived.await() + result.event.id.shouldBeEqual(entityId) + result.event.entityMetadata.shouldBeEqual(entityMetadata) + } + + launch { + clientReady.await() + + XesarMqttClient.connectAsync(config).await().use { client -> + val apiEvent = + ApiEvent( + commandId, + MediumMetadataDefinitionsUpdated( + entityMetadata = entityMetadata, + id = entityId, + ), + ) + + client + .publishAsync( + Topics.Event.MEDIUM_METADATA_DEFINITIONS_UPDATED, + encodeEvent(apiEvent), + ) + .await() + } + } + } + } + + test("should receive PersonMetadataDefinitionsUpdated event") { + withTimeout(5000) { + val clientReady = CompletableDeferred() + val eventReceived = + CompletableDeferred>() + + launch { + val api = XesarConnect.connectAndLoginAsync(config).await() + api.subscribeAsync(Topics(Topics.Event.PERSON_METADATA_DEFINITIONS_UPDATED)) + .await() + + api.onEvent( + TopicFilter(Topics.Event.PERSON_METADATA_DEFINITIONS_UPDATED) + ) { event -> + eventReceived.complete(event) + } + + clientReady.complete(Unit) + + val result = eventReceived.await() + result.event.id.shouldBeEqual(entityId) + result.event.entityMetadata.shouldBeEqual(entityMetadata) + } + + launch { + clientReady.await() + + XesarMqttClient.connectAsync(config).await().use { client -> + val apiEvent = + ApiEvent( + commandId, + PersonMetadataDefinitionsUpdated( + entityMetadata = entityMetadata, + id = entityId, + ), + ) + + client + .publishAsync( + Topics.Event.PERSON_METADATA_DEFINITIONS_UPDATED, + encodeEvent(apiEvent), + ) + .await() + } + } + } + } + + test("should receive ZoneMetadataDefinitionsUpdated event") { + withTimeout(5000) { + val clientReady = CompletableDeferred() + val eventReceived = CompletableDeferred>() + + launch { + val api = XesarConnect.connectAndLoginAsync(config).await() + api.subscribeAsync(Topics(Topics.Event.ZONE_METADATA_DEFINITIONS_UPDATED)) + .await() + + api.onEvent( + TopicFilter(Topics.Event.ZONE_METADATA_DEFINITIONS_UPDATED) + ) { event -> + eventReceived.complete(event) + } + + clientReady.complete(Unit) + + val result = eventReceived.await() + result.event.id.shouldBeEqual(entityId) + result.event.entityMetadata.shouldBeEqual(entityMetadata) + } + + launch { + clientReady.await() + + XesarMqttClient.connectAsync(config).await().use { client -> + val apiEvent = + ApiEvent( + commandId, + ZoneMetadataDefinitionsUpdated( + entityMetadata = entityMetadata, + id = entityId, + ), + ) + + client + .publishAsync( + Topics.Event.ZONE_METADATA_DEFINITIONS_UPDATED, + encodeEvent(apiEvent), + ) + .await() + } + } + } + } + }) diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/event/EventMobileRegistrationStateUpdatedTest.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/event/EventMobileRegistrationStateUpdatedTest.kt new file mode 100644 index 00000000..fb3d1d47 --- /dev/null +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/event/EventMobileRegistrationStateUpdatedTest.kt @@ -0,0 +1,75 @@ +package com.open200.xesar.connect.it.event + +import com.open200.xesar.connect.Topics +import com.open200.xesar.connect.XesarConnect +import com.open200.xesar.connect.XesarMqttClient +import com.open200.xesar.connect.filters.TopicFilter +import com.open200.xesar.connect.it.MosquittoContainer +import com.open200.xesar.connect.messages.event.ApiEvent +import com.open200.xesar.connect.messages.event.MobileRegistrationStateUpdated +import com.open200.xesar.connect.messages.event.encodeEvent +import io.kotest.core.spec.style.FunSpec +import io.kotest.extensions.testcontainers.perProject +import io.kotest.matchers.equals.shouldBeEqual +import java.util.UUID +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeout + +class EventMobileRegistrationStateUpdatedTest : + FunSpec({ + val container = MosquittoContainer.container() + val config = MosquittoContainer.config(container) + listener(container.perProject()) + + test("should receive MobileRegistrationStateUpdated event") { + withTimeout(5000) { + val clientReady = CompletableDeferred() + val eventReceived = CompletableDeferred>() + + val smartphoneId = UUID.fromString("43edc7cf-80ab-4486-86db-41cda2c7a2cd") + val commandId = UUID.fromString("00000000-1281-40ae-89d7-5c541d77a757") + + launch { + val api = XesarConnect.connectAndLoginAsync(config).await() + api.subscribeAsync(Topics(Topics.Event.MOBILE_REGISTRATION_STATE_UPDATED)) + .await() + + api.onEvent( + TopicFilter(Topics.Event.MOBILE_REGISTRATION_STATE_UPDATED) + ) { event -> + eventReceived.complete(event) + } + + clientReady.complete(Unit) + + val result = eventReceived.await() + + result.event.id.shouldBeEqual(smartphoneId) + result.event.registrationState!!.shouldBeEqual("REGISTERED") + } + + launch { + clientReady.await() + + XesarMqttClient.connectAsync(config).await().use { client -> + val apiEvent = + ApiEvent( + commandId, + MobileRegistrationStateUpdated( + id = smartphoneId, + registrationState = "REGISTERED", + ), + ) + + client + .publishAsync( + Topics.Event.MOBILE_REGISTRATION_STATE_UPDATED, + encodeEvent(apiEvent), + ) + .await() + } + } + } + } + }) diff --git a/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/event/EventSelfServiceModeTest.kt b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/event/EventSelfServiceModeTest.kt new file mode 100644 index 00000000..91345e61 --- /dev/null +++ b/xesar-connect/src/test/kotlin/com/open200/xesar/connect/it/event/EventSelfServiceModeTest.kt @@ -0,0 +1,184 @@ +package com.open200.xesar.connect.it.event + +import com.open200.xesar.connect.Topics +import com.open200.xesar.connect.XesarConnect +import com.open200.xesar.connect.XesarMqttClient +import com.open200.xesar.connect.filters.TopicFilter +import com.open200.xesar.connect.it.MosquittoContainer +import com.open200.xesar.connect.messages.event.* +import io.kotest.core.spec.style.FunSpec +import io.kotest.extensions.testcontainers.perProject +import io.kotest.matchers.equals.shouldBeEqual +import java.time.OffsetDateTime +import java.util.UUID +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeout + +class EventSelfServiceModeTest : + FunSpec({ + val container = MosquittoContainer.container() + val config = MosquittoContainer.config(container) + listener(container.perProject()) + + test("should receive SmartphoneLocked event") { + withTimeout(5000) { + val clientReady = CompletableDeferred() + val eventReceived = CompletableDeferred>() + + val smartphoneId = UUID.fromString("43edc7cf-80ab-4486-86db-41cda2c7a2cd") + val commandId = UUID.fromString("00000000-1281-40ae-89d7-5c541d77a757") + + launch { + val api = XesarConnect.connectAndLoginAsync(config).await() + api.subscribeAsync(Topics(Topics.Event.SMARTPHONE_LOCKED)).await() + + api.onEvent(TopicFilter(Topics.Event.SMARTPHONE_LOCKED)) { + event -> + eventReceived.complete(event) + } + + clientReady.complete(Unit) + + val result = eventReceived.await() + result.event.aggregateId.shouldBeEqual(smartphoneId) + result.event.mediumIdentifier.shouldBeEqual(123L) + result.event.hasMasterKeyAccess.shouldBeEqual(true) + } + + launch { + clientReady.await() + + XesarMqttClient.connectAsync(config).await().use { client -> + val apiEvent = + ApiEvent( + commandId, + SmartphoneLocked( + aggregateId = smartphoneId, + mediumIdentifier = 123L, + hasMasterKeyAccess = true, + ), + ) + + client + .publishAsync(Topics.Event.SMARTPHONE_LOCKED, encodeEvent(apiEvent)) + .await() + } + } + } + } + + test("should receive SmartphoneRevokePending event") { + withTimeout(5000) { + val clientReady = CompletableDeferred() + val eventReceived = CompletableDeferred>() + + val smartphoneId = UUID.fromString("43edc7cf-80ab-4486-86db-41cda2c7a2cd") + val transactionId = UUID.fromString("1e4a12b3-3c5f-4a6e-9b7d-8f0e1d2c3b4a") + val commandId = UUID.fromString("00000000-1281-40ae-89d7-5c541d77a757") + + launch { + val api = XesarConnect.connectAndLoginAsync(config).await() + api.subscribeAsync(Topics(Topics.Event.SMARTPHONE_REVOKE_PENDING)).await() + + api.onEvent( + TopicFilter(Topics.Event.SMARTPHONE_REVOKE_PENDING) + ) { event -> + eventReceived.complete(event) + } + + clientReady.complete(Unit) + + val result = eventReceived.await() + result.event.mediumId.shouldBeEqual(smartphoneId) + result.event.transactionId.shouldBeEqual(transactionId) + } + + launch { + clientReady.await() + + XesarMqttClient.connectAsync(config).await().use { client -> + val apiEvent = + ApiEvent( + commandId, + SmartphoneRevokePending( + mediumId = smartphoneId, + transactionId = transactionId, + ), + ) + + client + .publishAsync( + Topics.Event.SMARTPHONE_REVOKE_PENDING, + encodeEvent(apiEvent), + ) + .await() + } + } + } + } + + test("should receive SmartphoneUpdatePending event") { + withTimeout(5000) { + val clientReady = CompletableDeferred() + val eventReceived = CompletableDeferred>() + + val xsMediumId = UUID.fromString("43edc7cf-80ab-4486-86db-41cda2c7a2cd") + val transactionId = UUID.fromString("1e4a12b3-3c5f-4a6e-9b7d-8f0e1d2c3b4a") + val commandId = UUID.fromString("00000000-1281-40ae-89d7-5c541d77a757") + val ts = OffsetDateTime.parse("2023-06-01T10:00:00+02:00") + val validFrom = OffsetDateTime.parse("2023-06-01T00:00:00+02:00") + val validUntil = OffsetDateTime.parse("2024-06-01T00:00:00+02:00") + + launch { + val api = XesarConnect.connectAndLoginAsync(config).await() + api.subscribeAsync(Topics(Topics.Event.SMARTPHONE_UPDATE_PENDING)).await() + + api.onEvent( + TopicFilter(Topics.Event.SMARTPHONE_UPDATE_PENDING) + ) { event -> + eventReceived.complete(event) + } + + clientReady.complete(Unit) + + val result = eventReceived.await() + result.event.masterKey.shouldBeEqual(false) + result.event.xsMediumId.shouldBeEqual(xsMediumId) + result.event.transactionId.shouldBeEqual(transactionId) + result.event.version.shouldBeEqual(1) + } + + launch { + clientReady.await() + + XesarMqttClient.connectAsync(config).await().use { client -> + val apiEvent = + ApiEvent( + commandId, + SmartphoneUpdatePending( + masterKey = false, + mediumDataFrame = "AABBCCDD", + metadata = SmartphoneUpdatePending.Metadata(), + officeMode = true, + transactionId = transactionId, + ts = ts, + validFrom = validFrom, + validUntil = validUntil, + version = 1, + xsId = "abc123hash", + xsMediumId = xsMediumId, + ), + ) + + client + .publishAsync( + Topics.Event.SMARTPHONE_UPDATE_PENDING, + encodeEvent(apiEvent), + ) + .await() + } + } + } + } + })