From 823c86bd903558ee5479f3188a14ab8e3bd11c23 Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 5 Jun 2025 17:48:43 +0200 Subject: [PATCH 01/90] made wakeTimeHour to localtime to support minutes --- .../api/control/SeasonController.java | 14 ++++++-- .../api/mapping/SeasonSettingsMapper.java | 19 +++++++++++ .../api/model/dao/SeasonSettings.java | 10 +++--- .../beerpong/api/model/dto/ErrorCodes.java | 2 +- .../api/model/dto/SeasonSettingsDto.java | 15 ++++++++ .../api/model/dto/SeasonUpdateDto.java | 2 +- .../api/service/LeaderboardService.java | 2 +- .../beerpong/api/service/MatchService.java | 7 ++-- .../beerpong/api/service/SeasonService.java | 17 ++++++---- .../api/sockets/LocalTimeAdapter.java | 34 +++++++++++++++++++ .../api/sockets/SubscriptionHandler.java | 1 + .../api/sockets/ZonedDateTimeAdapter.java | 7 +++- 12 files changed, 110 insertions(+), 20 deletions(-) create mode 100644 api/src/main/java/pro/beerpong/api/mapping/SeasonSettingsMapper.java create mode 100644 api/src/main/java/pro/beerpong/api/model/dto/SeasonSettingsDto.java create mode 100644 api/src/main/java/pro/beerpong/api/sockets/LocalTimeAdapter.java diff --git a/api/src/main/java/pro/beerpong/api/control/SeasonController.java b/api/src/main/java/pro/beerpong/api/control/SeasonController.java index 59033162..17403c9a 100644 --- a/api/src/main/java/pro/beerpong/api/control/SeasonController.java +++ b/api/src/main/java/pro/beerpong/api/control/SeasonController.java @@ -1,12 +1,14 @@ package pro.beerpong.api.control; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import pro.beerpong.api.model.dto.*; import pro.beerpong.api.service.SeasonService; +import pro.beerpong.api.sockets.LocalTimeAdapter; +import java.time.LocalTime; +import java.time.format.DateTimeParseException; import java.util.List; @RestController @@ -94,7 +96,15 @@ public ResponseEntity> updateSeasonById(@PathVariabl return ResponseEnvelope.notOk(ErrorCodes.SEASON_ALREADY_ENDED); } - if (dto.getSeasonSettings().getWakeTimeHour() < 0 || dto.getSeasonSettings().getWakeTimeHour() > 23) { + LocalTime wakeTime; + + try { + wakeTime = LocalTime.parse(dto.getSeasonSettings().getWakeTime(), LocalTimeAdapter.FORMATTER); + } catch (DateTimeParseException e) { + wakeTime = null; + } + + if (wakeTime == null) { return ResponseEnvelope.notOk(ErrorCodes.SEASON_WRONG_TIME_FORMAT); } else if (dto.getSeasonSettings().getMinTeamSize() > dto.getSeasonSettings().getMaxTeamSize()) { return ResponseEnvelope.notOk(ErrorCodes.SEASON_WRONG_TEAM_SIZES); diff --git a/api/src/main/java/pro/beerpong/api/mapping/SeasonSettingsMapper.java b/api/src/main/java/pro/beerpong/api/mapping/SeasonSettingsMapper.java new file mode 100644 index 00000000..6abe0388 --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/mapping/SeasonSettingsMapper.java @@ -0,0 +1,19 @@ +package pro.beerpong.api.mapping; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import pro.beerpong.api.model.dao.SeasonSettings; +import pro.beerpong.api.model.dto.SeasonSettingsDto; +import pro.beerpong.api.sockets.LocalTimeAdapter; + +import java.time.LocalTime; + +@Mapper(componentModel = "spring") +public abstract class SeasonSettingsMapper { + @Mapping(target = "wakeTime", expression = "java(parseTime(seasonSettingsDto))") + public abstract SeasonSettings seasonSettingsDtoToSeasonSettings(SeasonSettingsDto seasonSettingsDto); + + protected LocalTime parseTime(SeasonSettingsDto seasonSettingsDto) { + return LocalTime.parse(seasonSettingsDto.getWakeTime(), LocalTimeAdapter.FORMATTER); + } +} \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/model/dao/SeasonSettings.java b/api/src/main/java/pro/beerpong/api/model/dao/SeasonSettings.java index 7752c3fe..468a29af 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/SeasonSettings.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/SeasonSettings.java @@ -1,13 +1,12 @@ package pro.beerpong.api.model.dao; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; +import jakarta.persistence.*; import lombok.Data; import pro.beerpong.api.util.DailyLeaderboard; import pro.beerpong.api.util.RankingAlgorithm; +import java.time.LocalTime; + @Entity(name = "season_settings") @Data public class SeasonSettings { @@ -20,5 +19,6 @@ public class SeasonSettings { private int maxTeamSize = 10; private RankingAlgorithm rankingAlgorithm = RankingAlgorithm.AVERAGE; private DailyLeaderboard dailyLeaderboard = DailyLeaderboard.WAKE_TIME; - private int wakeTimeHour = 0; + @Column(columnDefinition = "time default '00:00:00'") + private LocalTime wakeTime = LocalTime.of(0, 0); } diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java index f1d37fcc..3ae16ef4 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java @@ -20,7 +20,7 @@ public enum ErrorCodes { /* SEASONS */ SEASON_NOT_FOUND(HttpStatus.NOT_FOUND, "seasonNotFound", "The requested season could not be found!"), SEASON_ALREADY_ENDED(HttpStatus.FORBIDDEN, "seasonAlreadyEnded", "Past seasons are immutable!"), - SEASON_WRONG_TIME_FORMAT(HttpStatus.BAD_REQUEST, "seasonWrongTimeFormat", "The wake time hour has to be between 0 and 23!"), + SEASON_WRONG_TIME_FORMAT(HttpStatus.BAD_REQUEST, "seasonWrongTimeFormat", "The wake time has to be supplied in the following format: HH:mm and be a valid hour and minute"), SEASON_WRONG_TEAM_SIZES(HttpStatus.BAD_REQUEST, "seasonWrongTeamSizes", "The min team size has to be less then or equal to the max team size!"), SEASON_NOT_OF_GROUP(HttpStatus.FORBIDDEN, "seasonHasDifferentGroup", "The season does not match the provided group id!"), SEASON_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "seasonValidationFailed", "The validation of the created season has failed (invalid group id)"), diff --git a/api/src/main/java/pro/beerpong/api/model/dto/SeasonSettingsDto.java b/api/src/main/java/pro/beerpong/api/model/dto/SeasonSettingsDto.java new file mode 100644 index 00000000..0e467935 --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/model/dto/SeasonSettingsDto.java @@ -0,0 +1,15 @@ +package pro.beerpong.api.model.dto; + +import lombok.Data; +import pro.beerpong.api.util.DailyLeaderboard; +import pro.beerpong.api.util.RankingAlgorithm; + +@Data +public class SeasonSettingsDto { + private int minMatchesToQualify = 1; + private int minTeamSize = 1; + private int maxTeamSize = 10; + private RankingAlgorithm rankingAlgorithm = RankingAlgorithm.AVERAGE; + private DailyLeaderboard dailyLeaderboard = DailyLeaderboard.WAKE_TIME; + private String wakeTime = "00:00"; +} diff --git a/api/src/main/java/pro/beerpong/api/model/dto/SeasonUpdateDto.java b/api/src/main/java/pro/beerpong/api/model/dto/SeasonUpdateDto.java index 3c8e4981..eab1f93a 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/SeasonUpdateDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/SeasonUpdateDto.java @@ -8,5 +8,5 @@ @Data public class SeasonUpdateDto { - private @NotNull SeasonSettings seasonSettings; + private @NotNull SeasonSettingsDto seasonSettings; } diff --git a/api/src/main/java/pro/beerpong/api/service/LeaderboardService.java b/api/src/main/java/pro/beerpong/api/service/LeaderboardService.java index 20d589d8..ee044ae8 100644 --- a/api/src/main/java/pro/beerpong/api/service/LeaderboardService.java +++ b/api/src/main/java/pro/beerpong/api/service/LeaderboardService.java @@ -110,7 +110,7 @@ public LeaderboardDto generateLeaderboard(GroupDto group, String scope, boolean } else if (season.getSeasonSettings().getDailyLeaderboard() == DailyLeaderboard.RESET_AT_MIDNIGHT) { startedAt = ZonedDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0); } else { - startedAt = matchService.getWakeTime(ZonedDateTime.now(), season.getSeasonSettings().getWakeTimeHour()); + startedAt = matchService.getWakeTime(ZonedDateTime.now(), season.getSeasonSettings().getWakeTime()); } } default -> { diff --git a/api/src/main/java/pro/beerpong/api/service/MatchService.java b/api/src/main/java/pro/beerpong/api/service/MatchService.java index 560c48a3..d0606c22 100644 --- a/api/src/main/java/pro/beerpong/api/service/MatchService.java +++ b/api/src/main/java/pro/beerpong/api/service/MatchService.java @@ -14,6 +14,7 @@ import pro.beerpong.api.sockets.SubscriptionHandler; import java.time.Duration; +import java.time.LocalTime; import java.time.ZonedDateTime; import java.util.List; import java.util.Objects; @@ -200,7 +201,7 @@ public Stream streamAllMatchesToday(GroupDto group, Season season) { var now = ZonedDateTime.now(); Predicate predicate = switch (season.getSeasonSettings().getDailyLeaderboard()) { - case WAKE_TIME -> match -> match.getDate().isAfter(getWakeTime(now, season.getSeasonSettings().getWakeTimeHour())); + case WAKE_TIME -> match -> match.getDate().isAfter(getWakeTime(now, season.getSeasonSettings().getWakeTime())); case LAST_24_HOURS -> (match) -> !match.getDate().isAfter(now) && Duration.between(match.getDate(), now).toMinutes() < MINUTES_IN_DAY; case RESET_AT_MIDNIGHT -> (match) -> match.getDate().toLocalDate().equals(now.toLocalDate()); }; @@ -219,8 +220,8 @@ public Stream streamAllMatchesToday(GroupDto group, Season season) { } } - public ZonedDateTime getWakeTime(ZonedDateTime now, int wakeTimeHour) { - var wakeTimeToday = now.withHour(wakeTimeHour).withMinute(0).withSecond(0).withNano(0); + public ZonedDateTime getWakeTime(ZonedDateTime now, LocalTime wakeTime) { + var wakeTimeToday = now.withHour(wakeTime.getHour()).withMinute(wakeTime.getMinute()).withSecond(0).withNano(0); if (now.isBefore(wakeTimeToday)) { wakeTimeToday = wakeTimeToday.minusDays(1); diff --git a/api/src/main/java/pro/beerpong/api/service/SeasonService.java b/api/src/main/java/pro/beerpong/api/service/SeasonService.java index f8aee932..cbaa72f5 100644 --- a/api/src/main/java/pro/beerpong/api/service/SeasonService.java +++ b/api/src/main/java/pro/beerpong/api/service/SeasonService.java @@ -1,7 +1,6 @@ package pro.beerpong.api.service; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import pro.beerpong.api.mapping.*; @@ -11,11 +10,13 @@ import pro.beerpong.api.repository.PlayerRepository; import pro.beerpong.api.repository.PlayerStatisticsRepository; import pro.beerpong.api.repository.SeasonRepository; +import pro.beerpong.api.sockets.LocalTimeAdapter; import pro.beerpong.api.sockets.SocketEvent; import pro.beerpong.api.sockets.SocketEventData; import pro.beerpong.api.sockets.SubscriptionHandler; import pro.beerpong.api.util.NullablePair; +import java.time.LocalTime; import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; @@ -37,6 +38,7 @@ public class SeasonService { private final ProfileMapper profileMapper; private final PlayerStatisticsMapper playerStatisticsMapper; private final GroupService groupService; + private final SeasonSettingsMapper seasonSettingsMapper; @Autowired public SeasonService(SubscriptionHandler subscriptionHandler, @@ -45,7 +47,7 @@ public SeasonService(SubscriptionHandler subscriptionHandler, PlayerService playerService, RuleMoveService ruleMoveService, RuleService ruleService, - SeasonMapper seasonMapper, LeaderboardService leaderboardService, GroupMapper groupMapper, PlayerMapper playerMapper, PlayerStatisticsRepository playerStatisticsRepository, PlayerRepository playerRepository, ProfileMapper profileMapper, PlayerStatisticsMapper playerStatisticsMapper, GroupService groupService) { + SeasonMapper seasonMapper, LeaderboardService leaderboardService, GroupMapper groupMapper, PlayerMapper playerMapper, PlayerStatisticsRepository playerStatisticsRepository, PlayerRepository playerRepository, ProfileMapper profileMapper, PlayerStatisticsMapper playerStatisticsMapper, GroupService groupService, SeasonSettingsMapper seasonSettingsMapper) { this.subscriptionHandler = subscriptionHandler; this.seasonRepository = seasonRepository; this.groupRepository = groupRepository; @@ -61,6 +63,7 @@ public SeasonService(SubscriptionHandler subscriptionHandler, this.profileMapper = profileMapper; this.playerStatisticsMapper = playerStatisticsMapper; this.groupService = groupService; + this.seasonSettingsMapper = seasonSettingsMapper; } public SeasonDto startNewSeason(SeasonCreateDto dto, String groupId) { @@ -84,7 +87,7 @@ public SeasonDto startNewSeason(SeasonCreateDto dto, String groupId) { newSeason.getSeasonSettings().setMinMatchesToQualify(oldSeason.getSeasonSettings().getMinMatchesToQualify()); newSeason.getSeasonSettings().setRankingAlgorithm(oldSeason.getSeasonSettings().getRankingAlgorithm()); newSeason.getSeasonSettings().setDailyLeaderboard(oldSeason.getSeasonSettings().getDailyLeaderboard()); - newSeason.getSeasonSettings().setWakeTimeHour(oldSeason.getSeasonSettings().getWakeTimeHour()); + newSeason.getSeasonSettings().setWakeTime(oldSeason.getSeasonSettings().getWakeTime()); } var season = seasonRepository.save(newSeason); @@ -157,15 +160,17 @@ public SeasonDto updateSeason(Season season, SeasonUpdateDto dto) { return Optional.ofNullable(season) .map(existingSeason -> { if (existingSeason.getSeasonSettings() == null) { - dto.getSeasonSettings().setId(null); - existingSeason.setSeasonSettings(dto.getSeasonSettings()); + var seasonSettings = seasonSettingsMapper.seasonSettingsDtoToSeasonSettings(dto.getSeasonSettings()); + + seasonSettings.setId(null); + existingSeason.setSeasonSettings(seasonSettings); } else { existingSeason.getSeasonSettings().setMaxTeamSize(dto.getSeasonSettings().getMaxTeamSize()); existingSeason.getSeasonSettings().setMinTeamSize(dto.getSeasonSettings().getMinTeamSize()); existingSeason.getSeasonSettings().setMinMatchesToQualify(dto.getSeasonSettings().getMinMatchesToQualify()); existingSeason.getSeasonSettings().setRankingAlgorithm(dto.getSeasonSettings().getRankingAlgorithm()); existingSeason.getSeasonSettings().setDailyLeaderboard(dto.getSeasonSettings().getDailyLeaderboard()); - existingSeason.getSeasonSettings().setWakeTimeHour(dto.getSeasonSettings().getWakeTimeHour()); + existingSeason.getSeasonSettings().setWakeTime(LocalTime.parse(dto.getSeasonSettings().getWakeTime(), LocalTimeAdapter.FORMATTER)); } var seasonDto = seasonMapper.seasonToSeasonDto(seasonRepository.save(existingSeason)); diff --git a/api/src/main/java/pro/beerpong/api/sockets/LocalTimeAdapter.java b/api/src/main/java/pro/beerpong/api/sockets/LocalTimeAdapter.java new file mode 100644 index 00000000..f11044cd --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/sockets/LocalTimeAdapter.java @@ -0,0 +1,34 @@ +package pro.beerpong.api.sockets; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +public class LocalTimeAdapter extends TypeAdapter { + public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); + + @Override + public void write(JsonWriter out, LocalTime value) throws IOException { + if (value == null) { + out.nullValue(); + } else { + out.value(value.format(FORMATTER)); + } + } + + @Override + public LocalTime read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } else { + String timeString = in.nextString(); + return LocalTime.parse(timeString, FORMATTER); + } + } +} diff --git a/api/src/main/java/pro/beerpong/api/sockets/SubscriptionHandler.java b/api/src/main/java/pro/beerpong/api/sockets/SubscriptionHandler.java index d8336305..b95f2e39 100644 --- a/api/src/main/java/pro/beerpong/api/sockets/SubscriptionHandler.java +++ b/api/src/main/java/pro/beerpong/api/sockets/SubscriptionHandler.java @@ -30,6 +30,7 @@ public class SubscriptionHandler extends TextWebSocketHandler { private static final Gson GSON = new GsonBuilder() .serializeNulls() .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter()) + .registerTypeAdapter(LocalTimeAdapter.class, new LocalTimeAdapter()) .create(); private final Map> userGroups = new ConcurrentHashMap<>(); diff --git a/api/src/main/java/pro/beerpong/api/sockets/ZonedDateTimeAdapter.java b/api/src/main/java/pro/beerpong/api/sockets/ZonedDateTimeAdapter.java index 74af55dc..b6b0200f 100644 --- a/api/src/main/java/pro/beerpong/api/sockets/ZonedDateTimeAdapter.java +++ b/api/src/main/java/pro/beerpong/api/sockets/ZonedDateTimeAdapter.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; public class ZonedDateTimeAdapter extends TypeAdapter { @@ -29,7 +30,11 @@ public ZonedDateTime read(JsonReader in) throws IOException { return null; } else { String dateTimeString = in.nextString(); - return ZonedDateTime.parse(dateTimeString, formatter); + try { + return ZonedDateTime.parse(dateTimeString, formatter); + } catch (DateTimeParseException e) { + return null; + } } } } From c70dc343041f89a6fdb9547a490058990142a9aa Mon Sep 17 00:00:00 2001 From: Thiies <40271292+Thiies@users.noreply.github.com> Date: Thu, 5 Jun 2025 15:52:28 +0000 Subject: [PATCH 02/90] chore: update openapi types --- mobile-app/api/generated/openapi.json | 37 +++++++++++++++++++++++++-- mobile-app/openapi/openapi.d.ts | 23 +++++++++++++++-- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/mobile-app/api/generated/openapi.json b/mobile-app/api/generated/openapi.json index 16ab5458..8f8597e9 100644 --- a/mobile-app/api/generated/openapi.json +++ b/mobile-app/api/generated/openapi.json @@ -1182,6 +1182,15 @@ "imageUrl": { "type": "string" } } }, + "LocalTime": { + "type": "object", + "properties": { + "hour": { "type": "integer", "format": "int32" }, + "minute": { "type": "integer", "format": "int32" }, + "second": { "type": "integer", "format": "int32" }, + "nano": { "type": "integer", "format": "int32" } + } + }, "ResponseEnvelopeGroupDto": { "type": "object", "properties": { @@ -1226,7 +1235,7 @@ "LAST_24_HOURS" ] }, - "wakeTimeHour": { "type": "integer", "format": "int32" } + "wakeTime": { "$ref": "#/components/schemas/LocalTime" } } }, "ResponseEnvelopeAssetMetadataDto": { @@ -1391,12 +1400,36 @@ "playerId": { "type": "string" } } }, + "SeasonSettingsDto": { + "type": "object", + "properties": { + "minMatchesToQualify": { + "type": "integer", + "format": "int32" + }, + "minTeamSize": { "type": "integer", "format": "int32" }, + "maxTeamSize": { "type": "integer", "format": "int32" }, + "rankingAlgorithm": { + "type": "string", + "enum": ["AVERAGE", "ELO"] + }, + "dailyLeaderboard": { + "type": "string", + "enum": [ + "RESET_AT_MIDNIGHT", + "WAKE_TIME", + "LAST_24_HOURS" + ] + }, + "wakeTime": { "type": "string" } + } + }, "SeasonUpdateDto": { "required": ["seasonSettings"], "type": "object", "properties": { "seasonSettings": { - "$ref": "#/components/schemas/SeasonSettings" + "$ref": "#/components/schemas/SeasonSettingsDto" } } }, diff --git a/mobile-app/openapi/openapi.d.ts b/mobile-app/openapi/openapi.d.ts index a2c9f803..43ecd091 100644 --- a/mobile-app/openapi/openapi.d.ts +++ b/mobile-app/openapi/openapi.d.ts @@ -48,6 +48,12 @@ declare namespace Components { startedAt?: string; // date-time entries?: PlayerDto[]; } + export interface LocalTime { + hour?: number; // int32 + minute?: number; // int32 + second?: number; // int32 + nano?: number; // int32 + } export interface MatchCreateDto { teams?: TeamCreateDto[]; } @@ -284,10 +290,21 @@ declare namespace Components { | 'RESET_AT_MIDNIGHT' | 'WAKE_TIME' | 'LAST_24_HOURS'; - wakeTimeHour?: number; // int32 + wakeTime?: LocalTime; + } + export interface SeasonSettingsDto { + minMatchesToQualify?: number; // int32 + minTeamSize?: number; // int32 + maxTeamSize?: number; // int32 + rankingAlgorithm?: 'AVERAGE' | 'ELO'; + dailyLeaderboard?: + | 'RESET_AT_MIDNIGHT' + | 'WAKE_TIME' + | 'LAST_24_HOURS'; + wakeTime?: string; } export interface SeasonUpdateDto { - seasonSettings: SeasonSettings; + seasonSettings: SeasonSettingsDto; } export interface TeamCreateDto { teamMembers?: TeamMemberCreateDto[]; @@ -1338,6 +1355,7 @@ export type GroupCreateDto = Components.Schemas.GroupCreateDto; export type GroupDto = Components.Schemas.GroupDto; export type GroupPreset = Components.Schemas.GroupPreset; export type LeaderboardDto = Components.Schemas.LeaderboardDto; +export type LocalTime = Components.Schemas.LocalTime; export type MatchCreateDto = Components.Schemas.MatchCreateDto; export type MatchDto = Components.Schemas.MatchDto; export type MatchMoveDto = Components.Schemas.MatchMoveDto; @@ -1394,6 +1412,7 @@ export type Season = Components.Schemas.Season; export type SeasonCreateDto = Components.Schemas.SeasonCreateDto; export type SeasonDto = Components.Schemas.SeasonDto; export type SeasonSettings = Components.Schemas.SeasonSettings; +export type SeasonSettingsDto = Components.Schemas.SeasonSettingsDto; export type SeasonUpdateDto = Components.Schemas.SeasonUpdateDto; export type TeamCreateDto = Components.Schemas.TeamCreateDto; export type TeamDto = Components.Schemas.TeamDto; From 254316ced33b890c501fccb16be7c7f3d42ebb7c Mon Sep 17 00:00:00 2001 From: Thies Date: Fri, 6 Jun 2025 00:15:11 +0200 Subject: [PATCH 03/90] created daos --- .../pro/beerpong/api/model/dao/Device.java | 19 ++++++++++++++++ .../pro/beerpong/api/model/dao/Group.java | 3 +++ .../beerpong/api/model/dao/GroupMember.java | 15 +++++++++++++ .../pro/beerpong/api/model/dao/Profile.java | 4 ++++ .../java/pro/beerpong/api/model/dao/User.java | 22 +++++++++++++++++++ .../pro/beerpong/api/model/dto/UserDto.java | 8 +++++++ .../beerpong/api/util/InstallationType.java | 6 +++++ 7 files changed, 77 insertions(+) create mode 100644 api/src/main/java/pro/beerpong/api/model/dao/Device.java create mode 100644 api/src/main/java/pro/beerpong/api/model/dao/GroupMember.java create mode 100644 api/src/main/java/pro/beerpong/api/model/dao/User.java create mode 100644 api/src/main/java/pro/beerpong/api/model/dto/UserDto.java create mode 100644 api/src/main/java/pro/beerpong/api/util/InstallationType.java diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Device.java b/api/src/main/java/pro/beerpong/api/model/dao/Device.java new file mode 100644 index 00000000..0aba888a --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/model/dao/Device.java @@ -0,0 +1,19 @@ +package pro.beerpong.api.model.dao; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.Data; +import pro.beerpong.api.util.InstallationType; + +@Entity(name = "devices") +@Data +public class Device { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private String id; + private InstallationType type; + private String deviceId; + private String pushNotificationToken; +} diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Group.java b/api/src/main/java/pro/beerpong/api/model/dao/Group.java index 676fe545..0322eff8 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/Group.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/Group.java @@ -24,4 +24,7 @@ public class Group { private ZonedDateTime createdAt; private String sportPreset; private String customSportName; + @OneToOne + @JoinColumn + private GroupMember createdBy; } diff --git a/api/src/main/java/pro/beerpong/api/model/dao/GroupMember.java b/api/src/main/java/pro/beerpong/api/model/dao/GroupMember.java new file mode 100644 index 00000000..0866987d --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/model/dao/GroupMember.java @@ -0,0 +1,15 @@ +package pro.beerpong.api.model.dao; + +import jakarta.persistence.*; +import lombok.Data; + +import java.util.List; + +@Entity(name = "group_members") +@Data +public class GroupMember { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private String id; + private String groupId; +} diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Profile.java b/api/src/main/java/pro/beerpong/api/model/dao/Profile.java index 9cd80327..879d0a7e 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/Profile.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/Profile.java @@ -18,4 +18,8 @@ public class Profile { @ManyToOne @JoinColumn(name = "groupId") private Group group; + + @OneToOne + @JoinColumn + private GroupMember createdBy; } diff --git a/api/src/main/java/pro/beerpong/api/model/dao/User.java b/api/src/main/java/pro/beerpong/api/model/dao/User.java new file mode 100644 index 00000000..0b93c66e --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/model/dao/User.java @@ -0,0 +1,22 @@ +package pro.beerpong.api.model.dao; + +import jakarta.persistence.*; +import lombok.Data; + +import java.util.List; + +@Entity(name = "users") +@Data +public class User { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private String id; + + @OneToMany(mappedBy = "user") + private List devices; + + @OneToMany(mappedBy = "user") + private List groups; + + // future: premium subscriptions and other stuff here +} diff --git a/api/src/main/java/pro/beerpong/api/model/dto/UserDto.java b/api/src/main/java/pro/beerpong/api/model/dto/UserDto.java new file mode 100644 index 00000000..34d607fe --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/model/dto/UserDto.java @@ -0,0 +1,8 @@ +package pro.beerpong.api.model.dto; + +import lombok.Data; + +@Data +public class UserDto { + private String id; +} \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/util/InstallationType.java b/api/src/main/java/pro/beerpong/api/util/InstallationType.java new file mode 100644 index 00000000..ef37b57d --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/util/InstallationType.java @@ -0,0 +1,6 @@ +package pro.beerpong.api.util; + +public enum InstallationType { + IOS, + ANDROID +} From 64acbdb731d31fdeb76674ac9e052c4cfe1a1f65 Mon Sep 17 00:00:00 2001 From: Thies Date: Sat, 7 Jun 2025 13:18:18 +0200 Subject: [PATCH 04/90] impl jwt in pom --- api/pom.xml | 42 +++++++++++++------ .../pro/beerpong/api/model/dao/Group.java | 3 -- .../pro/beerpong/api/model/dao/Profile.java | 4 -- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index bd44563a..48db62cf 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -18,6 +18,17 @@ 1.6.3 + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.6.0 + org.springframework.boot spring-boot-starter-data-jpa @@ -31,11 +42,12 @@ spring-boot-starter-websocket + - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 + com.google.cloud.sql + postgres-socket-factory + 1.11.0 - org.postgresql postgresql @@ -47,6 +59,7 @@ test + org.projectlombok lombok @@ -62,21 +75,26 @@ lombok-mapstruct-binding 0.2.0 + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + - org.springframework.boot - spring-boot-starter-test - test + io.jsonwebtoken + jjwt-api + 0.11.5 - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.6.0 + io.jsonwebtoken + jjwt-impl + 0.11.5 - com.google.cloud.sql - postgres-socket-factory - 1.11.0 + io.jsonwebtoken + jjwt-jackson + 0.11.5 diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Group.java b/api/src/main/java/pro/beerpong/api/model/dao/Group.java index 0322eff8..676fe545 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/Group.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/Group.java @@ -24,7 +24,4 @@ public class Group { private ZonedDateTime createdAt; private String sportPreset; private String customSportName; - @OneToOne - @JoinColumn - private GroupMember createdBy; } diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Profile.java b/api/src/main/java/pro/beerpong/api/model/dao/Profile.java index 879d0a7e..9cd80327 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/Profile.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/Profile.java @@ -18,8 +18,4 @@ public class Profile { @ManyToOne @JoinColumn(name = "groupId") private Group group; - - @OneToOne - @JoinColumn - private GroupMember createdBy; } From 0d1aa4cb2fd4997b1bfacc9279ebd6a980deead6 Mon Sep 17 00:00:00 2001 From: Thies Date: Sat, 7 Jun 2025 14:45:02 +0200 Subject: [PATCH 05/90] implemented dtos, daos and db logic --- api/pom.xml | 1 + .../beerpong/api/auth/JwtTokenProvider.java | 56 +++++++++++++++++++ .../beerpong/api/control/AuthController.java | 36 ++++++++++++ .../pro/beerpong/api/model/dao/Device.java | 8 +-- .../pro/beerpong/api/model/dao/Group.java | 3 + .../beerpong/api/model/dao/GroupMember.java | 7 ++- .../api/model/dto/AuthRegisterDto.java | 11 ++++ .../beerpong/api/model/dto/AuthTokenDto.java | 12 ++++ .../beerpong/api/model/dto/ErrorCodes.java | 4 +- .../api/repository/DeviceRepository.java | 8 +++ .../api/repository/UserRepository.java | 8 +++ .../pro/beerpong/api/service/AuthService.java | 45 +++++++++++++++ .../java/pro/beerpong/api/util/TokenType.java | 6 ++ api/src/main/resources/application.properties | 2 + 14 files changed, 201 insertions(+), 6 deletions(-) create mode 100644 api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java create mode 100644 api/src/main/java/pro/beerpong/api/control/AuthController.java create mode 100644 api/src/main/java/pro/beerpong/api/model/dto/AuthRegisterDto.java create mode 100644 api/src/main/java/pro/beerpong/api/model/dto/AuthTokenDto.java create mode 100644 api/src/main/java/pro/beerpong/api/repository/DeviceRepository.java create mode 100644 api/src/main/java/pro/beerpong/api/repository/UserRepository.java create mode 100644 api/src/main/java/pro/beerpong/api/service/AuthService.java create mode 100644 api/src/main/java/pro/beerpong/api/util/TokenType.java create mode 100644 api/src/main/resources/application.properties diff --git a/api/pom.xml b/api/pom.xml index 48db62cf..4446f9a4 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -42,6 +42,7 @@ spring-boot-starter-websocket + com.google.cloud.sql diff --git a/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java b/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java new file mode 100644 index 00000000..786988b3 --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java @@ -0,0 +1,56 @@ +package pro.beerpong.api.auth; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Date; + +@Component +public class JwtTokenProvider { + private Key key; + @Value("${jwt.secret}") + private String secret; + @Value("${jwt.access.expiration-ms}") + private long accessExpirationMs; + + @PostConstruct + public void init() { + key = Keys.hmacShaKeyFor(secret.getBytes()); + } + + public String createRefreshToken(String userId) { + return Jwts.builder() + .setSubject(userId) + .claim("type", "refresh") + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + public String createAccessToken(String userId) { + Date now = new Date(); + Date expiry = new Date(now.getTime() + accessExpirationMs); + + return Jwts.builder() + .setSubject(userId) + .claim("type", "access") + .setIssuedAt(now) + .setExpiration(expiry) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + public Jws parseToken(String token) { + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token); + } +} + diff --git a/api/src/main/java/pro/beerpong/api/control/AuthController.java b/api/src/main/java/pro/beerpong/api/control/AuthController.java new file mode 100644 index 00000000..649aba6c --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/control/AuthController.java @@ -0,0 +1,36 @@ +package pro.beerpong.api.control; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import pro.beerpong.api.model.dto.AuthRegisterDto; +import pro.beerpong.api.model.dto.AuthTokenDto; +import pro.beerpong.api.model.dto.ErrorCodes; +import pro.beerpong.api.model.dto.ResponseEnvelope; +import pro.beerpong.api.service.AuthService; + +@RestController +@RequestMapping("/auth") +@RequiredArgsConstructor +public class AuthController { + private final AuthService authService; + + @GetMapping("signup") + public ResponseEntity> signup(@RequestBody AuthRegisterDto authRegisterDto) { + var result = authService.registerDevice(authRegisterDto); + + if (result == null) { + return ResponseEnvelope.notOk(ErrorCodes.AUTH_REGISTER_INVALID_DTO); + } + + return ResponseEnvelope.ok(result); + } + + @GetMapping("refresh") + public ResponseEntity> refreshAuth() { + return ResponseEnvelope.ok(null); + } +} diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Device.java b/api/src/main/java/pro/beerpong/api/model/dao/Device.java index 0aba888a..c8ba5cff 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/Device.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/Device.java @@ -1,9 +1,6 @@ package pro.beerpong.api.model.dao; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; +import jakarta.persistence.*; import lombok.Data; import pro.beerpong.api.util.InstallationType; @@ -16,4 +13,7 @@ public class Device { private InstallationType type; private String deviceId; private String pushNotificationToken; + @ManyToOne + @JoinColumn(name = "user_id") + private User user; } diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Group.java b/api/src/main/java/pro/beerpong/api/model/dao/Group.java index 676fe545..c2b03934 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/Group.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/Group.java @@ -5,6 +5,7 @@ import pro.beerpong.api.model.dto.GroupPreset; import java.time.ZonedDateTime; +import java.util.List; @Entity(name = "groups") @Data @@ -21,6 +22,8 @@ public class Group { @OneToOne @JoinColumn(name = "assetIdWallpaper") private Asset wallpaperAsset; + @OneToMany(mappedBy = "group") + private List members; private ZonedDateTime createdAt; private String sportPreset; private String customSportName; diff --git a/api/src/main/java/pro/beerpong/api/model/dao/GroupMember.java b/api/src/main/java/pro/beerpong/api/model/dao/GroupMember.java index 0866987d..bb3a779a 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/GroupMember.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/GroupMember.java @@ -11,5 +11,10 @@ public class GroupMember { @Id @GeneratedValue(strategy = GenerationType.UUID) private String id; - private String groupId; + @ManyToOne + @JoinColumn(name = "group_id") + private Group group; + @ManyToOne + @JoinColumn(name = "user_id") + private User user; } diff --git a/api/src/main/java/pro/beerpong/api/model/dto/AuthRegisterDto.java b/api/src/main/java/pro/beerpong/api/model/dto/AuthRegisterDto.java new file mode 100644 index 00000000..fb7924a2 --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/model/dto/AuthRegisterDto.java @@ -0,0 +1,11 @@ +package pro.beerpong.api.model.dto; + +import lombok.Data; +import pro.beerpong.api.util.InstallationType; +import pro.beerpong.api.util.TokenType; + +@Data +public class AuthRegisterDto { + private InstallationType installationType; + private String deviceId; +} \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/model/dto/AuthTokenDto.java b/api/src/main/java/pro/beerpong/api/model/dto/AuthTokenDto.java new file mode 100644 index 00000000..a3b5ee4e --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/model/dto/AuthTokenDto.java @@ -0,0 +1,12 @@ +package pro.beerpong.api.model.dto; + +import lombok.Data; +import pro.beerpong.api.util.TokenType; + +import java.time.ZonedDateTime; + +@Data +public class AuthTokenDto { + private String token; + private TokenType type; +} \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java index f1d37fcc..69f0c7be 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java @@ -49,7 +49,9 @@ public enum ErrorCodes { ASSET_NOT_FOUND(HttpStatus.NOT_FOUND, "assetNotFound", "The requested asset could not be found!"), /* LEADERBOARDS */ LEADERBOARD_SCOPE_NOT_FOUND(HttpStatus.NOT_FOUND, "leaderboardScopeNotFound", "The leaderboard scope has to be one of: all-time, today, season"), - LEADERBOARD_SEASON_NOT_FOUND(HttpStatus.NOT_FOUND, "leaderboardScopeNotFound", "The scope 'season' requires a seasonId param!"); + LEADERBOARD_SEASON_NOT_FOUND(HttpStatus.NOT_FOUND, "leaderboardScopeNotFound", "The scope 'season' requires a seasonId param!"), + /* AUTH */ + AUTH_REGISTER_INVALID_DTO(HttpStatus.BAD_REQUEST, "authRegisterInvalidDto", "The installationType or deviceId is invalid!"),; private final HttpStatus httpStatus; private final String code; diff --git a/api/src/main/java/pro/beerpong/api/repository/DeviceRepository.java b/api/src/main/java/pro/beerpong/api/repository/DeviceRepository.java new file mode 100644 index 00000000..8b7735f9 --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/repository/DeviceRepository.java @@ -0,0 +1,8 @@ +package pro.beerpong.api.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import pro.beerpong.api.model.dao.Device; +import pro.beerpong.api.model.dao.User; + +public interface DeviceRepository extends JpaRepository { +} \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/repository/UserRepository.java b/api/src/main/java/pro/beerpong/api/repository/UserRepository.java new file mode 100644 index 00000000..4c9a4181 --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/repository/UserRepository.java @@ -0,0 +1,8 @@ +package pro.beerpong.api.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import pro.beerpong.api.model.dao.Asset; +import pro.beerpong.api.model.dao.User; + +public interface UserRepository extends JpaRepository { +} \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/service/AuthService.java b/api/src/main/java/pro/beerpong/api/service/AuthService.java new file mode 100644 index 00000000..3b227572 --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/service/AuthService.java @@ -0,0 +1,45 @@ +package pro.beerpong.api.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import pro.beerpong.api.auth.JwtTokenProvider; +import pro.beerpong.api.model.dao.Device; +import pro.beerpong.api.model.dao.User; +import pro.beerpong.api.model.dto.AuthRegisterDto; +import pro.beerpong.api.model.dto.AuthTokenDto; +import pro.beerpong.api.repository.DeviceRepository; +import pro.beerpong.api.repository.UserRepository; +import pro.beerpong.api.util.TokenType; + +@Service +@RequiredArgsConstructor +public class AuthService { + private final JwtTokenProvider tokenProvider; + private final UserRepository userRepository; + private final DeviceRepository deviceRepository; + + public AuthTokenDto registerDevice(AuthRegisterDto dto) { + if (dto.getDeviceId() == null || dto.getDeviceId().isEmpty() || dto.getInstallationType() == null) { + return null; + } + + var user = new User(); + user = userRepository.save(user); + + var device = new Device(); + device.setUser(user); + device.setDeviceId(dto.getDeviceId()); + device.setType(dto.getInstallationType()); + //TODO set pushNotifyToken? + + deviceRepository.save(device); + + var token = tokenProvider.createRefreshToken(user.getId()); + var result = new AuthTokenDto(); + + result.setToken(token); + result.setType(TokenType.REFRESH); + + return result; + } +} diff --git a/api/src/main/java/pro/beerpong/api/util/TokenType.java b/api/src/main/java/pro/beerpong/api/util/TokenType.java new file mode 100644 index 00000000..b9e8faf8 --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/util/TokenType.java @@ -0,0 +1,6 @@ +package pro.beerpong.api.util; + +public enum TokenType { + ACCESS, + REFRESH +} diff --git a/api/src/main/resources/application.properties b/api/src/main/resources/application.properties new file mode 100644 index 00000000..1e89b51d --- /dev/null +++ b/api/src/main/resources/application.properties @@ -0,0 +1,2 @@ +jwt.secret=YourVeryLongRandomSecretKeyHere +jwt.access.expiration-ms=3600000 \ No newline at end of file From 3bbda541ac343d891cf8a7b694f107bdc0b925df Mon Sep 17 00:00:00 2001 From: Thies Date: Sat, 7 Jun 2025 15:03:05 +0200 Subject: [PATCH 06/90] auth endpoints working --- .../beerpong/api/auth/JwtTokenProvider.java | 19 +++++-- .../beerpong/api/control/AuthController.java | 23 +++++--- .../api/model/dto/AuthRefreshDto.java | 9 ++++ ...uthRegisterDto.java => AuthSignupDto.java} | 3 +- .../beerpong/api/model/dto/ErrorCodes.java | 4 +- .../pro/beerpong/api/service/AuthService.java | 54 +++++++++++++++++-- api/src/main/resources/application.properties | 2 +- 7 files changed, 93 insertions(+), 21 deletions(-) create mode 100644 api/src/main/java/pro/beerpong/api/model/dto/AuthRefreshDto.java rename api/src/main/java/pro/beerpong/api/model/dto/{AuthRegisterDto.java => AuthSignupDto.java} (72%) diff --git a/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java b/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java index 786988b3..159dc636 100644 --- a/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java +++ b/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java @@ -1,9 +1,6 @@ package pro.beerpong.api.auth; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jws; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Value; @@ -52,5 +49,19 @@ public Jws parseToken(String token) { .build() .parseClaimsJws(token); } + + public Claims validateToken(String token, String type) { + try { + var claims = parseToken(token).getBody(); + + if (!claims.get("type", String.class).equals(type)) { + return null; + } + + return claims; + } catch (JwtException | IllegalArgumentException e) { + return null; + } + } } diff --git a/api/src/main/java/pro/beerpong/api/control/AuthController.java b/api/src/main/java/pro/beerpong/api/control/AuthController.java index 649aba6c..f71ad23b 100644 --- a/api/src/main/java/pro/beerpong/api/control/AuthController.java +++ b/api/src/main/java/pro/beerpong/api/control/AuthController.java @@ -6,10 +6,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import pro.beerpong.api.model.dto.AuthRegisterDto; -import pro.beerpong.api.model.dto.AuthTokenDto; -import pro.beerpong.api.model.dto.ErrorCodes; -import pro.beerpong.api.model.dto.ResponseEnvelope; +import pro.beerpong.api.model.dto.*; import pro.beerpong.api.service.AuthService; @RestController @@ -19,8 +16,8 @@ public class AuthController { private final AuthService authService; @GetMapping("signup") - public ResponseEntity> signup(@RequestBody AuthRegisterDto authRegisterDto) { - var result = authService.registerDevice(authRegisterDto); + public ResponseEntity> signup(@RequestBody AuthSignupDto authSignupDto) { + var result = authService.registerDevice(authSignupDto); if (result == null) { return ResponseEnvelope.notOk(ErrorCodes.AUTH_REGISTER_INVALID_DTO); @@ -30,7 +27,17 @@ public ResponseEntity> signup(@RequestBody AuthRe } @GetMapping("refresh") - public ResponseEntity> refreshAuth() { - return ResponseEnvelope.ok(null); + public ResponseEntity> refreshAuth(@RequestBody AuthRefreshDto dto) { + if (dto.getRefreshToken() == null || dto.getRefreshToken().isEmpty()) { + return ResponseEnvelope.notOk(ErrorCodes.AUTH_REFRESH_INVALID_DTO); + } + + var result = authService.refreshAuth(dto); + + if (result == null) { + return ResponseEnvelope.notOk(ErrorCodes.AUTH_REFRESH_INVALID_TOKEN); + } + + return ResponseEnvelope.ok(result); } } diff --git a/api/src/main/java/pro/beerpong/api/model/dto/AuthRefreshDto.java b/api/src/main/java/pro/beerpong/api/model/dto/AuthRefreshDto.java new file mode 100644 index 00000000..4b6c7446 --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/model/dto/AuthRefreshDto.java @@ -0,0 +1,9 @@ +package pro.beerpong.api.model.dto; + +import lombok.Data; +import pro.beerpong.api.util.InstallationType; + +@Data +public class AuthRefreshDto { + private String refreshToken; +} \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/model/dto/AuthRegisterDto.java b/api/src/main/java/pro/beerpong/api/model/dto/AuthSignupDto.java similarity index 72% rename from api/src/main/java/pro/beerpong/api/model/dto/AuthRegisterDto.java rename to api/src/main/java/pro/beerpong/api/model/dto/AuthSignupDto.java index fb7924a2..1c22ede4 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/AuthRegisterDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/AuthSignupDto.java @@ -2,10 +2,9 @@ import lombok.Data; import pro.beerpong.api.util.InstallationType; -import pro.beerpong.api.util.TokenType; @Data -public class AuthRegisterDto { +public class AuthSignupDto { private InstallationType installationType; private String deviceId; } \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java index 69f0c7be..ddf83d90 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java @@ -51,7 +51,9 @@ public enum ErrorCodes { LEADERBOARD_SCOPE_NOT_FOUND(HttpStatus.NOT_FOUND, "leaderboardScopeNotFound", "The leaderboard scope has to be one of: all-time, today, season"), LEADERBOARD_SEASON_NOT_FOUND(HttpStatus.NOT_FOUND, "leaderboardScopeNotFound", "The scope 'season' requires a seasonId param!"), /* AUTH */ - AUTH_REGISTER_INVALID_DTO(HttpStatus.BAD_REQUEST, "authRegisterInvalidDto", "The installationType or deviceId is invalid!"),; + AUTH_REGISTER_INVALID_DTO(HttpStatus.BAD_REQUEST, "authRegisterInvalidDto", "The installationType or deviceId is invalid!"), + AUTH_REFRESH_INVALID_DTO(HttpStatus.BAD_REQUEST, "authRefreshInvalidDto", "The refreshToken has to be non-null and non-empty!"), + AUTH_REFRESH_INVALID_TOKEN(HttpStatus.BAD_REQUEST, "authRefreshInvalidToken", "The refreshToken is no refresh-token, invalid or the subject-userId is invalid!"); private final HttpStatus httpStatus; private final String code; diff --git a/api/src/main/java/pro/beerpong/api/service/AuthService.java b/api/src/main/java/pro/beerpong/api/service/AuthService.java index 3b227572..0022fd70 100644 --- a/api/src/main/java/pro/beerpong/api/service/AuthService.java +++ b/api/src/main/java/pro/beerpong/api/service/AuthService.java @@ -5,7 +5,8 @@ import pro.beerpong.api.auth.JwtTokenProvider; import pro.beerpong.api.model.dao.Device; import pro.beerpong.api.model.dao.User; -import pro.beerpong.api.model.dto.AuthRegisterDto; +import pro.beerpong.api.model.dto.AuthRefreshDto; +import pro.beerpong.api.model.dto.AuthSignupDto; import pro.beerpong.api.model.dto.AuthTokenDto; import pro.beerpong.api.repository.DeviceRepository; import pro.beerpong.api.repository.UserRepository; @@ -18,8 +19,8 @@ public class AuthService { private final UserRepository userRepository; private final DeviceRepository deviceRepository; - public AuthTokenDto registerDevice(AuthRegisterDto dto) { - if (dto.getDeviceId() == null || dto.getDeviceId().isEmpty() || dto.getInstallationType() == null) { + public AuthTokenDto registerDevice(AuthSignupDto dto) { + if (dto.getDeviceId() == null || dto.getDeviceId().trim().isEmpty() || dto.getInstallationType() == null) { return null; } @@ -34,11 +35,54 @@ public AuthTokenDto registerDevice(AuthRegisterDto dto) { deviceRepository.save(device); - var token = tokenProvider.createRefreshToken(user.getId()); + var refreshToken = tokenProvider.createRefreshToken(user.getId()); + + return this.buildDto(refreshToken, TokenType.REFRESH); + } + + public AuthTokenDto refreshAuth(AuthRefreshDto dto) { + if (dto.getRefreshToken() == null || dto.getRefreshToken().trim().isEmpty()) { + return null; + } + + var claims = tokenProvider.validateToken(dto.getRefreshToken(), "refresh"); + + if (claims == null) { + return null; + } + + var userId = claims.getSubject(); + + if (!userRepository.existsById(userId)) { + return null; + } + + var accessToken = tokenProvider.createAccessToken(userId); + + return this.buildDto(accessToken, TokenType.ACCESS); + } + + public User userFromAccessToken(String token) { + if (token == null || token.trim().isEmpty()) { + return null; + } + + var claims = tokenProvider.validateToken(token, "access"); + + if (claims == null) { + return null; + } + + var userId = claims.getSubject(); + + return userRepository.findById(userId).orElse(null); + } + + private AuthTokenDto buildDto(String token, TokenType type) { var result = new AuthTokenDto(); result.setToken(token); - result.setType(TokenType.REFRESH); + result.setType(type); return result; } diff --git a/api/src/main/resources/application.properties b/api/src/main/resources/application.properties index 1e89b51d..51c2c542 100644 --- a/api/src/main/resources/application.properties +++ b/api/src/main/resources/application.properties @@ -1,2 +1,2 @@ -jwt.secret=YourVeryLongRandomSecretKeyHere +jwt.secret=YourVeryLongRandomSecretKeyHereWhichIsVerySecureAndHasToBeReplacedWithEnvVariables jwt.access.expiration-ms=3600000 \ No newline at end of file From ffb4086f1aa179cd3c2b6466d56b5ffb28f73950 Mon Sep 17 00:00:00 2001 From: Thies Date: Sat, 7 Jun 2025 15:41:19 +0200 Subject: [PATCH 07/90] implemented in spring security --- api/pom.xml | 5 +- .../api/auth/JwtAuthenticationFilter.java | 70 +++++++++++++++++++ .../pro/beerpong/api/auth/SecurityConfig.java | 33 +++++++++ .../pro/beerpong/api/mapping/UserMapper.java | 13 ++++ .../api/repository/GroupMemberRepository.java | 13 ++++ .../pro/beerpong/api/service/AuthService.java | 15 +++- 6 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java create mode 100644 api/src/main/java/pro/beerpong/api/auth/SecurityConfig.java create mode 100644 api/src/main/java/pro/beerpong/api/mapping/UserMapper.java create mode 100644 api/src/main/java/pro/beerpong/api/repository/GroupMemberRepository.java diff --git a/api/pom.xml b/api/pom.xml index 4446f9a4..1a228b2b 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -41,7 +41,10 @@ org.springframework.boot spring-boot-starter-websocket - + + org.springframework.boot + spring-boot-starter-security + diff --git a/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java b/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java new file mode 100644 index 00000000..cf5f60ae --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java @@ -0,0 +1,70 @@ +package pro.beerpong.api.auth; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; +import pro.beerpong.api.service.AuthService; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.io.IOException; +import java.util.List; + +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + private static final String GROUPS_PATTERN = "/groups/{groupId}/**"; + + private final AuthService authService; + private final AntPathMatcher pathMatcher = new AntPathMatcher(); + + @Override + protected void doFilterInternal(HttpServletRequest req, + HttpServletResponse res, + FilterChain chain) throws ServletException, IOException { + var header = req.getHeader("Authorization"); + + if (!StringUtils.hasText(header) || !header.startsWith("Bearer ")) { + res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + res.getWriter().write("Missing or invalid Authorization header!"); + return; + } + + var token = header.substring(7); + var user = authService.userFromAccessToken(token); + + if (user == null) { + res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + res.getWriter().write("Invalid access token or invalid user-id!"); + return; + } + + var groupId = extractGroupId(req); + + if (!authService.hasAccessToGroup(user, groupId)) { + res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + res.getWriter().write("No access to this group!"); + return; + } + + var auth = new UsernamePasswordAuthenticationToken(user, null, List.of(new SimpleGrantedAuthority("ROLE_GROUP_MEMBER"))); + SecurityContextHolder.getContext().setAuthentication(auth); + + chain.doFilter(req, res); + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + return !pathMatcher.match(GROUPS_PATTERN, request.getRequestURI()); + } + + private String extractGroupId(HttpServletRequest request) { + return pathMatcher.extractUriTemplateVariables(GROUPS_PATTERN, request.getRequestURI()) + .get("groupId"); + } +} diff --git a/api/src/main/java/pro/beerpong/api/auth/SecurityConfig.java b/api/src/main/java/pro/beerpong/api/auth/SecurityConfig.java new file mode 100644 index 00000000..5022ecbf --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/auth/SecurityConfig.java @@ -0,0 +1,33 @@ +package pro.beerpong.api.auth; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import pro.beerpong.api.service.AuthService; + +@Configuration +@RequiredArgsConstructor +public class SecurityConfig { + private final AuthService authService; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + var jwtFilter = new JwtAuthenticationFilter(authService); + + http + .csrf(AbstractHttpConfigurer::disable) + .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/groups/{groupId}/**").authenticated() + .anyRequest().permitAll() + ) + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} diff --git a/api/src/main/java/pro/beerpong/api/mapping/UserMapper.java b/api/src/main/java/pro/beerpong/api/mapping/UserMapper.java new file mode 100644 index 00000000..77496dfd --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/mapping/UserMapper.java @@ -0,0 +1,13 @@ +package pro.beerpong.api.mapping; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import pro.beerpong.api.model.dao.Team; +import pro.beerpong.api.model.dao.User; +import pro.beerpong.api.model.dto.TeamDto; +import pro.beerpong.api.model.dto.UserDto; + +@Mapper(componentModel = "spring") +public interface UserMapper { + UserDto userToUserDto(User user); +} diff --git a/api/src/main/java/pro/beerpong/api/repository/GroupMemberRepository.java b/api/src/main/java/pro/beerpong/api/repository/GroupMemberRepository.java new file mode 100644 index 00000000..6591d8bc --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/repository/GroupMemberRepository.java @@ -0,0 +1,13 @@ +package pro.beerpong.api.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import pro.beerpong.api.model.dao.Asset; +import pro.beerpong.api.model.dao.GroupMember; + +import java.util.List; + +public interface GroupMemberRepository extends JpaRepository { + List findByUserId(String userId); + + boolean existsByUserIdAndGroupId(String userId, String groupId); +} \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/service/AuthService.java b/api/src/main/java/pro/beerpong/api/service/AuthService.java index 0022fd70..e9b04d61 100644 --- a/api/src/main/java/pro/beerpong/api/service/AuthService.java +++ b/api/src/main/java/pro/beerpong/api/service/AuthService.java @@ -3,12 +3,15 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import pro.beerpong.api.auth.JwtTokenProvider; +import pro.beerpong.api.mapping.UserMapper; import pro.beerpong.api.model.dao.Device; import pro.beerpong.api.model.dao.User; import pro.beerpong.api.model.dto.AuthRefreshDto; import pro.beerpong.api.model.dto.AuthSignupDto; import pro.beerpong.api.model.dto.AuthTokenDto; +import pro.beerpong.api.model.dto.UserDto; import pro.beerpong.api.repository.DeviceRepository; +import pro.beerpong.api.repository.GroupMemberRepository; import pro.beerpong.api.repository.UserRepository; import pro.beerpong.api.util.TokenType; @@ -18,6 +21,8 @@ public class AuthService { private final JwtTokenProvider tokenProvider; private final UserRepository userRepository; private final DeviceRepository deviceRepository; + private final UserMapper userMapper; + private final GroupMemberRepository groupMemberRepository; public AuthTokenDto registerDevice(AuthSignupDto dto) { if (dto.getDeviceId() == null || dto.getDeviceId().trim().isEmpty() || dto.getInstallationType() == null) { @@ -62,7 +67,7 @@ public AuthTokenDto refreshAuth(AuthRefreshDto dto) { return this.buildDto(accessToken, TokenType.ACCESS); } - public User userFromAccessToken(String token) { + public UserDto userFromAccessToken(String token) { if (token == null || token.trim().isEmpty()) { return null; } @@ -75,7 +80,13 @@ public User userFromAccessToken(String token) { var userId = claims.getSubject(); - return userRepository.findById(userId).orElse(null); + return userRepository.findById(userId) + .map(userMapper::userToUserDto) + .orElse(null); + } + + public boolean hasAccessToGroup(UserDto user, String groupId) { + return groupMemberRepository.existsByUserIdAndGroupId(user.getId(), groupId); } private AuthTokenDto buildDto(String token, TokenType type) { From 9ba0642fca2bc54011297c81e55a9f1674b6922b Mon Sep 17 00:00:00 2001 From: Thies Date: Sat, 7 Jun 2025 16:22:56 +0200 Subject: [PATCH 08/90] implemented GET /groups/user --- .../api/auth/JwtAuthenticationFilter.java | 2 +- .../beerpong/api/control/GroupController.java | 19 +++++++++++++++---- .../beerpong/api/model/dto/ErrorCodes.java | 1 + .../beerpong/api/service/GroupService.java | 17 +++++++++++++---- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java b/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java index cf5f60ae..57c8ff0e 100644 --- a/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java +++ b/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java @@ -46,7 +46,7 @@ protected void doFilterInternal(HttpServletRequest req, var groupId = extractGroupId(req); - if (!authService.hasAccessToGroup(user, groupId)) { + if (!groupId.equals("user") && !authService.hasAccessToGroup(user, groupId)) { res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); res.getWriter().write("No access to this group!"); return; diff --git a/api/src/main/java/pro/beerpong/api/control/GroupController.java b/api/src/main/java/pro/beerpong/api/control/GroupController.java index 481c9af0..c0a279c7 100644 --- a/api/src/main/java/pro/beerpong/api/control/GroupController.java +++ b/api/src/main/java/pro/beerpong/api/control/GroupController.java @@ -3,13 +3,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; -import pro.beerpong.api.model.dto.ErrorCodes; -import pro.beerpong.api.model.dto.GroupCreateDto; -import pro.beerpong.api.model.dto.GroupDto; -import pro.beerpong.api.model.dto.ResponseEnvelope; +import pro.beerpong.api.model.dto.*; import pro.beerpong.api.service.GroupService; +import java.util.List; + @RestController @RequestMapping("/groups") public class GroupController { @@ -39,6 +39,17 @@ public ResponseEntity> createGroup(@RequestBody Group return ResponseEnvelope.ok(group); } + @GetMapping("/user") + public ResponseEntity>> findUserGroups(@AuthenticationPrincipal UserDto user) { + if (user == null) { + return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER); + } + + var groups = groupService.findGroupsByUser(user); + + return ResponseEnvelope.ok(groups); + } + @GetMapping public ResponseEntity> findGroupByInviteCode(@RequestParam String inviteCode) { if (inviteCode == null || inviteCode.trim().isEmpty()) { diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java index ddf83d90..56b7ab54 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java @@ -53,6 +53,7 @@ public enum ErrorCodes { /* AUTH */ AUTH_REGISTER_INVALID_DTO(HttpStatus.BAD_REQUEST, "authRegisterInvalidDto", "The installationType or deviceId is invalid!"), AUTH_REFRESH_INVALID_DTO(HttpStatus.BAD_REQUEST, "authRefreshInvalidDto", "The refreshToken has to be non-null and non-empty!"), + AUTH_INVALID_USER(HttpStatus.UNAUTHORIZED, "authInvalidUser", "The user from the access token has to be non-null!"), AUTH_REFRESH_INVALID_TOKEN(HttpStatus.BAD_REQUEST, "authRefreshInvalidToken", "The refreshToken is no refresh-token, invalid or the subject-userId is invalid!"); private final HttpStatus httpStatus; diff --git a/api/src/main/java/pro/beerpong/api/service/GroupService.java b/api/src/main/java/pro/beerpong/api/service/GroupService.java index 4658a769..9212d677 100644 --- a/api/src/main/java/pro/beerpong/api/service/GroupService.java +++ b/api/src/main/java/pro/beerpong/api/service/GroupService.java @@ -8,10 +8,8 @@ import pro.beerpong.api.model.dao.Group; import pro.beerpong.api.model.dao.Season; import pro.beerpong.api.model.dao.SeasonSettings; -import pro.beerpong.api.model.dto.AssetMetadataDto; -import pro.beerpong.api.model.dto.GroupCreateDto; -import pro.beerpong.api.model.dto.GroupDto; -import pro.beerpong.api.model.dto.ProfileCreateDto; +import pro.beerpong.api.model.dto.*; +import pro.beerpong.api.repository.GroupMemberRepository; import pro.beerpong.api.repository.GroupRepository; import pro.beerpong.api.repository.MatchRepository; import pro.beerpong.api.repository.SeasonRepository; @@ -21,6 +19,7 @@ import java.time.ZonedDateTime; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import static pro.beerpong.api.util.RandomStringGenerator.generateRandomString; @@ -40,6 +39,7 @@ public class GroupService { private final PlayerService playerService; private final RuleMoveService ruleMoveService; private final RuleService ruleService; + private final GroupMemberRepository groupMemberRepository; public GroupDto createGroup(GroupCreateDto groupCreateDto) { Group group = groupMapper.groupCreateDtoToGroup(groupCreateDto); @@ -76,6 +76,15 @@ public GroupDto createGroup(GroupCreateDto groupCreateDto) { return withStats(groupMapper.groupToGroupDto(group)); } + public List findGroupsByUser(UserDto user) { + return groupMemberRepository.findByUserId(user.getId()).stream() + .map(groupMember -> withStats(groupRepository.findById(groupMember.getGroup().getId()) + .map(groupMapper::groupToGroupDto) + .orElse(null))) + .filter(Objects::nonNull) + .toList(); + } + public GroupDto findGroupsByInviteCode(String inviteCode) { return withStats(groupRepository.findByInviteCode(inviteCode) .map(groupMapper::groupToGroupDto) From 3a732bba319da3ff2fa73b091da28c77208fe9ef Mon Sep 17 00:00:00 2001 From: Thies Date: Sat, 7 Jun 2025 16:24:03 +0200 Subject: [PATCH 09/90] made endpoint name static --- .../java/pro/beerpong/api/auth/JwtAuthenticationFilter.java | 3 ++- .../main/java/pro/beerpong/api/control/GroupController.java | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java b/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java index 57c8ff0e..3129a273 100644 --- a/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java +++ b/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java @@ -9,6 +9,7 @@ import org.springframework.util.AntPathMatcher; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; +import pro.beerpong.api.control.GroupController; import pro.beerpong.api.service.AuthService; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; @@ -46,7 +47,7 @@ protected void doFilterInternal(HttpServletRequest req, var groupId = extractGroupId(req); - if (!groupId.equals("user") && !authService.hasAccessToGroup(user, groupId)) { + if (!groupId.equals(GroupController.USER_GROUPS_ENDPOINT) && !authService.hasAccessToGroup(user, groupId)) { res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); res.getWriter().write("No access to this group!"); return; diff --git a/api/src/main/java/pro/beerpong/api/control/GroupController.java b/api/src/main/java/pro/beerpong/api/control/GroupController.java index c0a279c7..e5f21c23 100644 --- a/api/src/main/java/pro/beerpong/api/control/GroupController.java +++ b/api/src/main/java/pro/beerpong/api/control/GroupController.java @@ -13,6 +13,8 @@ @RestController @RequestMapping("/groups") public class GroupController { + public static final String USER_GROUPS_ENDPOINT = "user"; + private final GroupService groupService; @Autowired @@ -39,7 +41,7 @@ public ResponseEntity> createGroup(@RequestBody Group return ResponseEnvelope.ok(group); } - @GetMapping("/user") + @GetMapping(USER_GROUPS_ENDPOINT) public ResponseEntity>> findUserGroups(@AuthenticationPrincipal UserDto user) { if (user == null) { return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER); From f8ce8cb71fdf06ead6715dd23db3165fdc92fefd Mon Sep 17 00:00:00 2001 From: Thies Date: Sat, 7 Jun 2025 16:31:47 +0200 Subject: [PATCH 10/90] added leave group endpoint --- .../beerpong/api/control/GroupController.java | 20 ++++++++++++++++++- .../api/repository/GroupMemberRepository.java | 2 ++ .../pro/beerpong/api/service/AuthService.java | 6 ++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/pro/beerpong/api/control/GroupController.java b/api/src/main/java/pro/beerpong/api/control/GroupController.java index e5f21c23..c66ac529 100644 --- a/api/src/main/java/pro/beerpong/api/control/GroupController.java +++ b/api/src/main/java/pro/beerpong/api/control/GroupController.java @@ -6,6 +6,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import pro.beerpong.api.model.dto.*; +import pro.beerpong.api.service.AuthService; import pro.beerpong.api.service.GroupService; import java.util.List; @@ -16,10 +17,12 @@ public class GroupController { public static final String USER_GROUPS_ENDPOINT = "user"; private final GroupService groupService; + private final AuthService authService; @Autowired - public GroupController(GroupService groupService) { + public GroupController(GroupService groupService, AuthService authService) { this.groupService = groupService; + this.authService = authService; } @PostMapping @@ -99,4 +102,19 @@ public ResponseEntity> updateGroup(@PathVariable Stri return ResponseEnvelope.notOk(ErrorCodes.GROUP_NOT_FOUND); } } + + @PostMapping("/{id}/leave-group") + public ResponseEntity> leaveGroup(@PathVariable String id, @AuthenticationPrincipal UserDto user) { + if (user == null) { + return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER); + } + + if (id == null || id.trim().isEmpty()) { + return ResponseEnvelope.notOk(ErrorCodes.INVALID_GROUP_ID); + } + + authService.leaveGroup(user, id); + + return ResponseEnvelope.ok("OK"); + } } diff --git a/api/src/main/java/pro/beerpong/api/repository/GroupMemberRepository.java b/api/src/main/java/pro/beerpong/api/repository/GroupMemberRepository.java index 6591d8bc..f9fdee1e 100644 --- a/api/src/main/java/pro/beerpong/api/repository/GroupMemberRepository.java +++ b/api/src/main/java/pro/beerpong/api/repository/GroupMemberRepository.java @@ -10,4 +10,6 @@ public interface GroupMemberRepository extends JpaRepository findByUserId(String userId); boolean existsByUserIdAndGroupId(String userId, String groupId); + + void deleteByUserIdAndGroupId(String userId, String groupId); } \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/service/AuthService.java b/api/src/main/java/pro/beerpong/api/service/AuthService.java index e9b04d61..0d694c72 100644 --- a/api/src/main/java/pro/beerpong/api/service/AuthService.java +++ b/api/src/main/java/pro/beerpong/api/service/AuthService.java @@ -1,5 +1,6 @@ package pro.beerpong.api.service; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import pro.beerpong.api.auth.JwtTokenProvider; @@ -89,6 +90,11 @@ public boolean hasAccessToGroup(UserDto user, String groupId) { return groupMemberRepository.existsByUserIdAndGroupId(user.getId(), groupId); } + @Transactional + public void leaveGroup(UserDto user, String groupId) { + groupMemberRepository.deleteByUserIdAndGroupId(user.getId(), groupId); + } + private AuthTokenDto buildDto(String token, TokenType type) { var result = new AuthTokenDto(); From 340678eb97ee16eb5f59e7bf5202410543c7527e Mon Sep 17 00:00:00 2001 From: Thies Date: Sat, 7 Jun 2025 16:33:50 +0200 Subject: [PATCH 11/90] renamed leave endpoint --- api/src/main/java/pro/beerpong/api/control/GroupController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/pro/beerpong/api/control/GroupController.java b/api/src/main/java/pro/beerpong/api/control/GroupController.java index c66ac529..8a38a44a 100644 --- a/api/src/main/java/pro/beerpong/api/control/GroupController.java +++ b/api/src/main/java/pro/beerpong/api/control/GroupController.java @@ -103,7 +103,7 @@ public ResponseEntity> updateGroup(@PathVariable Stri } } - @PostMapping("/{id}/leave-group") + @PostMapping("/{id}/leave") public ResponseEntity> leaveGroup(@PathVariable String id, @AuthenticationPrincipal UserDto user) { if (user == null) { return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER); From 34c4314b423b366cf1ed1dc3cb33051e0b4b49e9 Mon Sep 17 00:00:00 2001 From: Thies Date: Sat, 7 Jun 2025 16:59:18 +0200 Subject: [PATCH 12/90] implemented createdBy --- .../pro/beerpong/api/control/MatchController.java | 10 ++++++++-- .../pro/beerpong/api/control/ProfileController.java | 11 +++++++++-- .../main/java/pro/beerpong/api/model/dao/Group.java | 3 +++ .../main/java/pro/beerpong/api/model/dao/Match.java | 4 ++++ .../java/pro/beerpong/api/model/dao/Profile.java | 4 ++++ .../main/java/pro/beerpong/api/model/dao/Rule.java | 4 ++++ .../java/pro/beerpong/api/model/dao/Season.java | 4 ++++ .../java/pro/beerpong/api/model/dto/GroupDto.java | 2 ++ .../java/pro/beerpong/api/model/dto/MatchDto.java | 2 ++ .../java/pro/beerpong/api/model/dto/ProfileDto.java | 2 ++ .../java/pro/beerpong/api/model/dto/RuleDto.java | 2 ++ .../java/pro/beerpong/api/model/dto/SeasonDto.java | 2 ++ .../api/repository/GroupMemberRepository.java | 2 ++ .../java/pro/beerpong/api/service/AuthService.java | 5 +++++ .../java/pro/beerpong/api/service/MatchService.java | 7 +++++-- .../pro/beerpong/api/service/ProfileService.java | 13 ++++++++----- 16 files changed, 66 insertions(+), 11 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/control/MatchController.java b/api/src/main/java/pro/beerpong/api/control/MatchController.java index d05a1ec6..dd7fc0c9 100644 --- a/api/src/main/java/pro/beerpong/api/control/MatchController.java +++ b/api/src/main/java/pro/beerpong/api/control/MatchController.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import pro.beerpong.api.model.dto.*; import pro.beerpong.api.service.MatchService; @@ -29,7 +30,12 @@ public MatchController(MatchService matchService, SeasonService seasonService, S @PostMapping public ResponseEntity> createMatch(@PathVariable String groupId, @PathVariable String seasonId, - @RequestBody MatchCreateDto matchCreateDto) { + @RequestBody MatchCreateDto matchCreateDto, + @AuthenticationPrincipal UserDto user) { + if (user == null) { + return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER); + } + var pair = seasonService.getSeasonAndGroup(groupId, seasonId); var error = seasonService.validateActiveSeason(MatchDto.class, pair); @@ -41,7 +47,7 @@ public ResponseEntity> createMatch(@PathVariable Stri return ResponseEnvelope.notOk(ErrorCodes.MATCH_CREATE_DTO_VALIDATION_FAILED); } - var match = matchService.createNewMatch(pair.getFirst(), pair.getSecond(), matchCreateDto); + var match = matchService.createNewMatch(pair.getFirst(), pair.getSecond(), matchCreateDto, user); if (match != null) { if (match.getSeason().getId().equals(seasonId) && match.getSeason().getGroupId().equals(groupId)) { diff --git a/api/src/main/java/pro/beerpong/api/control/ProfileController.java b/api/src/main/java/pro/beerpong/api/control/ProfileController.java index 71b7dcc1..45643df1 100644 --- a/api/src/main/java/pro/beerpong/api/control/ProfileController.java +++ b/api/src/main/java/pro/beerpong/api/control/ProfileController.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import pro.beerpong.api.model.dto.*; import pro.beerpong.api.service.GroupService; @@ -23,11 +24,17 @@ public class ProfileController { private final SubscriptionHandler subscriptionHandler; @PostMapping - public ResponseEntity> createProfile(@PathVariable String groupId, @RequestBody ProfileCreateDto profileCreateDto) { + public ResponseEntity> createProfile(@PathVariable String groupId, + @RequestBody ProfileCreateDto profileCreateDto, + @AuthenticationPrincipal UserDto user) { + if (user == null) { + return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER); + } + var group = groupService.getGroupById(groupId); if (group != null) { - var dto = profileService.createPlayer(groupId, profileCreateDto); + var dto = profileService.createPlayer(groupId, profileCreateDto, user); if (dto == null) { return ResponseEnvelope.notOk(ErrorCodes.PROFILE_ALREADY_EXISTS); diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Group.java b/api/src/main/java/pro/beerpong/api/model/dao/Group.java index c2b03934..32bf40cc 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/Group.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/Group.java @@ -24,6 +24,9 @@ public class Group { private Asset wallpaperAsset; @OneToMany(mappedBy = "group") private List members; + @OneToOne + @JoinColumn(name = "createdBy") + private GroupMember createdBy; private ZonedDateTime createdAt; private String sportPreset; private String customSportName; diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Match.java b/api/src/main/java/pro/beerpong/api/model/dao/Match.java index 8a3cd76a..26249d61 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/Match.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/Match.java @@ -21,4 +21,8 @@ public class Match { @OneToMany(mappedBy = "match") private List teams; + + @OneToOne + @JoinColumn(name = "createdBy") + private GroupMember createdBy; } diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Profile.java b/api/src/main/java/pro/beerpong/api/model/dao/Profile.java index 9cd80327..63873328 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/Profile.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/Profile.java @@ -18,4 +18,8 @@ public class Profile { @ManyToOne @JoinColumn(name = "groupId") private Group group; + + @OneToOne + @JoinColumn(name = "createdBy") + private GroupMember createdBy; } diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Rule.java b/api/src/main/java/pro/beerpong/api/model/dao/Rule.java index c9c0924e..dbfe8a2b 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/Rule.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/Rule.java @@ -20,6 +20,10 @@ public class Rule implements Cloneable { @JoinColumn(name = "seasonId") private Season season; + @OneToOne + @JoinColumn(name = "createdBy") + private GroupMember createdBy; + @Override @SneakyThrows public Rule clone() { diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Season.java b/api/src/main/java/pro/beerpong/api/model/dao/Season.java index 4d111729..51da9f2e 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/Season.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/Season.java @@ -23,4 +23,8 @@ public class Season { @OneToOne(cascade = CascadeType.ALL) @JoinColumn private SeasonSettings seasonSettings; + + @OneToOne + @JoinColumn(name = "createdBy") + private GroupMember createdBy; } diff --git a/api/src/main/java/pro/beerpong/api/model/dto/GroupDto.java b/api/src/main/java/pro/beerpong/api/model/dto/GroupDto.java index a1560a87..daced112 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/GroupDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/GroupDto.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; +import pro.beerpong.api.model.dao.GroupMember; import pro.beerpong.api.model.dao.Season; import java.time.ZonedDateTime; @@ -14,6 +15,7 @@ public class GroupDto { private Season activeSeason; @JsonInclude(JsonInclude.Include.NON_NULL) private AssetMetadataDto wallpaperAsset; + private GroupMember createdBy; private ZonedDateTime createdAt; private GroupPreset sportPreset; private String customSportName; diff --git a/api/src/main/java/pro/beerpong/api/model/dto/MatchDto.java b/api/src/main/java/pro/beerpong/api/model/dto/MatchDto.java index e050ba36..e70bd29f 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/MatchDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/MatchDto.java @@ -1,6 +1,7 @@ package pro.beerpong.api.model.dto; import lombok.Data; +import pro.beerpong.api.model.dao.GroupMember; import pro.beerpong.api.model.dao.Season; import java.time.ZonedDateTime; @@ -11,6 +12,7 @@ public class MatchDto { private String id; private ZonedDateTime date; private Season season; + private GroupMember createdBy; private List teams; private List teamMembers; private List matchMoves; diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ProfileDto.java b/api/src/main/java/pro/beerpong/api/model/dto/ProfileDto.java index 9b276635..633d1063 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ProfileDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ProfileDto.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; +import pro.beerpong.api.model.dao.GroupMember; @Data public class ProfileDto { @@ -10,4 +11,5 @@ public class ProfileDto { @JsonInclude(JsonInclude.Include.NON_NULL) private AssetMetadataDto avatarAsset; private String groupId; + private GroupMember createdBy; } diff --git a/api/src/main/java/pro/beerpong/api/model/dto/RuleDto.java b/api/src/main/java/pro/beerpong/api/model/dto/RuleDto.java index 1907d02d..21a94a0c 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/RuleDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/RuleDto.java @@ -1,6 +1,7 @@ package pro.beerpong.api.model.dto; import lombok.Data; +import pro.beerpong.api.model.dao.GroupMember; import pro.beerpong.api.model.dao.Season; @Data @@ -9,4 +10,5 @@ public class RuleDto { private String title; private String description; private Season season; + private GroupMember createdBy; } \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/model/dto/SeasonDto.java b/api/src/main/java/pro/beerpong/api/model/dto/SeasonDto.java index ed50b7bc..a9c81432 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/SeasonDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/SeasonDto.java @@ -1,6 +1,7 @@ package pro.beerpong.api.model.dto; import lombok.Data; +import pro.beerpong.api.model.dao.GroupMember; import pro.beerpong.api.model.dao.SeasonSettings; import java.time.ZonedDateTime; @@ -13,4 +14,5 @@ public class SeasonDto { private ZonedDateTime endDate; private String groupId; private SeasonSettings seasonSettings; + private GroupMember createdBy; } \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/repository/GroupMemberRepository.java b/api/src/main/java/pro/beerpong/api/repository/GroupMemberRepository.java index f9fdee1e..cfcef817 100644 --- a/api/src/main/java/pro/beerpong/api/repository/GroupMemberRepository.java +++ b/api/src/main/java/pro/beerpong/api/repository/GroupMemberRepository.java @@ -12,4 +12,6 @@ public interface GroupMemberRepository extends JpaRepository Date: Sun, 8 Jun 2025 13:07:28 +0200 Subject: [PATCH 13/90] auth for every /groups path. excluding some endpoints --- .../api/auth/JwtAuthenticationFilter.java | 20 +++++++++++++------ .../pro/beerpong/api/model/dao/Profile.java | 2 +- .../beerpong/api/service/GroupService.java | 3 ++- .../beerpong/api/service/ProfileService.java | 6 +++++- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java b/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java index 3129a273..45ebee3a 100644 --- a/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java +++ b/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java @@ -19,7 +19,12 @@ @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { - private static final String GROUPS_PATTERN = "/groups/{groupId}/**"; + private static final String GROUPS_PATTERN = "/groups/**"; + private static final String GROUP_ID_PATTERN = "/groups/{groupId}/**"; + private static final List NO_VALIDATION_ENDPOINTS = List.of( + "/groups", + "/groups/" + GroupController.USER_GROUPS_ENDPOINT + ); private final AuthService authService; private final AntPathMatcher pathMatcher = new AntPathMatcher(); @@ -45,9 +50,7 @@ protected void doFilterInternal(HttpServletRequest req, return; } - var groupId = extractGroupId(req); - - if (!groupId.equals(GroupController.USER_GROUPS_ENDPOINT) && !authService.hasAccessToGroup(user, groupId)) { + if (!isExcludedFromValidation(req) && !authService.hasAccessToGroup(user, extractGroupId(req))) { res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); res.getWriter().write("No access to this group!"); return; @@ -64,8 +67,13 @@ protected boolean shouldNotFilter(HttpServletRequest request) { return !pathMatcher.match(GROUPS_PATTERN, request.getRequestURI()); } + private boolean isExcludedFromValidation(HttpServletRequest request) { + return NO_VALIDATION_ENDPOINTS.stream().anyMatch(s -> pathMatcher.match(s, request.getRequestURI())); + } + private String extractGroupId(HttpServletRequest request) { - return pathMatcher.extractUriTemplateVariables(GROUPS_PATTERN, request.getRequestURI()) - .get("groupId"); + return (pathMatcher.match(GROUP_ID_PATTERN, request.getRequestURI()) ? + pathMatcher.extractUriTemplateVariables(GROUP_ID_PATTERN, request.getRequestURI()).get("groupId") : + ""); } } diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Profile.java b/api/src/main/java/pro/beerpong/api/model/dao/Profile.java index 63873328..fa79a51f 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/Profile.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/Profile.java @@ -19,7 +19,7 @@ public class Profile { @JoinColumn(name = "groupId") private Group group; - @OneToOne + @ManyToOne @JoinColumn(name = "createdBy") private GroupMember createdBy; } diff --git a/api/src/main/java/pro/beerpong/api/service/GroupService.java b/api/src/main/java/pro/beerpong/api/service/GroupService.java index 9212d677..fbb185bd 100644 --- a/api/src/main/java/pro/beerpong/api/service/GroupService.java +++ b/api/src/main/java/pro/beerpong/api/service/GroupService.java @@ -67,7 +67,8 @@ public GroupDto createGroup(GroupCreateDto groupCreateDto) { groupCreateDto.getProfileNames().forEach(s -> { var profileDto = new ProfileCreateDto(); profileDto.setName(s); - profileService.createProfile(finalGroup.getId(), profileDto); + //TODO use user here instead of null + profileService.createProfile(finalGroup.getId(), profileDto, null); }); ruleMoveService.createDefaultRuleMoves(group, season); diff --git a/api/src/main/java/pro/beerpong/api/service/ProfileService.java b/api/src/main/java/pro/beerpong/api/service/ProfileService.java index d5fc5389..0678b1b5 100644 --- a/api/src/main/java/pro/beerpong/api/service/ProfileService.java +++ b/api/src/main/java/pro/beerpong/api/service/ProfileService.java @@ -78,7 +78,11 @@ public ProfileDto createProfile(String groupId, ProfileCreateDto profileCreateDt var profile = profileMapper.profileCreateDtoToProfile(profileCreateDto); profile.setGroup(groupOptional.orElseThrow()); - profile.setCreatedBy(authService.memberByUser(user, groupId)); + + //TODO user should be non-null. waiting for createdBy at group creation! + if (user != null) { + profile.setCreatedBy(authService.memberByUser(user, groupId)); + } var savedProfile = profileRepository.save(profile); From c3aae0668a965c7ec9d2d40006d099075153efb5 Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 8 Jun 2025 13:34:40 +0200 Subject: [PATCH 14/90] implemented group member dto + creating group member on group creation --- .../beerpong/api/control/GroupController.java | 9 +++++-- ...{AssetMapper.java => AssetAuthMapper.java} | 8 +++++- .../pro/beerpong/api/mapping/GroupMapper.java | 4 +-- .../api/mapping/GroupMemberMapper.java | 15 +++++++++++ .../beerpong/api/mapping/ProfileMapper.java | 2 +- .../pro/beerpong/api/mapping/RuleMapper.java | 2 +- .../beerpong/api/mapping/SeasonMapper.java | 2 +- .../pro/beerpong/api/mapping/UserMapper.java | 2 ++ .../pro/beerpong/api/model/dto/GroupDto.java | 3 +-- .../api/model/dto/GroupMemberDto.java | 10 +++++++ .../pro/beerpong/api/model/dto/MatchDto.java | 3 +-- .../beerpong/api/model/dto/ProfileDto.java | 3 +-- .../pro/beerpong/api/model/dto/RuleDto.java | 3 +-- .../pro/beerpong/api/model/dto/SeasonDto.java | 3 +-- .../beerpong/api/service/AssetService.java | 8 +++--- .../pro/beerpong/api/service/AuthService.java | 26 +++++++++++++++++++ .../beerpong/api/service/GroupService.java | 22 +++++++++++++--- .../beerpong/api/service/ProfileService.java | 15 +++++------ 18 files changed, 105 insertions(+), 35 deletions(-) rename api/src/main/java/pro/beerpong/api/mapping/{AssetMapper.java => AssetAuthMapper.java} (72%) create mode 100644 api/src/main/java/pro/beerpong/api/mapping/GroupMemberMapper.java create mode 100644 api/src/main/java/pro/beerpong/api/model/dto/GroupMemberDto.java diff --git a/api/src/main/java/pro/beerpong/api/control/GroupController.java b/api/src/main/java/pro/beerpong/api/control/GroupController.java index 8a38a44a..e5bb7731 100644 --- a/api/src/main/java/pro/beerpong/api/control/GroupController.java +++ b/api/src/main/java/pro/beerpong/api/control/GroupController.java @@ -26,7 +26,12 @@ public GroupController(GroupService groupService, AuthService authService) { } @PostMapping - public ResponseEntity> createGroup(@RequestBody GroupCreateDto groupCreateDto) { + public ResponseEntity> createGroup(@RequestBody GroupCreateDto groupCreateDto, + @AuthenticationPrincipal UserDto user) { + if (user == null) { + return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER); + } + if (groupCreateDto.invalidName()) { return ResponseEnvelope.notOk(ErrorCodes.INVALID_GROUP_NAME); } @@ -35,7 +40,7 @@ public ResponseEntity> createGroup(@RequestBody Group return ResponseEnvelope.notOk(ErrorCodes.INVALID_GROUP_PROFILE_NAMES); } - var group = groupService.createGroup(groupCreateDto); + var group = groupService.createGroup(groupCreateDto, user); if (group == null) { return ResponseEnvelope.notOk(ErrorCodes.INVALID_GROUP_SPORT); diff --git a/api/src/main/java/pro/beerpong/api/mapping/AssetMapper.java b/api/src/main/java/pro/beerpong/api/mapping/AssetAuthMapper.java similarity index 72% rename from api/src/main/java/pro/beerpong/api/mapping/AssetMapper.java rename to api/src/main/java/pro/beerpong/api/mapping/AssetAuthMapper.java index 4f5f9ad6..a09a8bba 100644 --- a/api/src/main/java/pro/beerpong/api/mapping/AssetMapper.java +++ b/api/src/main/java/pro/beerpong/api/mapping/AssetAuthMapper.java @@ -6,16 +6,22 @@ import org.springframework.web.util.UriComponentsBuilder; import pro.beerpong.api.config.ApiProperties; import pro.beerpong.api.model.dao.Asset; +import pro.beerpong.api.model.dao.GroupMember; import pro.beerpong.api.model.dto.AssetMetadataDto; +import pro.beerpong.api.model.dto.GroupMemberDto; @Mapper(componentModel = "spring") -public abstract class AssetMapper { +public abstract class AssetAuthMapper { @Autowired private ApiProperties apiProperties; @Mapping(target = "url", expression = "java(generateUrl(asset))") public abstract AssetMetadataDto assetToAssetMetadataDto(Asset asset); + @Mapping(source = "group.id", target = "groupId") + @Mapping(source = "user.id", target = "userId") + public abstract GroupMemberDto groupMemberToGroupMemberDto(GroupMember groupMember); + protected String generateUrl(Asset asset) { return UriComponentsBuilder.fromHttpUrl(apiProperties.getApiBaseUrl()) .pathSegment("assets") diff --git a/api/src/main/java/pro/beerpong/api/mapping/GroupMapper.java b/api/src/main/java/pro/beerpong/api/mapping/GroupMapper.java index f1bea6af..80c97f34 100644 --- a/api/src/main/java/pro/beerpong/api/mapping/GroupMapper.java +++ b/api/src/main/java/pro/beerpong/api/mapping/GroupMapper.java @@ -2,15 +2,13 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.springframework.web.util.UriComponentsBuilder; import pro.beerpong.api.control.GroupPresetsController; -import pro.beerpong.api.model.dao.Asset; import pro.beerpong.api.model.dao.Group; import pro.beerpong.api.model.dto.GroupCreateDto; import pro.beerpong.api.model.dto.GroupDto; import pro.beerpong.api.model.dto.GroupPreset; -@Mapper(componentModel = "spring", uses = AssetMapper.class) +@Mapper(componentModel = "spring", uses = AssetAuthMapper.class) public abstract class GroupMapper { @Mapping(target = "sportPreset", expression = "java(fromPreset(groupDto))") public abstract Group groupDtoToGroup(GroupDto groupDto); diff --git a/api/src/main/java/pro/beerpong/api/mapping/GroupMemberMapper.java b/api/src/main/java/pro/beerpong/api/mapping/GroupMemberMapper.java new file mode 100644 index 00000000..2322fd92 --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/mapping/GroupMemberMapper.java @@ -0,0 +1,15 @@ +package pro.beerpong.api.mapping; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import pro.beerpong.api.model.dao.GroupMember; +import pro.beerpong.api.model.dao.Team; +import pro.beerpong.api.model.dto.GroupMemberDto; +import pro.beerpong.api.model.dto.TeamDto; + +@Mapper(componentModel = "spring") +public interface GroupMemberMapper { + @Mapping(source = "group.id", target = "groupId") + @Mapping(source = "user.id", target = "userId") + GroupMemberDto groupMemberToGroupMemberDto(GroupMember groupMember); +} diff --git a/api/src/main/java/pro/beerpong/api/mapping/ProfileMapper.java b/api/src/main/java/pro/beerpong/api/mapping/ProfileMapper.java index b0d1eb17..844134c9 100644 --- a/api/src/main/java/pro/beerpong/api/mapping/ProfileMapper.java +++ b/api/src/main/java/pro/beerpong/api/mapping/ProfileMapper.java @@ -6,7 +6,7 @@ import pro.beerpong.api.model.dto.ProfileCreateDto; import pro.beerpong.api.model.dto.ProfileDto; -@Mapper(componentModel = "spring", uses = AssetMapper.class) +@Mapper(componentModel = "spring", uses = AssetAuthMapper.class) public interface ProfileMapper { @Mapping(source = "groupId", target = "group.id") Profile profileDtoToProfile(ProfileDto profileDto); diff --git a/api/src/main/java/pro/beerpong/api/mapping/RuleMapper.java b/api/src/main/java/pro/beerpong/api/mapping/RuleMapper.java index 4aaa0087..b63db205 100644 --- a/api/src/main/java/pro/beerpong/api/mapping/RuleMapper.java +++ b/api/src/main/java/pro/beerpong/api/mapping/RuleMapper.java @@ -5,7 +5,7 @@ import pro.beerpong.api.model.dto.RuleCreateDto; import pro.beerpong.api.model.dto.RuleDto; -@Mapper(componentModel = "spring") +@Mapper(componentModel = "spring", uses = AssetAuthMapper.class) public interface RuleMapper { Rule ruleCreateDtoToRule(RuleCreateDto dto); diff --git a/api/src/main/java/pro/beerpong/api/mapping/SeasonMapper.java b/api/src/main/java/pro/beerpong/api/mapping/SeasonMapper.java index c9dd7bb4..61bfec45 100644 --- a/api/src/main/java/pro/beerpong/api/mapping/SeasonMapper.java +++ b/api/src/main/java/pro/beerpong/api/mapping/SeasonMapper.java @@ -4,7 +4,7 @@ import pro.beerpong.api.model.dao.Season; import pro.beerpong.api.model.dto.SeasonDto; -@Mapper(componentModel = "spring") +@Mapper(componentModel = "spring", uses = AssetAuthMapper.class) public interface SeasonMapper { SeasonDto seasonToSeasonDto(Season season); } \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/mapping/UserMapper.java b/api/src/main/java/pro/beerpong/api/mapping/UserMapper.java index 77496dfd..a6830bf0 100644 --- a/api/src/main/java/pro/beerpong/api/mapping/UserMapper.java +++ b/api/src/main/java/pro/beerpong/api/mapping/UserMapper.java @@ -10,4 +10,6 @@ @Mapper(componentModel = "spring") public interface UserMapper { UserDto userToUserDto(User user); + + User userDtoToUser(UserDto userDto); } diff --git a/api/src/main/java/pro/beerpong/api/model/dto/GroupDto.java b/api/src/main/java/pro/beerpong/api/model/dto/GroupDto.java index daced112..1142a35c 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/GroupDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/GroupDto.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; -import pro.beerpong.api.model.dao.GroupMember; import pro.beerpong.api.model.dao.Season; import java.time.ZonedDateTime; @@ -15,7 +14,7 @@ public class GroupDto { private Season activeSeason; @JsonInclude(JsonInclude.Include.NON_NULL) private AssetMetadataDto wallpaperAsset; - private GroupMember createdBy; + private GroupMemberDto createdBy; private ZonedDateTime createdAt; private GroupPreset sportPreset; private String customSportName; diff --git a/api/src/main/java/pro/beerpong/api/model/dto/GroupMemberDto.java b/api/src/main/java/pro/beerpong/api/model/dto/GroupMemberDto.java new file mode 100644 index 00000000..93b241aa --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/model/dto/GroupMemberDto.java @@ -0,0 +1,10 @@ +package pro.beerpong.api.model.dto; + +import lombok.Data; + +@Data +public class GroupMemberDto { + private String id; + private String groupId; + private String userId; +} diff --git a/api/src/main/java/pro/beerpong/api/model/dto/MatchDto.java b/api/src/main/java/pro/beerpong/api/model/dto/MatchDto.java index e70bd29f..36b086b7 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/MatchDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/MatchDto.java @@ -1,7 +1,6 @@ package pro.beerpong.api.model.dto; import lombok.Data; -import pro.beerpong.api.model.dao.GroupMember; import pro.beerpong.api.model.dao.Season; import java.time.ZonedDateTime; @@ -12,7 +11,7 @@ public class MatchDto { private String id; private ZonedDateTime date; private Season season; - private GroupMember createdBy; + private GroupMemberDto createdBy; private List teams; private List teamMembers; private List matchMoves; diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ProfileDto.java b/api/src/main/java/pro/beerpong/api/model/dto/ProfileDto.java index 633d1063..ee6df307 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ProfileDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ProfileDto.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; -import pro.beerpong.api.model.dao.GroupMember; @Data public class ProfileDto { @@ -11,5 +10,5 @@ public class ProfileDto { @JsonInclude(JsonInclude.Include.NON_NULL) private AssetMetadataDto avatarAsset; private String groupId; - private GroupMember createdBy; + private GroupMemberDto createdBy; } diff --git a/api/src/main/java/pro/beerpong/api/model/dto/RuleDto.java b/api/src/main/java/pro/beerpong/api/model/dto/RuleDto.java index 21a94a0c..2857bdc5 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/RuleDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/RuleDto.java @@ -1,7 +1,6 @@ package pro.beerpong.api.model.dto; import lombok.Data; -import pro.beerpong.api.model.dao.GroupMember; import pro.beerpong.api.model.dao.Season; @Data @@ -10,5 +9,5 @@ public class RuleDto { private String title; private String description; private Season season; - private GroupMember createdBy; + private GroupMemberDto createdBy; } \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/model/dto/SeasonDto.java b/api/src/main/java/pro/beerpong/api/model/dto/SeasonDto.java index a9c81432..b3fa1eca 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/SeasonDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/SeasonDto.java @@ -1,7 +1,6 @@ package pro.beerpong.api.model.dto; import lombok.Data; -import pro.beerpong.api.model.dao.GroupMember; import pro.beerpong.api.model.dao.SeasonSettings; import java.time.ZonedDateTime; @@ -14,5 +13,5 @@ public class SeasonDto { private ZonedDateTime endDate; private String groupId; private SeasonSettings seasonSettings; - private GroupMember createdBy; + private GroupMemberDto createdBy; } \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/service/AssetService.java b/api/src/main/java/pro/beerpong/api/service/AssetService.java index 2330502d..4a85dc8e 100644 --- a/api/src/main/java/pro/beerpong/api/service/AssetService.java +++ b/api/src/main/java/pro/beerpong/api/service/AssetService.java @@ -2,7 +2,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import pro.beerpong.api.mapping.AssetMapper; +import pro.beerpong.api.mapping.AssetAuthMapper; import pro.beerpong.api.model.dao.Asset; import pro.beerpong.api.model.dto.AssetMetadataDto; import pro.beerpong.api.repository.AssetRepository; @@ -12,7 +12,7 @@ @Service @RequiredArgsConstructor public class AssetService { - private final AssetMapper assetMapper; + private final AssetAuthMapper assetAuthMapper; private final AssetRepository assetRepository; public boolean assetExists(String assetId) { @@ -34,7 +34,7 @@ public byte[] fetchAsset(String assetId) { } public AssetMetadataDto getAssetMetadata(String assetId) { - return assetMapper.assetToAssetMetadataDto(assetRepository.findById(assetId).orElse(null)); + return assetAuthMapper.assetToAssetMetadataDto(assetRepository.findById(assetId).orElse(null)); } public AssetMetadataDto storeAsset(byte[] assetBinary, String mediaType) { @@ -43,6 +43,6 @@ public AssetMetadataDto storeAsset(byte[] assetBinary, String mediaType) { asset.setMediaType(mediaType); asset.setUploadedAt(ZonedDateTime.now()); - return assetMapper.assetToAssetMetadataDto(assetRepository.save(asset)); + return assetAuthMapper.assetToAssetMetadataDto(assetRepository.save(asset)); } } \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/service/AuthService.java b/api/src/main/java/pro/beerpong/api/service/AuthService.java index 12d9c7be..b8ba424f 100644 --- a/api/src/main/java/pro/beerpong/api/service/AuthService.java +++ b/api/src/main/java/pro/beerpong/api/service/AuthService.java @@ -6,6 +6,7 @@ import pro.beerpong.api.auth.JwtTokenProvider; import pro.beerpong.api.mapping.UserMapper; import pro.beerpong.api.model.dao.Device; +import pro.beerpong.api.model.dao.Group; import pro.beerpong.api.model.dao.GroupMember; import pro.beerpong.api.model.dao.User; import pro.beerpong.api.model.dto.AuthRefreshDto; @@ -14,6 +15,7 @@ import pro.beerpong.api.model.dto.UserDto; import pro.beerpong.api.repository.DeviceRepository; import pro.beerpong.api.repository.GroupMemberRepository; +import pro.beerpong.api.repository.GroupRepository; import pro.beerpong.api.repository.UserRepository; import pro.beerpong.api.util.TokenType; @@ -25,6 +27,7 @@ public class AuthService { private final DeviceRepository deviceRepository; private final UserMapper userMapper; private final GroupMemberRepository groupMemberRepository; + private final GroupRepository groupRepository; public AuthTokenDto registerDevice(AuthSignupDto dto) { if (dto.getDeviceId() == null || dto.getDeviceId().trim().isEmpty() || dto.getInstallationType() == null) { @@ -95,6 +98,29 @@ public GroupMember memberByUser(UserDto user, String groupId) { return groupMemberRepository.findByUserIdAndGroupId(user.getId(), groupId); } + @Transactional + public GroupMember buildFirstGroupMember(UserDto user) { + var groupMember = new GroupMember(); + groupMember.setUser(userMapper.userDtoToUser(user)); + + return groupMember; + } + + @Transactional + public GroupMember joinGroup(UserDto user, String groupId) { + var groupMember = new GroupMember(); + groupMember.setUser(userMapper.userDtoToUser(user)); + groupMember.setGroup(groupRepository.findById(groupId).orElse(null)); + + saveMember(groupMember); + + return groupMember; + } + + public GroupMember saveMember(GroupMember groupMember) { + return groupMemberRepository.save(groupMember); + } + @Transactional public void leaveGroup(UserDto user, String groupId) { groupMemberRepository.deleteByUserIdAndGroupId(user.getId(), groupId); diff --git a/api/src/main/java/pro/beerpong/api/service/GroupService.java b/api/src/main/java/pro/beerpong/api/service/GroupService.java index fbb185bd..f7703766 100644 --- a/api/src/main/java/pro/beerpong/api/service/GroupService.java +++ b/api/src/main/java/pro/beerpong/api/service/GroupService.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Service; import pro.beerpong.api.mapping.GroupMapper; import pro.beerpong.api.model.dao.Group; +import pro.beerpong.api.model.dao.GroupMember; import pro.beerpong.api.model.dao.Season; import pro.beerpong.api.model.dao.SeasonSettings; import pro.beerpong.api.model.dto.*; @@ -40,8 +41,9 @@ public class GroupService { private final RuleMoveService ruleMoveService; private final RuleService ruleService; private final GroupMemberRepository groupMemberRepository; + private final AuthService authService; - public GroupDto createGroup(GroupCreateDto groupCreateDto) { + public GroupDto createGroup(GroupCreateDto groupCreateDto, UserDto user) { Group group = groupMapper.groupCreateDtoToGroup(groupCreateDto); group.setInviteCode(generateRandomString(GROUP_INVITE_CODE_LENGTH)); group.setCreatedAt(ZonedDateTime.now()); @@ -53,6 +55,8 @@ public GroupDto createGroup(GroupCreateDto groupCreateDto) { return null; } + var groupMember = authService.buildFirstGroupMember(user); + var season = new Season(); season.setStartDate(ZonedDateTime.now()); season.setSeasonSettings(new SeasonSettings()); @@ -60,15 +64,23 @@ public GroupDto createGroup(GroupCreateDto groupCreateDto) { group.setActiveSeason(season); group = groupRepository.save(group); + groupMember.setGroup(group); + + groupMember = authService.saveMember(groupMember); + + group.setCreatedBy(groupMember); + group = groupRepository.save(group); + + season.setCreatedBy(groupMember); season.setGroupId(group.getId()); seasonRepository.save(season); Group finalGroup = group; + GroupMember finalGroupMember = groupMember; groupCreateDto.getProfileNames().forEach(s -> { var profileDto = new ProfileCreateDto(); profileDto.setName(s); - //TODO use user here instead of null - profileService.createProfile(finalGroup.getId(), profileDto, null); + profileService.createProfile(finalGroup.getId(), profileDto, finalGroupMember); }); ruleMoveService.createDefaultRuleMoves(group, season); @@ -109,6 +121,10 @@ public GroupDto getRawGroupById(String id) { .orElse(null); } + public Group getDaoById(String id) { + return groupRepository.findById(id).orElse(null); + } + public GroupDto updateGroup(String id, GroupCreateDto groupCreateDto) { return groupRepository.findById(id) .map(existingGroup -> { diff --git a/api/src/main/java/pro/beerpong/api/service/ProfileService.java b/api/src/main/java/pro/beerpong/api/service/ProfileService.java index 0678b1b5..76649859 100644 --- a/api/src/main/java/pro/beerpong/api/service/ProfileService.java +++ b/api/src/main/java/pro/beerpong/api/service/ProfileService.java @@ -5,6 +5,7 @@ import org.springframework.stereotype.Service; import pro.beerpong.api.mapping.GroupMapper; import pro.beerpong.api.mapping.ProfileMapper; +import pro.beerpong.api.model.dao.GroupMember; import pro.beerpong.api.model.dao.Profile; import pro.beerpong.api.model.dao.Season; import pro.beerpong.api.model.dto.ProfileCreateDto; @@ -65,24 +66,20 @@ public ProfileCreatedDto createPlayer(String groupId, ProfileCreateDto dto, User return new ProfileCreatedDto(existing, false, (lastPlayer != null ? lastPlayer.getSeason().getId() : null)); } } else { - return new ProfileCreatedDto(this.createProfile(groupId, dto, user), false, null); + return new ProfileCreatedDto(this.createProfile(groupId, dto, authService.memberByUser(user, groupId)), false, null); } } - public ProfileDto createProfile(String groupId, ProfileCreateDto profileCreateDto, UserDto user) { - return this.createProfile(groupId, profileCreateDto, true, user); + public ProfileDto createProfile(String groupId, ProfileCreateDto profileCreateDto, GroupMember groupMember) { + return this.createProfile(groupId, profileCreateDto, true, groupMember); } - public ProfileDto createProfile(String groupId, ProfileCreateDto profileCreateDto, boolean createPlayer, UserDto user) { + public ProfileDto createProfile(String groupId, ProfileCreateDto profileCreateDto, boolean createPlayer, GroupMember groupMember) { var groupOptional = groupRepository.findById(groupId); var profile = profileMapper.profileCreateDtoToProfile(profileCreateDto); profile.setGroup(groupOptional.orElseThrow()); - - //TODO user should be non-null. waiting for createdBy at group creation! - if (user != null) { - profile.setCreatedBy(authService.memberByUser(user, groupId)); - } + profile.setCreatedBy(groupMember); var savedProfile = profileRepository.save(profile); From 9b121db0769ec3ad6467eeef9865be9294b436ee Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 8 Jun 2025 13:37:37 +0200 Subject: [PATCH 15/90] add createdBy to rules --- .../beerpong/api/control/RuleController.java | 17 +++++++++++------ .../pro/beerpong/api/service/RuleService.java | 10 ++++++++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/control/RuleController.java b/api/src/main/java/pro/beerpong/api/control/RuleController.java index a52615b2..02caab4e 100644 --- a/api/src/main/java/pro/beerpong/api/control/RuleController.java +++ b/api/src/main/java/pro/beerpong/api/control/RuleController.java @@ -3,11 +3,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; -import pro.beerpong.api.model.dto.ErrorCodes; -import pro.beerpong.api.model.dto.ResponseEnvelope; -import pro.beerpong.api.model.dto.RuleCreateDto; -import pro.beerpong.api.model.dto.RuleDto; +import pro.beerpong.api.model.dto.*; import pro.beerpong.api.service.RuleService; import pro.beerpong.api.service.SeasonService; import pro.beerpong.api.sockets.SocketEvent; @@ -46,7 +44,14 @@ public ResponseEntity>> getRules(@PathVariable St } @PutMapping - public ResponseEntity>> writeRules(@PathVariable String groupId, @PathVariable String seasonId, @RequestBody List rules) { + public ResponseEntity>> writeRules(@PathVariable String groupId, + @PathVariable String seasonId, + @RequestBody List rules, + @AuthenticationPrincipal UserDto user) { + if (user == null) { + return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER); + } + var pair = seasonService.getSeasonAndGroup(groupId, seasonId); if (pair.getFirst() == null) { @@ -59,7 +64,7 @@ public ResponseEntity>> writeRules(@PathVariable return ResponseEnvelope.notOk(ErrorCodes.SEASON_ALREADY_ENDED); } - var ruleDtos = ruleService.writeRules(groupId, pair.getSecond(), rules); + var ruleDtos = ruleService.writeRules(groupId, pair.getSecond(), rules, user); if (ruleDtos != null) { subscriptionHandler.callEvent(new SocketEvent<>(SocketEventData.RULES_WRITE, groupId, ruleDtos.toArray(new RuleDto[0]))); diff --git a/api/src/main/java/pro/beerpong/api/service/RuleService.java b/api/src/main/java/pro/beerpong/api/service/RuleService.java index 62e03841..510258b7 100644 --- a/api/src/main/java/pro/beerpong/api/service/RuleService.java +++ b/api/src/main/java/pro/beerpong/api/service/RuleService.java @@ -8,6 +8,7 @@ import pro.beerpong.api.model.dao.Season; import pro.beerpong.api.model.dto.RuleCreateDto; import pro.beerpong.api.model.dto.RuleDto; +import pro.beerpong.api.model.dto.UserDto; import pro.beerpong.api.repository.RuleRepository; import pro.beerpong.api.sockets.SocketEvent; import pro.beerpong.api.sockets.SocketEventData; @@ -43,22 +44,27 @@ public class RuleService { private final RuleRepository ruleRepository; private final RuleMapper ruleMapper; + private final AuthService authService; @Autowired - public RuleService(SubscriptionHandler subscriptionHandler, RuleRepository matchRepository, RuleMapper ruleMapper) { + public RuleService(SubscriptionHandler subscriptionHandler, RuleRepository matchRepository, RuleMapper ruleMapper, AuthService authService) { this.subscriptionHandler = subscriptionHandler; this.ruleRepository = matchRepository; this.ruleMapper = ruleMapper; + this.authService = authService; } @Transactional - public List writeRules(String groupId, Season season, List rules) { + public List writeRules(String groupId, Season season, List rules, UserDto user) { ruleRepository.deleteBySeasonId(season.getId()); + var createdBy = authService.memberByUser(user, groupId); + return rules.stream() .map(dto -> { var rule = ruleMapper.ruleCreateDtoToRule(dto); rule.setSeason(season); + rule.setCreatedBy(createdBy); return rule; }) .filter(dto -> dto.getSeason().getId().equals(season.getId()) && From 26de012e9b77381ac43401a41023732f1de62a4d Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 8 Jun 2025 13:39:02 +0200 Subject: [PATCH 16/90] copying createdBy from old season --- .../pro/beerpong/api/service/GroupService.java | 2 +- .../pro/beerpong/api/service/RuleService.java | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/service/GroupService.java b/api/src/main/java/pro/beerpong/api/service/GroupService.java index f7703766..141a3483 100644 --- a/api/src/main/java/pro/beerpong/api/service/GroupService.java +++ b/api/src/main/java/pro/beerpong/api/service/GroupService.java @@ -84,7 +84,7 @@ public GroupDto createGroup(GroupCreateDto groupCreateDto, UserDto user) { }); ruleMoveService.createDefaultRuleMoves(group, season); - ruleService.createDefaultRules(season); + ruleService.createDefaultRules(season, groupMember); return withStats(groupMapper.groupToGroupDto(group)); } diff --git a/api/src/main/java/pro/beerpong/api/service/RuleService.java b/api/src/main/java/pro/beerpong/api/service/RuleService.java index 510258b7..5aa4eaf3 100644 --- a/api/src/main/java/pro/beerpong/api/service/RuleService.java +++ b/api/src/main/java/pro/beerpong/api/service/RuleService.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import pro.beerpong.api.mapping.RuleMapper; +import pro.beerpong.api.model.dao.GroupMember; import pro.beerpong.api.model.dao.Rule; import pro.beerpong.api.model.dao.Season; import pro.beerpong.api.model.dto.RuleCreateDto; @@ -79,13 +80,14 @@ public void copyRulesFromOldSeason(Season oldSeason, Season newSeason) { } ruleRepository.findBySeasonId(oldSeason.getId()).forEach(oldRule -> { - var ruleMove = new Rule(); + var rule = new Rule(); - ruleMove.setTitle(oldRule.getTitle()); - ruleMove.setDescription(oldRule.getDescription()); - ruleMove.setSeason(newSeason); + rule.setTitle(oldRule.getTitle()); + rule.setDescription(oldRule.getDescription()); + rule.setSeason(newSeason); + rule.setCreatedBy(oldRule.getCreatedBy()); - ruleRepository.save(ruleMove); + ruleRepository.save(rule); }); } @@ -96,11 +98,12 @@ public List getAllRules(String seasonId) { .toList(); } - public void createDefaultRules(Season season) { + public void createDefaultRules(Season season, GroupMember createdBy) { DEFAULT_RULES.stream() .map(rule -> { var rle = rule.clone(); rle.setSeason(season); + rule.setCreatedBy(createdBy); return rle; }) .forEach(ruleRepository::save); From 0f2acfd1b3fb633dce9ae8c50aacd54c72fb525f Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 8 Jun 2025 13:42:27 +0200 Subject: [PATCH 17/90] saving createdBy for seasons --- .../pro/beerpong/api/control/SeasonController.java | 11 +++++++++-- .../java/pro/beerpong/api/service/SeasonService.java | 7 +++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/control/SeasonController.java b/api/src/main/java/pro/beerpong/api/control/SeasonController.java index 59033162..5cdc4cde 100644 --- a/api/src/main/java/pro/beerpong/api/control/SeasonController.java +++ b/api/src/main/java/pro/beerpong/api/control/SeasonController.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import pro.beerpong.api.model.dto.*; import pro.beerpong.api.service.SeasonService; @@ -20,7 +21,13 @@ public SeasonController(SeasonService seasonService) { } @PutMapping("/active-season") - public ResponseEntity> startNewSeason(@PathVariable String groupId, @RequestBody SeasonCreateDto dto) { + public ResponseEntity> startNewSeason(@PathVariable String groupId, + @RequestBody SeasonCreateDto dto, + @AuthenticationPrincipal UserDto user) { + if (user == null) { + return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER); + } + if (groupId == null || groupId.trim().isEmpty()) { return ResponseEnvelope.notOk(ErrorCodes.INVALID_GROUP_ID); } @@ -33,7 +40,7 @@ public ResponseEntity> startNewSeason(@PathVariable return ResponseEnvelope.notOk(ErrorCodes.INVALID_RULE_MOVES); } - var season = seasonService.startNewSeason(dto, groupId); + var season = seasonService.startNewSeason(dto, groupId, user); if (season != null) { return ResponseEnvelope.ok(season); diff --git a/api/src/main/java/pro/beerpong/api/service/SeasonService.java b/api/src/main/java/pro/beerpong/api/service/SeasonService.java index f8aee932..9f20862d 100644 --- a/api/src/main/java/pro/beerpong/api/service/SeasonService.java +++ b/api/src/main/java/pro/beerpong/api/service/SeasonService.java @@ -37,6 +37,7 @@ public class SeasonService { private final ProfileMapper profileMapper; private final PlayerStatisticsMapper playerStatisticsMapper; private final GroupService groupService; + private final AuthService authService; @Autowired public SeasonService(SubscriptionHandler subscriptionHandler, @@ -45,7 +46,7 @@ public SeasonService(SubscriptionHandler subscriptionHandler, PlayerService playerService, RuleMoveService ruleMoveService, RuleService ruleService, - SeasonMapper seasonMapper, LeaderboardService leaderboardService, GroupMapper groupMapper, PlayerMapper playerMapper, PlayerStatisticsRepository playerStatisticsRepository, PlayerRepository playerRepository, ProfileMapper profileMapper, PlayerStatisticsMapper playerStatisticsMapper, GroupService groupService) { + SeasonMapper seasonMapper, LeaderboardService leaderboardService, GroupMapper groupMapper, PlayerMapper playerMapper, PlayerStatisticsRepository playerStatisticsRepository, PlayerRepository playerRepository, ProfileMapper profileMapper, PlayerStatisticsMapper playerStatisticsMapper, GroupService groupService, AuthService authService) { this.subscriptionHandler = subscriptionHandler; this.seasonRepository = seasonRepository; this.groupRepository = groupRepository; @@ -61,9 +62,10 @@ public SeasonService(SubscriptionHandler subscriptionHandler, this.profileMapper = profileMapper; this.playerStatisticsMapper = playerStatisticsMapper; this.groupService = groupService; + this.authService = authService; } - public SeasonDto startNewSeason(SeasonCreateDto dto, String groupId) { + public SeasonDto startNewSeason(SeasonCreateDto dto, String groupId, UserDto user) { var groupOptional = groupRepository.findById(groupId); if (groupOptional.isEmpty()) { @@ -77,6 +79,7 @@ public SeasonDto startNewSeason(SeasonCreateDto dto, String groupId) { newSeason.setStartDate(ZonedDateTime.now()); newSeason.setGroupId(groupOptional.get().getId()); newSeason.setSeasonSettings(new SeasonSettings()); + newSeason.setCreatedBy(authService.memberByUser(user, groupId)); if (oldSeason != null && oldSeason.getSeasonSettings() != null) { newSeason.getSeasonSettings().setMaxTeamSize(oldSeason.getSeasonSettings().getMaxTeamSize()); From fc65709ce5eef10485dbcfcf24b48cc6d0d42343 Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 8 Jun 2025 13:45:17 +0200 Subject: [PATCH 18/90] saving for matches --- .../main/java/pro/beerpong/api/service/MatchService.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/pro/beerpong/api/service/MatchService.java b/api/src/main/java/pro/beerpong/api/service/MatchService.java index 98cb2bd6..a7ed79b8 100644 --- a/api/src/main/java/pro/beerpong/api/service/MatchService.java +++ b/api/src/main/java/pro/beerpong/api/service/MatchService.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import pro.beerpong.api.mapping.GroupMemberMapper; import pro.beerpong.api.mapping.MatchMoveMapper; import pro.beerpong.api.mapping.PlayerMapper; import pro.beerpong.api.model.dao.*; @@ -50,6 +51,7 @@ public class MatchService { private final RuleMoveService ruleMoveService; private final PlayerMapper playerMapper; private final AuthService authService; + private final GroupMemberMapper groupMemberMapper; @Autowired public MatchService(SubscriptionHandler subscriptionHandler, @@ -62,7 +64,7 @@ public MatchService(SubscriptionHandler subscriptionHandler, MatchMoveRepository matchMoveRepository, RuleMoveRepository ruleMoveRepository, MatchMoveMapper matchMoveMapper, - TeamService teamService, SeasonRepository seasonRepository, RuleMoveService ruleMoveService, PlayerMapper playerMapper, AuthService authService) { + TeamService teamService, SeasonRepository seasonRepository, RuleMoveService ruleMoveService, PlayerMapper playerMapper, AuthService authService, GroupMemberMapper groupMemberMapper) { this.subscriptionHandler = subscriptionHandler; this.matchRepository = matchRepository; @@ -80,6 +82,7 @@ public MatchService(SubscriptionHandler subscriptionHandler, this.ruleMoveService = ruleMoveService; this.playerMapper = playerMapper; this.authService = authService; + this.groupMemberMapper = groupMemberMapper; } public boolean invalidCreateDto(String groupId, String seasonId, MatchCreateDto dto) { @@ -364,6 +367,7 @@ private MatchDto matchToEmptyMatchDto(Match match) { dto.setId(match.getId()); dto.setDate(match.getDate()); dto.setSeason(match.getSeason()); + dto.setCreatedBy(groupMemberMapper.groupMemberToGroupMemberDto(match.getCreatedBy())); return dto; } @@ -374,6 +378,7 @@ private MatchDto matchToMatchDto(Match match) { dto.setId(match.getId()); dto.setDate(match.getDate()); dto.setSeason(match.getSeason()); + dto.setCreatedBy(groupMemberMapper.groupMemberToGroupMemberDto(match.getCreatedBy())); loadMatchInfo(match, dto); From 13bb72c3ab07bbaf547be58b7def3d840f9d9fab Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 8 Jun 2025 13:52:24 +0200 Subject: [PATCH 19/90] using seasondto instead of season dao --- .../java/pro/beerpong/api/model/dto/GroupDto.java | 2 +- .../java/pro/beerpong/api/model/dto/MatchDto.java | 3 +-- .../pro/beerpong/api/model/dto/MatchOverviewDto.java | 2 +- .../main/java/pro/beerpong/api/model/dto/RuleDto.java | 3 +-- .../java/pro/beerpong/api/model/dto/RuleMoveDto.java | 2 +- .../java/pro/beerpong/api/service/MatchService.java | 11 +++++++---- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/model/dto/GroupDto.java b/api/src/main/java/pro/beerpong/api/model/dto/GroupDto.java index 1142a35c..796b33a0 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/GroupDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/GroupDto.java @@ -11,7 +11,7 @@ public class GroupDto { private String id; private String name; private String inviteCode; - private Season activeSeason; + private SeasonDto activeSeason; @JsonInclude(JsonInclude.Include.NON_NULL) private AssetMetadataDto wallpaperAsset; private GroupMemberDto createdBy; diff --git a/api/src/main/java/pro/beerpong/api/model/dto/MatchDto.java b/api/src/main/java/pro/beerpong/api/model/dto/MatchDto.java index 36b086b7..d376ab66 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/MatchDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/MatchDto.java @@ -1,7 +1,6 @@ package pro.beerpong.api.model.dto; import lombok.Data; -import pro.beerpong.api.model.dao.Season; import java.time.ZonedDateTime; import java.util.List; @@ -10,7 +9,7 @@ public class MatchDto { private String id; private ZonedDateTime date; - private Season season; + private SeasonDto season; private GroupMemberDto createdBy; private List teams; private List teamMembers; diff --git a/api/src/main/java/pro/beerpong/api/model/dto/MatchOverviewDto.java b/api/src/main/java/pro/beerpong/api/model/dto/MatchOverviewDto.java index f047bee3..3e471c71 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/MatchOverviewDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/MatchOverviewDto.java @@ -10,7 +10,7 @@ public class MatchOverviewDto { private String id; private ZonedDateTime date; - private Season season; + private SeasonDto season; private MatchOverviewTeamDto blueTeam; private MatchOverviewTeamDto redTeam; diff --git a/api/src/main/java/pro/beerpong/api/model/dto/RuleDto.java b/api/src/main/java/pro/beerpong/api/model/dto/RuleDto.java index 2857bdc5..9c0657bf 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/RuleDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/RuleDto.java @@ -1,13 +1,12 @@ package pro.beerpong.api.model.dto; import lombok.Data; -import pro.beerpong.api.model.dao.Season; @Data public class RuleDto { private String id; private String title; private String description; - private Season season; + private SeasonDto season; private GroupMemberDto createdBy; } \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/model/dto/RuleMoveDto.java b/api/src/main/java/pro/beerpong/api/model/dto/RuleMoveDto.java index 5978e754..e409253a 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/RuleMoveDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/RuleMoveDto.java @@ -10,5 +10,5 @@ public class RuleMoveDto { private int pointsForTeam; private int pointsForScorer; private boolean finishingMove; - private Season season; + private SeasonDto season; } \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/service/MatchService.java b/api/src/main/java/pro/beerpong/api/service/MatchService.java index a7ed79b8..ace8c699 100644 --- a/api/src/main/java/pro/beerpong/api/service/MatchService.java +++ b/api/src/main/java/pro/beerpong/api/service/MatchService.java @@ -7,6 +7,7 @@ import pro.beerpong.api.mapping.GroupMemberMapper; import pro.beerpong.api.mapping.MatchMoveMapper; import pro.beerpong.api.mapping.PlayerMapper; +import pro.beerpong.api.mapping.SeasonMapper; import pro.beerpong.api.model.dao.*; import pro.beerpong.api.model.dto.*; import pro.beerpong.api.repository.*; @@ -52,6 +53,7 @@ public class MatchService { private final PlayerMapper playerMapper; private final AuthService authService; private final GroupMemberMapper groupMemberMapper; + private final SeasonMapper seasonMapper; @Autowired public MatchService(SubscriptionHandler subscriptionHandler, @@ -64,7 +66,7 @@ public MatchService(SubscriptionHandler subscriptionHandler, MatchMoveRepository matchMoveRepository, RuleMoveRepository ruleMoveRepository, MatchMoveMapper matchMoveMapper, - TeamService teamService, SeasonRepository seasonRepository, RuleMoveService ruleMoveService, PlayerMapper playerMapper, AuthService authService, GroupMemberMapper groupMemberMapper) { + TeamService teamService, SeasonRepository seasonRepository, RuleMoveService ruleMoveService, PlayerMapper playerMapper, AuthService authService, GroupMemberMapper groupMemberMapper, SeasonMapper seasonMapper) { this.subscriptionHandler = subscriptionHandler; this.matchRepository = matchRepository; @@ -83,6 +85,7 @@ public MatchService(SubscriptionHandler subscriptionHandler, this.playerMapper = playerMapper; this.authService = authService; this.groupMemberMapper = groupMemberMapper; + this.seasonMapper = seasonMapper; } public boolean invalidCreateDto(String groupId, String seasonId, MatchCreateDto dto) { @@ -202,7 +205,7 @@ public long numOfMatchesInPastSeasons(GroupDto group) { .reduce(0L, Long::sum); } - public Stream streamAllMatchesToday(GroupDto group, Season season) { + public Stream streamAllMatchesToday(GroupDto group, SeasonDto season) { var now = ZonedDateTime.now(); Predicate predicate = switch (season.getSeasonSettings().getDailyLeaderboard()) { @@ -366,7 +369,7 @@ private MatchDto matchToEmptyMatchDto(Match match) { dto.setId(match.getId()); dto.setDate(match.getDate()); - dto.setSeason(match.getSeason()); + dto.setSeason(seasonMapper.seasonToSeasonDto(match.getSeason())); dto.setCreatedBy(groupMemberMapper.groupMemberToGroupMemberDto(match.getCreatedBy())); return dto; @@ -377,7 +380,7 @@ private MatchDto matchToMatchDto(Match match) { dto.setId(match.getId()); dto.setDate(match.getDate()); - dto.setSeason(match.getSeason()); + dto.setSeason(seasonMapper.seasonToSeasonDto(match.getSeason())); dto.setCreatedBy(groupMemberMapper.groupMemberToGroupMemberDto(match.getCreatedBy())); loadMatchInfo(match, dto); From 5409fa68a9590c0a6cdce5f4ee43377490f70b99 Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 8 Jun 2025 13:59:04 +0200 Subject: [PATCH 20/90] use many-to-one relation --- api/src/main/java/pro/beerpong/api/model/dao/Group.java | 2 +- api/src/main/java/pro/beerpong/api/model/dao/Match.java | 2 +- api/src/main/java/pro/beerpong/api/model/dao/Rule.java | 2 +- api/src/main/java/pro/beerpong/api/model/dao/Season.java | 2 +- api/src/main/java/pro/beerpong/api/service/RuleService.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Group.java b/api/src/main/java/pro/beerpong/api/model/dao/Group.java index 32bf40cc..057c49ce 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/Group.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/Group.java @@ -24,7 +24,7 @@ public class Group { private Asset wallpaperAsset; @OneToMany(mappedBy = "group") private List members; - @OneToOne + @ManyToOne @JoinColumn(name = "createdBy") private GroupMember createdBy; private ZonedDateTime createdAt; diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Match.java b/api/src/main/java/pro/beerpong/api/model/dao/Match.java index 26249d61..3bcd3ebc 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/Match.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/Match.java @@ -22,7 +22,7 @@ public class Match { @OneToMany(mappedBy = "match") private List teams; - @OneToOne + @ManyToOne @JoinColumn(name = "createdBy") private GroupMember createdBy; } diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Rule.java b/api/src/main/java/pro/beerpong/api/model/dao/Rule.java index dbfe8a2b..052e1a36 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/Rule.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/Rule.java @@ -20,7 +20,7 @@ public class Rule implements Cloneable { @JoinColumn(name = "seasonId") private Season season; - @OneToOne + @ManyToOne @JoinColumn(name = "createdBy") private GroupMember createdBy; diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Season.java b/api/src/main/java/pro/beerpong/api/model/dao/Season.java index 51da9f2e..bbbdd48f 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/Season.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/Season.java @@ -24,7 +24,7 @@ public class Season { @JoinColumn private SeasonSettings seasonSettings; - @OneToOne + @ManyToOne @JoinColumn(name = "createdBy") private GroupMember createdBy; } diff --git a/api/src/main/java/pro/beerpong/api/service/RuleService.java b/api/src/main/java/pro/beerpong/api/service/RuleService.java index 5aa4eaf3..7941d315 100644 --- a/api/src/main/java/pro/beerpong/api/service/RuleService.java +++ b/api/src/main/java/pro/beerpong/api/service/RuleService.java @@ -103,7 +103,7 @@ public void createDefaultRules(Season season, GroupMember createdBy) { .map(rule -> { var rle = rule.clone(); rle.setSeason(season); - rule.setCreatedBy(createdBy); + rle.setCreatedBy(createdBy); return rle; }) .forEach(ruleRepository::save); From 9780bdb38f7a0d8bc1a7f3c932d91e67772b3abd Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 8 Jun 2025 14:14:16 +0200 Subject: [PATCH 21/90] soft delete group members --- .../beerpong/api/control/GroupController.java | 8 +++-- .../beerpong/api/model/dao/GroupMember.java | 1 + .../beerpong/api/model/dto/ErrorCodes.java | 1 + .../api/model/dto/GroupMemberDto.java | 1 + .../pro/beerpong/api/model/dto/UserDto.java | 1 + .../pro/beerpong/api/service/AuthService.java | 30 +++++++++++++++++-- 6 files changed, 36 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/control/GroupController.java b/api/src/main/java/pro/beerpong/api/control/GroupController.java index e5bb7731..a7013807 100644 --- a/api/src/main/java/pro/beerpong/api/control/GroupController.java +++ b/api/src/main/java/pro/beerpong/api/control/GroupController.java @@ -118,8 +118,10 @@ public ResponseEntity> leaveGroup(@PathVariable String return ResponseEnvelope.notOk(ErrorCodes.INVALID_GROUP_ID); } - authService.leaveGroup(user, id); - - return ResponseEnvelope.ok("OK"); + if (authService.leaveGroup(user, id)) { + return ResponseEnvelope.ok("OK"); + } else { + return ResponseEnvelope.notOk(ErrorCodes.AUTH_USER_NOT_IN_GROUP); + } } } diff --git a/api/src/main/java/pro/beerpong/api/model/dao/GroupMember.java b/api/src/main/java/pro/beerpong/api/model/dao/GroupMember.java index bb3a779a..741678c5 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/GroupMember.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/GroupMember.java @@ -11,6 +11,7 @@ public class GroupMember { @Id @GeneratedValue(strategy = GenerationType.UUID) private String id; + private boolean active; @ManyToOne @JoinColumn(name = "group_id") private Group group; diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java index 56b7ab54..f1c9ef86 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java @@ -54,6 +54,7 @@ public enum ErrorCodes { AUTH_REGISTER_INVALID_DTO(HttpStatus.BAD_REQUEST, "authRegisterInvalidDto", "The installationType or deviceId is invalid!"), AUTH_REFRESH_INVALID_DTO(HttpStatus.BAD_REQUEST, "authRefreshInvalidDto", "The refreshToken has to be non-null and non-empty!"), AUTH_INVALID_USER(HttpStatus.UNAUTHORIZED, "authInvalidUser", "The user from the access token has to be non-null!"), + AUTH_USER_NOT_IN_GROUP(HttpStatus.UNAUTHORIZED, "authUserNotInGroup", "The user is not in this group!"), AUTH_REFRESH_INVALID_TOKEN(HttpStatus.BAD_REQUEST, "authRefreshInvalidToken", "The refreshToken is no refresh-token, invalid or the subject-userId is invalid!"); private final HttpStatus httpStatus; diff --git a/api/src/main/java/pro/beerpong/api/model/dto/GroupMemberDto.java b/api/src/main/java/pro/beerpong/api/model/dto/GroupMemberDto.java index 93b241aa..fca15a01 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/GroupMemberDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/GroupMemberDto.java @@ -5,6 +5,7 @@ @Data public class GroupMemberDto { private String id; + private boolean active; private String groupId; private String userId; } diff --git a/api/src/main/java/pro/beerpong/api/model/dto/UserDto.java b/api/src/main/java/pro/beerpong/api/model/dto/UserDto.java index 34d607fe..3d63c534 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/UserDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/UserDto.java @@ -5,4 +5,5 @@ @Data public class UserDto { private String id; + private boolean active; } \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/service/AuthService.java b/api/src/main/java/pro/beerpong/api/service/AuthService.java index b8ba424f..e3ff46ff 100644 --- a/api/src/main/java/pro/beerpong/api/service/AuthService.java +++ b/api/src/main/java/pro/beerpong/api/service/AuthService.java @@ -91,7 +91,9 @@ public UserDto userFromAccessToken(String token) { } public boolean hasAccessToGroup(UserDto user, String groupId) { - return groupMemberRepository.existsByUserIdAndGroupId(user.getId(), groupId); + var existing = groupMemberRepository.findByUserIdAndGroupId(user.getId(), groupId); + + return existing != null && existing.isActive(); } public GroupMember memberByUser(UserDto user, String groupId) { @@ -102,15 +104,29 @@ public GroupMember memberByUser(UserDto user, String groupId) { public GroupMember buildFirstGroupMember(UserDto user) { var groupMember = new GroupMember(); groupMember.setUser(userMapper.userDtoToUser(user)); + groupMember.setActive(true); return groupMember; } @Transactional public GroupMember joinGroup(UserDto user, String groupId) { + var existing = groupMemberRepository.findByUserIdAndGroupId(user.getId(), groupId); + + if (existing != null) { + if (existing.isActive()) { + return null; + } else { + existing.setActive(true); + + return groupMemberRepository.save(existing); + } + } + var groupMember = new GroupMember(); groupMember.setUser(userMapper.userDtoToUser(user)); groupMember.setGroup(groupRepository.findById(groupId).orElse(null)); + groupMember.setActive(true); saveMember(groupMember); @@ -122,8 +138,16 @@ public GroupMember saveMember(GroupMember groupMember) { } @Transactional - public void leaveGroup(UserDto user, String groupId) { - groupMemberRepository.deleteByUserIdAndGroupId(user.getId(), groupId); + public boolean leaveGroup(UserDto user, String groupId) { + var groupMember = groupMemberRepository.findByUserIdAndGroupId(user.getId(), groupId); + + if (groupMember != null) { + groupMember.setActive(false); + + saveMember(groupMember); + return true; + } + return false; } private AuthTokenDto buildDto(String token, TokenType type) { From 1950088bfa5d0c7f95415742a613ebfb69d8ca11 Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 8 Jun 2025 14:22:15 +0200 Subject: [PATCH 22/90] join group endpoint --- .../api/auth/JwtAuthenticationFilter.java | 3 ++- .../beerpong/api/control/GroupController.java | 20 +++++++++++++++++++ .../beerpong/api/model/dto/ErrorCodes.java | 1 + 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java b/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java index 45ebee3a..3e44b701 100644 --- a/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java +++ b/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java @@ -23,7 +23,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private static final String GROUP_ID_PATTERN = "/groups/{groupId}/**"; private static final List NO_VALIDATION_ENDPOINTS = List.of( "/groups", - "/groups/" + GroupController.USER_GROUPS_ENDPOINT + "/groups/" + GroupController.USER_GROUPS_ENDPOINT, + "/groups/{groupId}/" + GroupController.JOIN_GROUP_ENDPOINT ); private final AuthService authService; diff --git a/api/src/main/java/pro/beerpong/api/control/GroupController.java b/api/src/main/java/pro/beerpong/api/control/GroupController.java index a7013807..14842116 100644 --- a/api/src/main/java/pro/beerpong/api/control/GroupController.java +++ b/api/src/main/java/pro/beerpong/api/control/GroupController.java @@ -15,6 +15,7 @@ @RequestMapping("/groups") public class GroupController { public static final String USER_GROUPS_ENDPOINT = "user"; + public static final String JOIN_GROUP_ENDPOINT = "join"; private final GroupService groupService; private final AuthService authService; @@ -108,6 +109,25 @@ public ResponseEntity> updateGroup(@PathVariable Stri } } + @PostMapping("/{id}/" + JOIN_GROUP_ENDPOINT) + public ResponseEntity> joinGroup(@PathVariable String id, @AuthenticationPrincipal UserDto user) { + if (user == null) { + return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER); + } + + if (id == null || id.trim().isEmpty()) { + return ResponseEnvelope.notOk(ErrorCodes.INVALID_GROUP_ID); + } + + var groupMember = authService.joinGroup(user, id); + + if (groupMember != null) { + return ResponseEnvelope.ok("OK"); + } else { + return ResponseEnvelope.notOk(ErrorCodes.GROUP_ALREADY_IN_GROUP); + } + } + @PostMapping("/{id}/leave") public ResponseEntity> leaveGroup(@PathVariable String id, @AuthenticationPrincipal UserDto user) { if (user == null) { diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java index f1c9ef86..17d09052 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java @@ -12,6 +12,7 @@ public enum ErrorCodes { GROUP_NOT_FOUND(HttpStatus.NOT_FOUND, "groupNotFound", "The requested group could not be found!"), GROUP_INVITE_NOT_FOUND(HttpStatus.NOT_FOUND, "groupInviteNotFound", "No group with the provided invite code could be found!"), GROUP_INVITE_CODE_NOT_PROVIDED(HttpStatus.BAD_REQUEST, "groupInviteCodeNotProvided", "The invite code needs to be provided!"), + GROUP_ALREADY_IN_GROUP(HttpStatus.FORBIDDEN, "groupAlreadyInGroup", "The user is already a member of this group!"), INVALID_GROUP_NAME(HttpStatus.BAD_REQUEST, "invalidGroupName", "Group name must be non-null, non-empty and between 2 and 50 characters!"), INVALID_GROUP_PROFILE_NAMES(HttpStatus.BAD_REQUEST, "invalidGroupProfileNames", "Group profileNames must be non-null and non-empty!"), INVALID_GROUP_SPORT(HttpStatus.BAD_REQUEST, "invalidGroupSport", "Either a valid preset-id has to be set to 'sportPreset' or a non-null, non-empty 'customSportName' has to be supplied!"), From a994137da8bed8884e631c8765993d71367cf0a3 Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 8 Jun 2025 15:50:21 +0200 Subject: [PATCH 23/90] fixed tests + made auth controller post endpoints --- .../beerpong/api/control/AuthController.java | 9 +- .../test/java/pro/beerpong/api/TestUtils.java | 91 +++++++++++++++++-- 2 files changed, 84 insertions(+), 16 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/control/AuthController.java b/api/src/main/java/pro/beerpong/api/control/AuthController.java index f71ad23b..8f76301e 100644 --- a/api/src/main/java/pro/beerpong/api/control/AuthController.java +++ b/api/src/main/java/pro/beerpong/api/control/AuthController.java @@ -2,10 +2,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import pro.beerpong.api.model.dto.*; import pro.beerpong.api.service.AuthService; @@ -15,7 +12,7 @@ public class AuthController { private final AuthService authService; - @GetMapping("signup") + @PostMapping("signup") public ResponseEntity> signup(@RequestBody AuthSignupDto authSignupDto) { var result = authService.registerDevice(authSignupDto); @@ -26,7 +23,7 @@ public ResponseEntity> signup(@RequestBody AuthSi return ResponseEnvelope.ok(result); } - @GetMapping("refresh") + @PostMapping("refresh") public ResponseEntity> refreshAuth(@RequestBody AuthRefreshDto dto) { if (dto.getRefreshToken() == null || dto.getRefreshToken().isEmpty()) { return ResponseEnvelope.notOk(ErrorCodes.AUTH_REFRESH_INVALID_DTO); diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index ab93bb55..e2bc64f4 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -3,37 +3,108 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.api.client.util.Lists; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; +import org.springframework.http.*; import org.springframework.stereotype.Component; -import pro.beerpong.api.model.dto.ResponseEnvelope; +import pro.beerpong.api.model.dto.*; +import pro.beerpong.api.util.InstallationType; +import pro.beerpong.api.util.TokenType; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; @Component @RequiredArgsConstructor public class TestUtils { + private static List GROUP_ACCESS = Lists.newArrayList(); + private static String REFRESH_TOKEN; + private static String AUTH_TOKEN; + private final TestRestTemplate restTemplate; public ResponseEntity performGet(int port, String path, Class firstClazz, Class... classes) { - return performCall(port, path, HttpMethod.GET, null, firstClazz, classes); + return performCall(true, port, path, HttpMethod.GET, null, firstClazz, classes); } public ResponseEntity performPost(int port, String path, Object body, Class firstClazz, Class... classes) { - return performCall(port, path, HttpMethod.POST, body, firstClazz, classes); + return performCall(true, port, path, HttpMethod.POST, body, firstClazz, classes); } public ResponseEntity performPut(int port, String path, Object body, Class firstClazz, Class... classes) { - return performCall(port, path, HttpMethod.PUT, body, firstClazz, classes); + return performCall(true, port, path, HttpMethod.PUT, body, firstClazz, classes); } public ResponseEntity performDelete(int port, String path, Object body, Class firstClazz, Class... classes) { - return performCall(port, path, HttpMethod.DELETE, body, firstClazz, classes); + return performCall(true, port, path, HttpMethod.DELETE, body, firstClazz, classes); } - public ResponseEntity performCall(int port, String path, HttpMethod method, Object body, Class firstClazz, Class... classes) { - var exchange = restTemplate.exchange("http://localhost:" + port + path, method, body == null ? null : new HttpEntity<>(body), String.class); + @SuppressWarnings("unchecked") + private String generateAuthToken(int port) { + if (REFRESH_TOKEN == null) { + var signupDto = new AuthSignupDto(); + signupDto.setDeviceId("test"); + signupDto.setInstallationType(InstallationType.IOS); + + var signupResponse = this.performCall(false, port, "/auth/signup", HttpMethod.POST, signupDto, AuthTokenDto.class); + var signupEnvelope = (ResponseEnvelope) signupResponse.getBody(); + assert signupEnvelope != null; + var signupTokenDto = signupEnvelope.getData(); + + // Assertions + assertNotNull(signupResponse); + assertEquals(200, signupResponse.getStatusCode().value()); + assertNotNull(signupEnvelope); + assertEquals(ResponseEnvelope.Status.OK, signupEnvelope.getStatus()); + assertNull(signupEnvelope.getError()); + assertEquals(200, signupEnvelope.getHttpCode()); + assertNotNull(signupTokenDto); + assertEquals(TokenType.REFRESH, signupTokenDto.getType()); + assertNotNull(signupTokenDto.getToken()); + + REFRESH_TOKEN = signupTokenDto.getToken(); + } + + if (AUTH_TOKEN == null) { + var refreshDto = new AuthRefreshDto(); + refreshDto.setRefreshToken(REFRESH_TOKEN); + + var refreshResponse = this.performCall(false, port, "/auth/refresh", HttpMethod.POST, refreshDto, AuthTokenDto.class); + var refreshEnvelope = (ResponseEnvelope) refreshResponse.getBody(); + assert refreshEnvelope != null; + var refreshTokenDto = refreshEnvelope.getData(); + + // Assertions + assertNotNull(refreshResponse); + assertEquals(200, refreshResponse.getStatusCode().value()); + assertNotNull(refreshEnvelope); + assertEquals(ResponseEnvelope.Status.OK, refreshEnvelope.getStatus()); + assertNull(refreshEnvelope.getError()); + assertEquals(200, refreshEnvelope.getHttpCode()); + assertNotNull(refreshTokenDto); + assertEquals(TokenType.ACCESS, refreshTokenDto.getType()); + assertNotNull(refreshTokenDto.getToken()); + + AUTH_TOKEN = refreshTokenDto.getToken(); + } + + return AUTH_TOKEN; + } + + public ResponseEntity performCall(boolean withAuth, int port, String path, HttpMethod method, Object body, Class firstClazz, Class... classes) { + HttpHeaders headers = new HttpHeaders(); + + if (withAuth) { + headers.setBearerAuth(generateAuthToken(port)); + } + + headers.setContentType(MediaType.APPLICATION_JSON); + + var entity = (body == null ? new HttpEntity<>(headers) : new HttpEntity<>(body, headers)); + var exchange = restTemplate.exchange("http://localhost:" + port + path, method, entity, String.class); var objectMapper = new ObjectMapper(); objectMapper.findAndRegisterModules(); From fb8631f903228198ef19d9d44d2eb0ceb2e3b590 Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 8 Jun 2025 16:39:08 +0200 Subject: [PATCH 24/90] loading jwt secret via env variables --- .env.example | 1 + api/src/main/resources/application.properties | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index f69c9a82..a4fa9ced 100644 --- a/.env.example +++ b/.env.example @@ -7,3 +7,4 @@ POSTGRES_URL=jdbc:postgresql://localhost:5432/${POSTGRES_DB_NAME} API_BASE_URL=http://localhost:8080/ EXPO_PUBLIC_API_BASE_URL=http://localhost:8080 EXPO_PUBLIC_API_WS_URL=ws://localhost:8080 +JWT_SECRET=SomeVeryLongRandomSecretKeyWhichIsUsedToEncodeJwtTokensForAuthorization \ No newline at end of file diff --git a/api/src/main/resources/application.properties b/api/src/main/resources/application.properties index 51c2c542..a65adddc 100644 --- a/api/src/main/resources/application.properties +++ b/api/src/main/resources/application.properties @@ -1,2 +1,2 @@ -jwt.secret=YourVeryLongRandomSecretKeyHereWhichIsVerySecureAndHasToBeReplacedWithEnvVariables +jwt.secret=${JWT_SECRET} jwt.access.expiration-ms=3600000 \ No newline at end of file From 98550ff9515532819a58ac55cc510d18c4054615 Mon Sep 17 00:00:00 2001 From: Laurin-Notemann Date: Sun, 8 Jun 2025 21:27:36 +0200 Subject: [PATCH 25/90] fix: add JWT_TOKEN to pass tests in ci --- .github/workflows/api-ci.yml | 1 + .github/workflows/generate-openapi.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/api-ci.yml b/.github/workflows/api-ci.yml index 989719fe..9ba6d65e 100644 --- a/.github/workflows/api-ci.yml +++ b/.github/workflows/api-ci.yml @@ -49,4 +49,5 @@ jobs: POSTGRES_PASSWORD: user POSTGRES_URL: jdbc:postgresql://localhost:5432/beerpong API_BASE_URL: http://localhost:8080/ + JWT_TOKEN: justforthisrunnerthishasnovalue working-directory: api diff --git a/.github/workflows/generate-openapi.yaml b/.github/workflows/generate-openapi.yaml index 21bdde18..fa1cb24f 100644 --- a/.github/workflows/generate-openapi.yaml +++ b/.github/workflows/generate-openapi.yaml @@ -93,6 +93,7 @@ jobs: POSTGRES_PASSWORD: user POSTGRES_URL: jdbc:postgresql://localhost:5432/beerpong API_BASE_URL: http://localhost:8080/ + JWT_TOKEN: justforthisrunnerthishasnovalue working-directory: api # replace all occurences of "*/*" with "application/json" in the openapi.json file From 46559964b80ae08602a8d00f7b8d3d335359de76 Mon Sep 17 00:00:00 2001 From: Laurin-Notemann Date: Sun, 8 Jun 2025 21:31:52 +0200 Subject: [PATCH 26/90] fix --- .github/workflows/api-ci.yml | 2 +- .github/workflows/generate-openapi.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/api-ci.yml b/.github/workflows/api-ci.yml index 9ba6d65e..b70c2e9c 100644 --- a/.github/workflows/api-ci.yml +++ b/.github/workflows/api-ci.yml @@ -49,5 +49,5 @@ jobs: POSTGRES_PASSWORD: user POSTGRES_URL: jdbc:postgresql://localhost:5432/beerpong API_BASE_URL: http://localhost:8080/ - JWT_TOKEN: justforthisrunnerthishasnovalue + JWT_SECRET: justforthisrunnerthishasnovalue working-directory: api diff --git a/.github/workflows/generate-openapi.yaml b/.github/workflows/generate-openapi.yaml index fa1cb24f..0271631f 100644 --- a/.github/workflows/generate-openapi.yaml +++ b/.github/workflows/generate-openapi.yaml @@ -93,7 +93,7 @@ jobs: POSTGRES_PASSWORD: user POSTGRES_URL: jdbc:postgresql://localhost:5432/beerpong API_BASE_URL: http://localhost:8080/ - JWT_TOKEN: justforthisrunnerthishasnovalue + JWT_SECRET: justforthisrunnerthishasnovalue working-directory: api # replace all occurences of "*/*" with "application/json" in the openapi.json file From d7b6aa91234c455b7e57c5913f556b4f2dffa69f Mon Sep 17 00:00:00 2001 From: Laurin-Notemann Date: Sun, 8 Jun 2025 22:09:01 +0200 Subject: [PATCH 27/90] fix: this time --- .github/workflows/api-ci.yml | 2 +- .github/workflows/generate-openapi.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/api-ci.yml b/.github/workflows/api-ci.yml index b70c2e9c..2d3af9df 100644 --- a/.github/workflows/api-ci.yml +++ b/.github/workflows/api-ci.yml @@ -49,5 +49,5 @@ jobs: POSTGRES_PASSWORD: user POSTGRES_URL: jdbc:postgresql://localhost:5432/beerpong API_BASE_URL: http://localhost:8080/ - JWT_SECRET: justforthisrunnerthishasnovalue + JWT_SECRET: justforthisrunnerthishasnovaluelalalalalalalalalalalalalalalalalalalalalalalalalalla working-directory: api diff --git a/.github/workflows/generate-openapi.yaml b/.github/workflows/generate-openapi.yaml index 0271631f..da8883bf 100644 --- a/.github/workflows/generate-openapi.yaml +++ b/.github/workflows/generate-openapi.yaml @@ -93,7 +93,7 @@ jobs: POSTGRES_PASSWORD: user POSTGRES_URL: jdbc:postgresql://localhost:5432/beerpong API_BASE_URL: http://localhost:8080/ - JWT_SECRET: justforthisrunnerthishasnovalue + JWT_SECRET: justforthisrunnerthishasnovaluelalalalalalalalalalalalalalalalalalalalalalalalalalla working-directory: api # replace all occurences of "*/*" with "application/json" in the openapi.json file From 1eacd240de3e1b16ac6c6eb81fd8af1ade2ed61a Mon Sep 17 00:00:00 2001 From: Laurin-Notemann Date: Sun, 8 Jun 2025 22:21:09 +0200 Subject: [PATCH 28/90] lsot --- .github/workflows/generate-openapi.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/generate-openapi.yaml b/.github/workflows/generate-openapi.yaml index da8883bf..071fc3d8 100644 --- a/.github/workflows/generate-openapi.yaml +++ b/.github/workflows/generate-openapi.yaml @@ -125,3 +125,4 @@ jobs: if: steps.check_authors.outputs.has_bot_author != 'true' with: commit_message: "chore: update openapi types" + skip_checkout: true \ No newline at end of file From 51ebd513041991ce3b94b44a55a3e497d8eb2054 Mon Sep 17 00:00:00 2001 From: Laurin-Notemann <47634664+Laurin-Notemann@users.noreply.github.com> Date: Sun, 8 Jun 2025 20:23:09 +0000 Subject: [PATCH 29/90] chore: update openapi types --- mobile-app/api/generated/openapi.json | 228 +++++++++++++++++++++++--- mobile-app/openapi/openapi.d.ts | 191 +++++++++++++++++++-- 2 files changed, 383 insertions(+), 36 deletions(-) diff --git a/mobile-app/api/generated/openapi.json b/mobile-app/api/generated/openapi.json index 5105841f..8c156c0d 100644 --- a/mobile-app/api/generated/openapi.json +++ b/mobile-app/api/generated/openapi.json @@ -617,6 +617,58 @@ } } }, + "/groups/{id}/leave": { + "post": { + "tags": ["group-controller"], + "operationId": "leaveGroup", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseEnvelopeString" + } + } + } + } + } + } + }, + "/groups/{id}/join": { + "post": { + "tags": ["group-controller"], + "operationId": "joinGroup", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseEnvelopeString" + } + } + } + } + } + } + }, "/groups/{groupId}/seasons/{seasonId}/rule-moves": { "get": { "tags": ["rule-move-controller"], @@ -821,6 +873,62 @@ } } }, + "/auth/signup": { + "post": { + "tags": ["auth-controller"], + "operationId": "signup", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthSignupDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseEnvelopeAuthTokenDto" + } + } + } + } + } + } + }, + "/auth/refresh": { + "post": { + "tags": ["auth-controller"], + "operationId": "refreshAuth", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthRefreshDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseEnvelopeAuthTokenDto" + } + } + } + } + } + } + }, "/healthcheck": { "get": { "tags": ["healthcheck-controller"], @@ -1017,6 +1125,24 @@ } } }, + "/groups/user": { + "get": { + "tags": ["group-controller"], + "operationId": "findUserGroups", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseEnvelopeListGroupDto" + } + } + } + } + } + } + }, "/group-presets": { "get": { "tags": ["group-presets-controller"], @@ -1160,10 +1286,15 @@ "id": { "type": "string" }, "name": { "type": "string" }, "inviteCode": { "type": "string" }, - "activeSeason": { "$ref": "#/components/schemas/Season" }, + "activeSeason": { + "$ref": "#/components/schemas/SeasonDto" + }, "wallpaperAsset": { "$ref": "#/components/schemas/AssetMetadataDto" }, + "createdBy": { + "$ref": "#/components/schemas/GroupMemberDto" + }, "createdAt": { "type": "string", "format": "date-time" }, "sportPreset": { "$ref": "#/components/schemas/GroupPreset" @@ -1174,6 +1305,15 @@ "numberOfSeasons": { "type": "integer", "format": "int32" } } }, + "GroupMemberDto": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "active": { "type": "boolean" }, + "groupId": { "type": "string" }, + "userId": { "type": "string" } + } + }, "GroupPreset": { "type": "object", "properties": { @@ -1191,7 +1331,7 @@ "error": { "$ref": "#/components/schemas/ErrorDetails" } } }, - "Season": { + "SeasonDto": { "type": "object", "properties": { "id": { "type": "string" }, @@ -1201,6 +1341,9 @@ "groupId": { "type": "string" }, "seasonSettings": { "$ref": "#/components/schemas/SeasonSettings" + }, + "createdBy": { + "$ref": "#/components/schemas/GroupMemberDto" } } }, @@ -1263,7 +1406,10 @@ "id": { "type": "string" }, "title": { "type": "string" }, "description": { "type": "string" }, - "season": { "$ref": "#/components/schemas/Season" } + "season": { "$ref": "#/components/schemas/SeasonDto" }, + "createdBy": { + "$ref": "#/components/schemas/GroupMemberDto" + } } }, "RuleMoveCreateDto": { @@ -1292,7 +1438,7 @@ "pointsForTeam": { "type": "integer", "format": "int32" }, "pointsForScorer": { "type": "integer", "format": "int32" }, "finishingMove": { "type": "boolean" }, - "season": { "$ref": "#/components/schemas/Season" } + "season": { "$ref": "#/components/schemas/SeasonDto" } } }, "MatchCreateDto": { @@ -1339,7 +1485,10 @@ "properties": { "id": { "type": "string" }, "date": { "type": "string", "format": "date-time" }, - "season": { "$ref": "#/components/schemas/Season" }, + "season": { "$ref": "#/components/schemas/SeasonDto" }, + "createdBy": { + "$ref": "#/components/schemas/GroupMemberDto" + }, "teams": { "type": "array", "items": { "$ref": "#/components/schemas/TeamDto" } @@ -1409,19 +1558,6 @@ "error": { "$ref": "#/components/schemas/ErrorDetails" } } }, - "SeasonDto": { - "type": "object", - "properties": { - "id": { "type": "string" }, - "name": { "type": "string" }, - "startDate": { "type": "string", "format": "date-time" }, - "endDate": { "type": "string", "format": "date-time" }, - "groupId": { "type": "string" }, - "seasonSettings": { - "$ref": "#/components/schemas/SeasonSettings" - } - } - }, "ProfileDto": { "type": "object", "properties": { @@ -1430,7 +1566,10 @@ "avatarAsset": { "$ref": "#/components/schemas/AssetMetadataDto" }, - "groupId": { "type": "string" } + "groupId": { "type": "string" }, + "createdBy": { + "$ref": "#/components/schemas/GroupMemberDto" + } } }, "ResponseEnvelopeProfileDto": { @@ -1458,6 +1597,15 @@ } } }, + "ResponseEnvelopeString": { + "type": "object", + "properties": { + "status": { "type": "string", "enum": ["OK", "ERROR"] }, + "httpCode": { "type": "integer", "format": "int32" }, + "data": { "type": "string" }, + "error": { "$ref": "#/components/schemas/ErrorDetails" } + } + }, "ProfileCreatedDto": { "type": "object", "properties": { @@ -1467,6 +1615,9 @@ "$ref": "#/components/schemas/AssetMetadataDto" }, "groupId": { "type": "string" }, + "createdBy": { + "$ref": "#/components/schemas/GroupMemberDto" + }, "reactivated": { "type": "boolean" }, "lastActiveSeasonId": { "type": "string" } } @@ -1482,15 +1633,36 @@ "error": { "$ref": "#/components/schemas/ErrorDetails" } } }, - "ResponseEnvelopeString": { + "AuthSignupDto": { + "type": "object", + "properties": { + "installationType": { + "type": "string", + "enum": ["IOS", "ANDROID"] + }, + "deviceId": { "type": "string" } + } + }, + "AuthTokenDto": { + "type": "object", + "properties": { + "token": { "type": "string" }, + "type": { "type": "string", "enum": ["ACCESS", "REFRESH"] } + } + }, + "ResponseEnvelopeAuthTokenDto": { "type": "object", "properties": { "status": { "type": "string", "enum": ["OK", "ERROR"] }, "httpCode": { "type": "integer", "format": "int32" }, - "data": { "type": "string" }, + "data": { "$ref": "#/components/schemas/AuthTokenDto" }, "error": { "$ref": "#/components/schemas/ErrorDetails" } } }, + "AuthRefreshDto": { + "type": "object", + "properties": { "refreshToken": { "type": "string" } } + }, "ResponseEnvelopeListSeasonDto": { "type": "object", "properties": { @@ -1580,7 +1752,7 @@ "properties": { "id": { "type": "string" }, "date": { "type": "string", "format": "date-time" }, - "season": { "$ref": "#/components/schemas/Season" }, + "season": { "$ref": "#/components/schemas/SeasonDto" }, "blueTeam": { "$ref": "#/components/schemas/MatchOverviewTeamDto" }, @@ -1668,6 +1840,18 @@ "error": { "$ref": "#/components/schemas/ErrorDetails" } } }, + "ResponseEnvelopeListGroupDto": { + "type": "object", + "properties": { + "status": { "type": "string", "enum": ["OK", "ERROR"] }, + "httpCode": { "type": "integer", "format": "int32" }, + "data": { + "type": "array", + "items": { "$ref": "#/components/schemas/GroupDto" } + }, + "error": { "$ref": "#/components/schemas/ErrorDetails" } + } + }, "ResponseEnvelopeListGroupPreset": { "type": "object", "properties": { diff --git a/mobile-app/openapi/openapi.d.ts b/mobile-app/openapi/openapi.d.ts index cd0a8797..566764dd 100644 --- a/mobile-app/openapi/openapi.d.ts +++ b/mobile-app/openapi/openapi.d.ts @@ -14,6 +14,17 @@ declare namespace Components { mediaType?: string; uploadedAt?: string; // date-time } + export interface AuthRefreshDto { + refreshToken?: string; + } + export interface AuthSignupDto { + installationType?: 'IOS' | 'ANDROID'; + deviceId?: string; + } + export interface AuthTokenDto { + token?: string; + type?: 'ACCESS' | 'REFRESH'; + } export interface ErrorDetails { code?: string; description?: string; @@ -28,8 +39,9 @@ declare namespace Components { id?: string; name?: string; inviteCode?: string; - activeSeason?: Season; + activeSeason?: SeasonDto; wallpaperAsset?: AssetMetadataDto; + createdBy?: GroupMemberDto; createdAt?: string; // date-time sportPreset?: GroupPreset; customSportName?: string; @@ -37,6 +49,12 @@ declare namespace Components { numberOfMatches?: number; // int32 numberOfSeasons?: number; // int32 } + export interface GroupMemberDto { + id?: string; + active?: boolean; + groupId?: string; + userId?: string; + } export interface GroupPreset { id?: string; title?: string; @@ -54,7 +72,8 @@ declare namespace Components { export interface MatchDto { id?: string; date?: string; // date-time - season?: Season; + season?: SeasonDto; + createdBy?: GroupMemberDto; teams?: TeamDto[]; teamMembers?: TeamMemberDto[]; matchMoves?: MatchMoveDtoComplete[]; @@ -72,7 +91,7 @@ declare namespace Components { export interface MatchOverviewDto { id?: string; date?: string; // date-time - season?: Season; + season?: SeasonDto; blueTeam?: MatchOverviewTeamDto; redTeam?: MatchOverviewTeamDto; } @@ -114,6 +133,7 @@ declare namespace Components { name?: string; avatarAsset?: AssetMetadataDto; groupId?: string; + createdBy?: GroupMemberDto; reactivated?: boolean; lastActiveSeasonId?: string; } @@ -122,6 +142,7 @@ declare namespace Components { name?: string; avatarAsset?: AssetMetadataDto; groupId?: string; + createdBy?: GroupMemberDto; } export interface ResponseEnvelopeAssetMetadataDto { status?: 'OK' | 'ERROR'; @@ -129,6 +150,12 @@ declare namespace Components { data?: AssetMetadataDto; error?: ErrorDetails; } + export interface ResponseEnvelopeAuthTokenDto { + status?: 'OK' | 'ERROR'; + httpCode?: number; // int32 + data?: AuthTokenDto; + error?: ErrorDetails; + } export interface ResponseEnvelopeGroupDto { status?: 'OK' | 'ERROR'; httpCode?: number; // int32 @@ -141,6 +168,12 @@ declare namespace Components { data?: LeaderboardDto; error?: ErrorDetails; } + export interface ResponseEnvelopeListGroupDto { + status?: 'OK' | 'ERROR'; + httpCode?: number; // int32 + data?: GroupDto[]; + error?: ErrorDetails; + } export interface ResponseEnvelopeListGroupPreset { status?: 'OK' | 'ERROR'; httpCode?: number; // int32 @@ -239,7 +272,8 @@ declare namespace Components { id?: string; title?: string; description?: string; - season?: Season; + season?: SeasonDto; + createdBy?: GroupMemberDto; } export interface RuleMoveCreateDto { name?: string; @@ -253,15 +287,7 @@ declare namespace Components { pointsForTeam?: number; // int32 pointsForScorer?: number; // int32 finishingMove?: boolean; - season?: Season; - } - export interface Season { - id?: string; - name?: string; - startDate?: string; // date-time - endDate?: string; // date-time - groupId?: string; - seasonSettings?: SeasonSettings; + season?: SeasonDto; } export interface SeasonCreateDto { oldSeasonName?: string; @@ -274,6 +300,7 @@ declare namespace Components { endDate?: string; // date-time groupId?: string; seasonSettings?: SeasonSettings; + createdBy?: GroupMemberDto; } export interface SeasonSettings { id?: string; @@ -408,6 +435,11 @@ declare namespace Paths { export type $200 = Components.Schemas.ResponseEnvelopeGroupDto; } } + namespace FindUserGroups { + namespace Responses { + export type $200 = Components.Schemas.ResponseEnvelopeListGroupDto; + } + } namespace GetAllMatchOverviews { namespace Parameters { export type GroupId = string; @@ -601,6 +633,28 @@ declare namespace Paths { export type $200 = Components.Schemas.ResponseEnvelopeSeasonDto; } } + namespace JoinGroup { + namespace Parameters { + export type Id = string; + } + export interface PathParameters { + id: Parameters.Id; + } + namespace Responses { + export type $200 = Components.Schemas.ResponseEnvelopeString; + } + } + namespace LeaveGroup { + namespace Parameters { + export type Id = string; + } + export interface PathParameters { + id: Parameters.Id; + } + namespace Responses { + export type $200 = Components.Schemas.ResponseEnvelopeString; + } + } namespace ListAllProfiles { namespace Parameters { export type GroupId = string; @@ -613,6 +667,12 @@ declare namespace Paths { Components.Schemas.ResponseEnvelopeListProfileDto; } } + namespace RefreshAuth { + export type RequestBody = Components.Schemas.AuthRefreshDto; + namespace Responses { + export type $200 = Components.Schemas.ResponseEnvelopeAuthTokenDto; + } + } namespace SetAvatar { namespace Parameters { export type GroupId = string; @@ -640,6 +700,12 @@ declare namespace Paths { Components.Schemas.ResponseEnvelopeAssetMetadataDto; } } + namespace Signup { + export type RequestBody = Components.Schemas.AuthSignupDto; + namespace Responses { + export type $200 = Components.Schemas.ResponseEnvelopeAuthTokenDto; + } + } namespace StartNewSeason { namespace Parameters { export type GroupId = string; @@ -877,6 +943,22 @@ export interface OperationMethods { data?: Paths.CreateGroup.RequestBody, config?: AxiosRequestConfig ): OperationResponse; + /** + * leaveGroup + */ + 'leaveGroup'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse; + /** + * joinGroup + */ + 'joinGroup'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse; /** * getAllRuleMoves */ @@ -925,6 +1007,22 @@ export interface OperationMethods { data?: Paths.CreateProfile.RequestBody, config?: AxiosRequestConfig ): OperationResponse; + /** + * signup + */ + 'signup'( + parameters?: Parameters | null, + data?: Paths.Signup.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse; + /** + * refreshAuth + */ + 'refreshAuth'( + parameters?: Parameters | null, + data?: Paths.RefreshAuth.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse; /** * getHealthcheck */ @@ -978,6 +1076,14 @@ export interface OperationMethods { data?: any, config?: AxiosRequestConfig ): OperationResponse; + /** + * findUserGroups + */ + 'findUserGroups'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse; /** * getPresets */ @@ -1169,6 +1275,26 @@ export interface PathsDictionary { config?: AxiosRequestConfig ): OperationResponse; }; + ['/groups/{id}/leave']: { + /** + * leaveGroup + */ + 'post'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse; + }; + ['/groups/{id}/join']: { + /** + * joinGroup + */ + 'post'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse; + }; ['/groups/{groupId}/seasons/{seasonId}/rule-moves']: { /** * getAllRuleMoves @@ -1223,6 +1349,26 @@ export interface PathsDictionary { config?: AxiosRequestConfig ): OperationResponse; }; + ['/auth/signup']: { + /** + * signup + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.Signup.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse; + }; + ['/auth/refresh']: { + /** + * refreshAuth + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.RefreshAuth.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse; + }; ['/healthcheck']: { /** * getHealthcheck @@ -1289,6 +1435,16 @@ export interface PathsDictionary { config?: AxiosRequestConfig ): OperationResponse; }; + ['/groups/user']: { + /** + * findUserGroups + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse; + }; ['/group-presets']: { /** * getPresets @@ -1334,9 +1490,13 @@ export interface PathsDictionary { export type Client = OpenAPIClient; export type AssetMetadataDto = Components.Schemas.AssetMetadataDto; +export type AuthRefreshDto = Components.Schemas.AuthRefreshDto; +export type AuthSignupDto = Components.Schemas.AuthSignupDto; +export type AuthTokenDto = Components.Schemas.AuthTokenDto; export type ErrorDetails = Components.Schemas.ErrorDetails; export type GroupCreateDto = Components.Schemas.GroupCreateDto; export type GroupDto = Components.Schemas.GroupDto; +export type GroupMemberDto = Components.Schemas.GroupMemberDto; export type GroupPreset = Components.Schemas.GroupPreset; export type LeaderboardDto = Components.Schemas.LeaderboardDto; export type MatchCreateDto = Components.Schemas.MatchCreateDto; @@ -1354,10 +1514,14 @@ export type ProfileCreatedDto = Components.Schemas.ProfileCreatedDto; export type ProfileDto = Components.Schemas.ProfileDto; export type ResponseEnvelopeAssetMetadataDto = Components.Schemas.ResponseEnvelopeAssetMetadataDto; +export type ResponseEnvelopeAuthTokenDto = + Components.Schemas.ResponseEnvelopeAuthTokenDto; export type ResponseEnvelopeGroupDto = Components.Schemas.ResponseEnvelopeGroupDto; export type ResponseEnvelopeLeaderboardDto = Components.Schemas.ResponseEnvelopeLeaderboardDto; +export type ResponseEnvelopeListGroupDto = + Components.Schemas.ResponseEnvelopeListGroupDto; export type ResponseEnvelopeListGroupPreset = Components.Schemas.ResponseEnvelopeListGroupPreset; export type ResponseEnvelopeListMatchDto = @@ -1391,7 +1555,6 @@ export type RuleCreateDto = Components.Schemas.RuleCreateDto; export type RuleDto = Components.Schemas.RuleDto; export type RuleMoveCreateDto = Components.Schemas.RuleMoveCreateDto; export type RuleMoveDto = Components.Schemas.RuleMoveDto; -export type Season = Components.Schemas.Season; export type SeasonCreateDto = Components.Schemas.SeasonCreateDto; export type SeasonDto = Components.Schemas.SeasonDto; export type SeasonSettings = Components.Schemas.SeasonSettings; From 237e1761b76ff9ccff8c3c296c31f4148066e84d Mon Sep 17 00:00:00 2001 From: Thies Date: Mon, 9 Jun 2025 12:43:28 +0200 Subject: [PATCH 30/90] added todo --- api/src/main/java/pro/beerpong/api/service/GroupService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/main/java/pro/beerpong/api/service/GroupService.java b/api/src/main/java/pro/beerpong/api/service/GroupService.java index 141a3483..7f5cdad0 100644 --- a/api/src/main/java/pro/beerpong/api/service/GroupService.java +++ b/api/src/main/java/pro/beerpong/api/service/GroupService.java @@ -68,6 +68,7 @@ public GroupDto createGroup(GroupCreateDto groupCreateDto, UserDto user) { groupMember = authService.saveMember(groupMember); + // TODO maybe find way to prevent double group saving. but not that big of a deal group.setCreatedBy(groupMember); group = groupRepository.save(group); From 02d7bd0bad9c616dc1540b8a66889658060b0dea Mon Sep 17 00:00:00 2001 From: Thies Date: Tue, 10 Jun 2025 10:17:02 +0200 Subject: [PATCH 31/90] add http request asserts --- .../test/java/pro/beerpong/api/TestUtils.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index e2bc64f4..94eb8a5e 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -167,4 +167,32 @@ public ResponseEntity performCall(boolean withAuth, int port, String pat return ResponseEntity.status(exchange.getStatusCode()).body(responseEnvelope); } + + public T assertSuccess(ResponseEntity response, Class tClass) { + assertNotNull(response); + assertEquals(200, response.getStatusCode().value()); + + ResponseEnvelope envelope = (ResponseEnvelope) response.getBody(); + assertNotNull(envelope); + assertEquals(ResponseEnvelope.Status.OK, envelope.getStatus()); + assertEquals(200, envelope.getHttpCode()); + assertNull(envelope.getError()); + assertNotNull(envelope.getData()); + + return tClass.cast(envelope.getData()); + } + + public void assertFailure(ResponseEntity response, ErrorCodes error) { + assertNotNull(response); + assertEquals(error.getHttpStatus().value(), response.getStatusCode().value()); + + ResponseEnvelope envelope = (ResponseEnvelope) response.getBody(); + assertNotNull(envelope); + assertEquals(ResponseEnvelope.Status.ERROR, envelope.getStatus()); + assertEquals(error.getHttpStatus().value(), envelope.getHttpCode()); + assertNotNull(envelope.getError()); + assertEquals(error.getCode(), envelope.getError().getCode()); + assertEquals(error.getDescription(), envelope.getError().getDescription()); + assertNull(envelope.getData()); + } } \ No newline at end of file From af12bc646aab57e4313d4599772986a5348af55a Mon Sep 17 00:00:00 2001 From: Thies Date: Tue, 10 Jun 2025 10:23:14 +0200 Subject: [PATCH 32/90] better create test --- .../test/java/pro/beerpong/api/TestUtils.java | 1 + .../api/control/GroupControllerTest.java | 40 ++++++++++++------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index 94eb8a5e..92f24179 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -178,6 +178,7 @@ public T assertSuccess(ResponseEntity response, Class tClass) { assertEquals(200, envelope.getHttpCode()); assertNull(envelope.getError()); assertNotNull(envelope.getData()); + assertEquals(tClass, envelope.getData().getClass()); return tClass.cast(envelope.getData()); } diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index d2eb0cf6..85659c9d 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -26,35 +26,45 @@ public class GroupControllerTest { @Test @Transactional - @SuppressWarnings("unchecked") - public void whenPassingValidGroupToCreatingGroup_ThenIsSuccessful() { + public void group_create() { var createDto = new GroupCreateDto(); createDto.setProfileNames(List.of("player1", "player2")); createDto.setName("test"); createDto.setSportPreset("beerpong"); var response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); - - assertNotNull(response); - assertEquals(200, response.getStatusCode().value()); - - ResponseEnvelope envelope = (ResponseEnvelope) response.getBody(); - assertNotNull(envelope); - assertEquals(ResponseEnvelope.Status.OK, envelope.getStatus()); - assertNull(envelope.getError()); - assertEquals(200, envelope.getHttpCode()); - - var group = envelope.getData(); + var group = testUtils.assertSuccess(response, GroupDto.class); assertNotNull(group); + assertNotNull(group.getId()); assertNotNull(group.getName()); assertEquals(createDto.getName(), group.getName()); - assertNotNull(group.getId()); assertNotNull(group.getInviteCode()); + assertNotNull(group.getCreatedAt()); + assertNull(group.getWallpaperAsset()); + assertNull(group.getCustomSportName()); + assertEquals(GroupPresetsController.BEERPONG.getId(), group.getSportPreset().getId()); + assertNotNull(group.getActiveSeason()); assertNotNull(group.getActiveSeason().getId()); + assertNull(group.getActiveSeason().getName()); + assertNotNull(group.getActiveSeason().getStartDate()); + assertNull(group.getActiveSeason().getEndDate()); assertEquals(group.getActiveSeason().getGroupId(), group.getId()); - assertEquals(GroupPresetsController.BEERPONG.getId(), group.getSportPreset().getId()); + assertNotNull(group.getActiveSeason().getSeasonSettings()); + assertEquals(group.getCreatedBy(), group.getActiveSeason().getCreatedBy()); + + createDto = new GroupCreateDto(); + createDto.setProfileNames(List.of("player1", "player2")); + createDto.setName("test"); + createDto.setCustomSportName("test123"); + + response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + group = testUtils.assertSuccess(response, GroupDto.class); + + assertNotNull(group); + assertEquals("test123", group.getCustomSportName()); + assertNull(group.getSportPreset()); } @Test From ee943b0612f0847b34de34e5a2d75e31a6afbfc9 Mon Sep 17 00:00:00 2001 From: Thies Date: Tue, 10 Jun 2025 10:27:30 +0200 Subject: [PATCH 33/90] more create tests --- .../api/control/GroupControllerTest.java | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index 85659c9d..0531b8ba 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -7,6 +7,7 @@ import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.test.context.ActiveProfiles; import pro.beerpong.api.TestUtils; +import pro.beerpong.api.model.dto.ErrorCodes; import pro.beerpong.api.model.dto.GroupCreateDto; import pro.beerpong.api.model.dto.GroupDto; import pro.beerpong.api.model.dto.ResponseEnvelope; @@ -26,7 +27,7 @@ public class GroupControllerTest { @Test @Transactional - public void group_create() { + public void group_create_success() { var createDto = new GroupCreateDto(); createDto.setProfileNames(List.of("player1", "player2")); createDto.setName("test"); @@ -67,6 +68,89 @@ public void group_create() { assertNull(group.getSportPreset()); } + @Test + @Transactional + public void group_create_invalidName() { + var createDto = new GroupCreateDto(); + createDto.setProfileNames(List.of("player1", "player2")); + createDto.setName(""); + createDto.setSportPreset("beerpong"); + + var response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + + createDto = new GroupCreateDto(); + createDto.setProfileNames(List.of("player1", "player2")); + createDto.setName(null); + createDto.setSportPreset("beerpong"); + + response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + + createDto = new GroupCreateDto(); + createDto.setProfileNames(List.of("player1", "player2")); + createDto.setName("a"); + createDto.setSportPreset("beerpong"); + + response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + + createDto = new GroupCreateDto(); + createDto.setProfileNames(List.of("player1", "player2")); + createDto.setName("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + createDto.setSportPreset("beerpong"); + + response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + } + + @Test + @Transactional + public void group_create_invalidProfiles() { + var createDto = new GroupCreateDto(); + createDto.setProfileNames(List.of()); + createDto.setName("test"); + createDto.setSportPreset("beerpong"); + + var response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_PROFILE_NAMES); + + createDto = new GroupCreateDto(); + createDto.setProfileNames(null); + createDto.setName("test"); + createDto.setSportPreset("beerpong"); + + response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_PROFILE_NAMES); + } + + @Test + @Transactional + public void group_create_invalidSport() { + var createDto = new GroupCreateDto(); + createDto.setProfileNames(List.of("player1", "player2")); + createDto.setName("test"); + createDto.setSportPreset("notExisting"); + + var response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); + + createDto = new GroupCreateDto(); + createDto.setProfileNames(List.of("player1", "player2")); + createDto.setName("test"); + + response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); + + createDto = new GroupCreateDto(); + createDto.setProfileNames(List.of("player1", "player2")); + createDto.setName("test"); + createDto.setCustomSportName(" "); + + response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); + } + @Test @Transactional @SuppressWarnings("unchecked") From 3d9df54a0e22918b6e2b6882b9081f9f39a055c8 Mon Sep 17 00:00:00 2001 From: Thies Date: Tue, 10 Jun 2025 12:00:55 +0200 Subject: [PATCH 34/90] more tests --- .../test/java/pro/beerpong/api/TestUtils.java | 6 ++- .../api/control/GroupControllerTest.java | 54 ++++++++++--------- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index 92f24179..025ba6dc 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -168,11 +168,12 @@ public ResponseEntity performCall(boolean withAuth, int port, String pat return ResponseEntity.status(exchange.getStatusCode()).body(responseEnvelope); } + @SuppressWarnings("unchecked") public T assertSuccess(ResponseEntity response, Class tClass) { assertNotNull(response); assertEquals(200, response.getStatusCode().value()); - ResponseEnvelope envelope = (ResponseEnvelope) response.getBody(); + ResponseEnvelope envelope = (ResponseEnvelope) response.getBody(); assertNotNull(envelope); assertEquals(ResponseEnvelope.Status.OK, envelope.getStatus()); assertEquals(200, envelope.getHttpCode()); @@ -183,11 +184,12 @@ public T assertSuccess(ResponseEntity response, Class tClass) { return tClass.cast(envelope.getData()); } + @SuppressWarnings("unchecked") public void assertFailure(ResponseEntity response, ErrorCodes error) { assertNotNull(response); assertEquals(error.getHttpStatus().value(), response.getStatusCode().value()); - ResponseEnvelope envelope = (ResponseEnvelope) response.getBody(); + ResponseEnvelope envelope = (ResponseEnvelope) response.getBody(); assertNotNull(envelope); assertEquals(ResponseEnvelope.Status.ERROR, envelope.getStatus()); assertEquals(error.getHttpStatus().value(), envelope.getHttpCode()); diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index 0531b8ba..541ce1c0 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -12,6 +12,7 @@ import pro.beerpong.api.model.dto.GroupDto; import pro.beerpong.api.model.dto.ResponseEnvelope; +import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -154,50 +155,53 @@ public void group_create_invalidSport() { @Test @Transactional @SuppressWarnings("unchecked") - public void whenPassingGroupInviteCodeToFindGroupByInviteCode_ThenIsSuccessful() { + public void group_userGroups() { var createDto = new GroupCreateDto(); createDto.setProfileNames(List.of("player1", "player2")); createDto.setName("test"); createDto.setSportPreset("beerpong"); var prerequisiteResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + var prerequisiteGroup = testUtils.assertSuccess(prerequisiteResponse, GroupDto.class); - assertNotNull(prerequisiteResponse); - assertEquals(200, prerequisiteResponse.getStatusCode().value()); + var response = testUtils.performGet(port, "/groups/user", List.class, GroupDto.class); + var groups = (List) testUtils.assertSuccess(response, ArrayList.class); - ResponseEnvelope prerequisiteEnvelope = (ResponseEnvelope) prerequisiteResponse.getBody(); - assertNotNull(prerequisiteEnvelope); - assertEquals(ResponseEnvelope.Status.OK, prerequisiteEnvelope.getStatus()); - assertNull(prerequisiteEnvelope.getError()); - assertEquals(200, prerequisiteEnvelope.getHttpCode()); - var prerequisiteGroup = prerequisiteEnvelope.getData(); - var response = testUtils.performGet(port, "/groups?inviteCode=" + prerequisiteGroup.getInviteCode(), GroupDto.class); + assertFalse(groups.isEmpty()); + assertTrue(groups.stream().anyMatch(groupDto -> groupDto.getId().equals(prerequisiteGroup.getId()))); + } - assertNotNull(response); - assertEquals(200, response.getStatusCode().value()); + @Test + @Transactional + public void group_findByInviteCode_success() { + var createDto = new GroupCreateDto(); + createDto.setProfileNames(List.of("player1", "player2")); + createDto.setName("test"); + createDto.setSportPreset("beerpong"); - ResponseEnvelope envelope = (ResponseEnvelope) response.getBody(); - assertNotNull(envelope); - assertEquals(ResponseEnvelope.Status.OK, envelope.getStatus()); - assertNull(envelope.getError()); - assertEquals(200, envelope.getHttpCode()); + var prerequisiteResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + var prerequisiteGroup = testUtils.assertSuccess(prerequisiteResponse, GroupDto.class); - var group = envelope.getData(); + var response = testUtils.performGet(port, "/groups?inviteCode=" + prerequisiteGroup.getInviteCode(), GroupDto.class); + var group = testUtils.assertSuccess(response, GroupDto.class); // if this is not here, the startDate millis are rounded and this test fails group.getActiveSeason().setStartDate(prerequisiteGroup.getActiveSeason().getStartDate()); group.setCreatedAt(prerequisiteGroup.getCreatedAt()); assertNotNull(group); - assertNotNull(group.getName()); assertEquals(prerequisiteGroup, group); - assertNotNull(group.getId()); - assertNotNull(group.getInviteCode()); - assertNotNull(group.getActiveSeason()); - assertNotNull(group.getActiveSeason().getId()); - assertEquals(group.getActiveSeason().getGroupId(), group.getId()); - assertEquals(group.getSportPreset(), prerequisiteGroup.getSportPreset()); + } + + @Test + @Transactional + public void group_findByInviteCode_invalidInviteCode() { + var response = testUtils.performGet(port, "/groups?inviteCode= ", GroupDto.class); + testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_INVITE_CODE); + + response = testUtils.performGet(port, "/groups?inviteCode=someIdThatNotExists", GroupDto.class); + testUtils.assertFailure(response, ErrorCodes.GROUP_INVITE_NOT_FOUND); } } \ No newline at end of file From 9337b17088009ac77bc4063c3da346b789df36c6 Mon Sep 17 00:00:00 2001 From: Thies Date: Tue, 10 Jun 2025 13:21:06 +0200 Subject: [PATCH 35/90] moreee tests --- .../beerpong/api/control/AuthController.java | 2 +- .../api/model/dto/GroupCreateDto.java | 2 +- .../api/model/dto/SeasonCreateDto.java | 2 +- .../test/java/pro/beerpong/api/TestUtils.java | 20 ++++- .../api/control/GroupControllerTest.java | 79 +++++++++++++++++++ 5 files changed, 101 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/control/AuthController.java b/api/src/main/java/pro/beerpong/api/control/AuthController.java index 8f76301e..29bb0599 100644 --- a/api/src/main/java/pro/beerpong/api/control/AuthController.java +++ b/api/src/main/java/pro/beerpong/api/control/AuthController.java @@ -25,7 +25,7 @@ public ResponseEntity> signup(@RequestBody AuthSi @PostMapping("refresh") public ResponseEntity> refreshAuth(@RequestBody AuthRefreshDto dto) { - if (dto.getRefreshToken() == null || dto.getRefreshToken().isEmpty()) { + if (dto.getRefreshToken() == null || dto.getRefreshToken().trim().isEmpty()) { return ResponseEnvelope.notOk(ErrorCodes.AUTH_REFRESH_INVALID_DTO); } diff --git a/api/src/main/java/pro/beerpong/api/model/dto/GroupCreateDto.java b/api/src/main/java/pro/beerpong/api/model/dto/GroupCreateDto.java index 6565da8c..d3d876f9 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/GroupCreateDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/GroupCreateDto.java @@ -16,7 +16,7 @@ public class GroupCreateDto { private String customSportName; public boolean invalidName() { - return this.name == null || this.name.isEmpty() || + return this.name == null || this.name.trim().isEmpty() || this.name.length() < GROUP_NAME_MIN_LENGTH || this.name.length() > GROUP_NAME_MAX_LENGTH; } diff --git a/api/src/main/java/pro/beerpong/api/model/dto/SeasonCreateDto.java b/api/src/main/java/pro/beerpong/api/model/dto/SeasonCreateDto.java index 94bda73f..08770cdd 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/SeasonCreateDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/SeasonCreateDto.java @@ -13,7 +13,7 @@ public class SeasonCreateDto { private List ruleMoves; public boolean invalidName() { - return this.oldSeasonName == null || this.oldSeasonName.isEmpty() || + return this.oldSeasonName == null || this.oldSeasonName.trim().isEmpty() || this.oldSeasonName.length() < SEASON_NAME_MIN_LENGTH || this.oldSeasonName.length() > SEASON_NAME_MAX_LENGTH; } diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index 025ba6dc..5ee242c4 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -18,7 +18,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; @Component -@RequiredArgsConstructor public class TestUtils { private static List GROUP_ACCESS = Lists.newArrayList(); private static String REFRESH_TOKEN; @@ -26,6 +25,10 @@ public class TestUtils { private final TestRestTemplate restTemplate; + public TestUtils(TestRestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + public ResponseEntity performGet(int port, String path, Class firstClazz, Class... classes) { return performCall(true, port, path, HttpMethod.GET, null, firstClazz, classes); } @@ -103,6 +106,8 @@ public ResponseEntity performCall(boolean withAuth, int port, String pat headers.setContentType(MediaType.APPLICATION_JSON); + System.out.println(headers.get("Authorization")); + var entity = (body == null ? new HttpEntity<>(headers) : new HttpEntity<>(body, headers)); var exchange = restTemplate.exchange("http://localhost:" + port + path, method, entity, String.class); @@ -157,6 +162,10 @@ public ResponseEntity performCall(boolean withAuth, int port, String pat return ResponseEntity.status(exchange.getStatusCode()).build(); } + if (!responseBody.startsWith("{") && !responseBody.startsWith("[")) { + return ResponseEntity.status(exchange.getStatusCode()).body(responseBody); + } + Object responseEnvelope; try { @@ -198,4 +207,13 @@ public void assertFailure(ResponseEntity response, ErrorCodes error) { assertEquals(error.getDescription(), envelope.getError().getDescription()); assertNull(envelope.getData()); } + + public void assertFailure(ResponseEntity response, HttpStatus status, String message) { + assertNotNull(response); + assertEquals(status.value(), response.getStatusCode().value()); + + String result = (String) response.getBody(); + assertNotNull(result); + assertEquals(message, result); + } } \ No newline at end of file diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index 541ce1c0..93effeb9 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; import org.springframework.test.context.ActiveProfiles; import pro.beerpong.api.TestUtils; import pro.beerpong.api.model.dto.ErrorCodes; @@ -204,4 +205,82 @@ public void group_findByInviteCode_invalidInviteCode() { response = testUtils.performGet(port, "/groups?inviteCode=someIdThatNotExists", GroupDto.class); testUtils.assertFailure(response, ErrorCodes.GROUP_INVITE_NOT_FOUND); } + + @Test + @Transactional + public void group_findById_success() { + var createDto = new GroupCreateDto(); + createDto.setProfileNames(List.of("player1", "player2")); + createDto.setName("test"); + createDto.setSportPreset("beerpong"); + + var prerequisiteResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + var prerequisiteGroup = testUtils.assertSuccess(prerequisiteResponse, GroupDto.class); + + var response = testUtils.performGet(port, "/groups/" + prerequisiteGroup.getId(), GroupDto.class); + var group = testUtils.assertSuccess(response, GroupDto.class); + + // if this is not here, the startDate millis are rounded and this test fails + group.getActiveSeason().setStartDate(prerequisiteGroup.getActiveSeason().getStartDate()); + group.setCreatedAt(prerequisiteGroup.getCreatedAt()); + + assertNotNull(group); + assertEquals(prerequisiteGroup, group); + } + + @Test + public void group_findById_invalidId() { + var response = testUtils.performGet(port, "/groups/someIdThatNotExists", GroupDto.class); + testUtils.assertFailure(response, HttpStatus.UNAUTHORIZED, "No access to this group!"); + } + + @Test + @Transactional + public void group_update_success() { + var createDto = new GroupCreateDto(); + createDto.setProfileNames(List.of("player1", "player2")); + createDto.setName("test"); + createDto.setSportPreset("beerpong"); + + var prerequisiteResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + var prerequisiteGroup = testUtils.assertSuccess(prerequisiteResponse, GroupDto.class); + + createDto.setName("test123"); + createDto.setCustomSportName("beerpong"); + createDto.setProfileNames(List.of()); + + var response = testUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); + var group = testUtils.assertSuccess(response, GroupDto.class); + + // if this is not here, the startDate millis are rounded and this test fails + group.getActiveSeason().setStartDate(prerequisiteGroup.getActiveSeason().getStartDate()); + group.setCreatedAt(prerequisiteGroup.getCreatedAt()); + + assertNotNull(prerequisiteGroup); + assertNotNull(group); + assertEquals(prerequisiteGroup.getId(), group.getId()); + assertEquals(createDto.getName(), group.getName()); + assertEquals(prerequisiteGroup.getInviteCode(), group.getInviteCode()); + assertEquals(prerequisiteGroup.getCreatedAt(), group.getCreatedAt()); + assertEquals(prerequisiteGroup.getWallpaperAsset(), group.getWallpaperAsset()); + assertEquals(prerequisiteGroup.getCustomSportName(), group.getCustomSportName()); + assertEquals(prerequisiteGroup.getSportPreset(), group.getSportPreset()); + assertEquals(prerequisiteGroup.getActiveSeason(), group.getActiveSeason()); + } + + @Test + public void group_update_invalidName() { + var createDto = new GroupCreateDto(); + createDto.setProfileNames(List.of("player1", "player2")); + createDto.setName("test123"); + createDto.setSportPreset("beerpong"); + + var prerequisiteResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + var prerequisiteGroup = testUtils.assertSuccess(prerequisiteResponse, GroupDto.class); + + createDto.setName(" "); + + var response = testUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); + testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + } } \ No newline at end of file From 47a57123227e0be608d63251adf23b2635b07ae3 Mon Sep 17 00:00:00 2001 From: Thies Date: Tue, 10 Jun 2025 13:37:03 +0200 Subject: [PATCH 36/90] group join tests --- .../test/java/pro/beerpong/api/TestUtils.java | 43 +++++++++++++++---- .../api/control/GroupControllerTest.java | 33 ++++++++++++++ 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index 5ee242c4..128db250 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -22,6 +22,7 @@ public class TestUtils { private static List GROUP_ACCESS = Lists.newArrayList(); private static String REFRESH_TOKEN; private static String AUTH_TOKEN; + private static boolean RESET_FOR_NEXT_REQUEST = false; private final TestRestTemplate restTemplate; @@ -45,9 +46,21 @@ public ResponseEntity performDelete(int port, String path, Object body, return performCall(true, port, path, HttpMethod.DELETE, body, firstClazz, classes); } + public void resetAuthForNextRequest() { + RESET_FOR_NEXT_REQUEST = true; + } + + public void resetAuth() { + REFRESH_TOKEN = null; + AUTH_TOKEN = null; + GROUP_ACCESS.clear(); + } + @SuppressWarnings("unchecked") - private String generateAuthToken(int port) { - if (REFRESH_TOKEN == null) { + private String generateAuthToken(int port, boolean force) { + String tempRefresh = REFRESH_TOKEN; + + if (force || REFRESH_TOKEN == null) { var signupDto = new AuthSignupDto(); signupDto.setDeviceId("test"); signupDto.setInstallationType(InstallationType.IOS); @@ -68,12 +81,19 @@ private String generateAuthToken(int port) { assertEquals(TokenType.REFRESH, signupTokenDto.getType()); assertNotNull(signupTokenDto.getToken()); - REFRESH_TOKEN = signupTokenDto.getToken(); + tempRefresh = signupTokenDto.getToken(); + + if (!force) { + REFRESH_TOKEN = tempRefresh; + } + } - if (AUTH_TOKEN == null) { + var tempAuth = AUTH_TOKEN; + + if (force || AUTH_TOKEN == null) { var refreshDto = new AuthRefreshDto(); - refreshDto.setRefreshToken(REFRESH_TOKEN); + refreshDto.setRefreshToken(tempRefresh); var refreshResponse = this.performCall(false, port, "/auth/refresh", HttpMethod.POST, refreshDto, AuthTokenDto.class); var refreshEnvelope = (ResponseEnvelope) refreshResponse.getBody(); @@ -91,21 +111,26 @@ private String generateAuthToken(int port) { assertEquals(TokenType.ACCESS, refreshTokenDto.getType()); assertNotNull(refreshTokenDto.getToken()); - AUTH_TOKEN = refreshTokenDto.getToken(); + tempAuth = refreshTokenDto.getToken(); + + if (!force) { + AUTH_TOKEN = tempAuth; + } } - return AUTH_TOKEN; + return tempAuth; } public ResponseEntity performCall(boolean withAuth, int port, String path, HttpMethod method, Object body, Class firstClazz, Class... classes) { HttpHeaders headers = new HttpHeaders(); if (withAuth) { - headers.setBearerAuth(generateAuthToken(port)); + headers.setBearerAuth(generateAuthToken(port, RESET_FOR_NEXT_REQUEST)); } - headers.setContentType(MediaType.APPLICATION_JSON); + RESET_FOR_NEXT_REQUEST = false; + headers.setContentType(MediaType.APPLICATION_JSON); System.out.println(headers.get("Authorization")); var entity = (body == null ? new HttpEntity<>(headers) : new HttpEntity<>(body, headers)); diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index 93effeb9..76fc0746 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -283,4 +283,37 @@ public void group_update_invalidName() { var response = testUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); } + + @Test + @Transactional + public void group_join_success() { + var createDto = new GroupCreateDto(); + createDto.setProfileNames(List.of("player1", "player2")); + createDto.setName("test"); + createDto.setSportPreset("beerpong"); + + var prerequisiteResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + var prerequisiteGroup = testUtils.assertSuccess(prerequisiteResponse, GroupDto.class); + + testUtils.resetAuthForNextRequest(); + + var response = testUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/join", null, String.class); + var ok = testUtils.assertSuccess(response, String.class); + + assertEquals("OK", ok); + } + + @Test + public void group_join_alreadyMember() { + var createDto = new GroupCreateDto(); + createDto.setProfileNames(List.of("player1", "player2")); + createDto.setName("test"); + createDto.setSportPreset("beerpong"); + + var prerequisiteResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + var prerequisiteGroup = testUtils.assertSuccess(prerequisiteResponse, GroupDto.class); + + var response = testUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/join", null, String.class); + testUtils.assertFailure(response, ErrorCodes.GROUP_ALREADY_IN_GROUP); + } } \ No newline at end of file From 7f20ed43ce48882ee483c68fd6cc1ee74eb400dd Mon Sep 17 00:00:00 2001 From: Thies Date: Tue, 10 Jun 2025 13:40:39 +0200 Subject: [PATCH 37/90] group tests complete --- .../api/control/GroupControllerTest.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index 76fc0746..78db9ec1 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -295,16 +295,20 @@ public void group_join_success() { var prerequisiteResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); var prerequisiteGroup = testUtils.assertSuccess(prerequisiteResponse, GroupDto.class); + var response = testUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/join", null, String.class); + testUtils.assertFailure(response, ErrorCodes.GROUP_ALREADY_IN_GROUP); + testUtils.resetAuthForNextRequest(); - var response = testUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/join", null, String.class); + response = testUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/join", null, String.class); var ok = testUtils.assertSuccess(response, String.class); assertEquals("OK", ok); } @Test - public void group_join_alreadyMember() { + @Transactional + public void group_leave() { var createDto = new GroupCreateDto(); createDto.setProfileNames(List.of("player1", "player2")); createDto.setName("test"); @@ -313,7 +317,17 @@ public void group_join_alreadyMember() { var prerequisiteResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); var prerequisiteGroup = testUtils.assertSuccess(prerequisiteResponse, GroupDto.class); - var response = testUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/join", null, String.class); - testUtils.assertFailure(response, ErrorCodes.GROUP_ALREADY_IN_GROUP); + testUtils.resetAuthForNextRequest(); + + var response = testUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/leave", null, String.class); + testUtils.assertFailure(response, ErrorCodes.AUTH_USER_NOT_IN_GROUP); + + response = testUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/leave", null, String.class); + var ok = testUtils.assertSuccess(response, String.class); + + assertEquals("OK", ok); + + response = testUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/leave", null, String.class); + testUtils.assertFailure(response, ErrorCodes.AUTH_USER_NOT_IN_GROUP); } } \ No newline at end of file From 5e7b35c398fd321c72bfbb100a422dc32719d0aa Mon Sep 17 00:00:00 2001 From: Thies Date: Tue, 10 Jun 2025 13:44:35 +0200 Subject: [PATCH 38/90] fixed group tests --- api/src/test/java/pro/beerpong/api/TestUtils.java | 3 +-- .../pro/beerpong/api/control/GroupControllerTest.java | 8 -------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index 128db250..8a7503c5 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -131,8 +131,7 @@ public ResponseEntity performCall(boolean withAuth, int port, String pat RESET_FOR_NEXT_REQUEST = false; headers.setContentType(MediaType.APPLICATION_JSON); - System.out.println(headers.get("Authorization")); - + var entity = (body == null ? new HttpEntity<>(headers) : new HttpEntity<>(body, headers)); var exchange = restTemplate.exchange("http://localhost:" + port + path, method, entity, String.class); diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index 78db9ec1..3b1c0865 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -317,17 +317,9 @@ public void group_leave() { var prerequisiteResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); var prerequisiteGroup = testUtils.assertSuccess(prerequisiteResponse, GroupDto.class); - testUtils.resetAuthForNextRequest(); - var response = testUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/leave", null, String.class); - testUtils.assertFailure(response, ErrorCodes.AUTH_USER_NOT_IN_GROUP); - - response = testUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/leave", null, String.class); var ok = testUtils.assertSuccess(response, String.class); assertEquals("OK", ok); - - response = testUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/leave", null, String.class); - testUtils.assertFailure(response, ErrorCodes.AUTH_USER_NOT_IN_GROUP); } } \ No newline at end of file From 0c25563ff4f3617e010065616839a4d70ceb8aa9 Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 12:18:59 +0200 Subject: [PATCH 39/90] added createTestGroup methods --- .../test/java/pro/beerpong/api/TestUtils.java | 41 ++++- .../api/control/AssetControllerTest.java | 33 ++-- .../api/control/GroupControllerTest.java | 145 +++--------------- .../api/control/SeasonControllerTest.java | 34 ++++ 4 files changed, 111 insertions(+), 142 deletions(-) create mode 100644 api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index 8a7503c5..df02cf9f 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -19,7 +19,9 @@ @Component public class TestUtils { - private static List GROUP_ACCESS = Lists.newArrayList(); + //TODO leaderboard, match, player, assets (needs s3 files), profile, rules, rulemoves, seasons + + private static final List GROUP_ACCESS = Lists.newArrayList(); private static String REFRESH_TOKEN; private static String AUTH_TOKEN; private static boolean RESET_FOR_NEXT_REQUEST = false; @@ -131,7 +133,7 @@ public ResponseEntity performCall(boolean withAuth, int port, String pat RESET_FOR_NEXT_REQUEST = false; headers.setContentType(MediaType.APPLICATION_JSON); - + var entity = (body == null ? new HttpEntity<>(headers) : new HttpEntity<>(body, headers)); var exchange = restTemplate.exchange("http://localhost:" + port + path, method, entity, String.class); @@ -240,4 +242,39 @@ public void assertFailure(ResponseEntity response, HttpStatus status, St assertNotNull(result); assertEquals(message, result); } + + public GroupDto createTestGroup(int port) { + return this.createTestGroup(port, "test"); + } + + public GroupDto createTestGroup(int port, String name) { + return this.createTestGroup(port, name, List.of("player1", "player2")); + } + + public GroupDto createTestGroup(int port, List profileNames) { + return this.createTestGroup(port, "test", profileNames); + } + + public GroupDto createTestGroup(int port, String name, List profileNames) { + return this.createTestGroup(port, name, profileNames, "beerpong", null); + } + + public GroupDto createTestGroup(int port, String name, List profileNames, String preset, String customSportName) { + var response = postGroup(port, name, profileNames, preset, customSportName); + return assertSuccess(response, GroupDto.class); + } + + public ResponseEntity postGroup(int port, String name, List profileNames, String preset) { + return this.postGroup(port, name, profileNames, preset, null); + } + + public ResponseEntity postGroup(int port, String name, List profileNames, String preset, String customSportName) { + var createDto = new GroupCreateDto(); + createDto.setProfileNames(profileNames); + createDto.setName(name); + createDto.setSportPreset(preset); + createDto.setCustomSportName(customSportName); + + return performPost(port, "/groups", createDto, GroupDto.class); + } } \ No newline at end of file diff --git a/api/src/test/java/pro/beerpong/api/control/AssetControllerTest.java b/api/src/test/java/pro/beerpong/api/control/AssetControllerTest.java index 22813f8b..be9b1394 100644 --- a/api/src/test/java/pro/beerpong/api/control/AssetControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/AssetControllerTest.java @@ -11,17 +11,22 @@ import org.springframework.transaction.support.DefaultTransactionDefinition; import pro.beerpong.api.TestUtils; import pro.beerpong.api.model.dto.AssetMetadataDto; +import pro.beerpong.api.model.dto.GroupCreateDto; +import pro.beerpong.api.model.dto.GroupDto; import pro.beerpong.api.model.dto.ResponseEnvelope; import java.time.Duration; import java.time.ZonedDateTime; +import java.util.List; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("test") public class AssetControllerTest { - /*@LocalServerPort + /* + TODO needs s3 files + @LocalServerPort private int port; @Autowired @@ -40,26 +45,16 @@ public void createTransaction() { @Test @SuppressWarnings("unchecked") - public void whenUploadingAsset_ThenIsSuccessful() { - var response = testUtils.performPost(port, "/assets", new byte[] {-128, 0, 127, 0}, AssetMetadataDto.class); + public void assets_groupWallpaper_success() { + var createDto = new GroupCreateDto(); + createDto.setProfileNames(List.of("player1", "player2")); + createDto.setName("test"); + createDto.setSportPreset("beerpong"); - assertNotNull(response); - assertEquals(200, response.getStatusCode().value()); - - var envelope = (ResponseEnvelope) response.getBody(); - assertNotNull(envelope); - assertEquals(ResponseEnvelope.Status.OK, envelope.getStatus()); - assertNull(envelope.getError()); - assertEquals(200, envelope.getHttpCode()); - - var assetMetadata = envelope.getData(); + var groupResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + var group = testUtils.assertSuccess(groupResponse, GroupDto.class); - assertNotNull(assetMetadata); - assertNotNull(assetMetadata.getId()); - assertNotNull(assetMetadata.getMediaType()); - assertEquals("application/octet-stream", assetMetadata.getMediaType()); - assertNotNull(assetMetadata.getUploadedAt()); - assertTrue(60 > Duration.between(ZonedDateTime.now(),assetMetadata.getUploadedAt()).getSeconds()); + var response = testUtils.performPut(port, "/groups/" + group.getId(), group, AssetMetadataDto.class); } @Test diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index 3b1c0865..0d52b092 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -30,18 +30,13 @@ public class GroupControllerTest { @Test @Transactional public void group_create_success() { - var createDto = new GroupCreateDto(); - createDto.setProfileNames(List.of("player1", "player2")); - createDto.setName("test"); - createDto.setSportPreset("beerpong"); - - var response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); - var group = testUtils.assertSuccess(response, GroupDto.class); + var name = "test"; + var group = testUtils.createTestGroup(port, name); assertNotNull(group); assertNotNull(group.getId()); assertNotNull(group.getName()); - assertEquals(createDto.getName(), group.getName()); + assertEquals(name, group.getName()); assertNotNull(group.getInviteCode()); assertNotNull(group.getCreatedAt()); assertNull(group.getWallpaperAsset()); @@ -57,13 +52,7 @@ public void group_create_success() { assertNotNull(group.getActiveSeason().getSeasonSettings()); assertEquals(group.getCreatedBy(), group.getActiveSeason().getCreatedBy()); - createDto = new GroupCreateDto(); - createDto.setProfileNames(List.of("player1", "player2")); - createDto.setName("test"); - createDto.setCustomSportName("test123"); - - response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); - group = testUtils.assertSuccess(response, GroupDto.class); + group = testUtils.createTestGroup(port, "test", List.of("player1", "player2"), null, "test123"); assertNotNull(group); assertEquals("test123", group.getCustomSportName()); @@ -73,83 +62,39 @@ public void group_create_success() { @Test @Transactional public void group_create_invalidName() { - var createDto = new GroupCreateDto(); - createDto.setProfileNames(List.of("player1", "player2")); - createDto.setName(""); - createDto.setSportPreset("beerpong"); - - var response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + var response = testUtils.postGroup(port, "", List.of("player1", "player2"), "beerpong"); testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); - createDto = new GroupCreateDto(); - createDto.setProfileNames(List.of("player1", "player2")); - createDto.setName(null); - createDto.setSportPreset("beerpong"); - - response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + response = testUtils.postGroup(port, null, List.of("player1", "player2"), "beerpong"); testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); - createDto = new GroupCreateDto(); - createDto.setProfileNames(List.of("player1", "player2")); - createDto.setName("a"); - createDto.setSportPreset("beerpong"); - - response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + response = testUtils.postGroup(port, "a", List.of("player1", "player2"), "beerpong"); testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); - createDto = new GroupCreateDto(); - createDto.setProfileNames(List.of("player1", "player2")); - createDto.setName("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - createDto.setSportPreset("beerpong"); - - response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + response = testUtils.postGroup(port, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", List.of("player1", "player2"), "beerpong"); testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); } @Test @Transactional public void group_create_invalidProfiles() { - var createDto = new GroupCreateDto(); - createDto.setProfileNames(List.of()); - createDto.setName("test"); - createDto.setSportPreset("beerpong"); - - var response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + var response = testUtils.postGroup(port, "test", List.of(), "beerpong"); testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_PROFILE_NAMES); - createDto = new GroupCreateDto(); - createDto.setProfileNames(null); - createDto.setName("test"); - createDto.setSportPreset("beerpong"); - - response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + response = testUtils.postGroup(port, "test", null, "beerpong"); testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_PROFILE_NAMES); } @Test @Transactional public void group_create_invalidSport() { - var createDto = new GroupCreateDto(); - createDto.setProfileNames(List.of("player1", "player2")); - createDto.setName("test"); - createDto.setSportPreset("notExisting"); - - var response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + var response = testUtils.postGroup(port, "test", List.of("player1", "player2"), "notExisting"); testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); - createDto = new GroupCreateDto(); - createDto.setProfileNames(List.of("player1", "player2")); - createDto.setName("test"); - - response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + response = testUtils.postGroup(port, "test", List.of("player1", "player2"), null); testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); - createDto = new GroupCreateDto(); - createDto.setProfileNames(List.of("player1", "player2")); - createDto.setName("test"); - createDto.setCustomSportName(" "); - - response = testUtils.performPost(port, "/groups", createDto, GroupDto.class); + response = testUtils.postGroup(port, "test", List.of("player1", "player2"), null, " "); testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); } @@ -157,19 +102,11 @@ public void group_create_invalidSport() { @Transactional @SuppressWarnings("unchecked") public void group_userGroups() { - var createDto = new GroupCreateDto(); - createDto.setProfileNames(List.of("player1", "player2")); - createDto.setName("test"); - createDto.setSportPreset("beerpong"); - - var prerequisiteResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); - var prerequisiteGroup = testUtils.assertSuccess(prerequisiteResponse, GroupDto.class); + var prerequisiteGroup = testUtils.createTestGroup(port); var response = testUtils.performGet(port, "/groups/user", List.class, GroupDto.class); var groups = (List) testUtils.assertSuccess(response, ArrayList.class); - - assertFalse(groups.isEmpty()); assertTrue(groups.stream().anyMatch(groupDto -> groupDto.getId().equals(prerequisiteGroup.getId()))); } @@ -177,13 +114,7 @@ public void group_userGroups() { @Test @Transactional public void group_findByInviteCode_success() { - var createDto = new GroupCreateDto(); - createDto.setProfileNames(List.of("player1", "player2")); - createDto.setName("test"); - createDto.setSportPreset("beerpong"); - - var prerequisiteResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); - var prerequisiteGroup = testUtils.assertSuccess(prerequisiteResponse, GroupDto.class); + var prerequisiteGroup = testUtils.createTestGroup(port); var response = testUtils.performGet(port, "/groups?inviteCode=" + prerequisiteGroup.getInviteCode(), GroupDto.class); var group = testUtils.assertSuccess(response, GroupDto.class); @@ -209,13 +140,7 @@ public void group_findByInviteCode_invalidInviteCode() { @Test @Transactional public void group_findById_success() { - var createDto = new GroupCreateDto(); - createDto.setProfileNames(List.of("player1", "player2")); - createDto.setName("test"); - createDto.setSportPreset("beerpong"); - - var prerequisiteResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); - var prerequisiteGroup = testUtils.assertSuccess(prerequisiteResponse, GroupDto.class); + var prerequisiteGroup = testUtils.createTestGroup(port); var response = testUtils.performGet(port, "/groups/" + prerequisiteGroup.getId(), GroupDto.class); var group = testUtils.assertSuccess(response, GroupDto.class); @@ -237,17 +162,12 @@ public void group_findById_invalidId() { @Test @Transactional public void group_update_success() { - var createDto = new GroupCreateDto(); - createDto.setProfileNames(List.of("player1", "player2")); - createDto.setName("test"); - createDto.setSportPreset("beerpong"); - - var prerequisiteResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); - var prerequisiteGroup = testUtils.assertSuccess(prerequisiteResponse, GroupDto.class); + var prerequisiteGroup = testUtils.createTestGroup(port); + var createDto = new GroupCreateDto(); createDto.setName("test123"); - createDto.setCustomSportName("beerpong"); - createDto.setProfileNames(List.of()); + createDto.setCustomSportName("kicker"); + createDto.setProfileNames(List.of("player3", "player4", "player1")); var response = testUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); var group = testUtils.assertSuccess(response, GroupDto.class); @@ -270,14 +190,9 @@ public void group_update_success() { @Test public void group_update_invalidName() { - var createDto = new GroupCreateDto(); - createDto.setProfileNames(List.of("player1", "player2")); - createDto.setName("test123"); - createDto.setSportPreset("beerpong"); - - var prerequisiteResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); - var prerequisiteGroup = testUtils.assertSuccess(prerequisiteResponse, GroupDto.class); + var prerequisiteGroup = testUtils.createTestGroup(port); + var createDto = new GroupCreateDto(); createDto.setName(" "); var response = testUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); @@ -287,13 +202,7 @@ public void group_update_invalidName() { @Test @Transactional public void group_join_success() { - var createDto = new GroupCreateDto(); - createDto.setProfileNames(List.of("player1", "player2")); - createDto.setName("test"); - createDto.setSportPreset("beerpong"); - - var prerequisiteResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); - var prerequisiteGroup = testUtils.assertSuccess(prerequisiteResponse, GroupDto.class); + var prerequisiteGroup = testUtils.createTestGroup(port); var response = testUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/join", null, String.class); testUtils.assertFailure(response, ErrorCodes.GROUP_ALREADY_IN_GROUP); @@ -309,13 +218,7 @@ public void group_join_success() { @Test @Transactional public void group_leave() { - var createDto = new GroupCreateDto(); - createDto.setProfileNames(List.of("player1", "player2")); - createDto.setName("test"); - createDto.setSportPreset("beerpong"); - - var prerequisiteResponse = testUtils.performPost(port, "/groups", createDto, GroupDto.class); - var prerequisiteGroup = testUtils.assertSuccess(prerequisiteResponse, GroupDto.class); + var prerequisiteGroup = testUtils.createTestGroup(port); var response = testUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/leave", null, String.class); var ok = testUtils.assertSuccess(response, String.class); diff --git a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java new file mode 100644 index 00000000..1163a63f --- /dev/null +++ b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java @@ -0,0 +1,34 @@ +package pro.beerpong.api.control; + +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.ActiveProfiles; +import pro.beerpong.api.TestUtils; +import pro.beerpong.api.model.dto.ErrorCodes; +import pro.beerpong.api.model.dto.GroupCreateDto; +import pro.beerpong.api.model.dto.GroupDto; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") +public class SeasonControllerTest { + @LocalServerPort + private int port; + + @Autowired + private TestUtils testUtils; + + @Test + @Transactional + public void season_start_success() { + + } +} \ No newline at end of file From 12000b67c092294391bfa626c0e115e85a4f4569 Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 13:03:02 +0200 Subject: [PATCH 40/90] added requestutils + start season tests --- .../pro/beerpong/api/CreateMatchTest.java | 26 +- .../java/pro/beerpong/api/RequestUtils.java | 242 ++++++++++++++++ .../test/java/pro/beerpong/api/TestUtils.java | 265 ++---------------- .../api/control/AssetControllerTest.java | 18 -- .../api/control/GroupControllerTest.java | 68 ++--- .../api/control/SeasonControllerTest.java | 81 +++++- 6 files changed, 396 insertions(+), 304 deletions(-) create mode 100644 api/src/test/java/pro/beerpong/api/RequestUtils.java diff --git a/api/src/test/java/pro/beerpong/api/CreateMatchTest.java b/api/src/test/java/pro/beerpong/api/CreateMatchTest.java index 09a72b7a..d46ddd8d 100644 --- a/api/src/test/java/pro/beerpong/api/CreateMatchTest.java +++ b/api/src/test/java/pro/beerpong/api/CreateMatchTest.java @@ -19,7 +19,7 @@ public class CreateMatchTest { private int port; @Autowired - private TestUtils testUtils; + private RequestUtils requestUtils; // @BeforeEach // void setUp() { @@ -39,7 +39,7 @@ void createMatch_withTeamsMembersAndMoves_shouldCreateMatchTeamsAndMoves() throw createGroupDto.setName("test"); createGroupDto.setSportPreset("beerpong"); - var prerequisiteGroupResponse = testUtils.performPost(port, "/groups", createGroupDto, GroupDto.class); + var prerequisiteGroupResponse = requestUtils.performPost(port, "/groups", createGroupDto, GroupDto.class); assertNotNull(prerequisiteGroupResponse); assertEquals(200, prerequisiteGroupResponse.getStatusCode().value()); @@ -52,7 +52,7 @@ void createMatch_withTeamsMembersAndMoves_shouldCreateMatchTeamsAndMoves() throw var prerequisiteGroup = prerequisiteEnvelope.getData(); assertEquals(GroupPresetsController.BEERPONG.getId(), prerequisiteGroup.getSportPreset().getId()); - var response = testUtils.performGet(port, "/groups?inviteCode=" + prerequisiteGroup.getInviteCode(), GroupDto.class); + var response = requestUtils.performGet(port, "/groups?inviteCode=" + prerequisiteGroup.getInviteCode(), GroupDto.class); assertNotNull(response); assertEquals(200, response.getStatusCode().value()); @@ -79,7 +79,7 @@ void createMatch_withTeamsMembersAndMoves_shouldCreateMatchTeamsAndMoves() throw var season = group.getActiveSeason(); - var playersResponse = testUtils.performGet(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/players", List.class, PlayerDto.class); + var playersResponse = requestUtils.performGet(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/players", List.class, PlayerDto.class); assertNotNull(playersResponse); assertEquals(200, playersResponse.getStatusCode().value()); @@ -103,8 +103,8 @@ void createMatch_withTeamsMembersAndMoves_shouldCreateMatchTeamsAndMoves() throw createRulemMoveDto1.setPointsForTeam(1); createRulemMoveDto1.setPointsForScorer(1); - var ruleMoveResponse = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRulemMoveDto, RuleMoveDto.class); - var ruleMoveResponse1 = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRulemMoveDto1, RuleMoveDto.class); + var ruleMoveResponse = requestUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRulemMoveDto, RuleMoveDto.class); + var ruleMoveResponse1 = requestUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRulemMoveDto1, RuleMoveDto.class); assertNotNull(ruleMoveResponse); assertNotNull(ruleMoveResponse1); @@ -186,7 +186,7 @@ void createMatch_withTeamsMembersAndMoves_shouldCreateMatchTeamsAndMoves() throw matchCreateDto.setTeams(List.of(team1, team2)); - var createMatchResponse = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches", matchCreateDto, MatchDto.class); + var createMatchResponse = requestUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches", matchCreateDto, MatchDto.class); assertNotNull(createMatchResponse); assertEquals(200, createMatchResponse.getStatusCode().value()); @@ -207,13 +207,13 @@ void updateMatch_withTeamsMembersAndMoves_shouldUpdateMatchTeamsAndMoves() throw createGroupDto.setName("test-update"); createGroupDto.setSportPreset("beerpong"); - var groupResponse = testUtils.performPost(port, "/groups", createGroupDto, GroupDto.class); + var groupResponse = requestUtils.performPost(port, "/groups", createGroupDto, GroupDto.class); ResponseEnvelope groupEnvelope = (ResponseEnvelope) groupResponse.getBody(); var group = groupEnvelope.getData(); var season = group.getActiveSeason(); - var playersResponse = testUtils.performGet(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/players", List.class, PlayerDto.class); + var playersResponse = requestUtils.performGet(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/players", List.class, PlayerDto.class); ResponseEnvelope> playersEnvelope = (ResponseEnvelope>) playersResponse.getBody(); var players = playersEnvelope.getData(); @@ -224,8 +224,8 @@ void updateMatch_withTeamsMembersAndMoves_shouldUpdateMatchTeamsAndMoves() throw createRuleMoveDto1.setName("UpdateRuleMove"); createRuleMoveDto1.setFinishingMove(true); - var ruleMoveResponse = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRuleMoveDto, RuleMoveDto.class); - var ruleMoveResponse1 = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRuleMoveDto1, RuleMoveDto.class); + var ruleMoveResponse = requestUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRuleMoveDto, RuleMoveDto.class); + var ruleMoveResponse1 = requestUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRuleMoveDto1, RuleMoveDto.class); ResponseEnvelope ruleMoveEnvelope = (ResponseEnvelope) ruleMoveResponse.getBody(); ResponseEnvelope ruleMoveEnvelope1 = (ResponseEnvelope) ruleMoveResponse1.getBody(); var ruleMove = ruleMoveEnvelope.getData(); @@ -257,7 +257,7 @@ void updateMatch_withTeamsMembersAndMoves_shouldUpdateMatchTeamsAndMoves() throw matchCreateDto.setTeams(List.of(team1, team2)); // Step 4: Create the match - var createMatchResponse = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches", matchCreateDto, MatchDto.class); + var createMatchResponse = requestUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches", matchCreateDto, MatchDto.class); ResponseEnvelope matchEnvelope = (ResponseEnvelope) createMatchResponse.getBody(); assert matchEnvelope != null; var match = matchEnvelope.getData(); @@ -266,7 +266,7 @@ void updateMatch_withTeamsMembersAndMoves_shouldUpdateMatchTeamsAndMoves() throw matchCreateDto.setTeams(List.of(team1, team2)); // Step 5: Update the match - var updateResponse = testUtils.performPut(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches/" + match.getId(), matchCreateDto, MatchDto.class); + var updateResponse = requestUtils.performPut(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches/" + match.getId(), matchCreateDto, MatchDto.class); ResponseEnvelope updateEnvelope = (ResponseEnvelope) updateResponse.getBody(); // Assertions diff --git a/api/src/test/java/pro/beerpong/api/RequestUtils.java b/api/src/test/java/pro/beerpong/api/RequestUtils.java new file mode 100644 index 00000000..20683710 --- /dev/null +++ b/api/src/test/java/pro/beerpong/api/RequestUtils.java @@ -0,0 +1,242 @@ +package pro.beerpong.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.api.client.util.Lists; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.*; +import org.springframework.stereotype.Component; +import pro.beerpong.api.model.dto.*; +import pro.beerpong.api.util.InstallationType; +import pro.beerpong.api.util.TokenType; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Component +public class RequestUtils { + private static final List GROUP_ACCESS = Lists.newArrayList(); + private static String REFRESH_TOKEN; + private static String AUTH_TOKEN; + private static boolean RESET_FOR_NEXT_REQUEST = false; + + private final TestRestTemplate restTemplate; + + public RequestUtils(TestRestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + public ResponseEntity performGet(int port, String path, Class firstClazz, Class... classes) { + return performCall(true, port, path, HttpMethod.GET, null, firstClazz, classes); + } + + public ResponseEntity performPost(int port, String path, Object body, Class firstClazz, Class... classes) { + return performCall(true, port, path, HttpMethod.POST, body, firstClazz, classes); + } + + public ResponseEntity performPut(int port, String path, Object body, Class firstClazz, Class... classes) { + return performCall(true, port, path, HttpMethod.PUT, body, firstClazz, classes); + } + + public ResponseEntity performDelete(int port, String path, Object body, Class firstClazz, Class... classes) { + return performCall(true, port, path, HttpMethod.DELETE, body, firstClazz, classes); + } + + public void resetAuthForNextRequest() { + RESET_FOR_NEXT_REQUEST = true; + } + + public void resetAuth() { + REFRESH_TOKEN = null; + AUTH_TOKEN = null; + GROUP_ACCESS.clear(); + } + + @SuppressWarnings("unchecked") + private String generateAuthToken(int port, boolean force) { + String tempRefresh = REFRESH_TOKEN; + + if (force || REFRESH_TOKEN == null) { + var signupDto = new AuthSignupDto(); + signupDto.setDeviceId("test"); + signupDto.setInstallationType(InstallationType.IOS); + + var signupResponse = this.performCall(false, port, "/auth/signup", HttpMethod.POST, signupDto, AuthTokenDto.class); + var signupEnvelope = (ResponseEnvelope) signupResponse.getBody(); + assert signupEnvelope != null; + var signupTokenDto = signupEnvelope.getData(); + + // Assertions + assertNotNull(signupResponse); + assertEquals(200, signupResponse.getStatusCode().value()); + assertNotNull(signupEnvelope); + assertEquals(ResponseEnvelope.Status.OK, signupEnvelope.getStatus()); + assertNull(signupEnvelope.getError()); + assertEquals(200, signupEnvelope.getHttpCode()); + assertNotNull(signupTokenDto); + assertEquals(TokenType.REFRESH, signupTokenDto.getType()); + assertNotNull(signupTokenDto.getToken()); + + tempRefresh = signupTokenDto.getToken(); + + if (!force) { + REFRESH_TOKEN = tempRefresh; + } + + } + + var tempAuth = AUTH_TOKEN; + + if (force || AUTH_TOKEN == null) { + var refreshDto = new AuthRefreshDto(); + refreshDto.setRefreshToken(tempRefresh); + + var refreshResponse = this.performCall(false, port, "/auth/refresh", HttpMethod.POST, refreshDto, AuthTokenDto.class); + var refreshEnvelope = (ResponseEnvelope) refreshResponse.getBody(); + assert refreshEnvelope != null; + var refreshTokenDto = refreshEnvelope.getData(); + + // Assertions + assertNotNull(refreshResponse); + assertEquals(200, refreshResponse.getStatusCode().value()); + assertNotNull(refreshEnvelope); + assertEquals(ResponseEnvelope.Status.OK, refreshEnvelope.getStatus()); + assertNull(refreshEnvelope.getError()); + assertEquals(200, refreshEnvelope.getHttpCode()); + assertNotNull(refreshTokenDto); + assertEquals(TokenType.ACCESS, refreshTokenDto.getType()); + assertNotNull(refreshTokenDto.getToken()); + + tempAuth = refreshTokenDto.getToken(); + + if (!force) { + AUTH_TOKEN = tempAuth; + } + } + + return tempAuth; + } + + public ResponseEntity performCall(boolean withAuth, int port, String path, HttpMethod method, Object body, Class firstClazz, Class... classes) { + HttpHeaders headers = new HttpHeaders(); + + if (withAuth) { + headers.setBearerAuth(generateAuthToken(port, RESET_FOR_NEXT_REQUEST)); + } + + RESET_FOR_NEXT_REQUEST = false; + + headers.setContentType(MediaType.APPLICATION_JSON); + + var entity = (body == null ? new HttpEntity<>(headers) : new HttpEntity<>(body, headers)); + var exchange = restTemplate.exchange("http://localhost:" + port + path, method, entity, String.class); + + var objectMapper = new ObjectMapper(); + objectMapper.findAndRegisterModules(); + + var typeFactory = objectMapper.getTypeFactory(); + + JavaType valueType = null; + + int limit = classes.length + 1; + + for (int i = limit - 1; i >= 0; i--) { + + if (limit - 1 == 0) { + //list is empty + valueType = typeFactory.constructParametricType(ResponseEnvelope.class, firstClazz); + } else { + //list isn't empty + + if (i == 0) { + //firstClass height + + if (limit - 1 == 1) { + //list has just 1 element + valueType = typeFactory.constructParametricType(firstClazz, classes[i]); + } else { + //list has > 1 element + valueType = typeFactory.constructParametricType(firstClazz, valueType); + } + + valueType = typeFactory.constructParametricType(ResponseEnvelope.class, valueType); + } else { + //over firstClass height, inside classes list + + if (limit - 1 > 1 && i < limit - 1) { + //list has > 1 element + skip last pair + + if (valueType == null) { + valueType = typeFactory.constructParametricType(classes[i-1], classes[i]); + } else { + valueType = typeFactory.constructParametricType(classes[i-1], valueType); + } + } + } + } + } + + var responseBody = exchange.getBody(); + + if (responseBody == null) { + return ResponseEntity.status(exchange.getStatusCode()).build(); + } + + if (!responseBody.startsWith("{") && !responseBody.startsWith("[")) { + return ResponseEntity.status(exchange.getStatusCode()).body(responseBody); + } + + Object responseEnvelope; + + try { + responseEnvelope = objectMapper.readValue(responseBody, valueType); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + return ResponseEntity.status(exchange.getStatusCode()).body(responseEnvelope); + } + + @SuppressWarnings("unchecked") + public T assertSuccess(ResponseEntity response, Class tClass) { + assertNotNull(response); + assertEquals(200, response.getStatusCode().value()); + + ResponseEnvelope envelope = (ResponseEnvelope) response.getBody(); + assertNotNull(envelope); + assertEquals(ResponseEnvelope.Status.OK, envelope.getStatus()); + assertEquals(200, envelope.getHttpCode()); + assertNull(envelope.getError()); + assertNotNull(envelope.getData()); + assertEquals(tClass, envelope.getData().getClass()); + + return tClass.cast(envelope.getData()); + } + + @SuppressWarnings("unchecked") + public void assertFailure(ResponseEntity response, ErrorCodes error) { + assertNotNull(response); + assertEquals(error.getHttpStatus().value(), response.getStatusCode().value()); + + ResponseEnvelope envelope = (ResponseEnvelope) response.getBody(); + assertNotNull(envelope); + assertEquals(ResponseEnvelope.Status.ERROR, envelope.getStatus()); + assertEquals(error.getHttpStatus().value(), envelope.getHttpCode()); + assertNotNull(envelope.getError()); + assertEquals(error.getCode(), envelope.getError().getCode()); + assertEquals(error.getDescription(), envelope.getError().getDescription()); + assertNull(envelope.getData()); + } + + public void assertFailure(ResponseEntity response, HttpStatus status, String message) { + assertNotNull(response); + assertEquals(status.value(), response.getStatusCode().value()); + + String result = (String) response.getBody(); + assertNotNull(result); + assertEquals(message, result); + } +} \ No newline at end of file diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index df02cf9f..228f670a 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -1,248 +1,26 @@ package pro.beerpong.api; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.api.client.util.Lists; -import lombok.RequiredArgsConstructor; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.http.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; -import pro.beerpong.api.model.dto.*; -import pro.beerpong.api.util.InstallationType; -import pro.beerpong.api.util.TokenType; +import pro.beerpong.api.model.dto.GroupCreateDto; +import pro.beerpong.api.model.dto.GroupDto; +import pro.beerpong.api.model.dto.RuleMoveCreateDto; +import pro.beerpong.api.model.dto.RuleMoveDto; import java.util.List; -import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertEquals; @Component public class TestUtils { //TODO leaderboard, match, player, assets (needs s3 files), profile, rules, rulemoves, seasons + //TODO test realtime events - private static final List GROUP_ACCESS = Lists.newArrayList(); - private static String REFRESH_TOKEN; - private static String AUTH_TOKEN; - private static boolean RESET_FOR_NEXT_REQUEST = false; - - private final TestRestTemplate restTemplate; - - public TestUtils(TestRestTemplate restTemplate) { - this.restTemplate = restTemplate; - } - - public ResponseEntity performGet(int port, String path, Class firstClazz, Class... classes) { - return performCall(true, port, path, HttpMethod.GET, null, firstClazz, classes); - } - - public ResponseEntity performPost(int port, String path, Object body, Class firstClazz, Class... classes) { - return performCall(true, port, path, HttpMethod.POST, body, firstClazz, classes); - } - - public ResponseEntity performPut(int port, String path, Object body, Class firstClazz, Class... classes) { - return performCall(true, port, path, HttpMethod.PUT, body, firstClazz, classes); - } - - public ResponseEntity performDelete(int port, String path, Object body, Class firstClazz, Class... classes) { - return performCall(true, port, path, HttpMethod.DELETE, body, firstClazz, classes); - } - - public void resetAuthForNextRequest() { - RESET_FOR_NEXT_REQUEST = true; - } - - public void resetAuth() { - REFRESH_TOKEN = null; - AUTH_TOKEN = null; - GROUP_ACCESS.clear(); - } - - @SuppressWarnings("unchecked") - private String generateAuthToken(int port, boolean force) { - String tempRefresh = REFRESH_TOKEN; - - if (force || REFRESH_TOKEN == null) { - var signupDto = new AuthSignupDto(); - signupDto.setDeviceId("test"); - signupDto.setInstallationType(InstallationType.IOS); - - var signupResponse = this.performCall(false, port, "/auth/signup", HttpMethod.POST, signupDto, AuthTokenDto.class); - var signupEnvelope = (ResponseEnvelope) signupResponse.getBody(); - assert signupEnvelope != null; - var signupTokenDto = signupEnvelope.getData(); - - // Assertions - assertNotNull(signupResponse); - assertEquals(200, signupResponse.getStatusCode().value()); - assertNotNull(signupEnvelope); - assertEquals(ResponseEnvelope.Status.OK, signupEnvelope.getStatus()); - assertNull(signupEnvelope.getError()); - assertEquals(200, signupEnvelope.getHttpCode()); - assertNotNull(signupTokenDto); - assertEquals(TokenType.REFRESH, signupTokenDto.getType()); - assertNotNull(signupTokenDto.getToken()); - - tempRefresh = signupTokenDto.getToken(); - - if (!force) { - REFRESH_TOKEN = tempRefresh; - } - - } - - var tempAuth = AUTH_TOKEN; - - if (force || AUTH_TOKEN == null) { - var refreshDto = new AuthRefreshDto(); - refreshDto.setRefreshToken(tempRefresh); - - var refreshResponse = this.performCall(false, port, "/auth/refresh", HttpMethod.POST, refreshDto, AuthTokenDto.class); - var refreshEnvelope = (ResponseEnvelope) refreshResponse.getBody(); - assert refreshEnvelope != null; - var refreshTokenDto = refreshEnvelope.getData(); - - // Assertions - assertNotNull(refreshResponse); - assertEquals(200, refreshResponse.getStatusCode().value()); - assertNotNull(refreshEnvelope); - assertEquals(ResponseEnvelope.Status.OK, refreshEnvelope.getStatus()); - assertNull(refreshEnvelope.getError()); - assertEquals(200, refreshEnvelope.getHttpCode()); - assertNotNull(refreshTokenDto); - assertEquals(TokenType.ACCESS, refreshTokenDto.getType()); - assertNotNull(refreshTokenDto.getToken()); - - tempAuth = refreshTokenDto.getToken(); - - if (!force) { - AUTH_TOKEN = tempAuth; - } - } - - return tempAuth; - } - - public ResponseEntity performCall(boolean withAuth, int port, String path, HttpMethod method, Object body, Class firstClazz, Class... classes) { - HttpHeaders headers = new HttpHeaders(); - - if (withAuth) { - headers.setBearerAuth(generateAuthToken(port, RESET_FOR_NEXT_REQUEST)); - } - - RESET_FOR_NEXT_REQUEST = false; - - headers.setContentType(MediaType.APPLICATION_JSON); - - var entity = (body == null ? new HttpEntity<>(headers) : new HttpEntity<>(body, headers)); - var exchange = restTemplate.exchange("http://localhost:" + port + path, method, entity, String.class); - - var objectMapper = new ObjectMapper(); - objectMapper.findAndRegisterModules(); - - var typeFactory = objectMapper.getTypeFactory(); - - JavaType valueType = null; - - int limit = classes.length + 1; - - for (int i = limit - 1; i >= 0; i--) { - - if (limit - 1 == 0) { - //list is empty - valueType = typeFactory.constructParametricType(ResponseEnvelope.class, firstClazz); - } else { - //list isn't empty - - if (i == 0) { - //firstClass height - - if (limit - 1 == 1) { - //list has just 1 element - valueType = typeFactory.constructParametricType(firstClazz, classes[i]); - } else { - //list has > 1 element - valueType = typeFactory.constructParametricType(firstClazz, valueType); - } - - valueType = typeFactory.constructParametricType(ResponseEnvelope.class, valueType); - } else { - //over firstClass height, inside classes list - - if (limit - 1 > 1 && i < limit - 1) { - //list has > 1 element + skip last pair - - if (valueType == null) { - valueType = typeFactory.constructParametricType(classes[i-1], classes[i]); - } else { - valueType = typeFactory.constructParametricType(classes[i-1], valueType); - } - } - } - } - } - - var responseBody = exchange.getBody(); - - if (responseBody == null) { - return ResponseEntity.status(exchange.getStatusCode()).build(); - } - - if (!responseBody.startsWith("{") && !responseBody.startsWith("[")) { - return ResponseEntity.status(exchange.getStatusCode()).body(responseBody); - } - - Object responseEnvelope; - - try { - responseEnvelope = objectMapper.readValue(responseBody, valueType); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - - return ResponseEntity.status(exchange.getStatusCode()).body(responseEnvelope); - } - - @SuppressWarnings("unchecked") - public T assertSuccess(ResponseEntity response, Class tClass) { - assertNotNull(response); - assertEquals(200, response.getStatusCode().value()); - - ResponseEnvelope envelope = (ResponseEnvelope) response.getBody(); - assertNotNull(envelope); - assertEquals(ResponseEnvelope.Status.OK, envelope.getStatus()); - assertEquals(200, envelope.getHttpCode()); - assertNull(envelope.getError()); - assertNotNull(envelope.getData()); - assertEquals(tClass, envelope.getData().getClass()); - - return tClass.cast(envelope.getData()); - } - - @SuppressWarnings("unchecked") - public void assertFailure(ResponseEntity response, ErrorCodes error) { - assertNotNull(response); - assertEquals(error.getHttpStatus().value(), response.getStatusCode().value()); - - ResponseEnvelope envelope = (ResponseEnvelope) response.getBody(); - assertNotNull(envelope); - assertEquals(ResponseEnvelope.Status.ERROR, envelope.getStatus()); - assertEquals(error.getHttpStatus().value(), envelope.getHttpCode()); - assertNotNull(envelope.getError()); - assertEquals(error.getCode(), envelope.getError().getCode()); - assertEquals(error.getDescription(), envelope.getError().getDescription()); - assertNull(envelope.getData()); - } - - public void assertFailure(ResponseEntity response, HttpStatus status, String message) { - assertNotNull(response); - assertEquals(status.value(), response.getStatusCode().value()); - - String result = (String) response.getBody(); - assertNotNull(result); - assertEquals(message, result); - } + @Autowired + private RequestUtils requestUtils; + /* GROUPS */ public GroupDto createTestGroup(int port) { return this.createTestGroup(port, "test"); } @@ -261,7 +39,7 @@ public GroupDto createTestGroup(int port, String name, List profileNames public GroupDto createTestGroup(int port, String name, List profileNames, String preset, String customSportName) { var response = postGroup(port, name, profileNames, preset, customSportName); - return assertSuccess(response, GroupDto.class); + return requestUtils.assertSuccess(response, GroupDto.class); } public ResponseEntity postGroup(int port, String name, List profileNames, String preset) { @@ -275,6 +53,23 @@ public ResponseEntity postGroup(int port, String name, List prof createDto.setSportPreset(preset); createDto.setCustomSportName(customSportName); - return performPost(port, "/groups", createDto, GroupDto.class); + return requestUtils.performPost(port, "/groups", createDto, GroupDto.class); + } + + /* RULE MOVES */ + public RuleMoveCreateDto buildRuleMove(String name, boolean finish, int scorerPoints, int teamPoints) { + var ruleMove = new RuleMoveCreateDto(); + ruleMove.setName(name); + ruleMove.setFinishingMove(finish); + ruleMove.setPointsForScorer(scorerPoints); + ruleMove.setPointsForTeam(teamPoints); + return ruleMove; + } + + public void assertRuleMoveEquals(RuleMoveCreateDto createDto, RuleMoveDto actual) { + assertEquals(createDto.getName(), actual.getName()); + assertEquals(createDto.isFinishingMove(), actual.isFinishingMove()); + assertEquals(createDto.getPointsForScorer(), actual.getPointsForScorer()); + assertEquals(createDto.getPointsForTeam(), actual.getPointsForTeam()); } -} \ No newline at end of file +} diff --git a/api/src/test/java/pro/beerpong/api/control/AssetControllerTest.java b/api/src/test/java/pro/beerpong/api/control/AssetControllerTest.java index be9b1394..de2a15f7 100644 --- a/api/src/test/java/pro/beerpong/api/control/AssetControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/AssetControllerTest.java @@ -1,25 +1,7 @@ package pro.beerpong.api.control; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.test.context.ActiveProfiles; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.support.DefaultTransactionDefinition; -import pro.beerpong.api.TestUtils; -import pro.beerpong.api.model.dto.AssetMetadataDto; -import pro.beerpong.api.model.dto.GroupCreateDto; -import pro.beerpong.api.model.dto.GroupDto; -import pro.beerpong.api.model.dto.ResponseEnvelope; - -import java.time.Duration; -import java.time.ZonedDateTime; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("test") diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index 0d52b092..7f1066a2 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -8,10 +8,10 @@ import org.springframework.http.HttpStatus; import org.springframework.test.context.ActiveProfiles; import pro.beerpong.api.TestUtils; +import pro.beerpong.api.RequestUtils; import pro.beerpong.api.model.dto.ErrorCodes; import pro.beerpong.api.model.dto.GroupCreateDto; import pro.beerpong.api.model.dto.GroupDto; -import pro.beerpong.api.model.dto.ResponseEnvelope; import java.util.ArrayList; import java.util.List; @@ -24,6 +24,8 @@ public class GroupControllerTest { @LocalServerPort private int port; + @Autowired + private RequestUtils requestUtils; @Autowired private TestUtils testUtils; @@ -63,39 +65,39 @@ public void group_create_success() { @Transactional public void group_create_invalidName() { var response = testUtils.postGroup(port, "", List.of("player1", "player2"), "beerpong"); - testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); response = testUtils.postGroup(port, null, List.of("player1", "player2"), "beerpong"); - testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); response = testUtils.postGroup(port, "a", List.of("player1", "player2"), "beerpong"); - testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); response = testUtils.postGroup(port, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", List.of("player1", "player2"), "beerpong"); - testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); } @Test @Transactional public void group_create_invalidProfiles() { var response = testUtils.postGroup(port, "test", List.of(), "beerpong"); - testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_PROFILE_NAMES); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_PROFILE_NAMES); response = testUtils.postGroup(port, "test", null, "beerpong"); - testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_PROFILE_NAMES); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_PROFILE_NAMES); } @Test @Transactional public void group_create_invalidSport() { var response = testUtils.postGroup(port, "test", List.of("player1", "player2"), "notExisting"); - testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); response = testUtils.postGroup(port, "test", List.of("player1", "player2"), null); - testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); response = testUtils.postGroup(port, "test", List.of("player1", "player2"), null, " "); - testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); } @Test @@ -104,8 +106,8 @@ public void group_create_invalidSport() { public void group_userGroups() { var prerequisiteGroup = testUtils.createTestGroup(port); - var response = testUtils.performGet(port, "/groups/user", List.class, GroupDto.class); - var groups = (List) testUtils.assertSuccess(response, ArrayList.class); + var response = requestUtils.performGet(port, "/groups/user", List.class, GroupDto.class); + var groups = (List) requestUtils.assertSuccess(response, ArrayList.class); assertFalse(groups.isEmpty()); assertTrue(groups.stream().anyMatch(groupDto -> groupDto.getId().equals(prerequisiteGroup.getId()))); @@ -116,8 +118,8 @@ public void group_userGroups() { public void group_findByInviteCode_success() { var prerequisiteGroup = testUtils.createTestGroup(port); - var response = testUtils.performGet(port, "/groups?inviteCode=" + prerequisiteGroup.getInviteCode(), GroupDto.class); - var group = testUtils.assertSuccess(response, GroupDto.class); + var response = requestUtils.performGet(port, "/groups?inviteCode=" + prerequisiteGroup.getInviteCode(), GroupDto.class); + var group = requestUtils.assertSuccess(response, GroupDto.class); // if this is not here, the startDate millis are rounded and this test fails group.getActiveSeason().setStartDate(prerequisiteGroup.getActiveSeason().getStartDate()); @@ -130,11 +132,11 @@ public void group_findByInviteCode_success() { @Test @Transactional public void group_findByInviteCode_invalidInviteCode() { - var response = testUtils.performGet(port, "/groups?inviteCode= ", GroupDto.class); - testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_INVITE_CODE); + var response = requestUtils.performGet(port, "/groups?inviteCode= ", GroupDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_INVITE_CODE); - response = testUtils.performGet(port, "/groups?inviteCode=someIdThatNotExists", GroupDto.class); - testUtils.assertFailure(response, ErrorCodes.GROUP_INVITE_NOT_FOUND); + response = requestUtils.performGet(port, "/groups?inviteCode=someIdThatNotExists", GroupDto.class); + requestUtils.assertFailure(response, ErrorCodes.GROUP_INVITE_NOT_FOUND); } @Test @@ -142,8 +144,8 @@ public void group_findByInviteCode_invalidInviteCode() { public void group_findById_success() { var prerequisiteGroup = testUtils.createTestGroup(port); - var response = testUtils.performGet(port, "/groups/" + prerequisiteGroup.getId(), GroupDto.class); - var group = testUtils.assertSuccess(response, GroupDto.class); + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId(), GroupDto.class); + var group = requestUtils.assertSuccess(response, GroupDto.class); // if this is not here, the startDate millis are rounded and this test fails group.getActiveSeason().setStartDate(prerequisiteGroup.getActiveSeason().getStartDate()); @@ -155,8 +157,8 @@ public void group_findById_success() { @Test public void group_findById_invalidId() { - var response = testUtils.performGet(port, "/groups/someIdThatNotExists", GroupDto.class); - testUtils.assertFailure(response, HttpStatus.UNAUTHORIZED, "No access to this group!"); + var response = requestUtils.performGet(port, "/groups/someIdThatNotExists", GroupDto.class); + requestUtils.assertFailure(response, HttpStatus.UNAUTHORIZED, "No access to this group!"); } @Test @@ -169,8 +171,8 @@ public void group_update_success() { createDto.setCustomSportName("kicker"); createDto.setProfileNames(List.of("player3", "player4", "player1")); - var response = testUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); - var group = testUtils.assertSuccess(response, GroupDto.class); + var response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); + var group = requestUtils.assertSuccess(response, GroupDto.class); // if this is not here, the startDate millis are rounded and this test fails group.getActiveSeason().setStartDate(prerequisiteGroup.getActiveSeason().getStartDate()); @@ -195,8 +197,8 @@ public void group_update_invalidName() { var createDto = new GroupCreateDto(); createDto.setName(" "); - var response = testUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); - testUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + var response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); } @Test @@ -204,13 +206,13 @@ public void group_update_invalidName() { public void group_join_success() { var prerequisiteGroup = testUtils.createTestGroup(port); - var response = testUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/join", null, String.class); - testUtils.assertFailure(response, ErrorCodes.GROUP_ALREADY_IN_GROUP); + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/join", null, String.class); + requestUtils.assertFailure(response, ErrorCodes.GROUP_ALREADY_IN_GROUP); - testUtils.resetAuthForNextRequest(); + requestUtils.resetAuthForNextRequest(); - response = testUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/join", null, String.class); - var ok = testUtils.assertSuccess(response, String.class); + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/join", null, String.class); + var ok = requestUtils.assertSuccess(response, String.class); assertEquals("OK", ok); } @@ -220,8 +222,8 @@ public void group_join_success() { public void group_leave() { var prerequisiteGroup = testUtils.createTestGroup(port); - var response = testUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/leave", null, String.class); - var ok = testUtils.assertSuccess(response, String.class); + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/leave", null, String.class); + var ok = requestUtils.assertSuccess(response, String.class); assertEquals("OK", ok); } diff --git a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java index 1163a63f..04b8679e 100644 --- a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java @@ -5,14 +5,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.http.HttpStatus; import org.springframework.test.context.ActiveProfiles; import pro.beerpong.api.TestUtils; -import pro.beerpong.api.model.dto.ErrorCodes; -import pro.beerpong.api.model.dto.GroupCreateDto; -import pro.beerpong.api.model.dto.GroupDto; +import pro.beerpong.api.RequestUtils; +import pro.beerpong.api.model.dto.*; -import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -23,12 +20,86 @@ public class SeasonControllerTest { @LocalServerPort private int port; + @Autowired + private RequestUtils requestUtils; @Autowired private TestUtils testUtils; + @Test + public void season_findById_success() { + var prerequisteGroup = testUtils.createTestGroup(port); + + var response = requestUtils.performGet(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + prerequisteGroup.getActiveSeason().getId(), SeasonDto.class); + var season = requestUtils.assertSuccess(response, SeasonDto.class); + + season.setStartDate(prerequisteGroup.getActiveSeason().getStartDate()); + + assertEquals(prerequisteGroup.getActiveSeason(), season); + } + + @Test + public void season_findById_invalidId() { + var prerequisteGroup1 = testUtils.createTestGroup(port); + + var response = requestUtils.performGet(port, "/groups/" + prerequisteGroup1.getId() + "/seasons/someIdThatNotExists", SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_FOUND); + + var prerequisteGroup2 = testUtils.createTestGroup(port); + + // season form other group + response = requestUtils.performGet(port, "/groups/" + prerequisteGroup1.getId() + "/seasons/" + prerequisteGroup2.getActiveSeason().getId(), SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_FOUND); + } + @Test @Transactional public void season_start_success() { + var prerequisteGroup = testUtils.createTestGroup(port); + var oldSeason = prerequisteGroup.getActiveSeason(); + + var seasonDto = new SeasonCreateDto(); + seasonDto.setOldSeasonName("testing"); + seasonDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + var newSeason = requestUtils.assertSuccess(response, SeasonDto.class); + + var groupResponse = requestUtils.performGet(port, "/groups/" + prerequisteGroup.getId(), GroupDto.class); + var updatedGroup = requestUtils.assertSuccess(groupResponse, GroupDto.class); + + var oldSeasonResponse = requestUtils.performGet(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + oldSeason.getId(), SeasonDto.class); + var updatedOldSeason = requestUtils.assertSuccess(oldSeasonResponse, SeasonDto.class); + + // test active season in group + assertEquals(updatedGroup.getActiveSeason(), newSeason); + assertNotEquals(oldSeason.getId(), newSeason.getId()); + + // test new season + assertNull(newSeason.getName()); + assertEquals(newSeason.getGroupId(), prerequisteGroup.getId()); + assertEquals(newSeason.getCreatedBy(), oldSeason.getCreatedBy()); + assertNotNull(newSeason.getStartDate()); + assertNull(newSeason.getEndDate()); + + // test copying of season settings + assertEquals(newSeason.getSeasonSettings().getMaxTeamSize(), updatedOldSeason.getSeasonSettings().getMaxTeamSize()); + assertEquals(newSeason.getSeasonSettings().getMinTeamSize(), updatedOldSeason.getSeasonSettings().getMinTeamSize()); + assertEquals(newSeason.getSeasonSettings().getMinMatchesToQualify(), updatedOldSeason.getSeasonSettings().getMinMatchesToQualify()); + assertEquals(newSeason.getSeasonSettings().getRankingAlgorithm(), updatedOldSeason.getSeasonSettings().getRankingAlgorithm()); + assertEquals(newSeason.getSeasonSettings().getDailyLeaderboard(), updatedOldSeason.getSeasonSettings().getDailyLeaderboard()); + //TODO adjust for new wakeTime + assertEquals(newSeason.getSeasonSettings().getWakeTimeHour(), updatedOldSeason.getSeasonSettings().getWakeTimeHour()); + + // test changes to old season + assertNotNull(updatedOldSeason.getName()); + assertNotNull(updatedOldSeason.getEndDate()); + assertEquals(seasonDto.getOldSeasonName(), updatedOldSeason.getName()); + //TODO test creation of players (with statistics) + //TODO test creation of rules + //TODO test creation of rule moves } } \ No newline at end of file From a29b36ccac9c3b3826170a78b28ce433093d67b4 Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 13:32:25 +0200 Subject: [PATCH 41/90] season tests --- .../api/model/dto/RuleMoveCreateDto.java | 2 +- .../test/java/pro/beerpong/api/TestUtils.java | 33 +++- .../api/control/GroupControllerTest.java | 20 +-- .../api/control/SeasonControllerTest.java | 148 +++++++++++++++++- 4 files changed, 177 insertions(+), 26 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/model/dto/RuleMoveCreateDto.java b/api/src/main/java/pro/beerpong/api/model/dto/RuleMoveCreateDto.java index 524b288a..4bbc3936 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/RuleMoveCreateDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/RuleMoveCreateDto.java @@ -10,7 +10,7 @@ public class RuleMoveCreateDto { private boolean finishingMove; public boolean invalidDto() { - return this.name == null || this.name.isEmpty() || + return this.name == null || this.name.trim().isEmpty() || this.pointsForTeam < 0 || this.pointsForScorer < 0; } } \ No newline at end of file diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index 228f670a..95aa904f 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -3,14 +3,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; -import pro.beerpong.api.model.dto.GroupCreateDto; -import pro.beerpong.api.model.dto.GroupDto; -import pro.beerpong.api.model.dto.RuleMoveCreateDto; -import pro.beerpong.api.model.dto.RuleMoveDto; +import pro.beerpong.api.model.dto.*; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; @Component public class TestUtils { @@ -21,6 +18,19 @@ public class TestUtils { private RequestUtils requestUtils; /* GROUPS */ + public void assertGroupEquals(GroupDto expected, GroupDto actual) { + if (expected == null || actual == null) { + assertNull(expected); + assertNull(actual); + return; + } + + actual.setCreatedAt(expected.getCreatedAt()); + actual.getActiveSeason().setStartDate(expected.getActiveSeason().getStartDate()); + + assertEquals(expected, actual); + } + public GroupDto createTestGroup(int port) { return this.createTestGroup(port, "test"); } @@ -72,4 +82,17 @@ public void assertRuleMoveEquals(RuleMoveCreateDto createDto, RuleMoveDto actual assertEquals(createDto.getPointsForScorer(), actual.getPointsForScorer()); assertEquals(createDto.getPointsForTeam(), actual.getPointsForTeam()); } + + /* SEASONS */ + public void assertSeasonEquals(SeasonDto expected, SeasonDto actual) { + if (expected == null || actual == null) { + assertNull(expected); + assertNull(actual); + return; + } + + actual.setStartDate(expected.getStartDate()); + + assertEquals(expected, actual); + } } diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index 7f1066a2..4a9bf2b7 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -121,12 +121,8 @@ public void group_findByInviteCode_success() { var response = requestUtils.performGet(port, "/groups?inviteCode=" + prerequisiteGroup.getInviteCode(), GroupDto.class); var group = requestUtils.assertSuccess(response, GroupDto.class); - // if this is not here, the startDate millis are rounded and this test fails - group.getActiveSeason().setStartDate(prerequisiteGroup.getActiveSeason().getStartDate()); - group.setCreatedAt(prerequisiteGroup.getCreatedAt()); - assertNotNull(group); - assertEquals(prerequisiteGroup, group); + testUtils.assertGroupEquals(prerequisiteGroup, group); } @Test @@ -147,12 +143,8 @@ public void group_findById_success() { var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId(), GroupDto.class); var group = requestUtils.assertSuccess(response, GroupDto.class); - // if this is not here, the startDate millis are rounded and this test fails - group.getActiveSeason().setStartDate(prerequisiteGroup.getActiveSeason().getStartDate()); - group.setCreatedAt(prerequisiteGroup.getCreatedAt()); - assertNotNull(group); - assertEquals(prerequisiteGroup, group); + testUtils.assertGroupEquals(prerequisiteGroup, group); } @Test @@ -174,20 +166,16 @@ public void group_update_success() { var response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); var group = requestUtils.assertSuccess(response, GroupDto.class); - // if this is not here, the startDate millis are rounded and this test fails - group.getActiveSeason().setStartDate(prerequisiteGroup.getActiveSeason().getStartDate()); - group.setCreatedAt(prerequisiteGroup.getCreatedAt()); - assertNotNull(prerequisiteGroup); assertNotNull(group); assertEquals(prerequisiteGroup.getId(), group.getId()); assertEquals(createDto.getName(), group.getName()); assertEquals(prerequisiteGroup.getInviteCode(), group.getInviteCode()); - assertEquals(prerequisiteGroup.getCreatedAt(), group.getCreatedAt()); + assertEquals(prerequisiteGroup.getCreatedBy(), group.getCreatedBy()); assertEquals(prerequisiteGroup.getWallpaperAsset(), group.getWallpaperAsset()); assertEquals(prerequisiteGroup.getCustomSportName(), group.getCustomSportName()); assertEquals(prerequisiteGroup.getSportPreset(), group.getSportPreset()); - assertEquals(prerequisiteGroup.getActiveSeason(), group.getActiveSeason()); + testUtils.assertSeasonEquals(prerequisiteGroup.getActiveSeason(), group.getActiveSeason()); } @Test diff --git a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java index 04b8679e..3682ac21 100644 --- a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java @@ -10,6 +10,7 @@ import pro.beerpong.api.RequestUtils; import pro.beerpong.api.model.dto.*; +import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -32,9 +33,7 @@ public void season_findById_success() { var response = requestUtils.performGet(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + prerequisteGroup.getActiveSeason().getId(), SeasonDto.class); var season = requestUtils.assertSuccess(response, SeasonDto.class); - season.setStartDate(prerequisteGroup.getActiveSeason().getStartDate()); - - assertEquals(prerequisteGroup.getActiveSeason(), season); + testUtils.assertSeasonEquals(prerequisteGroup.getActiveSeason(), season); } @Test @@ -74,7 +73,7 @@ public void season_start_success() { var updatedOldSeason = requestUtils.assertSuccess(oldSeasonResponse, SeasonDto.class); // test active season in group - assertEquals(updatedGroup.getActiveSeason(), newSeason); + testUtils.assertSeasonEquals(updatedGroup.getActiveSeason(), newSeason); assertNotEquals(oldSeason.getId(), newSeason.getId()); // test new season @@ -102,4 +101,145 @@ public void season_start_success() { //TODO test creation of rules //TODO test creation of rule moves } + + @Test + public void season_start_invalidName() { + var prerequisteGroup = testUtils.createTestGroup(port); + + var seasonDto = new SeasonCreateDto(); + seasonDto.setOldSeasonName(null); + seasonDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_SEASON_NAME); + + seasonDto.setOldSeasonName(""); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_SEASON_NAME); + + seasonDto.setOldSeasonName(" "); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_SEASON_NAME); + + seasonDto.setOldSeasonName("a"); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_SEASON_NAME); + + seasonDto.setOldSeasonName("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_SEASON_NAME); + } + + @Test + public void season_start_invalidRuleMoves() { + var prerequisteGroup = testUtils.createTestGroup(port); + + var seasonDto = new SeasonCreateDto(); + seasonDto.setOldSeasonName("testing"); + seasonDto.setRuleMoves(null); + + var response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_RULE_MOVES); + + seasonDto.setRuleMoves(List.of()); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_RULE_MOVES); + + seasonDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0) + )); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_RULE_MOVES); + + seasonDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Finish", true, 1, 0) + )); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_RULE_MOVES); + + seasonDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove(null, true, 1, 0) + )); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_RULE_MOVES); + + seasonDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("", true, 1, 0) + )); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_RULE_MOVES); + + seasonDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove(" ", true, 1, 0) + )); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_RULE_MOVES); + + seasonDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("test", true, -1, 0) + )); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_RULE_MOVES); + + seasonDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("test", true, 0, -1) + )); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_RULE_MOVES); + } + + @Test + @SuppressWarnings("unchecked") + public void season_findAll() { + var prerequisteGroup = testUtils.createTestGroup(port); + + var response = requestUtils.performGet(port, "/groups/" + prerequisteGroup.getId() + "/seasons", List.class, SeasonDto.class); + var seasons = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertEquals(1, seasons.size()); + + var firstSeason = seasons.getFirst(); + + testUtils.assertSeasonEquals(firstSeason, prerequisteGroup.getActiveSeason()); + + var seasonDto = new SeasonCreateDto(); + seasonDto.setOldSeasonName("testing"); + seasonDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + var newSeason = requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + + var oldSeasonResponse = requestUtils.performGet(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + firstSeason.getId(), SeasonDto.class); + var updatedOldSeason = requestUtils.assertSuccess(oldSeasonResponse, SeasonDto.class); + + response = requestUtils.performGet(port, "/groups/" + prerequisteGroup.getId() + "/seasons", List.class, SeasonDto.class); + seasons = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertEquals(2, seasons.size()); + testUtils.assertSeasonEquals(newSeason, seasons.stream().filter(toCheck -> toCheck.getId().equals(newSeason.getId())).findFirst().orElse(null)); + testUtils.assertSeasonEquals(updatedOldSeason, seasons.stream().filter(toCheck -> toCheck.getId().equals(firstSeason.getId())).findFirst().orElse(null)); + } } \ No newline at end of file From 82fef9f81563a302ed8e2bcfcbbc3b614f93bbaa Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 14:04:39 +0200 Subject: [PATCH 42/90] changed season update to use dto --- .../api/control/SeasonController.java | 6 +- .../api/model/dao/SeasonSettings.java | 25 ++++-- .../api/model/dto/SeasonSettingsDto.java | 16 ++++ .../api/model/dto/SeasonUpdateDto.java | 2 +- .../beerpong/api/service/GroupService.java | 2 +- .../beerpong/api/service/SeasonService.java | 31 ++++--- .../java/pro/beerpong/api/RequestUtils.java | 4 + .../api/control/GroupControllerTest.java | 8 ++ .../api/control/SeasonControllerTest.java | 87 +++++++++++++++++++ 9 files changed, 159 insertions(+), 22 deletions(-) create mode 100644 api/src/main/java/pro/beerpong/api/model/dto/SeasonSettingsDto.java diff --git a/api/src/main/java/pro/beerpong/api/control/SeasonController.java b/api/src/main/java/pro/beerpong/api/control/SeasonController.java index 5cdc4cde..94ac053a 100644 --- a/api/src/main/java/pro/beerpong/api/control/SeasonController.java +++ b/api/src/main/java/pro/beerpong/api/control/SeasonController.java @@ -101,9 +101,11 @@ public ResponseEntity> updateSeasonById(@PathVariabl return ResponseEnvelope.notOk(ErrorCodes.SEASON_ALREADY_ENDED); } - if (dto.getSeasonSettings().getWakeTimeHour() < 0 || dto.getSeasonSettings().getWakeTimeHour() > 23) { + if (dto.getSeasonSettings().getWakeTimeHour() != null && + (dto.getSeasonSettings().getWakeTimeHour() < 0 || dto.getSeasonSettings().getWakeTimeHour() > 23)) { return ResponseEnvelope.notOk(ErrorCodes.SEASON_WRONG_TIME_FORMAT); - } else if (dto.getSeasonSettings().getMinTeamSize() > dto.getSeasonSettings().getMaxTeamSize()) { + } else if ((dto.getSeasonSettings().getMinTeamSize() != null ? dto.getSeasonSettings().getMinTeamSize() : season.get().getSeasonSettings().getMinTeamSize()) > + (dto.getSeasonSettings().getMaxTeamSize() != null ? dto.getSeasonSettings().getMaxTeamSize() : season.get().getSeasonSettings().getMaxTeamSize())) { return ResponseEnvelope.notOk(ErrorCodes.SEASON_WRONG_TEAM_SIZES); } diff --git a/api/src/main/java/pro/beerpong/api/model/dao/SeasonSettings.java b/api/src/main/java/pro/beerpong/api/model/dao/SeasonSettings.java index 7752c3fe..333eda95 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/SeasonSettings.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/SeasonSettings.java @@ -15,10 +15,23 @@ public class SeasonSettings { @GeneratedValue(strategy = GenerationType.UUID) private String id; - private int minMatchesToQualify = 1; - private int minTeamSize = 1; - private int maxTeamSize = 10; - private RankingAlgorithm rankingAlgorithm = RankingAlgorithm.AVERAGE; - private DailyLeaderboard dailyLeaderboard = DailyLeaderboard.WAKE_TIME; - private int wakeTimeHour = 0; + private int minMatchesToQualify; + private int minTeamSize; + private int maxTeamSize; + private RankingAlgorithm rankingAlgorithm; + private DailyLeaderboard dailyLeaderboard; + private int wakeTimeHour; + + public static SeasonSettings createDefault() { + var seasonSettings = new SeasonSettings(); + + seasonSettings.setMinMatchesToQualify(1); + seasonSettings.setMinTeamSize(1); + seasonSettings.setMaxTeamSize(10); + seasonSettings.setRankingAlgorithm(RankingAlgorithm.AVERAGE); + seasonSettings.setDailyLeaderboard(DailyLeaderboard.WAKE_TIME); + seasonSettings.setWakeTimeHour(0); + + return seasonSettings; + } } diff --git a/api/src/main/java/pro/beerpong/api/model/dto/SeasonSettingsDto.java b/api/src/main/java/pro/beerpong/api/model/dto/SeasonSettingsDto.java new file mode 100644 index 00000000..20987b8c --- /dev/null +++ b/api/src/main/java/pro/beerpong/api/model/dto/SeasonSettingsDto.java @@ -0,0 +1,16 @@ +package pro.beerpong.api.model.dto; + +import lombok.Data; +import pro.beerpong.api.util.DailyLeaderboard; +import pro.beerpong.api.util.RankingAlgorithm; + +@Data +public class SeasonSettingsDto { + private Integer minMatchesToQualify; + private Integer minTeamSize; + private Integer maxTeamSize; + private RankingAlgorithm rankingAlgorithm; + private DailyLeaderboard dailyLeaderboard; + //TODO adjust for new wakeTime + private Integer wakeTimeHour; +} diff --git a/api/src/main/java/pro/beerpong/api/model/dto/SeasonUpdateDto.java b/api/src/main/java/pro/beerpong/api/model/dto/SeasonUpdateDto.java index 3c8e4981..eab1f93a 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/SeasonUpdateDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/SeasonUpdateDto.java @@ -8,5 +8,5 @@ @Data public class SeasonUpdateDto { - private @NotNull SeasonSettings seasonSettings; + private @NotNull SeasonSettingsDto seasonSettings; } diff --git a/api/src/main/java/pro/beerpong/api/service/GroupService.java b/api/src/main/java/pro/beerpong/api/service/GroupService.java index 7f5cdad0..10c788cc 100644 --- a/api/src/main/java/pro/beerpong/api/service/GroupService.java +++ b/api/src/main/java/pro/beerpong/api/service/GroupService.java @@ -59,7 +59,7 @@ public GroupDto createGroup(GroupCreateDto groupCreateDto, UserDto user) { var season = new Season(); season.setStartDate(ZonedDateTime.now()); - season.setSeasonSettings(new SeasonSettings()); + season.setSeasonSettings(SeasonSettings.createDefault()); group.setActiveSeason(season); group = groupRepository.save(group); diff --git a/api/src/main/java/pro/beerpong/api/service/SeasonService.java b/api/src/main/java/pro/beerpong/api/service/SeasonService.java index 9f20862d..81b1a9ad 100644 --- a/api/src/main/java/pro/beerpong/api/service/SeasonService.java +++ b/api/src/main/java/pro/beerpong/api/service/SeasonService.java @@ -1,11 +1,13 @@ package pro.beerpong.api.service; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import pro.beerpong.api.mapping.*; -import pro.beerpong.api.model.dao.*; +import pro.beerpong.api.model.dao.Group; +import pro.beerpong.api.model.dao.Player; +import pro.beerpong.api.model.dao.Season; +import pro.beerpong.api.model.dao.SeasonSettings; import pro.beerpong.api.model.dto.*; import pro.beerpong.api.repository.GroupRepository; import pro.beerpong.api.repository.PlayerRepository; @@ -78,7 +80,7 @@ public SeasonDto startNewSeason(SeasonCreateDto dto, String groupId, UserDto use newSeason.setStartDate(ZonedDateTime.now()); newSeason.setGroupId(groupOptional.get().getId()); - newSeason.setSeasonSettings(new SeasonSettings()); + newSeason.setSeasonSettings(SeasonSettings.createDefault()); newSeason.setCreatedBy(authService.memberByUser(user, groupId)); if (oldSeason != null && oldSeason.getSeasonSettings() != null) { @@ -142,7 +144,7 @@ public List calcStatsForPlayersInSeason(String seasonId, boolean show if (showStats && season != null) { return leaderboardService.generateLeaderboard( - groupService.getRawGroupById(season.getGroupId()), + groupService.getRawGroupById(season.getGroupId()), "season", true, seasonId, @@ -160,16 +162,21 @@ public SeasonDto updateSeason(Season season, SeasonUpdateDto dto) { return Optional.ofNullable(season) .map(existingSeason -> { if (existingSeason.getSeasonSettings() == null) { - dto.getSeasonSettings().setId(null); - existingSeason.setSeasonSettings(dto.getSeasonSettings()); - } else { - existingSeason.getSeasonSettings().setMaxTeamSize(dto.getSeasonSettings().getMaxTeamSize()); - existingSeason.getSeasonSettings().setMinTeamSize(dto.getSeasonSettings().getMinTeamSize()); + existingSeason.setSeasonSettings(SeasonSettings.createDefault()); + } + + if (dto.getSeasonSettings().getMinMatchesToQualify() != null) existingSeason.getSeasonSettings().setMinMatchesToQualify(dto.getSeasonSettings().getMinMatchesToQualify()); - existingSeason.getSeasonSettings().setRankingAlgorithm(dto.getSeasonSettings().getRankingAlgorithm()); - existingSeason.getSeasonSettings().setDailyLeaderboard(dto.getSeasonSettings().getDailyLeaderboard()); + if (dto.getSeasonSettings().getMinTeamSize() != null) + existingSeason.getSeasonSettings().setMinTeamSize(dto.getSeasonSettings().getMinTeamSize()); + if (dto.getSeasonSettings().getMaxTeamSize() != null) + existingSeason.getSeasonSettings().setMaxTeamSize(dto.getSeasonSettings().getMaxTeamSize()); + if (dto.getSeasonSettings().getWakeTimeHour() != null) existingSeason.getSeasonSettings().setWakeTimeHour(dto.getSeasonSettings().getWakeTimeHour()); - } + if (dto.getSeasonSettings().getDailyLeaderboard() != null) + existingSeason.getSeasonSettings().setDailyLeaderboard(dto.getSeasonSettings().getDailyLeaderboard()); + if (dto.getSeasonSettings().getRankingAlgorithm() != null) + existingSeason.getSeasonSettings().setRankingAlgorithm(dto.getSeasonSettings().getRankingAlgorithm()); var seasonDto = seasonMapper.seasonToSeasonDto(seasonRepository.save(existingSeason)); diff --git a/api/src/test/java/pro/beerpong/api/RequestUtils.java b/api/src/test/java/pro/beerpong/api/RequestUtils.java index 20683710..9c40346d 100644 --- a/api/src/test/java/pro/beerpong/api/RequestUtils.java +++ b/api/src/test/java/pro/beerpong/api/RequestUtils.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.api.client.util.Lists; +import lombok.extern.log4j.Log4j2; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.*; import org.springframework.stereotype.Component; @@ -12,10 +13,12 @@ import pro.beerpong.api.util.TokenType; import java.util.List; +import java.util.logging.Logger; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertEquals; +@Log4j2 @Component public class RequestUtils { private static final List GROUP_ACCESS = Lists.newArrayList(); @@ -194,6 +197,7 @@ public ResponseEntity performCall(boolean withAuth, int port, String pat try { responseEnvelope = objectMapper.readValue(responseBody, valueType); } catch (JsonProcessingException e) { + log.error("Error whilst parsing body '{}'!", responseBody); throw new RuntimeException(e); } diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index 4a9bf2b7..8ed7c857 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -12,6 +12,8 @@ import pro.beerpong.api.model.dto.ErrorCodes; import pro.beerpong.api.model.dto.GroupCreateDto; import pro.beerpong.api.model.dto.GroupDto; +import pro.beerpong.api.util.DailyLeaderboard; +import pro.beerpong.api.util.RankingAlgorithm; import java.util.ArrayList; import java.util.List; @@ -52,6 +54,12 @@ public void group_create_success() { assertNull(group.getActiveSeason().getEndDate()); assertEquals(group.getActiveSeason().getGroupId(), group.getId()); assertNotNull(group.getActiveSeason().getSeasonSettings()); + assertEquals(1, group.getActiveSeason().getSeasonSettings().getMinMatchesToQualify()); + assertEquals(1, group.getActiveSeason().getSeasonSettings().getMinTeamSize()); + assertEquals(10, group.getActiveSeason().getSeasonSettings().getMaxTeamSize()); + assertEquals(RankingAlgorithm.AVERAGE, group.getActiveSeason().getSeasonSettings().getRankingAlgorithm()); + assertEquals(DailyLeaderboard.WAKE_TIME, group.getActiveSeason().getSeasonSettings().getDailyLeaderboard()); + assertEquals(0, group.getActiveSeason().getSeasonSettings().getWakeTimeHour()); assertEquals(group.getCreatedBy(), group.getActiveSeason().getCreatedBy()); group = testUtils.createTestGroup(port, "test", List.of("player1", "player2"), null, "test123"); diff --git a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java index 3682ac21..832b9b22 100644 --- a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java @@ -8,10 +8,14 @@ import org.springframework.test.context.ActiveProfiles; import pro.beerpong.api.TestUtils; import pro.beerpong.api.RequestUtils; +import pro.beerpong.api.model.dao.SeasonSettings; import pro.beerpong.api.model.dto.*; +import pro.beerpong.api.util.DailyLeaderboard; +import pro.beerpong.api.util.RankingAlgorithm; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import static org.junit.jupiter.api.Assertions.*; @@ -242,4 +246,87 @@ public void season_findAll() { testUtils.assertSeasonEquals(newSeason, seasons.stream().filter(toCheck -> toCheck.getId().equals(newSeason.getId())).findFirst().orElse(null)); testUtils.assertSeasonEquals(updatedOldSeason, seasons.stream().filter(toCheck -> toCheck.getId().equals(firstSeason.getId())).findFirst().orElse(null)); } + + @Test + @Transactional + public void season_update_success() { + var prerequisteGroup = testUtils.createTestGroup(port); + var oldSeason = prerequisteGroup.getActiveSeason(); + + var seaonDto = buildUpdateDto(seasonSettings -> { + seasonSettings.setDailyLeaderboard(DailyLeaderboard.LAST_24_HOURS); + seasonSettings.setMinTeamSize(3); + }); + + var response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + oldSeason.getId(), seaonDto, SeasonDto.class); + var season = requestUtils.assertSuccess(response, SeasonDto.class); + + // test that rest of group is the same + assertEquals(oldSeason.getCreatedBy(), season.getCreatedBy()); + assertEquals(oldSeason.getEndDate(), season.getEndDate()); + assertEquals(oldSeason.getName(), season.getName()); + assertEquals(oldSeason.getGroupId(), season.getGroupId()); + + assertEquals(DailyLeaderboard.LAST_24_HOURS, season.getSeasonSettings().getDailyLeaderboard()); + assertEquals(oldSeason.getSeasonSettings().getRankingAlgorithm(), season.getSeasonSettings().getRankingAlgorithm()); + assertEquals(3, season.getSeasonSettings().getMinTeamSize()); + assertEquals(oldSeason.getSeasonSettings().getMaxTeamSize(), season.getSeasonSettings().getMaxTeamSize()); + assertEquals(oldSeason.getSeasonSettings().getMinMatchesToQualify(), season.getSeasonSettings().getMinMatchesToQualify()); + assertEquals(oldSeason.getSeasonSettings().getWakeTimeHour(), season.getSeasonSettings().getWakeTimeHour()); + + seaonDto = buildUpdateDto(seasonSettings -> { + seasonSettings.setMaxTeamSize(5); + }); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + oldSeason.getId(), seaonDto, SeasonDto.class); + season = requestUtils.assertSuccess(response, SeasonDto.class); + + assertEquals(DailyLeaderboard.LAST_24_HOURS, season.getSeasonSettings().getDailyLeaderboard()); + assertEquals(oldSeason.getSeasonSettings().getRankingAlgorithm(), season.getSeasonSettings().getRankingAlgorithm()); + assertEquals(3, season.getSeasonSettings().getMinTeamSize()); + assertEquals(5, season.getSeasonSettings().getMaxTeamSize()); + assertEquals(oldSeason.getSeasonSettings().getMinMatchesToQualify(), season.getSeasonSettings().getMinMatchesToQualify()); + assertEquals(oldSeason.getSeasonSettings().getWakeTimeHour(), season.getSeasonSettings().getWakeTimeHour()); + + seaonDto = buildUpdateDto(seasonSettings -> { + seasonSettings.setMinMatchesToQualify(7); + seasonSettings.setMaxTeamSize(5); + }); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + oldSeason.getId(), seaonDto, SeasonDto.class); + season = requestUtils.assertSuccess(response, SeasonDto.class); + + assertEquals(DailyLeaderboard.LAST_24_HOURS, season.getSeasonSettings().getDailyLeaderboard()); + assertEquals(oldSeason.getSeasonSettings().getRankingAlgorithm(), season.getSeasonSettings().getRankingAlgorithm()); + assertEquals(3, season.getSeasonSettings().getMinTeamSize()); + assertEquals(5, season.getSeasonSettings().getMaxTeamSize()); + assertEquals(7, season.getSeasonSettings().getMinMatchesToQualify()); + assertEquals(oldSeason.getSeasonSettings().getWakeTimeHour(), season.getSeasonSettings().getWakeTimeHour()); + + seaonDto = buildUpdateDto(seasonSettings -> { + seasonSettings.setRankingAlgorithm(RankingAlgorithm.ELO); + //TODO adjust for new wakeTime + seasonSettings.setWakeTimeHour(9); + }); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + oldSeason.getId(), seaonDto, SeasonDto.class); + season = requestUtils.assertSuccess(response, SeasonDto.class); + + assertEquals(DailyLeaderboard.LAST_24_HOURS, season.getSeasonSettings().getDailyLeaderboard()); + assertEquals(RankingAlgorithm.ELO, season.getSeasonSettings().getRankingAlgorithm()); + assertEquals(3, season.getSeasonSettings().getMinTeamSize()); + assertEquals(5, season.getSeasonSettings().getMaxTeamSize()); + assertEquals(7, season.getSeasonSettings().getMinMatchesToQualify()); + assertEquals(9, season.getSeasonSettings().getWakeTimeHour()); + } + + private SeasonUpdateDto buildUpdateDto(Consumer consumer) { + var seaonDto = new SeasonUpdateDto(); + var seasonSettings = new SeasonSettingsDto(); + + consumer.accept(seasonSettings); + seaonDto.setSeasonSettings(seasonSettings); + + return seaonDto; + } } \ No newline at end of file From 578748ba9048cd4faa1ebf0b0bb1f533da39c9d4 Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 14:07:56 +0200 Subject: [PATCH 43/90] fixed incomplete season update dto --- .../beerpong/api/control/SeasonController.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/control/SeasonController.java b/api/src/main/java/pro/beerpong/api/control/SeasonController.java index 94ac053a..f76db782 100644 --- a/api/src/main/java/pro/beerpong/api/control/SeasonController.java +++ b/api/src/main/java/pro/beerpong/api/control/SeasonController.java @@ -101,17 +101,20 @@ public ResponseEntity> updateSeasonById(@PathVariabl return ResponseEnvelope.notOk(ErrorCodes.SEASON_ALREADY_ENDED); } - if (dto.getSeasonSettings().getWakeTimeHour() != null && - (dto.getSeasonSettings().getWakeTimeHour() < 0 || dto.getSeasonSettings().getWakeTimeHour() > 23)) { + var wakeTimeHour = (dto.getSeasonSettings().getWakeTimeHour() != null ? dto.getSeasonSettings().getWakeTimeHour() : season.get().getSeasonSettings().getWakeTimeHour()); + var minMatches = (dto.getSeasonSettings().getMinMatchesToQualify() != null ? dto.getSeasonSettings().getMinMatchesToQualify() : season.get().getSeasonSettings().getMinMatchesToQualify()); + var minTeamSize = (dto.getSeasonSettings().getMinTeamSize() != null ? dto.getSeasonSettings().getMinTeamSize() : season.get().getSeasonSettings().getMinTeamSize()); + var maxTeamSize = (dto.getSeasonSettings().getMaxTeamSize() != null ? dto.getSeasonSettings().getMaxTeamSize() : season.get().getSeasonSettings().getMaxTeamSize()); + + if (wakeTimeHour < 0 || wakeTimeHour > 23) { return ResponseEnvelope.notOk(ErrorCodes.SEASON_WRONG_TIME_FORMAT); - } else if ((dto.getSeasonSettings().getMinTeamSize() != null ? dto.getSeasonSettings().getMinTeamSize() : season.get().getSeasonSettings().getMinTeamSize()) > - (dto.getSeasonSettings().getMaxTeamSize() != null ? dto.getSeasonSettings().getMaxTeamSize() : season.get().getSeasonSettings().getMaxTeamSize())) { + } else if (minTeamSize > maxTeamSize) { return ResponseEnvelope.notOk(ErrorCodes.SEASON_WRONG_TEAM_SIZES); } - dto.getSeasonSettings().setMinMatchesToQualify(Math.min(Math.max(dto.getSeasonSettings().getMinMatchesToQualify(), 0), 1000)); - dto.getSeasonSettings().setMinTeamSize(Math.min(Math.max(dto.getSeasonSettings().getMinTeamSize(), 1), 10)); - dto.getSeasonSettings().setMaxTeamSize(Math.min(Math.max(dto.getSeasonSettings().getMaxTeamSize(), 1), 10)); + dto.getSeasonSettings().setMinMatchesToQualify(Math.min(Math.max(minMatches, 0), 1000)); + dto.getSeasonSettings().setMinTeamSize(Math.min(Math.max(minTeamSize, 1), 10)); + dto.getSeasonSettings().setMaxTeamSize(Math.min(Math.max(maxTeamSize, 1), 10)); SeasonDto updatedSeason = seasonService.updateSeason(season.get(), dto); if (updatedSeason != null) { From ac178a34c928ddd6c7f8b509a42d32e3e786d366 Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 14:18:41 +0200 Subject: [PATCH 44/90] impl new waketime in tests and code --- .../pro/beerpong/api/control/SeasonController.java | 13 +++++++------ .../beerpong/api/control/GroupControllerTest.java | 3 ++- .../beerpong/api/control/SeasonControllerTest.java | 14 +++++++------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/control/SeasonController.java b/api/src/main/java/pro/beerpong/api/control/SeasonController.java index 685bd36e..9195759b 100644 --- a/api/src/main/java/pro/beerpong/api/control/SeasonController.java +++ b/api/src/main/java/pro/beerpong/api/control/SeasonController.java @@ -42,7 +42,7 @@ public ResponseEntity> startNewSeason(@PathVariable return ResponseEnvelope.notOk(ErrorCodes.INVALID_RULE_MOVES); } - var season = seasonService.startNewSeason(dto, groupId); + var season = seasonService.startNewSeason(dto, groupId, user); if (season != null) { return ResponseEnvelope.ok(season); @@ -103,17 +103,18 @@ public ResponseEntity> updateSeasonById(@PathVariabl return ResponseEnvelope.notOk(ErrorCodes.SEASON_ALREADY_ENDED); } - var wakeTimeHour = (dto.getSeasonSettings().getWakeTimeHour() != null ? dto.getSeasonSettings().getWakeTimeHour() : season.get().getSeasonSettings().getWakeTimeHour()); var minMatches = (dto.getSeasonSettings().getMinMatchesToQualify() != null ? dto.getSeasonSettings().getMinMatchesToQualify() : season.get().getSeasonSettings().getMinMatchesToQualify()); var minTeamSize = (dto.getSeasonSettings().getMinTeamSize() != null ? dto.getSeasonSettings().getMinTeamSize() : season.get().getSeasonSettings().getMinTeamSize()); var maxTeamSize = (dto.getSeasonSettings().getMaxTeamSize() != null ? dto.getSeasonSettings().getMaxTeamSize() : season.get().getSeasonSettings().getMaxTeamSize()); LocalTime wakeTime = season.get().getSeasonSettings().getWakeTime(); - try { - wakeTime = LocalTime.parse(dto.getSeasonSettings().getWakeTime(), LocalTimeAdapter.FORMATTER); - } catch (DateTimeParseException e) { - wakeTime = null; + if (dto.getSeasonSettings().getWakeTime() != null) { + try { + wakeTime = LocalTime.parse(dto.getSeasonSettings().getWakeTime(), LocalTimeAdapter.FORMATTER); + } catch (DateTimeParseException e) { + wakeTime = null; + } } if (wakeTime == null) { diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index 8ed7c857..fc90a933 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -15,6 +15,7 @@ import pro.beerpong.api.util.DailyLeaderboard; import pro.beerpong.api.util.RankingAlgorithm; +import java.time.LocalTime; import java.util.ArrayList; import java.util.List; @@ -59,7 +60,7 @@ public void group_create_success() { assertEquals(10, group.getActiveSeason().getSeasonSettings().getMaxTeamSize()); assertEquals(RankingAlgorithm.AVERAGE, group.getActiveSeason().getSeasonSettings().getRankingAlgorithm()); assertEquals(DailyLeaderboard.WAKE_TIME, group.getActiveSeason().getSeasonSettings().getDailyLeaderboard()); - assertEquals(0, group.getActiveSeason().getSeasonSettings().getWakeTimeHour()); + assertEquals(LocalTime.of(0, 0), group.getActiveSeason().getSeasonSettings().getWakeTime()); assertEquals(group.getCreatedBy(), group.getActiveSeason().getCreatedBy()); group = testUtils.createTestGroup(port, "test", List.of("player1", "player2"), null, "test123"); diff --git a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java index 832b9b22..b4dbc34c 100644 --- a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java @@ -13,6 +13,7 @@ import pro.beerpong.api.util.DailyLeaderboard; import pro.beerpong.api.util.RankingAlgorithm; +import java.time.LocalTime; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -93,8 +94,7 @@ public void season_start_success() { assertEquals(newSeason.getSeasonSettings().getMinMatchesToQualify(), updatedOldSeason.getSeasonSettings().getMinMatchesToQualify()); assertEquals(newSeason.getSeasonSettings().getRankingAlgorithm(), updatedOldSeason.getSeasonSettings().getRankingAlgorithm()); assertEquals(newSeason.getSeasonSettings().getDailyLeaderboard(), updatedOldSeason.getSeasonSettings().getDailyLeaderboard()); - //TODO adjust for new wakeTime - assertEquals(newSeason.getSeasonSettings().getWakeTimeHour(), updatedOldSeason.getSeasonSettings().getWakeTimeHour()); + assertEquals(newSeason.getSeasonSettings().getWakeTime(), updatedOldSeason.getSeasonSettings().getWakeTime()); // test changes to old season assertNotNull(updatedOldSeason.getName()); @@ -272,7 +272,7 @@ public void season_update_success() { assertEquals(3, season.getSeasonSettings().getMinTeamSize()); assertEquals(oldSeason.getSeasonSettings().getMaxTeamSize(), season.getSeasonSettings().getMaxTeamSize()); assertEquals(oldSeason.getSeasonSettings().getMinMatchesToQualify(), season.getSeasonSettings().getMinMatchesToQualify()); - assertEquals(oldSeason.getSeasonSettings().getWakeTimeHour(), season.getSeasonSettings().getWakeTimeHour()); + assertEquals(oldSeason.getSeasonSettings().getWakeTime(), season.getSeasonSettings().getWakeTime()); seaonDto = buildUpdateDto(seasonSettings -> { seasonSettings.setMaxTeamSize(5); @@ -286,7 +286,7 @@ public void season_update_success() { assertEquals(3, season.getSeasonSettings().getMinTeamSize()); assertEquals(5, season.getSeasonSettings().getMaxTeamSize()); assertEquals(oldSeason.getSeasonSettings().getMinMatchesToQualify(), season.getSeasonSettings().getMinMatchesToQualify()); - assertEquals(oldSeason.getSeasonSettings().getWakeTimeHour(), season.getSeasonSettings().getWakeTimeHour()); + assertEquals(oldSeason.getSeasonSettings().getWakeTime(), season.getSeasonSettings().getWakeTime()); seaonDto = buildUpdateDto(seasonSettings -> { seasonSettings.setMinMatchesToQualify(7); @@ -301,12 +301,12 @@ public void season_update_success() { assertEquals(3, season.getSeasonSettings().getMinTeamSize()); assertEquals(5, season.getSeasonSettings().getMaxTeamSize()); assertEquals(7, season.getSeasonSettings().getMinMatchesToQualify()); - assertEquals(oldSeason.getSeasonSettings().getWakeTimeHour(), season.getSeasonSettings().getWakeTimeHour()); + assertEquals(oldSeason.getSeasonSettings().getWakeTime(), season.getSeasonSettings().getWakeTime()); seaonDto = buildUpdateDto(seasonSettings -> { seasonSettings.setRankingAlgorithm(RankingAlgorithm.ELO); //TODO adjust for new wakeTime - seasonSettings.setWakeTimeHour(9); + seasonSettings.setWakeTime("09:33"); }); response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + oldSeason.getId(), seaonDto, SeasonDto.class); @@ -317,7 +317,7 @@ public void season_update_success() { assertEquals(3, season.getSeasonSettings().getMinTeamSize()); assertEquals(5, season.getSeasonSettings().getMaxTeamSize()); assertEquals(7, season.getSeasonSettings().getMinMatchesToQualify()); - assertEquals(9, season.getSeasonSettings().getWakeTimeHour()); + assertEquals(LocalTime.of(9, 33), season.getSeasonSettings().getWakeTime()); } private SeasonUpdateDto buildUpdateDto(Consumer consumer) { From 5ccfcb5f1cc8d6dd6c6a3857b6182eb19b1a4cb2 Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 14:21:56 +0200 Subject: [PATCH 45/90] removed some todos --- api/src/main/java/pro/beerpong/api/service/AuthService.java | 1 - .../test/java/pro/beerpong/api/control/SeasonControllerTest.java | 1 - 2 files changed, 2 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/service/AuthService.java b/api/src/main/java/pro/beerpong/api/service/AuthService.java index e3ff46ff..6214fb35 100644 --- a/api/src/main/java/pro/beerpong/api/service/AuthService.java +++ b/api/src/main/java/pro/beerpong/api/service/AuthService.java @@ -41,7 +41,6 @@ public AuthTokenDto registerDevice(AuthSignupDto dto) { device.setUser(user); device.setDeviceId(dto.getDeviceId()); device.setType(dto.getInstallationType()); - //TODO set pushNotifyToken? deviceRepository.save(device); diff --git a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java index b4dbc34c..71a4a0f2 100644 --- a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java @@ -305,7 +305,6 @@ public void season_update_success() { seaonDto = buildUpdateDto(seasonSettings -> { seasonSettings.setRankingAlgorithm(RankingAlgorithm.ELO); - //TODO adjust for new wakeTime seasonSettings.setWakeTime("09:33"); }); From aecb18ca1addd5bb965d91a5a56b991e6da2d67d Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 14:39:41 +0200 Subject: [PATCH 46/90] finished season tests --- .../beerpong/api/service/SeasonService.java | 2 +- .../api/control/SeasonControllerTest.java | 134 ++++++++++++++++-- 2 files changed, 124 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/service/SeasonService.java b/api/src/main/java/pro/beerpong/api/service/SeasonService.java index 72a83f9f..a7eb7f0e 100644 --- a/api/src/main/java/pro/beerpong/api/service/SeasonService.java +++ b/api/src/main/java/pro/beerpong/api/service/SeasonService.java @@ -176,7 +176,7 @@ public SeasonDto updateSeason(Season season, SeasonUpdateDto dto) { if (dto.getSeasonSettings().getMaxTeamSize() != null) existingSeason.getSeasonSettings().setMaxTeamSize(dto.getSeasonSettings().getMaxTeamSize()); if (dto.getSeasonSettings().getWakeTime() != null) - existingSeason.getSeasonSettings().setWakeTime(LocalTime.parse(dto.getSeasonSettings().getWakeTime())); + existingSeason.getSeasonSettings().setWakeTime(LocalTime.parse(dto.getSeasonSettings().getWakeTime(), LocalTimeAdapter.FORMATTER)); if (dto.getSeasonSettings().getDailyLeaderboard() != null) existingSeason.getSeasonSettings().setDailyLeaderboard(dto.getSeasonSettings().getDailyLeaderboard()); if (dto.getSeasonSettings().getRankingAlgorithm() != null) diff --git a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java index 71a4a0f2..83e881a2 100644 --- a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java @@ -253,12 +253,12 @@ public void season_update_success() { var prerequisteGroup = testUtils.createTestGroup(port); var oldSeason = prerequisteGroup.getActiveSeason(); - var seaonDto = buildUpdateDto(seasonSettings -> { + var seasonDto = buildUpdateDto(seasonSettings -> { seasonSettings.setDailyLeaderboard(DailyLeaderboard.LAST_24_HOURS); seasonSettings.setMinTeamSize(3); }); - var response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + oldSeason.getId(), seaonDto, SeasonDto.class); + var response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + oldSeason.getId(), seasonDto, SeasonDto.class); var season = requestUtils.assertSuccess(response, SeasonDto.class); // test that rest of group is the same @@ -274,11 +274,11 @@ public void season_update_success() { assertEquals(oldSeason.getSeasonSettings().getMinMatchesToQualify(), season.getSeasonSettings().getMinMatchesToQualify()); assertEquals(oldSeason.getSeasonSettings().getWakeTime(), season.getSeasonSettings().getWakeTime()); - seaonDto = buildUpdateDto(seasonSettings -> { + seasonDto = buildUpdateDto(seasonSettings -> { seasonSettings.setMaxTeamSize(5); }); - response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + oldSeason.getId(), seaonDto, SeasonDto.class); + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + oldSeason.getId(), seasonDto, SeasonDto.class); season = requestUtils.assertSuccess(response, SeasonDto.class); assertEquals(DailyLeaderboard.LAST_24_HOURS, season.getSeasonSettings().getDailyLeaderboard()); @@ -288,12 +288,12 @@ public void season_update_success() { assertEquals(oldSeason.getSeasonSettings().getMinMatchesToQualify(), season.getSeasonSettings().getMinMatchesToQualify()); assertEquals(oldSeason.getSeasonSettings().getWakeTime(), season.getSeasonSettings().getWakeTime()); - seaonDto = buildUpdateDto(seasonSettings -> { + seasonDto = buildUpdateDto(seasonSettings -> { seasonSettings.setMinMatchesToQualify(7); seasonSettings.setMaxTeamSize(5); }); - response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + oldSeason.getId(), seaonDto, SeasonDto.class); + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + oldSeason.getId(), seasonDto, SeasonDto.class); season = requestUtils.assertSuccess(response, SeasonDto.class); assertEquals(DailyLeaderboard.LAST_24_HOURS, season.getSeasonSettings().getDailyLeaderboard()); @@ -303,12 +303,12 @@ public void season_update_success() { assertEquals(7, season.getSeasonSettings().getMinMatchesToQualify()); assertEquals(oldSeason.getSeasonSettings().getWakeTime(), season.getSeasonSettings().getWakeTime()); - seaonDto = buildUpdateDto(seasonSettings -> { + seasonDto = buildUpdateDto(seasonSettings -> { seasonSettings.setRankingAlgorithm(RankingAlgorithm.ELO); seasonSettings.setWakeTime("09:33"); }); - response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + oldSeason.getId(), seaonDto, SeasonDto.class); + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + oldSeason.getId(), seasonDto, SeasonDto.class); season = requestUtils.assertSuccess(response, SeasonDto.class); assertEquals(DailyLeaderboard.LAST_24_HOURS, season.getSeasonSettings().getDailyLeaderboard()); @@ -319,13 +319,125 @@ public void season_update_success() { assertEquals(LocalTime.of(9, 33), season.getSeasonSettings().getWakeTime()); } + @Test + @Transactional + public void season_update_invalidDto() { + var prerequisteGroup = testUtils.createTestGroup(port); + + var seasonDto = new SeasonUpdateDto(); + + var response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + prerequisteGroup.getActiveSeason().getId(), seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_SEASON_DTO); + } + + @Test + @Transactional + public void season_update_invalidSeason() { + var prerequisteGroup = testUtils.createTestGroup(port); + var oldSeason = prerequisteGroup.getActiveSeason(); + + var seasonDto = this.buildUpdateDto(seasonSettings -> {}); + + // test season id that not exists + var response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/someIdThatNotExists", seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_FOUND); + + var prerequisteGroup2 = testUtils.createTestGroup(port); + + // test season id from other group than in path + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + prerequisteGroup2.getActiveSeason().getId(), seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_OF_GROUP); + + var seasonCreateDto = new SeasonCreateDto(); + seasonCreateDto.setOldSeasonName("testing"); + seasonCreateDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + // test season id from already ended season + var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/active-season", seasonCreateDto, SeasonDto.class); + requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + oldSeason.getId(), seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_ALREADY_ENDED); + } + + @Test + @Transactional + public void season_update_invalidWakeTimeFormat() { + var prerequisteGroup = testUtils.createTestGroup(port); + + var seasonDto = this.buildUpdateDto(seasonSettings -> seasonSettings.setWakeTime("")); + + var response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + prerequisteGroup.getActiveSeason().getId(), seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_WRONG_TIME_FORMAT); + + seasonDto = this.buildUpdateDto(seasonSettings -> seasonSettings.setWakeTime("-1")); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + prerequisteGroup.getActiveSeason().getId(), seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_WRONG_TIME_FORMAT); + + seasonDto = this.buildUpdateDto(seasonSettings -> seasonSettings.setWakeTime("XX:xx")); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + prerequisteGroup.getActiveSeason().getId(), seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_WRONG_TIME_FORMAT); + + seasonDto = this.buildUpdateDto(seasonSettings -> seasonSettings.setWakeTime("-02:00")); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + prerequisteGroup.getActiveSeason().getId(), seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_WRONG_TIME_FORMAT); + + seasonDto = this.buildUpdateDto(seasonSettings -> seasonSettings.setWakeTime("25:35")); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + prerequisteGroup.getActiveSeason().getId(), seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_WRONG_TIME_FORMAT); + + seasonDto = this.buildUpdateDto(seasonSettings -> seasonSettings.setWakeTime("14:61")); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + prerequisteGroup.getActiveSeason().getId(), seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_WRONG_TIME_FORMAT); + } + + @Test + @Transactional + public void season_update_invalidTeamSizes() { + var prerequisteGroup = testUtils.createTestGroup(port); + + var seasonDto = this.buildUpdateDto(seasonSettings -> { + seasonSettings.setMinTeamSize(3); + seasonSettings.setMaxTeamSize(7); + }); + + var response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + prerequisteGroup.getActiveSeason().getId(), seasonDto, SeasonDto.class); + requestUtils.assertSuccess(response, SeasonDto.class); + + seasonDto = this.buildUpdateDto(seasonSettings -> seasonSettings.setMaxTeamSize(2)); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + prerequisteGroup.getActiveSeason().getId(), seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_WRONG_TEAM_SIZES); + + seasonDto = this.buildUpdateDto(seasonSettings -> seasonSettings.setMinTeamSize(8)); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + prerequisteGroup.getActiveSeason().getId(), seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_WRONG_TEAM_SIZES); + + seasonDto = this.buildUpdateDto(seasonSettings -> { + seasonSettings.setMinTeamSize(8); + seasonSettings.setMaxTeamSize(7); + }); + + response = requestUtils.performPut(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + prerequisteGroup.getActiveSeason().getId(), seasonDto, SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_WRONG_TEAM_SIZES); + } + private SeasonUpdateDto buildUpdateDto(Consumer consumer) { - var seaonDto = new SeasonUpdateDto(); + var seasonDto = new SeasonUpdateDto(); var seasonSettings = new SeasonSettingsDto(); consumer.accept(seasonSettings); - seaonDto.setSeasonSettings(seasonSettings); + seasonDto.setSeasonSettings(seasonSettings); - return seaonDto; + return seasonDto; } } \ No newline at end of file From d3c5300e3221752e4b09a75ef9c94c8ef0cca26b Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 14:53:25 +0200 Subject: [PATCH 47/90] testing for valid userId in created + rule move tests --- .../java/pro/beerpong/api/RequestUtils.java | 19 ++++++- .../test/java/pro/beerpong/api/TestUtils.java | 15 +++++- .../api/control/GroupControllerTest.java | 10 ++++ .../api/control/RuleMoveControllerTest.java | 49 +++++++++++++++++++ .../api/control/SeasonControllerTest.java | 2 + 5 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 api/src/test/java/pro/beerpong/api/control/RuleMoveControllerTest.java diff --git a/api/src/test/java/pro/beerpong/api/RequestUtils.java b/api/src/test/java/pro/beerpong/api/RequestUtils.java index 9c40346d..1dbdaac1 100644 --- a/api/src/test/java/pro/beerpong/api/RequestUtils.java +++ b/api/src/test/java/pro/beerpong/api/RequestUtils.java @@ -8,6 +8,7 @@ import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.*; import org.springframework.stereotype.Component; +import pro.beerpong.api.auth.JwtTokenProvider; import pro.beerpong.api.model.dto.*; import pro.beerpong.api.util.InstallationType; import pro.beerpong.api.util.TokenType; @@ -27,9 +28,11 @@ public class RequestUtils { private static boolean RESET_FOR_NEXT_REQUEST = false; private final TestRestTemplate restTemplate; + private final JwtTokenProvider jwtTokenProvider; - public RequestUtils(TestRestTemplate restTemplate) { + public RequestUtils(TestRestTemplate restTemplate, JwtTokenProvider jwtTokenProvider) { this.restTemplate = restTemplate; + this.jwtTokenProvider = jwtTokenProvider; } public ResponseEntity performGet(int port, String path, Class firstClazz, Class... classes) { @@ -243,4 +246,18 @@ public void assertFailure(ResponseEntity response, HttpStatus status, St assertNotNull(result); assertEquals(message, result); } + + public String currentUserId() { + if (REFRESH_TOKEN == null || REFRESH_TOKEN.trim().isEmpty()) { + return null; + } + + var claims = jwtTokenProvider.validateToken(REFRESH_TOKEN, "refresh"); + + if (claims == null) { + return null; + } + + return claims.getSubject(); + } } \ No newline at end of file diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index 95aa904f..7fe5e162 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -11,7 +11,7 @@ @Component public class TestUtils { - //TODO leaderboard, match, player, assets (needs s3 files), profile, rules, rulemoves, seasons + //TODO leaderboard, match, player, assets (needs s3 files), profile, rules, rulemoves //TODO test realtime events @Autowired @@ -76,7 +76,18 @@ public RuleMoveCreateDto buildRuleMove(String name, boolean finish, int scorerPo return ruleMove; } - public void assertRuleMoveEquals(RuleMoveCreateDto createDto, RuleMoveDto actual) { + public void assertRuleMovesEquals(List expected, List actual) { + + } + + public void assertRuleMoveEquals(RuleMoveDto expected, RuleMoveDto actual) { + assertEquals(expected.getName(), actual.getName()); + assertEquals(expected.isFinishingMove(), actual.isFinishingMove()); + assertEquals(expected.getPointsForScorer(), actual.getPointsForScorer()); + assertEquals(expected.getPointsForTeam(), actual.getPointsForTeam()); + } + + public void assertCreatedRuleMoveEquals(RuleMoveCreateDto createDto, RuleMoveDto actual) { assertEquals(createDto.getName(), actual.getName()); assertEquals(createDto.isFinishingMove(), actual.isFinishingMove()); assertEquals(createDto.getPointsForScorer(), actual.getPointsForScorer()); diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index fc90a933..d92a8edf 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -61,6 +61,8 @@ public void group_create_success() { assertEquals(RankingAlgorithm.AVERAGE, group.getActiveSeason().getSeasonSettings().getRankingAlgorithm()); assertEquals(DailyLeaderboard.WAKE_TIME, group.getActiveSeason().getSeasonSettings().getDailyLeaderboard()); assertEquals(LocalTime.of(0, 0), group.getActiveSeason().getSeasonSettings().getWakeTime()); + assertEquals(requestUtils.currentUserId(), group.getCreatedBy().getUserId()); + assertEquals(group.getId(), group.getCreatedBy().getGroupId()); assertEquals(group.getCreatedBy(), group.getActiveSeason().getCreatedBy()); group = testUtils.createTestGroup(port, "test", List.of("player1", "player2"), null, "test123"); @@ -68,6 +70,14 @@ public void group_create_success() { assertNotNull(group); assertEquals("test123", group.getCustomSportName()); assertNull(group.getSportPreset()); + + group = testUtils.createTestGroup(port, "test", List.of("player1", "player2"), "kicker", "test123"); + + assertNotNull(group); + assertNull(group.getCustomSportName()); + assertEquals(GroupPresetsController.KICKER.getId(), group.getSportPreset().getId()); + + //TODO test } @Test diff --git a/api/src/test/java/pro/beerpong/api/control/RuleMoveControllerTest.java b/api/src/test/java/pro/beerpong/api/control/RuleMoveControllerTest.java new file mode 100644 index 00000000..1982116b --- /dev/null +++ b/api/src/test/java/pro/beerpong/api/control/RuleMoveControllerTest.java @@ -0,0 +1,49 @@ +package pro.beerpong.api.control; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.ActiveProfiles; +import pro.beerpong.api.RequestUtils; +import pro.beerpong.api.TestUtils; +import pro.beerpong.api.model.dto.*; + +import java.util.ArrayList; +import java.util.List; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") +public class RuleMoveControllerTest { + @LocalServerPort + private int port; + + @Autowired + private RequestUtils requestUtils; + @Autowired + private TestUtils testUtils; + + @Test + public void ruleMoves_findAll() { + var prerequisteGroup = testUtils.createTestGroup(port); + + var response = requestUtils.performGet(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + prerequisteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = requestUtils.assertSuccess(response, ArrayList.class); + + + } + + @Test + public void season_findById_invalidId() { + var prerequisteGroup1 = testUtils.createTestGroup(port); + + var response = requestUtils.performGet(port, "/groups/" + prerequisteGroup1.getId() + "/seasons/someIdThatNotExists", SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_FOUND); + + var prerequisteGroup2 = testUtils.createTestGroup(port); + + // season form other group + response = requestUtils.performGet(port, "/groups/" + prerequisteGroup1.getId() + "/seasons/" + prerequisteGroup2.getActiveSeason().getId(), SeasonDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_FOUND); + } +} \ No newline at end of file diff --git a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java index 83e881a2..45be429b 100644 --- a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java @@ -85,6 +85,7 @@ public void season_start_success() { assertNull(newSeason.getName()); assertEquals(newSeason.getGroupId(), prerequisteGroup.getId()); assertEquals(newSeason.getCreatedBy(), oldSeason.getCreatedBy()); + assertEquals(requestUtils.currentUserId(), newSeason.getCreatedBy().getUserId()); assertNotNull(newSeason.getStartDate()); assertNull(newSeason.getEndDate()); @@ -263,6 +264,7 @@ public void season_update_success() { // test that rest of group is the same assertEquals(oldSeason.getCreatedBy(), season.getCreatedBy()); + assertEquals(requestUtils.currentUserId(), season.getCreatedBy().getUserId()); assertEquals(oldSeason.getEndDate(), season.getEndDate()); assertEquals(oldSeason.getName(), season.getName()); assertEquals(oldSeason.getGroupId(), season.getGroupId()); From d7ad011c95296af9acc8445420b96c71213dba20 Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 15:04:23 +0200 Subject: [PATCH 48/90] changed rule move creation --- .../pro/beerpong/api/model/dao/RuleMove.java | 8 +----- .../beerpong/api/service/RuleMoveService.java | 16 +++++++----- .../test/java/pro/beerpong/api/TestUtils.java | 24 +++++++++++++++++ .../api/control/GroupControllerTest.java | 3 ++- .../api/control/RuleMoveControllerTest.java | 26 ++++++++++++++++--- .../api/control/SeasonControllerTest.java | 6 ++--- 6 files changed, 62 insertions(+), 21 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/model/dao/RuleMove.java b/api/src/main/java/pro/beerpong/api/model/dao/RuleMove.java index 933dc6a6..b3b5ce92 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/RuleMove.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/RuleMove.java @@ -8,7 +8,7 @@ @Entity(name = "rule_moves") @Data -public class RuleMove implements Cloneable { +public class RuleMove { @Id @GeneratedValue(strategy = GenerationType.UUID) private String id; @@ -27,10 +27,4 @@ public class RuleMove implements Cloneable { @OneToMany(mappedBy = "move") private List matchMoves; - - @Override - @SneakyThrows - public RuleMove clone() { - return (RuleMove) super.clone(); - } } \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/service/RuleMoveService.java b/api/src/main/java/pro/beerpong/api/service/RuleMoveService.java index a3d6da87..40f5473a 100644 --- a/api/src/main/java/pro/beerpong/api/service/RuleMoveService.java +++ b/api/src/main/java/pro/beerpong/api/service/RuleMoveService.java @@ -21,7 +21,7 @@ @Service public class RuleMoveService { - private static final List DEFAULT_BEERPONG_MOVES = List.of( + public static final List DEFAULT_BEERPONG_MOVES = List.of( buildRuleMove("Normal", 1, 0, false), buildRuleMove("Bomb", 2, 0, false), buildRuleMove("Bouncer", 2, 0, false), @@ -31,7 +31,7 @@ public class RuleMoveService { buildRuleMove("Finish - Ring of fire", 1, 10, true) ); - private static final List DEFAULT_MOVES = List.of( + public static final List DEFAULT_MOVES = List.of( buildRuleMove("Normal", 1, 0, false), buildRuleMove("Finish - Normal", 1, 3, true) ); @@ -129,7 +129,7 @@ public void copyRuleMovesFromOldSeason(Season oldSeason, Season newSeason) { } public void createDefaultRuleMoves(Group group, Season season) { - Stream ruleMoves; + Stream ruleMoves; if (group.getSportPreset() != null && group.getSportPreset().equals(GroupPresetsController.BEERPONG.getId())) { ruleMoves = DEFAULT_BEERPONG_MOVES.stream(); @@ -138,15 +138,19 @@ public void createDefaultRuleMoves(Group group, Season season) { } ruleMoves.map(ruleMove -> { - var move = ruleMove.clone(); + var move = new RuleMove(); + move.setName(ruleMove.getName()); + move.setFinishingMove(ruleMove.isFinishingMove()); + move.setPointsForScorer(ruleMove.getPointsForScorer()); + move.setPointsForTeam(ruleMove.getPointsForTeam()); move.setSeason(season); return move; }) .forEach(moveRepository::save); } - private static RuleMove buildRuleMove(String name, int pointsForScorer, int pointsForTeam, boolean finish) { - var ruleMove = new RuleMove(); + private static RuleMoveDto buildRuleMove(String name, int pointsForScorer, int pointsForTeam, boolean finish) { + var ruleMove = new RuleMoveDto(); ruleMove.setName(name); ruleMove.setPointsForScorer(pointsForScorer); diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index 7fe5e162..48df1162 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -47,6 +47,14 @@ public GroupDto createTestGroup(int port, String name, List profileNames return this.createTestGroup(port, name, profileNames, "beerpong", null); } + public GroupDto createTestGroup(int port, String name, String preset) { + return createTestGroup(port, name, preset, null); + } + + public GroupDto createTestGroup(int port, String name, String preset, String customSportName) { + return createTestGroup(port, name, List.of("player1", "player2"), preset, customSportName); + } + public GroupDto createTestGroup(int port, String name, List profileNames, String preset, String customSportName) { var response = postGroup(port, name, profileNames, preset, customSportName); return requestUtils.assertSuccess(response, GroupDto.class); @@ -77,10 +85,26 @@ public RuleMoveCreateDto buildRuleMove(String name, boolean finish, int scorerPo } public void assertRuleMovesEquals(List expected, List actual) { + assertRuleMovesEquals(expected, actual, false); + } + public void assertRuleMovesEquals(List expected, List actual, boolean full) { + assertEquals(expected.size(), actual.size()); + + for (int i = 0; i < expected.size(); i++) { + assertRuleMoveEquals(expected.get(i), actual.get(i), full); + } } public void assertRuleMoveEquals(RuleMoveDto expected, RuleMoveDto actual) { + assertRuleMoveEquals(expected, actual, false); + } + + public void assertRuleMoveEquals(RuleMoveDto expected, RuleMoveDto actual, boolean full) { + if (full) { + assertEquals(expected.getId(), actual.getId()); + assertEquals(expected.getSeason().getId(), actual.getSeason().getId()); + } assertEquals(expected.getName(), actual.getName()); assertEquals(expected.isFinishingMove(), actual.isFinishingMove()); assertEquals(expected.getPointsForScorer(), actual.getPointsForScorer()); diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index d92a8edf..180edc83 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -77,7 +77,8 @@ public void group_create_success() { assertNull(group.getCustomSportName()); assertEquals(GroupPresetsController.KICKER.getId(), group.getSportPreset().getId()); - //TODO test + //TODO test creation of profiles + //TODO test creation of rules } @Test diff --git a/api/src/test/java/pro/beerpong/api/control/RuleMoveControllerTest.java b/api/src/test/java/pro/beerpong/api/control/RuleMoveControllerTest.java index 1982116b..c91231fd 100644 --- a/api/src/test/java/pro/beerpong/api/control/RuleMoveControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/RuleMoveControllerTest.java @@ -1,5 +1,6 @@ package pro.beerpong.api.control; +import jakarta.transaction.Transactional; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -8,6 +9,7 @@ import pro.beerpong.api.RequestUtils; import pro.beerpong.api.TestUtils; import pro.beerpong.api.model.dto.*; +import pro.beerpong.api.service.RuleMoveService; import java.util.ArrayList; import java.util.List; @@ -24,13 +26,29 @@ public class RuleMoveControllerTest { private TestUtils testUtils; @Test - public void ruleMoves_findAll() { - var prerequisteGroup = testUtils.createTestGroup(port); + @Transactional + @SuppressWarnings("unchecked") + public void ruleMoves_groupCreation_corretMoves() { + var prerequisiteGroup = testUtils.createTestGroup(port, "test", "beerpong"); - var response = requestUtils.performGet(port, "/groups/" + prerequisteGroup.getId() + "/seasons/" + prerequisteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); - var ruleMoves = requestUtils.assertSuccess(response, ArrayList.class); + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(response, ArrayList.class); + testUtils.assertRuleMovesEquals(RuleMoveService.DEFAULT_BEERPONG_MOVES, ruleMoves); + prerequisiteGroup = testUtils.createTestGroup(port, "test", "kicker"); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + ruleMoves = (List) requestUtils.assertSuccess(response, ArrayList.class); + + testUtils.assertRuleMovesEquals(RuleMoveService.DEFAULT_MOVES, ruleMoves); + + prerequisiteGroup = testUtils.createTestGroup(port, "test", null, "test123"); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + ruleMoves = (List) requestUtils.assertSuccess(response, ArrayList.class); + + testUtils.assertRuleMovesEquals(RuleMoveService.DEFAULT_MOVES, ruleMoves); } @Test diff --git a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java index 45be429b..7ce1e6c0 100644 --- a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java @@ -102,9 +102,9 @@ public void season_start_success() { assertNotNull(updatedOldSeason.getEndDate()); assertEquals(seasonDto.getOldSeasonName(), updatedOldSeason.getName()); - //TODO test creation of players (with statistics) - //TODO test creation of rules - //TODO test creation of rule moves + //TODO test copying of players (with statistics) + //TODO test copying of rules + //TODO test copying of rule moves } @Test From 0b3b5d9699030f7010e3862681a7a0bc2b430654 Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 15:37:46 +0200 Subject: [PATCH 49/90] rule move tests --- .../api/control/RuleMoveController.java | 8 + .../beerpong/api/model/dto/ErrorCodes.java | 1 + .../api/control/RuleMoveControllerTest.java | 216 +++++++++++++++++- 3 files changed, 218 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/control/RuleMoveController.java b/api/src/main/java/pro/beerpong/api/control/RuleMoveController.java index 5da2d6af..3024a1ab 100644 --- a/api/src/main/java/pro/beerpong/api/control/RuleMoveController.java +++ b/api/src/main/java/pro/beerpong/api/control/RuleMoveController.java @@ -34,6 +34,10 @@ public ResponseEntity> createRuleMove(@PathVariabl return error; } + if (dto.invalidDto()) { + return ResponseEnvelope.notOk(ErrorCodes.RULE_MOVE_INVALID_DTO); + } + var move = moveService.createRuleMove(pair.getFirst(), pair.getSecond(), dto, true); if (move != null) { @@ -52,6 +56,10 @@ public ResponseEntity> updateRuleMove(@PathVariabl return error; } + if (dto.invalidDto()) { + return ResponseEnvelope.notOk(ErrorCodes.RULE_MOVE_INVALID_DTO); + } + var move = moveService.getById(ruleMoveId); if (move == null) { diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java index 0e68aad4..f6aba213 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java @@ -37,6 +37,7 @@ public enum ErrorCodes { /* RULE MOVES */ RULE_MOVE_NOT_FOUND(HttpStatus.NOT_FOUND, "ruleMoveNotFound", "The requested ruleMove could not be found!"), RULE_MOVE_VALIDATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "ruleMoveValidationFailed", "The rulemove is not part of the provided season or the provided season is not part of the provided gorup!"), + RULE_MOVE_INVALID_DTO(HttpStatus.BAD_REQUEST, "ruleMoveInvalidDto", "The name has to be non-null and non-empty and pointsForScorer and pointsForTeam have to be >= 0!"), RULE_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "ruleValidationFailed", "The validation of the created rules has failed (invalid group or season id)"), /* PLAYERS */ PLAYER_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "playerValidationFailed", "The player is not part of the provided season or group!"), diff --git a/api/src/test/java/pro/beerpong/api/control/RuleMoveControllerTest.java b/api/src/test/java/pro/beerpong/api/control/RuleMoveControllerTest.java index c91231fd..c45e814e 100644 --- a/api/src/test/java/pro/beerpong/api/control/RuleMoveControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/RuleMoveControllerTest.java @@ -14,6 +14,8 @@ import java.util.ArrayList; import java.util.List; +import static org.junit.jupiter.api.Assertions.*; + @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("test") public class RuleMoveControllerTest { @@ -28,7 +30,7 @@ public class RuleMoveControllerTest { @Test @Transactional @SuppressWarnings("unchecked") - public void ruleMoves_groupCreation_corretMoves() { + public void ruleMoves_findAll_groupCreationCorrectMoves() { var prerequisiteGroup = testUtils.createTestGroup(port, "test", "beerpong"); var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); @@ -52,16 +54,216 @@ public void ruleMoves_groupCreation_corretMoves() { } @Test - public void season_findById_invalidId() { - var prerequisteGroup1 = testUtils.createTestGroup(port); + @Transactional + @SuppressWarnings("unchecked") + public void ruleMoves_create_success() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + var allMovesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var oldRuleMoves = (List) requestUtils.assertSuccess(allMovesResponse, ArrayList.class); + + var ruleMoveDto = new RuleMoveCreateDto(); + ruleMoveDto.setName("testing"); + ruleMoveDto.setFinishingMove(false); + ruleMoveDto.setPointsForScorer(3); - var response = requestUtils.performGet(port, "/groups/" + prerequisteGroup1.getId() + "/seasons/someIdThatNotExists", SeasonDto.class); + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", ruleMoveDto, RuleMoveDto.class); + var ruleMove = requestUtils.assertSuccess(response, RuleMoveDto.class); + + allMovesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(allMovesResponse, ArrayList.class); + + assertEquals(oldRuleMoves.size() + 1, ruleMoves.size()); + assertTrue(ruleMoves.contains(ruleMove)); + assertFalse(oldRuleMoves.contains(ruleMove)); + testUtils.assertCreatedRuleMoveEquals(ruleMoveDto, ruleMove); + } + + @Test + public void ruleMoves_create_invalidSeason() { + var prerequisiteGroup = testUtils.createTestGroup(port); + var oldSeason = prerequisiteGroup.getActiveSeason(); + + var ruleMoveDto = new RuleMoveCreateDto(); + ruleMoveDto.setName("testing"); + ruleMoveDto.setFinishingMove(false); + ruleMoveDto.setPointsForScorer(3); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/someIdThatNotExists/rule-moves", ruleMoveDto, RuleMoveDto.class); requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_FOUND); - var prerequisteGroup2 = testUtils.createTestGroup(port); + var prerequisiteGroup1 = testUtils.createTestGroup(port); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/rule-moves", ruleMoveDto, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_OF_GROUP); + + var seasonCreateDto = new SeasonCreateDto(); + seasonCreateDto.setOldSeasonName("testing"); + seasonCreateDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonCreateDto, SeasonDto.class); + requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/rule-moves", ruleMoveDto, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_ALREADY_ENDED); + } + + @Test + public void ruleMoves_create_invalidDto() { + var prerequisiteGroup = testUtils.createTestGroup(port); + var oldSeason = prerequisiteGroup.getActiveSeason(); + + var ruleMoveDto = new RuleMoveCreateDto(); + ruleMoveDto.setName(null); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/rule-moves", ruleMoveDto, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.RULE_MOVE_INVALID_DTO); + + ruleMoveDto.setName(""); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/rule-moves", ruleMoveDto, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.RULE_MOVE_INVALID_DTO); + + ruleMoveDto.setName(" "); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/rule-moves", ruleMoveDto, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.RULE_MOVE_INVALID_DTO); + + ruleMoveDto.setName("test"); + ruleMoveDto.setPointsForTeam(-1); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/rule-moves", ruleMoveDto, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.RULE_MOVE_INVALID_DTO); + + ruleMoveDto.setName("test"); + ruleMoveDto.setPointsForTeam(0); + ruleMoveDto.setPointsForScorer(-1); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/rule-moves", ruleMoveDto, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.RULE_MOVE_INVALID_DTO); + } + + @Test + @Transactional + @SuppressWarnings("unchecked") + public void ruleMoves_update_success() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + var allMovesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var oldRuleMoves = (List) requestUtils.assertSuccess(allMovesResponse, ArrayList.class); + + assertFalse(oldRuleMoves.isEmpty()); + + var oldRuleMove = oldRuleMoves.getFirst(); + + var ruleMoveDto = new RuleMoveCreateDto(); + ruleMoveDto.setName("testing"); + ruleMoveDto.setPointsForScorer(3); + ruleMoveDto.setPointsForScorer(4); - // season form other group - response = requestUtils.performGet(port, "/groups/" + prerequisteGroup1.getId() + "/seasons/" + prerequisteGroup2.getActiveSeason().getId(), SeasonDto.class); + var response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves/" + oldRuleMove.getId(), ruleMoveDto, RuleMoveDto.class); + var ruleMove = requestUtils.assertSuccess(response, RuleMoveDto.class); + + assertEquals(oldRuleMove.getId(), ruleMove.getId()); + assertNotEquals(oldRuleMove, ruleMove); + testUtils.assertCreatedRuleMoveEquals(ruleMoveDto, ruleMove); + } + + @Test + @SuppressWarnings("unchecked") + public void ruleMoves_update_invalidSeason() { + var prerequisiteGroup = testUtils.createTestGroup(port); + var oldSeason = prerequisiteGroup.getActiveSeason(); + + var allMovesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var oldRuleMoves = (List) requestUtils.assertSuccess(allMovesResponse, ArrayList.class); + + assertFalse(oldRuleMoves.isEmpty()); + + var oldRuleMove = oldRuleMoves.getFirst(); + + var ruleMoveDto = new RuleMoveCreateDto(); + ruleMoveDto.setName("testing"); + ruleMoveDto.setFinishingMove(false); + ruleMoveDto.setPointsForScorer(3); + + var response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/someIdThatNotExists/rule-moves/" + oldRuleMove.getId(), ruleMoveDto, RuleMoveDto.class); requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_FOUND); + + var prerequisiteGroup1 = testUtils.createTestGroup(port); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/rule-moves/" + oldRuleMove.getId(), ruleMoveDto, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_OF_GROUP); + + var seasonCreateDto = new SeasonCreateDto(); + seasonCreateDto.setOldSeasonName("testing"); + seasonCreateDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonCreateDto, SeasonDto.class); + requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/rule-moves/" + oldRuleMove.getId(), ruleMoveDto, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_ALREADY_ENDED); + } + + @Test + @SuppressWarnings("unchecked") + public void ruleMoves_update_invalidDto() { + var prerequisiteGroup = testUtils.createTestGroup(port); + var oldSeason = prerequisiteGroup.getActiveSeason(); + + var allMovesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var oldRuleMoves = (List) requestUtils.assertSuccess(allMovesResponse, ArrayList.class); + + assertFalse(oldRuleMoves.isEmpty()); + + var oldRuleMove = oldRuleMoves.getFirst(); + + var ruleMoveDto = new RuleMoveCreateDto(); + ruleMoveDto.setName(null); + + var response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/rule-moves/" + oldRuleMove.getId(), ruleMoveDto, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.RULE_MOVE_INVALID_DTO); + + ruleMoveDto.setName(""); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/rule-moves/" + oldRuleMove.getId(), ruleMoveDto, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.RULE_MOVE_INVALID_DTO); + + ruleMoveDto.setName(" "); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/rule-moves/" + oldRuleMove.getId(), ruleMoveDto, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.RULE_MOVE_INVALID_DTO); + + ruleMoveDto.setName("test"); + ruleMoveDto.setPointsForTeam(-1); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/rule-moves/" + oldRuleMove.getId(), ruleMoveDto, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.RULE_MOVE_INVALID_DTO); + + ruleMoveDto.setName("test"); + ruleMoveDto.setPointsForTeam(0); + ruleMoveDto.setPointsForScorer(-1); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/rule-moves/" + oldRuleMove.getId(), ruleMoveDto, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.RULE_MOVE_INVALID_DTO); + } + + @Test + public void ruleMoves_update_invalidRuleMove() { + var prerequisiteGroup = testUtils.createTestGroup(port); + var oldSeason = prerequisiteGroup.getActiveSeason(); + + var ruleMoveDto = new RuleMoveCreateDto(); + ruleMoveDto.setName("test"); + + var response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/rule-moves/someIdThatNotExists", ruleMoveDto, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.RULE_MOVE_NOT_FOUND); } } \ No newline at end of file From b8557f071b1ad58567233fe6397a2fe9071e0bd4 Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 15:48:05 +0200 Subject: [PATCH 50/90] added test for creation of rule moves on new season --- .../test/java/pro/beerpong/api/TestUtils.java | 10 ++++- .../api/control/RuleControllerTest.java | 41 +++++++++++++++++++ .../api/control/RuleMoveControllerTest.java | 32 ++++++++++++++- .../api/control/SeasonControllerTest.java | 1 - 4 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 api/src/test/java/pro/beerpong/api/control/RuleControllerTest.java diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index 48df1162..d7591efb 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -11,7 +11,7 @@ @Component public class TestUtils { - //TODO leaderboard, match, player, assets (needs s3 files), profile, rules, rulemoves + //TODO leaderboard, match, player, assets (needs s3 files), profile //TODO test realtime events @Autowired @@ -111,6 +111,14 @@ public void assertRuleMoveEquals(RuleMoveDto expected, RuleMoveDto actual, boole assertEquals(expected.getPointsForTeam(), actual.getPointsForTeam()); } + public void assertCreatedRuleMovesEquals(List created, List actual) { + assertEquals(created.size(), actual.size()); + + for (int i = 0; i < created.size(); i++) { + assertCreatedRuleMoveEquals(created.get(i), actual.get(i)); + } + } + public void assertCreatedRuleMoveEquals(RuleMoveCreateDto createDto, RuleMoveDto actual) { assertEquals(createDto.getName(), actual.getName()); assertEquals(createDto.isFinishingMove(), actual.isFinishingMove()); diff --git a/api/src/test/java/pro/beerpong/api/control/RuleControllerTest.java b/api/src/test/java/pro/beerpong/api/control/RuleControllerTest.java new file mode 100644 index 00000000..ac1d35dd --- /dev/null +++ b/api/src/test/java/pro/beerpong/api/control/RuleControllerTest.java @@ -0,0 +1,41 @@ +package pro.beerpong.api.control; + +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.ActiveProfiles; +import pro.beerpong.api.RequestUtils; +import pro.beerpong.api.TestUtils; +import pro.beerpong.api.model.dto.*; +import pro.beerpong.api.service.RuleMoveService; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") +public class RuleControllerTest { + @LocalServerPort + private int port; + + @Autowired + private RequestUtils requestUtils; + @Autowired + private TestUtils testUtils; + + @Test + @Transactional + @SuppressWarnings("unchecked") + public void ruleMoves_findAll_groupCreationCorrectMoves() { + var prerequisiteGroup = testUtils.createTestGroup(port, "test", "beerpong"); + + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(response, ArrayList.class); + + testUtils.assertRuleMovesEquals(RuleMoveService.DEFAULT_BEERPONG_MOVES, ruleMoves); + } +} \ No newline at end of file diff --git a/api/src/test/java/pro/beerpong/api/control/RuleMoveControllerTest.java b/api/src/test/java/pro/beerpong/api/control/RuleMoveControllerTest.java index c45e814e..1147a5b6 100644 --- a/api/src/test/java/pro/beerpong/api/control/RuleMoveControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/RuleMoveControllerTest.java @@ -30,7 +30,7 @@ public class RuleMoveControllerTest { @Test @Transactional @SuppressWarnings("unchecked") - public void ruleMoves_findAll_groupCreationCorrectMoves() { + public void ruleMoves_create_groupCreation() { var prerequisiteGroup = testUtils.createTestGroup(port, "test", "beerpong"); var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); @@ -53,6 +53,36 @@ public void ruleMoves_findAll_groupCreationCorrectMoves() { testUtils.assertRuleMovesEquals(RuleMoveService.DEFAULT_MOVES, ruleMoves); } + @Test + @Transactional + @SuppressWarnings("unchecked") + public void ruleMoves_create_seasonStart() { + var prerequisiteGroup = testUtils.createTestGroup(port, "test", "beerpong"); + + var oldResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var oldRuleMoves = (List) requestUtils.assertSuccess(oldResponse, ArrayList.class); + + var createRuleMoves = List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + ); + + assertNotEquals(oldRuleMoves.size(), createRuleMoves.size()); + + var seasonCreateDto = new SeasonCreateDto(); + seasonCreateDto.setOldSeasonName("testing"); + seasonCreateDto.setRuleMoves(createRuleMoves); + + var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonCreateDto, SeasonDto.class); + var newSeason = requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + + var newResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/rule-moves", List.class, RuleMoveDto.class); + var newRuleMoves = (List) requestUtils.assertSuccess(newResponse, ArrayList.class); + + assertNotEquals(oldRuleMoves.size(), newRuleMoves.size()); + testUtils.assertCreatedRuleMovesEquals(createRuleMoves, newRuleMoves); + } + @Test @Transactional @SuppressWarnings("unchecked") diff --git a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java index 7ce1e6c0..e3de8578 100644 --- a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java @@ -104,7 +104,6 @@ public void season_start_success() { //TODO test copying of players (with statistics) //TODO test copying of rules - //TODO test copying of rule moves } @Test From fd8cb62476bc418b9ce3d93011fcc2d0d6a7192a Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 15:59:39 +0200 Subject: [PATCH 51/90] dont create beerpong rules for other presets --- .../java/pro/beerpong/api/model/dao/Rule.java | 8 +-- .../beerpong/api/service/GroupService.java | 2 +- .../pro/beerpong/api/service/RuleService.java | 29 ++++---- .../test/java/pro/beerpong/api/TestUtils.java | 27 ++++++-- .../api/control/GroupControllerTest.java | 1 - .../api/control/RuleControllerTest.java | 66 +++++++++++++++++-- .../api/control/SeasonControllerTest.java | 1 - 7 files changed, 104 insertions(+), 30 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Rule.java b/api/src/main/java/pro/beerpong/api/model/dao/Rule.java index 052e1a36..ed6df542 100644 --- a/api/src/main/java/pro/beerpong/api/model/dao/Rule.java +++ b/api/src/main/java/pro/beerpong/api/model/dao/Rule.java @@ -6,7 +6,7 @@ @Entity(name = "rules") @Data -public class Rule implements Cloneable { +public class Rule { @Id @GeneratedValue(strategy = GenerationType.UUID) private String id; @@ -23,10 +23,4 @@ public class Rule implements Cloneable { @ManyToOne @JoinColumn(name = "createdBy") private GroupMember createdBy; - - @Override - @SneakyThrows - public Rule clone() { - return (Rule) super.clone(); - } } diff --git a/api/src/main/java/pro/beerpong/api/service/GroupService.java b/api/src/main/java/pro/beerpong/api/service/GroupService.java index 10c788cc..0fb77979 100644 --- a/api/src/main/java/pro/beerpong/api/service/GroupService.java +++ b/api/src/main/java/pro/beerpong/api/service/GroupService.java @@ -85,7 +85,7 @@ public GroupDto createGroup(GroupCreateDto groupCreateDto, UserDto user) { }); ruleMoveService.createDefaultRuleMoves(group, season); - ruleService.createDefaultRules(season, groupMember); + ruleService.createDefaultRules(season, groupCreateDto.getSportPreset(), groupMember); return withStats(groupMapper.groupToGroupDto(group)); } diff --git a/api/src/main/java/pro/beerpong/api/service/RuleService.java b/api/src/main/java/pro/beerpong/api/service/RuleService.java index 7941d315..482d01a2 100644 --- a/api/src/main/java/pro/beerpong/api/service/RuleService.java +++ b/api/src/main/java/pro/beerpong/api/service/RuleService.java @@ -3,6 +3,7 @@ import jakarta.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import pro.beerpong.api.control.GroupPresetsController; import pro.beerpong.api.mapping.RuleMapper; import pro.beerpong.api.model.dao.GroupMember; import pro.beerpong.api.model.dao.Rule; @@ -19,7 +20,7 @@ @Service public class RuleService { - private static final List DEFAULT_RULES = List.of( + public static final List DEFAULT_RULES = List.of( buildRule("Teams", "The two teams can have any size, and they don't have to have the same number of players."), buildRule("Cup Setup", "Ten cups per side are to be arranged in a pyramid pointing towards the opponent. The back row must be no further from the table edge than one cup diameter. All cups are to be filled with the same amount of liquid, preferably halfway full."), buildRule("Number of Balls", "Each side throws at least two balls. If there are three or more players per side, increase the ball count by one per extra player."), @@ -98,19 +99,23 @@ public List getAllRules(String seasonId) { .toList(); } - public void createDefaultRules(Season season, GroupMember createdBy) { - DEFAULT_RULES.stream() - .map(rule -> { - var rle = rule.clone(); - rle.setSeason(season); - rle.setCreatedBy(createdBy); - return rle; - }) - .forEach(ruleRepository::save); + public void createDefaultRules(Season season, String sportPreset, GroupMember createdBy) { + if (sportPreset != null && sportPreset.equals(GroupPresetsController.BEERPONG.getId())) { + DEFAULT_RULES.stream() + .map(rule -> { + var rle = new Rule(); + rle.setTitle(rule.getTitle()); + rle.setDescription(rule.getDescription()); + rle.setSeason(season); + rle.setCreatedBy(createdBy); + return rle; + }) + .forEach(ruleRepository::save); + } } - private static Rule buildRule(String title, String description) { - var rule = new Rule(); + private static RuleDto buildRule(String title, String description) { + var rule = new RuleDto(); rule.setTitle(title); rule.setDescription(description); diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index d7591efb..22655ce4 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -96,10 +96,6 @@ public void assertRuleMovesEquals(List expected, List } } - public void assertRuleMoveEquals(RuleMoveDto expected, RuleMoveDto actual) { - assertRuleMoveEquals(expected, actual, false); - } - public void assertRuleMoveEquals(RuleMoveDto expected, RuleMoveDto actual, boolean full) { if (full) { assertEquals(expected.getId(), actual.getId()); @@ -126,6 +122,29 @@ public void assertCreatedRuleMoveEquals(RuleMoveCreateDto createDto, RuleMoveDto assertEquals(createDto.getPointsForTeam(), actual.getPointsForTeam()); } + /* RULES */ + public void assertRulesEquals(List expected, List actual) { + assertRulesEquals(expected, actual, false); + } + + public void assertRulesEquals(List expected, List actual, boolean full) { + assertEquals(expected.size(), actual.size()); + + for (int i = 0; i < expected.size(); i++) { + assertRuleEquals(expected.get(i), actual.get(i), full); + } + } + + public void assertRuleEquals(RuleDto expected, RuleDto actual, boolean full) { + if (full) { + assertEquals(expected.getId(), actual.getId()); + assertEquals(expected.getSeason().getId(), actual.getSeason().getId()); + assertEquals(expected.getCreatedBy().getUserId(), actual.getCreatedBy().getGroupId()); + } + assertEquals(expected.getTitle(), actual.getTitle()); + assertEquals(expected.getDescription(), actual.getDescription()); + } + /* SEASONS */ public void assertSeasonEquals(SeasonDto expected, SeasonDto actual) { if (expected == null || actual == null) { diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index 180edc83..a04225c9 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -78,7 +78,6 @@ public void group_create_success() { assertEquals(GroupPresetsController.KICKER.getId(), group.getSportPreset().getId()); //TODO test creation of profiles - //TODO test creation of rules } @Test diff --git a/api/src/test/java/pro/beerpong/api/control/RuleControllerTest.java b/api/src/test/java/pro/beerpong/api/control/RuleControllerTest.java index ac1d35dd..f9f5e79d 100644 --- a/api/src/test/java/pro/beerpong/api/control/RuleControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/RuleControllerTest.java @@ -10,6 +10,7 @@ import pro.beerpong.api.TestUtils; import pro.beerpong.api.model.dto.*; import pro.beerpong.api.service.RuleMoveService; +import pro.beerpong.api.service.RuleService; import java.util.ArrayList; import java.util.List; @@ -27,15 +28,72 @@ public class RuleControllerTest { @Autowired private TestUtils testUtils; + @Test + @SuppressWarnings("unchecked") + public void rules_create_groupCreation() { + var prerequisiteGroup = testUtils.createTestGroup(port, "test", "beerpong"); + + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rules", List.class, RuleDto.class); + var rules = (List) requestUtils.assertSuccess(response, ArrayList.class); + + testUtils.assertRulesEquals(RuleService.DEFAULT_RULES, rules); + + prerequisiteGroup = testUtils.createTestGroup(port, "test", "kicker"); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rules", List.class, RuleDto.class); + rules = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertTrue(rules.isEmpty()); + + prerequisiteGroup = testUtils.createTestGroup(port, "test", null, "test123"); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rules", List.class, RuleDto.class); + rules = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertTrue(rules.isEmpty()); + } + + @Test + public void rules_findAll_invalidSeason() { + var prerequisiteGroup = testUtils.createTestGroup(port, "test", "beerpong"); + var season = prerequisiteGroup.getActiveSeason(); + + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/someIdThatNotExists/rules", List.class, RuleDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_FOUND); + + var prerequisiteGroup1 = testUtils.createTestGroup(port, "test", "beerpong"); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup1.getId() + "/seasons/" + season.getId() + "/rules", List.class, RuleDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_OF_GROUP); + } + @Test @Transactional @SuppressWarnings("unchecked") - public void ruleMoves_findAll_groupCreationCorrectMoves() { + public void ruleMoves_copy_seasonStart() { var prerequisiteGroup = testUtils.createTestGroup(port, "test", "beerpong"); - var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); - var ruleMoves = (List) requestUtils.assertSuccess(response, ArrayList.class); + var oldResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var oldRuleMoves = (List) requestUtils.assertSuccess(oldResponse, ArrayList.class); + + var createRuleMoves = List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + ); + + assertNotEquals(oldRuleMoves.size(), createRuleMoves.size()); + + var seasonCreateDto = new SeasonCreateDto(); + seasonCreateDto.setOldSeasonName("testing"); + seasonCreateDto.setRuleMoves(createRuleMoves); + + var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonCreateDto, SeasonDto.class); + var newSeason = requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + + var newResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/rule-moves", List.class, RuleMoveDto.class); + var newRuleMoves = (List) requestUtils.assertSuccess(newResponse, ArrayList.class); - testUtils.assertRuleMovesEquals(RuleMoveService.DEFAULT_BEERPONG_MOVES, ruleMoves); + assertNotEquals(oldRuleMoves.size(), newRuleMoves.size()); + testUtils.assertCreatedRuleMovesEquals(createRuleMoves, newRuleMoves); } } \ No newline at end of file diff --git a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java index e3de8578..3b39e332 100644 --- a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java @@ -103,7 +103,6 @@ public void season_start_success() { assertEquals(seasonDto.getOldSeasonName(), updatedOldSeason.getName()); //TODO test copying of players (with statistics) - //TODO test copying of rules } @Test From be9f33e6b1ed103acf753a64f4ced2ea119fd637 Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 16:03:33 +0200 Subject: [PATCH 52/90] rule copy test --- .../api/control/RuleControllerTest.java | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/api/src/test/java/pro/beerpong/api/control/RuleControllerTest.java b/api/src/test/java/pro/beerpong/api/control/RuleControllerTest.java index f9f5e79d..2a7374fe 100644 --- a/api/src/test/java/pro/beerpong/api/control/RuleControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/RuleControllerTest.java @@ -9,7 +9,6 @@ import pro.beerpong.api.RequestUtils; import pro.beerpong.api.TestUtils; import pro.beerpong.api.model.dto.*; -import pro.beerpong.api.service.RuleMoveService; import pro.beerpong.api.service.RuleService; import java.util.ArrayList; @@ -71,29 +70,25 @@ public void rules_findAll_invalidSeason() { @Transactional @SuppressWarnings("unchecked") public void ruleMoves_copy_seasonStart() { - var prerequisiteGroup = testUtils.createTestGroup(port, "test", "beerpong"); - - var oldResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); - var oldRuleMoves = (List) requestUtils.assertSuccess(oldResponse, ArrayList.class); + var prerequisiteGroup = testUtils.createTestGroup(port); - var createRuleMoves = List.of( - testUtils.buildRuleMove("Normal", false, 1, 0), - testUtils.buildRuleMove("Finish", true, 1, 3) - ); - - assertNotEquals(oldRuleMoves.size(), createRuleMoves.size()); + var oldResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rules", List.class, RuleDto.class); + var oldRules = (List) requestUtils.assertSuccess(oldResponse, ArrayList.class); var seasonCreateDto = new SeasonCreateDto(); seasonCreateDto.setOldSeasonName("testing"); - seasonCreateDto.setRuleMoves(createRuleMoves); + seasonCreateDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonCreateDto, SeasonDto.class); - var newSeason = requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); - var newResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/rule-moves", List.class, RuleMoveDto.class); - var newRuleMoves = (List) requestUtils.assertSuccess(newResponse, ArrayList.class); + var newResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rules", List.class, RuleDto.class); + var newRules = (List) requestUtils.assertSuccess(newResponse, ArrayList.class); - assertNotEquals(oldRuleMoves.size(), newRuleMoves.size()); - testUtils.assertCreatedRuleMovesEquals(createRuleMoves, newRuleMoves); + assertEquals(oldRules.size(), newRules.size()); + testUtils.assertRulesEquals(oldRules, newRules); } } \ No newline at end of file From c4bdd65589dacfc7b753621f8608b74165556b12 Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 16:15:16 +0200 Subject: [PATCH 53/90] rule tests --- .../beerpong/api/control/RuleController.java | 2 + .../beerpong/api/model/dto/ErrorCodes.java | 2 + .../beerpong/api/model/dto/RuleCreateDto.java | 5 + .../test/java/pro/beerpong/api/TestUtils.java | 13 ++ .../api/control/RuleControllerTest.java | 127 +++++++++++++++++- 5 files changed, 148 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/pro/beerpong/api/control/RuleController.java b/api/src/main/java/pro/beerpong/api/control/RuleController.java index 02caab4e..77d30874 100644 --- a/api/src/main/java/pro/beerpong/api/control/RuleController.java +++ b/api/src/main/java/pro/beerpong/api/control/RuleController.java @@ -62,6 +62,8 @@ public ResponseEntity>> writeRules(@PathVariable return ResponseEnvelope.notOk(ErrorCodes.SEASON_NOT_OF_GROUP); } else if (pair.getSecond().getEndDate() != null) { return ResponseEnvelope.notOk(ErrorCodes.SEASON_ALREADY_ENDED); + } else if (rules.stream().anyMatch(RuleCreateDto::invalidDto)) { + return ResponseEnvelope.notOk(ErrorCodes.RULE_INVALID_DTO); } var ruleDtos = ruleService.writeRules(groupId, pair.getSecond(), rules, user); diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java index f6aba213..a35ef26a 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java @@ -39,6 +39,8 @@ public enum ErrorCodes { RULE_MOVE_VALIDATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "ruleMoveValidationFailed", "The rulemove is not part of the provided season or the provided season is not part of the provided gorup!"), RULE_MOVE_INVALID_DTO(HttpStatus.BAD_REQUEST, "ruleMoveInvalidDto", "The name has to be non-null and non-empty and pointsForScorer and pointsForTeam have to be >= 0!"), RULE_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "ruleValidationFailed", "The validation of the created rules has failed (invalid group or season id)"), + /* RULES */ + RULE_INVALID_DTO(HttpStatus.BAD_REQUEST, "ruleInvalidDto", "Every rule name and description has to be non-null and non-empty!"), /* PLAYERS */ PLAYER_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "playerValidationFailed", "The player is not part of the provided season or group!"), PLAYER_NOT_FOUND(HttpStatus.NOT_FOUND, "playerNotFound", "The requested player could not be found!"), diff --git a/api/src/main/java/pro/beerpong/api/model/dto/RuleCreateDto.java b/api/src/main/java/pro/beerpong/api/model/dto/RuleCreateDto.java index 2448dea7..c9cee991 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/RuleCreateDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/RuleCreateDto.java @@ -6,4 +6,9 @@ public class RuleCreateDto { private String title; private String description; + + public boolean invalidDto() { + return this.title == null || this.title.trim().isEmpty() || + this.description == null || this.description.trim().isEmpty(); + } } \ No newline at end of file diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index 22655ce4..c0774931 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -145,6 +145,19 @@ public void assertRuleEquals(RuleDto expected, RuleDto actual, boolean full) { assertEquals(expected.getDescription(), actual.getDescription()); } + public void assertCreatedRulesEquals(List created, List actual) { + assertEquals(created.size(), actual.size()); + + for (int i = 0; i < created.size(); i++) { + assertCreatedRuleEquals(created.get(i), actual.get(i)); + } + } + + public void assertCreatedRuleEquals(RuleCreateDto createDto, RuleDto actual) { + assertEquals(createDto.getDescription(), actual.getDescription()); + assertEquals(createDto.getTitle(), actual.getTitle()); + } + /* SEASONS */ public void assertSeasonEquals(SeasonDto expected, SeasonDto actual) { if (expected == null || actual == null) { diff --git a/api/src/test/java/pro/beerpong/api/control/RuleControllerTest.java b/api/src/test/java/pro/beerpong/api/control/RuleControllerTest.java index 2a7374fe..975f1283 100644 --- a/api/src/test/java/pro/beerpong/api/control/RuleControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/RuleControllerTest.java @@ -69,7 +69,7 @@ public void rules_findAll_invalidSeason() { @Test @Transactional @SuppressWarnings("unchecked") - public void ruleMoves_copy_seasonStart() { + public void rules_copy_seasonStart() { var prerequisiteGroup = testUtils.createTestGroup(port); var oldResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rules", List.class, RuleDto.class); @@ -91,4 +91,129 @@ public void ruleMoves_copy_seasonStart() { assertEquals(oldRules.size(), newRules.size()); testUtils.assertRulesEquals(oldRules, newRules); } + + @Test + @Transactional + @SuppressWarnings("unchecked") + public void rules_update_success() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + var oldResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rules", List.class, RuleDto.class); + var oldRules = (List) requestUtils.assertSuccess(oldResponse, ArrayList.class); + + var createdRules = List.of( + buildRule("rule1", "descr1"), + buildRule("rule2", "descr2"), + buildRule("rule3", "descr3") + ); + + assertNotEquals(oldRules.size(), createdRules.size()); + + var response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rules", createdRules, List.class, RuleDto.class); + var newRules = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertNotEquals(oldRules.size(), newRules.size()); + assertEquals(createdRules.size(), newRules.size()); + testUtils.assertCreatedRulesEquals(createdRules, newRules); + } + + @Test + public void rules_update_invalidSeason() { + var prerequisiteGroup = testUtils.createTestGroup(port); + var oldSeason = prerequisiteGroup.getActiveSeason(); + + var createdRules = List.of( + buildRule("rule1", "descr1"), + buildRule("rule2", "descr2"), + buildRule("rule3", "descr3") + ); + + var response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/someIdThatNotExists/rules", createdRules, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_FOUND); + + var prerequisiteGroup1 = testUtils.createTestGroup(port); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/rules", createdRules, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_OF_GROUP); + + var seasonCreateDto = new SeasonCreateDto(); + seasonCreateDto.setOldSeasonName("testing"); + seasonCreateDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonCreateDto, SeasonDto.class); + requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/rules", createdRules, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_ALREADY_ENDED); + } + + @Test + public void rules_update_invalidDto() { + var prerequisiteGroup = testUtils.createTestGroup(port); + var oldSeason = prerequisiteGroup.getActiveSeason(); + + var createdRules = List.of( + buildRule("rule1", "descr1"), + buildRule("rule2", "descr2"), + buildRule(null, "descr3") + ); + + var response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rules", createdRules, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.RULE_INVALID_DTO); + + createdRules = List.of( + buildRule("rule1", "descr1"), + buildRule("rule2", "descr2"), + buildRule("", "descr3") + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rules", createdRules, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.RULE_INVALID_DTO); + + createdRules = List.of( + buildRule("rule1", "descr1"), + buildRule("rule2", "descr2"), + buildRule(" ", "descr3") + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rules", createdRules, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.RULE_INVALID_DTO); + + createdRules = List.of( + buildRule("rule1", "descr1"), + buildRule("rule2", null), + buildRule("rul3", "descr3") + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rules", createdRules, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.RULE_INVALID_DTO); + + createdRules = List.of( + buildRule("rule1", "descr1"), + buildRule("rule2", ""), + buildRule("rul3", "descr3") + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rules", createdRules, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.RULE_INVALID_DTO); + + createdRules = List.of( + buildRule("rule1", "descr1"), + buildRule("rule2", " "), + buildRule("rul3", "descr3") + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rules", createdRules, RuleMoveDto.class); + requestUtils.assertFailure(response, ErrorCodes.RULE_INVALID_DTO); + } + + private RuleCreateDto buildRule(String title, String descr) { + var ruleCreateDto = new RuleCreateDto(); + ruleCreateDto.setTitle(title); + ruleCreateDto.setDescription(descr); + return ruleCreateDto; + } } \ No newline at end of file From a8af6ca342be3a95e817e7878dee6a8951a44e33 Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 16:57:01 +0200 Subject: [PATCH 54/90] profiles test --- .../api/control/ProfileController.java | 8 +- .../beerpong/api/model/dto/ErrorCodes.java | 1 + .../beerpong/api/service/ProfileService.java | 4 +- .../test/java/pro/beerpong/api/TestUtils.java | 2 +- .../api/control/GroupControllerTest.java | 2 - .../api/control/ProfileControllerTest.java | 127 ++++++++++++++++++ 6 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 api/src/test/java/pro/beerpong/api/control/ProfileControllerTest.java diff --git a/api/src/main/java/pro/beerpong/api/control/ProfileController.java b/api/src/main/java/pro/beerpong/api/control/ProfileController.java index 45643df1..ae3b4827 100644 --- a/api/src/main/java/pro/beerpong/api/control/ProfileController.java +++ b/api/src/main/java/pro/beerpong/api/control/ProfileController.java @@ -67,7 +67,11 @@ public ResponseEntity> getProfileById(@PathVariable var profile = profileService.getProfileById(id); if (profile != null) { - return ResponseEnvelope.ok(profile); + if (profile.getGroupId().equals(groupId)) { + return ResponseEnvelope.ok(profile); + } else { + return ResponseEnvelope.notOk(ErrorCodes.PROFILE_NOT_OF_GROUP); + } } else { return ResponseEnvelope.notOk(ErrorCodes.PROFILE_NOT_FOUND); } @@ -81,7 +85,7 @@ public ResponseEntity> updateProfile(@PathVariable var group = groupService.getGroupById(groupId); if (group != null) { - var updatedProfile = profileService.updateProfile(id, profileCreateDto); + var updatedProfile = profileService.updateProfile(id, groupId, profileCreateDto); if (updatedProfile != null) { subscriptionHandler.callEvent(new SocketEvent<>(SocketEventData.PROFILE_UPDATE, groupId, updatedProfile)); diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java index a35ef26a..4d06a7ce 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java @@ -48,6 +48,7 @@ public enum ErrorCodes { INVALID_PLAYER_ID(HttpStatus.BAD_REQUEST, "invalidPlayerId", "Player id must be non-null and non-empty!"), /* PROFILES */ PROFILE_NOT_FOUND(HttpStatus.NOT_FOUND, "profileNotFound", "The requested profile could not be found!"), + PROFILE_NOT_OF_GROUP(HttpStatus.BAD_REQUEST, "profileNotOfGroup", "The provided profile and group id do not match!"), PROFILE_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "profileAlreadyExists", "There already exists a profile with the provided name and an active player in the current season!"), /* ASSETS */ ASSET_NOT_FOUND(HttpStatus.NOT_FOUND, "assetNotFound", "The requested asset could not be found!"), diff --git a/api/src/main/java/pro/beerpong/api/service/ProfileService.java b/api/src/main/java/pro/beerpong/api/service/ProfileService.java index 76649859..9619ed2d 100644 --- a/api/src/main/java/pro/beerpong/api/service/ProfileService.java +++ b/api/src/main/java/pro/beerpong/api/service/ProfileService.java @@ -129,10 +129,10 @@ public boolean deleteProfile(String id) { return false; } - public ProfileDto updateProfile(String id, ProfileCreateDto profileCreateDto) { + public ProfileDto updateProfile(String id, String groupId, ProfileCreateDto profileCreateDto) { var profile = getRawProfileById(id); - if (profile == null) { + if (profile == null || !profile.getGroup().getId().equals(groupId)) { return null; } diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index c0774931..81aec249 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -11,7 +11,7 @@ @Component public class TestUtils { - //TODO leaderboard, match, player, assets (needs s3 files), profile + //TODO tests for: assets (needs s3 files), leaderboard, match, player //TODO test realtime events @Autowired diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index a04225c9..ff414d11 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -76,8 +76,6 @@ public void group_create_success() { assertNotNull(group); assertNull(group.getCustomSportName()); assertEquals(GroupPresetsController.KICKER.getId(), group.getSportPreset().getId()); - - //TODO test creation of profiles } @Test diff --git a/api/src/test/java/pro/beerpong/api/control/ProfileControllerTest.java b/api/src/test/java/pro/beerpong/api/control/ProfileControllerTest.java new file mode 100644 index 00000000..1a4599af --- /dev/null +++ b/api/src/test/java/pro/beerpong/api/control/ProfileControllerTest.java @@ -0,0 +1,127 @@ +package pro.beerpong.api.control; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.ActiveProfiles; +import pro.beerpong.api.RequestUtils; +import pro.beerpong.api.TestUtils; +import pro.beerpong.api.model.dto.ErrorCodes; +import pro.beerpong.api.model.dto.ProfileCreateDto; +import pro.beerpong.api.model.dto.ProfileDto; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") +public class ProfileControllerTest { + @LocalServerPort + private int port; + + @Autowired + private RequestUtils requestUtils; + @Autowired + private TestUtils testUtils; + + @Test + @SuppressWarnings("unchecked") + public void profiles_create_groupCreation() { + var profileNames = List.of("player1", "player2", "player3"); + var prerequisiteGroup = testUtils.createTestGroup(port, "test", profileNames); + + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/profiles", List.class, ProfileDto.class); + var profiles = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertEquals(profileNames.size(), profiles.size()); + assertTrue(profileNames.stream().allMatch(s -> profiles.stream().anyMatch(profileDto -> profileDto.getName().equals(s)))); + } + + @Test + @SuppressWarnings("unchecked") + public void profiles_findById_success() { + var profileNames = List.of("player1", "player2", "player3"); + var prerequisiteGroup = testUtils.createTestGroup(port, "test", profileNames); + + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/profiles", List.class, ProfileDto.class); + var profiles = (List) requestUtils.assertSuccess(response, ArrayList.class); + + for (ProfileDto profile : profiles) { + var prfleResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/profiles/" + profile.getId(), ProfileDto.class); + var fetched = requestUtils.assertSuccess(prfleResponse, ProfileDto.class); + + assertEquals(profile, fetched); + } + } + + @Test + @SuppressWarnings("unchecked") + public void profiles_findById_invalidProfile() { + var profileNames = List.of("player1", "player2", "player3"); + var prerequisiteGroup = testUtils.createTestGroup(port, "test", profileNames); + + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/profiles/someIdThatNotExists", ProfileDto.class); + requestUtils.assertFailure(response, ErrorCodes.PROFILE_NOT_FOUND); + + var prflResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/profiles", List.class, ProfileDto.class); + var profiles = (List) requestUtils.assertSuccess(prflResponse, ArrayList.class); + + assertFalse(profiles.isEmpty()); + + var prerequisiteGroup1 = testUtils.createTestGroup(port, "test", profileNames); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup1.getId() + "/profiles/" + profiles.getFirst().getId(), ProfileDto.class); + requestUtils.assertFailure(response, ErrorCodes.PROFILE_NOT_OF_GROUP); + } + + @Test + @SuppressWarnings("unchecked") + public void profiles_update_success() { + var profileNames = List.of("player1", "player2"); + var prerequisiteGroup = testUtils.createTestGroup(port, "test", profileNames); + + var prflResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/profiles", List.class, ProfileDto.class); + var profiles = (List) requestUtils.assertSuccess(prflResponse, ArrayList.class); + + assertFalse(profiles.isEmpty()); + + var oldProfile = profiles.getFirst(); + + var profileDto = new ProfileCreateDto(); + profileDto.setName(oldProfile.getName() + "test"); + + var response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/profiles/" + oldProfile.getId(), profileDto, ProfileDto.class); + var newProfile = requestUtils.assertSuccess(response, ProfileDto.class); + + assertEquals(profileDto.getName(), newProfile.getName()); + assertEquals(oldProfile.getId(), newProfile.getId()); + assertEquals(oldProfile.getCreatedBy(), newProfile.getCreatedBy()); + assertEquals(oldProfile.getGroupId(), newProfile.getGroupId()); + } + + @Test + @SuppressWarnings("unchecked") + public void profiles_update_invalidProfile() { + var profileNames = List.of("player1", "player2", "player3"); + var prerequisiteGroup = testUtils.createTestGroup(port, "test", profileNames); + + var profileDto = new ProfileCreateDto(); + profileDto.setName("testing"); + + var response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/profiles/someIdThatNotExists", profileDto, ProfileDto.class); + requestUtils.assertFailure(response, ErrorCodes.PROFILE_NOT_FOUND); + + var prflResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/profiles", List.class, ProfileDto.class); + var profiles = (List) requestUtils.assertSuccess(prflResponse, ArrayList.class); + + assertFalse(profiles.isEmpty()); + + var prerequisiteGroup1 = testUtils.createTestGroup(port, "test", profileNames); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup1.getId() + "/profiles/" + profiles.getFirst().getId(), profileDto, ProfileDto.class); + requestUtils.assertFailure(response, ErrorCodes.PROFILE_NOT_FOUND); + } +} \ No newline at end of file From 09985f4cbe87ae03ab31dd98827dea15241c3154 Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 17:10:55 +0200 Subject: [PATCH 55/90] profile tests --- .../api/model/dto/ProfileCreatedDto.java | 18 ++++++--- .../api/control/ProfileControllerTest.java | 38 +++++++++++++++++++ 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ProfileCreatedDto.java b/api/src/main/java/pro/beerpong/api/model/dto/ProfileCreatedDto.java index 4c91b0b4..86792ef3 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ProfileCreatedDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ProfileCreatedDto.java @@ -1,23 +1,29 @@ package pro.beerpong.api.model.dto; -import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import org.springframework.lang.Nullable; @EqualsAndHashCode(callSuper = true) @Data +@NoArgsConstructor public class ProfileCreatedDto extends ProfileDto { private boolean reactivated; @Nullable private String lastActiveSeasonId; - public ProfileCreatedDto(ProfileDto profile, boolean reactivated, @Nullable String lastActiveSeasonId) { - setId(profile.getId()); - setName(profile.getName()); - setAvatarAsset(profile.getAvatarAsset()); - setGroupId(profile.getGroupId()); + public ProfileCreatedDto(String id, String name, AssetMetadataDto asset, String groupId, GroupMemberDto createdBy, boolean reactivated, @Nullable String lastActiveSeasonId) { + setId(id); + setName(name); + setAvatarAsset(asset); + setGroupId(groupId); + setCreatedBy(createdBy); this.reactivated = reactivated; this.lastActiveSeasonId = lastActiveSeasonId; } + + public ProfileCreatedDto(ProfileDto profile, boolean reactivated, @Nullable String lastActiveSeasonId) { + this(profile.getId(), profile.getName(), profile.getAvatarAsset(), profile.getGroupId(), profile.getCreatedBy(), reactivated, lastActiveSeasonId); + } } diff --git a/api/src/test/java/pro/beerpong/api/control/ProfileControllerTest.java b/api/src/test/java/pro/beerpong/api/control/ProfileControllerTest.java index 1a4599af..da9d3628 100644 --- a/api/src/test/java/pro/beerpong/api/control/ProfileControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/ProfileControllerTest.java @@ -1,5 +1,6 @@ package pro.beerpong.api.control; +import jakarta.transaction.Transactional; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -9,6 +10,7 @@ import pro.beerpong.api.TestUtils; import pro.beerpong.api.model.dto.ErrorCodes; import pro.beerpong.api.model.dto.ProfileCreateDto; +import pro.beerpong.api.model.dto.ProfileCreatedDto; import pro.beerpong.api.model.dto.ProfileDto; import java.util.ArrayList; @@ -40,6 +42,42 @@ public void profiles_create_groupCreation() { assertTrue(profileNames.stream().allMatch(s -> profiles.stream().anyMatch(profileDto -> profileDto.getName().equals(s)))); } + @Test + @Transactional + public void profiles_create_success() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + var profileDto = new ProfileCreateDto(); + profileDto.setName("testing"); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/profiles", profileDto, ProfileCreatedDto.class); + var result = requestUtils.assertSuccess(response, ProfileCreatedDto.class); + + assertEquals(profileDto.getName(), result.getName()); + assertEquals(prerequisiteGroup.getId(), result.getGroupId()); + assertEquals(prerequisiteGroup.getCreatedBy(), result.getCreatedBy()); + assertNull(result.getAvatarAsset()); + + assertFalse(result.isReactivated()); + assertNull(result.getLastActiveSeasonId()); + + //TODO test with deletion of players + new season start + } + + @Test + @Transactional + public void profiles_create_alreadyExists() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + var profileDto = new ProfileCreateDto(); + profileDto.setName("player1"); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/profiles", profileDto, ProfileCreatedDto.class); + requestUtils.assertFailure(response, ErrorCodes.PROFILE_ALREADY_EXISTS); + + //TODO test with deletion of players + new season start + } + @Test @SuppressWarnings("unchecked") public void profiles_findById_success() { From aa9c2e7f3f7020f15eeec95198d1ecf5c81922f0 Mon Sep 17 00:00:00 2001 From: Thies Date: Thu, 12 Jun 2025 17:23:07 +0200 Subject: [PATCH 56/90] player tests --- .../api/model/dto/GroupCreateDto.java | 3 +- .../test/java/pro/beerpong/api/TestUtils.java | 2 +- .../api/control/GroupControllerTest.java | 3 + .../api/control/PlayerControllerTest.java | 73 +++++++++++++++++++ .../api/control/SeasonControllerTest.java | 2 - 5 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java diff --git a/api/src/main/java/pro/beerpong/api/model/dto/GroupCreateDto.java b/api/src/main/java/pro/beerpong/api/model/dto/GroupCreateDto.java index d3d876f9..4b4bbb06 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/GroupCreateDto.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/GroupCreateDto.java @@ -21,6 +21,7 @@ public boolean invalidName() { } public boolean invalidProfileName() { - return this.profileNames == null || this.profileNames.isEmpty(); + return this.profileNames == null || this.profileNames.isEmpty() || + this.profileNames.stream().anyMatch(s -> this.profileNames.stream().filter(s1 -> s1.equals(s)).count() > 1); } } diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index 81aec249..d7ffb010 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -11,7 +11,7 @@ @Component public class TestUtils { - //TODO tests for: assets (needs s3 files), leaderboard, match, player + //TODO tests for: assets (needs s3 files), leaderboard, match //TODO test realtime events @Autowired diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index ff414d11..5697cc96 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -102,6 +102,9 @@ public void group_create_invalidProfiles() { response = testUtils.postGroup(port, "test", null, "beerpong"); requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_PROFILE_NAMES); + + response = testUtils.postGroup(port, "test", List.of("player1", "player2", "player2"), "beerpong"); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_PROFILE_NAMES); } @Test diff --git a/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java b/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java new file mode 100644 index 00000000..efa369e5 --- /dev/null +++ b/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java @@ -0,0 +1,73 @@ +package pro.beerpong.api.control; + +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.ActiveProfiles; +import pro.beerpong.api.RequestUtils; +import pro.beerpong.api.TestUtils; +import pro.beerpong.api.model.dto.*; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") +public class PlayerControllerTest { + @LocalServerPort + private int port; + + @Autowired + private RequestUtils requestUtils; + @Autowired + private TestUtils testUtils; + + //TODO season start: test copying of players (with statistics) + + @Test + @Transactional + @SuppressWarnings("unchecked") + public void players_findAll_success() { + var profileNames = List.of("player1", "player2", "player3", "player4"); + var prerequisiteGroup = testUtils.createTestGroup(port, profileNames); + var season = prerequisiteGroup.getActiveSeason(); + + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + season.getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertEquals(profileNames.size(), players.size()); + + for (PlayerDto playerDto : players) { + assertTrue(profileNames.stream().anyMatch(s -> playerDto.getProfile().getName().equals(s))); + assertTrue(playerDto.isActiveThisSeason()); + assertNull(playerDto.getStatistics()); + assertEquals(season.getId(), playerDto.getSeason().getId()); + } + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + season.getId() + "/players?showStats=true", List.class, PlayerDto.class); + players = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertEquals(profileNames.size(), players.size()); + + for (PlayerDto playerDto : players) { + assertTrue(profileNames.stream().anyMatch(s -> playerDto.getProfile().getName().equals(s))); + assertTrue(playerDto.isActiveThisSeason()); + //TODO maybe check stats? + assertNotNull(playerDto.getStatistics()); + assertEquals(season.getId(), playerDto.getSeason().getId()); + } + + //TODO test showInactive=true + } + + @Test + @Transactional + public void players_delete_success() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } +} \ No newline at end of file diff --git a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java index 3b39e332..e4011cf8 100644 --- a/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/SeasonControllerTest.java @@ -101,8 +101,6 @@ public void season_start_success() { assertNotNull(updatedOldSeason.getName()); assertNotNull(updatedOldSeason.getEndDate()); assertEquals(seasonDto.getOldSeasonName(), updatedOldSeason.getName()); - - //TODO test copying of players (with statistics) } @Test From fb3a6aae6e0b32035e77b1146ade08a64d5896b6 Mon Sep 17 00:00:00 2001 From: Thies Date: Fri, 13 Jun 2025 00:21:53 +0200 Subject: [PATCH 57/90] player delete tests --- .../api/control/PlayerController.java | 14 +- .../beerpong/api/model/dto/ErrorCodes.java | 3 +- .../beerpong/api/service/SeasonService.java | 9 +- .../api/control/PlayerControllerTest.java | 130 +++++++++++++++++- 4 files changed, 146 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/control/PlayerController.java b/api/src/main/java/pro/beerpong/api/control/PlayerController.java index c805a1bc..d645b114 100644 --- a/api/src/main/java/pro/beerpong/api/control/PlayerController.java +++ b/api/src/main/java/pro/beerpong/api/control/PlayerController.java @@ -37,12 +37,22 @@ public ResponseEntity>> getPlayers(@PathVariabl return ResponseEnvelope.notOk(ErrorCodes.INVALID_SEASON_ID); } - var players = seasonService.calcStatsForPlayersInSeason(seasonId, showInactive, showStats); + var season = seasonService.getSeasonById(seasonId); + + if (season == null) { + return ResponseEnvelope.notOk(ErrorCodes.SEASON_NOT_FOUND); + } + + if (!season.getGroupId().equals(groupId)) { + return ResponseEnvelope.notOk(ErrorCodes.SEASON_NOT_OF_GROUP); + } + + var players = seasonService.calcStatsForPlayersInSeason(season, showInactive, showStats); if (players != null) { return ResponseEnvelope.ok(players); } else { - return ResponseEnvelope.notOk(ErrorCodes.SEASON_NOT_FOUND); + return ResponseEnvelope.notOk(ErrorCodes.ERROR); } } diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java index 4d06a7ce..870bbefc 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java @@ -8,7 +8,8 @@ @Getter @RequiredArgsConstructor public enum ErrorCodes { - /* GROUPS */ + ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "error", "An internal error occurred!") + /* GROUPS */, GROUP_NOT_FOUND(HttpStatus.NOT_FOUND, "groupNotFound", "The requested group could not be found!"), GROUP_INVITE_NOT_FOUND(HttpStatus.NOT_FOUND, "groupInviteNotFound", "No group with the provided invite code could be found!"), GROUP_INVITE_CODE_NOT_PROVIDED(HttpStatus.BAD_REQUEST, "groupInviteCodeNotProvided", "The invite code needs to be provided!"), diff --git a/api/src/main/java/pro/beerpong/api/service/SeasonService.java b/api/src/main/java/pro/beerpong/api/service/SeasonService.java index a7eb7f0e..f21b1d22 100644 --- a/api/src/main/java/pro/beerpong/api/service/SeasonService.java +++ b/api/src/main/java/pro/beerpong/api/service/SeasonService.java @@ -142,16 +142,15 @@ public SeasonDto startNewSeason(SeasonCreateDto dto, String groupId, UserDto use return newDto; } - public List calcStatsForPlayersInSeason(String seasonId, boolean showInactive, boolean showStats) { - var players = playerService.getBySeasonId(seasonId, showInactive); - var season = seasonRepository.findById(seasonId).orElse(null); + public List calcStatsForPlayersInSeason(SeasonDto season, boolean showInactive, boolean showStats) { + var players = playerService.getBySeasonId(season.getId(), showInactive); - if (showStats && season != null) { + if (showStats) { return leaderboardService.generateLeaderboard( groupService.getRawGroupById(season.getGroupId()), "season", true, - seasonId, + season.getId(), players.stream() ) .getEntries(); diff --git a/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java b/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java index efa369e5..c1dcebf8 100644 --- a/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java @@ -61,13 +61,139 @@ public void players_findAll_success() { assertEquals(season.getId(), playerDto.getSeason().getId()); } - //TODO test showInactive=true + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + season.getId() + "/players?showInactive=true", List.class, PlayerDto.class); + players = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertEquals(profileNames.size(), players.size()); + + for (PlayerDto playerDto : players) { + assertTrue(profileNames.stream().anyMatch(s -> playerDto.getProfile().getName().equals(s))); + assertTrue(playerDto.isActiveThisSeason()); + assertNull(playerDto.getStatistics()); + assertEquals(season.getId(), playerDto.getSeason().getId()); + } + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + season.getId() + "/players?showInactive=true&showStats=true", List.class, PlayerDto.class); + players = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertEquals(profileNames.size(), players.size()); + + for (PlayerDto playerDto : players) { + assertTrue(profileNames.stream().anyMatch(s -> playerDto.getProfile().getName().equals(s))); + assertTrue(playerDto.isActiveThisSeason()); + //TODO maybe check stats? + assertNotNull(playerDto.getStatistics()); + assertEquals(season.getId(), playerDto.getSeason().getId()); + } + + var player = players.getFirst(); + + var deleteResponse = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players/" + player.getId(), null, String.class); + var result = requestUtils.assertSuccess(deleteResponse, String.class); + + assertEquals("OK", result); + + var allPlayersResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + season.getId() + "/players?showInactive=true", List.class, PlayerDto.class); + var allPlayers = (List) requestUtils.assertSuccess(allPlayersResponse, ArrayList.class); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + season.getId() + "/players", List.class, PlayerDto.class); + players = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertEquals(players.size() + 1, allPlayers.size()); + assertTrue(allPlayers.stream().anyMatch(s -> s.getId().equals(player.getId()))); + assertFalse(players.stream().anyMatch(s -> s.getId().equals(player.getId()))); } @Test @Transactional + public void players_findAll_invalidSeason() { + var prerequisiteGroup = testUtils.createTestGroup(port, List.of("player1", "player2")); + + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/someIdThatNotExists/players", List.class, PlayerDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_FOUND); + + var prerequisiteGroup1 = testUtils.createTestGroup(port, List.of("player1", "player2")); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_OF_GROUP); + } + + @Test + @Transactional + @SuppressWarnings("unchecked") public void players_delete_success() { - var prerequisiteGroup = testUtils.createTestGroup(port); + var profileNames = List.of("player1", "player2"); + var prerequisiteGroup = testUtils.createTestGroup(port, profileNames); + + var playersResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playersResponse, ArrayList.class); + + assertEquals(profileNames.size(), players.size()); + + var player = players.getFirst(); + + assertEquals(profileNames.getFirst(), player.getProfile().getName()); + + var response = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players/" + player.getId(), null, String.class); + var result = requestUtils.assertSuccess(response, String.class); + + assertEquals("OK", result); + + var newPlayersResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var newPlayers = (List) requestUtils.assertSuccess(newPlayersResponse, ArrayList.class); + + assertFalse(newPlayers.isEmpty()); + assertEquals(players.size(), newPlayers.size() + 1); + assertTrue(newPlayers.stream().noneMatch(playerDto -> playerDto.getProfile().getId().equals(player.getProfile().getId()))); + + var first = newPlayers.getFirst(); + + response = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players/" + first.getId(), null, String.class); + result = requestUtils.assertSuccess(response, String.class); + + assertEquals("OK", result); + + newPlayersResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + newPlayers = (List) requestUtils.assertSuccess(newPlayersResponse, ArrayList.class); + + assertTrue(newPlayers.isEmpty()); + } + + + @Test + @Transactional + @SuppressWarnings("unchecked") + public void players_delete_invalidArgs() { + var prerequisiteGroup = testUtils.createTestGroup(port, List.of("player1", "player2")); + + var playersResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playersResponse, ArrayList.class); + var player = players.getFirst(); + + var response = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players/someIdThatNotExists", null, String.class); + requestUtils.assertFailure(response, ErrorCodes.PLAYER_NOT_FOUND); + + response = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/someIdThatNotExists/players/" + player.getId(), null, String.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_FOUND); + + var prerequisiteGroup1 = testUtils.createTestGroup(port, List.of("player1", "player2")); + + response = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/players/" + player.getId(), null, String.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_OF_GROUP); + + var otherPlayersResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup1.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var otherPlayers = (List) requestUtils.assertSuccess(otherPlayersResponse, ArrayList.class); + var otherPlayer = otherPlayers.getFirst(); + + response = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players/" + otherPlayer.getId(), null, String.class); + requestUtils.assertFailure(response, ErrorCodes.PLAYER_VALIDATION_FAILED); + + response = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players/" + player.getId(), null, String.class); + var result = requestUtils.assertSuccess(response, String.class); + + assertEquals("OK", result); + response = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players/" + player.getId(), null, String.class); + requestUtils.assertFailure(response, ErrorCodes.PLAYER_ALREADY_DELETED); } } \ No newline at end of file From 60100e3d265f082faad86062064f571e78c6169a Mon Sep 17 00:00:00 2001 From: Thies Date: Fri, 13 Jun 2025 00:33:13 +0200 Subject: [PATCH 58/90] test copying of players on season start --- .../api/control/PlayerControllerTest.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java b/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java index c1dcebf8..4d0dced8 100644 --- a/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java @@ -28,6 +28,48 @@ public class PlayerControllerTest { //TODO season start: test copying of players (with statistics) + @Test + @Transactional + @SuppressWarnings("unchecked") + public void players_copy_seasonStart() { + var profileNames = List.of("player1", "player2", "player3"); + var prerequisiteGroup = testUtils.createTestGroup(port, profileNames); + var oldSeason = prerequisiteGroup.getActiveSeason(); + + var profilesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/profiles", List.class, ProfileDto.class); + var profiles = (List) requestUtils.assertSuccess(profilesResponse, ArrayList.class); + + var oldPlayersResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/players", List.class, PlayerDto.class); + var oldPlayers = (List) requestUtils.assertSuccess(oldPlayersResponse, ArrayList.class); + + var deleteResponse = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/players/" + oldPlayers.getFirst().getId(), null, String.class); + var result = requestUtils.assertSuccess(deleteResponse, String.class); + + assertEquals("OK", result); + + var seasonDto = new SeasonCreateDto(); + seasonDto.setOldSeasonName("testing"); + seasonDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var seasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + var newSeason = requestUtils.assertSuccess(seasonResponse, SeasonDto.class); + + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertEquals(profileNames.size(), profiles.size()); + assertEquals(profiles.size(), oldPlayers.size()); + assertEquals(oldPlayers.size() - 1, players.size()); + assertTrue(oldPlayers.stream().allMatch(playerDto -> profiles.stream().anyMatch(profileDto -> profileDto.getId().equals(playerDto.getProfile().getId())))); + assertTrue(players.stream().allMatch(playerDto -> playerDto.isActiveThisSeason() && + playerDto.getStatistics() != null && + playerDto.getSeason().getId().equals(newSeason.getId()) && + profiles.stream().anyMatch(profileDto -> profileDto.getId().equals(playerDto.getProfile().getId())))); + } + @Test @Transactional @SuppressWarnings("unchecked") From 9d3198085590b1d79f77c813a70833248eaa300f Mon Sep 17 00:00:00 2001 From: Thies Date: Fri, 13 Jun 2025 00:47:28 +0200 Subject: [PATCH 59/90] player tests --- .../test/java/pro/beerpong/api/TestUtils.java | 4 +- .../api/control/PlayerControllerTest.java | 12 ++-- .../api/control/ProfileControllerTest.java | 67 ++++++++++++++++--- 3 files changed, 67 insertions(+), 16 deletions(-) diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index d7ffb010..959252bb 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -11,8 +11,8 @@ @Component public class TestUtils { - //TODO tests for: assets (needs s3 files), leaderboard, match - //TODO test realtime events + //TODO tests for: realtime events, assets (needs s3 files), leaderboard, match + //TODO add descr to every test case @Autowired private RequestUtils requestUtils; diff --git a/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java b/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java index 4d0dced8..6b80cb0a 100644 --- a/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java @@ -26,8 +26,6 @@ public class PlayerControllerTest { @Autowired private TestUtils testUtils; - //TODO season start: test copying of players (with statistics) - @Test @Transactional @SuppressWarnings("unchecked") @@ -64,10 +62,12 @@ public void players_copy_seasonStart() { assertEquals(profiles.size(), oldPlayers.size()); assertEquals(oldPlayers.size() - 1, players.size()); assertTrue(oldPlayers.stream().allMatch(playerDto -> profiles.stream().anyMatch(profileDto -> profileDto.getId().equals(playerDto.getProfile().getId())))); - assertTrue(players.stream().allMatch(playerDto -> playerDto.isActiveThisSeason() && - playerDto.getStatistics() != null && - playerDto.getSeason().getId().equals(newSeason.getId()) && - profiles.stream().anyMatch(profileDto -> profileDto.getId().equals(playerDto.getProfile().getId())))); + assertTrue(oldPlayers.stream().allMatch(playerDto -> playerDto.getSeason().getId().equals(oldSeason.getId()))); + assertTrue(players.stream().allMatch(playerDto -> profiles.stream().anyMatch(profileDto -> profileDto.getId().equals(playerDto.getProfile().getId())))); + assertTrue(players.stream().allMatch(PlayerDto::isActiveThisSeason)); + assertTrue(players.stream().allMatch(playerDto -> playerDto.getSeason().getId().equals(newSeason.getId()))); + + //TODO maybe check stats? } @Test diff --git a/api/src/test/java/pro/beerpong/api/control/ProfileControllerTest.java b/api/src/test/java/pro/beerpong/api/control/ProfileControllerTest.java index da9d3628..3290bd7f 100644 --- a/api/src/test/java/pro/beerpong/api/control/ProfileControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/ProfileControllerTest.java @@ -8,10 +8,7 @@ import org.springframework.test.context.ActiveProfiles; import pro.beerpong.api.RequestUtils; import pro.beerpong.api.TestUtils; -import pro.beerpong.api.model.dto.ErrorCodes; -import pro.beerpong.api.model.dto.ProfileCreateDto; -import pro.beerpong.api.model.dto.ProfileCreatedDto; -import pro.beerpong.api.model.dto.ProfileDto; +import pro.beerpong.api.model.dto.*; import java.util.ArrayList; import java.util.List; @@ -44,9 +41,13 @@ public void profiles_create_groupCreation() { @Test @Transactional + @SuppressWarnings("unchecked") public void profiles_create_success() { - var prerequisiteGroup = testUtils.createTestGroup(port); + var profileNames = List.of("player1", "player2", "player3"); + var prerequisiteGroup = testUtils.createTestGroup(port, profileNames); + var oldSeason = prerequisiteGroup.getActiveSeason(); + // test creation of profile with a non existing name var profileDto = new ProfileCreateDto(); profileDto.setName("testing"); @@ -61,7 +62,59 @@ public void profiles_create_success() { assertFalse(result.isReactivated()); assertNull(result.getLastActiveSeasonId()); - //TODO test with deletion of players + new season start + // test reactivating of players when player is deleted + var playersResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playersResponse, ArrayList.class); + var player = players.getFirst(); + + assertTrue(profileNames.stream().anyMatch(s -> player.getProfile().getName().equals(s))); + + var deleteResponse = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/players/" + player.getId(), null, String.class); + var deleteResult = requestUtils.assertSuccess(deleteResponse, String.class); + + assertEquals("OK", deleteResult); + + profileDto.setName(player.getProfile().getName()); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/profiles", profileDto, ProfileCreatedDto.class); + result = requestUtils.assertSuccess(response, ProfileCreatedDto.class); + + assertEquals(profileDto.getName(), result.getName()); + assertEquals(prerequisiteGroup.getId(), result.getGroupId()); + + assertTrue(result.isReactivated()); + assertEquals(result.getLastActiveSeasonId(), oldSeason.getId()); + + // test linking to old profile if no player exists in the current season + playersResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/players", List.class, PlayerDto.class); + players = (List) requestUtils.assertSuccess(playersResponse, ArrayList.class); + var first = players.getFirst(); + + deleteResponse = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/players/" + first.getId(), null, String.class); + deleteResult = requestUtils.assertSuccess(deleteResponse, String.class); + + assertEquals("OK", deleteResult); + + var seasonDto = new SeasonCreateDto(); + seasonDto.setOldSeasonName("testing"); + seasonDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var seasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + requestUtils.assertSuccess(seasonResponse, SeasonDto.class); + + profileDto.setName(first.getProfile().getName()); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/profiles", profileDto, ProfileCreatedDto.class); + result = requestUtils.assertSuccess(response, ProfileCreatedDto.class); + + assertEquals(profileDto.getName(), result.getName()); + assertEquals(prerequisiteGroup.getId(), result.getGroupId()); + + assertFalse(result.isReactivated()); + assertEquals(result.getLastActiveSeasonId(), oldSeason.getId()); } @Test @@ -74,8 +127,6 @@ public void profiles_create_alreadyExists() { var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/profiles", profileDto, ProfileCreatedDto.class); requestUtils.assertFailure(response, ErrorCodes.PROFILE_ALREADY_EXISTS); - - //TODO test with deletion of players + new season start } @Test From bde3cd57c1db83de957d8978c2dec1b6ab78b2ac Mon Sep 17 00:00:00 2001 From: Thies Date: Fri, 13 Jun 2025 00:55:49 +0200 Subject: [PATCH 60/90] base of match tests --- .../beerpong/api/service/MatchService.java | 2 +- .../api/control/MatchControllerTest.java | 143 ++++++++++++++++++ 2 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java diff --git a/api/src/main/java/pro/beerpong/api/service/MatchService.java b/api/src/main/java/pro/beerpong/api/service/MatchService.java index 137455ed..ebb5a3ca 100644 --- a/api/src/main/java/pro/beerpong/api/service/MatchService.java +++ b/api/src/main/java/pro/beerpong/api/service/MatchService.java @@ -355,7 +355,7 @@ public ErrorCodes deleteMatch(String id, String seasonId, String groupId) { matchRepository.deleteById(id); } else { - error.set(ErrorCodes.PLAYER_VALIDATION_FAILED); + error.set(ErrorCodes.MATCH_DTO_VALIDATION_FAILED); } } else { error.set(ErrorCodes.SEASON_ALREADY_ENDED); diff --git a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java new file mode 100644 index 00000000..e896a3f3 --- /dev/null +++ b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java @@ -0,0 +1,143 @@ +package pro.beerpong.api.control; + +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.ActiveProfiles; +import pro.beerpong.api.RequestUtils; +import pro.beerpong.api.TestUtils; +import pro.beerpong.api.model.dto.*; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") +public class MatchControllerTest { + @LocalServerPort + private int port; + + @Autowired + private RequestUtils requestUtils; + @Autowired + private TestUtils testUtils; + + @Test + @Transactional + public void matches_create_success() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } + + @Test + public void matches_create_invalidSeason() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } + + @Test + public void matches_create_invalidTeamSizes() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } + + @Test + public void matches_create_invalidDto() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } + + @Test + @Transactional + public void matches_findAll_success() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } + + @Test + public void matches_findAll_invalidSeason() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } + + @Test + @Transactional + public void matches_findById_success() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } + + @Test + public void matches_findById_invalidArgs() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } + + @Test + @Transactional + public void matches_overviewAll_success() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } + + @Test + public void matches_overviewAll_invalidArgs() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } + + @Test + @Transactional + public void matches_overviewById_success() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } + + @Test + public void matches_overviewById_invalidArgs() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } + + @Test + @Transactional + public void matches_update_success() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } + + @Test + public void matches_update_invalidSeason() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } + + @Test + public void matches_update_invalidTeamSizes() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } + + @Test + public void matches_update_invalidDto() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } + + @Test + @Transactional + public void matches_delete_success() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } + + @Test + public void matches_delete_invalidArgs() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + } +} \ No newline at end of file From a5999f7647db4eac1f74d82ddd64d94afc0a198c Mon Sep 17 00:00:00 2001 From: Thies Date: Fri, 13 Jun 2025 11:41:11 +0200 Subject: [PATCH 61/90] create tests --- .../beerpong/api/model/dto/ErrorCodes.java | 2 +- .../beerpong/api/service/MatchService.java | 23 +- .../api/control/MatchControllerTest.java | 503 +++++++++++++++++- 3 files changed, 518 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java index 870bbefc..a0ad1f22 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java @@ -34,7 +34,7 @@ public enum ErrorCodes { MATCH_NOT_FOUND(HttpStatus.NOT_FOUND, "matchNotFound", "The requested match could not be found!"), MATCH_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "matchValidationFailed", "The validation of the created match has failed (invalid group or season id)"), MATCH_CREATE_DTO_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "matchCreateDtoValidationFailed", "The team sizes are not in the boundaries of the season settings!"), - MATCH_DTO_VALIDATION_FAILED(HttpStatus.FORBIDDEN, "matchDtoValidationFailed", "The validation of the match create dto failed (invalid player, rulemove, season or group id, or no finish move)"), + MATCH_DTO_VALIDATION_FAILED(HttpStatus.FORBIDDEN, "matchDtoValidationFailed", "The validation of the match create dto failed (invalid player, rulemove, season or group id, double player, or no/more than exactly one finish move)"), /* RULE MOVES */ RULE_MOVE_NOT_FOUND(HttpStatus.NOT_FOUND, "ruleMoveNotFound", "The requested ruleMove could not be found!"), RULE_MOVE_VALIDATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "ruleMoveValidationFailed", "The rulemove is not part of the provided season or the provided season is not part of the provided gorup!"), diff --git a/api/src/main/java/pro/beerpong/api/service/MatchService.java b/api/src/main/java/pro/beerpong/api/service/MatchService.java index ebb5a3ca..b3e0c742 100644 --- a/api/src/main/java/pro/beerpong/api/service/MatchService.java +++ b/api/src/main/java/pro/beerpong/api/service/MatchService.java @@ -90,7 +90,21 @@ public MatchService(SubscriptionHandler subscriptionHandler, } public boolean invalidCreateDto(String groupId, String seasonId, MatchCreateDto dto) { - return !dto.getTeams().stream().allMatch(teamCreateDto -> + var playerIds = dto.getTeams().stream() + .flatMap(teamCreateDto -> teamCreateDto.getTeamMembers().stream().map(TeamMemberCreateDto::getPlayerId)) + .toList(); + + var finishMoves = dto.getTeams().stream() + .flatMap(teamCreateDto -> teamCreateDto.getTeamMembers().stream()) + .flatMap(memberCreateDto -> memberCreateDto.getMoves().stream()) + .filter(matchMoveDto -> ruleMoveService.isFinish(matchMoveDto.getMoveId())) + .toList(); + + //TODO should team amount be forced to 2? + return playerIds.stream().distinct().count() != playerIds.size() || + finishMoves.size() != 1 || + finishMoves.getFirst().getCount() != 1 || + !dto.getTeams().stream().allMatch(teamCreateDto -> teamCreateDto.getTeamMembers().stream().allMatch(memberDto -> { var player = playerRepository.findById(memberDto.getPlayerId()); @@ -103,12 +117,7 @@ public boolean invalidCreateDto(String groupId, String seasonId, MatchCreateDto return move.isPresent() && move.get().getSeason().getId().equals(seasonId) && move.get().getSeason().getGroupId().equals(groupId); }); - })) || - dto.getTeams().stream() - .flatMap(teamCreateDto -> teamCreateDto.getTeamMembers().stream()) - .flatMap(memberCreateDto -> memberCreateDto.getMoves().stream()) - .filter(matchMoveDto -> ruleMoveService.isFinish(matchMoveDto.getMoveId()) && matchMoveDto.getCount() == 1) - .count() != 1; + })); } @Transactional diff --git a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java index e896a3f3..f92c08f6 100644 --- a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java @@ -28,9 +28,482 @@ public class MatchControllerTest { @Test @Transactional - public void matches_create_success() { + @SuppressWarnings("unchecked") + public void matches_create_success_basic() { var prerequisiteGroup = testUtils.createTestGroup(port); + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertTrue(players.size() >= 2); + + var player1 = players.getFirst(); + var player2 = players.getLast(); + + assertNotNull(player1); + assertNotNull(player2); + assertNotEquals(player1, player2); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMove = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(normalMove); + assertFalse(normalMove.isFinishingMove()); + assertNotNull(finishMove); + assertTrue(finishMove.isFinishingMove()); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 5), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId(), + buildMove(normalMove.getId(), 3) + ) + ) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var match = requestUtils.assertSuccess(response, MatchDto.class); + + assertNotNull(match.getId()); + assertEquals(prerequisiteGroup.getActiveSeason().getId(), match.getSeason().getId()); + assertEquals(prerequisiteGroup.getCreatedBy(), match.getCreatedBy()); + assertNotNull(match.getDate()); + + assertEquals(2, match.getTeams().size()); + + var team1 = match.getTeams().getFirst(); + var team2 = match.getTeams().getLast(); + + assertNotNull(team1); + assertNotNull(team1.getId()); + assertEquals(match.getId(), team1.getMatchId()); + assertNotNull(team2); + assertNotNull(team2.getId()); + assertEquals(match.getId(), team2.getMatchId()); + + var teamMembers1 = match.getTeamMembers().stream() + .filter(teamMemberDto -> teamMemberDto.getTeamId().equals(team1.getId())) + .toList(); + var teamMembers2 = match.getTeamMembers().stream() + .filter(teamMemberDto -> teamMemberDto.getTeamId().equals(team2.getId())) + .toList(); + + assertEquals(1, teamMembers1.size()); + assertEquals(1, teamMembers2.size()); + + var teamMember1 = teamMembers1.getFirst(); + var teamMember2 = teamMembers2.getFirst(); + + assertNotNull(teamMember1); + assertNotNull(teamMember1.getId()); + assertEquals(team1.getId(), teamMember1.getTeamId()); + assertEquals(player1.getId(), teamMember1.getPlayerId()); + assertNotNull(teamMember2); + assertNotNull(teamMember2.getId()); + assertEquals(team2.getId(), teamMember2.getTeamId()); + assertEquals(player2.getId(), teamMember2.getPlayerId()); + + var matchMoves1 = match.getMatchMoves().stream() + .filter(matchMove -> matchMove.getTeamMemberId().equals(teamMember1.getId())) + .toList(); + var matchMoves2 = match.getMatchMoves().stream() + .filter(matchMove -> matchMove.getTeamMemberId().equals(teamMember2.getId())) + .toList(); + + assertEquals(2, matchMoves1.size()); + assertEquals(1, matchMoves2.size()); + + var normalMove1 = matchMoves1.stream().filter(dto -> dto.getMoveId().equals(normalMove.getId())).findFirst().orElseThrow(); + var finishMove1 = matchMoves1.stream().filter(dto -> dto.getMoveId().equals(finishMove.getId())).findFirst().orElseThrow(); + var normalMove2 = matchMoves2.getFirst(); + + assertNotNull(normalMove1); + assertNotNull(normalMove1.getId()); + assertEquals(teamMember1.getId(), normalMove1.getTeamMemberId()); + assertEquals(normalMove.getId(), normalMove1.getMoveId()); + assertEquals(5, normalMove1.getValue()); + + assertNotNull(finishMove1); + assertNotNull(finishMove1.getId()); + assertEquals(teamMember1.getId(), finishMove1.getTeamMemberId()); + assertEquals(finishMove.getId(), finishMove1.getMoveId()); + assertEquals(1, finishMove1.getValue()); + + assertNotNull(normalMove2); + assertNotNull(normalMove2.getId()); + assertEquals(teamMember2.getId(), normalMove2.getTeamMemberId()); + assertEquals(normalMove.getId(), normalMove2.getMoveId()); + assertEquals(3, normalMove2.getValue()); + } + + @Test + @Transactional + @SuppressWarnings("unchecked") + public void matches_create_success_morePlayers() { + var profileNames = List.of("player1", "player2", "player3", "player4"); + var prerequisiteGroup = testUtils.createTestGroup(port, profileNames); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertEquals(profileNames.size(), players.size()); + + var player1 = players.getFirst(); + var player2 = players.get(1); + var player3 = players.get(2); + var player4 = players.get(3); + + assertNotNull(player1); + assertNotNull(player2); + assertNotNull(player3); + assertNotNull(player4); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMove = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(normalMove); + assertFalse(normalMove.isFinishingMove()); + assertNotNull(finishMove); + assertTrue(finishMove.isFinishingMove()); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 5), + buildMove(finishMove.getId(), 1) + ), + buildMember( + player2.getId(), + buildMove(normalMove.getId(), 2) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 4) + ), + buildMember( + player4.getId(), + buildMove(normalMove.getId(), 2) + ) + ) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var match = requestUtils.assertSuccess(response, MatchDto.class); + + assertNotNull(match.getId()); + assertEquals(prerequisiteGroup.getActiveSeason().getId(), match.getSeason().getId()); + assertEquals(prerequisiteGroup.getCreatedBy(), match.getCreatedBy()); + assertNotNull(match.getDate()); + + assertEquals(2, match.getTeams().size()); + + var team1 = match.getTeams().getFirst(); + var team2 = match.getTeams().getLast(); + + assertNotNull(team1); + assertNotNull(team1.getId()); + assertEquals(match.getId(), team1.getMatchId()); + assertNotNull(team2); + assertNotNull(team2.getId()); + assertEquals(match.getId(), team2.getMatchId()); + + var teamMembers1 = match.getTeamMembers().stream() + .filter(teamMemberDto -> teamMemberDto.getTeamId().equals(team1.getId())) + .toList(); + var teamMembers2 = match.getTeamMembers().stream() + .filter(teamMemberDto -> teamMemberDto.getTeamId().equals(team2.getId())) + .toList(); + + assertEquals(2, teamMembers1.size()); + assertEquals(2, teamMembers2.size()); + + var teamMember1 = teamMembers1.getFirst(); + var teamMember2 = teamMembers1.getLast(); + var teamMember3 = teamMembers2.getFirst(); + var teamMember4 = teamMembers2.getLast(); + + assertNotNull(teamMember1); + assertNotNull(teamMember1.getId()); + assertEquals(team1.getId(), teamMember1.getTeamId()); + assertEquals(player1.getId(), teamMember1.getPlayerId()); + assertNotNull(teamMember2); + assertNotNull(teamMember2.getId()); + assertEquals(team1.getId(), teamMember2.getTeamId()); + assertEquals(player2.getId(), teamMember2.getPlayerId()); + assertNotNull(teamMember3); + assertNotNull(teamMember3.getId()); + assertEquals(team2.getId(), teamMember3.getTeamId()); + assertEquals(player3.getId(), teamMember3.getPlayerId()); + assertNotNull(teamMember4); + assertNotNull(teamMember4.getId()); + assertEquals(team2.getId(), teamMember4.getTeamId()); + assertEquals(player4.getId(), teamMember4.getPlayerId()); + + var matchMoves1 = match.getMatchMoves().stream() + .filter(matchMove -> matchMove.getTeamMemberId().equals(teamMember1.getId())) + .toList(); + var matchMoves2 = match.getMatchMoves().stream() + .filter(matchMove -> matchMove.getTeamMemberId().equals(teamMember2.getId())) + .toList(); + var matchMoves3 = match.getMatchMoves().stream() + .filter(matchMove -> matchMove.getTeamMemberId().equals(teamMember3.getId())) + .toList(); + var matchMoves4 = match.getMatchMoves().stream() + .filter(matchMove -> matchMove.getTeamMemberId().equals(teamMember4.getId())) + .toList(); + + assertEquals(2, matchMoves1.size()); + assertEquals(1, matchMoves2.size()); + assertEquals(1, matchMoves3.size()); + assertEquals(1, matchMoves4.size()); + + var normalMove1 = matchMoves1.stream().filter(dto -> dto.getMoveId().equals(normalMove.getId())).findFirst().orElseThrow(); + var finishMove1 = matchMoves1.stream().filter(dto -> dto.getMoveId().equals(finishMove.getId())).findFirst().orElseThrow(); + var normalMove2 = matchMoves2.getFirst(); + var normalMove3 = matchMoves3.getFirst(); + var normalMove4 = matchMoves4.getFirst(); + + assertNotNull(normalMove1); + assertNotNull(normalMove1.getId()); + assertEquals(teamMember1.getId(), normalMove1.getTeamMemberId()); + assertEquals(normalMove.getId(), normalMove1.getMoveId()); + assertEquals(5, normalMove1.getValue()); + + assertNotNull(finishMove1); + assertNotNull(finishMove1.getId()); + assertEquals(teamMember1.getId(), finishMove1.getTeamMemberId()); + assertEquals(finishMove.getId(), finishMove1.getMoveId()); + assertEquals(1, finishMove1.getValue()); + + assertNotNull(normalMove2); + assertNotNull(normalMove2.getId()); + assertEquals(teamMember2.getId(), normalMove2.getTeamMemberId()); + assertEquals(normalMove.getId(), normalMove2.getMoveId()); + assertEquals(2, normalMove2.getValue()); + + assertNotNull(normalMove3); + assertNotNull(normalMove3.getId()); + assertEquals(teamMember3.getId(), normalMove3.getTeamMemberId()); + assertEquals(normalMove.getId(), normalMove3.getMoveId()); + assertEquals(4, normalMove3.getValue()); + + assertNotNull(normalMove4); + assertNotNull(normalMove4.getId()); + assertEquals(teamMember4.getId(), normalMove4.getTeamMemberId()); + assertEquals(normalMove.getId(), normalMove4.getMoveId()); + assertEquals(2, normalMove4.getValue()); + } + + @Test + @Transactional + @SuppressWarnings("unchecked") + public void matches_create_success_moreMoves() { + var profileNames = List.of("player1", "player2", "player3", "player4"); + var prerequisiteGroup = testUtils.createTestGroup(port, profileNames); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertEquals(profileNames.size(), players.size()); + + var player1 = players.getFirst(); + var player2 = players.get(1); + var player3 = players.get(2); + var player4 = players.get(3); + + assertNotNull(player1); + assertNotNull(player2); + assertNotNull(player3); + assertNotNull(player4); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMoves = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).toList(); + + assertTrue(normalMoves.size() >= 2); + + var frstNormalMove = normalMoves.getFirst(); + var scndNormalMove = normalMoves.get(1); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(frstNormalMove); + assertFalse(frstNormalMove.isFinishingMove()); + assertNotNull(scndNormalMove); + assertFalse(scndNormalMove.isFinishingMove()); + assertNotNull(finishMove); + assertTrue(finishMove.isFinishingMove()); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(frstNormalMove.getId(), 5), + buildMove(scndNormalMove.getId(), 2), + buildMove(finishMove.getId(), 1) + ), + buildMember( + player2.getId(), + buildMove(frstNormalMove.getId(), 2) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(frstNormalMove.getId(), 4), + buildMove(scndNormalMove.getId(), 6) + ), + buildMember( + player4.getId(), + buildMove(scndNormalMove.getId(), 2) + ) + ) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var match = requestUtils.assertSuccess(response, MatchDto.class); + + assertNotNull(match.getId()); + assertEquals(prerequisiteGroup.getActiveSeason().getId(), match.getSeason().getId()); + assertEquals(prerequisiteGroup.getCreatedBy(), match.getCreatedBy()); + assertNotNull(match.getDate()); + + assertEquals(2, match.getTeams().size()); + + var team1 = match.getTeams().getFirst(); + var team2 = match.getTeams().getLast(); + + assertNotNull(team1); + assertNotNull(team1.getId()); + assertEquals(match.getId(), team1.getMatchId()); + assertNotNull(team2); + assertNotNull(team2.getId()); + assertEquals(match.getId(), team2.getMatchId()); + + var teamMembers1 = match.getTeamMembers().stream() + .filter(teamMemberDto -> teamMemberDto.getTeamId().equals(team1.getId())) + .toList(); + var teamMembers2 = match.getTeamMembers().stream() + .filter(teamMemberDto -> teamMemberDto.getTeamId().equals(team2.getId())) + .toList(); + + assertEquals(2, teamMembers1.size()); + assertEquals(2, teamMembers2.size()); + + var teamMember1 = teamMembers1.getFirst(); + var teamMember2 = teamMembers1.getLast(); + var teamMember3 = teamMembers2.getFirst(); + var teamMember4 = teamMembers2.getLast(); + + assertNotNull(teamMember1); + assertNotNull(teamMember1.getId()); + assertEquals(team1.getId(), teamMember1.getTeamId()); + assertEquals(player1.getId(), teamMember1.getPlayerId()); + + assertNotNull(teamMember2); + assertNotNull(teamMember2.getId()); + assertEquals(team1.getId(), teamMember2.getTeamId()); + assertEquals(player2.getId(), teamMember2.getPlayerId()); + + assertNotNull(teamMember3); + assertNotNull(teamMember3.getId()); + assertEquals(team2.getId(), teamMember3.getTeamId()); + assertEquals(player3.getId(), teamMember3.getPlayerId()); + + assertNotNull(teamMember4); + assertNotNull(teamMember4.getId()); + assertEquals(team2.getId(), teamMember4.getTeamId()); + assertEquals(player4.getId(), teamMember4.getPlayerId()); + + var matchMoves1 = match.getMatchMoves().stream() + .filter(matchMove -> matchMove.getTeamMemberId().equals(teamMember1.getId())) + .toList(); + var matchMoves2 = match.getMatchMoves().stream() + .filter(matchMove -> matchMove.getTeamMemberId().equals(teamMember2.getId())) + .toList(); + var matchMoves3 = match.getMatchMoves().stream() + .filter(matchMove -> matchMove.getTeamMemberId().equals(teamMember3.getId())) + .toList(); + var matchMoves4 = match.getMatchMoves().stream() + .filter(matchMove -> matchMove.getTeamMemberId().equals(teamMember4.getId())) + .toList(); + + assertEquals(3, matchMoves1.size()); + assertEquals(1, matchMoves2.size()); + assertEquals(2, matchMoves3.size()); + assertEquals(1, matchMoves4.size()); + + var normalMove11 = matchMoves1.stream().filter(dto -> dto.getMoveId().equals(frstNormalMove.getId())).findFirst().orElseThrow(); + var normalMove12 = matchMoves1.stream().filter(dto -> dto.getMoveId().equals(scndNormalMove.getId())).findFirst().orElseThrow(); + var finishMove1 = matchMoves1.stream().filter(dto -> dto.getMoveId().equals(finishMove.getId())).findFirst().orElseThrow(); + var normalMove21 = matchMoves2.getFirst(); + var normalMove31 = matchMoves1.stream().filter(dto -> dto.getMoveId().equals(frstNormalMove.getId())).findFirst().orElseThrow(); + var normalMove32 = matchMoves1.stream().filter(dto -> dto.getMoveId().equals(scndNormalMove.getId())).findFirst().orElseThrow(); + var normalMove42 = matchMoves4.getFirst(); + + assertNotNull(normalMove11); + assertNotNull(normalMove11.getId()); + assertEquals(teamMember1.getId(), normalMove11.getTeamMemberId()); + assertEquals(frstNormalMove.getId(), normalMove11.getMoveId()); + assertEquals(5, normalMove11.getValue()); + + assertNotNull(normalMove12); + assertNotNull(normalMove12.getId()); + assertEquals(teamMember1.getId(), normalMove12.getTeamMemberId()); + assertEquals(scndNormalMove.getId(), normalMove12.getMoveId()); + assertEquals(2, normalMove12.getValue()); + + assertNotNull(finishMove1); + assertNotNull(finishMove1.getId()); + assertEquals(teamMember1.getId(), finishMove1.getTeamMemberId()); + assertEquals(finishMove.getId(), finishMove1.getMoveId()); + assertEquals(1, finishMove1.getValue()); + + assertNotNull(normalMove21); + assertNotNull(normalMove21.getId()); + assertEquals(teamMember2.getId(), normalMove21.getTeamMemberId()); + assertEquals(frstNormalMove.getId(), normalMove21.getMoveId()); + assertEquals(2, normalMove21.getValue()); + + assertNotNull(normalMove31); + assertNotNull(normalMove31.getId()); + assertEquals(teamMember3.getId(), normalMove31.getTeamMemberId()); + assertEquals(frstNormalMove.getId(), normalMove31.getMoveId()); + assertEquals(4, normalMove31.getValue()); + + assertNotNull(normalMove32); + assertNotNull(normalMove32.getId()); + assertEquals(teamMember3.getId(), normalMove32.getTeamMemberId()); + assertEquals(scndNormalMove.getId(), normalMove32.getMoveId()); + assertEquals(6, normalMove32.getValue()); + + assertNotNull(normalMove42); + assertNotNull(normalMove42.getId()); + assertEquals(teamMember4.getId(), normalMove42.getTeamMemberId()); + assertEquals(scndNormalMove.getId(), normalMove42.getMoveId()); + assertEquals(2, normalMove42.getValue()); } @Test @@ -48,7 +521,7 @@ public void matches_create_invalidTeamSizes() { @Test public void matches_create_invalidDto() { var prerequisiteGroup = testUtils.createTestGroup(port); - + // test player double } @Test @@ -140,4 +613,30 @@ public void matches_delete_invalidArgs() { var prerequisiteGroup = testUtils.createTestGroup(port); } + + private MatchCreateDto buildDto(TeamCreateDto... teams) { + var dto = new MatchCreateDto(); + dto.setTeams(List.of(teams)); + return dto; + } + + private TeamCreateDto buildTeam(TeamMemberCreateDto... members) { + var dto = new TeamCreateDto(); + dto.setTeamMembers(List.of(members)); + return dto; + } + + private TeamMemberCreateDto buildMember(String playerId, MatchMoveDto... moves) { + var dto = new TeamMemberCreateDto(); + dto.setPlayerId(playerId); + dto.setMoves(List.of(moves)); + return dto; + } + + private MatchMoveDto buildMove(String moveId, int amount) { + var dto = new MatchMoveDto(); + dto.setMoveId(moveId); + dto.setCount(amount); + return dto; + } } \ No newline at end of file From 18412b40775daf3d9045e91499ab32873f611674 Mon Sep 17 00:00:00 2001 From: Thies Date: Fri, 13 Jun 2025 11:41:55 +0200 Subject: [PATCH 62/90] fix tests --- .../java/pro/beerpong/api/control/MatchControllerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java index f92c08f6..59977459 100644 --- a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java @@ -459,8 +459,8 @@ public void matches_create_success_moreMoves() { var normalMove12 = matchMoves1.stream().filter(dto -> dto.getMoveId().equals(scndNormalMove.getId())).findFirst().orElseThrow(); var finishMove1 = matchMoves1.stream().filter(dto -> dto.getMoveId().equals(finishMove.getId())).findFirst().orElseThrow(); var normalMove21 = matchMoves2.getFirst(); - var normalMove31 = matchMoves1.stream().filter(dto -> dto.getMoveId().equals(frstNormalMove.getId())).findFirst().orElseThrow(); - var normalMove32 = matchMoves1.stream().filter(dto -> dto.getMoveId().equals(scndNormalMove.getId())).findFirst().orElseThrow(); + var normalMove31 = matchMoves3.stream().filter(dto -> dto.getMoveId().equals(frstNormalMove.getId())).findFirst().orElseThrow(); + var normalMove32 = matchMoves3.stream().filter(dto -> dto.getMoveId().equals(scndNormalMove.getId())).findFirst().orElseThrow(); var normalMove42 = matchMoves4.getFirst(); assertNotNull(normalMove11); From 77f0ddb511c14f924d17e4f6802e62ede47b0847 Mon Sep 17 00:00:00 2001 From: Thies Date: Fri, 13 Jun 2025 11:55:13 +0200 Subject: [PATCH 63/90] force team amount --- .../beerpong/api/control/MatchController.java | 7 + .../beerpong/api/model/dto/ErrorCodes.java | 1 + .../api/control/MatchControllerTest.java | 224 +++++++++++++++++- 3 files changed, 231 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/pro/beerpong/api/control/MatchController.java b/api/src/main/java/pro/beerpong/api/control/MatchController.java index dd7fc0c9..326696e7 100644 --- a/api/src/main/java/pro/beerpong/api/control/MatchController.java +++ b/api/src/main/java/pro/beerpong/api/control/MatchController.java @@ -17,6 +17,9 @@ @RestController @RequestMapping("/groups/{groupId}/seasons/{seasonId}/matches") public class MatchController { + private static final int MIN_TEAM_AMOUNT = 2; + private static final int MAX_TEAM_AMOUNT = 2; + private final MatchService matchService; private final SeasonService seasonService; private final SubscriptionHandler subscriptionHandler; @@ -47,6 +50,10 @@ public ResponseEntity> createMatch(@PathVariable Stri return ResponseEnvelope.notOk(ErrorCodes.MATCH_CREATE_DTO_VALIDATION_FAILED); } + if (matchCreateDto.getTeams().size() < MIN_TEAM_AMOUNT || matchCreateDto.getTeams().size() > MAX_TEAM_AMOUNT) { + return ResponseEnvelope.notOk(ErrorCodes.MATCH_WRONG_AMOUNT_OF_TEAMS); + } + var match = matchService.createNewMatch(pair.getFirst(), pair.getSecond(), matchCreateDto, user); if (match != null) { diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java index a0ad1f22..7615019b 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java @@ -34,6 +34,7 @@ public enum ErrorCodes { MATCH_NOT_FOUND(HttpStatus.NOT_FOUND, "matchNotFound", "The requested match could not be found!"), MATCH_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "matchValidationFailed", "The validation of the created match has failed (invalid group or season id)"), MATCH_CREATE_DTO_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "matchCreateDtoValidationFailed", "The team sizes are not in the boundaries of the season settings!"), + MATCH_WRONG_AMOUNT_OF_TEAMS(HttpStatus.BAD_REQUEST, "matchWrongAmountOfTeams", "The amount of teams has to be exactly 2!"), MATCH_DTO_VALIDATION_FAILED(HttpStatus.FORBIDDEN, "matchDtoValidationFailed", "The validation of the match create dto failed (invalid player, rulemove, season or group id, double player, or no/more than exactly one finish move)"), /* RULE MOVES */ RULE_MOVE_NOT_FOUND(HttpStatus.NOT_FOUND, "ruleMoveNotFound", "The requested ruleMove could not be found!"), diff --git a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java index 59977459..bb8d9754 100644 --- a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java @@ -507,15 +507,237 @@ public void matches_create_success_moreMoves() { } @Test + @Transactional + @SuppressWarnings("unchecked") + public void matches_create_success_onlyFinish() { + var profileNames = List.of("player1", "player2", "player3", "player4"); + var prerequisiteGroup = testUtils.createTestGroup(port, profileNames); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertEquals(profileNames.size(), players.size()); + + var player1 = players.getFirst(); + var player2 = players.get(1); + var player3 = players.get(2); + var player4 = players.get(3); + + assertNotNull(player1); + assertNotNull(player2); + assertNotNull(player3); + assertNotNull(player4); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(finishMove); + assertTrue(finishMove.isFinishingMove()); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(finishMove.getId(), 1) + ), + buildMember( + player2.getId() + ) + ), + buildTeam( + buildMember( + player3.getId() + ), + buildMember( + player4.getId() + ) + ) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var match = requestUtils.assertSuccess(response, MatchDto.class); + + assertNotNull(match.getId()); + assertEquals(prerequisiteGroup.getActiveSeason().getId(), match.getSeason().getId()); + assertEquals(prerequisiteGroup.getCreatedBy(), match.getCreatedBy()); + assertNotNull(match.getDate()); + + assertEquals(2, match.getTeams().size()); + + var team1 = match.getTeams().getFirst(); + var team2 = match.getTeams().getLast(); + + assertNotNull(team1); + assertNotNull(team1.getId()); + assertEquals(match.getId(), team1.getMatchId()); + assertNotNull(team2); + assertNotNull(team2.getId()); + assertEquals(match.getId(), team2.getMatchId()); + + var teamMembers1 = match.getTeamMembers().stream() + .filter(teamMemberDto -> teamMemberDto.getTeamId().equals(team1.getId())) + .toList(); + var teamMembers2 = match.getTeamMembers().stream() + .filter(teamMemberDto -> teamMemberDto.getTeamId().equals(team2.getId())) + .toList(); + + assertEquals(2, teamMembers1.size()); + assertEquals(2, teamMembers2.size()); + + var teamMember1 = teamMembers1.getFirst(); + var teamMember2 = teamMembers1.getLast(); + var teamMember3 = teamMembers2.getFirst(); + var teamMember4 = teamMembers2.getLast(); + + assertNotNull(teamMember1); + assertNotNull(teamMember1.getId()); + assertEquals(team1.getId(), teamMember1.getTeamId()); + assertEquals(player1.getId(), teamMember1.getPlayerId()); + + assertNotNull(teamMember2); + assertNotNull(teamMember2.getId()); + assertEquals(team1.getId(), teamMember2.getTeamId()); + assertEquals(player2.getId(), teamMember2.getPlayerId()); + + assertNotNull(teamMember3); + assertNotNull(teamMember3.getId()); + assertEquals(team2.getId(), teamMember3.getTeamId()); + assertEquals(player3.getId(), teamMember3.getPlayerId()); + + assertNotNull(teamMember4); + assertNotNull(teamMember4.getId()); + assertEquals(team2.getId(), teamMember4.getTeamId()); + assertEquals(player4.getId(), teamMember4.getPlayerId()); + + var matchMoves1 = match.getMatchMoves().stream() + .filter(matchMove -> matchMove.getTeamMemberId().equals(teamMember1.getId())) + .toList(); + + assertEquals(1, matchMoves1.size()); + + var finishMove1 = matchMoves1.stream().filter(dto -> dto.getMoveId().equals(finishMove.getId())).findFirst().orElseThrow(); + + assertNotNull(finishMove1); + assertNotNull(finishMove1.getId()); + assertEquals(teamMember1.getId(), finishMove1.getTeamMemberId()); + assertEquals(finishMove.getId(), finishMove1.getMoveId()); + assertEquals(1, finishMove1.getValue()); + } + + @Test + @Transactional public void matches_create_invalidSeason() { var prerequisiteGroup = testUtils.createTestGroup(port); + var oldSeason = prerequisiteGroup.getActiveSeason(); + + var matchDto = buildDto(); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/someIdThatNotExists/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_FOUND); + + var prerequisiteGroup1 = testUtils.createTestGroup(port, List.of("player1", "player2")); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_OF_GROUP); + var seasonCreateDto = new SeasonCreateDto(); + seasonCreateDto.setOldSeasonName("testing"); + seasonCreateDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonCreateDto, SeasonDto.class); + requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_ALREADY_ENDED); } @Test - public void matches_create_invalidTeamSizes() { + @Transactional + @SuppressWarnings("unchecked") + public void matches_create_invalidTeams() { var prerequisiteGroup = testUtils.createTestGroup(port); + var oldSeason = prerequisiteGroup.getActiveSeason(); + var minTeamSize = oldSeason.getSeasonSettings().getMinTeamSize(); + var maxTeamSize = oldSeason.getSeasonSettings().getMaxTeamSize(); + + assertEquals(1, minTeamSize); + assertEquals(10, maxTeamSize); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertEquals(2, players.size()); + + var player1 = players.getFirst(); + var player2 = players.getLast(); + + var matchDto = buildDto( + buildTeam( + buildMember(player1.getId()) + ), + buildTeam( + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()) + ) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_CREATE_DTO_VALIDATION_FAILED); + + matchDto = buildDto( + buildTeam( + buildMember(player1.getId()) + ), + buildTeam() + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_CREATE_DTO_VALIDATION_FAILED); + + matchDto = buildDto( + buildTeam( + buildMember(player1.getId()) + ), + buildTeam( + buildMember(player2.getId()) + ), + buildTeam( + buildMember(player2.getId()) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_WRONG_AMOUNT_OF_TEAMS); + + matchDto = buildDto( + buildTeam( + buildMember(player1.getId()) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_WRONG_AMOUNT_OF_TEAMS); + + matchDto = buildDto(); + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_WRONG_AMOUNT_OF_TEAMS); } @Test From c3dac4248e73c3223717b5beb61e6175749ae52e Mon Sep 17 00:00:00 2001 From: Thies Date: Fri, 13 Jun 2025 12:24:21 +0200 Subject: [PATCH 64/90] invalid create tests --- .../api/service/MatchMoveService.java | 4 + .../beerpong/api/service/MatchService.java | 31 +- .../api/control/MatchControllerTest.java | 697 +++++++++++++++++- 3 files changed, 710 insertions(+), 22 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/service/MatchMoveService.java b/api/src/main/java/pro/beerpong/api/service/MatchMoveService.java index 90b8f77d..b0abfbec 100644 --- a/api/src/main/java/pro/beerpong/api/service/MatchMoveService.java +++ b/api/src/main/java/pro/beerpong/api/service/MatchMoveService.java @@ -33,6 +33,10 @@ public MatchMoveService(MatchMoveRepository matchMoveRepository, public void createMatchMoves(TeamMember teamMember, List moves) { for (MatchMoveDto moveDto : moves) { + if (moveDto.getCount() < 1) { + continue; + } + RuleMove ruleMove = ruleMoveRepository.findById(moveDto.getMoveId()) .orElseThrow(() -> new IllegalArgumentException("RuleMove not found: " + moveDto.getMoveId())); if (!Objects.equals(ruleMove.getSeason().getId(), teamMember.getTeam().getMatch().getSeason().getId())) { diff --git a/api/src/main/java/pro/beerpong/api/service/MatchService.java b/api/src/main/java/pro/beerpong/api/service/MatchService.java index b3e0c742..774b5594 100644 --- a/api/src/main/java/pro/beerpong/api/service/MatchService.java +++ b/api/src/main/java/pro/beerpong/api/service/MatchService.java @@ -21,8 +21,8 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.function.Predicate; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Predicate; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -97,27 +97,26 @@ public boolean invalidCreateDto(String groupId, String seasonId, MatchCreateDto var finishMoves = dto.getTeams().stream() .flatMap(teamCreateDto -> teamCreateDto.getTeamMembers().stream()) .flatMap(memberCreateDto -> memberCreateDto.getMoves().stream()) - .filter(matchMoveDto -> ruleMoveService.isFinish(matchMoveDto.getMoveId())) + .filter(matchMoveDto -> ruleMoveService.isFinish(matchMoveDto.getMoveId()) && matchMoveDto.getCount() > 0) .toList(); - //TODO should team amount be forced to 2? return playerIds.stream().distinct().count() != playerIds.size() || finishMoves.size() != 1 || finishMoves.getFirst().getCount() != 1 || !dto.getTeams().stream().allMatch(teamCreateDto -> - teamCreateDto.getTeamMembers().stream().allMatch(memberDto -> { - var player = playerRepository.findById(memberDto.getPlayerId()); + teamCreateDto.getTeamMembers().stream().allMatch(memberDto -> { + var player = playerRepository.findById(memberDto.getPlayerId()); - if (player.isEmpty() || !player.get().getSeason().getId().equals(seasonId) || !player.get().getSeason().getGroupId().equals(groupId)) { - return false; - } + if (player.isEmpty() || !player.get().getSeason().getId().equals(seasonId) || !player.get().getSeason().getGroupId().equals(groupId)) { + return false; + } - return memberDto.getMoves().stream().allMatch(matchMoveDto -> { - var move = ruleMoveRepository.findById(matchMoveDto.getMoveId()); + return memberDto.getMoves().stream().allMatch(matchMoveDto -> { + var move = ruleMoveRepository.findById(matchMoveDto.getMoveId()); - return move.isPresent() && move.get().getSeason().getId().equals(seasonId) && move.get().getSeason().getGroupId().equals(groupId); - }); - })); + return move.isPresent() && move.get().getSeason().getId().equals(seasonId) && move.get().getSeason().getGroupId().equals(groupId); + }); + })); } @Transactional @@ -219,8 +218,10 @@ public Stream streamAllMatchesToday(GroupDto group, SeasonDto season) var now = ZonedDateTime.now(); Predicate predicate = switch (season.getSeasonSettings().getDailyLeaderboard()) { - case WAKE_TIME -> match -> match.getDate().isAfter(getWakeTime(now, season.getSeasonSettings().getWakeTime())); - case LAST_24_HOURS -> (match) -> !match.getDate().isAfter(now) && Duration.between(match.getDate(), now).toMinutes() < MINUTES_IN_DAY; + case WAKE_TIME -> + match -> match.getDate().isAfter(getWakeTime(now, season.getSeasonSettings().getWakeTime())); + case LAST_24_HOURS -> + (match) -> !match.getDate().isAfter(now) && Duration.between(match.getDate(), now).toMinutes() < MINUTES_IN_DAY; case RESET_AT_MIDNIGHT -> (match) -> match.getDate().toLocalDate().equals(now.toLocalDate()); }; diff --git a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java index bb8d9754..f9df2164 100644 --- a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java @@ -362,22 +362,26 @@ public void matches_create_success_moreMoves() { player1.getId(), buildMove(frstNormalMove.getId(), 5), buildMove(scndNormalMove.getId(), 2), - buildMove(finishMove.getId(), 1) + buildMove(finishMove.getId(), 1), + buildMove(finishMove.getId(), 0) ), buildMember( player2.getId(), - buildMove(frstNormalMove.getId(), 2) + buildMove(frstNormalMove.getId(), 2), + buildMove(finishMove.getId(), 0) ) ), buildTeam( buildMember( player3.getId(), buildMove(frstNormalMove.getId(), 4), - buildMove(scndNormalMove.getId(), 6) + buildMove(scndNormalMove.getId(), 6), + buildMove(finishMove.getId(), 0) ), buildMember( player4.getId(), - buildMove(scndNormalMove.getId(), 2) + buildMove(scndNormalMove.getId(), 2), + buildMove(finishMove.getId(), 0) ) ) ); @@ -741,9 +745,688 @@ public void matches_create_invalidTeams() { } @Test - public void matches_create_invalidDto() { - var prerequisiteGroup = testUtils.createTestGroup(port); - // test player double + @SuppressWarnings("unchecked") + public void matches_create_nonUniquePlayers() { + var profileNames = List.of("player1", "player2", "player3", "player4"); + var prerequisiteGroup = testUtils.createTestGroup(port, profileNames); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertEquals(profileNames.size(), players.size()); + + var player1 = players.getFirst(); + var player2 = players.get(1); + var player3 = players.get(2); + var player4 = players.get(3); + + assertNotNull(player1); + assertNotNull(player2); + assertNotNull(player3); + assertNotNull(player4); + + // test non unique players in same team + var matchDto = buildDto( + buildTeam( + buildMember(player1.getId()), + buildMember(player2.getId()) + ), + buildTeam( + buildMember(player3.getId()), + buildMember(player3.getId()) + ) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test non unique players in different teams + matchDto = buildDto( + buildTeam( + buildMember(player1.getId()), + buildMember(player3.getId()) + ), + buildTeam( + buildMember(player3.getId()), + buildMember(player4.getId()) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + } + + @Test + @SuppressWarnings("unchecked") + public void matches_create_invalidFinishMove() { + var profileNames = List.of("player1", "player2", "player3", "player4"); + var prerequisiteGroup = testUtils.createTestGroup(port, profileNames); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertEquals(profileNames.size(), players.size()); + + var player1 = players.getFirst(); + var player2 = players.get(1); + var player3 = players.get(2); + var player4 = players.get(3); + + assertNotNull(player1); + assertNotNull(player2); + assertNotNull(player3); + assertNotNull(player4); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMove = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(normalMove); + assertFalse(normalMove.isFinishingMove()); + assertNotNull(finishMove); + assertTrue(finishMove.isFinishingMove()); + + // test too many finish moves in different teams + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 2), + buildMove(finishMove.getId(), 1) + ), + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 2) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(finishMove.getId(), 2) + ), + buildMember( + player4.getId() + ) + ) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test too many finish moves in same team + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 2), + buildMove(finishMove.getId(), 1) + ), + buildMember( + player3.getId(), + buildMove(finishMove.getId(), 2) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 2) + ), + buildMember( + player4.getId() + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test no finish move + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 2) + ), + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 2) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 2), + buildMove(finishMove.getId(), 0) + ), + buildMember( + player4.getId() + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test finish move amount!=1 + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 2) + ), + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 2), + buildMove(finishMove.getId(), 2) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 2) + ), + buildMember( + player4.getId() + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test finish move amount!=1 + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 2) + ), + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 2), + buildMove(finishMove.getId(), 2) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 2) + ), + buildMember( + player4.getId() + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + } + + @Test + @SuppressWarnings("unchecked") + public void matches_create_invalidPlayers() { + var profileNames = List.of("player1", "player2", "player3"); + var prerequisiteGroup = testUtils.createTestGroup(port, profileNames); + var oldSeason = prerequisiteGroup.getActiveSeason(); + + var oldPlayerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/players", List.class, PlayerDto.class); + var oldPlayers = (List) requestUtils.assertSuccess(oldPlayerResponse, ArrayList.class); + + assertEquals(profileNames.size(), oldPlayers.size()); + + var oldPlayer1 = oldPlayers.getFirst(); + var oldPlayer2 = oldPlayers.get(1); + var oldPlayer3 = oldPlayers.get(2); + + assertNotNull(oldPlayer1); + assertNotNull(oldPlayer2); + assertNotNull(oldPlayer3); + + var prerequisiteGroup1 = testUtils.createTestGroup(port, List.of("player1", "player2")); + + var otherPlayerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup1.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var otherPlayers = (List) requestUtils.assertSuccess(otherPlayerResponse, ArrayList.class); + + assertEquals(2, otherPlayers.size()); + + var otherPlayer1 = otherPlayers.getFirst(); + var otherPlayer2 = otherPlayers.getLast(); + + assertNotNull(otherPlayer1); + assertNotNull(otherPlayer2); + + var seasonCreateDto = new SeasonCreateDto(); + seasonCreateDto.setOldSeasonName("testing"); + seasonCreateDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonCreateDto, SeasonDto.class); + var newSeason = requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + + var newPlayerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/players", List.class, PlayerDto.class); + var newPlayers = (List) requestUtils.assertSuccess(newPlayerResponse, ArrayList.class); + + assertEquals(profileNames.size(), newPlayers.size()); + + var newPlayer1 = newPlayers.getFirst(); + var newPlayer2 = newPlayers.get(1); + var newPlayer3 = newPlayers.get(2); + + assertNotNull(newPlayer1); + assertNotNull(newPlayer2); + assertNotNull(newPlayer3); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMove = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(normalMove); + assertFalse(normalMove.isFinishingMove()); + assertNotNull(finishMove); + assertTrue(finishMove.isFinishingMove()); + + // test invalid player id + var matchDto = buildDto( + buildTeam( + buildMember(newPlayer1.getId()), + buildMember("someIdThatNotExists") + ), + buildTeam( + buildMember(newPlayer3.getId()) + ) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test invalid player id + matchDto = buildDto( + buildTeam( + buildMember("someIdThatNotExists") + ), + buildTeam( + buildMember(newPlayer3.getId()), + buildMember(newPlayer1.getId()) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test player from other season + matchDto = buildDto( + buildTeam( + buildMember(newPlayer1.getId()), + buildMember(oldPlayer1.getId()) + ), + buildTeam( + buildMember(newPlayer3.getId()) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test player from other season + matchDto = buildDto( + buildTeam( + buildMember(newPlayer1.getId()), + buildMember(newPlayer2.getId()) + ), + buildTeam( + buildMember(oldPlayer1.getId()) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test player from other group + matchDto = buildDto( + buildTeam( + buildMember(newPlayer1.getId()), + buildMember(newPlayer2.getId()) + ), + buildTeam( + buildMember(otherPlayer1.getId()) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test player from other group + matchDto = buildDto( + buildTeam( + buildMember(newPlayer1.getId()), + buildMember(otherPlayer1.getId()) + ), + buildTeam( + buildMember(newPlayer2.getId()) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + } + + @Test + @SuppressWarnings("unchecked") + public void matches_create_invalidMoves() { + var profileNames = List.of("player1", "player2", "player3"); + var prerequisiteGroup = testUtils.createTestGroup(port, profileNames); + var oldSeason = prerequisiteGroup.getActiveSeason(); + + var oldMovesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/rule-moves", List.class, RuleMoveDto.class); + var oldMoves = (List) requestUtils.assertSuccess(oldMovesResponse, ArrayList.class); + + assertTrue(oldMoves.size() >= 2); + + var oldNormalMove = oldMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var oldFinishMove = oldMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(oldNormalMove); + assertFalse(oldNormalMove.isFinishingMove()); + assertNotNull(oldFinishMove); + assertTrue(oldFinishMove.isFinishingMove()); + + var prerequisiteGroup1 = testUtils.createTestGroup(port, List.of("player1", "player2")); + + var otherMovesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup1.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var otherMoves = (List) requestUtils.assertSuccess(otherMovesResponse, ArrayList.class); + + assertTrue(otherMoves.size() >= 2); + + var otherNormalMove = otherMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var otherFinishMove = otherMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(otherNormalMove); + assertFalse(otherNormalMove.isFinishingMove()); + assertNotNull(otherFinishMove); + assertTrue(otherFinishMove.isFinishingMove()); + + var seasonCreateDto = new SeasonCreateDto(); + seasonCreateDto.setOldSeasonName("testing"); + seasonCreateDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonCreateDto, SeasonDto.class); + var newSeason = requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertEquals(profileNames.size(), players.size()); + + var player1 = players.getFirst(); + var player2 = players.get(1); + var player3 = players.get(2); + + assertNotNull(player1); + assertNotNull(player2); + assertNotNull(player3); + + var newMovesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/rule-moves", List.class, RuleMoveDto.class); + var newMoves = (List) requestUtils.assertSuccess(newMovesResponse, ArrayList.class); + + assertTrue(newMoves.size() >= 2); + + var newNormalMove = newMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var newFinishMove = newMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(newNormalMove); + assertFalse(newNormalMove.isFinishingMove()); + assertNotNull(newFinishMove); + assertTrue(newFinishMove.isFinishingMove()); + + // test invalid move id + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove("someIdThatNotExists", 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(newNormalMove.getId(), 2) + ) + ) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test invalid move id + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(newFinishMove.getId(), 1), + buildMove(newNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove("someIdThatNotExists", 2) + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test move from other season + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(newFinishMove.getId(), 1), + buildMove(oldNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(newNormalMove.getId(), 2) + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test move from other season + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(newFinishMove.getId(), 1), + buildMove(newNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(oldNormalMove.getId(), 2) + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test finish move from other season + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(oldFinishMove.getId(), 1), + buildMove(newNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(newNormalMove.getId(), 2) + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test move from other season + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(newNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(oldFinishMove.getId(), 1) + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test move from other group + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(newFinishMove.getId(), 1), + buildMove(otherNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(newNormalMove.getId(), 2) + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test move from other group + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(newFinishMove.getId(), 1), + buildMove(newNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(otherNormalMove.getId(), 2) + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test finish move from other group + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(otherFinishMove.getId(), 1), + buildMove(newNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(newNormalMove.getId(), 2) + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test move from other group + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(newNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(otherFinishMove.getId(), 1) + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); } @Test From 418241fe6c90fa784d9d07e6319be320b17476d8 Mon Sep 17 00:00:00 2001 From: Thies Date: Fri, 13 Jun 2025 12:27:45 +0200 Subject: [PATCH 65/90] fix tests --- .../api/control/MatchControllerTest.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java index f9df2164..9a5c435f 100644 --- a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java @@ -1046,7 +1046,7 @@ public void matches_create_invalidPlayers() { ) ); - var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); // test invalid player id @@ -1060,7 +1060,7 @@ public void matches_create_invalidPlayers() { ) ); - response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); // test player from other season @@ -1074,7 +1074,7 @@ public void matches_create_invalidPlayers() { ) ); - response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); // test player from other season @@ -1088,7 +1088,7 @@ public void matches_create_invalidPlayers() { ) ); - response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); // test player from other group @@ -1102,7 +1102,7 @@ public void matches_create_invalidPlayers() { ) ); - response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); // test player from other group @@ -1116,7 +1116,7 @@ public void matches_create_invalidPlayers() { ) ); - response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); } @@ -1211,7 +1211,7 @@ public void matches_create_invalidMoves() { ) ); - var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); // test invalid move id @@ -1235,7 +1235,7 @@ public void matches_create_invalidMoves() { ) ); - response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); // test move from other season @@ -1259,7 +1259,7 @@ public void matches_create_invalidMoves() { ) ); - response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); // test move from other season @@ -1283,7 +1283,7 @@ public void matches_create_invalidMoves() { ) ); - response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); // test finish move from other season @@ -1307,7 +1307,7 @@ public void matches_create_invalidMoves() { ) ); - response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); // test move from other season @@ -1330,7 +1330,7 @@ public void matches_create_invalidMoves() { ) ); - response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); // test move from other group @@ -1354,7 +1354,7 @@ public void matches_create_invalidMoves() { ) ); - response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); // test move from other group @@ -1378,7 +1378,7 @@ public void matches_create_invalidMoves() { ) ); - response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); // test finish move from other group @@ -1402,7 +1402,7 @@ public void matches_create_invalidMoves() { ) ); - response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); // test move from other group @@ -1425,7 +1425,7 @@ public void matches_create_invalidMoves() { ) ); - response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); } From f898a4a13824f40825c03d134380ccc43f60f99a Mon Sep 17 00:00:00 2001 From: Thies Date: Fri, 13 Jun 2025 12:28:27 +0200 Subject: [PATCH 66/90] remove todos --- api/src/test/java/pro/beerpong/api/TestUtils.java | 2 +- .../java/pro/beerpong/api/control/PlayerControllerTest.java | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/api/src/test/java/pro/beerpong/api/TestUtils.java b/api/src/test/java/pro/beerpong/api/TestUtils.java index 959252bb..aa6a3538 100644 --- a/api/src/test/java/pro/beerpong/api/TestUtils.java +++ b/api/src/test/java/pro/beerpong/api/TestUtils.java @@ -11,7 +11,7 @@ @Component public class TestUtils { - //TODO tests for: realtime events, assets (needs s3 files), leaderboard, match + //TODO tests for: realtime events, assets (needs s3 files), leaderboard (+stats) //TODO add descr to every test case @Autowired diff --git a/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java b/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java index 6b80cb0a..b3688484 100644 --- a/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/PlayerControllerTest.java @@ -66,8 +66,6 @@ public void players_copy_seasonStart() { assertTrue(players.stream().allMatch(playerDto -> profiles.stream().anyMatch(profileDto -> profileDto.getId().equals(playerDto.getProfile().getId())))); assertTrue(players.stream().allMatch(PlayerDto::isActiveThisSeason)); assertTrue(players.stream().allMatch(playerDto -> playerDto.getSeason().getId().equals(newSeason.getId()))); - - //TODO maybe check stats? } @Test @@ -98,7 +96,6 @@ public void players_findAll_success() { for (PlayerDto playerDto : players) { assertTrue(profileNames.stream().anyMatch(s -> playerDto.getProfile().getName().equals(s))); assertTrue(playerDto.isActiveThisSeason()); - //TODO maybe check stats? assertNotNull(playerDto.getStatistics()); assertEquals(season.getId(), playerDto.getSeason().getId()); } @@ -123,7 +120,6 @@ public void players_findAll_success() { for (PlayerDto playerDto : players) { assertTrue(profileNames.stream().anyMatch(s -> playerDto.getProfile().getName().equals(s))); assertTrue(playerDto.isActiveThisSeason()); - //TODO maybe check stats? assertNotNull(playerDto.getStatistics()); assertEquals(season.getId(), playerDto.getSeason().getId()); } From 208d1324e1110591b4812e5e2fffa8110a379624 Mon Sep 17 00:00:00 2001 From: Thies Date: Fri, 13 Jun 2025 12:57:21 +0200 Subject: [PATCH 67/90] more match tests --- .../beerpong/api/control/MatchController.java | 4 +- .../beerpong/api/model/dto/ErrorCodes.java | 1 + .../api/control/MatchControllerTest.java | 177 ++++++++++++++++++ 3 files changed, 180 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/control/MatchController.java b/api/src/main/java/pro/beerpong/api/control/MatchController.java index 326696e7..c4ce965b 100644 --- a/api/src/main/java/pro/beerpong/api/control/MatchController.java +++ b/api/src/main/java/pro/beerpong/api/control/MatchController.java @@ -62,7 +62,7 @@ public ResponseEntity> createMatch(@PathVariable Stri return ResponseEnvelope.ok(match); } else { - return ResponseEnvelope.notOk(ErrorCodes.SEASON_NOT_OF_GROUP); + return ResponseEnvelope.notOk(ErrorCodes.ERROR); } } else { return ResponseEnvelope.notOk(ErrorCodes.MATCH_DTO_VALIDATION_FAILED); @@ -90,7 +90,7 @@ public ResponseEntity> getMatchById(@PathVariable Str if (match.getSeason().getId().equals(seasonId) && match.getSeason().getGroupId().equals(groupId)) { return ResponseEnvelope.ok(match); } else { - return ResponseEnvelope.notOk(ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + return ResponseEnvelope.notOk(ErrorCodes.MATCH_NOT_OF_GROUP); } } else { return ResponseEnvelope.notOk(ErrorCodes.MATCH_NOT_FOUND); diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java index 7615019b..b8b227e4 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java @@ -36,6 +36,7 @@ public enum ErrorCodes { MATCH_CREATE_DTO_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "matchCreateDtoValidationFailed", "The team sizes are not in the boundaries of the season settings!"), MATCH_WRONG_AMOUNT_OF_TEAMS(HttpStatus.BAD_REQUEST, "matchWrongAmountOfTeams", "The amount of teams has to be exactly 2!"), MATCH_DTO_VALIDATION_FAILED(HttpStatus.FORBIDDEN, "matchDtoValidationFailed", "The validation of the match create dto failed (invalid player, rulemove, season or group id, double player, or no/more than exactly one finish move)"), + MATCH_NOT_OF_GROUP(HttpStatus.BAD_REQUEST, "matchNotOfGroup", "The provided match id does not match the provided season or group!"), /* RULE MOVES */ RULE_MOVE_NOT_FOUND(HttpStatus.NOT_FOUND, "ruleMoveNotFound", "The requested ruleMove could not be found!"), RULE_MOVE_VALIDATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "ruleMoveValidationFailed", "The rulemove is not part of the provided season or the provided season is not part of the provided gorup!"), diff --git a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java index 9a5c435f..deeba69a 100644 --- a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; @@ -1431,22 +1432,198 @@ public void matches_create_invalidMoves() { @Test @Transactional + @SuppressWarnings("unchecked") public void matches_findAll_success() { var prerequisiteGroup = testUtils.createTestGroup(port); + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", List.class, MatchDto.class); + var matches = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertNotNull(matches); + assertTrue(matches.isEmpty()); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertTrue(players.size() >= 2); + + var player1 = players.getFirst(); + var player2 = players.getLast(); + + assertNotNull(player1); + assertNotNull(player2); + assertNotEquals(player1, player2); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMove = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(normalMove); + assertFalse(normalMove.isFinishingMove()); + assertNotNull(finishMove); + assertTrue(finishMove.isFinishingMove()); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 5), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId(), + buildMove(normalMove.getId(), 3) + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var match1 = requestUtils.assertSuccess(response, MatchDto.class); + + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 9), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId(), + buildMove(normalMove.getId(), 2) + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var match2 = requestUtils.assertSuccess(response, MatchDto.class); + + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 1), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId() + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var match3 = requestUtils.assertSuccess(response, MatchDto.class); + + assertNotNull(match1); + assertNotNull(match2); + assertNotNull(match3); + assertNotEquals(match1.getId(), match2.getId()); + assertNotEquals(match2.getId(), match3.getId()); + assertNotEquals(match1.getId(), match3.getId()); + + match2.setDate(match1.getDate()); + match3.setDate(match1.getDate()); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", List.class, MatchDto.class); + matches = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertNotNull(matches); + assertFalse(matches.isEmpty()); + assertEquals(3, matches.size()); + + for (MatchDto match : matches) { + match.setDate(match1.getDate()); + assertTrue(List.of(match1, match2, match3).contains(match)); + } } @Test public void matches_findAll_invalidSeason() { var prerequisiteGroup = testUtils.createTestGroup(port); + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/someIdThatNotExists/matches", List.class, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_FOUND); + + var prerequisiteGroup1 = testUtils.createTestGroup(port, List.of("player1", "player2")); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/matches", List.class, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_OF_GROUP); } @Test @Transactional + @SuppressWarnings("unchecked") public void matches_findById_success() { var prerequisiteGroup = testUtils.createTestGroup(port); + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", List.class, MatchDto.class); + var matches = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertNotNull(matches); + assertTrue(matches.isEmpty()); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertTrue(players.size() >= 2); + + var player1 = players.getFirst(); + var player2 = players.getLast(); + + assertNotNull(player1); + assertNotNull(player2); + assertNotEquals(player1, player2); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMove = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(normalMove); + assertFalse(normalMove.isFinishingMove()); + assertNotNull(finishMove); + assertTrue(finishMove.isFinishingMove()); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 5), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId(), + buildMove(normalMove.getId(), 3) + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var match = requestUtils.assertSuccess(response, MatchDto.class); + + assertNotNull(match); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + match.getId(), MatchDto.class); + var fetched = requestUtils.assertSuccess(response, MatchDto.class); + + assertNotNull(fetched); + + match.setDate(fetched.getDate()); + assertEquals(match, fetched); } @Test From 756f01efb61629de29565b96e86058c3d9f38c73 Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 15 Jun 2025 20:15:15 +0200 Subject: [PATCH 68/90] match overview test --- .../beerpong/api/control/MatchController.java | 2 +- .../api/control/MatchControllerTest.java | 185 +++++++++++++++++- 2 files changed, 177 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/pro/beerpong/api/control/MatchController.java b/api/src/main/java/pro/beerpong/api/control/MatchController.java index c4ce965b..7a770b81 100644 --- a/api/src/main/java/pro/beerpong/api/control/MatchController.java +++ b/api/src/main/java/pro/beerpong/api/control/MatchController.java @@ -118,7 +118,7 @@ public ResponseEntity> getMatchOverviewById(@ if (match.getSeason().getId().equals(seasonId) && match.getSeason().getGroupId().equals(groupId)) { return ResponseEnvelope.ok(match); } else { - return ResponseEnvelope.notOk(ErrorCodes.SEASON_NOT_OF_GROUP); + return ResponseEnvelope.notOk(ErrorCodes.MATCH_NOT_OF_GROUP); } } else { return ResponseEnvelope.notOk(ErrorCodes.MATCH_NOT_FOUND); diff --git a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java index deeba69a..e7890f79 100644 --- a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java @@ -12,7 +12,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; @@ -1565,12 +1564,6 @@ public void matches_findAll_invalidSeason() { public void matches_findById_success() { var prerequisiteGroup = testUtils.createTestGroup(port); - var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", List.class, MatchDto.class); - var matches = (List) requestUtils.assertSuccess(response, ArrayList.class); - - assertNotNull(matches); - assertTrue(matches.isEmpty()); - var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); @@ -1612,7 +1605,7 @@ public void matches_findById_success() { ) ); - response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); var match = requestUtils.assertSuccess(response, MatchDto.class); assertNotNull(match); @@ -1627,9 +1620,82 @@ public void matches_findById_success() { } @Test + @Transactional + @SuppressWarnings("unchecked") public void matches_findById_invalidArgs() { var prerequisiteGroup = testUtils.createTestGroup(port); + var prerequisiteGroup1 = testUtils.createTestGroup(port); + + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", List.class, MatchDto.class); + var matches = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertNotNull(matches); + assertTrue(matches.isEmpty()); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertTrue(players.size() >= 2); + + var player1 = players.getFirst(); + var player2 = players.getLast(); + + assertNotNull(player1); + assertNotNull(player2); + assertNotEquals(player1, player2); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMove = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(normalMove); + assertFalse(normalMove.isFinishingMove()); + assertNotNull(finishMove); + assertTrue(finishMove.isFinishingMove()); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 5), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId(), + buildMove(normalMove.getId(), 3) + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var match = requestUtils.assertSuccess(response, MatchDto.class); + + assertNotNull(match); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/someIdThatNotExists", MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_FOUND); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup1.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + match.getId(), MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_OF_GROUP); + var seasonDto = new SeasonCreateDto(); + seasonDto.setOldSeasonName("testing"); + seasonDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + var newSeason = requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_OF_GROUP); } @Test @@ -1647,9 +1713,110 @@ public void matches_overviewAll_invalidArgs() { @Test @Transactional + @SuppressWarnings("unchecked") public void matches_overviewById_success() { - var prerequisiteGroup = testUtils.createTestGroup(port); + var profileNames = List.of("profile1", "profile2", "profile3"); + var prerequisiteGroup = testUtils.createTestGroup(port, profileNames); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertTrue(players.size() >= 3); + + var player1 = players.getFirst(); + var player2 = players.get(1); + var player3 = players.get(2); + + assertNotNull(player1); + assertNotNull(player2); + assertNotNull(player3); + assertNotEquals(player1, player2); + assertNotEquals(player2, player3); + assertNotEquals(player1, player3); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMove = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(normalMove); + assertFalse(normalMove.isFinishingMove()); + assertNotNull(finishMove); + assertTrue(finishMove.isFinishingMove()); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 5), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId(), + buildMove(normalMove.getId(), 3) + ), + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 2) + ) + ) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var match = requestUtils.assertSuccess(response, MatchDto.class); + + assertNotNull(match); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + match.getId() + "/overview", MatchOverviewDto.class); + var fetched = requestUtils.assertSuccess(response, MatchOverviewDto.class); + + assertNotNull(fetched); + assertNotNull(fetched.getDate()); + assertNotNull(fetched.getBlueTeam()); + assertNotNull(fetched.getRedTeam()); + assertEquals(match.getId(), fetched.getId()); + assertEquals(match.getSeason(), fetched.getSeason()); + + assertEquals((normalMove.getPointsForScorer() + normalMove.getPointsForTeam() * fetched.getBlueTeam().getMembers().size()) * 5 + + (finishMove.getPointsForScorer() + finishMove.getPointsForTeam() * fetched.getBlueTeam().getMembers().size()) /* *1 */, fetched.getBlueTeam().getPoints()); + assertEquals((normalMove.getPointsForScorer() + normalMove.getPointsForTeam() * fetched.getRedTeam().getMembers().size()) * 5, fetched.getRedTeam().getPoints()); + + var blueMembers = fetched.getBlueTeam().getMembers(); + var redMembers = fetched.getRedTeam().getMembers(); + + assertEquals(1, blueMembers.size()); + assertEquals(2, redMembers.size()); + + var member1 = blueMembers.getFirst(); + var member2 = redMembers.getFirst(); + var member3 = redMembers.getLast(); + + assertNotNull(member1); + assertNotNull(member2); + assertNotNull(member3); + + assertEquals(player1.getId(), member1.getPlayerId()); + assertEquals((normalMove.getPointsForScorer() + normalMove.getPointsForTeam() * fetched.getBlueTeam().getMembers().size()) * 5 + + (finishMove.getPointsForScorer() + finishMove.getPointsForTeam() * fetched.getBlueTeam().getMembers().size()) /* *1 */, member1.getPoints()); + assertEquals(2, member1.getMoves().size()); + assertEquals(5, member1.getMoves().stream().filter(matchMoveDto -> matchMoveDto.getMoveId().equals(normalMove.getId())).findFirst().orElseThrow().getCount()); + assertEquals(1, member1.getMoves().stream().filter(matchMoveDto -> matchMoveDto.getMoveId().equals(finishMove.getId())).findFirst().orElseThrow().getCount()); + + assertEquals(player2.getId(), member2.getPlayerId()); + assertEquals((normalMove.getPointsForScorer() + normalMove.getPointsForTeam() * fetched.getBlueTeam().getMembers().size()) * 3, member2.getPoints()); + assertEquals(1, member2.getMoves().size()); + assertEquals(3, member2.getMoves().stream().filter(matchMoveDto -> matchMoveDto.getMoveId().equals(normalMove.getId())).findFirst().orElseThrow().getCount()); + + assertEquals(player3.getId(), member3.getPlayerId()); + assertEquals((normalMove.getPointsForScorer() + normalMove.getPointsForTeam() * fetched.getBlueTeam().getMembers().size()) * 2, member3.getPoints()); + assertEquals(1, member3.getMoves().size()); + assertEquals(2, member3.getMoves().stream().filter(matchMoveDto -> matchMoveDto.getMoveId().equals(normalMove.getId())).findFirst().orElseThrow().getCount()); } @Test From 7bc664d468848b48d35c5f646373e1d09a20b39f Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 15 Jun 2025 20:22:27 +0200 Subject: [PATCH 69/90] overview invalid tests --- .../api/control/MatchControllerTest.java | 75 ++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java index e7890f79..b9c2f7f4 100644 --- a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java @@ -1706,9 +1706,16 @@ public void matches_overviewAll_success() { } @Test - public void matches_overviewAll_invalidArgs() { + public void matches_overviewAll_invalidSeason() { var prerequisiteGroup = testUtils.createTestGroup(port); + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/someIdThatNotExists/matches/overview", List.class, MatchOverviewDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_FOUND); + + var prerequisiteGroup1 = testUtils.createTestGroup(port, List.of("player1", "player2")); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/matches/overview", List.class, MatchOverviewDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_OF_GROUP); } @Test @@ -1820,9 +1827,75 @@ public void matches_overviewById_success() { } @Test + @SuppressWarnings("unchecked") public void matches_overviewById_invalidArgs() { var prerequisiteGroup = testUtils.createTestGroup(port); + var prerequisiteGroup1 = testUtils.createTestGroup(port); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertTrue(players.size() >= 2); + + var player1 = players.getFirst(); + var player2 = players.getLast(); + + assertNotNull(player1); + assertNotNull(player2); + assertNotEquals(player1, player2); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMove = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(normalMove); + assertFalse(normalMove.isFinishingMove()); + assertNotNull(finishMove); + assertTrue(finishMove.isFinishingMove()); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 5), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId(), + buildMove(normalMove.getId(), 3) + ) + ) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var match = requestUtils.assertSuccess(response, MatchDto.class); + assertNotNull(match); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/someIdThatNotExists/overview", MatchOverviewDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_FOUND); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup1.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + match.getId() + "/overview", MatchOverviewDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_OF_GROUP); + + var seasonDto = new SeasonCreateDto(); + seasonDto.setOldSeasonName("testing"); + seasonDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonDto, SeasonDto.class); + var newSeason = requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId() + "/overview", MatchOverviewDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_OF_GROUP); } @Test From 449b4d56a81f8eb961c9b0194b653e4818f29781 Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 15 Jun 2025 20:26:43 +0200 Subject: [PATCH 70/90] overview all tests --- .../api/control/MatchControllerTest.java | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java index b9c2f7f4..78bcffb7 100644 --- a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java @@ -1700,9 +1700,124 @@ public void matches_findById_invalidArgs() { @Test @Transactional + @SuppressWarnings("unchecked") public void matches_overviewAll_success() { var prerequisiteGroup = testUtils.createTestGroup(port); + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/overview", List.class, MatchOverviewDto.class); + var matches = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertNotNull(matches); + assertTrue(matches.isEmpty()); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertTrue(players.size() >= 2); + + var player1 = players.getFirst(); + var player2 = players.getLast(); + + assertNotNull(player1); + assertNotNull(player2); + assertNotEquals(player1, player2); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMove = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(normalMove); + assertFalse(normalMove.isFinishingMove()); + assertNotNull(finishMove); + assertTrue(finishMove.isFinishingMove()); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 5), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId(), + buildMove(normalMove.getId(), 3) + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var rawMatch1 = requestUtils.assertSuccess(response, MatchDto.class); + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + rawMatch1.getId() + "/overview", MatchOverviewDto.class); + var match1 = requestUtils.assertSuccess(response, MatchOverviewDto.class); + + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 9), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId(), + buildMove(normalMove.getId(), 2) + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var rawMatch2 = requestUtils.assertSuccess(response, MatchDto.class); + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + rawMatch2.getId() + "/overview", MatchOverviewDto.class); + var match2 = requestUtils.assertSuccess(response, MatchOverviewDto.class); + + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 1), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId() + ) + ) + ); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var rawMatch3 = requestUtils.assertSuccess(response, MatchDto.class); + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + rawMatch3.getId() + "/overview", MatchOverviewDto.class); + var match3 = requestUtils.assertSuccess(response, MatchOverviewDto.class); + + assertNotNull(match1); + assertNotNull(match2); + assertNotNull(match3); + assertNotEquals(match1.getId(), match2.getId()); + assertNotEquals(match2.getId(), match3.getId()); + assertNotEquals(match1.getId(), match3.getId()); + + match2.setDate(match1.getDate()); + match3.setDate(match1.getDate()); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/overview", List.class, MatchOverviewDto.class); + matches = (List) requestUtils.assertSuccess(response, ArrayList.class); + + assertNotNull(matches); + assertFalse(matches.isEmpty()); + assertEquals(3, matches.size()); + + for (MatchOverviewDto match : matches) { + match.setDate(match1.getDate()); + assertTrue(List.of(match1, match2, match3).contains(match)); + } } @Test From 7e0b4df7deb3c53dd858a340092fb8d8b97d2b33 Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 15 Jun 2025 20:30:45 +0200 Subject: [PATCH 71/90] remove old match test --- .../pro/beerpong/api/CreateMatchTest.java | 320 ------------------ 1 file changed, 320 deletions(-) delete mode 100644 api/src/test/java/pro/beerpong/api/CreateMatchTest.java diff --git a/api/src/test/java/pro/beerpong/api/CreateMatchTest.java b/api/src/test/java/pro/beerpong/api/CreateMatchTest.java deleted file mode 100644 index d46ddd8d..00000000 --- a/api/src/test/java/pro/beerpong/api/CreateMatchTest.java +++ /dev/null @@ -1,320 +0,0 @@ -package pro.beerpong.api; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.test.context.ActiveProfiles; -import pro.beerpong.api.control.GroupPresetsController; -import pro.beerpong.api.model.dto.*; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@ActiveProfiles("test") -public class CreateMatchTest { - @LocalServerPort - private int port; - - @Autowired - private RequestUtils requestUtils; - -// @BeforeEach -// void setUp() { -// if (seasonRepository.findById(seasonId).isEmpty()) { -// var season = new pro.beerpong.api.model.dao.Season(); -// season.setId(seasonId); -// season.setGroupId(groupId); -// seasonRepository.save(season); -// } -// } - - @Test - @SuppressWarnings("unchecked") - void createMatch_withTeamsMembersAndMoves_shouldCreateMatchTeamsAndMoves() throws Exception { - var createGroupDto = new GroupCreateDto(); - createGroupDto.setProfileNames(List.of("player1", "player2", "player3", "player4")); - createGroupDto.setName("test"); - createGroupDto.setSportPreset("beerpong"); - - var prerequisiteGroupResponse = requestUtils.performPost(port, "/groups", createGroupDto, GroupDto.class); - - assertNotNull(prerequisiteGroupResponse); - assertEquals(200, prerequisiteGroupResponse.getStatusCode().value()); - - ResponseEnvelope prerequisiteEnvelope = (ResponseEnvelope) prerequisiteGroupResponse.getBody(); - assertNotNull(prerequisiteEnvelope); - assertEquals(ResponseEnvelope.Status.OK, prerequisiteEnvelope.getStatus()); - assertNull(prerequisiteEnvelope.getError()); - assertEquals(200, prerequisiteEnvelope.getHttpCode()); - - var prerequisiteGroup = prerequisiteEnvelope.getData(); - assertEquals(GroupPresetsController.BEERPONG.getId(), prerequisiteGroup.getSportPreset().getId()); - var response = requestUtils.performGet(port, "/groups?inviteCode=" + prerequisiteGroup.getInviteCode(), GroupDto.class); - - assertNotNull(response); - assertEquals(200, response.getStatusCode().value()); - - ResponseEnvelope envelope = (ResponseEnvelope) response.getBody(); - assertNotNull(envelope); - assertEquals(ResponseEnvelope.Status.OK, envelope.getStatus()); - assertNull(envelope.getError()); - assertEquals(200, envelope.getHttpCode()); - - var group = envelope.getData(); - // if this is not here, the startDate millis are rounded and this test fails - group.getActiveSeason().setStartDate(prerequisiteGroup.getActiveSeason().getStartDate()); - group.setCreatedAt(prerequisiteGroup.getCreatedAt()); - - assertNotNull(group); - assertNotNull(group.getName()); - assertEquals(prerequisiteGroup, group); - assertNotNull(group.getId()); - assertNotNull(group.getInviteCode()); - assertNotNull(group.getActiveSeason()); - assertNotNull(group.getActiveSeason().getId()); - assertEquals(group.getActiveSeason().getGroupId(), group.getId()); - - var season = group.getActiveSeason(); - - var playersResponse = requestUtils.performGet(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/players", List.class, PlayerDto.class); - - assertNotNull(playersResponse); - assertEquals(200, playersResponse.getStatusCode().value()); - - ResponseEnvelope> playersEnvelope = (ResponseEnvelope>) playersResponse.getBody(); - assertNotNull(playersEnvelope); - assertEquals(ResponseEnvelope.Status.OK, playersEnvelope.getStatus()); - assertNull(playersEnvelope.getError()); - assertEquals(200, playersEnvelope.getHttpCode()); - - var players = playersEnvelope.getData(); - - var createRulemMoveDto = new RuleMoveCreateDto(); - createRulemMoveDto.setName("RuleMove1"); - createRulemMoveDto.setFinishingMove(false); - createRulemMoveDto.setPointsForTeam(0); - createRulemMoveDto.setPointsForScorer(1); - var createRulemMoveDto1 = new RuleMoveCreateDto(); - createRulemMoveDto1.setName("RuleMove2"); - createRulemMoveDto1.setFinishingMove(true); - createRulemMoveDto1.setPointsForTeam(1); - createRulemMoveDto1.setPointsForScorer(1); - - var ruleMoveResponse = requestUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRulemMoveDto, RuleMoveDto.class); - var ruleMoveResponse1 = requestUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRulemMoveDto1, RuleMoveDto.class); - - assertNotNull(ruleMoveResponse); - assertNotNull(ruleMoveResponse1); - assertEquals(200, ruleMoveResponse.getStatusCode().value()); - assertEquals(200, ruleMoveResponse1.getStatusCode().value()); - - ResponseEnvelope ruleMoveEnvelope = (ResponseEnvelope) ruleMoveResponse.getBody(); - ResponseEnvelope ruleMoveEnvelope1 = (ResponseEnvelope) ruleMoveResponse1.getBody(); - assertNotNull(ruleMoveEnvelope); - assertNotNull(ruleMoveEnvelope1); - assertEquals(ResponseEnvelope.Status.OK, ruleMoveEnvelope.getStatus()); - assertEquals(ResponseEnvelope.Status.OK, ruleMoveEnvelope1.getStatus()); - assertNull(ruleMoveEnvelope.getError()); - assertNull(ruleMoveEnvelope1.getError()); - assertEquals(200, ruleMoveEnvelope.getHttpCode()); - assertEquals(200, ruleMoveEnvelope1.getHttpCode()); - - var ruleMove = ruleMoveEnvelope.getData(); - var ruleMove1 = ruleMoveEnvelope1.getData(); - - // Arrange: Erstelle ein MatchCreateDto mit Teams, TeamMembers und Moves - MatchCreateDto matchCreateDto = new MatchCreateDto(); - - // Team 1 mit zwei Spielern und ihren Moves - TeamCreateDto team1 = new TeamCreateDto(); - TeamMemberCreateDto member1 = new TeamMemberCreateDto(); - member1.setPlayerId(players.get(0).getId()); - - MatchMoveDto move1 = new MatchMoveDto(); - move1.setMoveId(ruleMove.getId()); - move1.setCount(3); - - MatchMoveDto move2 = new MatchMoveDto(); - move2.setMoveId(ruleMove.getId()); - move2.setCount(5); - - MatchMoveDto move7 = new MatchMoveDto(); - move7.setMoveId(ruleMove1.getId()); - move7.setCount(1); - - member1.setMoves(List.of(move1, move2, move7)); - - TeamMemberCreateDto member2 = new TeamMemberCreateDto(); - member2.setPlayerId(players.get(1).getId()); - - MatchMoveDto move3 = new MatchMoveDto(); - move3.setMoveId(ruleMove.getId()); - move3.setCount(2); - - member2.setMoves(List.of(move3)); - - team1.setTeamMembers(List.of(member1, member2)); - - // Team 2 mit zwei Spielern und ihren Moves - TeamCreateDto team2 = new TeamCreateDto(); - TeamMemberCreateDto member3 = new TeamMemberCreateDto(); - member3.setPlayerId(players.get(2).getId()); - - MatchMoveDto move4 = new MatchMoveDto(); - move4.setMoveId(ruleMove.getId()); - move4.setCount(4); - - member3.setMoves(List.of(move4)); - - TeamMemberCreateDto member4 = new TeamMemberCreateDto(); - member4.setPlayerId(players.get(3).getId()); - - MatchMoveDto move5 = new MatchMoveDto(); - move5.setMoveId(ruleMove.getId()); - move5.setCount(1); - - MatchMoveDto move6 = new MatchMoveDto(); - move6.setMoveId(ruleMove.getId()); - move6.setCount(6); - - member4.setMoves(List.of(move5, move6)); - - team2.setTeamMembers(List.of(member3, member4)); - - matchCreateDto.setTeams(List.of(team1, team2)); - - var createMatchResponse = requestUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches", matchCreateDto, MatchDto.class); - - assertNotNull(createMatchResponse); - assertEquals(200, createMatchResponse.getStatusCode().value()); - - ResponseEnvelope matchEnvelope = (ResponseEnvelope) createMatchResponse.getBody(); - assertNotNull(matchEnvelope); - assertEquals(ResponseEnvelope.Status.OK, matchEnvelope.getStatus()); - assertNull(matchEnvelope.getError()); - assertEquals(200, matchEnvelope.getHttpCode()); - } - - @Test - @SuppressWarnings("unchecked") - void updateMatch_withTeamsMembersAndMoves_shouldUpdateMatchTeamsAndMoves() throws Exception { - // Step 1: Create a prerequisite group with players - var createGroupDto = new GroupCreateDto(); - createGroupDto.setProfileNames(List.of("player1", "player2", "player3", "player4")); - createGroupDto.setName("test-update"); - createGroupDto.setSportPreset("beerpong"); - - var groupResponse = requestUtils.performPost(port, "/groups", createGroupDto, GroupDto.class); - ResponseEnvelope groupEnvelope = (ResponseEnvelope) groupResponse.getBody(); - var group = groupEnvelope.getData(); - - var season = group.getActiveSeason(); - - var playersResponse = requestUtils.performGet(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/players", List.class, PlayerDto.class); - ResponseEnvelope> playersEnvelope = (ResponseEnvelope>) playersResponse.getBody(); - var players = playersEnvelope.getData(); - - // Step 2: Create a rule move - var createRuleMoveDto = new RuleMoveDto(); - createRuleMoveDto.setName("UpdateRuleMove"); - var createRuleMoveDto1 = new RuleMoveDto(); - createRuleMoveDto1.setName("UpdateRuleMove"); - createRuleMoveDto1.setFinishingMove(true); - - var ruleMoveResponse = requestUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRuleMoveDto, RuleMoveDto.class); - var ruleMoveResponse1 = requestUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRuleMoveDto1, RuleMoveDto.class); - ResponseEnvelope ruleMoveEnvelope = (ResponseEnvelope) ruleMoveResponse.getBody(); - ResponseEnvelope ruleMoveEnvelope1 = (ResponseEnvelope) ruleMoveResponse1.getBody(); - var ruleMove = ruleMoveEnvelope.getData(); - var ruleMove1 = ruleMoveEnvelope1.getData(); - - // Step 3: Create a match with teams and members - MatchCreateDto matchCreateDto = new MatchCreateDto(); - - // Team 1 - TeamCreateDto team1 = new TeamCreateDto(); - TeamMemberCreateDto member1 = new TeamMemberCreateDto(); - member1.setPlayerId(players.get(0).getId()); - MatchMoveDto move1 = new MatchMoveDto(); - move1.setMoveId(ruleMove1.getId()); - move1.setCount(1); - member1.setMoves(List.of(move1)); - team1.setTeamMembers(List.of(member1)); - - // Team 2 - TeamCreateDto team2 = new TeamCreateDto(); - TeamMemberCreateDto member2 = new TeamMemberCreateDto(); - member2.setPlayerId(players.get(1).getId()); - MatchMoveDto move2 = new MatchMoveDto(); - move2.setMoveId(ruleMove.getId()); - move2.setCount(5); - member2.setMoves(List.of(move2)); - team2.setTeamMembers(List.of(member2)); - - matchCreateDto.setTeams(List.of(team1, team2)); - - // Step 4: Create the match - var createMatchResponse = requestUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches", matchCreateDto, MatchDto.class); - ResponseEnvelope matchEnvelope = (ResponseEnvelope) createMatchResponse.getBody(); - assert matchEnvelope != null; - var match = matchEnvelope.getData(); - - team1.getTeamMembers().get(0).getMoves().get(0).setCount(1); - matchCreateDto.setTeams(List.of(team1, team2)); - - // Step 5: Update the match - var updateResponse = requestUtils.performPut(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches/" + match.getId(), matchCreateDto, MatchDto.class); - ResponseEnvelope updateEnvelope = (ResponseEnvelope) updateResponse.getBody(); - - // Assertions - assertNotNull(updateResponse); - assertEquals(200, updateResponse.getStatusCode().value()); - assertNotNull(updateEnvelope); - assertEquals(ResponseEnvelope.Status.OK, updateEnvelope.getStatus()); - assertNull(updateEnvelope.getError()); - assertEquals(200, updateEnvelope.getHttpCode()); - } - -// @Test -// @SuppressWarnings("unchecked") -// void getAllMatches_shouldReturnAllMatchesForSeason() throws Exception { -// // Step 1: Create a prerequisite group with players -// var createGroupDto = new GroupCreateDto(); -// createGroupDto.setProfileNames(List.of("player1", "player2")); -// createGroupDto.setName("test-get"); - //createGroupDto.setSportPreset("beerpong"); -// -// var groupResponse = testUtils.performPost(port, "/groups", createGroupDto, GroupDto.class); -// ResponseEnvelope groupEnvelope = (ResponseEnvelope) groupResponse.getBody(); -// var group = groupEnvelope.getData(); -// -// var season = group.getActiveSeason(); -// -// // Step 2: Create a match -// MatchCreateDto matchCreateDto = new MatchCreateDto(); -// TeamCreateDto team = new TeamCreateDto(); -// TeamMemberCreateDto member = new TeamMemberCreateDto(); -// member.setPlayerId("player1-id"); -// team.setTeamMembers(List.of(member)); -// matchCreateDto.setTeams(List.of(team)); -// -// testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/match", matchCreateDto, MatchDto.class); -// -// // Step 3: Get all matches for the season -// var getAllMatchesResponse = testUtils.performGet(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches", List.class, MatchDto.class); -// ResponseEnvelope> matchesEnvelope = (ResponseEnvelope>) getAllMatchesResponse.getBody(); -// -// // Assertions -// assertNotNull(getAllMatchesResponse); -// assertEquals(200, getAllMatchesResponse.getStatusCode().value()); -// assertNotNull(matchesEnvelope); -// assertEquals(ResponseEnvelope.Status.OK, matchesEnvelope.getStatus()); -// assertNull(matchesEnvelope.getError()); -// assertEquals(200, matchesEnvelope.getHttpCode()); -// assertNotNull(matchesEnvelope.getData()); -// assertEquals(1, matchesEnvelope.getData().size()); -// } -} \ No newline at end of file From cf1e7dcd648a3eaadccfe86f346071572e29d29f Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 15 Jun 2025 20:41:00 +0200 Subject: [PATCH 72/90] group test descr --- .../api/control/GroupControllerTest.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java index 5697cc96..8208efe3 100644 --- a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -38,6 +38,7 @@ public void group_create_success() { var name = "test"; var group = testUtils.createTestGroup(port, name); + // test simple group creation assertNotNull(group); assertNotNull(group.getId()); assertNotNull(group.getName()); @@ -67,12 +68,14 @@ public void group_create_success() { group = testUtils.createTestGroup(port, "test", List.of("player1", "player2"), null, "test123"); + // test group creation with custom sport name assertNotNull(group); assertEquals("test123", group.getCustomSportName()); assertNull(group.getSportPreset()); group = testUtils.createTestGroup(port, "test", List.of("player1", "player2"), "kicker", "test123"); + // test group creation with other game preset assertNotNull(group); assertNull(group.getCustomSportName()); assertEquals(GroupPresetsController.KICKER.getId(), group.getSportPreset().getId()); @@ -81,15 +84,19 @@ public void group_create_success() { @Test @Transactional public void group_create_invalidName() { + // test invalid group name (empty) var response = testUtils.postGroup(port, "", List.of("player1", "player2"), "beerpong"); requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + // test invalid group name (null) response = testUtils.postGroup(port, null, List.of("player1", "player2"), "beerpong"); requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + // test invalid group name (too short) response = testUtils.postGroup(port, "a", List.of("player1", "player2"), "beerpong"); requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + // test invalid group name (too long) response = testUtils.postGroup(port, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", List.of("player1", "player2"), "beerpong"); requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); } @@ -97,12 +104,15 @@ public void group_create_invalidName() { @Test @Transactional public void group_create_invalidProfiles() { + // test invalid profile names (empty) var response = testUtils.postGroup(port, "test", List.of(), "beerpong"); requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_PROFILE_NAMES); + // test invalid profile names (null) response = testUtils.postGroup(port, "test", null, "beerpong"); requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_PROFILE_NAMES); + // test invalid profile names (non unique names) response = testUtils.postGroup(port, "test", List.of("player1", "player2", "player2"), "beerpong"); requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_PROFILE_NAMES); } @@ -110,12 +120,15 @@ public void group_create_invalidProfiles() { @Test @Transactional public void group_create_invalidSport() { + // test invalid sport (non existing preset) var response = testUtils.postGroup(port, "test", List.of("player1", "player2"), "notExisting"); requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); + // test invalid sport (preset null) response = testUtils.postGroup(port, "test", List.of("player1", "player2"), null); requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); + // test invalid sport (custom sport empty) response = testUtils.postGroup(port, "test", List.of("player1", "player2"), null, " "); requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); } @@ -129,6 +142,7 @@ public void group_userGroups() { var response = requestUtils.performGet(port, "/groups/user", List.class, GroupDto.class); var groups = (List) requestUtils.assertSuccess(response, ArrayList.class); + // test groups a user has access to assertFalse(groups.isEmpty()); assertTrue(groups.stream().anyMatch(groupDto -> groupDto.getId().equals(prerequisiteGroup.getId()))); } @@ -141,6 +155,7 @@ public void group_findByInviteCode_success() { var response = requestUtils.performGet(port, "/groups?inviteCode=" + prerequisiteGroup.getInviteCode(), GroupDto.class); var group = requestUtils.assertSuccess(response, GroupDto.class); + // test group by invite code assertNotNull(group); testUtils.assertGroupEquals(prerequisiteGroup, group); } @@ -148,9 +163,11 @@ public void group_findByInviteCode_success() { @Test @Transactional public void group_findByInviteCode_invalidInviteCode() { + // test invalid inviteCode (empty) var response = requestUtils.performGet(port, "/groups?inviteCode= ", GroupDto.class); requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_INVITE_CODE); + // test invalid inviteCode (not existing) response = requestUtils.performGet(port, "/groups?inviteCode=someIdThatNotExists", GroupDto.class); requestUtils.assertFailure(response, ErrorCodes.GROUP_INVITE_NOT_FOUND); } @@ -163,12 +180,14 @@ public void group_findById_success() { var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId(), GroupDto.class); var group = requestUtils.assertSuccess(response, GroupDto.class); + // test group by id assertNotNull(group); testUtils.assertGroupEquals(prerequisiteGroup, group); } @Test public void group_findById_invalidId() { + // test invalid group id (not existing) var response = requestUtils.performGet(port, "/groups/someIdThatNotExists", GroupDto.class); requestUtils.assertFailure(response, HttpStatus.UNAUTHORIZED, "No access to this group!"); } @@ -186,6 +205,7 @@ public void group_update_success() { var response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); var group = requestUtils.assertSuccess(response, GroupDto.class); + // test group update assertNotNull(prerequisiteGroup); assertNotNull(group); assertEquals(prerequisiteGroup.getId(), group.getId()); @@ -205,8 +225,27 @@ public void group_update_invalidName() { var createDto = new GroupCreateDto(); createDto.setName(" "); + // test invalid group name (empty) var response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + + createDto.setName(null); + + // test invalid group name (null) + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + + createDto.setName("a"); + + // test invalid group name (too short) + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + + createDto.setName("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + + // test invalid group name (too long) + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); } @Test @@ -214,6 +253,7 @@ public void group_update_invalidName() { public void group_join_success() { var prerequisiteGroup = testUtils.createTestGroup(port); + // test group join (already in group) var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/join", null, String.class); requestUtils.assertFailure(response, ErrorCodes.GROUP_ALREADY_IN_GROUP); @@ -222,6 +262,7 @@ public void group_join_success() { response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/join", null, String.class); var ok = requestUtils.assertSuccess(response, String.class); + // test group join (not in group) assertEquals("OK", ok); } @@ -233,6 +274,7 @@ public void group_leave() { var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/leave", null, String.class); var ok = requestUtils.assertSuccess(response, String.class); + // test group leave assertEquals("OK", ok); } } \ No newline at end of file From 423d85695a0ddd7dd117af6890f952d0ab8046cd Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 15 Jun 2025 20:57:05 +0200 Subject: [PATCH 73/90] add team size check to match update --- .../main/java/pro/beerpong/api/control/MatchController.java | 5 +++++ api/src/main/java/pro/beerpong/api/service/MatchService.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/pro/beerpong/api/control/MatchController.java b/api/src/main/java/pro/beerpong/api/control/MatchController.java index 7a770b81..fd45e647 100644 --- a/api/src/main/java/pro/beerpong/api/control/MatchController.java +++ b/api/src/main/java/pro/beerpong/api/control/MatchController.java @@ -17,6 +17,7 @@ @RestController @RequestMapping("/groups/{groupId}/seasons/{seasonId}/matches") public class MatchController { + // currently we only support games played with exactly 2 teams private static final int MIN_TEAM_AMOUNT = 2; private static final int MAX_TEAM_AMOUNT = 2; @@ -139,6 +140,10 @@ public ResponseEntity> updateMatch(@PathVariable Stri return ResponseEnvelope.notOk(ErrorCodes.MATCH_CREATE_DTO_VALIDATION_FAILED); } + if (matchCreateDto.getTeams().size() < MIN_TEAM_AMOUNT || matchCreateDto.getTeams().size() > MAX_TEAM_AMOUNT) { + return ResponseEnvelope.notOk(ErrorCodes.MATCH_WRONG_AMOUNT_OF_TEAMS); + } + var match = matchService.getRawMatchById(id); if (match == null) { diff --git a/api/src/main/java/pro/beerpong/api/service/MatchService.java b/api/src/main/java/pro/beerpong/api/service/MatchService.java index 774b5594..72943b1e 100644 --- a/api/src/main/java/pro/beerpong/api/service/MatchService.java +++ b/api/src/main/java/pro/beerpong/api/service/MatchService.java @@ -365,7 +365,7 @@ public ErrorCodes deleteMatch(String id, String seasonId, String groupId) { matchRepository.deleteById(id); } else { - error.set(ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + error.set(ErrorCodes.MATCH_NOT_OF_GROUP); } } else { error.set(ErrorCodes.SEASON_ALREADY_ENDED); From 7009e538f8892488df4e9cd63b1f9f7600b570a1 Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 15 Jun 2025 21:10:41 +0200 Subject: [PATCH 74/90] match update invalid tests --- .../api/control/MatchControllerTest.java | 930 +++++++++++++++++- 1 file changed, 927 insertions(+), 3 deletions(-) diff --git a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java index 78bcffb7..99d2a74a 100644 --- a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java @@ -2018,24 +2018,948 @@ public void matches_overviewById_invalidArgs() { public void matches_update_success() { var prerequisiteGroup = testUtils.createTestGroup(port); + //TODO } @Test + @Transactional + @SuppressWarnings("unchecked") public void matches_update_invalidSeason() { var prerequisiteGroup = testUtils.createTestGroup(port); + var oldSeason = prerequisiteGroup.getActiveSeason(); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertTrue(players.size() >= 2); + + var player1 = players.getFirst(); + var player2 = players.getLast(); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMove = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 5), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId(), + buildMove(normalMove.getId(), 3) + ) + ) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var match = requestUtils.assertSuccess(response, MatchDto.class); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/someIdThatNotExists/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_FOUND); + + var prerequisiteGroup1 = testUtils.createTestGroup(port, List.of("player1", "player2")); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_OF_GROUP); + + var seasonCreateDto = new SeasonCreateDto(); + seasonCreateDto.setOldSeasonName("testing"); + seasonCreateDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonCreateDto, SeasonDto.class); + requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_ALREADY_ENDED); } @Test - public void matches_update_invalidTeamSizes() { + @SuppressWarnings("unchecked") + public void matches_update_invalidTeams() { var prerequisiteGroup = testUtils.createTestGroup(port); + var oldSeason = prerequisiteGroup.getActiveSeason(); + var minTeamSize = oldSeason.getSeasonSettings().getMinTeamSize(); + var maxTeamSize = oldSeason.getSeasonSettings().getMaxTeamSize(); + + assertEquals(1, minTeamSize); + assertEquals(10, maxTeamSize); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertTrue(players.size() >= 2); + + var player1 = players.getFirst(); + var player2 = players.getLast(); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMove = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 5), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId(), + buildMove(normalMove.getId(), 3) + ) + ) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var match = requestUtils.assertSuccess(response, MatchDto.class); + + matchDto = buildDto( + buildTeam( + buildMember(player1.getId()) + ), + buildTeam( + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()), + buildMember(player2.getId()) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_CREATE_DTO_VALIDATION_FAILED); + + matchDto = buildDto( + buildTeam( + buildMember(player1.getId()) + ), + buildTeam() + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_CREATE_DTO_VALIDATION_FAILED); + + matchDto = buildDto( + buildTeam( + buildMember(player1.getId()) + ), + buildTeam( + buildMember(player2.getId()) + ), + buildTeam( + buildMember(player2.getId()) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_WRONG_AMOUNT_OF_TEAMS); + + matchDto = buildDto( + buildTeam( + buildMember(player1.getId()) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_WRONG_AMOUNT_OF_TEAMS); + + matchDto = buildDto(); + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_WRONG_AMOUNT_OF_TEAMS); } @Test - public void matches_update_invalidDto() { - var prerequisiteGroup = testUtils.createTestGroup(port); + @SuppressWarnings("unchecked") + public void matches_update_nonUniquePlayers() { + var profileNames = List.of("player1", "player2", "player3", "player4"); + var prerequisiteGroup = testUtils.createTestGroup(port, profileNames); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertEquals(profileNames.size(), players.size()); + + var player1 = players.getFirst(); + var player2 = players.get(1); + var player3 = players.get(2); + var player4 = players.get(3); + + assertNotNull(player1); + assertNotNull(player2); + assertNotNull(player3); + assertNotNull(player4); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMove = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 5), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId(), + buildMove(normalMove.getId(), 3) + ) + ) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var match = requestUtils.assertSuccess(response, MatchDto.class); + + // test non unique players in same team + matchDto = buildDto( + buildTeam( + buildMember(player1.getId()), + buildMember(player2.getId()) + ), + buildTeam( + buildMember(player3.getId()), + buildMember(player3.getId()) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test non unique players in different teams + matchDto = buildDto( + buildTeam( + buildMember(player1.getId()), + buildMember(player3.getId()) + ), + buildTeam( + buildMember(player3.getId()), + buildMember(player4.getId()) + ) + ); + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + } + + @Test + @SuppressWarnings("unchecked") + public void matches_update_invalidFinishMove() { + var profileNames = List.of("player1", "player2", "player3", "player4"); + var prerequisiteGroup = testUtils.createTestGroup(port, profileNames); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertEquals(profileNames.size(), players.size()); + + var player1 = players.getFirst(); + var player2 = players.get(1); + var player3 = players.get(2); + var player4 = players.get(3); + + assertNotNull(player1); + assertNotNull(player2); + assertNotNull(player3); + assertNotNull(player4); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMove = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(normalMove); + assertFalse(normalMove.isFinishingMove()); + assertNotNull(finishMove); + assertTrue(finishMove.isFinishingMove()); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 5), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId(), + buildMove(normalMove.getId(), 3) + ) + ) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var match = requestUtils.assertSuccess(response, MatchDto.class); + + // test too many finish moves in different teams + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 2), + buildMove(finishMove.getId(), 1) + ), + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 2) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(finishMove.getId(), 2) + ), + buildMember( + player4.getId() + ) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test too many finish moves in same team + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 2), + buildMove(finishMove.getId(), 1) + ), + buildMember( + player3.getId(), + buildMove(finishMove.getId(), 2) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 2) + ), + buildMember( + player4.getId() + ) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test no finish move + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 2) + ), + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 2) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 2), + buildMove(finishMove.getId(), 0) + ), + buildMember( + player4.getId() + ) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test finish move amount!=1 + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 2) + ), + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 2), + buildMove(finishMove.getId(), 2) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 2) + ), + buildMember( + player4.getId() + ) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test finish move amount!=1 + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 2) + ), + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 2), + buildMove(finishMove.getId(), 2) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(normalMove.getId(), 2) + ), + buildMember( + player4.getId() + ) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + } + + @Test + @SuppressWarnings("unchecked") + public void matches_update_invalidPlayers() { + var profileNames = List.of("player1", "player2", "player3"); + var prerequisiteGroup = testUtils.createTestGroup(port, profileNames); + var oldSeason = prerequisiteGroup.getActiveSeason(); + + var oldPlayerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/players", List.class, PlayerDto.class); + var oldPlayers = (List) requestUtils.assertSuccess(oldPlayerResponse, ArrayList.class); + + assertEquals(profileNames.size(), oldPlayers.size()); + + var oldPlayer1 = oldPlayers.getFirst(); + var oldPlayer2 = oldPlayers.get(1); + var oldPlayer3 = oldPlayers.get(2); + + assertNotNull(oldPlayer1); + assertNotNull(oldPlayer2); + assertNotNull(oldPlayer3); + + var prerequisiteGroup1 = testUtils.createTestGroup(port, List.of("player1", "player2")); + + var otherPlayerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup1.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var otherPlayers = (List) requestUtils.assertSuccess(otherPlayerResponse, ArrayList.class); + + assertEquals(2, otherPlayers.size()); + + var otherPlayer1 = otherPlayers.getFirst(); + var otherPlayer2 = otherPlayers.getLast(); + + assertNotNull(otherPlayer1); + assertNotNull(otherPlayer2); + + var seasonCreateDto = new SeasonCreateDto(); + seasonCreateDto.setOldSeasonName("testing"); + seasonCreateDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonCreateDto, SeasonDto.class); + var newSeason = requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + + var newPlayerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/players", List.class, PlayerDto.class); + var newPlayers = (List) requestUtils.assertSuccess(newPlayerResponse, ArrayList.class); + + assertEquals(profileNames.size(), newPlayers.size()); + + var newPlayer1 = newPlayers.getFirst(); + var newPlayer2 = newPlayers.get(1); + var newPlayer3 = newPlayers.get(2); + + assertNotNull(newPlayer1); + assertNotNull(newPlayer2); + assertNotNull(newPlayer3); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMove = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(normalMove); + assertFalse(normalMove.isFinishingMove()); + assertNotNull(finishMove); + assertTrue(finishMove.isFinishingMove()); + + var matchDto = buildDto( + buildTeam( + buildMember( + newPlayer1.getId(), + buildMove(normalMove.getId(), 5), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + newPlayer2.getId(), + buildMove(normalMove.getId(), 3) + ) + ) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); + var match = requestUtils.assertSuccess(response, MatchDto.class); + + // test invalid player id + matchDto = buildDto( + buildTeam( + buildMember(newPlayer1.getId()), + buildMember("someIdThatNotExists") + ), + buildTeam( + buildMember(newPlayer3.getId()) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test invalid player id + matchDto = buildDto( + buildTeam( + buildMember("someIdThatNotExists") + ), + buildTeam( + buildMember(newPlayer3.getId()), + buildMember(newPlayer1.getId()) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test player from other season + matchDto = buildDto( + buildTeam( + buildMember(newPlayer1.getId()), + buildMember(oldPlayer1.getId()) + ), + buildTeam( + buildMember(newPlayer3.getId()) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test player from other season + matchDto = buildDto( + buildTeam( + buildMember(newPlayer1.getId()), + buildMember(newPlayer2.getId()) + ), + buildTeam( + buildMember(oldPlayer1.getId()) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test player from other group + matchDto = buildDto( + buildTeam( + buildMember(newPlayer1.getId()), + buildMember(newPlayer2.getId()) + ), + buildTeam( + buildMember(otherPlayer1.getId()) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test player from other group + matchDto = buildDto( + buildTeam( + buildMember(newPlayer1.getId()), + buildMember(otherPlayer1.getId()) + ), + buildTeam( + buildMember(newPlayer2.getId()) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + } + + @Test + @SuppressWarnings("unchecked") + public void matches_update_invalidMoves() { + var profileNames = List.of("player1", "player2", "player3"); + var prerequisiteGroup = testUtils.createTestGroup(port, profileNames); + var oldSeason = prerequisiteGroup.getActiveSeason(); + + var oldMovesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/rule-moves", List.class, RuleMoveDto.class); + var oldMoves = (List) requestUtils.assertSuccess(oldMovesResponse, ArrayList.class); + + assertTrue(oldMoves.size() >= 2); + + var oldNormalMove = oldMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var oldFinishMove = oldMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(oldNormalMove); + assertFalse(oldNormalMove.isFinishingMove()); + assertNotNull(oldFinishMove); + assertTrue(oldFinishMove.isFinishingMove()); + + var prerequisiteGroup1 = testUtils.createTestGroup(port, List.of("player1", "player2")); + + var otherMovesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup1.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var otherMoves = (List) requestUtils.assertSuccess(otherMovesResponse, ArrayList.class); + + assertTrue(otherMoves.size() >= 2); + + var otherNormalMove = otherMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var otherFinishMove = otherMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(otherNormalMove); + assertFalse(otherNormalMove.isFinishingMove()); + assertNotNull(otherFinishMove); + assertTrue(otherFinishMove.isFinishingMove()); + + var seasonCreateDto = new SeasonCreateDto(); + seasonCreateDto.setOldSeasonName("testing"); + seasonCreateDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonCreateDto, SeasonDto.class); + var newSeason = requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertEquals(profileNames.size(), players.size()); + + var player1 = players.getFirst(); + var player2 = players.get(1); + var player3 = players.get(2); + + assertNotNull(player1); + assertNotNull(player2); + assertNotNull(player3); + + var newMovesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/rule-moves", List.class, RuleMoveDto.class); + var newMoves = (List) requestUtils.assertSuccess(newMovesResponse, ArrayList.class); + + assertTrue(newMoves.size() >= 2); + + var newNormalMove = newMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var newFinishMove = newMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(newNormalMove); + assertFalse(newNormalMove.isFinishingMove()); + assertNotNull(newFinishMove); + assertTrue(newFinishMove.isFinishingMove()); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 5), + buildMove(newFinishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId(), + buildMove(newNormalMove.getId(), 3) + ) + ) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches", matchDto, MatchDto.class); + var match = requestUtils.assertSuccess(response, MatchDto.class); + + // test invalid move id + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove("someIdThatNotExists", 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(newNormalMove.getId(), 2) + ) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test invalid move id + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(newFinishMove.getId(), 1), + buildMove(newNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove("someIdThatNotExists", 2) + ) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test move from other season + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(newFinishMove.getId(), 1), + buildMove(oldNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(newNormalMove.getId(), 2) + ) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test move from other season + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(newFinishMove.getId(), 1), + buildMove(newNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(oldNormalMove.getId(), 2) + ) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test finish move from other season + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(oldFinishMove.getId(), 1), + buildMove(newNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(newNormalMove.getId(), 2) + ) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test move from other season + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(newNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(oldFinishMove.getId(), 1) + ) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test move from other group + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(newFinishMove.getId(), 1), + buildMove(otherNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(newNormalMove.getId(), 2) + ) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test move from other group + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(newFinishMove.getId(), 1), + buildMove(newNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(otherNormalMove.getId(), 2) + ) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test finish move from other group + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(otherFinishMove.getId(), 1), + buildMove(newNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(newNormalMove.getId(), 2) + ) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); + + // test move from other group + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(newNormalMove.getId(), 3) + ), + buildMember( + player2.getId(), + buildMove(newNormalMove.getId(), 3) + ) + ), + buildTeam( + buildMember( + player3.getId(), + buildMove(otherFinishMove.getId(), 1) + ) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), matchDto, MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_DTO_VALIDATION_FAILED); } @Test From b56a36afc6beaffa41ec740a74c79058501b5394 Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 15 Jun 2025 21:16:59 +0200 Subject: [PATCH 75/90] delete test --- .../api/control/MatchControllerTest.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java index 99d2a74a..9ffac2dc 100644 --- a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java @@ -2964,15 +2964,59 @@ public void matches_update_invalidMoves() { @Test @Transactional + @SuppressWarnings("unchecked") public void matches_delete_success() { var prerequisiteGroup = testUtils.createTestGroup(port); + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertTrue(players.size() >= 2); + + var player1 = players.getFirst(); + var player2 = players.getLast(); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMove = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 5), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId(), + buildMove(normalMove.getId(), 3) + ) + ) + ); + + var matchResponse = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var match = requestUtils.assertSuccess(matchResponse, MatchDto.class); + + var response = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + match.getId(), null, String.class); + var result = requestUtils.assertSuccess(response, String.class); + + assertEquals("OK", result); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + match.getId(), MatchDto.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_FOUND); } @Test public void matches_delete_invalidArgs() { var prerequisiteGroup = testUtils.createTestGroup(port); + //TODO } private MatchCreateDto buildDto(TeamCreateDto... teams) { From 8dcb051bb7ca0772b4f0ef67b2901fb11a8aabb1 Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 15 Jun 2025 21:25:58 +0200 Subject: [PATCH 76/90] invalid match delete tests --- .../api/control/MatchControllerTest.java | 141 +++++++++++++++++- 1 file changed, 139 insertions(+), 2 deletions(-) diff --git a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java index 9ffac2dc..6243477d 100644 --- a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java @@ -3013,10 +3013,147 @@ public void matches_delete_success() { } @Test - public void matches_delete_invalidArgs() { + @SuppressWarnings("unchecked") + public void matches_delete_invalidSeason() { var prerequisiteGroup = testUtils.createTestGroup(port); + var oldSeason = prerequisiteGroup.getActiveSeason(); - //TODO + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertTrue(players.size() >= 2); + + var player1 = players.getFirst(); + var player2 = players.getLast(); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam(buildMember(player2.getId())) + ); + + var matchResponse = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var match = requestUtils.assertSuccess(matchResponse, MatchDto.class); + + var response = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/someIdThatNotExists/matches/" + match.getId(), null, String.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_FOUND); + + var prerequisiteGroup1 = testUtils.createTestGroup(port, List.of("player1", "player2")); + + response = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/matches/" + match.getId(), null, String.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_OF_GROUP); + + var seasonCreateDto = new SeasonCreateDto(); + seasonCreateDto.setOldSeasonName("testing"); + seasonCreateDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonCreateDto, SeasonDto.class); + requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + + response = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/matches/" + match.getId(), null, String.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_ALREADY_ENDED); + } + + @Test + @Transactional + @SuppressWarnings("unchecked") + public void matches_delete_invalidMatch() { + var prerequisiteGroup = testUtils.createTestGroup(port); + var oldSeason = prerequisiteGroup.getActiveSeason(); + + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertTrue(players.size() >= 2); + + var player1 = players.getFirst(); + var player2 = players.getLast(); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam(buildMember(player2.getId())) + ); + + var matchResponse = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var oldMatch = requestUtils.assertSuccess(matchResponse, MatchDto.class); + + // test invalid match id (not existing) + var response = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + oldSeason.getId() + "/matches/someIdThatNotExists", null, String.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_FOUND); + + // test invalid match id (other season than provided) + var seasonCreateDto = new SeasonCreateDto(); + seasonCreateDto.setOldSeasonName("testing"); + seasonCreateDto.setRuleMoves(List.of( + testUtils.buildRuleMove("Normal", false, 1, 0), + testUtils.buildRuleMove("Finish", true, 1, 3) + )); + + var newSeasonResponse = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/active-season", seasonCreateDto, SeasonDto.class); + var newSeason = requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); + + response = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + oldMatch.getId(), null, String.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_OF_GROUP); + + // test invalid match id (other group than provided) + var prerequisiteGroup1 = testUtils.createTestGroup(port); + + playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup1.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertTrue(players.size() >= 2); + + player1 = players.getFirst(); + player2 = players.getLast(); + + movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup1.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam(buildMember(player2.getId())) + ); + + matchResponse = requestUtils.performPost(port, "/groups/" + prerequisiteGroup1.getId() + "/seasons/" + prerequisiteGroup1.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var otherGroupMatch = requestUtils.assertSuccess(matchResponse, MatchDto.class); + + response = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + otherGroupMatch.getId(), null, String.class); + requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_OF_GROUP); } private MatchCreateDto buildDto(TeamCreateDto... teams) { From e5165686887320c6c92317b4277aa7184615533f Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 15 Jun 2025 21:31:45 +0200 Subject: [PATCH 77/90] match update test --- .../api/control/MatchControllerTest.java | 152 +++++++++++++++++- 1 file changed, 151 insertions(+), 1 deletion(-) diff --git a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java index 6243477d..e925a145 100644 --- a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java @@ -2015,10 +2015,160 @@ public void matches_overviewById_invalidArgs() { @Test @Transactional + @SuppressWarnings("unchecked") public void matches_update_success() { var prerequisiteGroup = testUtils.createTestGroup(port); - //TODO + var playerResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/players", List.class, PlayerDto.class); + var players = (List) requestUtils.assertSuccess(playerResponse, ArrayList.class); + + assertTrue(players.size() >= 2); + + var player1 = players.getFirst(); + var player2 = players.getLast(); + + assertNotNull(player1); + assertNotNull(player2); + assertNotEquals(player1, player2); + + var movesResponse = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/rule-moves", List.class, RuleMoveDto.class); + var ruleMoves = (List) requestUtils.assertSuccess(movesResponse, ArrayList.class); + + assertTrue(ruleMoves.size() >= 2); + + var normalMove = ruleMoves.stream().filter(ruleMoveDto -> !ruleMoveDto.isFinishingMove()).findFirst().orElseThrow(); + var finishMove = ruleMoves.stream().filter(RuleMoveDto::isFinishingMove).findFirst().orElseThrow(); + + assertNotNull(normalMove); + assertFalse(normalMove.isFinishingMove()); + assertNotNull(finishMove); + assertTrue(finishMove.isFinishingMove()); + + var matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam(buildMember(player2.getId())) + ); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches", matchDto, MatchDto.class); + var oldMatch = requestUtils.assertSuccess(response, MatchDto.class); + + assertNotNull(oldMatch); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + oldMatch.getId(), MatchDto.class); + var fetched = requestUtils.assertSuccess(response, MatchDto.class); + + assertNotNull(fetched); + + oldMatch.setDate(fetched.getDate()); + assertEquals(oldMatch, fetched); + + matchDto = buildDto( + buildTeam( + buildMember( + player1.getId(), + buildMove(normalMove.getId(), 5), + buildMove(finishMove.getId(), 1) + ) + ), + buildTeam( + buildMember( + player2.getId(), + buildMove(normalMove.getId(), 3) + ) + ) + ); + + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + oldMatch.getId(), matchDto, MatchDto.class); + var match = requestUtils.assertSuccess(response, MatchDto.class); + + assertEquals(oldMatch.getId(), match.getId()); + assertEquals(oldMatch.getDate(), match.getDate()); + assertEquals(oldMatch.getSeason(), match.getSeason()); + assertEquals(oldMatch.getCreatedBy(), match.getCreatedBy()); + + assertNotNull(match.getId()); + assertEquals(prerequisiteGroup.getActiveSeason().getId(), match.getSeason().getId()); + assertEquals(prerequisiteGroup.getCreatedBy(), match.getCreatedBy()); + assertNotNull(match.getDate()); + + assertEquals(2, match.getTeams().size()); + + var team1 = match.getTeams().getFirst(); + var team2 = match.getTeams().getLast(); + + assertNotNull(team1); + assertNotNull(team1.getId()); + assertEquals(match.getId(), team1.getMatchId()); + assertNotNull(team2); + assertNotNull(team2.getId()); + assertEquals(match.getId(), team2.getMatchId()); + + var teamMembers1 = match.getTeamMembers().stream() + .filter(teamMemberDto -> teamMemberDto.getTeamId().equals(team1.getId())) + .toList(); + var teamMembers2 = match.getTeamMembers().stream() + .filter(teamMemberDto -> teamMemberDto.getTeamId().equals(team2.getId())) + .toList(); + + assertEquals(1, teamMembers1.size()); + assertEquals(1, teamMembers2.size()); + + var teamMember1 = teamMembers1.getFirst(); + var teamMember2 = teamMembers2.getFirst(); + + assertNotNull(teamMember1); + assertNotNull(teamMember1.getId()); + assertEquals(team1.getId(), teamMember1.getTeamId()); + assertEquals(player1.getId(), teamMember1.getPlayerId()); + assertNotNull(teamMember2); + assertNotNull(teamMember2.getId()); + assertEquals(team2.getId(), teamMember2.getTeamId()); + assertEquals(player2.getId(), teamMember2.getPlayerId()); + + var matchMoves1 = match.getMatchMoves().stream() + .filter(matchMove -> matchMove.getTeamMemberId().equals(teamMember1.getId())) + .toList(); + var matchMoves2 = match.getMatchMoves().stream() + .filter(matchMove -> matchMove.getTeamMemberId().equals(teamMember2.getId())) + .toList(); + + assertEquals(2, matchMoves1.size()); + assertEquals(1, matchMoves2.size()); + + var normalMove1 = matchMoves1.stream().filter(dto -> dto.getMoveId().equals(normalMove.getId())).findFirst().orElseThrow(); + var finishMove1 = matchMoves1.stream().filter(dto -> dto.getMoveId().equals(finishMove.getId())).findFirst().orElseThrow(); + var normalMove2 = matchMoves2.getFirst(); + + assertNotNull(normalMove1); + assertNotNull(normalMove1.getId()); + assertEquals(teamMember1.getId(), normalMove1.getTeamMemberId()); + assertEquals(normalMove.getId(), normalMove1.getMoveId()); + assertEquals(5, normalMove1.getValue()); + + assertNotNull(finishMove1); + assertNotNull(finishMove1.getId()); + assertEquals(teamMember1.getId(), finishMove1.getTeamMemberId()); + assertEquals(finishMove.getId(), finishMove1.getMoveId()); + assertEquals(1, finishMove1.getValue()); + + assertNotNull(normalMove2); + assertNotNull(normalMove2.getId()); + assertEquals(teamMember2.getId(), normalMove2.getTeamMemberId()); + assertEquals(normalMove.getId(), normalMove2.getMoveId()); + assertEquals(3, normalMove2.getValue()); + + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + match.getId(), MatchDto.class); + var newFetched = requestUtils.assertSuccess(response, MatchDto.class); + + assertNotNull(newFetched); + + match.setDate(newFetched.getDate()); + assertEquals(match, newFetched); } @Test From dee01e639a9ec4f9f16dbe9a9fe600db697db26e Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 15 Jun 2025 21:38:57 +0200 Subject: [PATCH 78/90] leaderboard tests --- .../control/LeaderboardControllerTest.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 api/src/test/java/pro/beerpong/api/control/LeaderboardControllerTest.java diff --git a/api/src/test/java/pro/beerpong/api/control/LeaderboardControllerTest.java b/api/src/test/java/pro/beerpong/api/control/LeaderboardControllerTest.java new file mode 100644 index 00000000..f7e67614 --- /dev/null +++ b/api/src/test/java/pro/beerpong/api/control/LeaderboardControllerTest.java @@ -0,0 +1,45 @@ +package pro.beerpong.api.control; + +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.ActiveProfiles; +import pro.beerpong.api.RequestUtils; +import pro.beerpong.api.TestUtils; +import pro.beerpong.api.model.dto.ErrorCodes; +import pro.beerpong.api.model.dto.GroupCreateDto; +import pro.beerpong.api.model.dto.GroupDto; +import pro.beerpong.api.util.DailyLeaderboard; +import pro.beerpong.api.util.RankingAlgorithm; + +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") +public class LeaderboardControllerTest { + @LocalServerPort + private int port; + + @Autowired + private RequestUtils requestUtils; + @Autowired + private TestUtils testUtils; + + @Test + @Transactional + public void leaderboard_get_success() { + + } + + @Test + public void leaderboard_get_invalidScope() { + + } +} \ No newline at end of file From e8fd529b6a7275b3c8151a8151eb3ca118694f37 Mon Sep 17 00:00:00 2001 From: Thies Date: Sun, 15 Jun 2025 21:45:06 +0200 Subject: [PATCH 79/90] remove useless boolean --- api/src/test/java/pro/beerpong/api/RequestUtils.java | 12 ++++++------ .../api/control/LeaderboardControllerTest.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/api/src/test/java/pro/beerpong/api/RequestUtils.java b/api/src/test/java/pro/beerpong/api/RequestUtils.java index 1dbdaac1..26c3ae94 100644 --- a/api/src/test/java/pro/beerpong/api/RequestUtils.java +++ b/api/src/test/java/pro/beerpong/api/RequestUtils.java @@ -62,10 +62,10 @@ public void resetAuth() { } @SuppressWarnings("unchecked") - private String generateAuthToken(int port, boolean force) { + private String generateAuthToken(int port) { String tempRefresh = REFRESH_TOKEN; - if (force || REFRESH_TOKEN == null) { + if (RESET_FOR_NEXT_REQUEST || REFRESH_TOKEN == null) { var signupDto = new AuthSignupDto(); signupDto.setDeviceId("test"); signupDto.setInstallationType(InstallationType.IOS); @@ -88,7 +88,7 @@ private String generateAuthToken(int port, boolean force) { tempRefresh = signupTokenDto.getToken(); - if (!force) { + if (!RESET_FOR_NEXT_REQUEST) { REFRESH_TOKEN = tempRefresh; } @@ -96,7 +96,7 @@ private String generateAuthToken(int port, boolean force) { var tempAuth = AUTH_TOKEN; - if (force || AUTH_TOKEN == null) { + if (RESET_FOR_NEXT_REQUEST || AUTH_TOKEN == null) { var refreshDto = new AuthRefreshDto(); refreshDto.setRefreshToken(tempRefresh); @@ -118,7 +118,7 @@ private String generateAuthToken(int port, boolean force) { tempAuth = refreshTokenDto.getToken(); - if (!force) { + if (!RESET_FOR_NEXT_REQUEST) { AUTH_TOKEN = tempAuth; } } @@ -130,7 +130,7 @@ public ResponseEntity performCall(boolean withAuth, int port, String pat HttpHeaders headers = new HttpHeaders(); if (withAuth) { - headers.setBearerAuth(generateAuthToken(port, RESET_FOR_NEXT_REQUEST)); + headers.setBearerAuth(generateAuthToken(port)); } RESET_FOR_NEXT_REQUEST = false; diff --git a/api/src/test/java/pro/beerpong/api/control/LeaderboardControllerTest.java b/api/src/test/java/pro/beerpong/api/control/LeaderboardControllerTest.java index f7e67614..8d5139b0 100644 --- a/api/src/test/java/pro/beerpong/api/control/LeaderboardControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/LeaderboardControllerTest.java @@ -35,7 +35,7 @@ public class LeaderboardControllerTest { @Test @Transactional public void leaderboard_get_success() { - + } @Test From ff90a271f85e9f7328bc416bd036ebb54136865c Mon Sep 17 00:00:00 2001 From: Thies Date: Tue, 17 Jun 2025 23:42:14 +0200 Subject: [PATCH 80/90] first leaderboard tests --- .../control/LeaderboardControllerTest.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/api/src/test/java/pro/beerpong/api/control/LeaderboardControllerTest.java b/api/src/test/java/pro/beerpong/api/control/LeaderboardControllerTest.java index 8d5139b0..b5ee3989 100644 --- a/api/src/test/java/pro/beerpong/api/control/LeaderboardControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/LeaderboardControllerTest.java @@ -12,6 +12,7 @@ import pro.beerpong.api.model.dto.ErrorCodes; import pro.beerpong.api.model.dto.GroupCreateDto; import pro.beerpong.api.model.dto.GroupDto; +import pro.beerpong.api.model.dto.LeaderboardDto; import pro.beerpong.api.util.DailyLeaderboard; import pro.beerpong.api.util.RankingAlgorithm; @@ -34,12 +35,38 @@ public class LeaderboardControllerTest { @Test @Transactional - public void leaderboard_get_success() { + public void leaderboard_get_Alltime_empty() { + var profileNames = List.of("player1", "player2", "player3"); + var prerequisiteGroup = testUtils.createTestGroup(port, "test", profileNames); + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/leaderboard?scope=all-time", LeaderboardDto.class); + var leaderboard = requestUtils.assertSuccess(response, LeaderboardDto.class); + + assertEquals(0, leaderboard.getNumMatches()); + assertEquals(3, leaderboard.getNumPlayers()); + assertEquals(prerequisiteGroup.getCreatedAt().toEpochSecond(), leaderboard.getStartedAt().toEpochSecond()); } @Test public void leaderboard_get_invalidScope() { + var prerequisiteGroup = testUtils.createTestGroup(port, "test"); + + // test invalid scope (non provided) + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/leaderboard?scope=something", LeaderboardDto.class); + requestUtils.assertFailure(response, ErrorCodes.LEADERBOARD_SCOPE_NOT_FOUND); + + // test invalid scope (scope=season without season id) + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/leaderboard?scope=season", LeaderboardDto.class); + requestUtils.assertFailure(response, ErrorCodes.LEADERBOARD_SEASON_NOT_FOUND); + + // test invalid scope (invalid season id) + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/leaderboard?scope=season&seasonId=someId", LeaderboardDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_FOUND); + + var prerequisiteGroup1 = testUtils.createTestGroup(port, "test123"); + // test invalid scope (season id from other group) + response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/leaderboard?scope=season&seasonId=" + prerequisiteGroup1.getActiveSeason().getId(), LeaderboardDto.class); + requestUtils.assertFailure(response, ErrorCodes.SEASON_NOT_OF_GROUP); } } \ No newline at end of file From 8d8c5e063994fc54545ed95ed4968a4028b41b11 Mon Sep 17 00:00:00 2001 From: Thies Date: Tue, 17 Jun 2025 23:46:02 +0200 Subject: [PATCH 81/90] fix tests --- api/src/test/java/pro/beerpong/api/RequestUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/test/java/pro/beerpong/api/RequestUtils.java b/api/src/test/java/pro/beerpong/api/RequestUtils.java index 26c3ae94..1d2302ef 100644 --- a/api/src/test/java/pro/beerpong/api/RequestUtils.java +++ b/api/src/test/java/pro/beerpong/api/RequestUtils.java @@ -123,6 +123,8 @@ private String generateAuthToken(int port) { } } + RESET_FOR_NEXT_REQUEST = false; + return tempAuth; } @@ -133,8 +135,6 @@ public ResponseEntity performCall(boolean withAuth, int port, String pat headers.setBearerAuth(generateAuthToken(port)); } - RESET_FOR_NEXT_REQUEST = false; - headers.setContentType(MediaType.APPLICATION_JSON); var entity = (body == null ? new HttpEntity<>(headers) : new HttpEntity<>(body, headers)); From 145652a2bc5446e79a8bc1ffad7953173a8cbb11 Mon Sep 17 00:00:00 2001 From: Thies Date: Tue, 23 Sep 2025 22:41:33 +0200 Subject: [PATCH 82/90] fix merge --- .../main/java/pro/beerpong/api/control/MatchController.java | 4 ++-- .../api/mapping/{AssetAuthMapper.java => AssetMapper.java} | 2 ++ api/src/main/java/pro/beerpong/api/mapping/GroupMapper.java | 2 +- api/src/main/java/pro/beerpong/api/mapping/ProfileMapper.java | 2 +- api/src/main/java/pro/beerpong/api/mapping/RuleMapper.java | 2 +- api/src/main/java/pro/beerpong/api/mapping/SeasonMapper.java | 2 +- api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java | 4 ---- 7 files changed, 8 insertions(+), 10 deletions(-) rename api/src/main/java/pro/beerpong/api/mapping/{AssetAuthMapper.java => AssetMapper.java} (93%) diff --git a/api/src/main/java/pro/beerpong/api/control/MatchController.java b/api/src/main/java/pro/beerpong/api/control/MatchController.java index 7f8499ae..f2257338 100644 --- a/api/src/main/java/pro/beerpong/api/control/MatchController.java +++ b/api/src/main/java/pro/beerpong/api/control/MatchController.java @@ -174,7 +174,7 @@ public ResponseEntity> deleteMatchById(@PathVariable St } @PutMapping("/{id}/photos/{teamId}") - public ResponseEntity> setPhoto(@PathVariable String groupId, @PathVariable String teamId) { + public ResponseEntity> setPhoto(@PathVariable String groupId, @PathVariable String teamId, @PathVariable String id, @PathVariable String seasonId) { var match = matchService.getMatchById(id); if (match == null) { @@ -201,7 +201,7 @@ public ResponseEntity> setPhoto(@PathVariable String g } @DeleteMapping("/{id}/photos/{teamId}") - public ResponseEntity> deletePhoto(@PathVariable String groupId, @PathVariable String teamId) { + public ResponseEntity> deletePhoto(@PathVariable String groupId, @PathVariable String teamId, @PathVariable String id, @PathVariable String seasonId) { var match = matchService.getMatchById(id); if (match == null) { diff --git a/api/src/main/java/pro/beerpong/api/mapping/AssetAuthMapper.java b/api/src/main/java/pro/beerpong/api/mapping/AssetMapper.java similarity index 93% rename from api/src/main/java/pro/beerpong/api/mapping/AssetAuthMapper.java rename to api/src/main/java/pro/beerpong/api/mapping/AssetMapper.java index ba653d10..629dcaf2 100644 --- a/api/src/main/java/pro/beerpong/api/mapping/AssetAuthMapper.java +++ b/api/src/main/java/pro/beerpong/api/mapping/AssetMapper.java @@ -18,6 +18,8 @@ public abstract class AssetMapper { @Mapping(target = "url", expression = "java(generateUrl(asset))") public abstract AssetMetadataDto assetToAssetMetadataDto(Asset asset); + public abstract Asset assetMetadataDtoToAsset(AssetMetadataDto dto); + @Mapping(source = "group.id", target = "groupId") @Mapping(source = "user.id", target = "userId") public abstract GroupMemberDto groupMemberToGroupMemberDto(GroupMember groupMember); diff --git a/api/src/main/java/pro/beerpong/api/mapping/GroupMapper.java b/api/src/main/java/pro/beerpong/api/mapping/GroupMapper.java index 80c97f34..edda96ac 100644 --- a/api/src/main/java/pro/beerpong/api/mapping/GroupMapper.java +++ b/api/src/main/java/pro/beerpong/api/mapping/GroupMapper.java @@ -8,7 +8,7 @@ import pro.beerpong.api.model.dto.GroupDto; import pro.beerpong.api.model.dto.GroupPreset; -@Mapper(componentModel = "spring", uses = AssetAuthMapper.class) +@Mapper(componentModel = "spring", uses = AssetMapper.class) public abstract class GroupMapper { @Mapping(target = "sportPreset", expression = "java(fromPreset(groupDto))") public abstract Group groupDtoToGroup(GroupDto groupDto); diff --git a/api/src/main/java/pro/beerpong/api/mapping/ProfileMapper.java b/api/src/main/java/pro/beerpong/api/mapping/ProfileMapper.java index 844134c9..b0d1eb17 100644 --- a/api/src/main/java/pro/beerpong/api/mapping/ProfileMapper.java +++ b/api/src/main/java/pro/beerpong/api/mapping/ProfileMapper.java @@ -6,7 +6,7 @@ import pro.beerpong.api.model.dto.ProfileCreateDto; import pro.beerpong.api.model.dto.ProfileDto; -@Mapper(componentModel = "spring", uses = AssetAuthMapper.class) +@Mapper(componentModel = "spring", uses = AssetMapper.class) public interface ProfileMapper { @Mapping(source = "groupId", target = "group.id") Profile profileDtoToProfile(ProfileDto profileDto); diff --git a/api/src/main/java/pro/beerpong/api/mapping/RuleMapper.java b/api/src/main/java/pro/beerpong/api/mapping/RuleMapper.java index b63db205..48f4cfa6 100644 --- a/api/src/main/java/pro/beerpong/api/mapping/RuleMapper.java +++ b/api/src/main/java/pro/beerpong/api/mapping/RuleMapper.java @@ -5,7 +5,7 @@ import pro.beerpong.api.model.dto.RuleCreateDto; import pro.beerpong.api.model.dto.RuleDto; -@Mapper(componentModel = "spring", uses = AssetAuthMapper.class) +@Mapper(componentModel = "spring", uses = AssetMapper.class) public interface RuleMapper { Rule ruleCreateDtoToRule(RuleCreateDto dto); diff --git a/api/src/main/java/pro/beerpong/api/mapping/SeasonMapper.java b/api/src/main/java/pro/beerpong/api/mapping/SeasonMapper.java index 61bfec45..a909fb6e 100644 --- a/api/src/main/java/pro/beerpong/api/mapping/SeasonMapper.java +++ b/api/src/main/java/pro/beerpong/api/mapping/SeasonMapper.java @@ -4,7 +4,7 @@ import pro.beerpong.api.model.dao.Season; import pro.beerpong.api.model.dto.SeasonDto; -@Mapper(componentModel = "spring", uses = AssetAuthMapper.class) +@Mapper(componentModel = "spring", uses = AssetMapper.class) public interface SeasonMapper { SeasonDto seasonToSeasonDto(Season season); } \ No newline at end of file diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java index 37ddc7ca..b8b76bf3 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java @@ -40,7 +40,6 @@ public enum ErrorCodes { MATCH_NOT_OF_GROUP(HttpStatus.BAD_REQUEST, "matchNotOfGroup", "The provided match id does not match the provided season or group!"), /* RULE MOVES */ MATCH_CREATE_DTO_NEEDS_IDS(HttpStatus.BAD_REQUEST, "matchCreateDtoNeedsIds", "Every team needs a 'existingTeamId' attribute containing the id of the existing team!"), - MATCH_DTO_VALIDATION_FAILED(HttpStatus.FORBIDDEN, "matchDtoValidationFailed", "The validation of the match create dto failed (invalid player, rulemove, season or group id, or no finish move)"), MATCH_NO_TEAM_FOUND(HttpStatus.FORBIDDEN, "matchNoTeamFound", "Could not find a team with the provided id linked to the provided match!"), MATCH_TEAM_HAS_NO_PHOTO(HttpStatus.NOT_FOUND, "matchTeamHasNoPhoto", "The provided team does not have a photo set!"), MATCH_GROUP_OR_SEASON_ID_DONT_MATCH(HttpStatus.BAD_REQUEST, "matchGroupOrSeasonIdDontMatch", "The provided group and season id dont match the group and season id of the provided match!") @@ -52,8 +51,6 @@ public enum ErrorCodes { /* RULES */ RULE_INVALID_DTO(HttpStatus.BAD_REQUEST, "ruleInvalidDto", "Every rule name and description has to be non-null and non-empty!"), /* PLAYERS */ - RULE_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "ruleValidationFailed", "The validation of the created rules has failed (invalid group or season id)") - /* PLAYERS */, PLAYER_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "playerValidationFailed", "The player is not part of the provided season or group!"), PLAYER_NOT_FOUND(HttpStatus.NOT_FOUND, "playerNotFound", "The requested player could not be found!"), PLAYER_ALREADY_DELETED(HttpStatus.FORBIDDEN, "playerAlreadyDeleted", "This player has been deleted!"), @@ -62,7 +59,6 @@ public enum ErrorCodes { PROFILE_NOT_FOUND(HttpStatus.NOT_FOUND, "profileNotFound", "The requested profile could not be found!"), PROFILE_NOT_OF_GROUP(HttpStatus.BAD_REQUEST, "profileNotOfGroup", "The provided profile and group id do not match!"), PROFILE_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "profileAlreadyExists", "There already exists a profile with the provided name and an active player in the current season!"), - PROFILE_NOT_OF_GROUP(HttpStatus.FORBIDDEN, "profileHasDifferentGroup", "The profiles group id does not match the provided group id!"), PROFILE_HAS_NO_AVATAR(HttpStatus.NOT_FOUND, "profileHasNoAvatar", "The provided profiles does not have an avatar saved!") /* ASSETS */, ASSET_NOT_FOUND(HttpStatus.NOT_FOUND, "assetNotFound", "The requested asset could not be found!"), From 9ec3c196e23bc1409d4e830a5bea02b33b21c5ac Mon Sep 17 00:00:00 2001 From: Thies Date: Tue, 23 Sep 2025 23:18:56 +0200 Subject: [PATCH 83/90] add group tests --- api/pom.xml | 1 - .../pro/beerpong/api/CreateMatchTest.java | 320 ------------------ .../api/control/GroupControllerTest.java | 280 +++++++++++++++ 3 files changed, 280 insertions(+), 321 deletions(-) delete mode 100644 api/src/test/java/pro/beerpong/api/CreateMatchTest.java create mode 100644 api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java diff --git a/api/pom.xml b/api/pom.xml index 5820fc57..8a1eb413 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -95,7 +95,6 @@ lombok-mapstruct-binding 0.2.0 - software.amazon.awssdk s3 diff --git a/api/src/test/java/pro/beerpong/api/CreateMatchTest.java b/api/src/test/java/pro/beerpong/api/CreateMatchTest.java deleted file mode 100644 index 5cc8eac0..00000000 --- a/api/src/test/java/pro/beerpong/api/CreateMatchTest.java +++ /dev/null @@ -1,320 +0,0 @@ -package pro.beerpong.api; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.test.context.ActiveProfiles; -import pro.beerpong.api.control.GroupPresetsController; -import pro.beerpong.api.model.dto.*; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@ActiveProfiles("test") -public class CreateMatchTest { - @LocalServerPort - private int port; - - @Autowired - private TestUtils testUtils; - -// @BeforeEach -// void setUp() { -// if (seasonRepository.findById(seasonId).isEmpty()) { -// var season = new pro.beerpong.api.model.dao.Season(); -// season.setId(seasonId); -// season.setGroupId(groupId); -// seasonRepository.save(season); -// } -// } - -// @Test -// @SuppressWarnings("unchecked") -// void createMatch_withTeamsMembersAndMoves_shouldCreateMatchTeamsAndMoves() throws Exception { -// var createGroupDto = new GroupCreateDto(); -// createGroupDto.setProfileNames(List.of("player1", "player2", "player3", "player4")); -// createGroupDto.setName("test"); -// createGroupDto.setSportPreset("beerpong"); -// -// var prerequisiteGroupResponse = testUtils.performPost(port, "/groups", createGroupDto, GroupDto.class); -// -// assertNotNull(prerequisiteGroupResponse); -// assertEquals(200, prerequisiteGroupResponse.getStatusCode().value()); -// -// ResponseEnvelope prerequisiteEnvelope = (ResponseEnvelope) prerequisiteGroupResponse.getBody(); -// assertNotNull(prerequisiteEnvelope); -// assertEquals(ResponseEnvelope.Status.OK, prerequisiteEnvelope.getStatus()); -// assertNull(prerequisiteEnvelope.getError()); -// assertEquals(200, prerequisiteEnvelope.getHttpCode()); -// -// var prerequisiteGroup = prerequisiteEnvelope.getData(); -// assertEquals(GroupPresetsController.BEERPONG.getId(), prerequisiteGroup.getSportPreset().getId()); -// var response = testUtils.performGet(port, "/groups?inviteCode=" + prerequisiteGroup.getInviteCode(), GroupDto.class); -// -// assertNotNull(response); -// assertEquals(200, response.getStatusCode().value()); -// -// ResponseEnvelope envelope = (ResponseEnvelope) response.getBody(); -// assertNotNull(envelope); -// assertEquals(ResponseEnvelope.Status.OK, envelope.getStatus()); -// assertNull(envelope.getError()); -// assertEquals(200, envelope.getHttpCode()); -// -// var group = envelope.getData(); -// // if this is not here, the startDate millis are rounded and this test fails -// group.getActiveSeason().setStartDate(prerequisiteGroup.getActiveSeason().getStartDate()); -// group.setCreatedAt(prerequisiteGroup.getCreatedAt()); -// -// assertNotNull(group); -// assertNotNull(group.getName()); -// assertEquals(prerequisiteGroup, group); -// assertNotNull(group.getId()); -// assertNotNull(group.getInviteCode()); -// assertNotNull(group.getActiveSeason()); -// assertNotNull(group.getActiveSeason().getId()); -// assertEquals(group.getActiveSeason().getGroupId(), group.getId()); -// -// var season = group.getActiveSeason(); -// -// var playersResponse = testUtils.performGet(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/players", List.class, PlayerDto.class); -// -// assertNotNull(playersResponse); -// assertEquals(200, playersResponse.getStatusCode().value()); -// -// ResponseEnvelope> playersEnvelope = (ResponseEnvelope>) playersResponse.getBody(); -// assertNotNull(playersEnvelope); -// assertEquals(ResponseEnvelope.Status.OK, playersEnvelope.getStatus()); -// assertNull(playersEnvelope.getError()); -// assertEquals(200, playersEnvelope.getHttpCode()); -// -// var players = playersEnvelope.getData(); -// -// var createRulemMoveDto = new RuleMoveCreateDto(); -// createRulemMoveDto.setName("RuleMove1"); -// createRulemMoveDto.setFinishingMove(false); -// createRulemMoveDto.setPointsForTeam(0); -// createRulemMoveDto.setPointsForScorer(1); -// var createRulemMoveDto1 = new RuleMoveCreateDto(); -// createRulemMoveDto1.setName("RuleMove2"); -// createRulemMoveDto1.setFinishingMove(true); -// createRulemMoveDto1.setPointsForTeam(1); -// createRulemMoveDto1.setPointsForScorer(1); -// -// var ruleMoveResponse = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRulemMoveDto, RuleMoveDto.class); -// var ruleMoveResponse1 = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRulemMoveDto1, RuleMoveDto.class); -// -// assertNotNull(ruleMoveResponse); -// assertNotNull(ruleMoveResponse1); -// assertEquals(200, ruleMoveResponse.getStatusCode().value()); -// assertEquals(200, ruleMoveResponse1.getStatusCode().value()); -// -// ResponseEnvelope ruleMoveEnvelope = (ResponseEnvelope) ruleMoveResponse.getBody(); -// ResponseEnvelope ruleMoveEnvelope1 = (ResponseEnvelope) ruleMoveResponse1.getBody(); -// assertNotNull(ruleMoveEnvelope); -// assertNotNull(ruleMoveEnvelope1); -// assertEquals(ResponseEnvelope.Status.OK, ruleMoveEnvelope.getStatus()); -// assertEquals(ResponseEnvelope.Status.OK, ruleMoveEnvelope1.getStatus()); -// assertNull(ruleMoveEnvelope.getError()); -// assertNull(ruleMoveEnvelope1.getError()); -// assertEquals(200, ruleMoveEnvelope.getHttpCode()); -// assertEquals(200, ruleMoveEnvelope1.getHttpCode()); -// -// var ruleMove = ruleMoveEnvelope.getData(); -// var ruleMove1 = ruleMoveEnvelope1.getData(); -// -// // Arrange: Erstelle ein MatchCreateDto mit Teams, TeamMembers und Moves -// MatchCreateDto matchCreateDto = new MatchCreateDto(); -// -// // Team 1 mit zwei Spielern und ihren Moves -// TeamCreateDto team1 = new TeamCreateDto(); -// TeamMemberCreateDto member1 = new TeamMemberCreateDto(); -// member1.setPlayerId(players.get(0).getId()); -// -// MatchMoveDto move1 = new MatchMoveDto(); -// move1.setMoveId(ruleMove.getId()); -// move1.setCount(3); -// -// MatchMoveDto move2 = new MatchMoveDto(); -// move2.setMoveId(ruleMove.getId()); -// move2.setCount(5); -// -// MatchMoveDto move7 = new MatchMoveDto(); -// move7.setMoveId(ruleMove1.getId()); -// move7.setCount(1); -// -// member1.setMoves(List.of(move1, move2, move7)); -// -// TeamMemberCreateDto member2 = new TeamMemberCreateDto(); -// member2.setPlayerId(players.get(1).getId()); -// -// MatchMoveDto move3 = new MatchMoveDto(); -// move3.setMoveId(ruleMove.getId()); -// move3.setCount(2); -// -// member2.setMoves(List.of(move3)); -// -// team1.setTeamMembers(List.of(member1, member2)); -// -// // Team 2 mit zwei Spielern und ihren Moves -// TeamCreateDto team2 = new TeamCreateDto(); -// TeamMemberCreateDto member3 = new TeamMemberCreateDto(); -// member3.setPlayerId(players.get(2).getId()); -// -// MatchMoveDto move4 = new MatchMoveDto(); -// move4.setMoveId(ruleMove.getId()); -// move4.setCount(4); -// -// member3.setMoves(List.of(move4)); -// -// TeamMemberCreateDto member4 = new TeamMemberCreateDto(); -// member4.setPlayerId(players.get(3).getId()); -// -// MatchMoveDto move5 = new MatchMoveDto(); -// move5.setMoveId(ruleMove.getId()); -// move5.setCount(1); -// -// MatchMoveDto move6 = new MatchMoveDto(); -// move6.setMoveId(ruleMove.getId()); -// move6.setCount(6); -// -// member4.setMoves(List.of(move5, move6)); -// -// team2.setTeamMembers(List.of(member3, member4)); -// -// matchCreateDto.setTeams(List.of(team1, team2)); -// -// var createMatchResponse = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches", matchCreateDto, MatchDto.class); -// -// assertNotNull(createMatchResponse); -// assertEquals(200, createMatchResponse.getStatusCode().value()); -// -// ResponseEnvelope matchEnvelope = (ResponseEnvelope) createMatchResponse.getBody(); -// assertNotNull(matchEnvelope); -// assertEquals(ResponseEnvelope.Status.OK, matchEnvelope.getStatus()); -// assertNull(matchEnvelope.getError()); -// assertEquals(200, matchEnvelope.getHttpCode()); -// } -// -// @Test -// @SuppressWarnings("unchecked") -// void updateMatch_withTeamsMembersAndMoves_shouldUpdateMatchTeamsAndMoves() throws Exception { -// // Step 1: Create a prerequisite group with players -// var createGroupDto = new GroupCreateDto(); -// createGroupDto.setProfileNames(List.of("player1", "player2", "player3", "player4")); -// createGroupDto.setName("test-update"); -// createGroupDto.setSportPreset("beerpong"); -// -// var groupResponse = testUtils.performPost(port, "/groups", createGroupDto, GroupDto.class); -// ResponseEnvelope groupEnvelope = (ResponseEnvelope) groupResponse.getBody(); -// var group = groupEnvelope.getData(); -// -// var season = group.getActiveSeason(); -// -// var playersResponse = testUtils.performGet(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/players", List.class, PlayerDto.class); -// ResponseEnvelope> playersEnvelope = (ResponseEnvelope>) playersResponse.getBody(); -// var players = playersEnvelope.getData(); -// -// // Step 2: Create a rule move -// var createRuleMoveDto = new RuleMoveDto(); -// createRuleMoveDto.setName("UpdateRuleMove"); -// var createRuleMoveDto1 = new RuleMoveDto(); -// createRuleMoveDto1.setName("UpdateRuleMove"); -// createRuleMoveDto1.setFinishingMove(true); -// -// var ruleMoveResponse = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRuleMoveDto, RuleMoveDto.class); -// var ruleMoveResponse1 = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRuleMoveDto1, RuleMoveDto.class); -// ResponseEnvelope ruleMoveEnvelope = (ResponseEnvelope) ruleMoveResponse.getBody(); -// ResponseEnvelope ruleMoveEnvelope1 = (ResponseEnvelope) ruleMoveResponse1.getBody(); -// var ruleMove = ruleMoveEnvelope.getData(); -// var ruleMove1 = ruleMoveEnvelope1.getData(); -// -// // Step 3: Create a match with teams and members -// MatchCreateDto matchCreateDto = new MatchCreateDto(); -// -// // Team 1 -// TeamCreateDto team1 = new TeamCreateDto(); -// TeamMemberCreateDto member1 = new TeamMemberCreateDto(); -// member1.setPlayerId(players.get(0).getId()); -// MatchMoveDto move1 = new MatchMoveDto(); -// move1.setMoveId(ruleMove1.getId()); -// move1.setCount(1); -// member1.setMoves(List.of(move1)); -// team1.setTeamMembers(List.of(member1)); -// -// // Team 2 -// TeamCreateDto team2 = new TeamCreateDto(); -// TeamMemberCreateDto member2 = new TeamMemberCreateDto(); -// member2.setPlayerId(players.get(1).getId()); -// MatchMoveDto move2 = new MatchMoveDto(); -// move2.setMoveId(ruleMove.getId()); -// move2.setCount(5); -// member2.setMoves(List.of(move2)); -// team2.setTeamMembers(List.of(member2)); -// -// matchCreateDto.setTeams(List.of(team1, team2)); -// -// // Step 4: Create the match -// var createMatchResponse = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches", matchCreateDto, MatchDto.class); -// ResponseEnvelope matchEnvelope = (ResponseEnvelope) createMatchResponse.getBody(); -// assert matchEnvelope != null; -// var match = matchEnvelope.getData(); -// -// team1.getTeamMembers().get(0).getMoves().get(0).setCount(1); -// matchCreateDto.setTeams(List.of(team1, team2)); -// -// // Step 5: Update the match -// var updateResponse = testUtils.performPut(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches/" + match.getId(), matchCreateDto, MatchDto.class); -// ResponseEnvelope updateEnvelope = (ResponseEnvelope) updateResponse.getBody(); -// -// // Assertions -// assertNotNull(updateResponse); -// assertEquals(200, updateResponse.getStatusCode().value()); -// assertNotNull(updateEnvelope); -// assertEquals(ResponseEnvelope.Status.OK, updateEnvelope.getStatus()); -// assertNull(updateEnvelope.getError()); -// assertEquals(200, updateEnvelope.getHttpCode()); -// } - -// @Test -// @SuppressWarnings("unchecked") -// void getAllMatches_shouldReturnAllMatchesForSeason() throws Exception { -// // Step 1: Create a prerequisite group with players -// var createGroupDto = new GroupCreateDto(); -// createGroupDto.setProfileNames(List.of("player1", "player2")); -// createGroupDto.setName("test-get"); - //createGroupDto.setSportPreset("beerpong"); -// -// var groupResponse = testUtils.performPost(port, "/groups", createGroupDto, GroupDto.class); -// ResponseEnvelope groupEnvelope = (ResponseEnvelope) groupResponse.getBody(); -// var group = groupEnvelope.getData(); -// -// var season = group.getActiveSeason(); -// -// // Step 2: Create a match -// MatchCreateDto matchCreateDto = new MatchCreateDto(); -// TeamCreateDto team = new TeamCreateDto(); -// TeamMemberCreateDto member = new TeamMemberCreateDto(); -// member.setPlayerId("player1-id"); -// team.setTeamMembers(List.of(member)); -// matchCreateDto.setTeams(List.of(team)); -// -// testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/match", matchCreateDto, MatchDto.class); -// -// // Step 3: Get all matches for the season -// var getAllMatchesResponse = testUtils.performGet(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches", List.class, MatchDto.class); -// ResponseEnvelope> matchesEnvelope = (ResponseEnvelope>) getAllMatchesResponse.getBody(); -// -// // Assertions -// assertNotNull(getAllMatchesResponse); -// assertEquals(200, getAllMatchesResponse.getStatusCode().value()); -// assertNotNull(matchesEnvelope); -// assertEquals(ResponseEnvelope.Status.OK, matchesEnvelope.getStatus()); -// assertNull(matchesEnvelope.getError()); -// assertEquals(200, matchesEnvelope.getHttpCode()); -// assertNotNull(matchesEnvelope.getData()); -// assertEquals(1, matchesEnvelope.getData().size()); -// } -} \ No newline at end of file diff --git a/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java new file mode 100644 index 00000000..8208efe3 --- /dev/null +++ b/api/src/test/java/pro/beerpong/api/control/GroupControllerTest.java @@ -0,0 +1,280 @@ +package pro.beerpong.api.control; + +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.ActiveProfiles; +import pro.beerpong.api.TestUtils; +import pro.beerpong.api.RequestUtils; +import pro.beerpong.api.model.dto.ErrorCodes; +import pro.beerpong.api.model.dto.GroupCreateDto; +import pro.beerpong.api.model.dto.GroupDto; +import pro.beerpong.api.util.DailyLeaderboard; +import pro.beerpong.api.util.RankingAlgorithm; + +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") +public class GroupControllerTest { + @LocalServerPort + private int port; + + @Autowired + private RequestUtils requestUtils; + @Autowired + private TestUtils testUtils; + + @Test + @Transactional + public void group_create_success() { + var name = "test"; + var group = testUtils.createTestGroup(port, name); + + // test simple group creation + assertNotNull(group); + assertNotNull(group.getId()); + assertNotNull(group.getName()); + assertEquals(name, group.getName()); + assertNotNull(group.getInviteCode()); + assertNotNull(group.getCreatedAt()); + assertNull(group.getWallpaperAsset()); + assertNull(group.getCustomSportName()); + assertEquals(GroupPresetsController.BEERPONG.getId(), group.getSportPreset().getId()); + + assertNotNull(group.getActiveSeason()); + assertNotNull(group.getActiveSeason().getId()); + assertNull(group.getActiveSeason().getName()); + assertNotNull(group.getActiveSeason().getStartDate()); + assertNull(group.getActiveSeason().getEndDate()); + assertEquals(group.getActiveSeason().getGroupId(), group.getId()); + assertNotNull(group.getActiveSeason().getSeasonSettings()); + assertEquals(1, group.getActiveSeason().getSeasonSettings().getMinMatchesToQualify()); + assertEquals(1, group.getActiveSeason().getSeasonSettings().getMinTeamSize()); + assertEquals(10, group.getActiveSeason().getSeasonSettings().getMaxTeamSize()); + assertEquals(RankingAlgorithm.AVERAGE, group.getActiveSeason().getSeasonSettings().getRankingAlgorithm()); + assertEquals(DailyLeaderboard.WAKE_TIME, group.getActiveSeason().getSeasonSettings().getDailyLeaderboard()); + assertEquals(LocalTime.of(0, 0), group.getActiveSeason().getSeasonSettings().getWakeTime()); + assertEquals(requestUtils.currentUserId(), group.getCreatedBy().getUserId()); + assertEquals(group.getId(), group.getCreatedBy().getGroupId()); + assertEquals(group.getCreatedBy(), group.getActiveSeason().getCreatedBy()); + + group = testUtils.createTestGroup(port, "test", List.of("player1", "player2"), null, "test123"); + + // test group creation with custom sport name + assertNotNull(group); + assertEquals("test123", group.getCustomSportName()); + assertNull(group.getSportPreset()); + + group = testUtils.createTestGroup(port, "test", List.of("player1", "player2"), "kicker", "test123"); + + // test group creation with other game preset + assertNotNull(group); + assertNull(group.getCustomSportName()); + assertEquals(GroupPresetsController.KICKER.getId(), group.getSportPreset().getId()); + } + + @Test + @Transactional + public void group_create_invalidName() { + // test invalid group name (empty) + var response = testUtils.postGroup(port, "", List.of("player1", "player2"), "beerpong"); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + + // test invalid group name (null) + response = testUtils.postGroup(port, null, List.of("player1", "player2"), "beerpong"); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + + // test invalid group name (too short) + response = testUtils.postGroup(port, "a", List.of("player1", "player2"), "beerpong"); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + + // test invalid group name (too long) + response = testUtils.postGroup(port, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", List.of("player1", "player2"), "beerpong"); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + } + + @Test + @Transactional + public void group_create_invalidProfiles() { + // test invalid profile names (empty) + var response = testUtils.postGroup(port, "test", List.of(), "beerpong"); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_PROFILE_NAMES); + + // test invalid profile names (null) + response = testUtils.postGroup(port, "test", null, "beerpong"); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_PROFILE_NAMES); + + // test invalid profile names (non unique names) + response = testUtils.postGroup(port, "test", List.of("player1", "player2", "player2"), "beerpong"); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_PROFILE_NAMES); + } + + @Test + @Transactional + public void group_create_invalidSport() { + // test invalid sport (non existing preset) + var response = testUtils.postGroup(port, "test", List.of("player1", "player2"), "notExisting"); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); + + // test invalid sport (preset null) + response = testUtils.postGroup(port, "test", List.of("player1", "player2"), null); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); + + // test invalid sport (custom sport empty) + response = testUtils.postGroup(port, "test", List.of("player1", "player2"), null, " "); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_SPORT); + } + + @Test + @Transactional + @SuppressWarnings("unchecked") + public void group_userGroups() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + var response = requestUtils.performGet(port, "/groups/user", List.class, GroupDto.class); + var groups = (List) requestUtils.assertSuccess(response, ArrayList.class); + + // test groups a user has access to + assertFalse(groups.isEmpty()); + assertTrue(groups.stream().anyMatch(groupDto -> groupDto.getId().equals(prerequisiteGroup.getId()))); + } + + @Test + @Transactional + public void group_findByInviteCode_success() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + var response = requestUtils.performGet(port, "/groups?inviteCode=" + prerequisiteGroup.getInviteCode(), GroupDto.class); + var group = requestUtils.assertSuccess(response, GroupDto.class); + + // test group by invite code + assertNotNull(group); + testUtils.assertGroupEquals(prerequisiteGroup, group); + } + + @Test + @Transactional + public void group_findByInviteCode_invalidInviteCode() { + // test invalid inviteCode (empty) + var response = requestUtils.performGet(port, "/groups?inviteCode= ", GroupDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_INVITE_CODE); + + // test invalid inviteCode (not existing) + response = requestUtils.performGet(port, "/groups?inviteCode=someIdThatNotExists", GroupDto.class); + requestUtils.assertFailure(response, ErrorCodes.GROUP_INVITE_NOT_FOUND); + } + + @Test + @Transactional + public void group_findById_success() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + var response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId(), GroupDto.class); + var group = requestUtils.assertSuccess(response, GroupDto.class); + + // test group by id + assertNotNull(group); + testUtils.assertGroupEquals(prerequisiteGroup, group); + } + + @Test + public void group_findById_invalidId() { + // test invalid group id (not existing) + var response = requestUtils.performGet(port, "/groups/someIdThatNotExists", GroupDto.class); + requestUtils.assertFailure(response, HttpStatus.UNAUTHORIZED, "No access to this group!"); + } + + @Test + @Transactional + public void group_update_success() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + var createDto = new GroupCreateDto(); + createDto.setName("test123"); + createDto.setCustomSportName("kicker"); + createDto.setProfileNames(List.of("player3", "player4", "player1")); + + var response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); + var group = requestUtils.assertSuccess(response, GroupDto.class); + + // test group update + assertNotNull(prerequisiteGroup); + assertNotNull(group); + assertEquals(prerequisiteGroup.getId(), group.getId()); + assertEquals(createDto.getName(), group.getName()); + assertEquals(prerequisiteGroup.getInviteCode(), group.getInviteCode()); + assertEquals(prerequisiteGroup.getCreatedBy(), group.getCreatedBy()); + assertEquals(prerequisiteGroup.getWallpaperAsset(), group.getWallpaperAsset()); + assertEquals(prerequisiteGroup.getCustomSportName(), group.getCustomSportName()); + assertEquals(prerequisiteGroup.getSportPreset(), group.getSportPreset()); + testUtils.assertSeasonEquals(prerequisiteGroup.getActiveSeason(), group.getActiveSeason()); + } + + @Test + public void group_update_invalidName() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + var createDto = new GroupCreateDto(); + createDto.setName(" "); + + // test invalid group name (empty) + var response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + + createDto.setName(null); + + // test invalid group name (null) + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + + createDto.setName("a"); + + // test invalid group name (too short) + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + + createDto.setName("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + + // test invalid group name (too long) + response = requestUtils.performPut(port, "/groups/" + prerequisiteGroup.getId(), createDto, GroupDto.class); + requestUtils.assertFailure(response, ErrorCodes.INVALID_GROUP_NAME); + } + + @Test + @Transactional + public void group_join_success() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + // test group join (already in group) + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/join", null, String.class); + requestUtils.assertFailure(response, ErrorCodes.GROUP_ALREADY_IN_GROUP); + + requestUtils.resetAuthForNextRequest(); + + response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/join", null, String.class); + var ok = requestUtils.assertSuccess(response, String.class); + + // test group join (not in group) + assertEquals("OK", ok); + } + + @Test + @Transactional + public void group_leave() { + var prerequisiteGroup = testUtils.createTestGroup(port); + + var response = requestUtils.performPost(port, "/groups/" + prerequisiteGroup.getId() + "/leave", null, String.class); + var ok = requestUtils.assertSuccess(response, String.class); + + // test group leave + assertEquals("OK", ok); + } +} \ No newline at end of file From de6b35666bf820f533c0481ae6a5a5ef723071cd Mon Sep 17 00:00:00 2001 From: Thies Date: Wed, 24 Sep 2025 11:27:04 +0200 Subject: [PATCH 84/90] fix tests --- .../beerpong/api/model/dto/ErrorCodes.java | 2 - .../beerpong/api/service/MatchService.java | 2 +- .../resources/application-test.properties | 1 + .../api/control/MatchControllerTest.java | 51 ++++++++++++++++--- 4 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 api/src/main/resources/application-test.properties diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java index b8b76bf3..8dfe57b0 100644 --- a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java +++ b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java @@ -37,8 +37,6 @@ public enum ErrorCodes { MATCH_CREATE_DTO_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "matchCreateDtoValidationFailed", "The team sizes are not in the boundaries of the season settings!"), MATCH_WRONG_AMOUNT_OF_TEAMS(HttpStatus.BAD_REQUEST, "matchWrongAmountOfTeams", "The amount of teams has to be exactly 2!"), MATCH_DTO_VALIDATION_FAILED(HttpStatus.FORBIDDEN, "matchDtoValidationFailed", "The validation of the match create dto failed (invalid player, rulemove, season or group id, double player, or no/more than exactly one finish move)"), - MATCH_NOT_OF_GROUP(HttpStatus.BAD_REQUEST, "matchNotOfGroup", "The provided match id does not match the provided season or group!"), - /* RULE MOVES */ MATCH_CREATE_DTO_NEEDS_IDS(HttpStatus.BAD_REQUEST, "matchCreateDtoNeedsIds", "Every team needs a 'existingTeamId' attribute containing the id of the existing team!"), MATCH_NO_TEAM_FOUND(HttpStatus.FORBIDDEN, "matchNoTeamFound", "Could not find a team with the provided id linked to the provided match!"), MATCH_TEAM_HAS_NO_PHOTO(HttpStatus.NOT_FOUND, "matchTeamHasNoPhoto", "The provided team does not have a photo set!"), diff --git a/api/src/main/java/pro/beerpong/api/service/MatchService.java b/api/src/main/java/pro/beerpong/api/service/MatchService.java index d76fa572..5e366844 100644 --- a/api/src/main/java/pro/beerpong/api/service/MatchService.java +++ b/api/src/main/java/pro/beerpong/api/service/MatchService.java @@ -417,7 +417,7 @@ public ErrorCodes deleteMatch(String id, String seasonId, String groupId) { teamRepository.deleteAllById(match.getTeams().stream().map(TeamDto::getId).toList()); matchRepository.deleteById(id); } else { - error.set(ErrorCodes.MATCH_NOT_OF_GROUP); + error.set(ErrorCodes.MATCH_GROUP_OR_SEASON_ID_DONT_MATCH); } } else { error.set(ErrorCodes.SEASON_ALREADY_ENDED); diff --git a/api/src/main/resources/application-test.properties b/api/src/main/resources/application-test.properties new file mode 100644 index 00000000..4d7749e8 --- /dev/null +++ b/api/src/main/resources/application-test.properties @@ -0,0 +1 @@ +sentry.enabled=false \ No newline at end of file diff --git a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java index e925a145..4d5a7bfa 100644 --- a/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java +++ b/api/src/test/java/pro/beerpong/api/control/MatchControllerTest.java @@ -10,6 +10,7 @@ import pro.beerpong.api.TestUtils; import pro.beerpong.api.model.dto.*; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; @@ -1682,7 +1683,7 @@ public void matches_findById_invalidArgs() { requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_FOUND); response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup1.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + match.getId(), MatchDto.class); - requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_OF_GROUP); + requestUtils.assertFailure(response, ErrorCodes.MATCH_GROUP_OR_SEASON_ID_DONT_MATCH); var seasonDto = new SeasonCreateDto(); seasonDto.setOldSeasonName("testing"); @@ -1695,7 +1696,7 @@ public void matches_findById_invalidArgs() { var newSeason = requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId(), MatchDto.class); - requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_OF_GROUP); + requestUtils.assertFailure(response, ErrorCodes.MATCH_GROUP_OR_SEASON_ID_DONT_MATCH); } @Test @@ -1997,7 +1998,7 @@ public void matches_overviewById_invalidArgs() { requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_FOUND); response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup1.getId() + "/seasons/" + prerequisiteGroup.getActiveSeason().getId() + "/matches/" + match.getId() + "/overview", MatchOverviewDto.class); - requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_OF_GROUP); + requestUtils.assertFailure(response, ErrorCodes.MATCH_GROUP_OR_SEASON_ID_DONT_MATCH); var seasonDto = new SeasonCreateDto(); seasonDto.setOldSeasonName("testing"); @@ -2010,7 +2011,7 @@ public void matches_overviewById_invalidArgs() { var newSeason = requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); response = requestUtils.performGet(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + match.getId() + "/overview", MatchOverviewDto.class); - requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_OF_GROUP); + requestUtils.assertFailure(response, ErrorCodes.MATCH_GROUP_OR_SEASON_ID_DONT_MATCH); } @Test @@ -2068,6 +2069,7 @@ public void matches_update_success() { assertEquals(oldMatch, fetched); matchDto = buildDto( + fetched, buildTeam( buildMember( player1.getId(), @@ -2393,6 +2395,7 @@ public void matches_update_nonUniquePlayers() { // test non unique players in same team matchDto = buildDto( + match, buildTeam( buildMember(player1.getId()), buildMember(player2.getId()) @@ -2408,6 +2411,7 @@ public void matches_update_nonUniquePlayers() { // test non unique players in different teams matchDto = buildDto( + match, buildTeam( buildMember(player1.getId()), buildMember(player3.getId()) @@ -2477,6 +2481,7 @@ public void matches_update_invalidFinishMove() { // test too many finish moves in different teams matchDto = buildDto( + match, buildTeam( buildMember( player1.getId(), @@ -2504,6 +2509,7 @@ public void matches_update_invalidFinishMove() { // test too many finish moves in same team matchDto = buildDto( + match, buildTeam( buildMember( player1.getId(), @@ -2531,6 +2537,7 @@ public void matches_update_invalidFinishMove() { // test no finish move matchDto = buildDto( + match, buildTeam( buildMember( player1.getId(), @@ -2558,6 +2565,7 @@ public void matches_update_invalidFinishMove() { // test finish move amount!=1 matchDto = buildDto( + match, buildTeam( buildMember( player1.getId(), @@ -2585,6 +2593,7 @@ public void matches_update_invalidFinishMove() { // test finish move amount!=1 matchDto = buildDto( + match, buildTeam( buildMember( player1.getId(), @@ -2701,6 +2710,7 @@ public void matches_update_invalidPlayers() { // test invalid player id matchDto = buildDto( + match, buildTeam( buildMember(newPlayer1.getId()), buildMember("someIdThatNotExists") @@ -2715,6 +2725,7 @@ public void matches_update_invalidPlayers() { // test invalid player id matchDto = buildDto( + match, buildTeam( buildMember("someIdThatNotExists") ), @@ -2729,6 +2740,7 @@ public void matches_update_invalidPlayers() { // test player from other season matchDto = buildDto( + match, buildTeam( buildMember(newPlayer1.getId()), buildMember(oldPlayer1.getId()) @@ -2743,6 +2755,7 @@ public void matches_update_invalidPlayers() { // test player from other season matchDto = buildDto( + match, buildTeam( buildMember(newPlayer1.getId()), buildMember(newPlayer2.getId()) @@ -2757,6 +2770,7 @@ public void matches_update_invalidPlayers() { // test player from other group matchDto = buildDto( + match, buildTeam( buildMember(newPlayer1.getId()), buildMember(newPlayer2.getId()) @@ -2771,6 +2785,7 @@ public void matches_update_invalidPlayers() { // test player from other group matchDto = buildDto( + match, buildTeam( buildMember(newPlayer1.getId()), buildMember(otherPlayer1.getId()) @@ -2876,6 +2891,7 @@ public void matches_update_invalidMoves() { // test invalid move id matchDto = buildDto( + match, buildTeam( buildMember( player1.getId(), @@ -2899,6 +2915,7 @@ public void matches_update_invalidMoves() { // test invalid move id matchDto = buildDto( + match, buildTeam( buildMember( player1.getId(), @@ -2923,6 +2940,7 @@ public void matches_update_invalidMoves() { // test move from other season matchDto = buildDto( + match, buildTeam( buildMember( player1.getId(), @@ -2947,6 +2965,7 @@ public void matches_update_invalidMoves() { // test move from other season matchDto = buildDto( + match, buildTeam( buildMember( player1.getId(), @@ -2971,6 +2990,7 @@ public void matches_update_invalidMoves() { // test finish move from other season matchDto = buildDto( + match, buildTeam( buildMember( player1.getId(), @@ -2995,6 +3015,7 @@ public void matches_update_invalidMoves() { // test move from other season matchDto = buildDto( + match, buildTeam( buildMember( player1.getId(), @@ -3018,6 +3039,7 @@ public void matches_update_invalidMoves() { // test move from other group matchDto = buildDto( + match, buildTeam( buildMember( player1.getId(), @@ -3042,6 +3064,7 @@ public void matches_update_invalidMoves() { // test move from other group matchDto = buildDto( + match, buildTeam( buildMember( player1.getId(), @@ -3066,6 +3089,7 @@ public void matches_update_invalidMoves() { // test finish move from other group matchDto = buildDto( + match, buildTeam( buildMember( player1.getId(), @@ -3090,6 +3114,7 @@ public void matches_update_invalidMoves() { // test move from other group matchDto = buildDto( + match, buildTeam( buildMember( player1.getId(), @@ -3269,7 +3294,7 @@ public void matches_delete_invalidMatch() { var newSeason = requestUtils.assertSuccess(newSeasonResponse, SeasonDto.class); response = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + oldMatch.getId(), null, String.class); - requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_OF_GROUP); + requestUtils.assertFailure(response, ErrorCodes.MATCH_GROUP_OR_SEASON_ID_DONT_MATCH); // test invalid match id (other group than provided) var prerequisiteGroup1 = testUtils.createTestGroup(port); @@ -3303,12 +3328,24 @@ public void matches_delete_invalidMatch() { var otherGroupMatch = requestUtils.assertSuccess(matchResponse, MatchDto.class); response = requestUtils.performDelete(port, "/groups/" + prerequisiteGroup.getId() + "/seasons/" + newSeason.getId() + "/matches/" + otherGroupMatch.getId(), null, String.class); - requestUtils.assertFailure(response, ErrorCodes.MATCH_NOT_OF_GROUP); + requestUtils.assertFailure(response, ErrorCodes.MATCH_GROUP_OR_SEASON_ID_DONT_MATCH); } private MatchCreateDto buildDto(TeamCreateDto... teams) { + return buildDto(null, teams); + } + + private MatchCreateDto buildDto(@Nullable MatchDto existing, TeamCreateDto... teams) { + var teamList = List.of(teams); + + if (existing != null) { + for (int i = 0; i < teams.length; i++) { + teamList.get(i).setExistingTeamId(existing.getTeams().get(i).getId()); + } + } + var dto = new MatchCreateDto(); - dto.setTeams(List.of(teams)); + dto.setTeams(teamList); return dto; } From aa51535167a0f8d939760fc56083446fad9141d0 Mon Sep 17 00:00:00 2001 From: Thies Date: Wed, 24 Sep 2025 11:30:47 +0200 Subject: [PATCH 85/90] disabled elo test in normal test runtime --- api/src/test/java/pro/beerpong/api/util/EloTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/src/test/java/pro/beerpong/api/util/EloTest.java b/api/src/test/java/pro/beerpong/api/util/EloTest.java index b6d8a565..0d27dd91 100644 --- a/api/src/test/java/pro/beerpong/api/util/EloTest.java +++ b/api/src/test/java/pro/beerpong/api/util/EloTest.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.stream.Collectors; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import com.google.api.client.util.Lists; @@ -27,6 +28,7 @@ public class EloTest { private static final Gson GSON = new GsonBuilder().create(); @Test + @Disabled public void testElo() { var classLoader = getClass().getClassLoader(); From 6daa2265489271f2476d79575a1a28cf1f7bcd4e Mon Sep 17 00:00:00 2001 From: Linus Bolls Date: Fri, 26 Sep 2025 14:14:32 +0200 Subject: [PATCH 86/90] feat(frontend): accounts --- docker/docker-compose-dev.yml | 4 +- mobile-app/api/calls/groupHooks.ts | 31 +++ mobile-app/api/calls/matchHooks.ts | 1 + mobile-app/api/propHooks/groupPropHooks.ts | 35 +++- .../api/realtime/useRealtimeConnection.ts | 29 +-- mobile-app/api/utils/create-api.tsx | 23 ++- mobile-app/app/(tabs)/_layout.tsx | 7 - mobile-app/app/_layout.tsx | 10 + mobile-app/app/auth/useAuth.ts | 194 ++++++++++++++++++ mobile-app/app/createGroupSetGame.tsx | 10 +- mobile-app/app/debugLog.tsx | 4 +- mobile-app/app/deviceStorage.ts | 45 ++++ mobile-app/app/joinGroup.tsx | 14 +- mobile-app/components/screens/Sidebar.tsx | 15 +- mobile-app/openapi/openapi.d.ts | 11 + mobile-app/zustand/group/stateGroupStore.ts | 148 +++++-------- 16 files changed, 433 insertions(+), 148 deletions(-) create mode 100644 mobile-app/app/auth/useAuth.ts create mode 100644 mobile-app/app/deviceStorage.ts diff --git a/docker/docker-compose-dev.yml b/docker/docker-compose-dev.yml index b4a82536..748435a3 100644 --- a/docker/docker-compose-dev.yml +++ b/docker/docker-compose-dev.yml @@ -5,6 +5,7 @@ services: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB_NAME} + POSTGRES_PORT: ${POSTGRES_PORT} volumes: - postgres:/var/lib/postgresql/data networks: @@ -17,7 +18,7 @@ services: environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_URL: jdbc:postgresql://${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB_NAME} + POSTGRES_URL: jdbc:postgresql://${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB_NAME}?user=${POSTGRES_USER}&password=${POSTGRES_PASSWORD} API_BASE_URL: ${API_BASE_URL} # Sentry BACKEND_SENTRY_DSN: ${BACKEND_SENTRY_DSN} @@ -27,6 +28,7 @@ services: AWS_ENDPOINT: ${AWS_ENDPOINT} AWS_ACCESS_KEY: ${AWS_ACCESS_KEY} AWS_SECRET_KEY: ${AWS_SECRET_KEY} + JWT_SECRET: ${JWT_SECRET} networks: - backend volumes: diff --git a/mobile-app/api/calls/groupHooks.ts b/mobile-app/api/calls/groupHooks.ts index ff6ebcb6..3a11c83f 100644 --- a/mobile-app/api/calls/groupHooks.ts +++ b/mobile-app/api/calls/groupHooks.ts @@ -36,6 +36,7 @@ export const useGroupQuery = (id: ApiId | null) => { export const useJoinGroupMutation = () => { const { api } = useApi(); + return useMutation< Paths.FindGroupByInviteCode.Responses.$200 | null, Error, @@ -43,14 +44,44 @@ export const useJoinGroupMutation = () => { >({ mutationFn: async (inviteCode) => { const res = await (await api).findGroupByInviteCode({ inviteCode }); + + if (res.data.data) { + await (await api).joinGroup({ id: res.data.data.id! }, {}); + } return res?.data; }, onError: captureMutationErr('joinGroup'), }); }; +export const useLeaveGroupMutation = () => { + const { api } = useApi(); + + return useMutation({ + mutationFn: async (id) => { + const res = await (await api).leaveGroup({ id }); + + return res?.data; + }, + }); +}; + +export const useGetMyGroupsQuery = () => { + const { api } = useApi(); + + return useQuery({ + queryFn: async () => { + const res = await (await api).findUserGroups(); + + return res?.data; + }, + queryKey: [QK.group, 'myGroups'], + }); +}; + export const useCreateGroupMutation = () => { const { api } = useApi(); + return useMutation< Paths.CreateGroup.Responses.$200 | null, Error, diff --git a/mobile-app/api/calls/matchHooks.ts b/mobile-app/api/calls/matchHooks.ts index d706b486..88dd08ea 100644 --- a/mobile-app/api/calls/matchHooks.ts +++ b/mobile-app/api/calls/matchHooks.ts @@ -23,6 +23,7 @@ export const useMatchQuery = ( if (!groupId || !seasonId || !matchId) { return null; } + const res = await ( await api ).getMatchById({ groupId, seasonId, id: matchId }); diff --git a/mobile-app/api/propHooks/groupPropHooks.ts b/mobile-app/api/propHooks/groupPropHooks.ts index 245794b9..fd3f4e2a 100644 --- a/mobile-app/api/propHooks/groupPropHooks.ts +++ b/mobile-app/api/propHooks/groupPropHooks.ts @@ -1,3 +1,4 @@ +import { useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'expo-router'; import { @@ -17,11 +18,13 @@ import { launchImageLibrary } from '@/utils/fileUpload'; import { ConsoleLogger } from '@/utils/logging'; import { useGroupStore } from '@/zustand/group/stateGroupStore'; +import { QK } from '../utils/reactQuery'; + export const useGroupSettingsProps = (): ScreenState => { const router = useRouter(); const { groupId, group } = useGroup(); - const { removeGroup } = useGroupStore(); + const { leaveGroupMutation } = useGroupStore(); const seasonsQuery = useAllSeasonsQuery(groupId); @@ -72,19 +75,31 @@ export const useGroupSettingsProps = (): ScreenState => { } } - function onLeaveGroup() { + const queryClient = useQueryClient(); + + async function onLeaveGroup() { if (!groupId) return; - // TODO: i can't get this to actually show up - setTimeout( - () => showYouLeftGroupToast(group.data?.name ?? 'Unknown Group'), - 3000 - ); + try { + // TODO: i can't get this to actually show up + setTimeout( + () => + showYouLeftGroupToast(group.data?.name ?? 'Unknown Group'), + 3000 + ); - removeGroup(groupId); + await leaveGroupMutation.mutateAsync(groupId); - router.dismissAll(); - router.replace('/'); + await queryClient.invalidateQueries({ + queryKey: [QK.group, 'myGroups'], + }); + + router.dismissAll(); + router.replace('/'); + } catch (err) { + ConsoleLogger.error('failed to leave group:', err); + showErrorToast('Failed to leave group.'); + } } const props: GroupSettingsProps | null = data?.data diff --git a/mobile-app/api/realtime/useRealtimeConnection.ts b/mobile-app/api/realtime/useRealtimeConnection.ts index 72d22ca5..893cb8af 100644 --- a/mobile-app/api/realtime/useRealtimeConnection.ts +++ b/mobile-app/api/realtime/useRealtimeConnection.ts @@ -10,16 +10,13 @@ import { } from '@/api/utils/reactQuery'; import { Logs } from '@/utils/logging'; import { useLogging } from '@/utils/useLogging'; -import { useGroupStore } from '@/zustand/group/stateGroupStore'; import { RealtimeClient, RealtimeEventHandler } from '.'; export function useRealtimeConnection() { - const { groupIds } = useGroupStore(); - const qc = useQueryClient(); - const client = useRef(new RealtimeClient(env.realtimeBaseUrl, groupIds)); + const client = useRef(null); const { writeLog } = useLogging(); @@ -28,7 +25,11 @@ export function useRealtimeConnection() { } const { invalidateLeaderboard } = useQueryInvalidation(); - const hoher: RealtimeEventHandler = (e) => { + const onRealtimeEvent: RealtimeEventHandler = (e) => { + if (!client.current) return; + + console.log('received event'); + switch (e.eventType) { case 'GROUPS': client.current.logger.info('refetching groups'); @@ -190,23 +191,25 @@ export function useRealtimeConnection() { if (client.current) { client.current.logger.addEventListener('*', writeLogs); - client.current.on.event(hoher); + client.current.on.event(onRealtimeEvent); - return () => - client.current.logger.removeEventListener('*', writeLogs); + return () => { + if (client.current) { + client.current.logger.removeEventListener('*', writeLogs); + } + }; } }, [client.current]); - useEffect(() => { - client.current.subscribeToGroups(groupIds); - }, [groupIds]); - function refetchGroup(groupId: string) { qc.invalidateQueries({ queryKey: [QK.group, groupId], exact: true, }); } + function connectRealtime(groupIds: string[]) { + client.current = new RealtimeClient(env.realtimeBaseUrl, groupIds); + } - return client.current; + return { realtime: client.current, connectRealtime }; } diff --git a/mobile-app/api/utils/create-api.tsx b/mobile-app/api/utils/create-api.tsx index c580bbca..0d2a8921 100644 --- a/mobile-app/api/utils/create-api.tsx +++ b/mobile-app/api/utils/create-api.tsx @@ -12,11 +12,12 @@ import { env } from '@/api/env'; import beerpongDefinition from '@/api/generated/openapi.json'; import { RealtimeClient } from '@/api/realtime'; import { useRealtimeConnection } from '@/api/realtime/useRealtimeConnection'; +import { useAuth } from '@/app/auth/useAuth'; import { Client as BeerPongClient } from '@/openapi/openapi'; import { useLogging } from '@/utils/useLogging'; type ApiContextType = { - realtime: RealtimeClient; + realtime: RealtimeClient | null; api: Promise; isLoading: boolean; error: Error | null; @@ -32,11 +33,22 @@ const openApi = new OpenAPIClientAxios({ }); export function ApiProvider({ children }: { children: ReactNode }) { + const auth = useAuth(); + const api = useRef( new Promise(async (resolve) => { - const awaitedApi = await openApi.getClient(); + const client = await openApi.init(); + + const { accessToken } = await auth.getAccessToken(client); - const client = await openApi.init(); + client.interceptors.request.use((config) => { + config.headers.Authorization = 'Bearer ' + accessToken; + console.log( + 'using request interceptor:', + config.headers.Authorization + ); + return config; + }); client.interceptors.response.use( (res) => { @@ -84,11 +96,12 @@ export function ApiProvider({ children }: { children: ReactNode }) { return Promise.reject(err); } ); - resolve(awaitedApi); + // without this, the auth interceptor will not get fired + setTimeout(() => resolve(client), 0); }) ); - const realtime = useRealtimeConnection(); + const { realtime } = useRealtimeConnection(); const { writeLog } = useLogging(); // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/mobile-app/app/(tabs)/_layout.tsx b/mobile-app/app/(tabs)/_layout.tsx index d6bc49c5..37510f7e 100644 --- a/mobile-app/app/(tabs)/_layout.tsx +++ b/mobile-app/app/(tabs)/_layout.tsx @@ -26,8 +26,6 @@ const GroupsButton = () => { }; export default function TabLayout() { - const nav = useNavigation(); - const { selectedGroupId } = useGroupStore(); const selectedGroup = useGroupQuery(selectedGroupId); @@ -39,11 +37,6 @@ export default function TabLayout() { const headerTitleIfGroupIsLoading = ''; const headerTitleIfGroupCantBeFound = ''; - if (!selectedGroupId) { - nav.navigate('onboarding'); - return; - } - const groupHeader = { ...navStyles, headerTitle: selectedGroup.isLoading diff --git a/mobile-app/app/_layout.tsx b/mobile-app/app/_layout.tsx index c8554484..c3a7208e 100644 --- a/mobile-app/app/_layout.tsx +++ b/mobile-app/app/_layout.tsx @@ -16,6 +16,7 @@ import 'react-native-reanimated'; import { RootSiblingParent } from 'react-native-root-siblings'; import { env } from '@/api/env'; +import { useRealtimeConnection } from '@/api/realtime/useRealtimeConnection'; import { ApiProvider } from '@/api/utils/create-api'; import { createQueryClient, persister } from '@/api/utils/query-client'; import { useRefetchEverythingOnWifiReconnect } from '@/api/utils/useRefetchEverythingOnWifiReconnect'; @@ -25,6 +26,7 @@ import { Sidebar } from '@/components/screens/Sidebar'; import { useColorScheme } from '@/hooks/useColorScheme'; import { useTheme } from '@/theme'; import { LoggingProvider } from '@/utils/useLogging'; +import { useGroupStore } from '@/zustand/group/stateGroupStore'; import { ScopePickerProvider } from '@/zustand/useScopePicker'; // https://sentry.io is a error reporting SaaS we use to remotely track production issues @@ -36,6 +38,14 @@ const Drawer = createDrawerNavigator(); SplashScreen.preventAutoHideAsync(); function Everything() { + const { connectRealtime } = useRealtimeConnection(); + + const { groupIds } = useGroupStore(); + + useEffect(() => { + connectRealtime(groupIds); + }, [groupIds]); + const modalStyles = useModalStyles(); return ( diff --git a/mobile-app/app/auth/useAuth.ts b/mobile-app/app/auth/useAuth.ts new file mode 100644 index 00000000..2297d10d --- /dev/null +++ b/mobile-app/app/auth/useAuth.ts @@ -0,0 +1,194 @@ +import * as Sentry from '@sentry/react-native'; +import * as Application from 'expo-application'; +// import * as Notifications from 'expo-notifications'; +// import * as Permissions from 'expo-permissions'; +import jwt, { JWTBody, JWTDefaultBody } from 'expo-jwt'; +import { useState } from 'react'; +import { Platform } from 'react-native'; + +import { versusDeviceStorage } from '@/app/deviceStorage'; +import { Client as BeerPongClient } from '@/openapi/openapi'; +import { ConsoleLogger } from '@/utils/logging'; + +const REGENERATE_ACCESS_TOKEN_WHEN_ITS_ABOUT_TO_EXPIRE_IN_SECONDS = 10; + +/** + * should be unique for every device, even across reinstalls + */ +async function getInstallationId() { + // important: the platform-specific Application methods throw an error if they're called on the wrong platform! + const installationId = + Platform.OS === 'ios' + ? await Application.getIosIdForVendorAsync() + : Application.getAndroidId(); + + if (installationId == null) { + throw new Error('failed to get installation id'); + } + return installationId; +} + +// async function registerForPushNotifications() { +// const { status: existingStatus } = await Permissions.getAsync( +// Permissions.NOTIFICATIONS +// ); +// let finalStatus = existingStatus; + +// if (existingStatus !== 'granted') { +// const { status } = await Permissions.askAsync( +// Permissions.NOTIFICATIONS +// ); +// finalStatus = status; +// } +// if (finalStatus !== 'granted') return null; + +// const tokenData = await Notifications.getExpoPushTokenAsync(); + +// const pushNotificationToken = tokenData.data; // e.g. “ExponentPushToken[xxxxxxxxxxxxxx]” +// } + +// export function usePushNotifications() { +// const registerForPushNotificationsMutation = +// useRegisterForPushNotificationsMutation(); + +// return { +// registerForPushNotifications: async function () { +// const expoToken = await registerForPushNotifications(); +// if (expoToken) { +// await registerForPushNotificationsMutation.mutateAsync( +// expoToken +// ); +// } +// }, +// }; +// } + +async function getRefreshToken(api: BeerPongClient): Promise { + try { + const existingRefreshToken = + await versusDeviceStorage.getRefreshToken(); + + if (existingRefreshToken) return existingRefreshToken; + + const installationType = Platform.OS === 'ios' ? 'IOS' : 'ANDROID'; + + const deviceId = await getInstallationId(); + + const signupRes = await api.signup({}, { installationType, deviceId }); + + const refreshToken = signupRes.data.data?.token; + + if (!refreshToken) { + throw new Error('response.data.data.token is null'); + } + await versusDeviceStorage.setRefreshToken(refreshToken); + + return refreshToken; + } catch (err) { + ConsoleLogger.error('Failed to get refresh token:', err); + + (err as Error).message = + 'Failed to get refresh token: ' + + (err instanceof Error ? err.message : 'Unknown error'); + + throw err; + } +} + +interface GetAccessTokenResult { + accessToken: string; + accessTokenPayload: JWTBody; +} + +async function getAccessToken( + api: BeerPongClient +): Promise { + let refreshToken: string | null = null; + try { + refreshToken = await getRefreshToken(api); + + const accessTokenRes = await api.refreshAuth({}, { refreshToken }); + + const accessToken = accessTokenRes.data.data?.token; + + if (!accessToken) { + throw new Error('response.data.data.token is null'); + } + try { + const accessTokenPayload = jwt.decode(accessToken, null); + + return { accessToken, accessTokenPayload }; + } catch (err) { + throw new Error( + 'Failed to decode: ' + + (err instanceof Error ? err.message : 'Unknown error') + ); + } + } catch (err) { + ConsoleLogger.error( + 'Failed to get access token:', + err, + 'with refreshToken', + refreshToken + ); + (err as Error).message = + 'Failed to get access token: ' + + (err instanceof Error ? err.message : 'Unknown error'); + + Sentry.captureException(err, { + extra: { + refreshToken, + installationId: getInstallationId(), + }, + }); + throw err; + } +} + +export function useAuth() { + const [fetchingPromise, setFetchingPromise] = + useState | null>(null); + const [accessToken, setAccessToken] = useState( + null + ); + + return { + getAccessToken: async (api: BeerPongClient) => { + const isAboutToExpire = + (accessToken?.accessTokenPayload.exp ?? 0) < + Date.now() - + REGENERATE_ACCESS_TOKEN_WHEN_ITS_ABOUT_TO_EXPIRE_IN_SECONDS * + 1000; + + if (accessToken && !isAboutToExpire) { + return accessToken; + } + if (fetchingPromise) return await fetchingPromise; + + try { + ConsoleLogger.info('refreshing access token'); + const promise = getAccessToken(api); + + setFetchingPromise(promise); + + const value = await promise; + + ConsoleLogger.info('refreshed access token'); + + setAccessToken(value); + + return value; + } catch (err) { + ConsoleLogger.error( + 'Failed to resolve access token promise:', + err + ); + setFetchingPromise(null); + (err as Error).message = + 'Failed to retrieve access token: ' + + (err instanceof Error ? err.message : 'Unknown error'); + throw err; + } + }, + }; +} diff --git a/mobile-app/app/createGroupSetGame.tsx b/mobile-app/app/createGroupSetGame.tsx index cf0827e8..1d5db12b 100644 --- a/mobile-app/app/createGroupSetGame.tsx +++ b/mobile-app/app/createGroupSetGame.tsx @@ -1,3 +1,4 @@ +import { useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'expo-router'; import React from 'react'; @@ -5,21 +6,21 @@ import { useCreateGroupMutation, useGroupPresetsQuery, } from '@/api/calls/groupHooks'; +import { QK } from '@/api/utils/reactQuery'; import LoadingScreen from '@/components/LoadingScreen'; import { CreateGroupSetGame } from '@/components/screens/CreateGroupSetGame'; import { showErrorToast, showSuccessToast } from '@/toast'; import { ConsoleLogger } from '@/utils/logging'; import { useCreateGroupStore } from '@/zustand/group/stateCreateGroupStore'; -import { useGroupStore } from '@/zustand/group/stateGroupStore'; import { useMatchDraftStore } from '@/zustand/matchDraftStore'; export default function Page() { const router = useRouter(); const { members, name } = useCreateGroupStore(); const createGroupMutation = useCreateGroupMutation(); - const { addGroup } = useGroupStore(); const presetsQuery = useGroupPresetsQuery(); const matchDraft = useMatchDraftStore(); + const queryClient = useQueryClient(); const presets = presetsQuery.data?.data?.map((i) => ({ @@ -46,8 +47,9 @@ export default function Page() { if (!data?.data?.id) { throw new Error('invalid create group response'); } - addGroup(data.data.id); - + await queryClient.invalidateQueries({ + queryKey: [QK.group, 'myGroups'], + }); matchDraft.actions.clear(); showSuccessToast(`You created "${name}"`); diff --git a/mobile-app/app/debugLog.tsx b/mobile-app/app/debugLog.tsx index 1b72b75a..b50bb1bf 100644 --- a/mobile-app/app/debugLog.tsx +++ b/mobile-app/app/debugLog.tsx @@ -41,8 +41,8 @@ export default function Page() { useEffect(() => { // we need to keep this in state because `realtime` is a ref and will not cause a rerender if it changes, // so the indicator could be misleading - setIsRealtimeOpen(realtime.isOpen); - }, [realtime.isOpen]); + setIsRealtimeOpen(realtime?.isOpen ?? false); + }, [realtime?.isOpen]); const theme = useTheme(); diff --git a/mobile-app/app/deviceStorage.ts b/mobile-app/app/deviceStorage.ts new file mode 100644 index 00000000..f55c3bcd --- /dev/null +++ b/mobile-app/app/deviceStorage.ts @@ -0,0 +1,45 @@ +import * as SecureStore from 'expo-secure-store'; + +import { ScopedLogger } from '@/utils/logging'; + +class VersusDeviceStorage { + private store = SecureStore; + + private logger = new ScopedLogger('versus-device-storage'); + + private KEYCHAIN_SERVICE = 'Versus'; + + private REFRESH_TOKEN_KEY = 'refresh-token'; + + private async getItemAsync(key: string) { + try { + return this.store.getItemAsync(key, { + keychainAccessible: 0, + keychainService: this.KEYCHAIN_SERVICE, + }); + } catch (err) { + this.logger.error(`Failed to get item with key "${key}":`, err); + } + } + private async setItemAsync(key: string, value: string) { + try { + return this.store.setItemAsync(key, value, { + keychainAccessible: 0, + keychainService: this.KEYCHAIN_SERVICE, + }); + } catch (err) { + this.logger.error( + `Failed to set item with key "${key}": to value "${value}"`, + err + ); + } + } + + public async getRefreshToken() { + return this.getItemAsync(this.REFRESH_TOKEN_KEY); + } + public async setRefreshToken(token: string) { + return this.setItemAsync(this.REFRESH_TOKEN_KEY, token); + } +} +export const versusDeviceStorage = new VersusDeviceStorage(); diff --git a/mobile-app/app/joinGroup.tsx b/mobile-app/app/joinGroup.tsx index 4669b0e5..a30ae0cb 100644 --- a/mobile-app/app/joinGroup.tsx +++ b/mobile-app/app/joinGroup.tsx @@ -1,8 +1,9 @@ +import { useQueryClient } from '@tanstack/react-query'; import { AxiosError } from 'axios'; import { useRouter } from 'expo-router'; import React from 'react'; -import { useJoinGroupMutation } from '@/api/calls/groupHooks'; +import { QK } from '@/api/utils/reactQuery'; import JoinGroup from '@/components/screens/JoinGroup'; import { showSuccessToast } from '@/toast'; import { ConsoleLogger } from '@/utils/logging'; @@ -10,18 +11,23 @@ import { useGroupStore } from '@/zustand/group/stateGroupStore'; export default function Page() { const router = useRouter(); - const joinGroupMutation = useJoinGroupMutation(); + const { joinGroupMutation, selectGroup } = useGroupStore(); - const { addGroup, selectGroup } = useGroupStore(); + const queryClient = useQueryClient(); async function onSubmit(code: string) { try { const data = await joinGroupMutation.mutateAsync(code); if (data?.data?.id) { - addGroup(data.data.id); selectGroup(data.data.id); + await queryClient.invalidateQueries({ + queryKey: [QK.group, 'myGroups'], + }); + + nav.navigate('index'); + showSuccessToast(`You joined "${data.data.name}"`); router.dismissAll(); router.replace('/'); diff --git a/mobile-app/components/screens/Sidebar.tsx b/mobile-app/components/screens/Sidebar.tsx index e94ae338..7ca21914 100644 --- a/mobile-app/components/screens/Sidebar.tsx +++ b/mobile-app/components/screens/Sidebar.tsx @@ -1,4 +1,5 @@ import { DrawerContentComponentProps } from '@react-navigation/drawer'; +import { useQueryClient } from '@tanstack/react-query'; import { AxiosError } from 'axios'; import { Link } from 'expo-router'; import React, { useState } from 'react'; @@ -15,6 +16,7 @@ import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import { useGroupQuery } from '@/api/calls/groupHooks'; import { env } from '@/api/env'; +import { QK } from '@/api/utils/reactQuery'; import { useNavigation } from '@/app/navigation/useNavigation'; import ConfirmationModal from '@/components/ConfirmationModal'; import MenuItem from '@/components/Menu/MenuItem'; @@ -181,7 +183,7 @@ export interface SidebarGroup { // eslint-disable-next-line no-empty-pattern export function Sidebar(props: DrawerContentComponentProps) { - const { groupIds, selectedGroupId, selectGroup, removeGroup } = + const { groupIds, selectedGroupId, selectGroup, leaveGroupMutation } = useGroupStore(); const nav = useNavigation(); @@ -198,6 +200,8 @@ export function Sidebar(props: DrawerContentComponentProps) { const theme = useTheme(); + const queryClient = useQueryClient(); + return ( { + onPress: async () => { if (groupIdToBeDeleted) { - removeGroup(groupIdToBeDeleted); + await leaveGroupMutation.mutateAsync( + groupIdToBeDeleted + ); + await queryClient.invalidateQueries({ + queryKey: [QK.group, 'myGroups'], + }); } setGroupIdToBeDeleted(null); }, diff --git a/mobile-app/openapi/openapi.d.ts b/mobile-app/openapi/openapi.d.ts index cac1051e..f186233e 100644 --- a/mobile-app/openapi/openapi.d.ts +++ b/mobile-app/openapi/openapi.d.ts @@ -32,6 +32,17 @@ declare namespace Components { token?: string; type?: 'ACCESS' | 'REFRESH'; } + export interface AuthRefreshDto { + refreshToken?: string; + } + export interface AuthSignupDto { + installationType?: 'IOS' | 'ANDROID'; + deviceId?: string; + } + export interface AuthTokenDto { + token?: string; + type?: 'ACCESS' | 'REFRESH'; + } export interface ErrorDetails { code?: string; description?: string; diff --git a/mobile-app/zustand/group/stateGroupStore.ts b/mobile-app/zustand/group/stateGroupStore.ts index 3166699d..d06879b5 100644 --- a/mobile-app/zustand/group/stateGroupStore.ts +++ b/mobile-app/zustand/group/stateGroupStore.ts @@ -1,113 +1,63 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; +import { useEffect, useMemo } from 'react'; import { create } from 'zustand'; -import { createJSONStorage, persist } from 'zustand/middleware'; -interface GroupState { - // State - groupIds: string[]; - selectedGroupId: string | null; - isLoading: boolean; - error: string | null; - lastUpdated: number | null; - - // Basic CRUD - addGroup: (groupId: string) => void; - removeGroup: (groupId: string) => void; - clearGroups: () => void; +import { + useGetMyGroupsQuery, + useJoinGroupMutation, + useLeaveGroupMutation, +} from '@/api/calls/groupHooks'; +import { useNavigation } from '@/app/navigation/useNavigation'; - // Selection +const useStore = create<{ + selectedGroupId: string | null; selectGroup: (groupId: string | null) => void; - getSelectedGroupId: () => string | null; +}>()((set) => ({ + selectedGroupId: null, - // Utility - setError: (error: string | null) => void; - setLoading: (isLoading: boolean) => void; -} + selectGroup: (selectedGroupId) => { + set({ selectedGroupId }); + }, +})); -export const useGroupStore = create()( - persist( - (set, get) => ({ - // Initial state - groupIds: [], - selectedGroupId: null, - isLoading: false, - error: null, - lastUpdated: null, +export function useGroupStore() { + const store = useStore(); - // Initialize the store - initialize: async () => { - set({ isLoading: false, error: null }); - }, + const myGroupsQuery = useGetMyGroupsQuery(); - // Add a new group - addGroup: (groupId) => { - set((state) => ({ - groupIds: [...new Set([...state.groupIds, groupId])], - selectedGroupId: groupId, - lastUpdated: Date.now(), - error: null, - })); - }, + const leaveGroupMutation = useLeaveGroupMutation(); - // Remove a group - removeGroup: (groupId) => { - set((state) => ({ - groupIds: state.groupIds.filter( - (group) => group !== groupId - ), - selectedGroupId: - state.selectedGroupId === groupId - ? (state.groupIds.find((i) => i !== groupId) ?? - null) - : state.selectedGroupId, - lastUpdated: Date.now(), - error: null, - })); - }, + const joinGroupMutation = useJoinGroupMutation(); - // Clear all groups - clearGroups: () => { - set({ - groupIds: [], - selectedGroupId: null, - lastUpdated: Date.now(), - error: null, - }); - }, + const groupIds = useMemo( + () => myGroupsQuery.data?.data?.map((g) => g.id!) ?? [], + [myGroupsQuery.data] + ); + const isLoadingGroups = myGroupsQuery.isLoading; - // Select a group - selectGroup: (groupId) => { - if ( - groupId === null || - get().groupIds.some((g) => g === groupId) - ) { - set({ selectedGroupId: groupId, error: null }); - } else { - set({ error: 'Invalid group selection' }); - } - }, + const nav = useNavigation(); - // Get selected group - getSelectedGroupId: () => { - const state = get(); - return ( - state.groupIds.find((g) => g === state.selectedGroupId) || - null - ); - }, + // if this changes we either left, created, or joined a group. + const groupsKey = myGroupsQuery.data?.data?.map((i) => i.id)?.join(); - // Utility functions - setError: (error) => set({ error }), - setLoading: (isLoading) => set({ isLoading }), - }), - { - name: 'group-storage', // name of the item in storage - storage: createJSONStorage(() => AsyncStorage), // AsyncStorage for React Native - partialize: (state) => ({ - // Only persist these fields - groupIds: state.groupIds, - selectedGroupId: state.selectedGroupId, - }), + useEffect(() => { + if (!store.selectedGroupId && myGroupsQuery.data) { + const groupId = myGroupsQuery.data.data?.[0]?.id ?? null; + if (groupId) { + store.selectGroup(myGroupsQuery.data.data?.[0]?.id ?? null); + } else { + nav.navigate('onboarding'); + } } - ) -); + }, [groupsKey]); + + return { + groupIds, + selectedGroupId: store.selectedGroupId, + selectGroup: store.selectGroup, + + myGroupsQuery, + joinGroupMutation, + leaveGroupMutation, + isLoadingGroups, + }; +} From 4dd7b8685b6c1b408dfc39b184bff801b6267b47 Mon Sep 17 00:00:00 2001 From: Linus Bolls Date: Fri, 26 Sep 2025 14:39:36 +0200 Subject: [PATCH 87/90] devops: force openapi action to rerun --- .github/workflows/generate-openapi.yaml | 2 +- api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/generate-openapi.yaml b/.github/workflows/generate-openapi.yaml index db0c5fd3..c6f19187 100644 --- a/.github/workflows/generate-openapi.yaml +++ b/.github/workflows/generate-openapi.yaml @@ -60,7 +60,7 @@ jobs: echo "Commit messages in range:" echo "$COMMIT_MESSAGES" - if echo "$COMMIT_MESSAGES" | grep -q "chore: update openapi types"; then + if echo "$COMMIT_MESSAGES" | grep -q "chore: update openapi types (TEMPORARILY DISABLE ACTION)"; then echo "has_bot_author=true" >> $GITHUB_OUTPUT echo "A recent commit contains the version bump message. Stopping the workflow." exit 0 # This will stop the workflow without failing it diff --git a/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java b/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java index 159dc636..4ed423fb 100644 --- a/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java +++ b/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java @@ -1,3 +1,4 @@ +// temp comment to force openapi action to rerun package pro.beerpong.api.auth; import io.jsonwebtoken.*; @@ -64,4 +65,3 @@ public Claims validateToken(String token, String type) { } } } - From 61df6d148706b2002e96d2a8788167e1f146a398 Mon Sep 17 00:00:00 2001 From: LinusBolls <82451500+LinusBolls@users.noreply.github.com> Date: Fri, 26 Sep 2025 12:41:46 +0000 Subject: [PATCH 88/90] chore: update openapi types --- mobile-app/api/generated/openapi.json | 273 +++++++++++++++++++++++--- mobile-app/openapi/openapi.d.ts | 19 +- 2 files changed, 249 insertions(+), 43 deletions(-) diff --git a/mobile-app/api/generated/openapi.json b/mobile-app/api/generated/openapi.json index 94454db0..ef521b0e 100644 --- a/mobile-app/api/generated/openapi.json +++ b/mobile-app/api/generated/openapi.json @@ -382,7 +382,7 @@ "schema": { "type": "string" } }, { - "name": "seasonId", + "name": "teamId", "in": "path", "required": true, "schema": { "type": "string" } @@ -394,7 +394,7 @@ "schema": { "type": "string" } }, { - "name": "teamId", + "name": "seasonId", "in": "path", "required": true, "schema": { "type": "string" } @@ -424,7 +424,7 @@ "schema": { "type": "string" } }, { - "name": "seasonId", + "name": "teamId", "in": "path", "required": true, "schema": { "type": "string" } @@ -436,7 +436,7 @@ "schema": { "type": "string" } }, { - "name": "teamId", + "name": "seasonId", "in": "path", "required": true, "schema": { "type": "string" } @@ -759,6 +759,58 @@ } } }, + "/groups/{id}/leave": { + "post": { + "tags": ["group-controller"], + "operationId": "leaveGroup", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseEnvelopeString" + } + } + } + } + } + } + }, + "/groups/{id}/join": { + "post": { + "tags": ["group-controller"], + "operationId": "joinGroup", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseEnvelopeString" + } + } + } + } + } + } + }, "/groups/{groupId}/seasons/{seasonId}/rule-moves": { "get": { "tags": ["rule-move-controller"], @@ -963,6 +1015,62 @@ } } }, + "/auth/signup": { + "post": { + "tags": ["auth-controller"], + "operationId": "signup", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthSignupDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseEnvelopeAuthTokenDto" + } + } + } + } + } + } + }, + "/auth/refresh": { + "post": { + "tags": ["auth-controller"], + "operationId": "refreshAuth", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthRefreshDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseEnvelopeAuthTokenDto" + } + } + } + } + } + } + }, "/healthcheck": { "get": { "tags": ["healthcheck-controller"], @@ -1159,6 +1267,24 @@ } } }, + "/groups/user": { + "get": { + "tags": ["group-controller"], + "operationId": "findUserGroups", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseEnvelopeListGroupDto" + } + } + } + } + } + } + }, "/group-presets": { "get": { "tags": ["group-presets-controller"], @@ -1287,10 +1413,15 @@ "id": { "type": "string" }, "name": { "type": "string" }, "inviteCode": { "type": "string" }, - "activeSeason": { "$ref": "#/components/schemas/Season" }, + "activeSeason": { + "$ref": "#/components/schemas/SeasonDto" + }, "wallpaperAsset": { "$ref": "#/components/schemas/AssetMetadataDto" }, + "createdBy": { + "$ref": "#/components/schemas/GroupMemberDto" + }, "createdAt": { "type": "string", "format": "date-time" }, "sportPreset": { "$ref": "#/components/schemas/GroupPreset" @@ -1301,6 +1432,15 @@ "numberOfSeasons": { "type": "integer", "format": "int32" } } }, + "GroupMemberDto": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "active": { "type": "boolean" }, + "groupId": { "type": "string" }, + "userId": { "type": "string" } + } + }, "GroupPreset": { "type": "object", "properties": { @@ -1309,6 +1449,15 @@ "imageUrl": { "type": "string" } } }, + "LocalTime": { + "type": "object", + "properties": { + "hour": { "type": "integer", "format": "int32" }, + "minute": { "type": "integer", "format": "int32" }, + "second": { "type": "integer", "format": "int32" }, + "nano": { "type": "integer", "format": "int32" } + } + }, "ResponseEnvelopeGroupDto": { "type": "object", "properties": { @@ -1318,7 +1467,7 @@ "error": { "$ref": "#/components/schemas/ErrorDetails" } } }, - "Season": { + "SeasonDto": { "type": "object", "properties": { "id": { "type": "string" }, @@ -1328,6 +1477,9 @@ "groupId": { "type": "string" }, "seasonSettings": { "$ref": "#/components/schemas/SeasonSettings" + }, + "createdBy": { + "$ref": "#/components/schemas/GroupMemberDto" } } }, @@ -1353,7 +1505,7 @@ "LAST_24_HOURS" ] }, - "wakeTimeHour": { "type": "integer", "format": "int32" } + "wakeTime": { "$ref": "#/components/schemas/LocalTime" } } }, "AssetCropDto": { @@ -1398,7 +1550,10 @@ "id": { "type": "string" }, "title": { "type": "string" }, "description": { "type": "string" }, - "season": { "$ref": "#/components/schemas/Season" } + "season": { "$ref": "#/components/schemas/SeasonDto" }, + "createdBy": { + "$ref": "#/components/schemas/GroupMemberDto" + } } }, "RuleMoveCreateDto": { @@ -1427,7 +1582,7 @@ "pointsForTeam": { "type": "integer", "format": "int32" }, "pointsForScorer": { "type": "integer", "format": "int32" }, "finishingMove": { "type": "boolean" }, - "season": { "$ref": "#/components/schemas/Season" } + "season": { "$ref": "#/components/schemas/SeasonDto" } } }, "MatchCreateDto": { @@ -1476,7 +1631,10 @@ "properties": { "id": { "type": "string" }, "date": { "type": "string", "format": "date-time" }, - "season": { "$ref": "#/components/schemas/Season" }, + "season": { "$ref": "#/components/schemas/SeasonDto" }, + "createdBy": { + "$ref": "#/components/schemas/GroupMemberDto" + }, "teams": { "type": "array", "items": { "$ref": "#/components/schemas/TeamDto" } @@ -1540,12 +1698,36 @@ "error": { "$ref": "#/components/schemas/ErrorDetails" } } }, + "SeasonSettingsDto": { + "type": "object", + "properties": { + "minMatchesToQualify": { + "type": "integer", + "format": "int32" + }, + "minTeamSize": { "type": "integer", "format": "int32" }, + "maxTeamSize": { "type": "integer", "format": "int32" }, + "rankingAlgorithm": { + "type": "string", + "enum": ["AVERAGE", "ELO"] + }, + "dailyLeaderboard": { + "type": "string", + "enum": [ + "RESET_AT_MIDNIGHT", + "WAKE_TIME", + "LAST_24_HOURS" + ] + }, + "wakeTime": { "type": "string" } + } + }, "SeasonUpdateDto": { "required": ["seasonSettings"], "type": "object", "properties": { "seasonSettings": { - "$ref": "#/components/schemas/SeasonSettings" + "$ref": "#/components/schemas/SeasonSettingsDto" } } }, @@ -1558,19 +1740,6 @@ "error": { "$ref": "#/components/schemas/ErrorDetails" } } }, - "SeasonDto": { - "type": "object", - "properties": { - "id": { "type": "string" }, - "name": { "type": "string" }, - "startDate": { "type": "string", "format": "date-time" }, - "endDate": { "type": "string", "format": "date-time" }, - "groupId": { "type": "string" }, - "seasonSettings": { - "$ref": "#/components/schemas/SeasonSettings" - } - } - }, "ProfileCreateDto": { "type": "object", "properties": { "name": { "type": "string" } } @@ -1583,7 +1752,10 @@ "avatarAsset": { "$ref": "#/components/schemas/AssetMetadataDto" }, - "groupId": { "type": "string" } + "groupId": { "type": "string" }, + "createdBy": { + "$ref": "#/components/schemas/GroupMemberDto" + } } }, "ResponseEnvelopeProfileDto": { @@ -1607,6 +1779,15 @@ } } }, + "ResponseEnvelopeString": { + "type": "object", + "properties": { + "status": { "type": "string", "enum": ["OK", "ERROR"] }, + "httpCode": { "type": "integer", "format": "int32" }, + "data": { "type": "string" }, + "error": { "$ref": "#/components/schemas/ErrorDetails" } + } + }, "ProfileCreatedDto": { "type": "object", "properties": { @@ -1616,6 +1797,9 @@ "$ref": "#/components/schemas/AssetMetadataDto" }, "groupId": { "type": "string" }, + "createdBy": { + "$ref": "#/components/schemas/GroupMemberDto" + }, "reactivated": { "type": "boolean" }, "lastActiveSeasonId": { "type": "string" } } @@ -1631,15 +1815,36 @@ "error": { "$ref": "#/components/schemas/ErrorDetails" } } }, - "ResponseEnvelopeString": { + "AuthSignupDto": { + "type": "object", + "properties": { + "installationType": { + "type": "string", + "enum": ["IOS", "ANDROID"] + }, + "deviceId": { "type": "string" } + } + }, + "AuthTokenDto": { + "type": "object", + "properties": { + "token": { "type": "string" }, + "type": { "type": "string", "enum": ["ACCESS", "REFRESH"] } + } + }, + "ResponseEnvelopeAuthTokenDto": { "type": "object", "properties": { "status": { "type": "string", "enum": ["OK", "ERROR"] }, "httpCode": { "type": "integer", "format": "int32" }, - "data": { "type": "string" }, + "data": { "$ref": "#/components/schemas/AuthTokenDto" }, "error": { "$ref": "#/components/schemas/ErrorDetails" } } }, + "AuthRefreshDto": { + "type": "object", + "properties": { "refreshToken": { "type": "string" } } + }, "ResponseEnvelopeListSeasonDto": { "type": "object", "properties": { @@ -1730,7 +1935,7 @@ "properties": { "id": { "type": "string" }, "date": { "type": "string", "format": "date-time" }, - "season": { "$ref": "#/components/schemas/Season" }, + "season": { "$ref": "#/components/schemas/SeasonDto" }, "blueTeam": { "$ref": "#/components/schemas/MatchOverviewTeamDto" }, @@ -1818,6 +2023,18 @@ "error": { "$ref": "#/components/schemas/ErrorDetails" } } }, + "ResponseEnvelopeListGroupDto": { + "type": "object", + "properties": { + "status": { "type": "string", "enum": ["OK", "ERROR"] }, + "httpCode": { "type": "integer", "format": "int32" }, + "data": { + "type": "array", + "items": { "$ref": "#/components/schemas/GroupDto" } + }, + "error": { "$ref": "#/components/schemas/ErrorDetails" } + } + }, "ResponseEnvelopeListGroupPreset": { "type": "object", "properties": { diff --git a/mobile-app/openapi/openapi.d.ts b/mobile-app/openapi/openapi.d.ts index f186233e..46d1595f 100644 --- a/mobile-app/openapi/openapi.d.ts +++ b/mobile-app/openapi/openapi.d.ts @@ -32,17 +32,6 @@ declare namespace Components { token?: string; type?: 'ACCESS' | 'REFRESH'; } - export interface AuthRefreshDto { - refreshToken?: string; - } - export interface AuthSignupDto { - installationType?: 'IOS' | 'ANDROID'; - deviceId?: string; - } - export interface AuthTokenDto { - token?: string; - type?: 'ACCESS' | 'REFRESH'; - } export interface ErrorDetails { code?: string; description?: string; @@ -465,9 +454,9 @@ declare namespace Paths { } export interface PathParameters { groupId: Parameters.GroupId; - seasonId: Parameters.SeasonId; - id: Parameters.Id; teamId: Parameters.TeamId; + id: Parameters.Id; + seasonId: Parameters.SeasonId; } namespace Responses { export type $200 = Components.Schemas.ResponseEnvelopeTeamDto; @@ -771,9 +760,9 @@ declare namespace Paths { } export interface PathParameters { groupId: Parameters.GroupId; - seasonId: Parameters.SeasonId; - id: Parameters.Id; teamId: Parameters.TeamId; + id: Parameters.Id; + seasonId: Parameters.SeasonId; } namespace Responses { export type $200 = Components.Schemas.ResponseEnvelopeTeamDto; From 2838fa4a343f27d5141e72b2e0c71665208e5472 Mon Sep 17 00:00:00 2001 From: Linus Bolls Date: Fri, 26 Sep 2025 15:26:29 +0200 Subject: [PATCH 89/90] feat(frontend): auth --- mobile-app/api/utils/create-api.tsx | 5 +-- mobile-app/app/auth/useAuth.ts | 49 +++++++++++++++++++---- mobile-app/app/deviceStorage.ts | 13 ++++++ mobile-app/components/OnboardingModal.tsx | 2 +- 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/mobile-app/api/utils/create-api.tsx b/mobile-app/api/utils/create-api.tsx index 0d2a8921..37ff1cb3 100644 --- a/mobile-app/api/utils/create-api.tsx +++ b/mobile-app/api/utils/create-api.tsx @@ -43,10 +43,7 @@ export function ApiProvider({ children }: { children: ReactNode }) { client.interceptors.request.use((config) => { config.headers.Authorization = 'Bearer ' + accessToken; - console.log( - 'using request interceptor:', - config.headers.Authorization - ); + return config; }); diff --git a/mobile-app/app/auth/useAuth.ts b/mobile-app/app/auth/useAuth.ts index 2297d10d..c50ff4ae 100644 --- a/mobile-app/app/auth/useAuth.ts +++ b/mobile-app/app/auth/useAuth.ts @@ -1,5 +1,6 @@ import * as Sentry from '@sentry/react-native'; import * as Application from 'expo-application'; +import { isAxiosError } from 'axios'; // import * as Notifications from 'expo-notifications'; // import * as Permissions from 'expo-permissions'; import jwt, { JWTBody, JWTDefaultBody } from 'expo-jwt'; @@ -74,7 +75,10 @@ async function getRefreshToken(api: BeerPongClient): Promise { const deviceId = await getInstallationId(); - const signupRes = await api.signup({}, { installationType, deviceId }); + const signupRes = await api.signup(undefined, { + installationType, + deviceId, + }); const refreshToken = signupRes.data.data?.token; @@ -107,7 +111,9 @@ async function getAccessToken( try { refreshToken = await getRefreshToken(api); - const accessTokenRes = await api.refreshAuth({}, { refreshToken }); + const accessTokenRes = await api.refreshAuth(undefined, { + refreshToken, + }); const accessToken = accessTokenRes.data.data?.token; @@ -131,9 +137,39 @@ async function getAccessToken( 'with refreshToken', refreshToken ); - (err as Error).message = - 'Failed to get access token: ' + - (err instanceof Error ? err.message : 'Unknown error'); + if (isAxiosError(err)) { + // more detailed error response returned by the backend, can be found in ErrorCodes.java + const customErrorCode = err.response?.data.error?.code; + + // standard http error code, e.g. "Bad Request", automatically thrown by spring boot + const httpErrorCode = err.response?.data.error; + + const isInvalidRefreshToken = + customErrorCode === 'authRefreshInvalidToken'; + + if (isInvalidRefreshToken) { + ConsoleLogger.error( + 'Failed to get access token: refresh token not accepted by backend' + ); + await versusDeviceStorage.removeRefreshToken(); + + err = new Error( + 'Failed to get access token: refresh token not accepted by backend' + ); + } else { + const message = customErrorCode ?? httpErrorCode ?? err.message; + + (err as Error).message = + 'Failed to get access token: ' + + message + + ': ' + + err.message; + } + } else { + (err as Error).message = + 'Failed to get access token: ' + + ((err as Error).message ?? 'Unknown error'); + } Sentry.captureException(err, { extra: { @@ -167,14 +203,13 @@ export function useAuth() { try { ConsoleLogger.info('refreshing access token'); + const promise = getAccessToken(api); setFetchingPromise(promise); const value = await promise; - ConsoleLogger.info('refreshed access token'); - setAccessToken(value); return value; diff --git a/mobile-app/app/deviceStorage.ts b/mobile-app/app/deviceStorage.ts index f55c3bcd..21ada225 100644 --- a/mobile-app/app/deviceStorage.ts +++ b/mobile-app/app/deviceStorage.ts @@ -34,6 +34,16 @@ class VersusDeviceStorage { ); } } + private async removeItemAsync(key: string) { + try { + return this.store.deleteItemAsync(key, { + keychainAccessible: 0, + keychainService: this.KEYCHAIN_SERVICE, + }); + } catch (err) { + this.logger.error(`Failed to remove item with key "${key}":`, err); + } + } public async getRefreshToken() { return this.getItemAsync(this.REFRESH_TOKEN_KEY); @@ -41,5 +51,8 @@ class VersusDeviceStorage { public async setRefreshToken(token: string) { return this.setItemAsync(this.REFRESH_TOKEN_KEY, token); } + public async removeRefreshToken() { + return this.removeItemAsync(this.REFRESH_TOKEN_KEY); + } } export const versusDeviceStorage = new VersusDeviceStorage(); diff --git a/mobile-app/components/OnboardingModal.tsx b/mobile-app/components/OnboardingModal.tsx index 9a26ee24..a66ed4c9 100644 --- a/mobile-app/components/OnboardingModal.tsx +++ b/mobile-app/components/OnboardingModal.tsx @@ -43,7 +43,7 @@ export default function OnboardingModal() { Welcome to{' '} Date: Fri, 26 Sep 2025 15:28:49 +0200 Subject: [PATCH 90/90] devops: re-disable openapi action --- .github/workflows/generate-openapi.yaml | 2 +- api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java | 1 - mobile-app/app/joinGroup.tsx | 4 ++++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/generate-openapi.yaml b/.github/workflows/generate-openapi.yaml index c6f19187..db0c5fd3 100644 --- a/.github/workflows/generate-openapi.yaml +++ b/.github/workflows/generate-openapi.yaml @@ -60,7 +60,7 @@ jobs: echo "Commit messages in range:" echo "$COMMIT_MESSAGES" - if echo "$COMMIT_MESSAGES" | grep -q "chore: update openapi types (TEMPORARILY DISABLE ACTION)"; then + if echo "$COMMIT_MESSAGES" | grep -q "chore: update openapi types"; then echo "has_bot_author=true" >> $GITHUB_OUTPUT echo "A recent commit contains the version bump message. Stopping the workflow." exit 0 # This will stop the workflow without failing it diff --git a/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java b/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java index 4ed423fb..46de7ef3 100644 --- a/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java +++ b/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java @@ -1,4 +1,3 @@ -// temp comment to force openapi action to rerun package pro.beerpong.api.auth; import io.jsonwebtoken.*; diff --git a/mobile-app/app/joinGroup.tsx b/mobile-app/app/joinGroup.tsx index a30ae0cb..f6f3b5f7 100644 --- a/mobile-app/app/joinGroup.tsx +++ b/mobile-app/app/joinGroup.tsx @@ -4,6 +4,7 @@ import { useRouter } from 'expo-router'; import React from 'react'; import { QK } from '@/api/utils/reactQuery'; +import { useNavigation } from '@/app/navigation/useNavigation'; import JoinGroup from '@/components/screens/JoinGroup'; import { showSuccessToast } from '@/toast'; import { ConsoleLogger } from '@/utils/logging'; @@ -11,6 +12,9 @@ import { useGroupStore } from '@/zustand/group/stateGroupStore'; export default function Page() { const router = useRouter(); + + const nav = useNavigation(); + const { joinGroupMutation, selectGroup } = useGroupStore(); const queryClient = useQueryClient();