Skip to content

Commit

Permalink
MARP-1687 Write unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
quanpham-axonivy committed Dec 25, 2024
1 parent e875aeb commit a648020
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@ public class DirectoryConstants {
public static final String DATA_DIR = "data";
public static final String WORK_DIR = "work";
public static final String CACHE_DIR = "market-cache";
public static final String PREVIEW_DIR = "marketplace-service/release-preview";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.axonivy.market.constants;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class PreviewConstants {

public static final String PREVIEW_DIR = "marketplace-service/release-preview";

public static final String IMAGE_DOWNLOAD_URL = "%s/api/image/preview/%s";

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import java.util.List;
import java.util.Optional;

import static com.axonivy.market.constants.DirectoryConstants.PREVIEW_DIR;
import static com.axonivy.market.constants.PreviewConstants.PREVIEW_DIR;

@Service
@Log4j2
Expand Down Expand Up @@ -121,7 +121,6 @@ public byte[] readPreviewImageByName(String imageName) {
log.info("#readPreviewImageByName: Preview folder not found");
}
try {

Optional<Path> imagePath = Files.walk(previewPath)
.filter(Files::isRegularFile)
.filter(path -> path.getFileName().toString().equalsIgnoreCase(imageName))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,87 +5,58 @@
import com.axonivy.market.model.ReadmeContentsModel;
import com.axonivy.market.model.ReleasePreview;
import com.axonivy.market.service.ReleasePreviewService;
import com.axonivy.market.util.FileUtils;
import com.axonivy.market.util.ProductContentUtils;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import static com.axonivy.market.constants.DirectoryConstants.PREVIEW_DIR;
import static com.axonivy.market.constants.PreviewConstants.IMAGE_DOWNLOAD_URL;
import static com.axonivy.market.constants.PreviewConstants.PREVIEW_DIR;

@Log4j2
@Service
@AllArgsConstructor
public class ReleasePreviewServiceImpl implements ReleasePreviewService {

private final static String IMAGE_DOWNLOAD_URL = "%s/api/image/preview/%s";

@Override
public ReleasePreview extract(MultipartFile file, String baseUrl) {
unzip(file);
return extractREADME(baseUrl);
}

private void unzip(MultipartFile file) {
try {
File extractDir = new File(PREVIEW_DIR);
prepareUnZipDirectory(extractDir.toPath());

try (ZipInputStream zipInputStream = new ZipInputStream(file.getInputStream())) {
ZipEntry entry;
while ((entry = zipInputStream.getNextEntry()) != null) {
File outFile = new File(extractDir, entry.getName());
if (entry.isDirectory()) {
outFile.mkdirs();
} else {
new File(outFile.getParent()).mkdirs();
try (FileOutputStream fos = new FileOutputStream(outFile)) {
byte[] buffer = new byte[1024];
int length;
while ((length = zipInputStream.read(buffer)) > 0) {
fos.write(buffer, 0, length);
}
}
}
zipInputStream.closeEntry();
}
}
} catch (IOException e) {
log.error("#unzip An exception occurred when unzip file {} - message {}", file.getName(), e.getMessage());
FileUtils.unzip(file, PREVIEW_DIR);
} catch (IOException e){
log.info("#extract Error extracting zip file, message: {}", e.getMessage());
}
return extractREADME(baseUrl, PREVIEW_DIR);
}

private ReleasePreview extractREADME(String baseUrl) {
public ReleasePreview extractREADME(String baseUrl, String location) {
Map<String, Map<String, String>> moduleContents = new HashMap<>();
try (Stream<Path> readmePathStream = Files.walk(Paths.get(PREVIEW_DIR))) {
try (Stream<Path> readmePathStream = Files.walk(Paths.get(location))) {
List<Path> readmeFiles = readmePathStream.filter(Files::isRegularFile)
.filter(path -> path.getFileName().toString().startsWith(ReadmeConstants.README_FILE_NAME))
.toList();
if (readmeFiles.isEmpty()) {
return null;
}
for (Path readmeFile : readmeFiles) {
processReadme(readmeFile, moduleContents, baseUrl);
processReadme(readmeFile, moduleContents, baseUrl, location);
}
return ReleasePreview.from(moduleContents);
} catch (IOException e) {
log.error("Cannot get README file's content from folder {}: {}",
com.axonivy.market.constants.DirectoryConstants.PREVIEW_DIR, e.getMessage());
PREVIEW_DIR, e.getMessage());
return null;
}
}
Expand All @@ -111,25 +82,11 @@ public String updateImagesWithDownloadUrl(String unzippedFolderPath,
}
}

private static void prepareUnZipDirectory(Path directory) {
try {
if (Files.exists(directory)) {
Files.walk(directory)
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
}
Files.createDirectories(directory);
} catch (IOException e) {
log.error("#prepareDirectory: Error managing directory {} : {}", directory.toString(), e.getMessage());
}
}

private void processReadme(Path readmeFile, Map<String, Map<String, String>> moduleContents,
String baseUrl) throws IOException {
public void processReadme(Path readmeFile, Map<String, Map<String, String>> moduleContents,
String baseUrl, String location) throws IOException {
String readmeContents = Files.readString(readmeFile);
if (ProductContentUtils.hasImageDirectives(readmeContents)) {
readmeContents = updateImagesWithDownloadUrl(PREVIEW_DIR, readmeContents, baseUrl);
readmeContents = updateImagesWithDownloadUrl(location, readmeContents, baseUrl);
}
ReadmeContentsModel readmeContentsModel = ProductContentUtils.getExtractedPartsOfReadme(readmeContents);
ProductContentUtils.mappingDescriptionSetupAndDemo(
Expand All @@ -138,4 +95,5 @@ private void processReadme(Path readmeFile, Map<String, Map<String, String>> mod
readmeContentsModel
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class FileUtils {
Expand All @@ -28,4 +36,60 @@ public static void writeToFile(File file, String content) throws IOException {
}
}

public static void unzip(MultipartFile file, String location) throws IOException {
File extractDir = new File(location);
prepareUnZipDirectory(extractDir.toPath());

try (ZipInputStream zipInputStream = new ZipInputStream(file.getInputStream())) {
ZipEntry entry;
while ((entry = zipInputStream.getNextEntry()) != null) {
Path entryPath = Paths.get(entry.getName()).normalize();
Path resolvedPath = extractDir.toPath().resolve(entryPath).normalize();

// Ensure the resolved path is within the target directory
if (!resolvedPath.startsWith(extractDir.toPath())) {
throw new IOException("Entry is outside the target dir: " + entry.getName());
}

File outFile = resolvedPath.toFile();
if (entry.isDirectory()) {
if (!outFile.mkdirs() && !outFile.isDirectory()) {
throw new IOException("Failed to create directory: " + outFile);
}
} else {
File parentDir = outFile.getParentFile();
if (parentDir != null && !parentDir.exists() && !parentDir.mkdirs()) {
throw new IOException("Failed to create parent directory: " + parentDir);
}

try (FileOutputStream fos = new FileOutputStream(outFile)) {
byte[] buffer = new byte[1024];
int length;
while ((length = zipInputStream.read(buffer)) > 0) {
fos.write(buffer, 0, length);
}
}
}
zipInputStream.closeEntry();
}
} catch (IOException e) {
throw new IOException("Error unzipping file", e);
}
}


public static void prepareUnZipDirectory(Path directory) throws IOException {
clearDirectory(directory);
Files.createDirectories(directory);
}

public static void clearDirectory(Path path) throws IOException {
if (Files.exists(path)) {
Files.walk(path)
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(java.io.File::delete);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package com.axonivy.market.service.impl;

import com.axonivy.market.model.ReleasePreview;
import com.axonivy.market.util.FileUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

import static com.axonivy.market.constants.PreviewConstants.IMAGE_DOWNLOAD_URL;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

;

@ExtendWith(MockitoExtension.class)
class ReleasePreviewServiceImplTest {

private ReleasePreviewServiceImpl releasePreviewService;

private Path tempDirectory;

private final String baseUrl = "http://example.com";

private final String readmeContent = "# Sample README Content\n![image](image1.png)";

private final String updatedReadme = "# Sample README Content\n![image](http://example" +
".com/api/image/preview/image1.png)";

@BeforeEach
void setUp() throws IOException {
releasePreviewService = spy(new ReleasePreviewServiceImpl());
tempDirectory = Files.createTempDirectory("test-dir");
}

@AfterEach
void tearDown() throws IOException {
FileUtils.clearDirectory(tempDirectory);
}

@Test
void testProcessReadme() throws IOException {
Path tempReadmeFile = Files.createTempFile("README", ".md");
Files.writeString(tempReadmeFile, readmeContent);
Map<String, Map<String, String>> moduleContents = new HashMap<>();
doReturn(updatedReadme).when(releasePreviewService)
.updateImagesWithDownloadUrl(tempDirectory.toString(), readmeContent, baseUrl);
releasePreviewService.processReadme(tempReadmeFile, moduleContents, baseUrl, tempDirectory.toString());
assertEquals(3, moduleContents.size());
Files.deleteIfExists(tempReadmeFile);
}

@Test
void testUpdateImagesWithDownloadUrl_Success() throws IOException {
Path tempReadmeFile = Files.createTempFile("README", ".md");
Files.writeString(tempReadmeFile, readmeContent);
String parentPath = tempReadmeFile.getParent().toString();

Path imagePath1 = Paths.get(parentPath + "/image1.png");
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
mockedFiles.when(() -> Files.walk(Paths.get(parentPath)))
.thenReturn(Stream.of(imagePath1));
mockedFiles.when(() -> Files.isRegularFile(any()))
.thenReturn(true);
String result = releasePreviewService.updateImagesWithDownloadUrl(parentPath,
readmeContent
, baseUrl);

assertNotNull(result);
assertTrue(result.contains(String.format(IMAGE_DOWNLOAD_URL, baseUrl, "image1.png")));
}
}

@Test
void testUpdateImagesWithDownloadUrl_IOException() {
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
mockedFiles.when(() -> Files.walk(tempDirectory))
.thenThrow(new IOException("Simulated IOException"));
String result = releasePreviewService.updateImagesWithDownloadUrl(tempDirectory.toString(), readmeContent,
baseUrl);
assertNull(result);
assertDoesNotThrow(
() -> releasePreviewService.updateImagesWithDownloadUrl(tempDirectory.toString(), readmeContent, baseUrl));
}
}

@Test
void testExtractREADME_Success() throws IOException {
String parentPath = tempDirectory.getParent().toString();
Path readmeFile1 = FileUtils.createFile(parentPath + "/README.md").toPath();
Files.writeString(readmeFile1, readmeContent);

try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
mockedFiles.when(() -> Files.walk(tempDirectory))
.thenReturn(Stream.of(readmeFile1));
mockedFiles.when(() -> Files.isRegularFile(any()))
.thenReturn(true);
mockedFiles.when(() -> Files.readString(any()))
.thenReturn(readmeContent);
when(releasePreviewService.updateImagesWithDownloadUrl(any(), anyString(), anyString())).thenReturn(
updatedReadme);
ReleasePreview result = releasePreviewService.extractREADME(baseUrl, tempDirectory.toString());
assertNotNull(result);
}
}

@Test
void testExtractREADME_NoReadmeFiles() {
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
mockedFiles.when(() -> Files.walk(tempDirectory))
.thenReturn(Stream.empty());
ReleasePreview result = releasePreviewService.extractREADME(baseUrl, tempDirectory.toString());
assertNull(result);
mockedFiles.verify(() -> Files.walk(tempDirectory), times(1));
}
}

@Test
void testExtractREADME_IOException() {
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
mockedFiles.when(() -> Files.walk(tempDirectory))
.thenThrow(new IOException("Simulated IOException"));
ReleasePreview result = releasePreviewService.extractREADME(baseUrl, tempDirectory.toString());
assertNull(result);
assertDoesNotThrow(
() -> releasePreviewService.extractREADME(baseUrl, tempDirectory.toString()));
}
}

}
Loading

0 comments on commit a648020

Please sign in to comment.