diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTag.kt index a56c7c0b7..a89b79a77 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTag.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTag.kt @@ -47,6 +47,26 @@ data class ParticipantTag( val role: String?, val proof: String?, ) : PubKeyReferenceTag { + /** Match the role string against [ROLE], case-insensitive. */ + fun effectiveRole(): ROLE? = role?.let { r -> ROLE.entries.firstOrNull { it.code.equals(r, ignoreCase = true) } } + + fun isHost(): Boolean = effectiveRole() == ROLE.HOST + + fun isModerator(): Boolean = effectiveRole() == ROLE.MODERATOR + + fun isSpeaker(): Boolean = effectiveRole() == ROLE.SPEAKER + + /** + * `true` for hosts, moderators, and speakers — anyone who has + * the floor / can publish audio. Audience members + * (`role == null` or `ROLE.PARTICIPANT`) return `false`. + */ + fun canSpeak(): Boolean = + when (effectiveRole()) { + ROLE.HOST, ROLE.MODERATOR, ROLE.SPEAKER -> true + ROLE.PARTICIPANT, null -> false + } + companion object { const val TAG_NAME = "p" diff --git a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTagTest.kt b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTagTest.kt new file mode 100644 index 000000000..ba366226c --- /dev/null +++ b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTagTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class ParticipantTagTest { + private val pk = "a".repeat(64) + + private fun tag(role: String?) = ParticipantTag(pubKey = pk, relayHint = null, role = role, proof = null) + + @Test + fun roleHelpersHostModeratorSpeakerParticipant() { + assertTrue(tag("host").isHost()) + assertFalse(tag("host").isModerator()) + assertFalse(tag("host").isSpeaker()) + + assertTrue(tag("moderator").isModerator()) + assertTrue(tag("speaker").isSpeaker()) + } + + @Test + fun roleHelpersAreCaseInsensitive() { + assertTrue(tag("HOST").isHost()) + assertTrue(tag("Speaker").isSpeaker()) + } + + @Test + fun unknownRoleHasNoEffectiveRole() { + assertNull(tag("director").effectiveRole()) + assertNull(tag(null).effectiveRole()) + assertFalse(tag("director").canSpeak()) + } + + @Test + fun canSpeakIncludesHostModeratorAndSpeakerOnly() { + assertTrue(tag("host").canSpeak()) + assertTrue(tag("moderator").canSpeak()) + assertTrue(tag("speaker").canSpeak()) + assertFalse(tag("participant").canSpeak()) + assertFalse(tag(null).canSpeak()) + } + + @Test + fun effectiveRoleParsesEnumValue() { + assertEquals(ROLE.HOST, tag("host").effectiveRole()) + assertEquals(ROLE.MODERATOR, tag("moderator").effectiveRole()) + assertEquals(ROLE.SPEAKER, tag("speaker").effectiveRole()) + assertEquals(ROLE.PARTICIPANT, tag("participant").effectiveRole()) + } +}