feature(database-connection): Add tests for timetracking and add fallback value to max season for fetching stats
This commit is contained in:
15
backend/package-lock.json
generated
15
backend/package-lock.json
generated
@@ -5988,6 +5988,15 @@
|
|||||||
"@jest/types": "^25.5.0"
|
"@jest/types": "^25.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"jest-mock-extended": {
|
||||||
|
"version": "1.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-1.0.10.tgz",
|
||||||
|
"integrity": "sha512-R2wKiOgEUPoHZ2kLsAQeQP2IfVEgo3oQqWLSXKdMXK06t3UHkQirA2Xnsdqg/pX6KPWTsdnrzE2ig6nqNjdgVw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ts-essentials": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"jest-pnp-resolver": {
|
"jest-pnp-resolver": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz",
|
||||||
@@ -9459,6 +9468,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz",
|
||||||
"integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
|
"integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
|
||||||
},
|
},
|
||||||
|
"ts-essentials": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-uQJX+SRY9mtbKU+g9kl5Fi7AEMofPCvHfJkQlaygpPmHPZrtgaBqbWFOYyiA47RhnSwwnXdepUJrgqUYxoUyhQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"ts-jest": {
|
"ts-jest": {
|
||||||
"version": "26.1.1",
|
"version": "26.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.1.1.tgz",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"eslint-config-prettier": "^6.10.0",
|
"eslint-config-prettier": "^6.10.0",
|
||||||
"eslint-plugin-import": "^2.20.1",
|
"eslint-plugin-import": "^2.20.1",
|
||||||
"jest": "^25.1.0",
|
"jest": "^25.1.0",
|
||||||
|
"jest-mock-extended": "^1.0.10",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
"supertest": "^4.0.2",
|
"supertest": "^4.0.2",
|
||||||
"ts-jest": "^26.1.1",
|
"ts-jest": "^26.1.1",
|
||||||
|
|||||||
25
backend/src/api/timeutil.spec.ts
Normal file
25
backend/src/api/timeutil.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { TimeUtil } from './timeutil';
|
||||||
|
|
||||||
|
describe('TimeUtil', () => {
|
||||||
|
it('should humanize raw seconds', () => {
|
||||||
|
// const rawTime = 12345;
|
||||||
|
// const expected = "0d 3h 25m 45s";
|
||||||
|
const input = {
|
||||||
|
rawTime: [
|
||||||
|
12345,
|
||||||
|
123,
|
||||||
|
4
|
||||||
|
],
|
||||||
|
expected: [
|
||||||
|
"0d 3h 25m 45s",
|
||||||
|
"0d 0h 2m 3s",
|
||||||
|
"0d 0h 0m 4s"
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < input.rawTime.length; i++) {
|
||||||
|
const actual = TimeUtil.humanize(input.rawTime[i]);
|
||||||
|
expect(actual).toEqual(input.expected[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { AppController } from './app.controller';
|
|
||||||
import { AppService } from './app.service';
|
|
||||||
|
|
||||||
describe('AppController', () => {
|
|
||||||
let appController: AppController;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const app: TestingModule = await Test.createTestingModule({
|
|
||||||
controllers: [AppController],
|
|
||||||
providers: [AppService],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
appController = app.get<AppController>(AppController);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('root', () => {
|
|
||||||
it('should return "Hello World!"', () => {
|
|
||||||
expect(appController.getHello()).toBe('Hello World!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -33,12 +33,9 @@ export class AppController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('/stats/season/:id')
|
@Get('/stats/season/:id')
|
||||||
async getStats(@Param('id') id: string): Promise<UserStatsResponse> {
|
async getStats(@Param('id') id?: string): Promise<UserStatsResponse> {
|
||||||
return this.databaseService
|
return await this.databaseService
|
||||||
.fetchStats(id)
|
.fetchStats(id)
|
||||||
.then(value => {
|
|
||||||
return value;
|
|
||||||
})
|
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
logger.error(`Error occured when fetching stats.`, err);
|
logger.error(`Error occured when fetching stats.`, err);
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { seasons } from '@prisma/client';
|
||||||
import { DatabaseService } from './database.service';
|
import { DatabaseService } from './database.service';
|
||||||
|
import { mockReset, MockProxy, mockDeep } from 'jest-mock-extended';
|
||||||
|
import { SeasonInfo } from '../models/aliases';
|
||||||
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
import { TimeTrackerPredicate } from './timetracking.predicate';
|
||||||
|
import { mocked } from 'ts-jest';
|
||||||
|
|
||||||
describe('DatabaseService', () => {
|
describe('DatabaseService', () => {
|
||||||
let service: DatabaseService;
|
let service: DatabaseService;
|
||||||
|
const mockedPrismaService: MockProxy<PrismaService> = mockDeep<PrismaService>();
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
mockReset(mockedPrismaService);
|
||||||
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [DatabaseService],
|
providers: [DatabaseService, PrismaService, TimeTrackerPredicate],
|
||||||
}).compile();
|
})
|
||||||
|
.overrideProvider(PrismaService)
|
||||||
|
.useValue(mockedPrismaService)
|
||||||
|
.compile();
|
||||||
|
|
||||||
service = module.get<DatabaseService>(DatabaseService);
|
service = module.get<DatabaseService>(DatabaseService);
|
||||||
});
|
});
|
||||||
@@ -15,4 +27,43 @@ describe('DatabaseService', () => {
|
|||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(service).toBeDefined();
|
expect(service).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO This will be skipped as Prisma__#client can barely be mocked.
|
||||||
|
// it('fetch season info with season ID given', async () => {
|
||||||
|
// const seasonIdToBeTested = 1;
|
||||||
|
// const mockedValue: seasons = {
|
||||||
|
// season_id: 2,
|
||||||
|
// start_date: new Date('1995-12-17T03:24:00'),
|
||||||
|
// end_date: null
|
||||||
|
// };
|
||||||
|
// const mockedMaxSeasonId = {
|
||||||
|
// max: {
|
||||||
|
// season_id: 2
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// mockedPrismaService.seasons.findUnique.calledWith({
|
||||||
|
// where: {
|
||||||
|
// season_id: Number(seasonIdToBeTested)
|
||||||
|
// }
|
||||||
|
// }).mockImplementation((subset) => {
|
||||||
|
// return new Promise(resolve => resolve(mockedValue));
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// mockedPrismaService.seasons.aggregate.calledWith({
|
||||||
|
// max: {
|
||||||
|
// season_id: true
|
||||||
|
// }
|
||||||
|
// }).mockImplementation(() => Promise.resolve(mockedMaxSeasonId));
|
||||||
|
//
|
||||||
|
// const expected: SeasonInfo = {
|
||||||
|
// season_id: 2,
|
||||||
|
// maxSeasonId: 2,
|
||||||
|
// start_date: new Date('1995-12-17T03:24:00'),
|
||||||
|
// end_date: null
|
||||||
|
// }
|
||||||
|
// const actual = service.fetchSeasonInfos();
|
||||||
|
//
|
||||||
|
// await expect(actual).resolves.toEqual(expected);
|
||||||
|
// });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,40 +6,35 @@ import {
|
|||||||
UserStatsResponse,
|
UserStatsResponse,
|
||||||
} from '../models/aliases';
|
} from '../models/aliases';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
import { seasons } from '@prisma/client';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DatabaseService {
|
export class DatabaseService {
|
||||||
// private host = process.env.MYSQL_HOST
|
|
||||||
// private port = process.env.MYSQL_PORT
|
|
||||||
// private credentials = {
|
|
||||||
// username: process.env.MYSQL_USERNAME,
|
|
||||||
// password: process.env.MYSQL_PASSWORD
|
|
||||||
// }
|
|
||||||
// private database = process.env.MYSQL_DATABASE
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly prismaClient: PrismaService,
|
private readonly prismaClient: PrismaService,
|
||||||
private readonly timetrackerPredicate: TimeTrackerPredicate,
|
private readonly timetrackerPredicate: TimeTrackerPredicate,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public fetchSeasonInfos = async (seasonId: string): Promise<SeasonInfo> => {
|
public fetchSeasonInfos = async (seasonId?: string): Promise<SeasonInfo> => {
|
||||||
let seasons;
|
let maxSeasonId: number;
|
||||||
return this.prismaClient.seasons
|
|
||||||
.findOne({
|
return this.fetchMaxSeasonId().then(value => {
|
||||||
where: {
|
maxSeasonId = value;
|
||||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
return value;
|
||||||
season_id: Number(seasonId),
|
})
|
||||||
},
|
.then(value => this.prismaClient.seasons.findUnique({
|
||||||
})
|
where: {
|
||||||
.then(result => (seasons = result))
|
season_id: (!seasonId ? value : seasonId)
|
||||||
.then(this.fetchMaxSeasonId)
|
}
|
||||||
.then(result => {
|
}) as Promise<seasons>)
|
||||||
seasons.maxSeasonId = result;
|
.then((result: SeasonInfo) => {
|
||||||
return seasons;
|
result.maxSeasonId = maxSeasonId;
|
||||||
});
|
return result;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
public fetchStats = async (seasonId: string): Promise<UserStatsResponse> => {
|
public fetchStats = async (seasonId?: string): Promise<UserStatsResponse> => {
|
||||||
let response;
|
let response;
|
||||||
return this.fetchSeasonInfos(seasonId)
|
return this.fetchSeasonInfos(seasonId)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
@@ -61,18 +56,6 @@ export class DatabaseService {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// public fetchStats = async (seasonId: string): Promise<TableEntry[]> => this.prismaClient.timetracker.findMany({
|
|
||||||
// include: {
|
|
||||||
// user: true,
|
|
||||||
// ranks: true
|
|
||||||
// },
|
|
||||||
// where: {
|
|
||||||
// // eslint-disable-next-line @typescript-eslint/camelcase
|
|
||||||
// season_id: Number(seasonId)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .then(this.timetrackerPredicate.process);
|
|
||||||
|
|
||||||
fetchTimeTrackerStats: (string) => Promise<TimeTrackerStats[]> = (
|
fetchTimeTrackerStats: (string) => Promise<TimeTrackerStats[]> = (
|
||||||
seasonId: string,
|
seasonId: string,
|
||||||
) =>
|
) =>
|
||||||
|
|||||||
71
backend/src/database/timetracking.predicate.spec.ts
Normal file
71
backend/src/database/timetracking.predicate.spec.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { TimeTrackerPredicate } from './timetracking.predicate';
|
||||||
|
import { TimeTrackerStats } from '../models/aliases';
|
||||||
|
|
||||||
|
describe('TimeTrackerPredicate', () => {
|
||||||
|
let timeTrackerPredicate: TimeTrackerPredicate;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
timeTrackerPredicate = new TimeTrackerPredicate();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(timeTrackerPredicate).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process UserStats properly to TableEntry', () => {
|
||||||
|
const input: TimeTrackerStats[] = [
|
||||||
|
{
|
||||||
|
entry_id: 1,
|
||||||
|
user_uid: 'TEST_UUID1',
|
||||||
|
season_id: 1,
|
||||||
|
rank_id: 1,
|
||||||
|
time: 12345,
|
||||||
|
user: {
|
||||||
|
uid: 'TEST_UUID1',
|
||||||
|
name: 'Test User 1'
|
||||||
|
},
|
||||||
|
ranks: {
|
||||||
|
entry_id: 1,
|
||||||
|
season_id: 1,
|
||||||
|
rank_id: 1,
|
||||||
|
rank_name: 'Test Rank 1'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entry_id: 2,
|
||||||
|
user_uid: 'TEST_UUID2',
|
||||||
|
season_id: 1,
|
||||||
|
rank_id: 2,
|
||||||
|
time: 123455,
|
||||||
|
user: {
|
||||||
|
uid: 'TEST_UUID2',
|
||||||
|
name: 'Test User 2'
|
||||||
|
},
|
||||||
|
ranks: {
|
||||||
|
entry_id: 1,
|
||||||
|
season_id: 1,
|
||||||
|
rank_id: 2,
|
||||||
|
rank_name: 'Test Rank 2'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const expected = [
|
||||||
|
{
|
||||||
|
name: 'Test User 2',
|
||||||
|
onlineTime: "1d 10h 17m 35s",
|
||||||
|
rank: 'Test Rank 2',
|
||||||
|
rawTime: 123455
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Test User 1',
|
||||||
|
onlineTime: "0d 3h 25m 45s",
|
||||||
|
rank: 'Test Rank 1',
|
||||||
|
rawTime: 12345
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const actual = timeTrackerPredicate.process(input);
|
||||||
|
expect(actual).toEqual(expected);
|
||||||
|
})
|
||||||
|
});
|
||||||
@@ -1,19 +1,17 @@
|
|||||||
import { ranks, seasons, timetracker, user } from '@prisma/client';
|
|
||||||
import { Predicate } from 'src/api/predicate';
|
|
||||||
import { Sort } from 'src/api/sort';
|
|
||||||
import { TimeUtil } from 'src/api/timeutil';
|
|
||||||
import { TableEntry } from 'src/models/TableEntry';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { TimeTrackerStats } from '../models/aliases';
|
||||||
type UserStats = timetracker & { user: user; ranks: ranks };
|
import { Predicate } from '../api/predicate';
|
||||||
|
import { TableEntry } from '../models/TableEntry';
|
||||||
|
import { TimeUtil } from '../api/timeutil';
|
||||||
|
import { Sort } from '../api/sort';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TimeTrackerPredicate
|
export class TimeTrackerPredicate
|
||||||
implements Predicate<UserStats[], TableEntry[]> {
|
implements Predicate<TimeTrackerStats[], TableEntry[]> {
|
||||||
process(input: UserStats[]): TableEntry[] {
|
process(input: TimeTrackerStats[]): TableEntry[] {
|
||||||
return input
|
return input
|
||||||
.filter((userStats: UserStats) => userStats.time != null)
|
.filter((userStats: TimeTrackerStats) => userStats.time != null)
|
||||||
.map((userStats: UserStats) => {
|
.map((userStats: TimeTrackerStats) => {
|
||||||
return {
|
return {
|
||||||
name: userStats.user.name,
|
name: userStats.user.name,
|
||||||
rawTime: userStats.time,
|
rawTime: userStats.time,
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import { LoggerMiddleware } from './logger.middleware';
|
|
||||||
|
|
||||||
describe('LoggerMiddleware', () => {
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(new LoggerMiddleware()).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { PrismaService } from './prisma.service';
|
|
||||||
|
|
||||||
describe('PrismaService', () => {
|
|
||||||
let service: PrismaService;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [PrismaService],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
service = module.get<PrismaService>(PrismaService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(service).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user