강동현
Builds for 1 pipeline passed in 8 minutes 6 seconds

Merge branch 'feature/login-with-nickname' into develop

......@@ -14,7 +14,8 @@
## 로그인
서버에 `login`을 보냅니다. 아직 로그인 로직이 구현되지 않았으므로, 임의로 사용할 `username`만을 포함하여 보내면 됩니다.
서버에 `login`을 보냅니다. 본인이 사용하고 싶은 닉네임을 `nickname`속성에 담아 보내면 됩니다. 닉네임은 공백이 허용되지 않고 최대 12글자입니다. 만약 이 조건을 만족하지 않는다면 요청에 실패하고 사유가 전달됩니다. 요청에 성공한다면 문자열이 반환되는데, 이는 본인의 고유한 `uuid`값입니다. 앞으로 사용되는 모든 `username` 속성에 이 값을 사용해주세요. **`username`은 유저의 이름이 아니라 고유한 `uuid`를 의미하고, `nickname`은 고유하지 않을 수 있습니다!**
상대방의 닉네임은 방 접속시에 얻을 수 있습니다. 오직 UI에 보여주기 위한 용도로만 사용되며, 실제 처리는 모두 `username`을 통해 이뤄집니다.
## 로비
......@@ -24,6 +25,8 @@
새로운 방을 만들고 싶다면 `createRoom`을 보내면 됩니다. 방의 이름을 `name` 속성에 담아 보내야 합니다. 방의 이름은 공백일 수 없고, 최대 30자까지 가능합니다. 방을 만드는데 성공했다면 방에 자동으로 접속되고 방장을 맡게 됩니다. 이때 반환값은 `joinRoom`과 동일한 `RoomInfo`를 받습니다.
`RoomInfo`에는 각 유저의 닉네임을 `nickname` 속성으로 담고 있습니다.
### 방
방에 접속중인 유저의 목록에 변화가 생기면, `updateRoomUser`가 수신됩니다. `state`는 다음 3가지 값 중 하나의 값을 가집니다.
......@@ -32,6 +35,8 @@
- `updated`: 기존 유저의 정보가 업데이트되었습니다. 해당 유저의 방장, 준비 여부가 변경되면 수신됩니다.
- `removed`: 유저가 방에서 퇴장하였습니다.
해당 메세지에는 변화된 유저를 특정하기 위한 `username`이 포함되어 있고, 그 유저의 닉네임인 `nickname`, 방장 여부인 `admin`, 준비 여부인 `ready`를 포함하고 있습니다.
유저가 채팅을 입력했다면 `chat`을 보내면 됩니다. 다른 사람이 채팅을 입력한 경우에도 `chat`이 수신됩니다(`ServerInboundMessage<"chat">``ServerOutboundMessage<"chat">`이 다르게 정의되었다는 점에 유의하세요). 자신이 보낸 채팅에 대해서는 수신되지 않으므로 이 경우 오프라인으로 메세지를 추가하여야 합니다. 채팅 문자열의 양 끝 공백을 제거했을 때 문자열이 빈 문자열이거나, 채팅 문자열의 길이가 300을 초과하는 경우 요청이 실패 처리됩니다.
방을 나가고 싶으면 `leaveRoom`을 보내면 됩니다.
......
......@@ -38,6 +38,7 @@ export const RoomInfoRecord = Record({
users: Array(
Record({
username: String,
nickname: String,
admin: Boolean,
ready: Boolean,
})
......
......@@ -22,7 +22,7 @@ import {
// 'result' 속성은 서버 요청 결과에만 포함되는 특별한 속성입니다.
export class ServerInboundMessageRecordMap {
// 로그인을 시도합니다.
login = Record({ username: String });
login = Record({ nickname: String, result: String });
// 방 목록을 요청합니다.
roomList = Record({
......@@ -94,6 +94,7 @@ interface ServerOutboundMessageMap {
state: "added" | "updated" | "removed";
user: {
username: string;
nickname: string;
admin: boolean;
ready: boolean;
};
......
......@@ -77,7 +77,11 @@ export class Connection {
private handleLogin(
message: ServerInboundMessage<"login">
): ServerResponse<"login"> {
this.user = new User(message.username, this);
if (message.nickname.length > 12 || message.nickname.trim().length === 0) {
return { ok: false };
}
this.user = new User(message.nickname, this);
// console.log(`User ${message.username} has logged in!`);
return { ok: true };
......
......@@ -106,6 +106,7 @@ export class Room {
state: "added",
user: {
username: user.username,
nickname: user.nickname,
admin: user === this.admin,
ready: this.usersReady.includes(user),
},
......@@ -128,6 +129,7 @@ export class Room {
state: "removed",
user: {
username: user.username,
nickname: user.nickname,
admin: user === this.admin,
ready: this.usersReady.includes(user),
},
......@@ -221,6 +223,7 @@ export class Room {
state: "updated",
user: {
username: user.username,
nickname: user.nickname,
admin: this.isAdmin(user),
ready: this.isReady(user),
},
......@@ -244,6 +247,7 @@ export class Room {
users: this.users.map((u) => {
return {
username: u.username,
nickname: u.nickname,
admin: this.isAdmin(u),
ready: this.usersReady.includes(u),
};
......
......@@ -24,16 +24,14 @@ describe("방장", () => {
const response = socket2.test("joinRoom", { uuid: room.uuid });
expect(response.ok).eq(true);
expect(room.admin).eq(user1);
expect(response.result?.users[0]).deep.eq({
username: user1.username,
admin: true,
ready: false,
});
expect(response.result?.users[1]).deep.eq({
username: user2.username,
admin: false,
ready: false,
});
expect(response.result?.users[0].nickname).eq(user1.nickname);
expect(response.result?.users[0].admin).eq(true);
expect(response.result?.users[0].ready).eq(false);
expect(response.result?.users[1].nickname).eq(user2.nickname);
expect(response.result?.users[1].admin).eq(false);
expect(response.result?.users[1].ready).eq(false);
});
it("방장이 나가면 방장이 인계됩니다", () => {
const {
......
......@@ -14,6 +14,7 @@ describe("방 입장", () => {
it("방에 입장합니다", () => {
const {
sockets: [socket],
users: [user],
rooms: [room],
} = prepareUsersEmptyRooms(1, 1);
......@@ -23,16 +24,17 @@ describe("방 입장", () => {
expect(response.result?.name).eq(room.name);
expect(response.result?.maxUsers).eq(room.maxUsers);
expect(response.result?.users?.length).eq(1);
expect(response.result?.users[0]?.username).eq("guest1");
expect(response.result?.users[0]?.nickname).eq(user.nickname);
});
it("방에 입장하면 유저 목록이 업데이트 됩니다", () => {
const {
sockets: [socket1, socket2],
users: [user1, user2],
} = prepareJoinedRoom(2);
const updated = socket1.socket.received("updateRoomUser");
expect(updated.state).eq("added");
expect(updated.user.username).eq("guest2");
expect(updated.user.nickname).eq(user2.nickname);
});
it("방에 이미 입장한 상태에서 다른 방에 입장할 수 없습니다", () => {
const {
......
......@@ -3,10 +3,22 @@ import { RoomManager } from "../room/RoomManager";
import { SocketTester } from "./util/SocketTester";
describe("로그인", () => {
it("로그인합니다", () => {
it("닉네임으로 로그인합니다", () => {
const roomManager = new RoomManager();
const socket = new SocketTester(roomManager);
socket.testOk("login", { username: "guest" });
expect(socket.connection.user?.username).eq("guest");
socket.testOk("login", { nickname: "guest" });
expect(socket.connection.user?.nickname).eq("guest");
});
it("빈 닉네임으로 로그인할 수 없습니다", () => {
const roomManager = new RoomManager();
const socket = new SocketTester(roomManager);
socket.testNotOk("login", { nickname: "" });
socket.testNotOk("login", { nickname: " " });
});
it("닉네임은 최대 12자입니다", () => {
const roomManager = new RoomManager();
const socket = new SocketTester(roomManager);
socket.testNotOk("login", { nickname: "a".repeat(13) });
socket.testNotOk("login", { nickname: "가".repeat(13) });
});
});
......
......@@ -7,12 +7,13 @@ describe("준비", () => {
it("방에 입장하면 준비되지 않은 상태입니다", () => {
const {
sockets: [socket],
users: [user],
rooms: [room],
} = prepareUsersEmptyRooms(1, 1);
const response = socket.test("joinRoom", { uuid: room.uuid });
expect(response.ok).eq(true);
expect(response.result?.users[0]?.username).eq("guest1");
expect(response.result?.users[0]?.nickname).eq(user.nickname);
expect(response.result?.users[0]?.ready).eq(false);
});
it("준비 상태를 설정합니다", () => {
......@@ -26,25 +27,19 @@ describe("준비", () => {
socket.testOk("ready", { ready: true });
expect(room.isReady(user)).eq(true);
expect(socket.socket.received("updateRoomUser")).deep.eq({
state: "updated",
user: {
username: user.username,
admin: false,
ready: true,
},
});
let response = socket.socket.received("updateRoomUser");
expect(response.state).eq("updated");
expect(response.user.nickname).eq(user.nickname);
expect(response.user.ready).eq(true);
socket.testOk("ready", { ready: false });
expect(room.isReady(user)).eq(false);
expect(socket.socket.received("updateRoomUser")).deep.eq({
state: "updated",
user: {
username: user.username,
admin: false,
ready: false,
},
});
response = socket.socket.received("updateRoomUser");
expect(response.state).eq("updated");
expect(response.user.nickname).eq(user.nickname);
expect(response.user.ready).eq(false);
});
it("방장은 준비할 수 없습니다", () => {
const {
......
......@@ -49,8 +49,8 @@ export class SocketTester {
return this.connection.handleRaw(obj);
}
public login(username: string): void {
this.testOk("login", { username });
public login(nickname: string): void {
this.testOk("login", { nickname });
expect(this.connection.user !== undefined).eq(true);
}
......
......@@ -3,9 +3,11 @@ import { Connection } from "../connection/Connection";
import { MessageHandler } from "../message/MessageHandler";
import { Room } from "../room/Room";
import { RoomManager } from "../room/RoomManager";
import { v4 as uuidv4 } from "uuid";
export class User {
public readonly username: string;
public readonly username: string; // TODO: 실제 역할은 uuid임
public readonly nickname: string;
public readonly connection: Connection;
......@@ -13,8 +15,9 @@ export class User {
public handler: MessageHandler;
constructor(username: string, connection: Connection) {
this.username = username;
constructor(nickname: string, connection: Connection) {
this.username = uuidv4();
this.nickname = nickname;
this.connection = connection;
this.handler = new MessageHandler({
roomList: (user, message) => {
......