1
0
Fork 0
mirror of https://codeberg.org/beerbrawl/beerbrawl.git synced 2024-09-22 21:20:52 +02:00

fix(#175): add testData for MR3

This commit is contained in:
Moritz Kepplinger 2024-06-26 16:41:33 +02:00
parent 1555be5dbc
commit 816951151c
8 changed files with 416 additions and 14 deletions

View file

@ -4,12 +4,14 @@ import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.UserDetailDto;
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.UserLoginDto;
import at.ac.tuwien.sepr.groupphase.backend.entity.Tournament;
import at.ac.tuwien.sepr.groupphase.backend.exception.UserAlreadyExistsException;
import at.ac.tuwien.sepr.groupphase.backend.service.TestDataService;
import at.ac.tuwien.sepr.groupphase.backend.service.TournamentService;
import at.ac.tuwien.sepr.groupphase.backend.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.annotation.security.PermitAll;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
@ -32,6 +34,7 @@ import java.net.URI;
import java.net.URISyntaxException;
@RestController
@AllArgsConstructor
@RequestMapping(value = UserEndpoint.BASE_ENDPOINT)
public class UserEndpoint {
public static final String BASE_ENDPOINT = "/api/v1/user";
@ -39,11 +42,8 @@ public class UserEndpoint {
private final UserService userService;
private final TournamentService tournamentService;
private final TestDataService testDataService;
public UserEndpoint(UserService userService, TournamentService tournamentService) {
this.userService = userService;
this.tournamentService = tournamentService;
}
/**
* Registers a new user in the system. If a user with the same username already exists,
@ -157,6 +157,22 @@ public class UserEndpoint {
return ResponseEntity.notFound().build();
}
/**
* Get Username and password for user.
* The target user is retrieved through the JWT
*r
*/
@GetMapping(value = "genTestData", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
@PreAuthorize("isAuthenticated()")
@Operation(summary = "Get detailed information about user and their tournaments.", security = @SecurityRequirement(name = "apiKey"))
public void generateTestData(
Authentication authentication
) {
this.testDataService.generateTestDataForUser(authentication.getName());
}
private void logClientError(HttpStatus status, String message, Exception e) {
LOG.warn("{} {}: {}: {}", status.value(), message, e.getClass().getSimpleName(), e.getMessage());

View file

@ -27,6 +27,15 @@ public interface TournamentRepository extends JpaRepository<Tournament, Long> {
*/
List<Tournament> findAllByOrganizerIdOrderByNameAsc(Long organizerId);
/**
* Find all tournaments where the organizer's username matches the provided username.
*
* @param organizerUsername The username of the organizer
* @return List of tournaments organized by the provided username
*/
List<Tournament> findAllByOrganizerUsername(String organizerUsername);
/**
* Find all tournaments with their qualification matches eagerly loaded
* where the organizer's ID matches the provided ID, ordered by name (ascending).

View file

@ -0,0 +1,5 @@
package at.ac.tuwien.sepr.groupphase.backend.service;
public interface TestDataService {
void generateTestDataForUser(String personName);
}

View file

@ -0,0 +1,297 @@
package at.ac.tuwien.sepr.groupphase.backend.service.impl;
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.TournamentUpdateQualificationMatchDto;
import at.ac.tuwien.sepr.groupphase.backend.entity.BeerPongTable;
import at.ac.tuwien.sepr.groupphase.backend.entity.QualificationMatch;
import at.ac.tuwien.sepr.groupphase.backend.entity.Team;
import at.ac.tuwien.sepr.groupphase.backend.entity.Tournament;
import at.ac.tuwien.sepr.groupphase.backend.entity.domainservice.MatchDomainService;
import at.ac.tuwien.sepr.groupphase.backend.repository.BeerPongTableRepository;
import at.ac.tuwien.sepr.groupphase.backend.repository.QualificationMatchRepository;
import at.ac.tuwien.sepr.groupphase.backend.repository.QualificationParticipationRepository;
import at.ac.tuwien.sepr.groupphase.backend.repository.SharedMediaRepository;
import at.ac.tuwien.sepr.groupphase.backend.repository.TeamRepository;
import at.ac.tuwien.sepr.groupphase.backend.repository.TournamentRepository;
import at.ac.tuwien.sepr.groupphase.backend.repository.UserRepository;
import at.ac.tuwien.sepr.groupphase.backend.service.TestDataService;
import at.ac.tuwien.sepr.groupphase.backend.service.TournamentKoPhaseService;
import at.ac.tuwien.sepr.groupphase.backend.service.TournamentQualificationService;
import at.ac.tuwien.sepr.groupphase.backend.service.TournamentService;
import at.ac.tuwien.sepr.groupphase.backend.service.TournamentTeamService;
import at.ac.tuwien.sepr.groupphase.backend.util.BeerDateTime;
import lombok.AllArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.stream.IntStream;
@AllArgsConstructor
@Service
public class TestDataServiceImpl implements TestDataService {
private final TournamentRepository tournamentRepository;
private final UserRepository userRepository;
private final TeamRepository teamRepository;
private final TournamentTeamService teamService;
private final TournamentQualificationService tournamentQualificationService;
private final PasswordEncoder passwordEncoder;
private final TournamentService tournamentService;
private final BeerPongTableRepository beerPongTableRepository;
private final MatchDomainService matchDomainService;
private final TournamentQualificationService qualificationService;
private final TournamentKoPhaseService koPhaseService;
private final QualificationParticipationRepository qualificationParticipationRepository;
private final SharedMediaRepository sharedMediaRepository;
private final QualificationMatchRepository qualificationMatchRepository;
@Override
public void generateTestDataForUser(String username) {
var tournaments = this.tournamentRepository.findAllByOrganizerUsername(username);
tournamentRepository.deleteAllById(tournaments.stream().map(Tournament::getId).toList());
final var tournament = new Tournament(
"Spring Tournament", BeerDateTime.nowUtc().plusDays(7), 64L,
"TEST_TOURNAMENT_DESCRIPTION", userRepository.findByUsername(username));
final var tournament2 = new Tournament(
"Summer Tournament", BeerDateTime.nowUtc().plusDays(7), 32L,
"TEST_TOURNAMENT2_DESCRIPTION", userRepository.findByUsername(username));
final var tournament3 = new Tournament(
"Fall Tournament", BeerDateTime.nowUtc().plusDays(7), 32L,
"TEST_TOURNAMENT3_DESCRIPTION", userRepository.findByUsername(username));
tournamentRepository.saveAllAndFlush(List.of(tournament, tournament2, tournament3));
final var teams1 = IntStream.range(0, 64)
.mapToObj(i -> new Team("Test Team #%02d".formatted(i), tournament))
.toList();
teamRepository.saveAllAndFlush(teams1);
var teams2 = IntStream.range(0, 32)
.mapToObj(i -> new Team("Test Team #%02d".formatted(i), tournament2))
.toList();
teams2 = teamRepository.saveAllAndFlush(teams2);
teams2.forEach(t -> teamService.markTeamAsReady(tournament2.getId(), t.getId()));
var teams3 = IntStream.range(0, 32)
.mapToObj(i -> new Team("Test Team #%02d".formatted(i), tournament3))
.toList();
teams3 = teamRepository.saveAllAndFlush(teams3);
teams3.forEach(t -> teamService.markTeamAsReady(tournament3.getId(), t.getId()));
tournamentQualificationService.generateQualificationMatchesForTournament(tournament3.getId(), username);
var tables3 = IntStream.range(0, 4)
.mapToObj(i -> new BeerPongTable("Test Table #%02d".formatted(i), tournament3))
.toList();
beerPongTableRepository.saveAllAndFlush(tables3);
matchDomainService.scheduleQualiMatches(tournament3.getId());
//generateTestTournamentsWithFinishedQualificationAndStartedKoPhase(username);
generateTournamentWithFinishedQualiPhaseAndDifferentScores(username);
}
protected void generateTestTournamentsWithFinishedQualificationAndStartedKoPhase(String username) {
final var tournament = tournamentService.create(
new Tournament(
"score Tournament", BeerDateTime.nowUtc().plusDays(1), 32L,
"testdescription", null),
username);
var teams = IntStream.range(0, 32)
.mapToObj(i -> new Team("Test Team #%02d".formatted(i), tournament))
.toList();
teamRepository.saveAllAndFlush(teams);
var qualificationMatches = qualificationService.generateQualificationMatchesForTournament(tournament.getId(),
username);
for (var qualificationMatch : qualificationMatches) {
var participations = qualificationParticipationRepository
.findAllByQualificationMatchId(qualificationMatch.getId());
var team1 = participations.get(0).getTeam().getId();
var team2 = participations.get(1).getTeam().getId();
teamService.markTeamAsReady(tournament.getId(), team1);
teamService.markTeamAsReady(tournament.getId(), team2);
qualificationService.updateQualificationMatch(tournament.getId(), qualificationMatch.getId(),
new TournamentUpdateQualificationMatchDto(
null,
new TournamentUpdateQualificationMatchDto.DrinksPickupDto(team1)));
qualificationService.updateQualificationMatch(tournament.getId(), qualificationMatch.getId(),
new TournamentUpdateQualificationMatchDto(
null,
new TournamentUpdateQualificationMatchDto.DrinksPickupDto(team2)));
qualificationService.updateQualificationMatch(tournament.getId(), qualificationMatch.getId(),
new TournamentUpdateQualificationMatchDto(
new TournamentUpdateQualificationMatchDto.ScoreUpdateDto(team1, 10L),
null));
}
koPhaseService.generateKoMatchesForTournament(tournament.getId(),
teams.stream().limit(16).map(Team::getId).toList(),
username);
var tables = IntStream.range(0, 4)
.mapToObj(i -> new BeerPongTable("Test Table #%02d".formatted(i), tournament))
.toList();
beerPongTableRepository.saveAllAndFlush(tables);
matchDomainService.scheduleQualiMatches(tournament.getId());
matchDomainService.scheduleKoMatches(tournament.getId());
}
protected void generateTournamentWithFinishedQualiPhaseAndDifferentScores(String username) {
var tournament = new Tournament(
"testname", BeerDateTime.nowUtc().plusDays(1), 64L, "testdescription",
userRepository.findByUsername(username));
tournamentService.create(tournament, username);
generate32Teams(tournament);
qualificationService.generateQualificationMatchesForTournament(tournament.getId(), username);
markAllTeamsAsReady(tournament);
/*
* We want to have a score table like this:
* First of all we want one team that wins 2 matches and one team that looses 2 matches.
* In between all the teams should have 1 win and 1 loss.
* - In the middle of these teams there are 4 teams with exactly 5 points. So they share the places
* 15, 16, 17 and 18. In a real tournament it is not clear who will be qualified for the ko phase,
* so the organizer has to pick 2 of them.
*
* - All the other teams are split into two groups with the same amount of teams. One group has more
* than 5 points and the other group has less than 5 points. The exact amount of points is random.
*/
var alwaysWinsId = teamRepository.findAllByTournamentId(tournament.getId()).get(0).getId();
var alwaysLoosesId = teamRepository.findAllByTournamentId(tournament.getId()).get(1).getId();
// all the team ids that have already won one match
var teamsWithOneWin = new LinkedList<Long>();
// we also track the teams in the specific
var teamsWith5Points = new LinkedList<Long>();
var teamsWithMoreThan5Points = new LinkedList<Long>();
var teamsWithLessThan5Points = new LinkedList<Long>();
final var qMatches = qualificationMatchRepository.findAllByTournamentId(tournament.getId());
for (var qm : qMatches) {
markParticipantsAsDrinksCollected(tournament, qm);
var winnerInfo = determineWinner(
qm, alwaysWinsId, alwaysLoosesId,
teamsWithOneWin, teamsWith5Points,
teamsWithMoreThan5Points, teamsWithLessThan5Points
);
qualificationService.updateQualificationMatch(
tournament.getId(), qm.getId(),
new TournamentUpdateQualificationMatchDto(
new TournamentUpdateQualificationMatchDto.ScoreUpdateDto(
winnerInfo.winnerId, winnerInfo.winnerPoints
),
null
)
);
}
}
protected void generate32Teams(Tournament tournament) {
for (int i = 1; i <= 32; i++) {
var result = teamService.signupTeamForTournament(tournament.getId(),
tournament.getPublicAccessToken(), "team" + i);
if (result != Tournament.SignupTeamResult.SUCCESS) {
throw new IllegalStateException("Failed to sign up team for tournament");
}
}
}
protected void markAllTeamsAsReady(Tournament tournament) {
for (final var team : teamRepository.findAllByTournamentId(tournament.getId())) {
teamService.markTeamAsReady(tournament.getId(), team.getId());
}
}
protected void markParticipantsAsDrinksCollected(Tournament tournament, QualificationMatch qm) {
qm.getParticipations()
.stream().map(p -> p.getTeam().getId())
.forEach(p -> qualificationService.updateQualificationMatch(
tournament.getId(), qm.getId(),
new TournamentUpdateQualificationMatchDto(
null, new TournamentUpdateQualificationMatchDto.DrinksPickupDto(p)
)
));
}
protected WinnerInfo determineWinner(
QualificationMatch qm,
Long alwaysWinsId,
Long alwaysLoosesId,
LinkedList<Long> teamsWithOneWin,
LinkedList<Long> teamsWith5Points,
LinkedList<Long> teamsWithMoreThan5Points,
LinkedList<Long> teamsWithLessThan5Points
) {
var currentTeamIds = qm.getParticipations().stream().map(p -> p.getTeam().getId()).toList();
Long winnerId;
Long winnerPoints;
if (currentTeamIds.contains(alwaysWinsId)) {
// the match includes the "alwaysWins" team, so it wins
winnerId = alwaysWinsId;
winnerPoints = 10L;
} else if (currentTeamIds.contains(alwaysLoosesId)) {
// the match includes the "alwaysLooses" team, so it looses
winnerId = currentTeamIds.stream().filter(id -> !Objects.equals(id, alwaysLoosesId)).findFirst().get();
teamsWithOneWin.add(winnerId);
winnerPoints = getWinnerPoints(teamsWith5Points, teamsWithMoreThan5Points, teamsWithLessThan5Points, winnerId);
} else {
var teamWithOneWin = currentTeamIds.stream().filter(teamsWithOneWin::contains).findFirst();
if (teamWithOneWin.isPresent()) {
//the match includes a team that has already won one match, so the other team wins
winnerId = currentTeamIds.stream().filter(id -> !Objects.equals(id, teamWithOneWin.get())).findFirst().get();
} else {
// the match includes two teams that have not won a match yet, so we just pick the first one
winnerId = currentTeamIds.getFirst();
}
teamsWithOneWin.add(winnerId);
winnerPoints = getWinnerPoints(teamsWith5Points, teamsWithMoreThan5Points, teamsWithLessThan5Points, winnerId);
}
return new WinnerInfo(winnerId, winnerPoints);
}
private static class WinnerInfo {
Long winnerId;
Long winnerPoints;
WinnerInfo(Long winnerId, Long winnerPoints) {
this.winnerId = winnerId;
this.winnerPoints = winnerPoints;
}
}
protected Long getWinnerPoints(LinkedList<Long> teamsWith5Points, LinkedList<Long> teamsWithMoreThan5Points, LinkedList<Long> teamsWithLessThan5Points, Long winnerId) {
Long winnerPoints;
if (teamsWith5Points.size() < 4) {
// it joins the group with exactly 5 points
winnerPoints = 5L;
teamsWith5Points.add(winnerId);
} else {
if (teamsWithMoreThan5Points.size() < teamsWithLessThan5Points.size()) {
// it joins the group with more than 5 points
// random number from 1 to 4 (both inclusive)
winnerPoints = 1L + new Random().nextInt(4);
teamsWithMoreThan5Points.add(winnerId);
} else {
// it joins the group with less than 5 points
// random number from 6 to 9 (both inclusive)
winnerPoints = 6L + new Random().nextInt(4);
teamsWithLessThan5Points.add(winnerId);
}
}
return winnerPoints;
}
}

View file

@ -144,9 +144,9 @@ public class TournamentQualificationServiceImpl implements TournamentQualificati
.orElseThrow(() -> new NotFoundException("Match not found"));
}
private void updateQualificationMatchDrinksStatus(
QualificationMatch match,
TournamentUpdateQualificationMatchDto.DrinksPickupDto updateDto
protected void updateQualificationMatchDrinksStatus(
QualificationMatch match,
TournamentUpdateQualificationMatchDto.DrinksPickupDto updateDto
) {
final var participation = match.getParticipations()
.stream().filter(p -> p.getTeam().getId() == updateDto.teamId())

View file

@ -3,7 +3,7 @@
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: v0
*
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
@ -13,7 +13,7 @@
import { Inject, Injectable, Optional } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams,
HttpResponse, HttpEvent, HttpParameterCodec, HttpContext
HttpResponse, HttpEvent, HttpParameterCodec, HttpContext
} from '@angular/common/http';
import { CustomHttpParameterCodec } from '../encoder';
import { Observable } from 'rxjs';
@ -95,7 +95,7 @@ export class UserEndpointService {
/**
* Delete user and all data belonging to them(Tournaments, Teams, etc) from the database.
* @param username
* @param username
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
@ -159,7 +159,7 @@ export class UserEndpointService {
/**
* Get detailed information about user and their tournaments.
* @param username
* @param username
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
@ -222,7 +222,66 @@ export class UserEndpointService {
}
/**
* @param userLoginDto
* Get detailed information about user and their tournaments.
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public generateTestData(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<any>;
public generateTestData(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<any>>;
public generateTestData(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<any>>;
public generateTestData(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<any> {
let localVarHeaders = this.defaultHeaders;
let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
if (localVarHttpHeaderAcceptSelected === undefined) {
// to determine the Accept header
const httpHeaderAccepts: string[] = [
];
localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
}
if (localVarHttpHeaderAcceptSelected !== undefined) {
localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected);
}
let localVarHttpContext: HttpContext | undefined = options && options.context;
if (localVarHttpContext === undefined) {
localVarHttpContext = new HttpContext();
}
let localVarTransferCache: boolean | undefined = options && options.transferCache;
if (localVarTransferCache === undefined) {
localVarTransferCache = true;
}
let responseType_: 'text' | 'json' | 'blob' = 'json';
if (localVarHttpHeaderAcceptSelected) {
if (localVarHttpHeaderAcceptSelected.startsWith('text')) {
responseType_ = 'text';
} else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) {
responseType_ = 'json';
} else {
responseType_ = 'blob';
}
}
let localVarPath = `/api/v1/user/genTestData`;
return this.httpClient.request<any>('get', `${this.configuration.basePath}${localVarPath}`,
{
context: localVarHttpContext,
responseType: <any>responseType_,
withCredentials: this.configuration.withCredentials,
headers: localVarHeaders,
observe: observe,
transferCache: localVarTransferCache,
reportProgress: reportProgress
}
);
}
/**
* @param userLoginDto
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
@ -295,8 +354,8 @@ export class UserEndpointService {
}
/**
* @param username
* @param userLoginDto
* @param username
* @param userLoginDto
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/

View file

@ -21,6 +21,7 @@
<button mat-flat-button class="delete-button" (click)="onDelete()">
<mat-icon>delete</mat-icon> Delete your Account
</button>
<button mat-button class="delete-button" (click)="genTestData()">Generate Testdata</button>
</mat-card-actions>
</mat-card>
</div>

View file

@ -7,6 +7,7 @@ import { UpdateUserComponent } from '../update-user/update-user.component';
import { HttpErrorResponse } from '@angular/common/http';
import { UserEndpointService, UserDetailDto } from '@api';
import { ConfirmationService } from '../../services/confirmation.service';
import { firstValueFrom } from 'rxjs';
@Component({
selector: 'app-user-detail',
@ -90,4 +91,18 @@ export class UserDetailComponent implements OnInit {
duration: 5000,
});
}
async genTestData() {
await firstValueFrom(this.userService.generateTestData()).then(
() => {
this.snackBar.open('Successfully generated test data', 'Close', {
duration: 5000,
});
},
(error: HttpErrorResponse) => {
console.error('Error generating test data', error);
this.defaultServiceErrorHandling(error);
},
);
}
}