Fix space test + room history visibility bug

This commit is contained in:
Valere 2021-05-05 17:46:58 +02:00
parent b09e509db7
commit 4abfc1fd2b
3 changed files with 218 additions and 126 deletions

View file

@ -16,7 +16,9 @@
package org.matrix.android.sdk.session.space package org.matrix.android.sdk.session.space
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
@ -26,7 +28,7 @@ import org.junit.runner.RunWith
import org.junit.runners.JUnit4 import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
@ -56,22 +58,29 @@ class SpaceCreationTest : InstrumentedTest {
val session = commonTestHelper.createAccount("Hubble", SessionTestParams(true)) val session = commonTestHelper.createAccount("Hubble", SessionTestParams(true))
val roomName = "My Space" val roomName = "My Space"
val topic = "A public space for test" val topic = "A public space for test"
val spaceId: String var spaceId: String = ""
runBlocking { commonTestHelper.waitWithLatch {
spaceId = session.spaceService().createSpace(roomName, topic, null, true) GlobalScope.launch {
// wait a bit to let the summary update it self :/ spaceId = session.spaceService().createSpace(roomName, topic, null, true)
delay(400) // wait a bit to let the summary update it self :/
it.countDown()
}
} }
val syncedSpace = session.spaceService().getSpace(spaceId) val syncedSpace = session.spaceService().getSpace(spaceId)
assertEquals(roomName, syncedSpace?.asRoom()?.roomSummary()?.name, "Room name should be set") commonTestHelper.waitWithLatch {
assertEquals(topic, syncedSpace?.asRoom()?.roomSummary()?.topic, "Room topic should be set") commonTestHelper.retryPeriodicallyWithLatch(it) {
syncedSpace?.asRoom()?.roomSummary()?.name != null
}
}
assertEquals("Room name should be set", roomName, syncedSpace?.asRoom()?.roomSummary()?.name)
assertEquals("Room topic should be set", topic, syncedSpace?.asRoom()?.roomSummary()?.topic)
// assertEquals(topic, syncedSpace.asRoom().roomSummary()?., "Room topic should be set") // assertEquals(topic, syncedSpace.asRoom().roomSummary()?., "Room topic should be set")
assertNotNull("Space should be found by Id", syncedSpace) assertNotNull("Space should be found by Id", syncedSpace)
val creationEvent = syncedSpace!!.asRoom().getStateEvent(EventType.STATE_ROOM_CREATE) val creationEvent = syncedSpace!!.asRoom().getStateEvent(EventType.STATE_ROOM_CREATE)
val createContent = creationEvent?.content.toModel<RoomCreateContent>() val createContent = creationEvent?.content.toModel<RoomCreateContent>()
assertEquals(RoomType.SPACE, createContent?.type, "Room type should be space") assertEquals("Room type should be space", RoomType.SPACE, createContent?.type)
var powerLevelsContent: PowerLevelsContent? = null var powerLevelsContent: PowerLevelsContent? = null
commonTestHelper.waitWithLatch { latch -> commonTestHelper.waitWithLatch { latch ->
@ -120,8 +129,8 @@ class SpaceCreationTest : InstrumentedTest {
assertEquals(JoinSpaceResult.Success, joinResult) assertEquals(JoinSpaceResult.Success, joinResult)
val spaceBobPov = bobSession.spaceService().getSpace(spaceId) val spaceBobPov = bobSession.spaceService().getSpace(spaceId)
assertEquals(roomName, spaceBobPov?.asRoom()?.roomSummary()?.name, "Room name should be set") assertEquals("Room name should be set", roomName, spaceBobPov?.asRoom()?.roomSummary()?.name)
assertEquals(topic, spaceBobPov?.asRoom()?.roomSummary()?.topic, "Room topic should be set") assertEquals("Room topic should be set", topic, spaceBobPov?.asRoom()?.roomSummary()?.topic)
commonTestHelper.signOutAndClose(aliceSession) commonTestHelper.signOutAndClose(aliceSession)
commonTestHelper.signOutAndClose(bobSession) commonTestHelper.signOutAndClose(bobSession)
@ -139,54 +148,73 @@ class SpaceCreationTest : InstrumentedTest {
val syncedSpace = aliceSession.spaceService().getSpace(spaceId) val syncedSpace = aliceSession.spaceService().getSpace(spaceId)
// create a room // create a room
val firstChild: String = runBlocking { var firstChild: String? = null
aliceSession.createRoom(CreateRoomParams().apply { commonTestHelper.waitWithLatch {
this.name = "FirstRoom" GlobalScope.launch {
this.topic = "Description of first room" firstChild = aliceSession.createRoom(CreateRoomParams().apply {
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT this.name = "FirstRoom"
}) this.topic = "Description of first room"
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
})
it.countDown()
}
} }
runBlocking { commonTestHelper.waitWithLatch {
syncedSpace?.addChildren(firstChild, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", true) GlobalScope.launch {
syncedSpace?.addChildren(firstChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", true, suggested = true)
it.countDown()
}
} }
val secondChild: String = runBlocking { var secondChild: String? = null
aliceSession.createRoom(CreateRoomParams().apply { commonTestHelper.waitWithLatch {
this.name = "SecondRoom" GlobalScope.launch {
this.topic = "Description of second room" secondChild = aliceSession.createRoom(CreateRoomParams().apply {
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT this.name = "SecondRoom"
}) this.topic = "Description of second room"
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
})
it.countDown()
}
} }
runBlocking { commonTestHelper.waitWithLatch {
syncedSpace?.addChildren(secondChild, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", false) GlobalScope.launch {
syncedSpace?.addChildren(secondChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", false, suggested = true)
it.countDown()
}
} }
// Try to join from bob, it's a public space no need to invite // Try to join from bob, it's a public space no need to invite
var joinResult: JoinSpaceResult? = null
val joinResult = runBlocking { commonTestHelper.waitWithLatch {
bobSession.spaceService().joinSpace(spaceId) GlobalScope.launch {
joinResult = bobSession.spaceService().joinSpace(spaceId)
// wait a bit to let the summary update it self :/
it.countDown()
}
} }
assertEquals(JoinSpaceResult.Success, joinResult) assertEquals(JoinSpaceResult.Success, joinResult)
val spaceBobPov = bobSession.spaceService().getSpace(spaceId) val spaceBobPov = bobSession.spaceService().getSpace(spaceId)
assertEquals(roomName, spaceBobPov?.asRoom()?.roomSummary()?.name, "Room name should be set") assertEquals("Room name should be set", roomName, spaceBobPov?.asRoom()?.roomSummary()?.name)
assertEquals(topic, spaceBobPov?.asRoom()?.roomSummary()?.topic, "Room topic should be set") assertEquals("Room topic should be set", topic, spaceBobPov?.asRoom()?.roomSummary()?.topic)
// check if bob has joined automatically the first room // check if bob has joined automatically the first room
val bobMembershipFirstRoom = bobSession.getRoom(firstChild)?.roomSummary()?.membership val bobMembershipFirstRoom = bobSession.getRoomSummary(firstChild!!)?.membership
assertEquals("Bob should have joined this room", Membership.JOIN, bobMembershipFirstRoom) assertEquals("Bob should have joined this room", Membership.JOIN, bobMembershipFirstRoom)
RoomSummaryQueryParams.Builder() RoomSummaryQueryParams.Builder()
val spaceSummaryBobPov = bobSession.spaceService().getSpaceSummaries(roomSummaryQueryParams { val childCount = bobSession.getRoomSummaries(
this.roomId = QueryStringValue.Equals(spaceId) roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN) activeSpaceId = ActiveSpaceFilter.ActiveSpace(spaceId)
}).firstOrNull() }
).size
assertEquals("Unexpected number of children", 2, spaceSummaryBobPov?.spaceChildren?.size ?: -1) assertEquals("Unexpected number of joined children", 1, childCount)
commonTestHelper.signOutAndClose(aliceSession) commonTestHelper.signOutAndClose(aliceSession)
commonTestHelper.signOutAndClose(bobSession) commonTestHelper.signOutAndClose(bobSession)

View file

@ -20,7 +20,6 @@ import android.util.Log
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
@ -32,6 +31,7 @@ import org.junit.runner.RunWith
import org.junit.runners.JUnit4 import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.RoomType
@ -51,27 +51,38 @@ class SpaceHierarchyTest : InstrumentedTest {
val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val session = commonTestHelper.createAccount("John", SessionTestParams(true))
val spaceName = "My Space" val spaceName = "My Space"
val topic = "A public space for test" val topic = "A public space for test"
val spaceId: String var spaceId: String = ""
runBlocking { commonTestHelper.waitWithLatch {
spaceId = session.spaceService().createSpace(spaceName, topic, null, true) GlobalScope.launch {
// wait a bit to let the summary update it self :/ spaceId = session.spaceService().createSpace(spaceName, topic, null, true)
delay(400) it.countDown()
}
} }
val syncedSpace = session.spaceService().getSpace(spaceId) val syncedSpace = session.spaceService().getSpace(spaceId)
val roomId = runBlocking { var roomId: String = ""
session.createRoom(CreateRoomParams().apply { name = "General" }) commonTestHelper.waitWithLatch {
GlobalScope.launch {
roomId = session.createRoom(CreateRoomParams().apply { name = "General" })
it.countDown()
}
} }
val viaServers = listOf(session.sessionParams.homeServerHost ?: "") val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
runBlocking { commonTestHelper.waitWithLatch {
syncedSpace!!.addChildren(roomId, viaServers, null, true) GlobalScope.launch {
syncedSpace!!.addChildren(roomId, viaServers, null, true)
it.countDown()
}
} }
runBlocking { commonTestHelper.waitWithLatch {
session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers) GlobalScope.launch {
session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers)
it.countDown()
}
} }
Thread.sleep(9000) Thread.sleep(9000)
@ -92,52 +103,72 @@ class SpaceHierarchyTest : InstrumentedTest {
assertEquals(spaceName, canonicalParents.first().roomSummary?.name) assertEquals(spaceName, canonicalParents.first().roomSummary?.name)
} }
@Test // @Test
fun testCreateChildRelations() { // fun testCreateChildRelations() {
val session = commonTestHelper.createAccount("Jhon", SessionTestParams(true)) // val session = commonTestHelper.createAccount("Jhon", SessionTestParams(true))
val spaceName = "My Space" // val spaceName = "My Space"
val topic = "A public space for test" // val topic = "A public space for test"
Log.d("## TEST", "Before") // Log.d("## TEST", "Before")
val spaceId = runBlocking { //
session.spaceService().createSpace(spaceName, topic, null, true) // var spaceId = ""
} // commonTestHelper.waitWithLatch {
// GlobalScope.launch {
Log.d("## TEST", "created space $spaceId ${Thread.currentThread()}") // spaceId = session.spaceService().createSpace(spaceName, topic, null, true)
val syncedSpace = session.spaceService().getSpace(spaceId) // it.countDown()
// }
val children = listOf("General" to true /*canonical*/, "Random" to false) // }
//
val roomIdList = children.map { // Log.d("## TEST", "created space $spaceId ${Thread.currentThread()}")
runBlocking { // val syncedSpace = session.spaceService().getSpace(spaceId)
session.createRoom(CreateRoomParams().apply { name = it.first }) //
} to it.second // val children = listOf("General" to true /*canonical*/, "Random" to false)
} //
// // val roomIdList = children.map {
val viaServers = listOf(session.sessionParams.homeServerHost ?: "") // // runBlocking {
// // session.createRoom(CreateRoomParams().apply { name = it.first })
runBlocking { // // } to it.second
roomIdList.forEach { entry -> // // }
syncedSpace!!.addChildren(entry.first, viaServers, null, true) // val roomIdList = mutableListOf<Pair<String, Boolean>>()
} // commonTestHelper.waitWithLatch {
} // GlobalScope.launch {
// children.forEach {
runBlocking { // val rID = session.createRoom(CreateRoomParams().apply { name = it.first })
roomIdList.forEach { // roomIdList.add(rID to it.second)
session.spaceService().setSpaceParent(it.first, spaceId, it.second, viaServers) // }
} // it.countDown()
delay(400) // }
} // }
//
roomIdList.forEach { // val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
val parents = session.getRoom(it.first)?.roomSummary()?.spaceParents //
val canonicalParents = session.getRoom(it.first)?.roomSummary()?.spaceParents?.filter { it.canonical == true } // commonTestHelper.waitWithLatch {
// GlobalScope.launch {
assertNotNull(parents) // roomIdList.forEach { entry ->
assertEquals("Unexpected number of parent", 1, parents!!.size) // syncedSpace!!.addChildren(entry.first, viaServers, null, true)
assertEquals("Unexpected parent name", spaceName, parents.first().roomSummary?.name) // }
assertEquals("Parent of ${it.first} should be canonical ${it.second}", if (it.second) 1 else 0, canonicalParents?.size ?: 0) // it.countDown()
} // }
} // }
//
// commonTestHelper.waitWithLatch {
// GlobalScope.launch {
// roomIdList.forEach {
// session.spaceService().setSpaceParent(it.first, spaceId, it.second, viaServers)
// }
// it.countDown()
// }
// }
//
// roomIdList.forEach {
// val parents = session.getRoom(it.first)?.roomSummary()?.spaceParents
// val canonicalParents = session.getRoom(it.first)?.roomSummary()?.spaceParents?.filter { it.canonical == true }
//
// assertNotNull(parents)
// assertEquals("Unexpected number of parent", 1, parents!!.size)
// assertEquals("Unexpected parent name", spaceName, parents.first().roomSummary?.name)
// assertEquals("Parent of ${it.first} should be canonical ${it.second}", if (it.second) 1 else 0, canonicalParents?.size ?: 0)
// }
// }
@Test @Test
fun testFilteringBySpace() { fun testFilteringBySpace() {
@ -162,18 +193,30 @@ class SpaceHierarchyTest : InstrumentedTest {
// add C as a subspace of A // add C as a subspace of A
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId) val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
val viaServers = listOf(session.sessionParams.homeServerHost ?: "") val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
runBlocking { commonTestHelper.waitWithLatch {
spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) GlobalScope.launch {
session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers) spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
it.countDown()
}
} }
// Create orphan rooms // Create orphan rooms
val orphan1 = runBlocking { var orphan1 = ""
session.createRoom(CreateRoomParams().apply { name = "O1" }) commonTestHelper.waitWithLatch {
GlobalScope.launch {
orphan1 = session.createRoom(CreateRoomParams().apply { name = "O1" })
it.countDown()
}
} }
val orphan2 = runBlocking {
session.createRoom(CreateRoomParams().apply { name = "O2" }) var orphan2 = ""
commonTestHelper.waitWithLatch {
GlobalScope.launch {
orphan2 = session.createRoom(CreateRoomParams().apply { name = "O2" })
it.countDown()
}
} }
val allRooms = session.getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) }) val allRooms = session.getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) })
@ -195,16 +238,18 @@ class SpaceHierarchyTest : InstrumentedTest {
assertTrue("A1 should be a grand child of A", aChildren.any { it.name == "C2" }) assertTrue("A1 should be a grand child of A", aChildren.any { it.name == "C2" })
// Add a non canonical child and check that it does not appear as orphan // Add a non canonical child and check that it does not appear as orphan
val a3 = runBlocking { commonTestHelper.waitWithLatch {
session.createRoom(CreateRoomParams().apply { name = "A3" }) GlobalScope.launch {
} val a3 = session.createRoom(CreateRoomParams().apply { name = "A3" })
runBlocking { spaceA!!.addChildren(a3, viaServers, null, false)
spaceA!!.addChildren(a3, viaServers, null, false) it.countDown()
delay(400) }
// here we do not set the parent!!
} }
val orphansUpdate = session.getFlattenRoomSummaryChildrenOf(null) Thread.sleep(2_000)
val orphansUpdate = session.getRoomSummaries(roomSummaryQueryParams {
activeSpaceId = ActiveSpaceFilter.ActiveSpace(null)
})
assertEquals("Unexpected number of orphan rooms ${orphansUpdate.map { it.name }}", 2, orphansUpdate.size) assertEquals("Unexpected number of orphan rooms ${orphansUpdate.map { it.name }}", 2, orphansUpdate.size)
} }
@ -225,15 +270,21 @@ class SpaceHierarchyTest : InstrumentedTest {
// add C as a subspace of A // add C as a subspace of A
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId) val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
val viaServers = listOf(session.sessionParams.homeServerHost ?: "") val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
runBlocking { commonTestHelper.waitWithLatch {
spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) GlobalScope.launch {
session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers) spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
it.countDown()
}
} }
// add back A as subspace of C // add back A as subspace of C
runBlocking { commonTestHelper.waitWithLatch {
val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId) GlobalScope.launch {
spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true) val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId)
spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true)
it.countDown()
}
} }
Thread.sleep(1000) Thread.sleep(1000)
@ -343,8 +394,12 @@ class SpaceHierarchyTest : InstrumentedTest {
childInfo: List<Triple<String, Boolean, Boolean?>> childInfo: List<Triple<String, Boolean, Boolean?>>
/** Name, auto-join, canonical*/ /** Name, auto-join, canonical*/
): TestSpaceCreationResult { ): TestSpaceCreationResult {
val spaceId = runBlocking { var spaceId = ""
session.spaceService().createSpace(spaceName, "Test Topic", null, true) commonTestHelper.waitWithLatch {
GlobalScope.launch {
spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true)
it.countDown()
}
} }
val syncedSpace = session.spaceService().getSpace(spaceId) val syncedSpace = session.spaceService().getSpace(spaceId)
@ -352,9 +407,14 @@ class SpaceHierarchyTest : InstrumentedTest {
val roomIds = val roomIds =
childInfo.map { entry -> childInfo.map { entry ->
runBlocking { var roomId = ""
session.createRoom(CreateRoomParams().apply { name = entry.first }) commonTestHelper.waitWithLatch {
GlobalScope.launch {
roomId = session.createRoom(CreateRoomParams().apply { name = entry.first })
it.countDown()
}
} }
roomId
} }
roomIds.forEachIndexed { index, roomId -> roomIds.forEachIndexed { index, roomId ->
@ -401,7 +461,7 @@ class SpaceHierarchyTest : InstrumentedTest {
// + A // + A
// a1, a2 // a1, a2
// + B // + B
// b1, b2, b3 // b1, b2, b3
// + C // + C
// + c1, c2 // + c1, c2

View file

@ -16,31 +16,35 @@
package org.matrix.android.sdk.api.session.room.model package org.matrix.android.sdk.api.session.room.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/** /**
* Ref: https://matrix.org/docs/spec/client_server/latest#room-history-visibility * Ref: https://matrix.org/docs/spec/client_server/latest#room-history-visibility
*/ */
@JsonClass(generateAdapter = false)
enum class RoomHistoryVisibility { enum class RoomHistoryVisibility {
/** /**
* All events while this is the m.room.history_visibility value may be shared by any * All events while this is the m.room.history_visibility value may be shared by any
* participating homeserver with anyone, regardless of whether they have ever joined the room. * participating homeserver with anyone, regardless of whether they have ever joined the room.
*/ */
WORLD_READABLE, @Json(name = "world_readable") WORLD_READABLE,
/** /**
* Previous events are always accessible to newly joined members. All events in the * Previous events are always accessible to newly joined members. All events in the
* room are accessible, even those sent when the member was not a part of the room. * room are accessible, even those sent when the member was not a part of the room.
*/ */
SHARED, @Json(name = "shared") SHARED,
/** /**
* Events are accessible to newly joined members from the point they were invited onwards. * Events are accessible to newly joined members from the point they were invited onwards.
* Events stop being accessible when the member's state changes to something other than invite or join. * Events stop being accessible when the member's state changes to something other than invite or join.
*/ */
INVITED, @Json(name = "invited") INVITED,
/** /**
* Events are accessible to newly joined members from the point they joined the room onwards. * Events are accessible to newly joined members from the point they joined the room onwards.
* Events stop being accessible when the member's state changes to something other than join. * Events stop being accessible when the member's state changes to something other than join.
*/ */
JOINED @Json(name = "joined") JOINED
} }