Skip to content

Commit

Permalink
Add tests for matchMaking input
Browse files Browse the repository at this point in the history
  • Loading branch information
niekcandaele committed Jul 25, 2020
1 parent 25d53ab commit eff8d5d
Show file tree
Hide file tree
Showing 17 changed files with 499 additions and 45 deletions.
2 changes: 1 addition & 1 deletion src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ dotenv.config();
BANTR_STEAM_API: Joi.string().required(),
BANTR_FACEIT_MATCH_CRON: Joi.string().default('0 */2 * * *'),
BANTR_STEAM_BANS_CRON: Joi.string().default('0 */2 * * *'),
BANTR_STEAM_MATCH_CRON: Joi.string().default('0 */2 * * *'),
BANTR_STEAM_MATCH_CRON: Joi.string().default('0 * * * *'),
BANTR_DISCORD_BOT_TOKEN: Joi.string(),
BANTR_GLOBAL_NOTIFICATION_DISCORD: Joi.string().default(''),
BANTR_DEMO_DOWNLOAD_LOCATION: Joi.string().default('tmp/'),
Expand Down
3 changes: 2 additions & 1 deletion src/ban/ban.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { NotificationService } from '../notification/notification.service';
import Player from '../player/player.entity';
import { PlayerService } from '../player/player.service';
import { QueueService } from '../queue/queue.service';
import { EconomyBan, SteamService } from '../steam/steam.service';
import { EconomyBan } from '../steam/interface/IGetPlayerBansResponse.interface';
import { SteamService } from '../steam/steam.service';
import { BanRepository } from './ban.repository';
import { BanService } from './ban.service';

Expand Down
4 changes: 1 addition & 3 deletions src/ban/ban.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Queue } from 'bull';
import Player from 'src/player/player.entity';
import IGetPlayerBansResponse, {
EconomyBan
} from 'src/steam/interface/IGetPlayerBansResponse.interface';

import { NotificationService } from '../notification/notification.service';
import { PlayerService } from '../player/player.service';
import { QueueService } from '../queue/queue.service';
import IGetPlayerBansResponse, { EconomyBan } from '../steam/interface/IGetPlayerBansResponse.interface';
import { SteamService } from '../steam/steam.service';
import Ban from './ban.entity';
import { BanRepository } from './ban.repository';
Expand Down
4 changes: 2 additions & 2 deletions src/faceit/faceit.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ const talkbackServer = talkback({
port: 7456,
path: `${__dirname}/tapes`,
ignoreHeaders: ['authorization'],
silent: true,
tapeNameGenerator: (tapeNumber, tape) => {
// TODO: This might fail on Windows...
return path.join(`${tape.req.method}`, `-${tape.req.url}`);
}
});
Expand Down Expand Up @@ -72,7 +72,7 @@ describe('FaceitService', () => {
userRepository = await module.get<UserRepository>(UserRepository);
httpService = await module.get<HttpService>(HttpService);
configService = await module.get<ConfigService>(ConfigService);
matchService = await module.get<FaceitService>(FaceitService);
matchService = await module.get<MatchService>(MatchService);

// Override the private method and supply it with our proxy server
service['getFaceItApiUrl'] = () => 'http://localhost:7456';
Expand Down
3 changes: 2 additions & 1 deletion src/match/match.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { OnQueueCompleted, OnQueueError, OnQueueFailed, OnQueueProgress, Process, Processor } from '@nestjs/bull';
import { HttpService, Injectable, Logger } from '@nestjs/common';
import { forwardRef, HttpService, Inject, Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm';
import * as Sentry from '@sentry/node';
Expand Down Expand Up @@ -48,6 +48,7 @@ export class MatchService {
private userRepository: UserRepository,
private readonly httpService: HttpService,
private queueService: QueueService,
@Inject(forwardRef(() => PlayerService))
private playerService: PlayerService,
private readonly config: ConfigService,
private connection: Connection
Expand Down
8 changes: 4 additions & 4 deletions src/steam/steam.module.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { HttpModule, Module } from '@nestjs/common';
import { forwardRef, HttpModule, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserRepository } from 'src/user/user.repository';

import { MatchModule } from '../match/match.module';
import { UserRepository } from '../user/user.repository';
import { SteamService } from './steam.service';
import { MatchModule } from 'src/match/match.module';

@Module({
imports: [
TypeOrmModule.forFeature([UserRepository]),
HttpModule,
MatchModule
forwardRef(() => MatchModule)
],
providers: [SteamService],
exports: [SteamService]
Expand Down
119 changes: 114 additions & 5 deletions src/steam/steam.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,143 @@
import { HttpService } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import * as dotenv from 'dotenv';
import * as path from 'path';

import { mockUser } from '../../test/globals';
import { MatchService } from '../match/match.service';
import { UserRepository } from '../user/user.repository';
import { SteamService } from './steam.service';
import SteamBot from './SteamBot';

const mockHttpService = () => ({});
jest.mock('./SteamBot');

dotenv.config();
const mockHttpService = new HttpService();

const mockConfigService = () => ({
get: jest.fn()
get: jest.fn(() => process.env.BANTR_STEAM_API)
});

//eslint-disable-next-line @typescript-eslint/no-var-requires
const talkback = require('talkback');

const talkbackServer = talkback({
host: 'https://api.steampowered.com',
record: talkback.Options.RecordMode.NEW,
port: 7457,
silent: true,
path: `${__dirname}/tapes`,
ignoreQueryParams: ['key', 'steamidkey'],
tapeNameGenerator: (tapeNumber, tape) => {
return path.join(`${tape.req.method}`, `-${tape.req.url}`);
}
});

const mockedUser = mockUser({});

const mockUserRepository = () => ({
getUsersSortedByLastChecked: jest.fn().mockReturnValue([mockedUser])
});

const mockMatchService = {
addMatchToQueue: jest.fn()
};

/**
* This is a bit of a weird method,
* but had trouble getting the TestingModule to initialize a real SteamService
* Something to do with circular dependency I think...
* This works for now, it's just a test :)
*/
const testService = new SteamService(
(mockUserRepository() as unknown) as UserRepository,
mockHttpService,
(mockConfigService() as unknown) as ConfigService,
(mockMatchService as unknown) as MatchService
);

describe('SteamService', () => {
let service: SteamService;
let httpService;
let configService;
let matchService: MatchService;
let userRepository;

beforeAll(() => {
talkbackServer.start();
});

afterAll(() => {
talkbackServer.close();
});

beforeEach(async () => {
jest.clearAllMocks();
const module: TestingModule = await Test.createTestingModule({
providers: [SteamService,
{ provide: HttpService, useFactory: mockHttpService },
providers: [
{ provide: SteamService, useValue: testService },
{ provide: UserRepository, useFactory: mockUserRepository },
{ provide: HttpService, useValue: mockHttpService },
{ provide: MatchService, useValue: mockMatchService },
{ provide: ConfigService, useFactory: mockConfigService }
]
}).compile();

service = module.get<SteamService>(SteamService);
userRepository = await module.get<UserRepository>(UserRepository);
matchService = await module.get<MatchService>(MatchService);
service = await module.get<SteamService>(SteamService);
httpService = module.get<HttpService>(HttpService);
configService = module.get<ConfigService>(ConfigService);

// Override the private method and supply it with our proxy server
service['getSteamApiUrl'] = () => 'http://localhost:7457';
});

it('should be defined', () => {
expect(service).toBeDefined();
});

it('Does not start a Steam bot when no auth credentials are provided', async () => {
expect(service.SteamBot).toBeUndefined;
});

describe('Getting matchmaking matches from steam', () => {
beforeEach(() => {
// This is mocked
service['SteamBot'] = new SteamBot('user', 'password');
mockedUser.settings.lastKnownMatch = 'CSGO-nvYA4-FkWRA-dp8AK-qLFot-7WFuA';
});

it('Gets new matches until there are no new ones and adds them to the match queue', async () => {
const addToQueueSpy = jest.spyOn(matchService, 'addMatchToQueue');
const getDemoUrlFromShareCodeSpy = jest.spyOn(
service.SteamBot,
'getDemoUrlFromShareCode'
);
await service.getMatchesForUsers();

// In the tapes, there's 7 valid matches
expect(getDemoUrlFromShareCodeSpy).toHaveBeenCalledTimes(7);
expect(addToQueueSpy).toHaveBeenCalledTimes(7);
});

it('Does not throw when Steam bot is not running', async () => {
const addToQueueSpy = jest.spyOn(matchService, 'addMatchToQueue');

service['SteamBot'] = undefined;
await service.getMatchesForUsers();

expect(addToQueueSpy).toHaveBeenCalledTimes(0);
});

it('Updates the lastKnownCode for a user after getting matches', async () => {
await service.getMatchesForUsers();

expect(mockedUser.settings.lastKnownMatch).toBe(
'CSGO-wkzOw-RzsHo-AXBL7-tLzdR-rvMpP'
);
expect(mockedUser.settings.save).toHaveBeenCalledTimes(1);
});
});
});
60 changes: 33 additions & 27 deletions src/steam/steam.service.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
import { User } from '@bantr/lib/dist/entities';
import {
OnQueueCompleted,
OnQueueError,
OnQueueFailed,
Process,
Processor
} from '@nestjs/bull';
import { IMatchType } from '@bantr/lib/dist/types';
import { OnQueueCompleted, OnQueueError, OnQueueFailed, Process, Processor } from '@nestjs/bull';
import { HttpService, Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm';
import * as Sentry from '@sentry/node';
import { Job } from 'bull';
import { LastCheckedType, UserRepository } from 'src/user/user.repository';
import SteamBot from './SteamBot';

import { CsgoMatchDto } from '../match/dto/csgoMatch.dto';
import { MatchService } from '../match/match.service';
import { LastCheckedType, UserRepository } from '../user/user.repository';
import IGetPlayerBansResponse from './interface/IGetPlayerBansResponse.interface';
import { CsgoMatchDto } from 'src/match/dto/csgoMatch.dto';
import { IMatchType } from '@bantr/lib/dist/types';
import { MatchService } from 'src/match/match.service';
import SteamBot from './SteamBot';

/**
* Handles actions to do with Steam
*/
Expand All @@ -33,7 +28,7 @@ export class SteamService {
*/
private steamApiKey: string;

private SteamBot: SteamBot;
SteamBot: SteamBot;

/**
* The service constructor
Expand All @@ -48,28 +43,35 @@ export class SteamService {
private readonly matchService: MatchService
) {
this.steamApiKey = configService.get('BANTR_STEAM_API');
this.SteamBot = new SteamBot(
configService.get('BANTR_STEAM_BOT_USERNAME'),

if (
configService.get('BANTR_STEAM_BOT_USERNAME') &&
configService.get('BANTR_STEAM_BOT_PASSWORD')
);
) {
this.SteamBot = new SteamBot(
configService.get('BANTR_STEAM_BOT_USERNAME'),
configService.get('BANTR_STEAM_BOT_PASSWORD')
);
}
}

@Process({ name: '__default__' })
async getMatchesForUsers(): Promise<void> {
if (!this.SteamBot) {
this.logger.warn(
'There are no Steam credentials configured, Steam bot is not active and we cannot get new demos from Steam matchmaking'
);
return;
}

this.logger.log(`Checking for new Matchmaking matches`);
const users = await this.userRepository.getUsersSortedByLastChecked(
LastCheckedType.steam
);

for (const user of users) {
let apiResponses: Array<string>;
try {
apiResponses = await this.getNewMatchesForUser(user);
} catch (e) {
this.logger.error(e);
continue;
}

const apiResponses = await this.getNewMatchesForUser(user);
user.settings.save();
for (const shareCode of apiResponses) {
const match = new CsgoMatchDto();
match.externalId = shareCode;
Expand All @@ -81,14 +83,18 @@ export class SteamService {
}
}

private getSteamApiUrl() {
return 'https://api.steampowered.com';
}

/**
* Get ban status for an array of steam IDs
* @param steamIds
*/
async getUserBans(steamIds: string[]) {
// TODO: create interface for steamId
const response = await this.httpService
.get(`https://api.steampowered.com/ISteamUser/GetPlayerBans/v1/`, {
.get(`${this.getSteamApiUrl()}/ISteamUser/GetPlayerBans/v1/`, {
params: {
key: this.steamApiKey,
steamids: steamIds.join(',')
Expand All @@ -106,7 +112,7 @@ export class SteamService {
async getUserProfiles(steamIds: string[]) {
// TODO: create interface for steamId
const response = await this.httpService
.get(`https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/`, {
.get(`${this.getSteamApiUrl()}/ISteamUser/GetPlayerSummaries/v2/`, {
params: {
key: this.steamApiKey,
steamids: steamIds.join(',')
Expand All @@ -125,7 +131,7 @@ export class SteamService {
);
const response = await this.httpService
.get(
`https://api.steampowered.com/ICSGOPlayers_730/GetNextMatchSharingCode/v1?`,
`${this.getSteamApiUrl()}/ICSGOPlayers_730/GetNextMatchSharingCode/v1?`,
{
params: {
key: this.steamApiKey,
Expand Down
Loading

0 comments on commit eff8d5d

Please sign in to comment.