Skip to content

Commit

Permalink
實作離開房間 (#101)
Browse files Browse the repository at this point in the history
* 依據第二次code review修正
依據第一次code review修正
實作離開房間

* 依據第三次code review修正

* 根據第四次code review修正

---------

Co-authored-by: Ted <[email protected]>
  • Loading branch information
ted791029 and Ted authored Jul 8, 2023
1 parent 8c61ad2 commit e3d44f1
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ interface RoomRepository {
fun update(room: Room): Room
fun findByStatus(status: Room.Status, page: Pagination<Any>): Pagination<Room>
fun deleteById(roomId: Room.Id)
fun leaveRoom(room: Room)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package tw.waterballsa.gaas.application.usecases

import tw.waterballsa.gaas.application.eventbus.EventBus
import tw.waterballsa.gaas.application.repositories.RoomRepository
import tw.waterballsa.gaas.domain.Room
import tw.waterballsa.gaas.domain.Room.Player
import tw.waterballsa.gaas.exceptions.NotFoundException.Companion.notFound
import javax.inject.Named

@Named
class LeaveRoomUsecase(
private val roomRepository: RoomRepository,
private val eventBus: EventBus
) {

fun execute(request: LeaveRoomUsecase.Request) {
with(request) {
val room = findRoomById(Room.Id(roomId))
room.leaveRoom(Player.Id(playerId))
roomRepository.leaveRoom(room)
}
}

private fun findRoomById(roomId: Room.Id) =
roomRepository.findById(roomId)
?: throw notFound(Room::class).id(roomId)

data class Request(
val roomId: String,
val playerId: String
)

}
14 changes: 13 additions & 1 deletion domain/src/main/kotlin/tw/waterballsa/gaas/domain/Room.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import tw.waterballsa.gaas.exceptions.PlatformException
class Room(
var roomId: Id? = null,
val game: GameRegistration,
val host: Player,
var host: Player,
val players: MutableList<Player>,
val maxPlayers: Int,
val minPlayers: Int,
Expand Down Expand Up @@ -44,6 +44,18 @@ class Room(
players.remove(player)
}

fun leaveRoom(playerId: Player.Id) {
players.removeIf { it.id == playerId }
if (playerId == host.id) {
changeHost()
}
}

private fun changeHost() {
players.firstOrNull()
?.let { host = it }
}

private fun findPlayer(playerId: Player.Id): Player? = players.find { it.id == playerId }

@JvmInline
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ class RoomController(
private val getRoomsUseCase: GetRoomsUseCase,
private val closeRoomsUseCase: CloseRoomUsecase,
private val changePlayerReadinessUsecase: ChangePlayerReadinessUsecase,
private val kickPlayerUseCase: KickPlayerUsecase
private val kickPlayerUseCase: KickPlayerUsecase,
private val leaveRoomUsecase: LeaveRoomUsecase
) {
@PostMapping
fun createRoom(
Expand Down Expand Up @@ -112,6 +113,16 @@ class RoomController(
return PlatformViewModel.success()
}

@DeleteMapping("/{roomId}/players/me")
@ResponseStatus(NO_CONTENT)
fun leaveRoom(
@AuthenticationPrincipal jwt: Jwt,
@PathVariable roomId: String
) {
val leaverId = jwt.subject ?: throw PlatformException("User id must exist.")
leaveRoomUsecase.execute(LeaveRoomUsecase.Request(roomId, leaverId))
}

class CreateRoomRequest(
private val name: String,
private val gameId: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ class SpringRoomRepository(
roomDAO.deleteById(roomId.value)
}

override fun leaveRoom(room: Room) {
roomDAO.save(room.toData())
}

private fun RoomData.toDomain(): Room =
Room(
roomId = Id(id!!),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
package tw.waterballsa.gaas.spring.it.controllers

import org.assertj.core.api.Assertions.*
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.oauth2.core.oidc.OidcIdToken
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser
import org.springframework.security.oauth2.core.oidc.user.OidcUser
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin
import org.springframework.test.web.servlet.ResultActions
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import tw.waterballsa.gaas.application.model.Pagination
Expand Down Expand Up @@ -272,6 +272,18 @@ class RoomControllerTest @Autowired constructor(
.thenPlayersShouldBeInTheRoom(roomC.roomId!!, hostA, playerB)
}

@Test
fun givenHostAndPlayerBAndPlayerCAreInRoomD_WhenHostLeaveRoomD_ThenPreviousHostShouldBeNotInRoomDAndChangedNewHost() {
val userA = testUser
val host = userA.toRoomPlayer()
val playerB = createUser("2", "[email protected]", "winner1122").toRoomPlayer()
val playerC = createUser("3", "[email protected]", "winner0033").toRoomPlayer()

givenHostAndPlayersAreInTheRoom(host, playerB, playerC)
.whenUserLeaveTheRoom(userA)
.thenPlayerShouldBeNotInRoomAndHostIsChanged(host)
}

private fun TestGetRoomsRequest.whenUserAVisitLobby(joinUser: User): ResultActions =
mockMvc.perform(
get("/rooms")
Expand Down Expand Up @@ -325,6 +337,12 @@ class RoomControllerTest @Autowired constructor(
.withJwt(user.id!!.value.toJwt())
)

private fun leaveRoom(leaveUser: Jwt): ResultActions =
mockMvc.perform(
delete("/rooms/${testRoom.roomId!!.value}/players/me")
.withJwt(leaveUser)
)

private fun givenTheHostCreatePublicRoom(host: User): Room {
testRoom = createRoom(host)
return testRoom
Expand All @@ -335,6 +353,12 @@ class RoomControllerTest @Autowired constructor(
return testRoom
}

private fun givenHostAndPlayersAreInTheRoom(host: Player, vararg players: Player): Room {
val combinedPlayers = (listOf(host) + players).toMutableList()
testRoom = createRoom(host, combinedPlayers)
return testRoom
}

private fun Room.whenUserJoinTheRoom(user: User, password: String? = null): ResultActions {
val request = joinRoomRequest(password)
val joinUser = mockOidcUser(user)
Expand All @@ -349,6 +373,11 @@ class RoomControllerTest @Autowired constructor(
post("/rooms/${roomId.value}/players/me:cancel").withJwt(user.id!!.value.toJwt())
)

private fun Room.whenUserLeaveTheRoom(user: User): ResultActions {
val leaveUser = user.id!!.value.toJwt()
return leaveRoom(leaveUser)
}

private fun ResultActions.thenCreateRoomSuccessfully(request: TestCreateRoomRequest) {
request.let {
andExpect(status().isCreated)
Expand Down Expand Up @@ -380,6 +409,13 @@ class RoomControllerTest @Autowired constructor(
.andExpect(jsonPath("$.message").value("${resourceType.simpleName} not found"))
}

private fun ResultActions.thenPlayerShouldBeNotInRoomAndHostIsChanged(player: Player) {
andExpect(status().isNoContent)
val room = roomRepository.findById(testRoom.roomId!!)!!
assertFalse(room.hasPlayer(player.id))
assertFalse(room.isHost(player.id))
}

private fun createUser(id: String, email: String, nickname: String): User =
userRepository.createUser(User(User.Id(id), email, nickname))

Expand Down Expand Up @@ -411,6 +447,20 @@ class RoomControllerTest @Autowired constructor(
)
)

private fun createRoom(host: Player, players: MutableList<Player>, password: String? = null): Room =
roomRepository.createRoom(
Room(
game = testGame,
host = host,
players = players,
maxPlayers = testGame.maxPlayers,
minPlayers = testGame.minPlayers,
name = "My Room",
status = Room.Status.WAITING,
password = password
)
)

private fun createRoomRequest(password: String? = null): TestCreateRoomRequest =
TestCreateRoomRequest(
name = "Rapid Mahjong Room",
Expand Down Expand Up @@ -466,4 +516,13 @@ class RoomControllerTest @Autowired constructor(
findRoomById(roomId)!!.players.map { it.id.value }.let {
assertThat(it).containsAll(users.map { user -> user.id!!.value })
}

private fun User.toRoomPlayer(): Player =
Player(Player.Id(id!!.value), nickname)

private fun Room.hasPlayer(playerId: Player.Id): Boolean =
players.any { it.id == playerId }

private fun Room.isHost(playerId: Player.Id): Boolean =
host.id == playerId
}

0 comments on commit e3d44f1

Please sign in to comment.