From 7c795b0fb5984e642e9f691253f7471d218474bd Mon Sep 17 00:00:00 2001 From: tsa96 Date: Tue, 16 Jan 2024 18:48:41 +0000 Subject: [PATCH] test(back-e2e): static map list system --- apps/backend-e2e/src/admin.e2e-spec.ts | 127 ++++++++++++++++++- apps/backend-e2e/src/maps-2.e2e-spec.ts | 29 +++++ apps/backend-e2e/src/maps.e2e-spec.ts | 156 +++++++++++++++++++++++- libs/test-utils/src/utils/s3.util.ts | 11 ++ 4 files changed, 316 insertions(+), 7 deletions(-) diff --git a/apps/backend-e2e/src/admin.e2e-spec.ts b/apps/backend-e2e/src/admin.e2e-spec.ts index 608b3f19a8..2623a88bae 100644 --- a/apps/backend-e2e/src/admin.e2e-spec.ts +++ b/apps/backend-e2e/src/admin.e2e-spec.ts @@ -13,6 +13,7 @@ import { ActivityType, AdminActivityType, Ban, + FlatMapList, Gamemode, MapCreditType, MapStatus, @@ -1707,7 +1708,8 @@ describe('Admin', () => { Promise.all([ db.cleanup('mMap', 'adminActivity'), fileStore.deleteDirectory('maps'), - fileStore.deleteDirectory('submissions') + fileStore.deleteDirectory('submissions'), + fileStore.deleteDirectory('maplist') ]) ); @@ -2268,6 +2270,54 @@ describe('Admin', () => { ).toHaveLength(0); }); + it('should generate new map list version files', async () => { + // This is just stored in memory so we can't test this without calling + // another endpoint. In future we could probably just peer inside of + // Redis. + const oldVersion = await req.get({ + url: 'maps/maplistversion', + status: 200, + token: adminToken + }); + + await req.patch({ + url: `admin/maps/${map.id}`, + status: 204, + body: { status: MapStatus.APPROVED, finalLeaderboards }, + token: adminToken + }); + + const newVersion = await req.get({ + url: 'maps/maplistversion', + status: 200, + token: adminToken + }); + + expect(newVersion.body.approved).toBe(oldVersion.body.approved + 1); + expect(newVersion.body.submissions).toBe( + oldVersion.body.submissions + 1 + ); + + const approvedMapList = await fileStore.getMapListVersion( + FlatMapList.APPROVED, + newVersion.body.approved + ); + expect(approvedMapList).toHaveLength(1); + expect(approvedMapList[0]).toMatchObject({ + id: map.id, + leaderboards: expect.anything(), + info: expect.anything(), + thumbnail: expect.anything() + }); + expect(approvedMapList[0]).not.toHaveProperty('zones'); + + const submissionMapList = await fileStore.getMapListVersion( + FlatMapList.SUBMISSION, + newVersion.body.submissions + ); + expect(submissionMapList).toHaveLength(0); + }); + it('should 400 when moving from FA to approved if leaderboards are not provided', async () => { await req.patch({ url: `admin/maps/${map.id}`, @@ -2278,6 +2328,48 @@ describe('Admin', () => { }); }); + it('should generate a new map list version file without the map when the map gets disabled', async () => { + const oldVersion = await req.get({ + url: 'maps/maplistversion', + status: 200, + token: adminToken + }); + + const map1 = await db.createMap({ + ...createMapData, + status: MapStatusNew.APPROVED + }); + const map2 = await db.createMap({ + ...createMapData, + name: 'surf_thisfileistoobig', + fileName: 'aaaaaaaaaa', + status: MapStatusNew.APPROVED + }); + + await req.patch({ + url: `admin/maps/${map1.id}`, + status: 204, + body: { status: MapStatusNew.DISABLED }, + token: adminToken + }); + + const newVersion = await req.get({ + url: 'maps/maplistversion', + status: 200, + token: adminToken + }); + + expect(newVersion.body.submissions).toBe(oldVersion.body.submissions); + expect(newVersion.body.approved).toBe(oldVersion.body.approved + 1); + + const approvedMapList = await fileStore.getMapListVersion( + FlatMapList.APPROVED, + newVersion.body.approved + ); + expect(approvedMapList).toHaveLength(1); + expect(approvedMapList[0]).toMatchObject({ id: map2.id }); + }); + it('should return 404 if map not found', () => req.patch({ url: `admin/maps/${NULL_ID}`, @@ -2320,7 +2412,9 @@ describe('Admin', () => { await db.createLbRun({ map: m1, user: u1, time: 1, rank: 1 }); }); - afterEach(() => db.cleanup('mMap')); + afterEach(() => + Promise.all([db.cleanup('mMap'), fileStore.deleteDirectory('/maplist')]) + ); it('should successfully delete the map and related stored data', async () => { const fileName = 'my_cool_map'; @@ -2379,6 +2473,35 @@ describe('Admin', () => { ).toBe(true); }); + it('should update the map list version', async () => { + const oldVersion = await req.get({ + url: 'maps/maplistversion', + status: 200, + token: adminToken + }); + + await req.del({ + url: `admin/maps/${m1.id}`, + status: 204, + token: adminToken + }); + + const newVersion = await req.get({ + url: 'maps/maplistversion', + status: 200, + token: adminToken + }); + + expect(newVersion.body.submissions).toBe(oldVersion.body.submissions); + expect(newVersion.body.approved).toBe(oldVersion.body.approved + 1); + + const approvedMapList = await fileStore.getMapListVersion( + FlatMapList.APPROVED, + newVersion.body.approved + ); + expect(approvedMapList).toHaveLength(0); + }); + it('should return 404 if map not found', () => req.del({ url: `admin/maps/${NULL_ID}`, diff --git a/apps/backend-e2e/src/maps-2.e2e-spec.ts b/apps/backend-e2e/src/maps-2.e2e-spec.ts index 31905ee317..416622646d 100644 --- a/apps/backend-e2e/src/maps-2.e2e-spec.ts +++ b/apps/backend-e2e/src/maps-2.e2e-spec.ts @@ -43,6 +43,7 @@ import { setupE2ETestEnvironment, teardownE2ETestEnvironment } from './support/environment'; +import { MapListVersionDto } from '../../backend/src/app/dto/map/map-list-version.dto'; describe('Maps Part 2', () => { let app, @@ -64,6 +65,34 @@ describe('Maps Part 2', () => { afterAll(() => teardownE2ETestEnvironment(app)); + describe('maps/maplist', () => { + describe('GET', () => { + let token; + beforeAll(async () => (token = await db.loginNewUser())); + afterAll(() => db.cleanup('user')); + + it('should respond with map lists', async () => { + // We really don't have to test much here, since these values are just + // stored in memory. Tests doing map submission and approval test this + // system more thoroughly. + const res = await req.get({ + url: 'maps/maplistversion', + status: 200, + validate: MapListVersionDto, + token + }); + + expect(res.body).toMatchObject({ + approved: expect.any(Number), + submissions: expect.any(Number) + }); + }); + + it('should 401 when no access token is provided', () => + req.unauthorizedTest('maps/maplistversion', 'get')); + }); + }); + describe('maps/{mapID}/info', () => { describe('GET', () => { let token, map; diff --git a/apps/backend-e2e/src/maps.e2e-spec.ts b/apps/backend-e2e/src/maps.e2e-spec.ts index 02bc080f88..e6ca4445a7 100644 --- a/apps/backend-e2e/src/maps.e2e-spec.ts +++ b/apps/backend-e2e/src/maps.e2e-spec.ts @@ -19,7 +19,8 @@ import { MIN_PUBLIC_TESTING_DURATION, Role, TrackType, - MapZones + MapZones, + FlatMapList } from '@momentum/constants'; import { AuthUtil, @@ -568,12 +569,19 @@ describe('Maps', () => { afterAll(async () => { await db.cleanup('user', 'mMap'); await fileStore.deleteDirectory('submissions'); + await fileStore.deleteDirectory('maplist'); }); describe('should submit a map', () => { - let res, createdMap; + let res, createdMap, oldListVersion; beforeAll(async () => { + oldListVersion = await req.get({ + url: 'maps/maplistversion', + status: 200, + token: token + }); + res = await req.postAttach({ url: 'maps', status: 201, @@ -606,7 +614,12 @@ describe('Maps', () => { }); }); - afterAll(() => db.cleanup('mMap')); + afterAll(() => + Promise.all([ + db.cleanup('mMap'), + fileStore.deleteDirectory('/maplist') + ]) + ); it('should respond with a MapDto', () => { expect(res.body).toBeValidDto(MapDto); @@ -738,6 +751,29 @@ describe('Maps', () => { expect(invitees).toEqual(expect.arrayContaining([u2.id, u3.id])); expect(invitees).toHaveLength(2); }); + + it('should update the map list version for submissions', async () => { + const newListVersion = await req.get({ + url: 'maps/maplistversion', + status: 200, + token + }); + + expect(newListVersion.body.approved).toBe( + oldListVersion.body.approved + ); + expect(newListVersion.body.submissions).toBe( + oldListVersion.body.submissions + 1 + ); + + const submissionMapList = await fileStore.getMapListVersion( + FlatMapList.SUBMISSION, + newListVersion.body.submissions + ); + expect(submissionMapList).toHaveLength(1); + expect(submissionMapList[0].id).toBe(res.body.id); + expect(submissionMapList[0]).not.toHaveProperty('zones'); + }); }); describe('Permission checks', () => { @@ -1621,7 +1657,9 @@ describe('Maps', () => { ); }); - afterEach(() => db.cleanup('mMap')); + afterEach(() => + Promise.all([db.cleanup('mMap'), fileStore.deleteDirectory('/maplist')]) + ); it('should add a new map submission version', async () => { const changelog = 'Added walls, floors etc...'; @@ -1847,6 +1885,44 @@ describe('Maps', () => { ).toHaveLength(0); }); + it('should update the map list version for submissions', async () => { + const oldListVersion = await req.get({ + url: 'maps/maplistversion', + status: 200, + token: u1Token + }); + + await req.postAttach({ + url: `maps/${map.id}`, + status: 201, + data: { changelog: 'haha i am making ur thing update' }, + files: [ + { file: bspBuffer, field: 'bsp', fileName: 'surf_map.bsp' }, + { file: vmfBuffer, field: 'vmfs', fileName: 'surf_map.vmf' } + ], + token: u1Token + }); + + const newListVersion = await req.get({ + url: 'maps/maplistversion', + status: 200, + token: u1Token + }); + + expect(newListVersion.body.approved).toBe(oldListVersion.body.approved); + expect(newListVersion.body.submissions).toBe( + oldListVersion.body.submissions + 1 + ); + + const submissionMapList = await fileStore.getMapListVersion( + FlatMapList.SUBMISSION, + newListVersion.body.submissions + ); + expect(submissionMapList).toHaveLength(1); + expect(submissionMapList[0].id).toBe(map.id); + expect(submissionMapList[0]).not.toHaveProperty('zones'); + }); + it('should 400 for bad zones', () => req.postAttach({ url: `maps/${map.id}`, @@ -2146,7 +2222,9 @@ describe('Maps', () => { afterAll(() => db.cleanup('user')); - afterEach(() => db.cleanup('mMap')); + afterEach(() => + Promise.all([db.cleanup('mMap'), fileStore.deleteDirectory('/maplist')]) + ); for (const status of CombinedMapStatuses.IN_SUBMISSION) { it(`should allow the submitter to change most data during ${MapStatusNew[status]}`, async () => { @@ -2432,6 +2510,74 @@ describe('Maps', () => { ).toHaveLength(1); }); + it('should update the map list version for submissions', async () => { + const oldListVersion = await req.get({ + url: 'maps/maplistversion', + status: 200, + token + }); + + const map = await db.createMap({ + ...createMapData, + status: MapStatusNew.PRIVATE_TESTING + }); + + await req.patch({ + url: `maps/${map.id}`, + status: 204, + body: { status: MapStatusNew.CONTENT_APPROVAL }, + token + }); + + const newListVersion = await req.get({ + url: 'maps/maplistversion', + status: 200, + token + }); + + expect(newListVersion.body.approved).toBe(oldListVersion.body.approved); + expect(newListVersion.body.submissions).toBe( + oldListVersion.body.submissions + 1 + ); + }); + + it('should not update the map list version if the status doesnt change', async () => { + const oldListVersion = await req.get({ + url: 'maps/maplistversion', + status: 200, + token + }); + + const map = await db.createMap({ + ...createMapData, + status: MapStatusNew.PRIVATE_TESTING + }); + + await req.patch({ + url: `maps/${map.id}`, + status: 204, + body: { + status: MapStatusNew.PRIVATE_TESTING, + info: { + youtubeID: '64X4cuAR2fI' + } + }, + token + }); + + const newListVersion = await req.get({ + url: 'maps/maplistversion', + status: 200, + + token + }); + + expect(newListVersion.body.approved).toBe(oldListVersion.body.approved); + expect(newListVersion.body.submissions).toBe( + oldListVersion.body.submissions + ); + }); + it('should wipe leaderboards if resetLeaderboards is true', async () => { const map = await db.createMap({ ...createMapData, diff --git a/libs/test-utils/src/utils/s3.util.ts b/libs/test-utils/src/utils/s3.util.ts index 8774cdddac..94a4b64889 100644 --- a/libs/test-utils/src/utils/s3.util.ts +++ b/libs/test-utils/src/utils/s3.util.ts @@ -7,6 +7,8 @@ S3Client } from '@aws-sdk/client-s3'; import { createSha1Hash } from './crypto.util'; +import { FlatMapList } from '@momentum/constants'; +import zlib from 'node:zlib'; /** * Simple handler class wrapped over the AWS S3 client for use in tests. @@ -122,4 +124,13 @@ export class FileStoreUtil { return false; } } + + async getMapListVersion(type: FlatMapList, version: number) { + const file = await this.get( + `maplist/${ + type === FlatMapList.APPROVED ? 'approved' : 'submissions' + }/${version}.json.deflate` + ); + return JSON.parse(zlib.inflateSync(file).toString()); + } }