Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add /rename endpoint for lobby #414

Merged
merged 2 commits into from
Jun 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/api/Lobby.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,20 @@ Accepts two parameters, all required:

Returns `playerCredentials` which is the token this player will require to authenticate their actions in the future.

#### Renaming a player

##### POST `/games/{name}/{id}/rename`

Rename a user in the room instance `id` of a game named `name` previously joined by the player.

Accepts three parameters, all required:

`playerID`: the ID used by the player in the game (0, 1...).

`credentials`: the authentication token of the player.

`newName`: the new name of the player.

#### Leaving a room

##### POST `/games/{name}/{id}/leave`
Expand Down
29 changes: 29 additions & 0 deletions src/server/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,35 @@ export const addApiToServer = ({ app, db, games, lobbyConfig }) => {
ctx.body = {};
});

router.post('/games/:name/:id/rename', koaBody(), async ctx => {
const gameName = ctx.params.name;
const roomID = ctx.params.id;
const playerID = ctx.request.body.playerID;
const credentials = ctx.request.body.credentials;
const newName = ctx.request.body.newName;
const namespacedGameID = getNamespacedGameID(roomID, gameName);
const gameMetadata = await db.get(GameMetadataKey(namespacedGameID));
if (typeof playerID === 'undefined') {
ctx.throw(403, 'playerID is required');
}
if (!newName) {
ctx.throw(403, 'newName is required');
}
if (!gameMetadata) {
ctx.throw(404, 'Game ' + roomID + ' not found');
}
if (!gameMetadata.players[playerID]) {
ctx.throw(404, 'Player ' + playerID + ' not found');
}
if (credentials !== gameMetadata.players[playerID].credentials) {
ctx.throw(403, 'Invalid credentials ' + credentials);
}

gameMetadata.players[playerID].name = newName;
await db.set(GameMetadataKey(namespacedGameID), gameMetadata);
ctx.body = {};
});

app.use(cors());

// If API_SECRET is set, then require that requests set an
Expand Down
120 changes: 120 additions & 0 deletions src/server/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,126 @@ describe('.createApiServer', () => {
});
});

describe('rename', () => {
let response;
let db;
let games;

beforeEach(() => {
games = [Game({ name: 'foo' })];
});

describe('for an unprotected lobby', () => {
beforeEach(() => {
delete process.env.API_SECRET;
});

describe('when the game does not exist', () => {
test('throws a "not found" error', async () => {
db = {
get: async () => null,
};
const app = createApiServer({ db, games });
response = await request(app.callback())
.post('/games/foo/1/rename')
.send('playerID=0&playerName=alice&newName=ali');
expect(response.status).toEqual(404);
});
});

describe('when the game does exist', () => {
describe('when the playerID does exist', () => {
let setSpy;
beforeEach(async () => {
setSpy = jest.fn();
db = {
get: async () => {
return {
players: {
'0': {
name: 'alice',
credentials: 'SECRET1',
},
'1': {
name: 'bob',
credentials: 'SECRET2',
},
},
};
},
set: async (id, game) => setSpy(id, game),
};
const app = createApiServer({ db, games });
response = await request(app.callback())
.post('/games/foo/1/rename')
.send('playerID=0&credentials=SECRET1&newName=ali');
});

test('is successful', async () => {
expect(response.status).toEqual(200);
});

test('updates the players', async () => {
expect(setSpy).toHaveBeenCalledWith(
expect.stringMatching(':metadata'),
expect.objectContaining({
players: expect.objectContaining({
'0': expect.objectContaining({
name: 'ali',
}),
}),
})
);
});
});

describe('when the playerID does not exist', () => {
test('throws error 404', async () => {
const app = createApiServer({ db, games });
response = await request(app.callback())
.post('/games/foo/1/rename')
.send('playerID=2&credentials=SECRET1&newName=joe');
expect(response.status).toEqual(404);
});
});

describe('when the credentials are invalid', () => {
test('throws error 404', async () => {
const app = createApiServer({ db, games });
response = await request(app.callback())
.post('/games/foo/1/rename')
.send('playerID=0&credentials=SECRET2&newName=mike');
expect(response.status).toEqual(403);
});
});
describe('when playerID is omitted', () => {
beforeEach(async () => {
const app = createApiServer({ db, games });
response = await request(app.callback())
.post('/games/foo/1/rename')
.send('credentials=foo&newName=bill');
});

test('throws error 403', async () => {
expect(response.status).toEqual(403);
});
describe('when newName is omitted', () => {
beforeEach(async () => {
const app = createApiServer({ db, games });
response = await request(app.callback())
.post('/games/foo/1/rename')
.send('credentials=foo&playerID=0');
});

test('throws error 403', async () => {
expect(response.status).toEqual(403);
});
});
});
});
});
});

describe('leaving a room', () => {
let response;
let db;
Expand Down