diff --git a/build.gradle b/build.gradle index 7c812e1c6..4e3673abd 100644 --- a/build.gradle +++ b/build.gradle @@ -204,6 +204,14 @@ dependencyManagement { } } +test { + useJUnitPlatform { + } + testLogging { + events("passed", "skipped", "failed") + } +} + dependencies { compileOnly("org.projectlombok:lombok:${lombokVersion}") annotationProcessor("org.projectlombok:lombok:${lombokVersion}") @@ -251,7 +259,6 @@ dependencies { compile("org.luaj:luaj-jse:${luajVersion}") compile("com.github.micheljung:nocatch:${nocatchVersion}") compile("junit-addons:junit-addons:${junitAddonsVersion}") - compile("com.googlecode.zohhak:zohhak:${zohhakVersion}") compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${jacksonDatatypeJsr310Version}") compile("com.mandrillapp.wrapper.lutung:lutung:${lutungVersion}") compile("org.apache.commons:commons-compress:${commonsCompressVersion}") @@ -265,9 +272,17 @@ dependencies { testCompile("org.springframework.boot:spring-boot-starter-test") testCompile("org.springframework.restdocs:spring-restdocs-mockmvc") testCompile("org.springframework.security:spring-security-test") + testImplementation("junit:junit") + testImplementation("org.junit.jupiter:junit-jupiter:${jUnit5Version}") + testImplementation("org.junit.jupiter:junit-jupiter-params:${jUnit5Version}") + testImplementation("org.mockito:mockito-junit-jupiter:${mockitoVersion}") + testRuntimeOnly("org.junit.vintage:junit-vintage-engine:${jUnit5Version}") testCompile("com.h2database:h2:${h2Version}") testCompile("com.jayway.jsonpath:json-path:${jsonPath}") testCompile("com.jayway.jsonpath:json-path-assert:${jsonPathAssert}") codacy("com.github.codacy:codacy-coverage-reporter:${codacyCoverageReporterVersion}") } + +ext["mockito.version"] = "${mockitoVersion}" +ext["junit-jupiter.version"] = "${jUnit5Version}" diff --git a/gradle.properties b/gradle.properties index 19a3f6ae8..1f9ea4ee1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,13 +16,12 @@ springBootAdminClientVersion=2.0.6 luajVersion=3.0.1 nocatchVersion=1.1 junitAddonsVersion=1.4 -zohhakVersion=1.1.1 githubApiVersion=1.84 jgitVersionn=4.5.0.201609210915-r -fafCommonsVersion=8860b91752f6b5713926036dbb4740197d27211b +fafCommonsVersion=4ca1f756 h2Version=1.4.193 jacksonDatatypeJsr310Version=2.9.7 -mockitoVersion=2.19.0 +mockitoVersion=2.23.4 lutungVersion=0.0.7 commonsCompressVersion=1.13 jsonPath=2.2.0 @@ -32,3 +31,4 @@ codacyCoverageReporterVersion=4.0.0 jsonVersion=20180813 javaxInterceptorApiVersion=1.2 lombokVersion=1.18.4 +jUnit5Version=5.5.1 diff --git a/src/main/java/com/faforever/api/config/security/WebSecurityConfig.java b/src/main/java/com/faforever/api/config/security/WebSecurityConfig.java index b6cf75d40..802e16b36 100644 --- a/src/main/java/com/faforever/api/config/security/WebSecurityConfig.java +++ b/src/main/java/com/faforever/api/config/security/WebSecurityConfig.java @@ -24,7 +24,6 @@ import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; @@ -91,7 +90,7 @@ public boolean matches(HttpServletRequest request) { @Bean public WebMvcConfigurer corsConfigurer() { - return new WebMvcConfigurerAdapter() { + return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") diff --git a/src/main/java/com/faforever/api/error/ApiException.java b/src/main/java/com/faforever/api/error/ApiException.java index 4aa304289..be4cf6d0f 100644 --- a/src/main/java/com/faforever/api/error/ApiException.java +++ b/src/main/java/com/faforever/api/error/ApiException.java @@ -4,6 +4,7 @@ import lombok.ToString; import java.util.Arrays; +import java.util.Collection; @Getter @ToString @@ -15,8 +16,16 @@ public ApiException(Error error) { this(new Error[]{error}); } - public ApiException(Error[] errors) { + public ApiException(Error... errors) { super(Arrays.toString(errors)); this.errors = errors; } + + public static ApiException of(ErrorCode errorCode, Object... args) { + return new ApiException(new Error(errorCode, args)); + } + + public static ApiException of(Collection errors) { + return new ApiException(errors.toArray(new Error[0])); + } } diff --git a/src/main/java/com/faforever/api/error/ErrorCode.java b/src/main/java/com/faforever/api/error/ErrorCode.java index e66c73569..72a260125 100644 --- a/src/main/java/com/faforever/api/error/ErrorCode.java +++ b/src/main/java/com/faforever/api/error/ErrorCode.java @@ -90,9 +90,14 @@ public enum ErrorCode { VOTING_SUBJECT_DOES_NOT_EXIST(180, "Voting subject does not exist", "There is no voting subject with the ID ''{0}''."), VOTING_CHOICE_DOES_NOT_EXIST(181, "Invalid choice", "There is no voting choice with the ID ''{0}''."), STEAM_ID_ALREADY_LINKED(182, " Steam account already linked to a FAF account", "You linked this account already to user with name ''{0}''."), - MAP_NAME_INVALID(183, "Map name invalid", "The name of the map in the scenario file can only contain printable ASCII characters and blanks."), - MOD_NAME_INVALID(184, "Mod name invalid", "The name of the mod in the scenario file can only contain printable ASCII characters and blanks."); - + MAP_NAME_INVALID_CHARACTER(183, "Map name invalid", "Only latin characters, numbers, blanks and the minus are allowed."), + MOD_NAME_INVALID(184, "Mod name invalid", "The name of the mod in the scenario file can only contain printable ASCII characters and blanks."), + MAP_NAME_INVALID_MINUS_OCCURENCE(185, "Map name invalid", "Only a maximum of {0} minus characters are allowed."), + MAP_SCRIPT_LINE_MISSING(186, "Missing scenario.lua line", "The scenario.lua has to contain the following line: {0}"), + MAP_NAME_TOO_SHORT(187, "Map name invalid", "The map name must have at least {0, number} characters, was: {1, number}"), + MAP_NAME_DOES_NOT_START_WITH_LETTER(188, "Map name invalid", "The map name has to begin with a letter"), + PARSING_LUA_FILE_FAILED(189, "Parsing lua files failed", "During the parsing of the lua file an error occured: {0}"), + NO_RUSH_RADIUS_MISSING(190, "No rush radius missing", "The scenario file must specify a no rush radius"); private final int code; private final String title; diff --git a/src/main/java/com/faforever/api/map/MapLuaAccessor.java b/src/main/java/com/faforever/api/map/MapLuaAccessor.java new file mode 100644 index 000000000..832e929db --- /dev/null +++ b/src/main/java/com/faforever/api/map/MapLuaAccessor.java @@ -0,0 +1,116 @@ +package com.faforever.api.map; + +import com.faforever.commons.lua.LuaAccessor; +import org.luaj.vm2.LuaValue; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.OptionalInt; + +public class MapLuaAccessor { + private static final String ROOT_ELEMENT = "ScenarioInfo"; + private static final String CONFIGURATIONS = "Configurations"; + private static final String NAME = "name"; + private static final String DESCRIPTION = "description"; + private static final String TYPE = "type"; + private static final String SIZE = "size"; + private static final String NO_RUSH_RADIUS = "norushradius"; + private static final String MAP_VERSION = "map_version"; + private static final String CONFIGURATION_STANDARD = "standard"; + private static final String CONFIGURATION_STANDARD_TEAMS = "teams"; + private static final String CONFIGURATION_STANDARD_TEAMS_NAME = "name"; + private static final String CONFIGURATION_STANDARD_TEAMS_ARMIES = "armies"; + private static final String ADAPTIVE_MAP = "AdaptiveMap"; + + private final LuaAccessor luaAccessor; + + private MapLuaAccessor(LuaAccessor luaAccessor) { + this.luaAccessor = luaAccessor; + } + + public static MapLuaAccessor of(Path scenarioLuaPath) throws IOException { + return new MapLuaAccessor(LuaAccessor.of(scenarioLuaPath, ROOT_ELEMENT)); + } + + public static MapLuaAccessor of(String scenarioLuaCode) throws IOException { + return new MapLuaAccessor(LuaAccessor.of(scenarioLuaCode, ROOT_ELEMENT)); + } + + public Optional getName() { + return luaAccessor.readVariableString(NAME); + } + + public Optional getDescription() { + return luaAccessor.readVariableString(DESCRIPTION); + } + + public Optional getType() { + return luaAccessor.readVariableString(TYPE); + } + + public Optional getSize() { + return luaAccessor.readVariable(SIZE); + } + + public OptionalInt getMapVersion() { + return luaAccessor.readVariableInt(MAP_VERSION); + } + + public OptionalInt getNoRushRadius() { + return luaAccessor.readVariableInt(NO_RUSH_RADIUS); + } + + public Optional isAdaptive() { + return luaAccessor.readVariableBool(ADAPTIVE_MAP); + } + + public boolean hasVariableMatchingIgnoreCase(String regex, String... names) { + return luaAccessor.hasVariableMatchingIgnoreCase(regex, names); + } + + public Optional getFirstTeam() { + Optional configurationStandardTeamsOptional = luaAccessor.readVariable( + CONFIGURATIONS, CONFIGURATION_STANDARD, CONFIGURATION_STANDARD_TEAMS); + + return configurationStandardTeamsOptional + .map(teams -> teams.get(1)); + } + + public boolean hasInvalidTeam() { + return getFirstTeam() + .map(firstTeam -> + !LuaAccessor.isValue(firstTeam, CONFIGURATION_STANDARD_TEAMS_NAME) || + !LuaAccessor.isValue(firstTeam, CONFIGURATION_STANDARD_TEAMS_ARMIES) || + !firstTeam.get(CONFIGURATION_STANDARD_TEAMS_NAME).tojstring().equals("FFA")) + .orElse(true); + } + + public String getName$() { + return getName().get(); + } + + public String getDescription$() { + return getDescription().get(); + } + + public String getType$() { + return getType().get(); + } + + public LuaValue getSize$() { + return getSize().get(); + } + + public int getMapVersion$() { + return getMapVersion().getAsInt(); + } + + public LuaValue getFirstTeam$() { + return getFirstTeam().get(); + } + + public boolean isAdaptive$() { + return isAdaptive().get(); + } +} diff --git a/src/main/java/com/faforever/api/map/MapNameValidationResponse.java b/src/main/java/com/faforever/api/map/MapNameValidationResponse.java new file mode 100644 index 000000000..0769be285 --- /dev/null +++ b/src/main/java/com/faforever/api/map/MapNameValidationResponse.java @@ -0,0 +1,22 @@ +package com.faforever.api.map; + +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +public class MapNameValidationResponse { + String displayName; + int nextVersion; + String folderName; + FileNames fileNames; + + @Value + @Builder + static class FileNames { + String scmap; + String saveLua; + String scenarioLua; + String scriptLua; + } +} diff --git a/src/main/java/com/faforever/api/map/MapService.java b/src/main/java/com/faforever/api/map/MapService.java index 15c3630d8..dac9a6dfc 100644 --- a/src/main/java/com/faforever/api/map/MapService.java +++ b/src/main/java/com/faforever/api/map/MapService.java @@ -8,298 +8,377 @@ import com.faforever.api.error.ApiException; import com.faforever.api.error.Error; import com.faforever.api.error.ErrorCode; -import com.faforever.api.error.ProgrammingError; +import com.faforever.api.map.MapNameValidationResponse.FileNames; import com.faforever.api.utils.FilePermissionUtil; import com.faforever.api.utils.NameUtil; import com.faforever.commons.io.Unzipper; import com.faforever.commons.io.Zipper; -import com.faforever.commons.lua.LuaLoader; import com.faforever.commons.map.PreviewGenerator; -import lombok.Data; +import com.google.common.annotations.VisibleForTesting; +import lombok.AllArgsConstructor; +import lombok.Getter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.compress.archivers.ArchiveException; +import org.luaj.vm2.LuaError; import org.luaj.vm2.LuaValue; import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import org.springframework.util.FileSystemUtils; +import org.springframework.util.StringUtils; import javax.imageio.ImageIO; -import javax.inject.Inject; import java.awt.image.BufferedImage; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Optional; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; +import static com.faforever.api.map.MapService.ScenarioMapInfo.CONFIGURATION_STANDARD_TEAMS_ARMIES; +import static com.faforever.api.map.MapService.ScenarioMapInfo.CONFIGURATION_STANDARD_TEAMS_NAME; +import static com.faforever.api.map.MapService.ScenarioMapInfo.FILE_DECLARATION_MAP; +import static com.faforever.api.map.MapService.ScenarioMapInfo.FILE_DECLARATION_SAVE; +import static com.faforever.api.map.MapService.ScenarioMapInfo.FILE_DECLARATION_SCRIPT; +import static com.faforever.api.map.MapService.ScenarioMapInfo.FILE_ENDING_MAP; +import static com.faforever.api.map.MapService.ScenarioMapInfo.FILE_ENDING_SAVE; +import static com.faforever.api.map.MapService.ScenarioMapInfo.FILE_ENDING_SCENARIO; +import static com.faforever.api.map.MapService.ScenarioMapInfo.FILE_ENDING_SCRIPT; +import static com.faforever.api.map.MapService.ScenarioMapInfo.MANDATORY_FILES; import static com.github.nocatch.NoCatch.noCatch; +import static java.text.MessageFormat.format; @Service @Slf4j +@AllArgsConstructor public class MapService { - private static final String[] REQUIRED_FILES = new String[]{ - ".scmap", - "_save.lua", - "_scenario.lua", - "_script.lua"}; + private static final Pattern MAP_NAME_INVALID_CHARACTER_PATTERN = Pattern.compile("[a-zA-Z0-9\\- ]+"); + private static final Pattern MAP_NAME_DOES_NOT_START_WITH_LETTER_PATTERN = Pattern.compile("^[^a-zA-Z]+"); + private static final Pattern ADAPTIVE_MAP_PATTERN = Pattern.compile("AdaptiveMap\\w=\\wtrue"); + private static final int MAP_NAME_MINUS_MAX_OCCURENCE = 3; + private static final int MAP_NAME_MIN_LENGTH = 4; + private static final int MAP_NAME_MAX_LENGTH = 50; + + private static final String[] ADAPTIVE_REQUIRED_FILES = new String[]{ + "_options.lua", + "_tables.lua", + }; + private static final Charset MAP_CHARSET = StandardCharsets.ISO_8859_1; - private static final String STUPID_MAP_FOLDER_PREFIX = "maps/"; - public static final int MAP_DISPLAY_NAME_MAX_LENGTH = 100; + private static final String LEGACY_FOLDER_PREFIX = "maps/"; private final FafApiProperties fafApiProperties; private final MapRepository mapRepository; private final ContentService contentService; - @Inject - public MapService(FafApiProperties fafApiProperties, MapRepository mapRepository, ContentService contentService) { - this.fafApiProperties = fafApiProperties; - this.mapRepository = mapRepository; - this.contentService = contentService; + public MapNameValidationResponse requestMapNameValidation(String mapName) { + Assert.notNull(mapName, "The map name is mandatory."); + + validateMapName(mapName); + MapNameBuilder mapNameBuilder = new MapNameBuilder(mapName); + + Integer nextVersion = mapRepository.findOneByDisplayName(mapNameBuilder.getDisplayName()) + .map(map -> map.getVersions().stream() + .mapToInt(MapVersion::getVersion) + .max() + .orElse(1)) + .orElse(1); + + return MapNameValidationResponse.builder() + .displayName(mapNameBuilder.getDisplayName()) + .nextVersion(nextVersion) + .folderName(mapNameBuilder.buildFolderName(nextVersion)) + .fileNames( + FileNames.builder() + .scmap(mapNameBuilder.buildFileName(FILE_ENDING_MAP)) + .scenarioLua(mapNameBuilder.buildFileName(FILE_ENDING_SCENARIO)) + .scriptLua(mapNameBuilder.buildFileName(FILE_ENDING_SCRIPT)) + .saveLua(mapNameBuilder.buildFileName(FILE_ENDING_SAVE)) + .build() + ) + .build(); + } + + @VisibleForTesting + void validateMapName(String mapName) { + List errors = new ArrayList<>(); + + if (!MAP_NAME_INVALID_CHARACTER_PATTERN.matcher(mapName).matches()) { + errors.add(new Error(ErrorCode.MAP_NAME_INVALID_CHARACTER)); + } + + if (mapName.length() < MAP_NAME_MIN_LENGTH) { + errors.add(new Error(ErrorCode.MAP_NAME_TOO_SHORT, MAP_NAME_MIN_LENGTH, mapName.length())); + } + + if (mapName.length() > MAP_NAME_MAX_LENGTH) { + errors.add(new Error(ErrorCode.MAP_NAME_TOO_LONG, MAP_NAME_MAX_LENGTH, mapName.length())); + } + + if (StringUtils.countOccurrencesOf(mapName, "-") > MAP_NAME_MINUS_MAX_OCCURENCE) { + errors.add(new Error(ErrorCode.MAP_NAME_INVALID_MINUS_OCCURENCE, MAP_NAME_MINUS_MAX_OCCURENCE)); + } + + if (MAP_NAME_DOES_NOT_START_WITH_LETTER_PATTERN.matcher(mapName).find()) { + errors.add(new Error(ErrorCode.MAP_NAME_DOES_NOT_START_WITH_LETTER)); + } + + if (!errors.isEmpty()) { + throw new ApiException(errors.toArray(new Error[0])); + } + } + + @VisibleForTesting + void validateScenarioLua(String scenarioLua) { + try { + MapLuaAccessor mapLua = MapLuaAccessor.of(scenarioLua); + MapNameBuilder mapNameBuilder = new MapNameBuilder(mapLua.getName() + .orElseThrow(() -> ApiException.of(ErrorCode.MAP_NAME_MISSING))); + validateScenarioLua(mapLua, mapNameBuilder); + } catch (IOException | LuaError e) { + throw ApiException.of(ErrorCode.PARSING_LUA_FILE_FAILED, e.getMessage()); + } } @Transactional @SneakyThrows @CacheEvict(value = {Map.TYPE_NAME, MapVersion.TYPE_NAME}, allEntries = true) - public void uploadMap(byte[] mapData, String mapFilename, Player author, boolean isRanked) { + public void uploadMap(InputStream mapDataInputStream, String mapFilename, Player author, boolean isRanked) { Assert.notNull(author, "'author' must not be null"); - Assert.isTrue(mapData.length > 0, "'mapData' must not be empty"); + Assert.isTrue(mapDataInputStream.available() > 0, "'mapData' must not be empty"); - MapUploadData progressData = new MapUploadData() - .setBaseDir(contentService.createTempDir()) - .setUploadFileName(mapFilename) - .setAuthorEntity(author) - .setRanked(isRanked); - - progressData.setUploadedFile(progressData.getBaseDir().resolve(mapFilename)); - copyToTemporaryDirectory(mapData, progressData); + Path rootTempFolder = contentService.createTempDir(); try { - unzipFile(progressData); - postProcessZipFiles(progressData); + Path unzippedFileFolder = unzipToTemporaryDirectory(mapDataInputStream, rootTempFolder); + Path mapFolder = validateMapFolderStructure(unzippedFileFolder); + validateRequiredFiles(mapFolder, MANDATORY_FILES); + + MapLuaAccessor mapLua = parseScenarioLua(mapFolder); + MapNameBuilder mapNameBuilder = new MapNameBuilder(mapLua.getName() + .orElseThrow(() -> ApiException.of(ErrorCode.MAP_NAME_MISSING))); - parseScenarioLua(progressData); - checkLua(progressData); - postProcessLuaFile(progressData); + mapLua.isAdaptive().ifPresent(isAdaptive -> { + if (isAdaptive) { + validateRequiredFiles(mapFolder, ADAPTIVE_REQUIRED_FILES); + } + }); - updateMapEntities(progressData); + validateScenarioLua(mapLua, mapNameBuilder); - renameFolderNameAndCorrectPathInLuaFiles(progressData); - generatePreview(progressData); + Optional existingMapOptional = validateMapMetadata(mapLua, mapNameBuilder, author); - zipMapData(progressData); + updateHibernateMapEntities(mapLua, existingMapOptional, author, isRanked, mapNameBuilder); + + Path mapFolderAfterRenaming = unzippedFileFolder.resolveSibling( + mapNameBuilder.buildFolderName(mapLua.getMapVersion$())); + Files.move(mapFolder, mapFolderAfterRenaming); + + updateLuaFiles(mapFolder, mapFolderAfterRenaming); + generatePreview(mapFolderAfterRenaming); + + zipMapData(mapFolderAfterRenaming, mapNameBuilder.buildFinalZipPath(mapLua.getMapVersion$())); } finally { - cleanup(progressData); + mapDataInputStream.close(); + FileSystemUtils.deleteRecursively(rootTempFolder); } } - @SneakyThrows - private Path copyToTemporaryDirectory(byte[] mapData, MapUploadData progressData) { - return Files.write(progressData.getUploadedFile(), mapData); - } + private Path unzipToTemporaryDirectory(InputStream mapDataInputStream, Path rootTempFolder) + throws IOException, ArchiveException { + Path unzippedDirectory = Files.createDirectories(rootTempFolder.resolve("unzipped-content")); + log.debug("Unzipping uploaded file ''{}'' to: {}", mapDataInputStream, unzippedDirectory); - @SneakyThrows - private void unzipFile(MapUploadData mapData) { - Unzipper.from(mapData.getUploadedFile()) - .to(mapData.getBaseDir()) - .unzip(); + Unzipper.from(mapDataInputStream) + .to(unzippedDirectory) + .unzip(); + + return unzippedDirectory; } - @SneakyThrows - private void postProcessZipFiles(MapUploadData mapUploadData) { - Optional mapFolder; - try (Stream mapFolderStream = Files.list(mapUploadData.getBaseDir())) { + /** + * @param zipContentFolder The folder containing the content of the zipped map file + * @return the root folder of the map + */ + private Path validateMapFolderStructure(Path zipContentFolder) throws IOException { + Path mapFolder; + + try (Stream mapFolderStream = Files.list(zipContentFolder)) { mapFolder = mapFolderStream .filter(path -> Files.isDirectory(path)) - .findFirst(); - } - - if (!mapFolder.isPresent()) { - throw new ApiException(new Error(ErrorCode.MAP_MISSING_MAP_FOLDER_INSIDE_ZIP)); + .findFirst() + .orElseThrow(() -> ApiException.of(ErrorCode.MAP_MISSING_MAP_FOLDER_INSIDE_ZIP)); } - try (Stream mapFolderStream = Files.list(mapUploadData.getBaseDir())) { - if (mapFolderStream.count() != 2) { - throw new ApiException(new Error(ErrorCode.MAP_INVALID_ZIP)); + try (Stream mapFolderStream = Files.list(zipContentFolder)) { + if (mapFolderStream.count() != 1) { + throw ApiException.of(ErrorCode.MAP_INVALID_ZIP); } } - mapUploadData.setOriginalMapFolder(mapFolder.get()); - mapUploadData.setUploadFolderName(mapUploadData.getOriginalMapFolder().getFileName().toString()); - - List filePaths = new ArrayList<>(); - try (Stream mapFileStream = Files.list(mapUploadData.getOriginalMapFolder())) { - mapFileStream.forEach(filePaths::add); - Arrays.stream(REQUIRED_FILES) - .forEach(filePattern -> { - if (filePaths.stream() - .noneMatch(filePath -> filePath.toString().endsWith(filePattern))) { - throw new ApiException(new Error(ErrorCode.MAP_FILE_INSIDE_ZIP_MISSING, filePattern)); - } - }); - } + return mapFolder; } @SneakyThrows - private void parseScenarioLua(MapUploadData progressData) { - try (Stream mapFilesStream = Files.list(progressData.getOriginalMapFolder())) { - Path scenarioLuaPath = noCatch(() -> mapFilesStream) - .filter(myFile -> myFile.toString().endsWith("_scenario.lua")) + private void validateRequiredFiles(Path mapFolder, String[] requiredFiles) { + try (Stream mapFileStream = Files.list(mapFolder)) { + List fileNames = mapFileStream + .map(Path::toString) + .collect(Collectors.toList()); + + List errors = Arrays.stream(requiredFiles) + .filter(requiredEnding -> fileNames.stream().noneMatch(fileName -> fileName.endsWith(requiredEnding))) + .map(requiredEnding -> new Error(ErrorCode.MAP_FILE_INSIDE_ZIP_MISSING, requiredEnding)) + .collect(Collectors.toList()); + + if (!errors.isEmpty()) { + throw ApiException.of(errors); + } + } + } + + private MapLuaAccessor parseScenarioLua(Path mapFolder) throws IOException { + try (Stream mapFilesStream = Files.list(mapFolder)) { + Path scenarioLuaPath = mapFilesStream + .filter(myFile -> myFile.toString().endsWith(FILE_ENDING_SCENARIO)) .findFirst() - .orElseThrow(() -> new ApiException(new Error(ErrorCode.MAP_SCENARIO_LUA_MISSING))); - LuaValue root = noCatch(() -> LuaLoader.loadFile(scenarioLuaPath), IllegalStateException.class); - progressData.setLuaRoot(root); + .orElseThrow(() -> ApiException.of(ErrorCode.MAP_SCENARIO_LUA_MISSING)); + return MapLuaAccessor.of(scenarioLuaPath); + } catch (LuaError e) { + throw ApiException.of(ErrorCode.PARSING_LUA_FILE_FAILED, e.getMessage()); } } - private void checkLua(MapUploadData progressData) { + private void validateScenarioLua(MapLuaAccessor mapLua, MapNameBuilder mapNameBuilder) { List errors = new ArrayList<>(); - LuaValue scenarioInfo = progressData.getLuaScenarioInfo(); - if (scenarioInfo.get(ScenarioMapInfo.NAME) == LuaValue.NIL) { - errors.add(new Error(ErrorCode.MAP_NAME_MISSING)); - } - String displayName = scenarioInfo.get(ScenarioMapInfo.NAME).tojstring(); - if (displayName.length() > MAP_DISPLAY_NAME_MAX_LENGTH) { - throw new ApiException(new Error(ErrorCode.MAP_NAME_TOO_LONG, MAP_DISPLAY_NAME_MAX_LENGTH, displayName.length())); - } - if (!NameUtil.isPrintableAsciiString(displayName)) { - throw new ApiException(new Error(ErrorCode.MAP_NAME_INVALID)); - } - if (scenarioInfo.get(ScenarioMapInfo.DESCRIPTION) == LuaValue.NIL) { + validateLuaPathVariable(mapLua, FILE_DECLARATION_MAP, mapNameBuilder, FILE_ENDING_MAP).ifPresent(errors::add); + validateLuaPathVariable(mapLua, FILE_DECLARATION_SAVE, mapNameBuilder, FILE_ENDING_SAVE).ifPresent(errors::add); + validateLuaPathVariable(mapLua, FILE_DECLARATION_SCRIPT, mapNameBuilder, FILE_ENDING_SCRIPT).ifPresent(errors::add); + + if (!mapLua.getDescription().isPresent()) { errors.add(new Error(ErrorCode.MAP_DESCRIPTION_MISSING)); } - if (invalidTeam(scenarioInfo)) { + if (mapLua.hasInvalidTeam()) { errors.add(new Error(ErrorCode.MAP_FIRST_TEAM_FFA)); } - if (scenarioInfo.get(ScenarioMapInfo.TYPE) == LuaValue.NIL) { + if (!mapLua.getType().isPresent()) { errors.add(new Error(ErrorCode.MAP_TYPE_MISSING)); } - if (scenarioInfo.get(ScenarioMapInfo.SIZE) == LuaValue.NIL) { + if (!mapLua.getSize().isPresent()) { errors.add(new Error(ErrorCode.MAP_SIZE_MISSING)); } - if (scenarioInfo.get(ScenarioMapInfo.MAP_VERSION) == LuaValue.NIL) { + + if (!mapLua.getMapVersion().isPresent()) { errors.add(new Error(ErrorCode.MAP_VERSION_MISSING)); } + + if (!mapLua.getNoRushRadius().isPresent()) { + // The game can start without it, but the GPG map editor will crash on opening such a map. + errors.add(new Error(ErrorCode.NO_RUSH_RADIUS_MISSING)); + } + if (!errors.isEmpty()) { - throw new ApiException(errors.toArray(new Error[errors.size()])); + throw ApiException.of(errors); } } - private boolean invalidTeam(LuaValue scenarioInfo) { - LuaValue scenario = scenarioInfo.get(ScenarioMapInfo.CONFIGURATIONS); - if (scenario == LuaValue.NIL) { - return true; - } - LuaValue standard = scenario.get(ScenarioMapInfo.CONFIGURATION_STANDARD); - if (standard == LuaValue.NIL) { - return true; - } - LuaValue teams = standard.get(ScenarioMapInfo.CONFIGURATION_STANDARD_TEAMS); - if (teams == LuaValue.NIL) { - return true; - } - LuaValue firstTeam = teams.get(1); - if (firstTeam == LuaValue.NIL) { - return true; - } - LuaValue teamName = firstTeam.get(ScenarioMapInfo.CONFIGURATION_STANDARD_TEAMS_NAME); - if (teamName == LuaValue.NIL) { - return true; + private Optional validateLuaPathVariable(MapLuaAccessor mapLua, String variableName, MapNameBuilder mapNameBuilder, String fileEnding) { + String mapFileName = mapNameBuilder.buildFileName(fileEnding); + String mapFolderNameWithoutVersion = mapNameBuilder.buildFolderNameWithoutVersion(); + + String regex = format("\\/maps\\/{0}(\\.v\\d{4})?\\/{1}", + mapFolderNameWithoutVersion, mapFileName); + + if (!mapLua.hasVariableMatchingIgnoreCase(regex, variableName)) { + return Optional.of(new Error(ErrorCode.MAP_SCRIPT_LINE_MISSING, + format("{0} = ''/maps/{1}/{2}''", variableName, mapFolderNameWithoutVersion, mapFileName))); } - LuaValue armies = firstTeam.get(ScenarioMapInfo.CONFIGURATION_STANDARD_TEAMS_ARMIES); - return armies == LuaValue.NIL || !teamName.tojstring().equals("FFA"); + return Optional.empty(); } - private void postProcessLuaFile(MapUploadData progressData) { - LuaValue scenarioInfo = progressData.getLuaScenarioInfo(); - Optional mapEntity = mapRepository.findOneByDisplayName( - scenarioInfo.get(ScenarioMapInfo.NAME).toString()); - if (!mapEntity.isPresent()) { - return; - } - Player author = mapEntity.get().getAuthor(); - if (author == null) { - return; - } - if (author.getId() != progressData.getAuthorEntity().getId()) { - throw new ApiException(new Error(ErrorCode.MAP_NOT_ORIGINAL_AUTHOR, mapEntity.get().getDisplayName())); - } - int newVersion = scenarioInfo.get(ScenarioMapInfo.MAP_VERSION).toint(); - if (mapEntity.get().getVersions().stream() - .anyMatch(mapVersion -> mapVersion.getVersion() == newVersion)) { - throw new ApiException(new Error(ErrorCode.MAP_VERSION_EXISTS, mapEntity.get().getDisplayName(), newVersion)); + private Optional validateMapMetadata(MapLuaAccessor mapLua, MapNameBuilder mapNameBuilder, Player author) { + String displayName = mapNameBuilder.getDisplayName(); + + validateMapName(displayName); + + int newVersion = mapLua.getMapVersion() + .orElseThrow(() -> ApiException.of(ErrorCode.MAP_VERSION_MISSING)); + + if (Files.exists(mapNameBuilder.buildFinalZipPath(newVersion))) { + throw ApiException.of(ErrorCode.MAP_NAME_CONFLICT, mapNameBuilder.buildFinalZipName(newVersion)); } - progressData.setMapEntity(mapEntity.get()); + + Optional existingMapOptional = mapRepository.findOneByDisplayName(displayName); + existingMapOptional + .ifPresent(existingMap -> { + Optional.ofNullable(existingMap.getAuthor()) + .filter(existingMapAuthor -> !Objects.equals(existingMapAuthor, author)) + .ifPresent(existingMapAuthor -> { + throw ApiException.of(ErrorCode.MAP_NOT_ORIGINAL_AUTHOR, existingMap.getDisplayName()); + }); + + if (existingMap.getVersions().stream() + .anyMatch(mapVersion -> mapVersion.getVersion() == newVersion)) { + throw ApiException.of(ErrorCode.MAP_VERSION_EXISTS, existingMap.getDisplayName(), newVersion); + } + }); + + return existingMapOptional; } - private void updateMapEntities(MapUploadData progressData) { - LuaValue scenarioInfo = progressData.getLuaScenarioInfo(); - Map map = progressData.getMapEntity(); - if (map == null) { - map = new Map(); - } + private Map updateHibernateMapEntities(MapLuaAccessor mapLua, Optional existingMapOptional, Player author, boolean isRanked, MapNameBuilder mapNameBuilder) { + // the scenario lua is supposed to be validate already, thus we call the unwrapping $-methods + String mapName = mapNameBuilder.getDisplayName(); - String name = scenarioInfo.get(ScenarioMapInfo.NAME).toString(); + Map map = existingMapOptional + .orElseGet(() -> + new Map() + .setDisplayName(mapName) + .setAuthor(author) + ); - map.setDisplayName(name) - .setMapType(scenarioInfo.get(ScenarioMapInfo.TYPE).tojstring()) - .setBattleType(scenarioInfo.get(ScenarioMapInfo.CONFIGURATIONS).get(ScenarioMapInfo.CONFIGURATION_STANDARD).get(ScenarioMapInfo.CONFIGURATION_STANDARD_TEAMS).get(1) - .get(ScenarioMapInfo.CONFIGURATION_STANDARD_TEAMS_NAME).tojstring()) - .setAuthor(progressData.getAuthorEntity()); + LuaValue standardTeamsConfig = mapLua.getFirstTeam$(); - LuaValue size = scenarioInfo.get(ScenarioMapInfo.SIZE); + map + .setMapType(mapLua.getType$()) + .setBattleType(standardTeamsConfig.get(CONFIGURATION_STANDARD_TEAMS_NAME).tojstring()); + + LuaValue size = mapLua.getSize$(); MapVersion version = new MapVersion() - .setDescription(scenarioInfo.get(ScenarioMapInfo.DESCRIPTION).tojstring().replaceAll("", "")) + .setDescription(mapLua.getDescription$().replaceAll("", "")) .setWidth(size.get(1).toint()) .setHeight(size.get(2).toint()) .setHidden(false) - .setRanked(progressData.isRanked()) - .setMaxPlayers(scenarioInfo.get(ScenarioMapInfo.CONFIGURATIONS).get(ScenarioMapInfo.CONFIGURATION_STANDARD).get(ScenarioMapInfo.CONFIGURATION_STANDARD_TEAMS).get(1) - .get(ScenarioMapInfo.CONFIGURATION_STANDARD_TEAMS_ARMIES).length()) - .setVersion(scenarioInfo.get(ScenarioMapInfo.MAP_VERSION).toint()); - map.getVersions().add(version); - version.setMap(map); - - progressData.setMapEntity(map); - progressData.setMapVersionEntity(version); + .setRanked(isRanked) + .setMaxPlayers(standardTeamsConfig.get(CONFIGURATION_STANDARD_TEAMS_ARMIES).length()) + .setVersion(mapLua.getMapVersion$()) + .setMap(map) + .setFilename(LEGACY_FOLDER_PREFIX + mapNameBuilder.buildFinalZipName(mapLua.getMapVersion$())); - version.setFilename(STUPID_MAP_FOLDER_PREFIX + progressData.getFinalZipName()); - progressData.setFinalZipFile( - this.fafApiProperties.getMap().getTargetDirectory() - .resolve(progressData.getFinalZipName())); - - if (Files.exists(progressData.getFinalZipFile())) { - throw new ApiException(new Error(ErrorCode.MAP_NAME_CONFLICT, progressData.getFinalZipName())); - } + map.getVersions().add(version); // this triggers validation mapRepository.save(map); - } - @SneakyThrows - private void renameFolderNameAndCorrectPathInLuaFiles(MapUploadData progressData) { - progressData.setNewMapFolder(progressData.getBaseDir().resolve(progressData.getNewFolderName())); - Files.move(progressData.getOriginalMapFolder(), progressData.getNewMapFolder()); - updateLuaFiles(progressData); + return map; } - @SneakyThrows - private void updateLuaFiles(MapUploadData mapData) { - String oldNameFolder = "/maps/" + mapData.getUploadFolderName(); - String newNameFolder = "/maps/" + mapData.getNewFolderName(); - try (Stream mapFileStream = Files.list(mapData.getNewMapFolder())) { + private void updateLuaFiles(Path oldFolderPath, Path newFolderPath) throws IOException { + String oldNameFolder = "/maps/" + oldFolderPath.getFileName(); + String newNameFolder = "/maps/" + newFolderPath.getFileName(); + try (Stream mapFileStream = Files.list(newFolderPath)) { mapFileStream .filter(path -> path.toString().toLowerCase().endsWith(".lua")) .forEach(path -> noCatch(() -> { @@ -311,46 +390,30 @@ private void updateLuaFiles(MapUploadData mapData) { } } - - @SneakyThrows - private void generatePreview(MapUploadData mapData) { - String previewFilename = mapData.getNewFolderName() + ".png"; + private void generatePreview(Path newMapFolder) throws IOException { + String previewFilename = newMapFolder.getFileName() + ".png"; generateImage( fafApiProperties.getMap().getDirectoryPreviewPathSmall().resolve(previewFilename), - mapData.getNewMapFolder(), + newMapFolder, fafApiProperties.getMap().getPreviewSizeSmall()); generateImage( fafApiProperties.getMap().getDirectoryPreviewPathLarge().resolve(previewFilename), - mapData.getNewMapFolder(), + newMapFolder, fafApiProperties.getMap().getPreviewSizeLarge()); } - @SneakyThrows - private void zipMapData(MapUploadData progressData) { - cleanupBaseDir(progressData); - Path finalZipFile = progressData.getFinalZipFile(); - Files.createDirectories(finalZipFile.getParent(), FilePermissionUtil.directoryPermissionFileAttributes()); + private void zipMapData(Path newMapFolder, Path finalZipPath) throws IOException, ArchiveException { + Files.createDirectories(finalZipPath.getParent(), FilePermissionUtil.directoryPermissionFileAttributes()); Zipper - .contentOf(progressData.getBaseDir()) - .to(finalZipFile) + .of(newMapFolder) + .to(finalZipPath) .zip(); // TODO if possible, this should be done using umask instead - FilePermissionUtil.setDefaultFilePermission(finalZipFile); - } - - @SneakyThrows - private void cleanupBaseDir(MapUploadData progressData) { - Files.delete(progressData.getUploadedFile()); - try (Stream stream = Files.list(progressData.getBaseDir())) { - if (stream.count() != 1) { - throw new ProgrammingError("Folder containing unknown data: " + progressData.getBaseDir()); - } - } + FilePermissionUtil.setDefaultFilePermission(finalZipPath); } - @SneakyThrows - private void generateImage(Path target, Path baseDir, int size) { + private void generateImage(Path target, Path baseDir, int size) throws IOException { BufferedImage image = PreviewGenerator.generatePreview(baseDir, size, size); if (target.getNameCount() > 0) { Files.createDirectories(target.getParent(), FilePermissionUtil.directoryPermissionFileAttributes()); @@ -358,64 +421,58 @@ private void generateImage(Path target, Path baseDir, int size) { ImageIO.write(image, "png", target.toFile()); } - private boolean cleanup(MapUploadData mapData) { - return FileSystemUtils.deleteRecursively(mapData.getBaseDir().toFile()); + static class ScenarioMapInfo { + static final String CONFIGURATION_STANDARD_TEAMS_NAME = "name"; + static final String CONFIGURATION_STANDARD_TEAMS_ARMIES = "armies"; + static final String FILE_DECLARATION_MAP = "map"; + static final String FILE_ENDING_SCENARIO = "_scenario.lua"; + static final String FILE_ENDING_MAP = ".scmap"; + static final String FILE_DECLARATION_SAVE = "save"; + static final String FILE_ENDING_SAVE = "_save.lua"; + static final String FILE_DECLARATION_SCRIPT = "script"; + static final String FILE_ENDING_SCRIPT = "_script.lua"; + + static final String[] MANDATORY_FILES = new String[]{ + FILE_ENDING_SCENARIO, + FILE_ENDING_MAP, + FILE_ENDING_SAVE, + FILE_ENDING_SCRIPT, + }; } - @Data - private class MapUploadData { - private String uploadFileName; - private String uploadFolderName; - private String newFolderName; - private Path uploadedFile; - private Path baseDir; - private Path originalMapFolder; - private Path newMapFolder; - private Path finalZipFile; - private LuaValue luaRoot; - private Map mapEntity; - private MapVersion mapVersionEntity; - private Player authorEntity; - private boolean isRanked; - private LuaValue scenarioInfo; - - private LuaValue getLuaScenarioInfo() { - if (getLuaRoot() == null) { - throw new IllegalStateException("*_scenario.lua parse result not available"); - } - if (scenarioInfo == null) { - scenarioInfo = getLuaRoot().get("ScenarioInfo"); - } - return scenarioInfo; + private class MapNameBuilder { + @Getter + private final String displayName; + private final String normalizedDisplayName; + private String folderName; + + private MapNameBuilder(String displayName) { + this.displayName = displayName; + this.normalizedDisplayName = NameUtil.normalizeWhitespaces(displayName.toLowerCase()); } - private String getNewFolderName() { - return generateNewMapNameWithVersion(""); + String buildFolderNameWithoutVersion() { + return normalizedDisplayName; } - private String generateNewMapNameWithVersion(String extension) { - return Paths.get(String.format("%s.v%04d%s", - NameUtil.normalizeFileName(mapEntity.getDisplayName()), - mapVersionEntity.getVersion(), - extension)) - .normalize().toString(); + String buildFolderName(int version) { + if (folderName == null) { + folderName = String.format("%s.v%04d", normalizedDisplayName, version); + } + + return folderName; } - private String getFinalZipName() { - return generateNewMapNameWithVersion(".zip"); + String buildFileName(String fileEnding) { + return normalizedDisplayName + fileEnding; } - } - private class ScenarioMapInfo { - private static final String CONFIGURATIONS = "Configurations"; - private static final String NAME = "name"; - private static final String DESCRIPTION = "description"; - private static final String TYPE = "type"; - private static final String SIZE = "size"; - private static final String MAP_VERSION = "map_version"; - private static final String CONFIGURATION_STANDARD = "standard"; - private static final String CONFIGURATION_STANDARD_TEAMS = "teams"; - private static final String CONFIGURATION_STANDARD_TEAMS_NAME = "name"; - private static final String CONFIGURATION_STANDARD_TEAMS_ARMIES = "armies"; + String buildFinalZipName(int version) { + return buildFolderName(version) + ".zip"; + } + + Path buildFinalZipPath(int version) { + return fafApiProperties.getMap().getTargetDirectory().resolve(buildFinalZipName(version)); + } } } diff --git a/src/main/java/com/faforever/api/map/MapsController.java b/src/main/java/com/faforever/api/map/MapsController.java index 9d996172b..09a3aa00e 100644 --- a/src/main/java/com/faforever/api/map/MapsController.java +++ b/src/main/java/com/faforever/api/map/MapsController.java @@ -12,6 +12,7 @@ import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; +import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.RequestMapping; @@ -19,27 +20,54 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.ModelAndView; -import javax.inject.Inject; import java.io.IOException; +import java.util.Map; import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; @RestController @RequestMapping(path = "/maps") @Slf4j +@AllArgsConstructor public class MapsController { private final MapService mapService; private final FafApiProperties fafApiProperties; private final ObjectMapper objectMapper; private final PlayerService playerService; - @Inject - public MapsController(MapService mapService, FafApiProperties fafApiProperties, ObjectMapper objectMapper, PlayerService playerService) { - this.mapService = mapService; - this.fafApiProperties = fafApiProperties; - this.objectMapper = objectMapper; - this.playerService = playerService; + + @RequestMapping(path = "/validate", method = RequestMethod.GET, produces = APPLICATION_JSON_UTF8_VALUE) + public ModelAndView showValidationForm(Map model) { + return new ModelAndView("validate_map_metadata.html"); + } + + @ApiOperation("Validate map name") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Information about derived names to be used in the scenario.lua"), + @ApiResponse(code = 422, message = "A list of reasons why the name is not valid.") + }) + @RequestMapping( + path = "/validateMapName", + method = RequestMethod.POST, + produces = APPLICATION_JSON_UTF8_VALUE + ) + public MapNameValidationResponse validateMapName(@RequestParam("mapName") String mapName) { + return mapService.requestMapNameValidation(mapName); + } + + @ApiOperation("Validate scenario.lua") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Valid without further information"), + @ApiResponse(code = 422, message = "A list of errors in the scenario.lua")}) + @RequestMapping( + path = "/validateScenarioLua", + method = RequestMethod.POST, + produces = APPLICATION_JSON_UTF8_VALUE + ) + public void validateScenarioLua(@RequestParam(name = "scenarioLua") String scenarioLua) { + mapService.validateScenarioLua(scenarioLua); } @ApiOperation("Upload a map") @@ -70,6 +98,6 @@ public void uploadMap(@RequestParam("file") MultipartFile file, } Player player = playerService.getPlayer(authentication); - mapService.uploadMap(file.getBytes(), file.getOriginalFilename(), player, ranked); + mapService.uploadMap(file.getInputStream(), file.getOriginalFilename(), player, ranked); } } diff --git a/src/main/java/com/faforever/api/utils/NameUtil.java b/src/main/java/com/faforever/api/utils/NameUtil.java index a1c8d69da..da07d0875 100644 --- a/src/main/java/com/faforever/api/utils/NameUtil.java +++ b/src/main/java/com/faforever/api/utils/NameUtil.java @@ -20,6 +20,10 @@ public static boolean isPrintableAsciiString(String name) { return !name.matches(".*[^\\x20-\\x7E]+.*"); } + public static String normalizeWhitespaces(String anyName) { + return StringUtils.strip(anyName.replace(" ", "_")); + } + public static String normalizeFileName(String originalFileName) { return StringUtils.strip( Normalizer.normalize( diff --git a/src/main/resources/config/application-dev.yml b/src/main/resources/config/application-dev.yml index 7c405ce76..c6ccaba5e 100644 --- a/src/main/resources/config/application-dev.yml +++ b/src/main/resources/config/application-dev.yml @@ -71,4 +71,5 @@ spring: password: ${ADMIN_CLIENT_PASSWORD:banana} logging: level: - com.faforever.api: trace + com.faforever.api: debug + diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html index de6681ed1..5d69605bc 100644 --- a/src/main/resources/templates/login.html +++ b/src/main/resources/templates/login.html @@ -3,9 +3,7 @@ Log-in - - - + + + + + + + diff --git a/src/test/java/com/faforever/api/achievements/EventsServiceTest.java b/src/test/java/com/faforever/api/achievements/EventsServiceTest.java index 853413bad..58e1c34d9 100644 --- a/src/test/java/com/faforever/api/achievements/EventsServiceTest.java +++ b/src/test/java/com/faforever/api/achievements/EventsServiceTest.java @@ -19,7 +19,7 @@ import static com.faforever.api.data.domain.AchievementState.REVEALED; import static com.faforever.api.data.domain.AchievementState.UNLOCKED; -import static com.faforever.api.error.ApiExceptionWithCode.apiExceptionWithCode; +import static com.faforever.api.error.ApiExceptionMatcher.hasErrorCode; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.anyInt; @@ -108,7 +108,7 @@ public void incrementExisting() throws Exception { public void incrementNonIncremental() throws Exception { mockAchievement("111", AchievementType.STANDARD, null); - expectedException.expect(apiExceptionWithCode(ErrorCode.ACHIEVEMENT_NOT_INCREMENTAL)); + expectedException.expect(hasErrorCode(ErrorCode.ACHIEVEMENT_NOT_INCREMENTAL)); instance.increment(PLAYER_ID, "111", 3); @@ -204,7 +204,7 @@ public void unlockSecondTime() throws Exception { public void unlockIncremental() throws Exception { mockAchievement("111", AchievementType.INCREMENTAL, 1); - expectedException.expect(apiExceptionWithCode(ErrorCode.ACHIEVEMENT_NOT_STANDARD)); + expectedException.expect(hasErrorCode(ErrorCode.ACHIEVEMENT_NOT_STANDARD)); instance.unlock(PLAYER_ID, "111"); diff --git a/src/test/java/com/faforever/api/avatar/AvatarServiceTest.java b/src/test/java/com/faforever/api/avatar/AvatarServiceTest.java index 0a075a7c8..890de9a61 100644 --- a/src/test/java/com/faforever/api/avatar/AvatarServiceTest.java +++ b/src/test/java/com/faforever/api/avatar/AvatarServiceTest.java @@ -3,7 +3,7 @@ import com.faforever.api.config.FafApiProperties; import com.faforever.api.data.domain.Avatar; import com.faforever.api.data.domain.AvatarAssignment; -import com.faforever.api.error.ApiExceptionWithCode; +import com.faforever.api.error.ApiExceptionMatcher; import com.faforever.api.error.ErrorCode; import com.faforever.api.error.NotFoundApiException; import com.faforever.api.utils.NameUtil; @@ -131,7 +131,7 @@ public void duplicateAvatarUpload() throws Exception { final String avatarFileName = VALID_AVATAR_FILENAME; when(avatarRepository.findOneByUrl(String.format(DOWNLOAD_URL_FORMAT, avatarFileName))).thenReturn(Optional.of(new Avatar())); try (final InputStream imageInputStream = loadResource(avatarFileName).openStream()) { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.AVATAR_NAME_CONFLICT)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.AVATAR_NAME_CONFLICT)); avatarService.createAvatar(AVATAR_METADATA, avatarFileName, imageInputStream, VALID_FILE_SIZE); } } @@ -143,7 +143,7 @@ public void nonExistingAvatarReupload() throws Exception { Optional.empty() ); try (final InputStream imageInputStream = loadResource(avatarFileName).openStream()) { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.ENTITY_NOT_FOUND)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.ENTITY_NOT_FOUND)); avatarService.updateAvatar(NON_EXISTING_AVATAR_ID, AVATAR_METADATA, avatarFileName, imageInputStream, VALID_FILE_SIZE); } } @@ -152,7 +152,7 @@ public void nonExistingAvatarReupload() throws Exception { public void invalidExtensionAvatarUpload() throws Exception { final String avatarFileName = INVALID_EXTENSION_AVATAR_FILENAME; try (final InputStream imageInputStream = loadResource(avatarFileName).openStream()) { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.UPLOAD_INVALID_FILE_EXTENSIONS)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.UPLOAD_INVALID_FILE_EXTENSIONS)); avatarService.createAvatar(AVATAR_METADATA, avatarFileName, imageInputStream, VALID_FILE_SIZE); } } @@ -161,7 +161,7 @@ public void invalidExtensionAvatarUpload() throws Exception { public void invalidExtensionAvatarReupload() throws Exception { final String avatarFileName = INVALID_EXTENSION_AVATAR_FILENAME; try (final InputStream imageInputStream = loadResource(avatarFileName).openStream()) { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.UPLOAD_INVALID_FILE_EXTENSIONS)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.UPLOAD_INVALID_FILE_EXTENSIONS)); avatarService.updateAvatar(EXISTING_AVATAR_ID, AVATAR_METADATA, avatarFileName, imageInputStream, VALID_FILE_SIZE); } } @@ -170,7 +170,7 @@ public void invalidExtensionAvatarReupload() throws Exception { public void bigSizeAvatarUpload() throws Exception { final String avatarFileName = BIG_AVATAR_FILENAME; try (final InputStream imageInputStream = loadResource(avatarFileName).openStream()) { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.FILE_SIZE_EXCEEDED)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.FILE_SIZE_EXCEEDED)); avatarService.createAvatar(AVATAR_METADATA, avatarFileName, imageInputStream, TOO_BIG_FILE_SIZE); } } @@ -179,7 +179,7 @@ public void bigSizeAvatarUpload() throws Exception { public void bigSizeAvatarReupload() throws Exception { final String avatarFileName = BIG_AVATAR_FILENAME; try (final InputStream imageInputStream = loadResource(avatarFileName).openStream()) { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.FILE_SIZE_EXCEEDED)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.FILE_SIZE_EXCEEDED)); avatarService.updateAvatar(EXISTING_AVATAR_ID, AVATAR_METADATA, avatarFileName, imageInputStream, TOO_BIG_FILE_SIZE); } } @@ -188,7 +188,7 @@ public void bigSizeAvatarReupload() throws Exception { public void longFileNameAvatarUpload() throws Exception { final String avatarFileName = LONG_AVATAR_FILENAME; try (final InputStream imageInputStream = loadResource(avatarFileName).openStream()) { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.FILE_NAME_TOO_LONG)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.FILE_NAME_TOO_LONG)); avatarService.createAvatar(AVATAR_METADATA, avatarFileName, imageInputStream, VALID_FILE_SIZE); } } @@ -197,7 +197,7 @@ public void longFileNameAvatarUpload() throws Exception { public void longFileNameAvatarReupload() throws Exception { final String avatarFileName = LONG_AVATAR_FILENAME; try (final InputStream imageInputStream = loadResource(avatarFileName).openStream()) { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.FILE_NAME_TOO_LONG)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.FILE_NAME_TOO_LONG)); avatarService.updateAvatar(EXISTING_AVATAR_ID, AVATAR_METADATA, avatarFileName, imageInputStream, VALID_FILE_SIZE); } } @@ -206,7 +206,7 @@ public void longFileNameAvatarReupload() throws Exception { public void invalidDimensionsAvatarUpload() throws Exception { final String avatarFileName = INVALID_AVATAR_DIMENSIONS_FILENAME; try (final InputStream imageInputStream = loadResource(avatarFileName).openStream()) { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.INVALID_AVATAR_DIMENSION)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.INVALID_AVATAR_DIMENSION)); avatarService.createAvatar(AVATAR_METADATA, avatarFileName, imageInputStream, VALID_FILE_SIZE); } } @@ -215,7 +215,7 @@ public void invalidDimensionsAvatarUpload() throws Exception { public void invalidDimensionsAvatarReupload() throws Exception { final String avatarFileName = INVALID_AVATAR_DIMENSIONS_FILENAME; try (final InputStream imageInputStream = loadResource(avatarFileName).openStream()) { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.INVALID_AVATAR_DIMENSION)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.INVALID_AVATAR_DIMENSION)); avatarService.updateAvatar(EXISTING_AVATAR_ID, AVATAR_METADATA, avatarFileName, imageInputStream, VALID_FILE_SIZE); } } @@ -246,7 +246,7 @@ public void deleteNotExistingAvatar() throws Exception { @Test public void deleteAvatarWithAssignments() throws Exception { when(avatarRepository.findById(AVATAR_ID)).thenReturn(Optional.of(new Avatar().setAssignments(Collections.singletonList(new AvatarAssignment())))); - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.AVATAR_IN_USE)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.AVATAR_IN_USE)); avatarService.deleteAvatar(AVATAR_ID); diff --git a/src/test/java/com/faforever/api/clan/ClanServiceTest.java b/src/test/java/com/faforever/api/clan/ClanServiceTest.java index d084a9e66..10fbee2b0 100644 --- a/src/test/java/com/faforever/api/clan/ClanServiceTest.java +++ b/src/test/java/com/faforever/api/clan/ClanServiceTest.java @@ -28,7 +28,7 @@ import java.util.Collections; import java.util.Optional; -import static com.faforever.api.error.ApiExceptionWithCode.apiExceptionWithCode; +import static com.faforever.api.error.ApiExceptionMatcher.hasErrorCode; import static org.hamcrest.Matchers.greaterThan; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -72,7 +72,7 @@ public void createClanWhereLeaderIsAlreadyInAClan() { instance.create(clanName, tag, description, creator); fail(); } catch (ApiException e) { - assertThat(e, apiExceptionWithCode(ErrorCode.CLAN_CREATE_CREATOR_IS_IN_A_CLAN)); + assertThat(e, hasErrorCode(ErrorCode.CLAN_CREATE_CREATOR_IS_IN_A_CLAN)); } verify(clanRepository, Mockito.never()).save(any(Clan.class)); } @@ -91,7 +91,7 @@ public void createClanWithSameName() { instance.create(clanName, tag, description, creator); fail(); } catch (ApiException e) { - assertThat(e, apiExceptionWithCode(ErrorCode.CLAN_NAME_EXISTS)); + assertThat(e, hasErrorCode(ErrorCode.CLAN_NAME_EXISTS)); } ArgumentCaptor clanCaptor = ArgumentCaptor.forClass(Clan.class); @@ -114,7 +114,7 @@ public void createClanWithSameTag() { instance.create(clanName, tag, description, creator); fail(); } catch (ApiException e) { - assertThat(e, apiExceptionWithCode(ErrorCode.CLAN_TAG_EXISTS)); + assertThat(e, hasErrorCode(ErrorCode.CLAN_TAG_EXISTS)); } ArgumentCaptor clanCaptor = ArgumentCaptor.forClass(Clan.class); @@ -155,7 +155,7 @@ public void generatePlayerInvitationTokenWithInvalidClan() throws IOException { instance.generatePlayerInvitationToken(requester, 45, 42); fail(); } catch (ApiException e) { - assertThat(e, apiExceptionWithCode(ErrorCode.CLAN_NOT_EXISTS)); + assertThat(e, hasErrorCode(ErrorCode.CLAN_NOT_EXISTS)); } verify(jwtService, Mockito.never()).sign(any()); } @@ -179,7 +179,7 @@ public void generatePlayerInvitationTokenFromNonLeader() throws IOException { instance.generatePlayerInvitationToken(requester, newMember.getId(), clan.getId()); fail(); } catch (ApiException e) { - assertThat(e, apiExceptionWithCode(ErrorCode.CLAN_NOT_LEADER)); + assertThat(e, hasErrorCode(ErrorCode.CLAN_NOT_LEADER)); } verify(jwtService, Mockito.never()).sign(any()); } @@ -197,7 +197,7 @@ public void generatePlayerInvitationTokenInvalidPlayer() throws IOException { instance.generatePlayerInvitationToken(requester, 42, clan.getId()); fail(); } catch (ApiException e) { - assertThat(e, apiExceptionWithCode(ErrorCode.CLAN_GENERATE_LINK_PLAYER_NOT_FOUND)); + assertThat(e, hasErrorCode(ErrorCode.CLAN_GENERATE_LINK_PLAYER_NOT_FOUND)); } verify(jwtService, Mockito.never()).sign(any()); } @@ -245,7 +245,7 @@ public void acceptPlayerInvitationTokenExpire() throws IOException { instance.acceptPlayerInvitationToken(stringToken, null); fail(); } catch (ApiException e) { - assertThat(e, apiExceptionWithCode(ErrorCode.CLAN_ACCEPT_TOKEN_EXPIRE)); + assertThat(e, hasErrorCode(ErrorCode.CLAN_ACCEPT_TOKEN_EXPIRE)); } verify(clanMembershipRepository, Mockito.never()).save(any(ClanMembership.class)); } @@ -265,7 +265,7 @@ public void acceptPlayerInvitationTokenInvalidClan() throws IOException { instance.acceptPlayerInvitationToken(stringToken, null); fail(); } catch (ApiException e) { - assertThat(e, apiExceptionWithCode(ErrorCode.CLAN_NOT_EXISTS)); + assertThat(e, hasErrorCode(ErrorCode.CLAN_NOT_EXISTS)); } verify(clanMembershipRepository, Mockito.never()).save(any(ClanMembership.class)); } @@ -321,7 +321,7 @@ public void acceptPlayerInvitationTokenWrongPlayer() throws IOException { instance.acceptPlayerInvitationToken(stringToken, null); fail(); } catch (ApiException e) { - assertThat(e, apiExceptionWithCode(ErrorCode.CLAN_ACCEPT_WRONG_PLAYER)); + assertThat(e, hasErrorCode(ErrorCode.CLAN_ACCEPT_WRONG_PLAYER)); } verify(clanMembershipRepository, Mockito.never()).save(any(ClanMembership.class)); } @@ -352,7 +352,7 @@ public void acceptPlayerInvitationTokenPlayerIAlreadyInAClan() throws IOExceptio instance.acceptPlayerInvitationToken(stringToken, null); fail(); } catch (ApiException e) { - assertThat(e, apiExceptionWithCode(ErrorCode.CLAN_ACCEPT_PLAYER_IN_A_CLAN)); + assertThat(e, hasErrorCode(ErrorCode.CLAN_ACCEPT_PLAYER_IN_A_CLAN)); } verify(clanMembershipRepository, Mockito.never()).save(any(ClanMembership.class)); } diff --git a/src/test/java/com/faforever/api/email/EmailServiceTest.java b/src/test/java/com/faforever/api/email/EmailServiceTest.java index 7e18dbc02..c400f14c1 100644 --- a/src/test/java/com/faforever/api/email/EmailServiceTest.java +++ b/src/test/java/com/faforever/api/email/EmailServiceTest.java @@ -3,7 +3,7 @@ import com.faforever.api.config.FafApiProperties; import com.faforever.api.config.FafApiProperties.PasswordReset; import com.faforever.api.config.FafApiProperties.Registration; -import com.faforever.api.error.ApiExceptionWithCode; +import com.faforever.api.error.ApiExceptionMatcher; import com.faforever.api.error.ErrorCode; import org.junit.Before; import org.junit.Rule; @@ -46,20 +46,20 @@ public void validateEmailAddress() throws Exception { @Test public void validateEmailAddressMissingAt() throws Exception { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.EMAIL_INVALID)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.EMAIL_INVALID)); instance.validateEmailAddress("testexample.com"); } @Test public void validateEmailAddressMissingTld() throws Exception { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.EMAIL_INVALID)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.EMAIL_INVALID)); instance.validateEmailAddress("test@example"); } @Test public void validateEmailAddressBlacklisted() throws Exception { when(domainBlacklistRepository.existsByDomain("example.com")).thenReturn(true); - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.EMAIL_BLACKLISTED)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.EMAIL_BLACKLISTED)); instance.validateEmailAddress("test@example.com"); } diff --git a/src/test/java/com/faforever/api/error/ApiExceptionMatcher.java b/src/test/java/com/faforever/api/error/ApiExceptionMatcher.java new file mode 100644 index 000000000..24b15b7d3 --- /dev/null +++ b/src/test/java/com/faforever/api/error/ApiExceptionMatcher.java @@ -0,0 +1,33 @@ +package com.faforever.api.error; + +import org.hamcrest.FeatureMatcher; +import org.hamcrest.Matcher; + +import java.util.Arrays; + +import static org.hamcrest.Matchers.arrayContainingInAnyOrder; + +public final class ApiExceptionMatcher { + + public static Matcher hasErrorCodes(ErrorCode... errorCodes) { + return new FeatureMatcher(arrayContainingInAnyOrder(errorCodes), "error codes", "error codes") { + @Override + protected ErrorCode[] featureValueOf(ApiException actual) { + return Arrays.stream(actual.getErrors()) + .map(Error::getErrorCode) + .toArray(ErrorCode[]::new); + } + }; + } + + public static Matcher hasErrorCode(ErrorCode errorCode) { + return new FeatureMatcher(arrayContainingInAnyOrder(errorCode), "error code", "error code") { + @Override + protected ErrorCode[] featureValueOf(ApiException actual) { + return Arrays.stream(actual.getErrors()) + .map(Error::getErrorCode) + .toArray(ErrorCode[]::new); + } + }; + } +} diff --git a/src/test/java/com/faforever/api/error/ApiExceptionWithCode.java b/src/test/java/com/faforever/api/error/ApiExceptionWithCode.java deleted file mode 100644 index 051b0efab..000000000 --- a/src/test/java/com/faforever/api/error/ApiExceptionWithCode.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.faforever.api.error; - -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; - -public final class ApiExceptionWithCode extends BaseMatcher { - - private final ErrorCode errorCode; - - private ApiExceptionWithCode(ErrorCode errorCode) { - this.errorCode = errorCode; - } - - @Override - public void describeTo(Description description) { - description.appendText("an ApiException with exactly one error: " + errorCode); - } - - @Override - public boolean matches(Object item) { - ApiException apiException = (ApiException) item; - return apiException.getErrors().length == 1 - && apiException.getErrors()[0].getErrorCode() == errorCode; - } - - public static ApiExceptionWithCode apiExceptionWithCode(ErrorCode errorCode) { - return new ApiExceptionWithCode(errorCode); - } -} diff --git a/src/test/java/com/faforever/api/error/ApiExceptionWithMultipleCodes.java b/src/test/java/com/faforever/api/error/ApiExceptionWithMultipleCodes.java deleted file mode 100644 index e844151d4..000000000 --- a/src/test/java/com/faforever/api/error/ApiExceptionWithMultipleCodes.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.faforever.api.error; - -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; - -import java.util.Arrays; - -public final class ApiExceptionWithMultipleCodes extends BaseMatcher { - - private final ErrorCode[] errorCode; - - private ApiExceptionWithMultipleCodes(ErrorCode... errorCode) { - this.errorCode = errorCode; - } - - @Override - public void describeTo(Description description) { - description.appendText("an ApiException with multiple errors: " + Arrays.toString(errorCode)); - } - - @Override - public boolean matches(Object item) { - ApiException apiException = (ApiException) item; - return Arrays.deepEquals(errorCode, - Arrays.asList(apiException.getErrors()).stream().map(Error::getErrorCode).toArray()); - - } - - public static ApiExceptionWithMultipleCodes apiExceptionWithCode(ErrorCode... errorCode) { - return new ApiExceptionWithMultipleCodes(errorCode); - } -} diff --git a/src/test/java/com/faforever/api/map/MapServiceTest.java b/src/test/java/com/faforever/api/map/MapServiceTest.java index a5eec1518..257ac0993 100644 --- a/src/test/java/com/faforever/api/map/MapServiceTest.java +++ b/src/test/java/com/faforever/api/map/MapServiceTest.java @@ -6,297 +6,374 @@ import com.faforever.api.data.domain.MapVersion; import com.faforever.api.data.domain.Player; import com.faforever.api.error.ApiException; -import com.faforever.api.error.ApiExceptionWithMultipleCodes; +import com.faforever.api.error.Error; import com.faforever.api.error.ErrorCode; import com.faforever.commons.io.Unzipper; import com.google.common.io.ByteStreams; -import com.googlecode.zohhak.api.TestWith; -import com.googlecode.zohhak.api.runners.ZohhakRunner; import junitx.framework.FileAssert; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; -import org.springframework.util.FileSystemUtils; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.thymeleaf.util.StringUtils; -import java.io.BufferedInputStream; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.Stream; -import java.util.zip.ZipInputStream; -import static com.faforever.api.error.ApiExceptionWithCode.apiExceptionWithCode; +import static com.faforever.api.error.ApiExceptionMatcher.hasErrorCode; +import static com.faforever.api.error.ApiExceptionMatcher.hasErrorCodes; +import static com.faforever.api.error.ErrorCode.MAP_NAME_DOES_NOT_START_WITH_LETTER; +import static com.faforever.api.error.ErrorCode.MAP_NAME_INVALID_CHARACTER; +import static com.faforever.api.error.ErrorCode.MAP_NAME_INVALID_MINUS_OCCURENCE; +import static com.faforever.api.error.ErrorCode.MAP_NAME_TOO_LONG; +import static com.faforever.api.error.ErrorCode.MAP_NAME_TOO_SHORT; +import static com.faforever.api.error.ErrorCode.MAP_SCRIPT_LINE_MISSING; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -@RunWith(ZohhakRunner.class) +@ExtendWith(MockitoExtension.class) public class MapServiceTest { - @Rule - public final TemporaryFolder temporaryDirectory = new TemporaryFolder(); - @Rule - public final TemporaryFolder finalDirectory = new TemporaryFolder(); - @Rule - public final ExpectedException expectedException = ExpectedException.none(); - private final MapRepository mapRepository = mock(MapRepository.class); - private final FafApiProperties fafApiProperties = mock(FafApiProperties.class); - private final ContentService contentService = mock(ContentService.class); - private final Player author = mock(Player.class); + private Path temporaryDirectory; + private Path finalDirectory; + + @Mock + private MapRepository mapRepository; + @Mock + private FafApiProperties fafApiProperties; + @Mock + private ContentService contentService; + @Mock + private Player author; + private MapService instance; private Map mapProperties; - @Before - public void setUp() { + @BeforeEach + void beforeEach() { instance = new MapService(fafApiProperties, mapRepository, contentService); - mapProperties = new Map() - .setTargetDirectory(finalDirectory.getRoot().toPath()) - .setDirectoryPreviewPathLarge(finalDirectory.getRoot().toPath().resolve("large")) - .setDirectoryPreviewPathSmall(finalDirectory.getRoot().toPath().resolve("small")); - when(fafApiProperties.getMap()).thenReturn(mapProperties); - when(contentService.createTempDir()).thenReturn(temporaryDirectory.getRoot().toPath()); } - @After - public void shutDown() { - if (Files.exists(temporaryDirectory.getRoot().toPath())) { - FileSystemUtils.deleteRecursively(temporaryDirectory.getRoot()); - } - if (Files.exists(temporaryDirectory.getRoot().toPath())) { - FileSystemUtils.deleteRecursively(finalDirectory.getRoot()); - } + private String loadMapAsString(String filename) throws IOException { + return new String(loadMapAsBytes(filename), StandardCharsets.UTF_8); } - @Test - public void zipFilenamealreadyExists() throws IOException { - Path clashedMap = finalDirectory.getRoot().toPath().resolve("sludge_test____.___..v0001.zip"); - assertTrue(clashedMap.toFile().createNewFile()); - String zipFilename = "scmp_037.zip"; - when(mapRepository.findOneByDisplayName(any())).thenReturn(Optional.empty()); - try (InputStream inputStream = loadMapResourceAsStream(zipFilename)) { - try { - byte[] mapData = ByteStreams.toByteArray(inputStream); - instance.uploadMap(mapData, zipFilename, author, true); - fail(); - } catch (ApiException e) { - assertThat(e, apiExceptionWithCode(ErrorCode.MAP_NAME_CONFLICT)); - } - verify(mapRepository, never()).save(any(com.faforever.api.data.domain.Map.class)); - } + private InputStream loadMapAsInputSteam(String filename) { + return MapServiceTest.class.getResourceAsStream("/maps/" + filename); } - @Test - public void emptyZip() throws IOException { - String zipFilename = "empty.zip"; - try (InputStream inputStream = loadMapResourceAsStream(zipFilename)) { - try { - byte[] mapData = ByteStreams.toByteArray(inputStream); - instance.uploadMap(mapData, zipFilename, author, true); - fail(); - } catch (ApiException e) { - assertThat(e, apiExceptionWithCode(ErrorCode.MAP_MISSING_MAP_FOLDER_INSIDE_ZIP)); - } - verify(mapRepository, never()).save(any(com.faforever.api.data.domain.Map.class)); + private byte[] loadMapAsBytes(String filename) throws IOException { + try (InputStream inputStream = MapServiceTest.class.getResourceAsStream("/maps/" + filename)) { + return ByteStreams.toByteArray(inputStream); } } - @Test - public void notCorrectAuthor() throws IOException { - String zipFilename = "scmp_037.zip"; - - Player me = new Player(); - me.setId(1); - Player bob = new Player(); - bob.setId(2); - - com.faforever.api.data.domain.Map map = new com.faforever.api.data.domain.Map().setAuthor(bob); - when(mapRepository.findOneByDisplayName(any())).thenReturn(Optional.of(map)); - try (InputStream inputStream = loadMapResourceAsStream(zipFilename)) { - try { - byte[] mapData = ByteStreams.toByteArray(inputStream); - instance.uploadMap(mapData, zipFilename, me, true); - fail(); - } catch (ApiException e) { - assertThat(e, apiExceptionWithCode(ErrorCode.MAP_NOT_ORIGINAL_AUTHOR)); - } - verify(mapRepository, never()).save(any(com.faforever.api.data.domain.Map.class)); + @Nested + class Validation { + + @ParameterizedTest + @ValueSource(strings = { + "Map1", + "map-2", + "A very but overall not really too long map name", + "Three - dashes - are - allowed" + }) + void testMapNameValid(String name) { + instance.validateMapName(name); } - } - @Test - public void versionExistsAlready() throws IOException { - String zipFilename = "scmp_037.zip"; - - Player me = new Player(); - me.setId(1); - - com.faforever.api.data.domain.Map map = new com.faforever.api.data.domain.Map() - .setAuthor(me) - .setVersions(Collections.singletonList(new MapVersion().setVersion(1))); - - when(mapRepository.findOneByDisplayName(any())).thenReturn(Optional.of(map)); - try (InputStream inputStream = loadMapResourceAsStream(zipFilename)) { - try { - byte[] mapData = ByteStreams.toByteArray(inputStream); - instance.uploadMap(mapData, zipFilename, me, true); - fail(); - } catch (ApiException e) { - assertThat(e, apiExceptionWithCode(ErrorCode.MAP_VERSION_EXISTS)); - } - verify(mapRepository, never()).save(any(com.faforever.api.data.domain.Map.class)); + @Test + void testMapNameMultiErrorsButNoScenarioValidation() { + String mapName = "123Invalid-in$-many-ways-atOnce" + StringUtils.repeat("x", 50); + ApiException result = assertThrows(ApiException.class, () -> instance.validateMapName(mapName)); + List errorCodes = Arrays.stream(result.getErrors()).map(Error::getErrorCode).collect(Collectors.toList()); + assertThat(errorCodes, hasItems(MAP_NAME_INVALID_CHARACTER, MAP_NAME_TOO_LONG, MAP_NAME_INVALID_MINUS_OCCURENCE)); + assertThat(errorCodes, not(contains(MAP_SCRIPT_LINE_MISSING))); + } + + @ParameterizedTest + @ValueSource(strings = { + "Map.With.Dots", + "Map/With/Slashes", + "Map,With,Commas", + "Map|With|Pipes", + "SomeMore:", + "SomeMore(", + "SomeMore)", + "SomeMore[", + "SomeMore]", + "SomeMore?", + "SomeMore$", + }) + void testMapNameInvalidChars(String name) { + ApiException result = assertThrows(ApiException.class, () -> instance.validateMapName(name)); + assertThat(result, hasErrorCode(MAP_NAME_INVALID_CHARACTER)); + } + + @Test + void testMapNameTooManyDashes() { + ApiException result = assertThrows(ApiException.class, () -> instance.validateMapName("More-than-three-dashes-invalid")); + assertThat(result, hasErrorCode(MAP_NAME_INVALID_MINUS_OCCURENCE)); + } + + @Test + void testMapNameToShort() { + ApiException result = assertThrows(ApiException.class, () -> instance.validateMapName("x")); + assertThat(result, hasErrorCode(MAP_NAME_TOO_SHORT)); + } + + @Test + void testMapNameToLong() { + ApiException result = assertThrows(ApiException.class, () -> instance.validateMapName(StringUtils.repeat("x", 51))); + assertThat(result, hasErrorCode(MAP_NAME_TOO_LONG)); + } + + @Test + void testMapNameStartsInvalid() { + ApiException result = assertThrows(ApiException.class, () -> instance.validateMapName("123x")); + assertThat(result, hasErrorCode(MAP_NAME_DOES_NOT_START_WITH_LETTER)); + } + + @Test + void testScenarioLuaSuccessWithoutVersion() throws Exception { + instance.validateScenarioLua(loadMapAsString("scenario/valid_without_version_scenario.lua")); + } + + @Test + void testScenarioLuaSuccessWithVersion() throws Exception { + instance.validateScenarioLua(loadMapAsString("scenario/valid_with_version_scenario.lua")); + } + + @Test + void testScenarioLuaEmptyScenarioLua() { + ApiException result = assertThrows(ApiException.class, () -> instance.validateScenarioLua("")); + assertThat(result, hasErrorCodes( + ErrorCode.PARSING_LUA_FILE_FAILED + )); + } + + @Test + void testScenarioLuaMissingAllLines() { + ApiException result = assertThrows(ApiException.class, () -> + instance.validateScenarioLua(loadMapAsString("scenario/valid_empty.lua"))); + assertThat(result, hasErrorCodes(ErrorCode.MAP_NAME_MISSING)); + } + + @Test + void testScenarioLuaWrongMapLine() { + ApiException result = assertThrows(ApiException.class, () -> + instance.validateScenarioLua(loadMapAsString("scenario/missing_map_variable_scenario.lua"))); + + assertThat(result, hasErrorCodes( + ErrorCode.MAP_SCRIPT_LINE_MISSING + )); + + assertThat(result.getErrors().length, is(1)); + Error mapLineError = Arrays.stream(result.getErrors()) + .filter(error -> error.getErrorCode() == MAP_SCRIPT_LINE_MISSING) + .findFirst().get(); + + assertThat(mapLineError.getArgs().length, is(1)); + assertThat(mapLineError.getArgs()[0], is("map = '/maps/mirage/mirage.scmap'")); } } - @TestWith({"without_savelua.zip", "without_scenariolua.zip", "without_scmap.zip", "without_scriptlua.zip"}) - public void fileIsMissingInsideZip(String zipFilename) throws IOException { - try (InputStream inputStream = loadMapResourceAsStream(zipFilename)) { - try { - byte[] mapData = ByteStreams.toByteArray(inputStream); - instance.uploadMap(mapData, zipFilename, author, true); - fail(); - } catch (ApiException e) { - assertThat(e, apiExceptionWithCode(ErrorCode.MAP_FILE_INSIDE_ZIP_MISSING)); - } + @Nested + class WithTempDir { + @TempDir + Path baseTemporaryDirectory; + + @BeforeEach + void setUp() throws Exception { + temporaryDirectory = Files.createDirectory(baseTemporaryDirectory.resolve("temp")); + finalDirectory = Files.createDirectory(baseTemporaryDirectory.resolve("final")); + + mapProperties = new Map() + .setTargetDirectory(finalDirectory) + .setDirectoryPreviewPathLarge(finalDirectory.resolve("large")) + .setDirectoryPreviewPathSmall(finalDirectory.resolve("small")); + when(contentService.createTempDir()).thenReturn(temporaryDirectory); + } + + @ParameterizedTest(name = "Expecting ErrorCode.{0} with file ''{1}''") + @CsvSource(value = { + "MAP_MISSING_MAP_FOLDER_INSIDE_ZIP,empty.zip", + "MAP_FIRST_TEAM_FFA,wrong_team_name.zip", + "MAP_INVALID_ZIP,invalid_zip.zip", // map with more than 1 root folders in zip + "MAP_NAME_INVALID_CHARACTER,map_name_invalid_character.zip", + "MAP_FILE_INSIDE_ZIP_MISSING,without_savelua.zip", + "MAP_FILE_INSIDE_ZIP_MISSING,without_scenariolua.zip", + "MAP_FILE_INSIDE_ZIP_MISSING,without_scmap.zip", + "MAP_FILE_INSIDE_ZIP_MISSING,without_scriptlua.zip", + }) + void uploadFails(String errorCodeEnumValue, String fileName) { + uploadFails(ErrorCode.valueOf(errorCodeEnumValue), fileName); + } + + void uploadFails(ErrorCode expectedErrorCode, String fileName) { + InputStream mapData = loadMapAsInputSteam(fileName); + ApiException result = assertThrows(ApiException.class, () -> instance.uploadMap(mapData, fileName, author, true)); + assertThat(result, hasErrorCode(expectedErrorCode)); verify(mapRepository, never()).save(any(com.faforever.api.data.domain.Map.class)); } - } - @Test - public void battleTypeNotFFA() throws IOException { - String zipFilename = "wrong_team_name.zip"; - when(mapRepository.findOneByDisplayName(any())).thenReturn(Optional.empty()); - try (InputStream inputStream = loadMapResourceAsStream(zipFilename)) { - byte[] mapData = ByteStreams.toByteArray(inputStream); - expectedException.expect(apiExceptionWithCode(ErrorCode.MAP_FIRST_TEAM_FFA)); - instance.uploadMap(mapData, zipFilename, author, true); + @Test + void zipFilenameAlreadyExists() throws IOException { + when(fafApiProperties.getMap()).thenReturn(mapProperties); + Path clashedMap = finalDirectory.resolve("command_conquer_rush.v0007.zip"); + assertTrue(clashedMap.toFile().createNewFile()); + + uploadFails(ErrorCode.MAP_NAME_CONFLICT, "command_conquer_rush.v0007.zip"); } - verify(mapRepository, never()).save(any(com.faforever.api.data.domain.Map.class)); - } - @Test - public void invalidScenario() throws IOException { - String zipFilename = "invalid_scenario.zip"; - when(mapRepository.findOneByDisplayName(any())).thenReturn(Optional.empty()); - try (InputStream inputStream = loadMapResourceAsStream(zipFilename)) { - byte[] mapData = ByteStreams.toByteArray(inputStream); - expectedException.expect(ApiExceptionWithMultipleCodes.apiExceptionWithCode( - ErrorCode.MAP_NAME_MISSING, - ErrorCode.MAP_DESCRIPTION_MISSING, - ErrorCode.MAP_FIRST_TEAM_FFA, - ErrorCode.MAP_TYPE_MISSING, - ErrorCode.MAP_SIZE_MISSING, - ErrorCode.MAP_VERSION_MISSING)); - instance.uploadMap(mapData, zipFilename, author, true); + @Test + void notCorrectAuthor() { + when(fafApiProperties.getMap()).thenReturn(mapProperties); + + Player me = new Player(); + me.setId(1); + Player bob = new Player(); + bob.setId(2); + + com.faforever.api.data.domain.Map map = new com.faforever.api.data.domain.Map().setAuthor(bob); + when(mapRepository.findOneByDisplayName(any())).thenReturn(Optional.of(map)); + + uploadFails(ErrorCode.MAP_NOT_ORIGINAL_AUTHOR, "command_conquer_rush.v0007.zip"); } - verify(mapRepository, never()).save(any(com.faforever.api.data.domain.Map.class)); - } - @Test - public void mapWithMoreRootFoldersInZip() throws IOException { - String zipFilename = "scmp_037_invalid.zip"; - when(mapRepository.findOneByDisplayName(any())).thenReturn(Optional.empty()); - try (InputStream inputStream = loadMapResourceAsStream(zipFilename)) { - byte[] mapData = ByteStreams.toByteArray(inputStream); - try { - instance.uploadMap(mapData, zipFilename, author, true); - } catch (ApiException e) { - assertThat(e, apiExceptionWithCode(ErrorCode.MAP_INVALID_ZIP)); - } + @Test + void versionExistsAlready() { + when(fafApiProperties.getMap()).thenReturn(mapProperties); + + com.faforever.api.data.domain.Map map = new com.faforever.api.data.domain.Map() + .setDisplayName("someName") + .setAuthor(author) + .setVersions(Collections.singletonList(new MapVersion().setVersion(7))); + + when(mapRepository.findOneByDisplayName(any())).thenReturn(Optional.of(map)); + + uploadFails(ErrorCode.MAP_VERSION_EXISTS, "command_conquer_rush.v0007.zip"); } - } - @Test - public void uploadMapWithInvalidCharactersName() throws IOException { - String zipFilename = "scmp_037_no_ascii.zip"; - when(mapRepository.findOneByDisplayName(any())).thenReturn(Optional.empty()); - try (InputStream inputStream = loadMapResourceAsStream(zipFilename)) { - byte[] mapData = ByteStreams.toByteArray(inputStream); - - Path tmpDir = temporaryDirectory.getRoot().toPath(); - try { - instance.uploadMap(mapData, zipFilename, author, true); - } catch (ApiException e) { - assertThat(e, apiExceptionWithCode(ErrorCode.MAP_NAME_INVALID)); - } + @Test + void noMapName() { + String zipFilename = "no_map_name.zip"; + InputStream mapData = loadMapAsInputSteam(zipFilename); + ApiException result = assertThrows(ApiException.class, () -> instance.uploadMap(mapData, zipFilename, author, true)); + assertThat(result, hasErrorCodes(ErrorCode.MAP_NAME_MISSING)); + verify(mapRepository, never()).save(any(com.faforever.api.data.domain.Map.class)); + } + @Test + void adaptiveFilesMissing() { + String zipFilename = "adaptive_map_files_missing.zip"; + InputStream mapData = loadMapAsInputSteam(zipFilename); + ApiException result = assertThrows(ApiException.class, () -> instance.uploadMap(mapData, zipFilename, author, true)); + assertThat(result, hasErrorCodes( + ErrorCode.MAP_FILE_INSIDE_ZIP_MISSING, + ErrorCode.MAP_FILE_INSIDE_ZIP_MISSING + )); + verify(mapRepository, never()).save(any(com.faforever.api.data.domain.Map.class)); + } + + @Test + void invalidScenario() { + String zipFilename = "invalid_scenario.zip"; + InputStream mapData = loadMapAsInputSteam(zipFilename); + ApiException result = assertThrows(ApiException.class, () -> instance.uploadMap(mapData, zipFilename, author, true)); + assertThat(result, hasErrorCodes( + ErrorCode.MAP_SCRIPT_LINE_MISSING, + ErrorCode.MAP_SCRIPT_LINE_MISSING, + ErrorCode.MAP_SCRIPT_LINE_MISSING, + ErrorCode.MAP_DESCRIPTION_MISSING, + ErrorCode.MAP_FIRST_TEAM_FFA, + ErrorCode.MAP_TYPE_MISSING, + ErrorCode.MAP_SIZE_MISSING, + ErrorCode.MAP_VERSION_MISSING, + ErrorCode.NO_RUSH_RADIUS_MISSING)); verify(mapRepository, never()).save(any(com.faforever.api.data.domain.Map.class)); } - } - @Test - public void positiveUploadTest() throws Exception { - String zipFilename = "scmp_037.zip"; - when(mapRepository.findOneByDisplayName(any())).thenReturn(Optional.empty()); - try (InputStream inputStream = loadMapResourceAsStream(zipFilename)) { - byte[] mapData = ByteStreams.toByteArray(inputStream); + @Test + void positiveUploadTest() throws Exception { + String zipFilename = "command_conquer_rush.v0007.zip"; + when(fafApiProperties.getMap()).thenReturn(mapProperties); + when(mapRepository.findOneByDisplayName(any())).thenReturn(Optional.empty()); + InputStream mapData = loadMapAsInputSteam(zipFilename); - Path tmpDir = temporaryDirectory.getRoot().toPath(); + Path tmpDir = temporaryDirectory; instance.uploadMap(mapData, zipFilename, author, true); ArgumentCaptor mapCaptor = ArgumentCaptor.forClass(com.faforever.api.data.domain.Map.class); - verify(mapRepository, Mockito.times(1)).save(mapCaptor.capture()); - assertEquals("Sludge_Test &!$.#+/.", mapCaptor.getValue().getDisplayName()); + verify(mapRepository).save(mapCaptor.capture()); + assertEquals("Command Conquer Rush", mapCaptor.getValue().getDisplayName()); assertEquals("skirmish", mapCaptor.getValue().getMapType()); assertEquals("FFA", mapCaptor.getValue().getBattleType()); assertEquals(1, mapCaptor.getValue().getVersions().size()); MapVersion mapVersion = mapCaptor.getValue().getVersions().get(0); - assertEquals("The thick, brackish water clings to everything, staining anything it touches. If it weren't for this planet's proximity to the Quarantine Zone, no one would ever bother coming here.", mapVersion.getDescription()); - assertEquals(1, mapVersion.getVersion()); + assertEquals("For example on map crazyrush. Universal Command Conquer 3 modification by RuCommunity. Prealpha test", mapVersion.getDescription()); + assertEquals(7, mapVersion.getVersion()); assertEquals(256, mapVersion.getHeight()); assertEquals(256, mapVersion.getWidth()); - assertEquals(3, mapVersion.getMaxPlayers()); - assertEquals("maps/sludge_test____.___..v0001.zip", mapVersion.getFilename()); + assertEquals(8, mapVersion.getMaxPlayers()); + assertEquals("maps/command_conquer_rush.v0007.zip", mapVersion.getFilename()); assertFalse(Files.exists(tmpDir)); - Path generatedFile = finalDirectory.getRoot().toPath().resolve("sludge_test____.___..v0001.zip"); + Path generatedFile = finalDirectory.resolve("command_conquer_rush.v0007.zip"); assertTrue(Files.exists(generatedFile)); - Path generatedFiles = finalDirectory.getRoot().toPath().resolve("generated_files"); + Path generatedFiles = finalDirectory.resolve("generated_files"); Unzipper.from(generatedFile).to(generatedFiles).unzip(); - Path expectedFiles = finalDirectory.getRoot().toPath().resolve("expected_files"); - Unzipper.from(generatedFile) - .to(expectedFiles) - .unzip(); + Path expectedFiles = finalDirectory.resolve("expected_files"); + Unzipper.from(generatedFile) + .to(expectedFiles) + .unzip(); - expectedFiles = expectedFiles.resolve("sludge_test____.___..v0001"); + expectedFiles = expectedFiles.resolve("command_conquer_rush.v0007"); try (Stream fileStream = Files.list(expectedFiles)) { - assertEquals(fileStream.count(), (long) 4); + assertEquals(fileStream.count(), 4); } try (Stream fileStream = Files.list(expectedFiles)) { - Path finalGeneratedFile = generatedFiles.resolve("sludge_test____.___..v0001"); + Path finalGeneratedFile = generatedFiles.resolve("command_conquer_rush.v0007"); fileStream.forEach(expectedFile -> FileAssert.assertEquals("Difference in " + expectedFile.getFileName().toString(), expectedFile.toFile(), finalGeneratedFile.resolve(expectedFile.getFileName().toString()).toFile()) ); - assertTrue(Files.exists(mapProperties.getDirectoryPreviewPathLarge().resolve("sludge_test____.___..v0001.png"))); - assertTrue(Files.exists(mapProperties.getDirectoryPreviewPathSmall().resolve("sludge_test____.___..v0001.png"))); + assertTrue(Files.exists(mapProperties.getDirectoryPreviewPathLarge().resolve("command_conquer_rush.v0007.png"))); + assertTrue(Files.exists(mapProperties.getDirectoryPreviewPathSmall().resolve("command_conquer_rush.v0007.png"))); } } } - - private InputStream loadMapResourceAsStream(String filename) { - return MapServiceTest.class.getResourceAsStream("/maps/" + filename); - } } diff --git a/src/test/java/com/faforever/api/map/MapsControllerTest.java b/src/test/java/com/faforever/api/map/MapsControllerTest.java index 9cd25f9fd..c8e6eb889 100644 --- a/src/test/java/com/faforever/api/map/MapsControllerTest.java +++ b/src/test/java/com/faforever/api/map/MapsControllerTest.java @@ -6,14 +6,14 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.io.ByteStreams; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import javax.inject.Inject; @@ -23,11 +23,11 @@ import static org.hamcrest.Matchers.is; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@RunWith(SpringRunner.class) +@ExtendWith(SpringExtension.class) @WebMvcTest(MapsController.class) @Import(TestWebSecurityConfig.class) public class MapsControllerTest { @@ -43,13 +43,13 @@ public class MapsControllerTest { private ObjectMapper objectMapper; @Inject - public void init(MockMvc mvc) { + void init(MockMvc mvc) { this.mvc = mvc; } @Test - public void fileMissing() throws Exception { - this.mvc.perform(fileUpload("/maps/upload")) + void fileMissing() throws Exception { + this.mvc.perform(multipart("/maps/upload")) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.errors", hasSize(1))) .andExpect(jsonPath("$.errors[0].status", is(HttpStatus.BAD_REQUEST.toString()))) @@ -58,10 +58,10 @@ public void fileMissing() throws Exception { } @Test - public void jsonMetaDataMissing() throws Exception { + void jsonMetaDataMissing() throws Exception { MockMultipartFile file = new MockMultipartFile("file", "filename.txt", "text/plain", "some xml".getBytes()); - this.mvc.perform(fileUpload("/maps/upload") + this.mvc.perform(multipart("/maps/upload") .file(file)) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.errors", hasSize(1))) @@ -71,7 +71,7 @@ public void jsonMetaDataMissing() throws Exception { } @Test - public void successUpload() throws Exception { + void successUpload() throws Exception { FafApiProperties props = new FafApiProperties(); String jsonString = "{}"; ObjectMapper mapper = new ObjectMapper(); @@ -79,14 +79,14 @@ public void successUpload() throws Exception { when(fafApiProperties.getMap()).thenReturn(props.getMap()); when(objectMapper.readTree(anyString())).thenReturn(node); - String zipFile = "scmp_037.zip"; + String zipFile = "command_conquer_rush.v0007.zip"; try (InputStream inputStream = loadMapResourceAsStream(zipFile)) { MockMultipartFile file = new MockMultipartFile("file", zipFile, "application/zip", ByteStreams.toByteArray(inputStream)); - this.mvc.perform(fileUpload("/maps/upload") + this.mvc.perform(multipart("/maps/upload") .file(file) .param("metadata", jsonString) ).andExpect(status().isOk()); diff --git a/src/test/java/com/faforever/api/mod/ModServiceTest.java b/src/test/java/com/faforever/api/mod/ModServiceTest.java index 53b1c797c..46996044e 100644 --- a/src/test/java/com/faforever/api/mod/ModServiceTest.java +++ b/src/test/java/com/faforever/api/mod/ModServiceTest.java @@ -4,7 +4,7 @@ import com.faforever.api.data.domain.Mod; import com.faforever.api.data.domain.ModVersion; import com.faforever.api.data.domain.Player; -import com.faforever.api.error.ApiExceptionWithCode; +import com.faforever.api.error.ApiExceptionMatcher; import com.faforever.api.error.ErrorCode; import org.jetbrains.annotations.NotNull; import org.junit.Before; @@ -103,7 +103,7 @@ public void testExistingUid() throws Exception { Path uploadedFile = prepareMod(TEST_MOD); when(modVersionRepository.existsByUid("26778D4E-BA75-5CC2-CBA8-63795BDE74AA")).thenReturn(true); - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.MOD_UID_EXISTS)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.MOD_UID_EXISTS)); instance.processUploadedMod(uploadedFile, new Player()); } @@ -116,7 +116,7 @@ public void testNotOriginalUploader() throws Exception { when(modRepository.existsByDisplayNameAndUploaderIsNot("No Friendly Fire", uploader)).thenReturn(true); when(modRepository.findOneByDisplayName("No Friendly Fire")).thenReturn(Optional.of(new Mod())); - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.MOD_NOT_ORIGINAL_AUTHOR)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.MOD_NOT_ORIGINAL_AUTHOR)); instance.processUploadedMod(uploadedFile, uploader); } @@ -127,7 +127,7 @@ public void testInvalidFileStructure() throws Exception { Player uploader = new Player(); - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.MOD_STRUCTURE_INVALID)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.MOD_STRUCTURE_INVALID)); instance.processUploadedMod(uploadedFile, uploader); } diff --git a/src/test/java/com/faforever/api/security/FafTokenServiceTest.java b/src/test/java/com/faforever/api/security/FafTokenServiceTest.java index 12f190de5..c0da2e39e 100644 --- a/src/test/java/com/faforever/api/security/FafTokenServiceTest.java +++ b/src/test/java/com/faforever/api/security/FafTokenServiceTest.java @@ -1,7 +1,7 @@ package com.faforever.api.security; import com.faforever.api.config.FafApiProperties; -import com.faforever.api.error.ApiExceptionWithCode; +import com.faforever.api.error.ApiExceptionMatcher; import com.faforever.api.error.ErrorCode; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -98,7 +98,7 @@ public void resolveTokenWithAttributes() throws Exception { @Test public void resolveTokenExpired() throws Exception { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.TOKEN_EXPIRED)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.TOKEN_EXPIRED)); String token = instance.createToken(FafTokenType.REGISTRATION, Duration.ofSeconds(-1), Collections.emptyMap()); instance.resolveToken(FafTokenType.REGISTRATION, token); @@ -106,7 +106,7 @@ public void resolveTokenExpired() throws Exception { @Test public void resolveTokenInvalidType() throws Exception { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.TOKEN_INVALID)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.TOKEN_INVALID)); String token = instance.createToken(FafTokenType.REGISTRATION, Duration.ofSeconds(-1), Collections.emptyMap()); instance.resolveToken(FafTokenType.PASSWORD_RESET, token); @@ -114,7 +114,7 @@ public void resolveTokenInvalidType() throws Exception { @Test public void resolveGibberishToken() throws Exception { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.TOKEN_INVALID)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.TOKEN_INVALID)); instance.resolveToken(FafTokenType.PASSWORD_RESET, "gibberish token"); } } diff --git a/src/test/java/com/faforever/api/user/UserServiceTest.java b/src/test/java/com/faforever/api/user/UserServiceTest.java index 2ffb151d2..4b52a54ee 100644 --- a/src/test/java/com/faforever/api/user/UserServiceTest.java +++ b/src/test/java/com/faforever/api/user/UserServiceTest.java @@ -6,7 +6,7 @@ import com.faforever.api.data.domain.NameRecord; import com.faforever.api.data.domain.User; import com.faforever.api.email.EmailService; -import com.faforever.api.error.ApiExceptionWithCode; +import com.faforever.api.error.ApiExceptionMatcher; import com.faforever.api.error.ErrorCode; import com.faforever.api.mautic.MauticService; import com.faforever.api.player.PlayerRepository; @@ -138,40 +138,40 @@ public void register() { @Test public void registerEmailAlreadyRegistered() { when(userRepository.existsByEmail(TEST_CURRENT_EMAIL)).thenReturn(true); - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.EMAIL_REGISTERED)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.EMAIL_REGISTERED)); instance.register("junit", TEST_CURRENT_EMAIL, TEST_CURRENT_PASSWORD); } @Test public void registerInvalidUsernameWithComma() { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.USERNAME_INVALID)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.USERNAME_INVALID)); instance.register("junit,", TEST_CURRENT_EMAIL, TEST_CURRENT_PASSWORD); } @Test public void registerInvalidUsernameStartsUnderscore() { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.USERNAME_INVALID)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.USERNAME_INVALID)); instance.register("_junit", TEST_CURRENT_EMAIL, TEST_CURRENT_PASSWORD); } @Test public void registerInvalidUsernameTooShort() { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.USERNAME_INVALID)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.USERNAME_INVALID)); instance.register("ju", TEST_CURRENT_EMAIL, TEST_CURRENT_PASSWORD); } @Test public void registerUsernameTaken() { when(userRepository.existsByLogin("junit")).thenReturn(true); - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.USERNAME_TAKEN)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.USERNAME_TAKEN)); instance.register("junit", TEST_CURRENT_EMAIL, TEST_CURRENT_PASSWORD); } @Test public void registerUsernameReserved() { when(nameRecordRepository.getLastUsernameOwnerWithinMonths(any(), anyInt())).thenReturn(Optional.of(1)); - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.USERNAME_RESERVED)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.USERNAME_RESERVED)); instance.register("junit", TEST_CURRENT_EMAIL, TEST_CURRENT_PASSWORD); } @@ -215,7 +215,7 @@ public void changePassword() { @Test public void changePasswordInvalidPassword() { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.PASSWORD_CHANGE_FAILED_WRONG_PASSWORD)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.PASSWORD_CHANGE_FAILED_WRONG_PASSWORD)); User user = createUser(TEST_USERID, TEST_USERNAME, INVALID_PASSWORD, TEST_CURRENT_EMAIL); instance.changePassword(TEST_CURRENT_PASSWORD, TEST_NEW_PASSWORD, user); @@ -236,7 +236,7 @@ public void changeEmail() { @Test public void changeEmailInvalidPassword() { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.EMAIL_CHANGE_FAILED_WRONG_PASSWORD)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.EMAIL_CHANGE_FAILED_WRONG_PASSWORD)); User user = createUser(TEST_USERID, TEST_USERNAME, INVALID_PASSWORD, TEST_CURRENT_EMAIL); instance.changeEmail(TEST_CURRENT_PASSWORD, TEST_NEW_PASSWORD, user, IP_ADDRESS); @@ -260,7 +260,7 @@ public void changeLogin() { @Test public void changeLoginWithUsernameInUse() { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.USERNAME_TAKEN)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.USERNAME_TAKEN)); User user = createUser(TEST_USERID, TEST_USERNAME, TEST_CURRENT_PASSWORD, TEST_CURRENT_EMAIL); when(userRepository.existsByLogin(TEST_USERNAME_CHANGED)).thenReturn(true); @@ -269,7 +269,7 @@ public void changeLoginWithUsernameInUse() { @Test public void changeLoginWithUsernameInUseButForced() { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.USERNAME_TAKEN)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.USERNAME_TAKEN)); User user = createUser(TEST_USERID, TEST_USERNAME, TEST_CURRENT_PASSWORD, TEST_CURRENT_EMAIL); when(userRepository.existsByLogin(TEST_USERNAME_CHANGED)).thenReturn(true); @@ -278,7 +278,7 @@ public void changeLoginWithUsernameInUseButForced() { @Test public void changeLoginWithInvalidUsername() { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.USERNAME_INVALID)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.USERNAME_INVALID)); User user = createUser(TEST_USERID, TEST_USERNAME, TEST_CURRENT_PASSWORD, TEST_CURRENT_EMAIL); instance.changeLogin("$%&", user, IP_ADDRESS); @@ -286,7 +286,7 @@ public void changeLoginWithInvalidUsername() { @Test public void changeLoginWithInvalidUsernameButForced() { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.USERNAME_INVALID)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.USERNAME_INVALID)); User user = createUser(TEST_USERID, TEST_USERNAME, TEST_CURRENT_PASSWORD, TEST_CURRENT_EMAIL); instance.changeLoginForced("$%&", user, IP_ADDRESS); @@ -294,7 +294,7 @@ public void changeLoginWithInvalidUsernameButForced() { @Test public void changeLoginTooEarly() { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.USERNAME_CHANGE_TOO_EARLY)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.USERNAME_CHANGE_TOO_EARLY)); when(nameRecordRepository.getDaysSinceLastNewRecord(anyInt(), anyInt())).thenReturn(Optional.of(BigInteger.valueOf(5))); User user = createUser(TEST_USERID, TEST_USERNAME, TEST_CURRENT_PASSWORD, TEST_CURRENT_EMAIL); @@ -310,7 +310,7 @@ public void changeLoginTooEarlyButForce() { @Test public void changeLoginUsernameReserved() { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.USERNAME_RESERVED)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.USERNAME_RESERVED)); when(nameRecordRepository.getLastUsernameOwnerWithinMonths(any(), anyInt())).thenReturn(Optional.of(TEST_USERID + 1)); User user = createUser(TEST_USERID, TEST_USERNAME, TEST_CURRENT_PASSWORD, TEST_CURRENT_EMAIL); @@ -384,7 +384,7 @@ public void resetPasswordByEmail() { @Test public void resetPasswordUnknownUsernameAndEmail() { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.UNKNOWN_IDENTIFIER)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.UNKNOWN_IDENTIFIER)); when(userRepository.findOneByEmail(TEST_CURRENT_EMAIL)).thenReturn(Optional.empty()); when(userRepository.findOneByEmail(TEST_CURRENT_EMAIL)).thenReturn(Optional.empty()); @@ -438,7 +438,7 @@ public void buildSteamLinkUrl() { @Test public void buildSteamLinkUrlAlreadLinked() { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.STEAM_ID_UNCHANGEABLE)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.STEAM_ID_UNCHANGEABLE)); User user = createUser(TEST_USERID, TEST_USERNAME, TEST_CURRENT_PASSWORD, TEST_CURRENT_EMAIL); user.setSteamId(STEAM_ID); @@ -469,7 +469,7 @@ public void linkToSteam() { @Test public void linkToSteamUnknownUser() { - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.TOKEN_INVALID)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.TOKEN_INVALID)); when(fafTokenService.resolveToken(FafTokenType.LINK_TO_STEAM, TOKEN_VALUE)).thenReturn(ImmutableMap.of(KEY_USER_ID, "5")); when(userRepository.findById(5)).thenReturn(Optional.empty()); diff --git a/src/test/java/com/faforever/api/voting/VotingServiceTest.java b/src/test/java/com/faforever/api/voting/VotingServiceTest.java index 1d6624ec5..8523195d6 100644 --- a/src/test/java/com/faforever/api/voting/VotingServiceTest.java +++ b/src/test/java/com/faforever/api/voting/VotingServiceTest.java @@ -7,7 +7,7 @@ import com.faforever.api.data.domain.VotingQuestion; import com.faforever.api.data.domain.VotingSubject; import com.faforever.api.error.ApiException; -import com.faforever.api.error.ApiExceptionWithCode; +import com.faforever.api.error.ApiExceptionMatcher; import com.faforever.api.error.ErrorCode; import com.faforever.api.game.GamePlayerStatsRepository; import org.junit.Before; @@ -79,7 +79,7 @@ public void saveVoteInvalidVoteId() { vote.setVotingSubject(votingSubject); - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.VOTING_SUBJECT_DOES_NOT_EXIST)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.VOTING_SUBJECT_DOES_NOT_EXIST)); instance.saveVote(vote, new Player()); verify(voteRepository, never()).save(vote); @@ -181,7 +181,7 @@ public void saveVoteInvalidChoiceId() { when(voteRepository.findByPlayerAndVotingSubjectId(player, votingSubject.getId())).thenReturn(Optional.empty()); when(votingSubjectRepository.findById(votingSubject.getId())).thenReturn(Optional.of(votingSubject)); - expectedException.expect(ApiExceptionWithCode.apiExceptionWithCode(ErrorCode.VOTING_CHOICE_DOES_NOT_EXIST)); + expectedException.expect(ApiExceptionMatcher.hasErrorCode(ErrorCode.VOTING_CHOICE_DOES_NOT_EXIST)); instance.saveVote(vote, player); verify(voteRepository, never()).save(vote); diff --git a/src/test/resources/maps/adaptive_map_files_missing.zip b/src/test/resources/maps/adaptive_map_files_missing.zip new file mode 100644 index 000000000..5db1a48b0 Binary files /dev/null and b/src/test/resources/maps/adaptive_map_files_missing.zip differ diff --git a/src/test/resources/maps/command_conquer_rush.v0007.zip b/src/test/resources/maps/command_conquer_rush.v0007.zip new file mode 100644 index 000000000..0f1679c28 Binary files /dev/null and b/src/test/resources/maps/command_conquer_rush.v0007.zip differ diff --git a/src/test/resources/maps/invalid_scenario.zip b/src/test/resources/maps/invalid_scenario.zip index e488810cf..e40e63b52 100644 Binary files a/src/test/resources/maps/invalid_scenario.zip and b/src/test/resources/maps/invalid_scenario.zip differ diff --git a/src/test/resources/maps/invalid_zip.zip b/src/test/resources/maps/invalid_zip.zip new file mode 100644 index 000000000..871dab8d8 Binary files /dev/null and b/src/test/resources/maps/invalid_zip.zip differ diff --git a/src/test/resources/maps/map_name_invalid_character.zip b/src/test/resources/maps/map_name_invalid_character.zip new file mode 100644 index 000000000..a802e7035 Binary files /dev/null and b/src/test/resources/maps/map_name_invalid_character.zip differ diff --git a/src/test/resources/maps/no_map_name.zip b/src/test/resources/maps/no_map_name.zip new file mode 100644 index 000000000..a6e28256b Binary files /dev/null and b/src/test/resources/maps/no_map_name.zip differ diff --git a/src/test/resources/maps/scenario/missing_map_variable_scenario.lua b/src/test/resources/maps/scenario/missing_map_variable_scenario.lua new file mode 100644 index 000000000..c21c39dc4 --- /dev/null +++ b/src/test/resources/maps/scenario/missing_map_variable_scenario.lua @@ -0,0 +1,26 @@ +version = 3 -- Lua Version. Dont touch this +ScenarioInfo = { + name = "Mirage", + description = "Map By: Morax", + preview = '', + map_version = 2, + type = 'skirmish', + starts = true, + size = {256, 256}, + save = '/maps/mirage/mirage_save.lua', + script = '/maps/mirage/mirage_script.lua', + norushradius = 40, + Configurations = { + ['standard'] = { + teams = { + { + name = 'FFA', + armies = {'ARMY_1', 'ARMY_2'} + }, + }, + customprops = { + ['ExtraArmies'] = STRING( 'ARMY_17 NEUTRAL_CIVILIAN' ), + }, + }, + }, +} diff --git a/src/test/resources/maps/scenario/valid_empty.lua b/src/test/resources/maps/scenario/valid_empty.lua new file mode 100644 index 000000000..7623f161e --- /dev/null +++ b/src/test/resources/maps/scenario/valid_empty.lua @@ -0,0 +1,3 @@ +version = 3 -- Lua Version. Dont touch this +ScenarioInfo = { +} diff --git a/src/test/resources/maps/scenario/valid_with_version_scenario.lua b/src/test/resources/maps/scenario/valid_with_version_scenario.lua new file mode 100644 index 000000000..5016f4236 --- /dev/null +++ b/src/test/resources/maps/scenario/valid_with_version_scenario.lua @@ -0,0 +1,27 @@ +version = 3 -- Lua Version. Dont touch this +ScenarioInfo = { + name = "Mirage", + description = "Map By: Morax", + preview = '', + map_version = 2, + type = 'skirmish', + starts = true, + size = {256, 256}, + map = '/maps/mirage.v0002/mirage.scmap', + save = '/maps/mirage.v0002/mirage_save.lua', + script = '/maps/mirage.v0002/mirage_script.lua', + norushradius = 40, + Configurations = { + ['standard'] = { + teams = { + { + name = 'FFA', + armies = {'ARMY_1', 'ARMY_2'} + }, + }, + customprops = { + ['ExtraArmies'] = STRING( 'ARMY_17 NEUTRAL_CIVILIAN' ), + }, + }, + }, +} diff --git a/src/test/resources/maps/scenario/valid_without_version_scenario.lua b/src/test/resources/maps/scenario/valid_without_version_scenario.lua new file mode 100644 index 000000000..e6302664d --- /dev/null +++ b/src/test/resources/maps/scenario/valid_without_version_scenario.lua @@ -0,0 +1,27 @@ +version = 3 -- Lua Version. Dont touch this +ScenarioInfo = { + name = "Mirage", + description = "Map By: Morax", + preview = '', + map_version = 2, + type = 'skirmish', + starts = true, + size = {256, 256}, + map = '/maps/mirage/mirage.scmap', + save = '/maps/mirage/mirage_save.lua', + script = '/maps/mirage/mirage_script.lua', + norushradius = 40, + Configurations = { + ['standard'] = { + teams = { + { + name = 'FFA', + armies = {'ARMY_1', 'ARMY_2'} + }, + }, + customprops = { + ['ExtraArmies'] = STRING( 'ARMY_17 NEUTRAL_CIVILIAN' ), + }, + }, + }, +} diff --git a/src/test/resources/maps/scmp 037.zip b/src/test/resources/maps/scmp 037.zip deleted file mode 100644 index b4f7b4c49..000000000 Binary files a/src/test/resources/maps/scmp 037.zip and /dev/null differ diff --git a/src/test/resources/maps/scmp_037.zip b/src/test/resources/maps/scmp_037.zip deleted file mode 100644 index efecaa756..000000000 Binary files a/src/test/resources/maps/scmp_037.zip and /dev/null differ diff --git a/src/test/resources/maps/scmp_037_invalid.zip b/src/test/resources/maps/scmp_037_invalid.zip deleted file mode 100644 index 38bce2afb..000000000 Binary files a/src/test/resources/maps/scmp_037_invalid.zip and /dev/null differ diff --git a/src/test/resources/maps/scmp_037_no_ascii.zip b/src/test/resources/maps/scmp_037_no_ascii.zip deleted file mode 100644 index 6e3ba7ddc..000000000 Binary files a/src/test/resources/maps/scmp_037_no_ascii.zip and /dev/null differ diff --git a/src/test/resources/maps/sludge_test____.___..v0001.zip b/src/test/resources/maps/sludge_test____.___..v0001.zip deleted file mode 100644 index c95c43d39..000000000 Binary files a/src/test/resources/maps/sludge_test____.___..v0001.zip and /dev/null differ diff --git a/src/test/resources/maps/without_savelua.zip b/src/test/resources/maps/without_savelua.zip index 1277735f2..97aaf9fe9 100644 Binary files a/src/test/resources/maps/without_savelua.zip and b/src/test/resources/maps/without_savelua.zip differ diff --git a/src/test/resources/maps/without_scenariolua.zip b/src/test/resources/maps/without_scenariolua.zip index 50e968f8a..f9fe3e36b 100644 Binary files a/src/test/resources/maps/without_scenariolua.zip and b/src/test/resources/maps/without_scenariolua.zip differ diff --git a/src/test/resources/maps/without_scriptlua.zip b/src/test/resources/maps/without_scriptlua.zip index de2dce7a5..ac2bfdbf1 100644 Binary files a/src/test/resources/maps/without_scriptlua.zip and b/src/test/resources/maps/without_scriptlua.zip differ diff --git a/src/test/resources/maps/wrong_team_name.zip b/src/test/resources/maps/wrong_team_name.zip index 5c886c05b..f7a1d408c 100644 Binary files a/src/test/resources/maps/wrong_team_name.zip and b/src/test/resources/maps/wrong_team_name.zip differ