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/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() } 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() + } + } + } + } + })