From 4b980d5bb441c701b972607b1379365d2b42351c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szrnka?= Date: Mon, 13 Nov 2023 13:23:15 +0100 Subject: [PATCH] Initial commit: Full refactor with tests --- code/pom.xml | 13 + .../hosting/FirebaseHostingApiClient.java | 209 ++------ .../service/AbstractUtilityService.java | 26 + .../firebase/hosting/service/FileService.java | 27 + .../hosting/service/ReleaseService.java | 17 + .../hosting/service/VersionService.java | 23 + .../hosting/service/impl/FileServiceImpl.java | 80 +++ .../service/impl/ReleaseServiceImpl.java | 41 ++ .../service/impl/VersionServiceImpl.java | 68 +++ .../hosting/util/ConfigValidationUtils.java | 37 ++ .../firebase/hosting/util/Constants.java | 4 + .../firebase/hosting/util/FileUtils.java | 25 +- .../firebase/hosting/util/VersionUtils.java | 45 ++ .../hosting/FirebaseHostingApiClientTest.java | 493 +++++++----------- .../service/impl/FileServiceImplTest.java | 179 +++++++ .../service/impl/ReleaseServiceImplTest.java | 123 +++++ .../service/impl/VersionServiceImplTest.java | 151 ++++++ .../util/ConfigValidationUtilsTest.java | 76 +++ .../firebase/hosting/util/FileUtilsTest.java | 33 +- .../hosting/util/TestConfigGenerator.java | 34 ++ .../hosting/util/VersionUtilsTest.java | 84 +++ 21 files changed, 1312 insertions(+), 476 deletions(-) create mode 100644 code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/AbstractUtilityService.java create mode 100644 code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/FileService.java create mode 100644 code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/ReleaseService.java create mode 100644 code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/VersionService.java create mode 100644 code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/impl/FileServiceImpl.java create mode 100644 code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/impl/ReleaseServiceImpl.java create mode 100644 code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/impl/VersionServiceImpl.java create mode 100644 code/src/main/java/io/github/szrnkapeter/firebase/hosting/util/ConfigValidationUtils.java create mode 100644 code/src/main/java/io/github/szrnkapeter/firebase/hosting/util/VersionUtils.java create mode 100644 code/src/test/java/io/github/szrnkapeter/firebase/hosting/service/impl/FileServiceImplTest.java create mode 100644 code/src/test/java/io/github/szrnkapeter/firebase/hosting/service/impl/ReleaseServiceImplTest.java create mode 100644 code/src/test/java/io/github/szrnkapeter/firebase/hosting/service/impl/VersionServiceImplTest.java create mode 100644 code/src/test/java/io/github/szrnkapeter/firebase/hosting/util/ConfigValidationUtilsTest.java create mode 100644 code/src/test/java/io/github/szrnkapeter/firebase/hosting/util/TestConfigGenerator.java create mode 100644 code/src/test/java/io/github/szrnkapeter/firebase/hosting/util/VersionUtilsTest.java diff --git a/code/pom.xml b/code/pom.xml index d1dd195..648344f 100644 --- a/code/pom.xml +++ b/code/pom.xml @@ -86,6 +86,12 @@ google-auth-library-oauth2-http ${google-auth-library.version} + + org.projectlombok + lombok + 1.18.30 + test + com.fasterxml.jackson.core jackson-databind @@ -128,6 +134,13 @@ ${junit.jupiter.version} test + + org.junit.jupiter + junit-jupiter-params + ${junit.jupiter.version} + test + + org.mockito mockito-core diff --git a/code/src/main/java/io/github/szrnkapeter/firebase/hosting/FirebaseHostingApiClient.java b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/FirebaseHostingApiClient.java index e5dc618..24bdbcd 100644 --- a/code/src/main/java/io/github/szrnkapeter/firebase/hosting/FirebaseHostingApiClient.java +++ b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/FirebaseHostingApiClient.java @@ -1,18 +1,5 @@ package io.github.szrnkapeter.firebase.hosting; -import java.io.IOException; -import java.net.MalformedURLException; -import java.security.NoSuchAlgorithmException; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import com.google.common.annotations.VisibleForTesting; - import io.github.szrnkapeter.firebase.hosting.config.FirebaseHostingApiConfig; import io.github.szrnkapeter.firebase.hosting.model.DeployItem; import io.github.szrnkapeter.firebase.hosting.model.DeployRequest; @@ -25,11 +12,26 @@ import io.github.szrnkapeter.firebase.hosting.model.Release; import io.github.szrnkapeter.firebase.hosting.model.UploadFileRequest; import io.github.szrnkapeter.firebase.hosting.model.Version; -import io.github.szrnkapeter.firebase.hosting.util.ConnectionUtils; -import io.github.szrnkapeter.firebase.hosting.util.Constants; +import io.github.szrnkapeter.firebase.hosting.service.FileService; +import io.github.szrnkapeter.firebase.hosting.service.ReleaseService; +import io.github.szrnkapeter.firebase.hosting.service.VersionService; +import io.github.szrnkapeter.firebase.hosting.service.impl.FileServiceImpl; +import io.github.szrnkapeter.firebase.hosting.service.impl.ReleaseServiceImpl; +import io.github.szrnkapeter.firebase.hosting.service.impl.VersionServiceImpl; import io.github.szrnkapeter.firebase.hosting.util.FileUtils; import io.github.szrnkapeter.firebase.hosting.util.GoogleCredentialUtils; +import java.io.IOException; +import java.net.MalformedURLException; +import java.security.NoSuchAlgorithmException; +import java.util.Set; +import java.util.stream.Collectors; + +import static io.github.szrnkapeter.firebase.hosting.util.ConfigValidationUtils.preValidateConfig; +import static io.github.szrnkapeter.firebase.hosting.util.FileUtils.generateFileListAndHash; +import static io.github.szrnkapeter.firebase.hosting.util.VersionUtils.getVersionId; +import static io.github.szrnkapeter.firebase.hosting.util.VersionUtils.getVersionName; + /** * Firebase REST API client for Java * @@ -38,12 +40,21 @@ */ public class FirebaseHostingApiClient { - private static final String FILES = "/files"; - private static final String VERSIONS = "/versions/"; - private static final String SITES = "sites/"; - private final FirebaseHostingApiConfig config; - private final String accessToken; + + private final ReleaseService releaseService; + private final VersionService versionService; + private final FileService fileService; + + public static FirebaseHostingApiClient newClient(FirebaseHostingApiConfig config) { + String accessToken = GoogleCredentialUtils.getAccessToken(config); + + ReleaseService releaseService = new ReleaseServiceImpl(config, accessToken); + VersionService versionService = new VersionServiceImpl(config, accessToken); + FileService fileService = new FileServiceImpl(config, accessToken); + + return new FirebaseHostingApiClient(config, releaseService, versionService, fileService); + } /** * Default constructor. @@ -52,11 +63,14 @@ public class FirebaseHostingApiClient { * * @since 0.2 */ - public FirebaseHostingApiClient(FirebaseHostingApiConfig firebaseRestApiConfig) { + protected FirebaseHostingApiClient(FirebaseHostingApiConfig firebaseRestApiConfig, ReleaseService releaseService, + VersionService versionService, FileService fileService) { preValidateConfig(firebaseRestApiConfig); config = firebaseRestApiConfig; - accessToken = GoogleCredentialUtils.getAccessToken(config); + this.releaseService = releaseService; + this.versionService = versionService; + this.fileService = fileService; } /** @@ -112,7 +126,7 @@ public DeployResponse createDeploy(DeployRequest request) throws IOException, No PopulateFilesResponse populateFilesResponse = populateFiles(populateRequest, versionId); // Upload them - uploadFiles(versionId, request, populateFilesResponse); + fileService.uploadFiles(versionId, request.getFiles(), populateFilesResponse.getUploadRequiredHashes()); // Finalize the new version finalizeVersion(versionId); @@ -121,7 +135,7 @@ public DeployResponse createDeploy(DeployRequest request) throws IOException, No Release newRelease = createRelease(versionId); // Post delete earlier deployments - deletePreviousVersion(request); + versionService.deletePreviousVersions(request, getReleases().getReleases()); // Create the release return new DeployResponse(newRelease); @@ -137,12 +151,8 @@ public DeployResponse createDeploy(DeployRequest request) throws IOException, No * * @since 0.2 */ - public Release createRelease(String version) throws IOException { - Release newRelease = ConnectionUtils.openSimpleHTTPPostConnection(config, Release.class, accessToken, - SITES + config.getSiteId() + "/releases?versionName=" + getVersionName(version), null, "createRelease"); - - responseCallback("createRelease", newRelease); - return newRelease; + public Release createRelease(String version) throws IOException { + return releaseService.createRelease(getVersionName(config, version)); } /** @@ -155,11 +165,7 @@ public Release createRelease(String version) throws IOException { * @since 0.2 */ public Version createVersion() throws IOException { - Version newVersion = ConnectionUtils.openSimpleHTTPPostConnection(config, Version.class, accessToken, - SITES + config.getSiteId() + "/versions", "{}", "createVersion"); - - responseCallback("createVersion", newVersion); - return newVersion; + return versionService.createVersion(); } /** @@ -172,7 +178,7 @@ public Version createVersion() throws IOException { * @since 0.2 */ public void deleteVersion(String version) throws IOException { - ConnectionUtils.openSimpleHTTPConnection("DELETE", config, null, accessToken, getVersionName(version), null, "deleteVersion"); + versionService.deleteVersion(getVersionName(config, version)); } /** @@ -186,11 +192,7 @@ public void deleteVersion(String version) throws IOException { * @since 0.2 */ public Version finalizeVersion(String version) throws IOException { - Version newVersion = ConnectionUtils.openSimpleHTTPConnection("PATCH", config, Version.class, accessToken, - SITES + config.getSiteId() + VERSIONS + version + "?update_mask=status", "{ \"status\": \"FINALIZED\" }", "finalizeVersion"); - - responseCallback("finalizeVersion", newVersion); - return newVersion; + return versionService.finalizeVersion(version); } /** @@ -204,11 +206,7 @@ public Version finalizeVersion(String version) throws IOException { * @since 0.2 */ public GetReleasesResponse getReleases() throws IOException { - GetReleasesResponse response = ConnectionUtils.openHTTPGetConnection(config, GetReleasesResponse.class, accessToken, - SITES + config.getSiteId() + "/releases"); - - responseCallback("getReleases", response); - return response; + return releaseService.getReleases(); } /** @@ -222,10 +220,7 @@ public GetReleasesResponse getReleases() throws IOException { * @since 0.2 */ public GetVersionFilesResponse getVersionFiles(String version) throws IOException { - GetVersionFilesResponse response = ConnectionUtils.openHTTPGetConnection(config, GetVersionFilesResponse.class, accessToken, getVersionName(version) + FILES); - - responseCallback("getVersionFiles", response); - return response; + return fileService.getVersionFiles(getVersionName(config, version)); } /** @@ -240,13 +235,8 @@ public GetVersionFilesResponse getVersionFiles(String version) throws IOExceptio * @since 0.2 */ public PopulateFilesResponse populateFiles(PopulateFilesRequest request, String version) throws IOException { - String data = config.getSerializer().toJson(PopulateFilesRequest.class, request); - PopulateFilesResponse response = ConnectionUtils.openSimpleHTTPPostConnection(config, PopulateFilesResponse.class, accessToken, SITES + config.getSiteId() + VERSIONS + version + ":populateFiles", data, "populateFiles"); - - responseCallback("populateFiles", response); - return response; + return fileService.populateFiles(request, version); } - /** * Uploads a file. @@ -258,111 +248,6 @@ public PopulateFilesResponse populateFiles(PopulateFilesRequest request, String * @since 0.2 */ public void uploadFile(UploadFileRequest request) throws NoSuchAlgorithmException, IOException { - String calculatedHash = FileUtils.getSHA256Checksum(request.getFileContent()); - String url = Constants.UPLOAD_FIREBASE_API_URL + "upload/" + SITES + config.getSiteId() + VERSIONS + request.getVersion() + FILES + "/" + calculatedHash; - - if(request.getUploadUrl() != null) { - url = request.getUploadUrl() + "/" + calculatedHash; - } - - ConnectionUtils.uploadFile(config, accessToken, request.getFileName(), url, request.getFileContent()); - } - - @VisibleForTesting - String getVersionId(String version) { - if(version == null || version.isEmpty()) { - return null; - } - - return version.substring(version.lastIndexOf("/") + 1); - } - - private void responseCallback(String function, Object response) { - if(config.getServiceResponseListener() == null) { - return; - } - - config.getServiceResponseListener().getResponse(function, response); - } - - private void deletePreviousVersion(DeployRequest request) throws IOException { - if(!request.isDeletePreviousVersions()) { - return; - } - - GetReleasesResponse response = getReleases(); - AtomicInteger i = new AtomicInteger(0); - - for(Release release : response.getReleases()) { - if(i.get() > 0 && Constants.FINALIZED.equals(release.getVersion().getStatus())) { - deleteVersion(release.getVersion().getName()); - } - - i.incrementAndGet(); - } - } - - private String getVersionName(String version) { - if(version == null || version.isEmpty()) { - throw new IllegalArgumentException("Version field is mandatory!"); - } - - String versionName = SITES + config.getSiteId() + VERSIONS + version; - - Pattern p = Pattern.compile(SITES + config.getSiteId() + VERSIONS + ".*"); - Matcher m = p.matcher(version); - - if (m.matches()) { - versionName = version; - } - - return versionName; - } - - private void uploadFiles(String versionId, DeployRequest request, PopulateFilesResponse populateFilesResponse) throws IOException, NoSuchAlgorithmException { - for(DeployItem item : request.getFiles()) { - byte[] fileContent = FileUtils.compressAndReadFile(item.getContent()); - String checkSum = FileUtils.getSHA256Checksum(fileContent); - - if(populateFilesResponse.getUploadRequiredHashes() != null && !populateFilesResponse.getUploadRequiredHashes().contains(checkSum)) { - continue; - } - - UploadFileRequest uploadFilesRequest = new UploadFileRequest(); - uploadFilesRequest.setVersion(versionId); - uploadFilesRequest.setFileContent(fileContent); - uploadFilesRequest.setFileName(item.getName()); - uploadFile(uploadFilesRequest); - } - } - - private Map generateFileListAndHash(Set files) throws IOException, NoSuchAlgorithmException { - Map result = new HashMap<>(); - - for(DeployItem file : files) { - byte[] gzippedContent = FileUtils.compressAndReadFile(file.getContent()); - String checkSum = FileUtils.getSHA256Checksum(gzippedContent); - result.put("/" + file.getName(), checkSum); - } - - return result; - } - - private void preValidateConfig(FirebaseHostingApiConfig firebaseRestApiConfig) { - if (firebaseRestApiConfig == null) { - throw new IllegalArgumentException("FirebaseRestApiConfig field is mandatory!"); - } - - if (firebaseRestApiConfig.getSiteId() == null || firebaseRestApiConfig.getSiteId().isEmpty()) { - throw new IllegalArgumentException("Site name is mandatory!"); - } - - if (firebaseRestApiConfig.getServiceAccountFileStream() == null) { - throw new IllegalArgumentException("Service account file stream is missing from the configuration!"); - } - - if (firebaseRestApiConfig.getSerializer() == null) { - throw new IllegalArgumentException("Serializer is missing from the configuration!"); - } + fileService.uploadFile(request); } } \ No newline at end of file diff --git a/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/AbstractUtilityService.java b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/AbstractUtilityService.java new file mode 100644 index 0000000..241a06b --- /dev/null +++ b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/AbstractUtilityService.java @@ -0,0 +1,26 @@ +package io.github.szrnkapeter.firebase.hosting.service; + +import io.github.szrnkapeter.firebase.hosting.config.FirebaseHostingApiConfig; + +/** + * @author Peter Szrnka + * @since 0.8 + */ +public abstract class AbstractUtilityService { + + protected final FirebaseHostingApiConfig config; + protected final String accessToken; + + protected AbstractUtilityService(FirebaseHostingApiConfig config, String accessToken) { + this.config = config; + this.accessToken = accessToken; + } + + protected void responseCallback(String function, Object response) { + if(config.getServiceResponseListener() == null) { + return; + } + + config.getServiceResponseListener().getResponse(function, response); + } +} diff --git a/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/FileService.java b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/FileService.java new file mode 100644 index 0000000..c2c8c39 --- /dev/null +++ b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/FileService.java @@ -0,0 +1,27 @@ +package io.github.szrnkapeter.firebase.hosting.service; + +import io.github.szrnkapeter.firebase.hosting.model.DeployItem; +import io.github.szrnkapeter.firebase.hosting.model.GetVersionFilesResponse; +import io.github.szrnkapeter.firebase.hosting.model.PopulateFilesRequest; +import io.github.szrnkapeter.firebase.hosting.model.PopulateFilesResponse; +import io.github.szrnkapeter.firebase.hosting.model.UploadFileRequest; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.Set; + +/** + * @author Peter Szrnka + * @since 0.8 + */ +public interface FileService { + + GetVersionFilesResponse getVersionFiles(String versionName) throws IOException; + + PopulateFilesResponse populateFiles(PopulateFilesRequest request, String version) throws IOException; + + void uploadFile(UploadFileRequest request) throws NoSuchAlgorithmException, IOException; + + void uploadFiles(String versionId, Set files, List requiredHashes) throws IOException, NoSuchAlgorithmException; +} diff --git a/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/ReleaseService.java b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/ReleaseService.java new file mode 100644 index 0000000..67f2c89 --- /dev/null +++ b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/ReleaseService.java @@ -0,0 +1,17 @@ +package io.github.szrnkapeter.firebase.hosting.service; + +import io.github.szrnkapeter.firebase.hosting.model.GetReleasesResponse; +import io.github.szrnkapeter.firebase.hosting.model.Release; + +import java.io.IOException; + +/** + * @author Peter Szrnka + * @since 0.8 + */ +public interface ReleaseService { + + Release createRelease(String version) throws IOException; + + GetReleasesResponse getReleases() throws IOException; +} diff --git a/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/VersionService.java b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/VersionService.java new file mode 100644 index 0000000..b273637 --- /dev/null +++ b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/VersionService.java @@ -0,0 +1,23 @@ +package io.github.szrnkapeter.firebase.hosting.service; + +import io.github.szrnkapeter.firebase.hosting.model.DeployRequest; +import io.github.szrnkapeter.firebase.hosting.model.Release; +import io.github.szrnkapeter.firebase.hosting.model.Version; + +import java.io.IOException; +import java.util.List; + +/** + * @author Peter Szrnka + * @since 0.8 + */ +public interface VersionService { + + Version createVersion() throws IOException; + + void deleteVersion(String versionName) throws IOException; + + void deletePreviousVersions(DeployRequest request, List releaseList) throws IOException; + + Version finalizeVersion(String version) throws IOException; +} diff --git a/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/impl/FileServiceImpl.java b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/impl/FileServiceImpl.java new file mode 100644 index 0000000..eeb3b03 --- /dev/null +++ b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/impl/FileServiceImpl.java @@ -0,0 +1,80 @@ +package io.github.szrnkapeter.firebase.hosting.service.impl; + +import io.github.szrnkapeter.firebase.hosting.config.FirebaseHostingApiConfig; +import io.github.szrnkapeter.firebase.hosting.model.DeployItem; +import io.github.szrnkapeter.firebase.hosting.model.GetVersionFilesResponse; +import io.github.szrnkapeter.firebase.hosting.model.PopulateFilesRequest; +import io.github.szrnkapeter.firebase.hosting.model.PopulateFilesResponse; +import io.github.szrnkapeter.firebase.hosting.model.UploadFileRequest; +import io.github.szrnkapeter.firebase.hosting.service.AbstractUtilityService; +import io.github.szrnkapeter.firebase.hosting.service.FileService; +import io.github.szrnkapeter.firebase.hosting.util.ConnectionUtils; +import io.github.szrnkapeter.firebase.hosting.util.Constants; +import io.github.szrnkapeter.firebase.hosting.util.FileUtils; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.Set; + +import static io.github.szrnkapeter.firebase.hosting.util.Constants.FILES; +import static io.github.szrnkapeter.firebase.hosting.util.Constants.SITES; +import static io.github.szrnkapeter.firebase.hosting.util.Constants.VERSIONS; + +/** + * @author Peter Szrnka + * @since 0.8 + */ +public class FileServiceImpl extends AbstractUtilityService implements FileService { + + public FileServiceImpl(FirebaseHostingApiConfig config, String accessToken) { + super(config, accessToken); + } + + @Override + public GetVersionFilesResponse getVersionFiles(String versionName) throws IOException { + GetVersionFilesResponse response = ConnectionUtils.openHTTPGetConnection(config, GetVersionFilesResponse.class, accessToken, versionName + FILES); + + responseCallback("getVersionFiles", response); + return response; + } + + @Override + public PopulateFilesResponse populateFiles(PopulateFilesRequest request, String version) throws IOException { + String data = config.getSerializer().toJson(PopulateFilesRequest.class, request); + PopulateFilesResponse response = ConnectionUtils.openSimpleHTTPPostConnection(config, PopulateFilesResponse.class, accessToken, SITES + config.getSiteId() + VERSIONS + version + ":populateFiles", data, "populateFiles"); + + responseCallback("populateFiles", response); + return response; + } + + @Override + public void uploadFile(UploadFileRequest request) throws NoSuchAlgorithmException, IOException { + String calculatedHash = FileUtils.getSHA256Checksum(request.getFileContent()); + String url = Constants.UPLOAD_FIREBASE_API_URL + "upload/" + SITES + config.getSiteId() + VERSIONS + request.getVersion() + FILES + "/" + calculatedHash; + + if(request.getUploadUrl() != null) { + url = request.getUploadUrl() + "/" + calculatedHash; + } + + ConnectionUtils.uploadFile(config, accessToken, request.getFileName(), url, request.getFileContent()); + } + + @Override + public void uploadFiles(String versionId, Set files, List requiredHashes) throws IOException, NoSuchAlgorithmException { + for(DeployItem item : files) { + byte[] fileContent = FileUtils.compressAndReadFile(item.getContent()); + String checkSum = FileUtils.getSHA256Checksum(fileContent); + + if(requiredHashes != null && !requiredHashes.contains(checkSum)) { + continue; + } + + UploadFileRequest uploadFilesRequest = new UploadFileRequest(); + uploadFilesRequest.setVersion(versionId); + uploadFilesRequest.setFileContent(fileContent); + uploadFilesRequest.setFileName(item.getName()); + uploadFile(uploadFilesRequest); + } + } +} diff --git a/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/impl/ReleaseServiceImpl.java b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/impl/ReleaseServiceImpl.java new file mode 100644 index 0000000..8ae940a --- /dev/null +++ b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/impl/ReleaseServiceImpl.java @@ -0,0 +1,41 @@ +package io.github.szrnkapeter.firebase.hosting.service.impl; + +import io.github.szrnkapeter.firebase.hosting.config.FirebaseHostingApiConfig; +import io.github.szrnkapeter.firebase.hosting.model.GetReleasesResponse; +import io.github.szrnkapeter.firebase.hosting.model.Release; +import io.github.szrnkapeter.firebase.hosting.service.AbstractUtilityService; +import io.github.szrnkapeter.firebase.hosting.service.ReleaseService; +import io.github.szrnkapeter.firebase.hosting.util.ConnectionUtils; + +import java.io.IOException; + +import static io.github.szrnkapeter.firebase.hosting.util.Constants.SITES; + +/** + * @author Peter Szrnka + * @since 0.8 + */ +public class ReleaseServiceImpl extends AbstractUtilityService implements ReleaseService { + + public ReleaseServiceImpl(FirebaseHostingApiConfig config, String accessToken) { + super(config, accessToken); + } + + @Override + public Release createRelease(String versionName) throws IOException { + Release newRelease = ConnectionUtils.openSimpleHTTPPostConnection(config, Release.class, accessToken, + SITES + config.getSiteId() + "/releases?versionName=" + versionName, null, "createRelease"); + + responseCallback("createRelease", newRelease); + return newRelease; + } + + @Override + public GetReleasesResponse getReleases() throws IOException { + GetReleasesResponse response = ConnectionUtils.openHTTPGetConnection(config, GetReleasesResponse.class, accessToken, + SITES + config.getSiteId() + "/releases"); + + responseCallback("getReleases", response); + return response; + } +} diff --git a/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/impl/VersionServiceImpl.java b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/impl/VersionServiceImpl.java new file mode 100644 index 0000000..7b03244 --- /dev/null +++ b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/service/impl/VersionServiceImpl.java @@ -0,0 +1,68 @@ +package io.github.szrnkapeter.firebase.hosting.service.impl; + +import io.github.szrnkapeter.firebase.hosting.config.FirebaseHostingApiConfig; +import io.github.szrnkapeter.firebase.hosting.model.DeployRequest; +import io.github.szrnkapeter.firebase.hosting.model.Release; +import io.github.szrnkapeter.firebase.hosting.model.Version; +import io.github.szrnkapeter.firebase.hosting.service.AbstractUtilityService; +import io.github.szrnkapeter.firebase.hosting.service.VersionService; +import io.github.szrnkapeter.firebase.hosting.util.ConnectionUtils; +import io.github.szrnkapeter.firebase.hosting.util.Constants; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import static io.github.szrnkapeter.firebase.hosting.util.Constants.SITES; +import static io.github.szrnkapeter.firebase.hosting.util.Constants.VERSIONS; + +/** + * @author Peter Szrnka + * @since 0.8 + */ +public class VersionServiceImpl extends AbstractUtilityService implements VersionService { + + public VersionServiceImpl(FirebaseHostingApiConfig config, String accessToken) { + super(config, accessToken); + } + + @Override + public Version createVersion() throws IOException { + Version newVersion = ConnectionUtils.openSimpleHTTPPostConnection(config, Version.class, accessToken, + SITES + config.getSiteId() + "/versions", "{}", "createVersion"); + + responseCallback("createVersion", newVersion); + return newVersion; + } + + @Override + public void deleteVersion(String versionName) throws IOException { + ConnectionUtils.openSimpleHTTPConnection("DELETE", config, null, accessToken, versionName, null, "deleteVersion"); + } + + @Override + public void deletePreviousVersions(DeployRequest request, List releaseList) throws IOException { + if(!request.isDeletePreviousVersions()) { + return; + } + + AtomicInteger i = new AtomicInteger(0); + + for(Release release : releaseList) { + if(i.get() > 0 && Constants.FINALIZED.equals(release.getVersion().getStatus())) { + deleteVersion(release.getVersion().getName()); + } + + i.incrementAndGet(); + } + } + + @Override + public Version finalizeVersion(String version) throws IOException { + Version newVersion = ConnectionUtils.openSimpleHTTPConnection("PATCH", config, Version.class, accessToken, + SITES + config.getSiteId() + VERSIONS + version + "?update_mask=status", "{ \"status\": \"FINALIZED\" }", "finalizeVersion"); + + responseCallback("finalizeVersion", newVersion); + return newVersion; + } +} diff --git a/code/src/main/java/io/github/szrnkapeter/firebase/hosting/util/ConfigValidationUtils.java b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/util/ConfigValidationUtils.java new file mode 100644 index 0000000..9a21738 --- /dev/null +++ b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/util/ConfigValidationUtils.java @@ -0,0 +1,37 @@ +package io.github.szrnkapeter.firebase.hosting.util; + +import io.github.szrnkapeter.firebase.hosting.config.FirebaseHostingApiConfig; + +/** + * API config validation utility + * + * @author Peter Szrnka + * @since 0.8 + */ +public class ConfigValidationUtils { + + private ConfigValidationUtils() {} + + /** + * Pre-validation is essential to check the input configuration and avoid any unwanted issues. + * + * @param firebaseRestApiConfig A {@link FirebaseHostingApiConfig} instance + */ + public static void preValidateConfig(FirebaseHostingApiConfig firebaseRestApiConfig) { + if (firebaseRestApiConfig == null) { + throw new IllegalArgumentException("FirebaseRestApiConfig is mandatory!"); + } + + if (firebaseRestApiConfig.getSiteId() == null || firebaseRestApiConfig.getSiteId().isEmpty()) { + throw new IllegalArgumentException("Site ID is mandatory!"); + } + + if (firebaseRestApiConfig.getServiceAccountFileStream() == null) { + throw new IllegalArgumentException("Service account file stream is missing from the configuration!"); + } + + if (firebaseRestApiConfig.getSerializer() == null) { + throw new IllegalArgumentException("Serializer is missing from the configuration!"); + } + } +} diff --git a/code/src/main/java/io/github/szrnkapeter/firebase/hosting/util/Constants.java b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/util/Constants.java index ccd6ac0..a5be045 100644 --- a/code/src/main/java/io/github/szrnkapeter/firebase/hosting/util/Constants.java +++ b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/util/Constants.java @@ -9,6 +9,10 @@ public class Constants { private Constants() { } + public static final String FILES = "/files"; + public static final String VERSIONS = "/versions/"; + public static final String SITES = "sites/"; + public static final String FINALIZED = "FINALIZED"; public static final String POST = "POST"; public static final String PATCH = "PATCH"; diff --git a/code/src/main/java/io/github/szrnkapeter/firebase/hosting/util/FileUtils.java b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/util/FileUtils.java index c8daf9c..8c1ec97 100644 --- a/code/src/main/java/io/github/szrnkapeter/firebase/hosting/util/FileUtils.java +++ b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/util/FileUtils.java @@ -1,5 +1,7 @@ package io.github.szrnkapeter.firebase.hosting.util; +import io.github.szrnkapeter.firebase.hosting.model.DeployItem; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -7,6 +9,9 @@ import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; import java.util.logging.Logger; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -24,6 +29,18 @@ public class FileUtils { private FileUtils() { } + public static Map generateFileListAndHash(Set files) throws IOException, NoSuchAlgorithmException { + Map result = new HashMap<>(); + + for(DeployItem file : files) { + byte[] gzippedContent = compressAndReadFile(file.getContent()); + String checkSum = getSHA256Checksum(gzippedContent); + result.put("/" + file.getName(), checkSum); + } + + return result; + } + /** * Returns with the SHA-256 checksum of the given input stream. * @@ -86,9 +103,7 @@ public static byte[] compressAndReadFile(byte[] fileContent) throws IOException public static byte[] getRemoteFile(String urlString) throws IOException { URL url = new URL(urlString); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - InputStream is = null; - try { - is = url.openStream(); + try (InputStream is = url.openStream()) { byte[] byteChunk = new byte[4096]; // Or whatever size you want to read in at a time. int n; @@ -98,10 +113,6 @@ public static byte[] getRemoteFile(String urlString) throws IOException { } catch (IOException e) { logger.warning(String.format("Failed while reading bytes from %s: %s", url.toExternalForm(), e.getMessage())); // Perform any other exception handling that's appropriate. - } finally { - if (is != null) { - is.close(); - } } return baos.toByteArray(); diff --git a/code/src/main/java/io/github/szrnkapeter/firebase/hosting/util/VersionUtils.java b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/util/VersionUtils.java new file mode 100644 index 0000000..a00722a --- /dev/null +++ b/code/src/main/java/io/github/szrnkapeter/firebase/hosting/util/VersionUtils.java @@ -0,0 +1,45 @@ +package io.github.szrnkapeter.firebase.hosting.util; + +import io.github.szrnkapeter.firebase.hosting.config.FirebaseHostingApiConfig; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static io.github.szrnkapeter.firebase.hosting.util.Constants.SITES; +import static io.github.szrnkapeter.firebase.hosting.util.Constants.VERSIONS; + +/** + * Version scraping utilities + * + * @author Peter Szrnka + * @since 0.8 + */ +public class VersionUtils { + + private VersionUtils() {} + + public static String getVersionId(String version) { + if(version == null || version.isEmpty()) { + return null; + } + + return version.substring(version.lastIndexOf("/") + 1); + } + + public static String getVersionName(FirebaseHostingApiConfig config, String version) { + if(version == null || version.isEmpty()) { + throw new IllegalArgumentException("Version field is mandatory!"); + } + + String versionName = SITES + config.getSiteId() + VERSIONS + version; + + Pattern p = Pattern.compile(SITES + config.getSiteId() + VERSIONS + ".*"); + Matcher m = p.matcher(version); + + if (m.matches()) { + versionName = version; + } + + return versionName; + } +} diff --git a/code/src/test/java/io/github/szrnkapeter/firebase/hosting/FirebaseHostingApiClientTest.java b/code/src/test/java/io/github/szrnkapeter/firebase/hosting/FirebaseHostingApiClientTest.java index 168bf2c..b157241 100644 --- a/code/src/test/java/io/github/szrnkapeter/firebase/hosting/FirebaseHostingApiClientTest.java +++ b/code/src/test/java/io/github/szrnkapeter/firebase/hosting/FirebaseHostingApiClientTest.java @@ -1,37 +1,7 @@ package io.github.szrnkapeter.firebase.hosting; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mockStatic; - -import java.io.File; -import java.io.FileInputStream; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatchers; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - import com.fasterxml.jackson.databind.ObjectMapper; - -import io.github.szrnkapeter.firebase.hosting.builder.FirebaseHostingApiConfigBuilder; import io.github.szrnkapeter.firebase.hosting.config.FirebaseHostingApiConfig; -import io.github.szrnkapeter.firebase.hosting.listener.HttpResponseListener; -import io.github.szrnkapeter.firebase.hosting.listener.ServiceResponseListener; import io.github.szrnkapeter.firebase.hosting.model.DeployItem; import io.github.szrnkapeter.firebase.hosting.model.DeployRequest; import io.github.szrnkapeter.firebase.hosting.model.DeployResponse; @@ -43,284 +13,230 @@ import io.github.szrnkapeter.firebase.hosting.model.Release; import io.github.szrnkapeter.firebase.hosting.model.UploadFileRequest; import io.github.szrnkapeter.firebase.hosting.model.Version; -import io.github.szrnkapeter.firebase.hosting.serializer.GsonSerializer; -import io.github.szrnkapeter.firebase.hosting.util.ConnectionUtils; +import io.github.szrnkapeter.firebase.hosting.service.FileService; +import io.github.szrnkapeter.firebase.hosting.service.ReleaseService; +import io.github.szrnkapeter.firebase.hosting.service.VersionService; +import io.github.szrnkapeter.firebase.hosting.util.ConfigValidationUtils; import io.github.szrnkapeter.firebase.hosting.util.FileUtils; import io.github.szrnkapeter.firebase.hosting.util.GoogleCredentialUtils; +import io.github.szrnkapeter.firebase.hosting.util.VersionUtils; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.MockedStatic; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import static io.github.szrnkapeter.firebase.hosting.util.TestConfigGenerator.getFirebaseRestApiConfig; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; -@SuppressWarnings("unchecked") class FirebaseHostingApiClientTest { - private static final String SEPARATOR = " / "; private static final String ACCESS_TOKEN = "TOKEN"; - private static final String VERSION_NAME = "version1"; - private static int mockCounter = 0; - - private static final MockedStatic mockedGoogleCredentialUtils = mockStatic(GoogleCredentialUtils.class); - private static final MockedStatic mockedConnectionUtilsUtils = mockStatic(ConnectionUtils.class); - private static final MockedStatic mockedFileUtilsUtils = mockStatic(FileUtils.class); - - @BeforeEach - public void setup() { - mockedGoogleCredentialUtils.when(() -> GoogleCredentialUtils.getAccessToken(ArgumentMatchers.any(FirebaseHostingApiConfig.class))).thenReturn(ACCESS_TOKEN); - } - - @AfterAll - public static void tearAllDown() { - mockedGoogleCredentialUtils.close(); - mockedConnectionUtilsUtils.close(); - mockedFileUtilsUtils.close(); - } - - private FirebaseHostingApiConfig getFirebaseRestApiConfig() throws Exception { - return FirebaseHostingApiConfigBuilder.builder() - .withServiceAccountFileStream(new FileInputStream("src/test/resources/test.json")) - .withSiteId("test") - .withSerializer(new GsonSerializer()) - .withDefaultConnectionTimeout(60000).withDefaultReadTimeout(60000) - .withHttpResponseListener((function, code, responseMessage) -> System.out.println(function + SEPARATOR + code + SEPARATOR + responseMessage)) - .withServiceResponseListener(new ServiceResponseListener() { - - @Override - public void getResponse(String function, T response) { - System.out.println(function + SEPARATOR + response); - } - }) - .build(); + private static final String VERSION = "1.0"; + + private FirebaseHostingApiConfig config; + private ReleaseService releaseService; + private VersionService versionService; + private FileService fileService; + private FirebaseHostingApiClient client; + + @SneakyThrows + void setup() { + config = getFirebaseRestApiConfig(); + releaseService = mock(ReleaseService.class); + versionService = mock(VersionService.class); + fileService = mock(FileService.class); + client = new FirebaseHostingApiClient(config, releaseService, versionService, fileService); } @Test - void test001_GetReleases() throws Exception { - FirebaseHostingApiClient client = new FirebaseHostingApiClient(getFirebaseRestApiConfig()); + void shouldCreateWithStaticMethod() { + setup(); - ObjectMapper objectMapper = new ObjectMapper(); - GetReleasesResponse mockGetReleasesResponse = objectMapper.readValue( - new File("src/test/resources/sample-response-get-releases1.json"), GetReleasesResponse.class); + try (MockedStatic mockedGoogleCredentialUtils = mockStatic(GoogleCredentialUtils.class); + MockedStatic mockedConfigValidationUtil = mockStatic(ConfigValidationUtils.class)) { + // arrange + mockedGoogleCredentialUtils.when(() -> GoogleCredentialUtils.getAccessToken(any(FirebaseHostingApiConfig.class))).thenReturn(ACCESS_TOKEN); - mockedConnectionUtilsUtils.when(() -> ConnectionUtils.openHTTPGetConnection(ArgumentMatchers.any(FirebaseHostingApiConfig.class), - ArgumentMatchers.any(Class.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) - .thenReturn(mockGetReleasesResponse); - - GetReleasesResponse response = client.getReleases(); + // act + FirebaseHostingApiClient response = FirebaseHostingApiClient.newClient(config); - assertNotNull(response); - assertEquals("XY012345", response.getNextPageToken()); - assertEquals(25, response.getReleases().size()); - } - - @Test - void test002_GetVersionFiles_VersionNull() throws Exception { - FirebaseHostingApiClient client = new FirebaseHostingApiClient(getFirebaseRestApiConfig()); - - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> client.getVersionFiles(null)); - - assertEquals("Version field is mandatory!", exception.getMessage()); + // assert + assertNotNull(response); + mockedGoogleCredentialUtils.verify(() -> GoogleCredentialUtils.getAccessToken(any(FirebaseHostingApiConfig.class))); + mockedConfigValidationUtil.verify(() -> ConfigValidationUtils.preValidateConfig(config)); + } } @Test - void test003_GetVersionFiles_VersionEmpty() throws Exception { - FirebaseHostingApiClient client = new FirebaseHostingApiClient(getFirebaseRestApiConfig()); - - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> client.getVersionFiles("")); - assertEquals("Version field is mandatory!", exception.getMessage()); + @SneakyThrows + void shouldCreateRelease() { + try (MockedStatic mockedVersionUtils = mockStatic(VersionUtils.class)) { + // arrange + setup(); + Release mockResponse = new Release(); + when(releaseService.createRelease(VERSION)).thenReturn(mockResponse); + mockedVersionUtils.when(() -> + VersionUtils.getVersionName(any(FirebaseHostingApiConfig.class), eq(VERSION))).thenReturn(VERSION); + + // act + Release response = client.createRelease(VERSION); + + // assert + assertEquals(mockResponse, response); + verify(releaseService).createRelease(VERSION); + } } @Test - void test004_GetVersionFiles_VersionEmpty() throws Exception { - FirebaseHostingApiClient client = new FirebaseHostingApiClient(getFirebaseRestApiConfig()); - - GetVersionFilesResponse getVersionFilesResponse = new GetVersionFilesResponse(); - mockedConnectionUtilsUtils.when(() ->ConnectionUtils.openHTTPGetConnection(ArgumentMatchers.any(FirebaseHostingApiConfig.class), - ArgumentMatchers.any(Class.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) - .thenReturn(getVersionFilesResponse); - - GetVersionFilesResponse response = client.getVersionFiles(VERSION_NAME); - assertNotNull(response); + @SneakyThrows + void shouldShouldCreateVersion() { + // arrange + setup(); + Version mockVersion = new Version(); + when(versionService.createVersion()).thenReturn(mockVersion); + + // act + Version response = client.createVersion(); - response = client.getVersionFiles("sites/test/versions/72e01552bb5498a1"); - assertNotNull(response); + // assert + assertEquals(mockVersion, response); + verify(versionService).createVersion(); } @Test - void test005_BadConstructor() throws Exception { - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> new FirebaseHostingApiClient(null)); - assertEquals("FirebaseRestApiConfig field is mandatory!", exception.getMessage()); + @SneakyThrows + void shouldDeleteVersion() { + try (MockedStatic mockedVersionUtils = mockStatic(VersionUtils.class)) { + // arrange + setup(); + mockedVersionUtils.when(() -> + VersionUtils.getVersionName(any(FirebaseHostingApiConfig.class), eq(VERSION))).thenReturn(VERSION); + + // act + client.deleteVersion(VERSION); + + // assert + verify(versionService).deleteVersion(VERSION); + } } @Test - void test006_CreateRelease() throws Exception { - FirebaseHostingApiClient client = new FirebaseHostingApiClient(getFirebaseRestApiConfig()); - - Release mockResponse = new Release(); - mockedConnectionUtilsUtils.when(() ->ConnectionUtils.openSimpleHTTPPostConnection(ArgumentMatchers.any(FirebaseHostingApiConfig.class), - ArgumentMatchers.any(Class.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), - ArgumentMatchers.isNull(), ArgumentMatchers.anyString())).thenReturn(mockResponse); - - Release response = client.createRelease(VERSION_NAME); - assertNotNull(response); + @SneakyThrows + void shouldShouldFinalizeVersion() { + // arrange + setup(); + Version mockVersion = new Version(); + when(versionService.finalizeVersion(VERSION)).thenReturn(mockVersion); + + // act + Version response = client.finalizeVersion(VERSION); + + // assert + assertEquals(mockVersion, response); + verify(versionService).finalizeVersion(VERSION); } @Test - void test007_CreateVersion() throws Exception { - FirebaseHostingApiClient client = new FirebaseHostingApiClient(getFirebaseRestApiConfig()); + @SneakyThrows + void shouldGetReleases() { + // arrange + setup(); + ObjectMapper objectMapper = new ObjectMapper(); + GetReleasesResponse mockGetReleasesResponse = objectMapper.readValue( + new File("src/test/resources/sample-response-get-releases1.json"), GetReleasesResponse.class); + when(releaseService.getReleases()).thenReturn(mockGetReleasesResponse); - String randomName = UUID.randomUUID().toString(); - Version mockResponse = new Version(); - mockResponse.setName(randomName); - mockedConnectionUtilsUtils.when(() ->ConnectionUtils.openSimpleHTTPPostConnection(ArgumentMatchers.any(FirebaseHostingApiConfig.class), - ArgumentMatchers.any(Class.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), - ArgumentMatchers.anyString(), ArgumentMatchers.anyString())).thenReturn(mockResponse); + // act + GetReleasesResponse response = client.getReleases(); - Version response = client.createVersion(); + // assert assertNotNull(response); - assertEquals(randomName, response.getName()); - } - - @Test - void test008_DeleteVersion() throws Exception { - FirebaseHostingApiClient client = new FirebaseHostingApiClient(getFirebaseRestApiConfig()); - - mockedConnectionUtilsUtils.when(() ->ConnectionUtils.openSimpleHTTPConnection(ArgumentMatchers.anyString(), - ArgumentMatchers.any(FirebaseHostingApiConfig.class), ArgumentMatchers.any(Class.class), - ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.isNull(), ArgumentMatchers.anyString())) - .then(new Answer() { - - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - String version = invocation.getArgument(0); - assertEquals(VERSION_NAME, version); - return null; - } - }); - - client.deleteVersion(VERSION_NAME); + assertEquals(mockGetReleasesResponse, response); } @Test - void test009_FinalizeVersion() throws Exception { - FirebaseHostingApiClient client = new FirebaseHostingApiClient(getFirebaseRestApiConfig()); - - String randomName = UUID.randomUUID().toString(); - Version mockResponse = new Version(); - mockResponse.setName(randomName); - mockedConnectionUtilsUtils.when(() ->ConnectionUtils.openSimpleHTTPConnection(ArgumentMatchers.anyString(), - ArgumentMatchers.any(FirebaseHostingApiConfig.class), ArgumentMatchers.any(Class.class), - ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) - .thenReturn(mockResponse); - - Version response = client.finalizeVersion(VERSION_NAME); - assertNotNull(response); - assertEquals(randomName, response.getName()); + @SneakyThrows + void shouldGetVersionFiles() { + try (MockedStatic mockedVersionUtils = mockStatic(VersionUtils.class)) { + // arrange + setup(); + GetVersionFilesResponse getVersionFilesResponse = new GetVersionFilesResponse(); + when(fileService.getVersionFiles(VERSION)).thenReturn(getVersionFilesResponse); + mockedVersionUtils.when(() -> + VersionUtils.getVersionName(any(FirebaseHostingApiConfig.class), eq(VERSION))).thenReturn(VERSION); + + // act + GetVersionFilesResponse response = client.getVersionFiles(VERSION); + + // assert + assertEquals(getVersionFilesResponse, response); + verify(fileService).getVersionFiles(VERSION); + } } @Test - void test010_PopulateFiles() throws Exception { - FirebaseHostingApiClient client = new FirebaseHostingApiClient(getFirebaseRestApiConfig()); + @SneakyThrows + void shouldPopulateFiles() { + // arrange + setup(); + PopulateFilesRequest request = new PopulateFilesRequest(); String randomHash = UUID.randomUUID().toString(); PopulateFilesResponse mockResponse = new PopulateFilesResponse(); List mockHashes = new ArrayList<>(); mockHashes.add(randomHash); mockResponse.setUploadRequiredHashes(mockHashes); + when(fileService.populateFiles(request, VERSION)).thenReturn(mockResponse); - mockedConnectionUtilsUtils.when(() -> ConnectionUtils.openSimpleHTTPPostConnection(ArgumentMatchers.any(FirebaseHostingApiConfig.class), - ArgumentMatchers.any(Class.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), - ArgumentMatchers.anyString(), ArgumentMatchers.anyString())).thenReturn(mockResponse); - - PopulateFilesRequest request = new PopulateFilesRequest(); - request.setFiles(new HashMap()); - PopulateFilesResponse response = client.populateFiles(request, VERSION_NAME); - assertNotNull(response); - assertFalse(response.getUploadRequiredHashes().isEmpty()); - } - - @Test - void test011_CreateDeploy() throws Exception { - initCreateDeploy(true, false, false); - initCreateDeploy(false, false, false); - initCreateDeploy(false, true, false); - initCreateDeploy(false, false, true); - } - - @Test - void test012_UploadFile() throws Exception { - FirebaseHostingApiClient client = new FirebaseHostingApiClient(getFirebaseRestApiConfig()); - - mockCounter = 0; - mockedFileUtilsUtils.when(() ->FileUtils.getSHA256Checksum(ArgumentMatchers.any(byte[].class))).thenAnswer(new Answer() { - - @Override - public String answer(InvocationOnMock invocation) throws Throwable { - mockCounter++; - return "testSha"; - } - }); - - UploadFileRequest request = new UploadFileRequest(); - request.setFileContent("test".getBytes()); - request.setUploadUrl("http://www.test.com"); - client.uploadFile(request); - - assertEquals(1, mockCounter); - } - - @Test - void test013_GetVersionId_Fail1() throws Exception { - FirebaseHostingApiClient client = new FirebaseHostingApiClient(getFirebaseRestApiConfig()); - - String response = client.getVersionId(null); - assertNull(response); - } - - @Test - void test013_GetVersionId_Fail2() throws Exception { - FirebaseHostingApiClient client = new FirebaseHostingApiClient(getFirebaseRestApiConfig()); - - String response = client.getVersionId(""); - assertNull(response); - } - - @Test - void test014_Init_Fail1_SiteIdNull() throws Exception { - FirebaseHostingApiConfig invalidConfig = getFirebaseRestApiConfig(); - invalidConfig.setSiteId(null); + // act + PopulateFilesResponse response = client.populateFiles(request, VERSION); - // act & assert - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> new FirebaseHostingApiClient(invalidConfig)); - assertEquals("Site name is mandatory!", exception.getMessage()); + // assert + assertEquals(mockResponse, response); + verify(fileService).populateFiles(request, VERSION); } @Test - void test015_Init_Fail1_SiteIdEmpty() throws Exception { - FirebaseHostingApiConfig invalidConfig = getFirebaseRestApiConfig(); - invalidConfig.setSiteId(""); - - // act & assert - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> new FirebaseHostingApiClient(invalidConfig)); - assertEquals("Site name is mandatory!", exception.getMessage()); - } + @SneakyThrows + void shouldUploadFile() { + // arrange + setup(); + UploadFileRequest request = new UploadFileRequest(); - @Test - void test016_Init_Fail1_ServiceAccountStreamNull() throws Exception { - FirebaseHostingApiConfig invalidConfig = getFirebaseRestApiConfig(); - invalidConfig.setServiceAccountFileStream(null); + // act + client.uploadFile(request); - // act & assert - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> new FirebaseHostingApiClient(invalidConfig)); - assertEquals("Service account file stream is missing from the configuration!", exception.getMessage()); + // assert + verify(fileService).uploadFile(request); } - + @Test - void test017_Init_Fail1_SiteIdEmpty() throws Exception { - FirebaseHostingApiConfig invalidConfig = getFirebaseRestApiConfig(); - invalidConfig.setSerializer(null); - - // act & assert - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> new FirebaseHostingApiClient(invalidConfig)); - assertEquals("Serializer is missing from the configuration!", exception.getMessage()); + @SneakyThrows + void shouldCreateDeploy() { + initCreateDeploy(true, false, false); + initCreateDeploy(false, false, false); + initCreateDeploy(false, true, false); + initCreateDeploy(false, false, true); } private void initCreateDeploy(boolean cleanDeploy, boolean nullRelease, boolean nullResponseListener) throws Exception { + MockedStatic mockedFileUtils = mockStatic(FileUtils.class); DeployRequest request = new DeployRequest(); request.setCleanDeploy(cleanDeploy); Set fileList = new HashSet<>(); @@ -338,17 +254,18 @@ private void initCreateDeploy(boolean cleanDeploy, boolean nullRelease, boolean config.setHttpResponseListener(null); } - FirebaseHostingApiClient client = new FirebaseHostingApiClient(config); - + releaseService = mock(ReleaseService.class); + versionService = mock(VersionService.class); + fileService = mock(FileService.class); + client = new FirebaseHostingApiClient(config, releaseService, versionService, fileService); + // Mocking getReleases() ObjectMapper objectMapper = new ObjectMapper(); GetReleasesResponse mockGetReleasesResponse = objectMapper.readValue( new File("src/test/resources/sample-response-get-releases1.json"), GetReleasesResponse.class); - mockedConnectionUtilsUtils.when(() -> ConnectionUtils.openHTTPGetConnection(ArgumentMatchers.any(FirebaseHostingApiConfig.class), - ArgumentMatchers.eq(GetReleasesResponse.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) - .thenReturn(mockGetReleasesResponse); - + when(releaseService.getReleases()).thenReturn(mockGetReleasesResponse); + // Mocking getVersionFiles() GetVersionFilesResponse getVersionFilesResponse = new GetVersionFilesResponse(); List mockFiles = new ArrayList<>(); @@ -357,17 +274,14 @@ private void initCreateDeploy(boolean cleanDeploy, boolean nullRelease, boolean mockFiles.add(FirebaseHostingApiClientTestHelper.createFileDetails("/test2.txt", "DEPLOYED")); getVersionFilesResponse.setFiles(mockFiles); - mockedConnectionUtilsUtils.when(() -> ConnectionUtils.openHTTPGetConnection(ArgumentMatchers.any(FirebaseHostingApiConfig.class), - ArgumentMatchers.eq(GetVersionFilesResponse.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) - .thenReturn(getVersionFilesResponse); - + when(fileService.getVersionFiles(anyString())).thenReturn(getVersionFilesResponse); + // Mocking createVersion() Version mockResponse = new Version(); String randomName = UUID.randomUUID().toString(); mockResponse.setName(randomName); - mockedConnectionUtilsUtils.when(() -> ConnectionUtils.openSimpleHTTPPostConnection(ArgumentMatchers.any(FirebaseHostingApiConfig.class), - ArgumentMatchers.eq(Version.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), - ArgumentMatchers.anyString(), ArgumentMatchers.anyString())).thenReturn(mockResponse); + + when(versionService.createVersion()).thenReturn(mockResponse); // Mocking populateFiles() PopulateFilesResponse mockPopResponse = new PopulateFilesResponse(); @@ -375,39 +289,16 @@ private void initCreateDeploy(boolean cleanDeploy, boolean nullRelease, boolean mockHashes.add("asd1"); mockPopResponse.setUploadRequiredHashes(mockHashes); - mockedConnectionUtilsUtils.when(() -> ConnectionUtils.openSimpleHTTPPostConnection(ArgumentMatchers.any(FirebaseHostingApiConfig.class), - ArgumentMatchers.eq(PopulateFilesResponse.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), - ArgumentMatchers.anyString(), ArgumentMatchers.anyString())).thenReturn(mockPopResponse); + when(fileService.populateFiles(any(PopulateFilesRequest.class), anyString())).thenReturn(mockPopResponse); // Mock finalizeVersion() String randomVersionName = UUID.randomUUID().toString(); Version mockVersionResponse = new Version(); mockResponse.setName(randomVersionName); - mockedConnectionUtilsUtils.when(() -> ConnectionUtils.openSimpleHTTPConnection(ArgumentMatchers.anyString(), - ArgumentMatchers.any(FirebaseHostingApiConfig.class), ArgumentMatchers.eq(Version.class), - ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) - .thenReturn(mockVersionResponse); - + when(versionService.finalizeVersion(VERSION)).thenReturn(mockVersionResponse); + // Mocking fileUtils - Mockito.when(FileUtils.getRemoteFile(ArgumentMatchers.anyString())).thenReturn(new byte[0]); - - mockCounter = 0; - - byte[] mockCompressedFile = FileUtils.compressAndReadFile((Files.readAllBytes(new File("src/test/resources/test1.txt").toPath()))); - mockedFileUtilsUtils.when(() -> FileUtils.compressAndReadFile(ArgumentMatchers.any(byte[].class))).thenReturn(mockCompressedFile); - mockedFileUtilsUtils.when(() -> FileUtils.getSHA256Checksum(mockCompressedFile)).thenAnswer(new Answer() { - - @Override - public String answer(InvocationOnMock invocation) throws Throwable { - mockCounter++; - - if(mockCounter % 2 == 1) { - return "asd1"; - } - - return "asd2"; - } - }); + mockedFileUtils.when(() -> FileUtils.getRemoteFile(ArgumentMatchers.anyString())).thenReturn(new byte[0]); // Service call DeployResponse response = client.createDeploy(request); @@ -421,25 +312,21 @@ public String answer(InvocationOnMock invocation) throws Throwable { if(nullRelease && !cleanDeploy) { mockGetReleasesResponse = objectMapper.readValue( new File("src/test/resources/sample-response-get-releases2.json"), GetReleasesResponse.class); - mockedConnectionUtilsUtils.when(() -> ConnectionUtils.openHTTPGetConnection(ArgumentMatchers.any(FirebaseHostingApiConfig.class), - ArgumentMatchers.eq(GetReleasesResponse.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) - .thenReturn(mockGetReleasesResponse); + when(releaseService.getReleases()).thenReturn(mockGetReleasesResponse); response = client.createDeploy(request); assertNull(response); mockGetReleasesResponse = objectMapper.readValue( new File("src/test/resources/sample-response-get-releases3.json"), GetReleasesResponse.class); - mockedConnectionUtilsUtils.when(() -> ConnectionUtils.openHTTPGetConnection(ArgumentMatchers.any(FirebaseHostingApiConfig.class), - ArgumentMatchers.eq(GetReleasesResponse.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) - .thenReturn(mockGetReleasesResponse); + when(releaseService.getReleases()).thenReturn(mockGetReleasesResponse); + response = client.createDeploy(request); assertNull(response); - - mockedConnectionUtilsUtils.when(() -> ConnectionUtils.openHTTPGetConnection(ArgumentMatchers.any(FirebaseHostingApiConfig.class), - ArgumentMatchers.eq(GetReleasesResponse.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) - .thenReturn(null); + + when(releaseService.getReleases()).thenReturn(null); response = client.createDeploy(request); assertNull(response); } + mockedFileUtils.close(); } } \ No newline at end of file diff --git a/code/src/test/java/io/github/szrnkapeter/firebase/hosting/service/impl/FileServiceImplTest.java b/code/src/test/java/io/github/szrnkapeter/firebase/hosting/service/impl/FileServiceImplTest.java new file mode 100644 index 0000000..5370634 --- /dev/null +++ b/code/src/test/java/io/github/szrnkapeter/firebase/hosting/service/impl/FileServiceImplTest.java @@ -0,0 +1,179 @@ +package io.github.szrnkapeter.firebase.hosting.service.impl; + +import io.github.szrnkapeter.firebase.hosting.config.FirebaseHostingApiConfig; +import io.github.szrnkapeter.firebase.hosting.model.DeployItem; +import io.github.szrnkapeter.firebase.hosting.model.GetVersionFilesResponse; +import io.github.szrnkapeter.firebase.hosting.model.PopulateFilesRequest; +import io.github.szrnkapeter.firebase.hosting.model.PopulateFilesResponse; +import io.github.szrnkapeter.firebase.hosting.model.UploadFileRequest; +import io.github.szrnkapeter.firebase.hosting.serializer.GsonSerializer; +import io.github.szrnkapeter.firebase.hosting.util.ConnectionUtils; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedStatic; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.when; + +/** + * @author Peter Szrnka + * @since 0.8 + */ +class FileServiceImplTest { + + private static final String VERSION_NAME = "version1"; + + private FirebaseHostingApiConfig config; + private FileServiceImpl service; + + @BeforeEach + void setup() { + config = mock(FirebaseHostingApiConfig.class); + service = new FileServiceImpl(config, "accessToken"); + + when(config.getSiteId()).thenReturn("test"); + } + + @Test + @SneakyThrows + void shouldGetVersionFiles() { + try (MockedStatic mockedConnectionUtilsUtils = mockStatic(ConnectionUtils.class)) { + // arrange + GetVersionFilesResponse getVersionFilesResponse = new GetVersionFilesResponse(); + mockedConnectionUtilsUtils.when(() -> ConnectionUtils.openHTTPGetConnection(any(FirebaseHostingApiConfig.class), + any(Class.class), anyString(), anyString())) + .thenReturn(getVersionFilesResponse); + + // act + GetVersionFilesResponse response = service.getVersionFiles(VERSION_NAME); + + // assert + assertNotNull(response); + mockedConnectionUtilsUtils.verify(() -> ConnectionUtils.openHTTPGetConnection(any(FirebaseHostingApiConfig.class), + any(Class.class), anyString(), anyString())); + } + } + + @Test + @SneakyThrows + void shouldPopulateFiles() { + try (MockedStatic mockedConnectionUtilsUtils = mockStatic(ConnectionUtils.class)) { + // arrange + String randomHash = UUID.randomUUID().toString(); + PopulateFilesResponse mockResponse = new PopulateFilesResponse(); + List mockHashes = new ArrayList<>(); + mockHashes.add(randomHash); + mockResponse.setUploadRequiredHashes(mockHashes); + + mockedConnectionUtilsUtils.when(() -> ConnectionUtils.openSimpleHTTPPostConnection(any(FirebaseHostingApiConfig.class), + any(Class.class), anyString(), anyString(), + anyString(), anyString())).thenReturn(mockResponse); + + PopulateFilesRequest request = new PopulateFilesRequest(); + request.setFiles(new HashMap<>()); + + when(config.getSerializer()).thenReturn(new GsonSerializer()); + + // act + PopulateFilesResponse response = service.populateFiles(request, VERSION_NAME); + + // assert + assertNotNull(response); + assertFalse(response.getUploadRequiredHashes().isEmpty()); + + ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); + mockedConnectionUtilsUtils.verify(() -> ConnectionUtils.openSimpleHTTPPostConnection(any(FirebaseHostingApiConfig.class), + any(Class.class), anyString(), anyString(), dataCaptor.capture(), anyString())); + + assertEquals("{\"files\":{}}", dataCaptor.getValue()); + } + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("uploadFileData") + void shouldUploadFile(String uploadUrl, String expectedUrl) { + try (MockedStatic mockedConnectionUtilsUtils = mockStatic(ConnectionUtils.class)) { + // arrange + UploadFileRequest request = new UploadFileRequest(); + request.setFileName("file1.txt"); + request.setFileContent("test1".getBytes()); + request.setVersion("1.0"); + request.setUploadUrl(uploadUrl); + + // act + service.uploadFile(request); + + // assert + ArgumentCaptor urlCaptor = ArgumentCaptor.forClass(String.class); + mockedConnectionUtilsUtils.verify(() -> + ConnectionUtils.uploadFile(eq(config), eq("accessToken"), anyString(), urlCaptor.capture(), any())); + assertEquals(expectedUrl, urlCaptor.getValue()); + } + } + + @Test + @SneakyThrows + void shouldNotUploadFiles() { + try (MockedStatic mockedConnectionUtilsUtils = mockStatic(ConnectionUtils.class)) { + // arrange + Set files = new HashSet<>(); + + // act + service.uploadFiles("1.0", files, null); + + // assert + mockedConnectionUtilsUtils.verify(() -> + ConnectionUtils.uploadFile(eq(config), eq("accessToken"), anyString(), anyString(), any()), never()); + } + } + + @Test + @SneakyThrows + void shouldUploadFiles() { + try (MockedStatic mockedConnectionUtilsUtils = mockStatic(ConnectionUtils.class)) { + // arrange + DeployItem item1 = new DeployItem("file1.txt", "test".getBytes()); + DeployItem item2 = new DeployItem("file2.txt", "test2".getBytes()); + Set files = new HashSet<>(); + files.add(item1); + files.add(item2); + + List requiredHashes = new ArrayList<>(); + requiredHashes.add("f7beb20179aee76b26b8b4b0840a89a70b1fbb72333892df6c54fe1010640cb3"); + + // act + service.uploadFiles("1.0", files, requiredHashes); + + // assert + mockedConnectionUtilsUtils.verify(() -> + ConnectionUtils.uploadFile(eq(config), eq("accessToken"), anyString(), anyString(), any())); + } + } + + private static Object[][] uploadFileData() { + return new Object[][] { + { "", "/1b4f0e9851971998e732078544c96b36c3d01cedf7caa332359d6f1d83567014" }, + { "upload-url", "upload-url/1b4f0e9851971998e732078544c96b36c3d01cedf7caa332359d6f1d83567014" } + }; + } +} diff --git a/code/src/test/java/io/github/szrnkapeter/firebase/hosting/service/impl/ReleaseServiceImplTest.java b/code/src/test/java/io/github/szrnkapeter/firebase/hosting/service/impl/ReleaseServiceImplTest.java new file mode 100644 index 0000000..3991438 --- /dev/null +++ b/code/src/test/java/io/github/szrnkapeter/firebase/hosting/service/impl/ReleaseServiceImplTest.java @@ -0,0 +1,123 @@ +package io.github.szrnkapeter.firebase.hosting.service.impl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.szrnkapeter.firebase.hosting.config.FirebaseHostingApiConfig; +import io.github.szrnkapeter.firebase.hosting.listener.ServiceResponseListener; +import io.github.szrnkapeter.firebase.hosting.model.GetReleasesResponse; +import io.github.szrnkapeter.firebase.hosting.model.Release; +import io.github.szrnkapeter.firebase.hosting.util.ConnectionUtils; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.MockedStatic; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @author Peter Szrnka + * @since 0.8 + */ +class ReleaseServiceImplTest { + + private static final String VERSION_NAME = "version1"; + + private FirebaseHostingApiConfig config; + private ReleaseServiceImpl service; + + @BeforeEach + @SneakyThrows + void setup() { + config = mock(FirebaseHostingApiConfig.class); + service = new ReleaseServiceImpl(config, "accessToken"); + + when(config.getSiteId()).thenReturn("test"); + } + + @SneakyThrows + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void shouldCreateRelease(boolean addResponseListener) { + ServiceResponseListener mockServiceResponseListener = mock(ServiceResponseListener.class); + if (addResponseListener) { + doNothing().when(mockServiceResponseListener).getResponse(anyString(), any()); + when(config.getServiceResponseListener()).thenReturn(mockServiceResponseListener); + } + try (MockedStatic mockedConnectionUtilsUtils = mockStatic(ConnectionUtils.class)) { + // arrange + Release mockResponse = new Release(); + mockedConnectionUtilsUtils.when(() -> ConnectionUtils.openSimpleHTTPPostConnection(any(FirebaseHostingApiConfig.class), + any(Class.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), + ArgumentMatchers.isNull(), ArgumentMatchers.anyString())).thenReturn(mockResponse); + + // act + Release response = service.createRelease(VERSION_NAME); + + // assert + assertNotNull(response); + ArgumentCaptor urlCaptor = ArgumentCaptor.forClass(String.class); + mockedConnectionUtilsUtils.verify(() -> ConnectionUtils.openSimpleHTTPPostConnection(any(FirebaseHostingApiConfig.class), + any(Class.class), ArgumentMatchers.anyString(), urlCaptor.capture(), + ArgumentMatchers.isNull(), ArgumentMatchers.anyString())); + + assertEquals("sites/test/releases?versionName=version1", urlCaptor.getValue()); + } + + verify(config, times(addResponseListener ? 2 : 1)).getServiceResponseListener(); + if (addResponseListener) { + ArgumentCaptor functionCaptor = ArgumentCaptor.forClass(String.class); + verify(config.getServiceResponseListener()).getResponse(functionCaptor.capture(), any()); + assertEquals("createRelease", functionCaptor.getValue()); + } + } + + @SneakyThrows + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void shouldGetReleases(boolean addResponseListener) { + ServiceResponseListener mockServiceResponseListener = mock(ServiceResponseListener.class); + if (addResponseListener) { + when(config.getServiceResponseListener()).thenReturn(mockServiceResponseListener); + } + try (MockedStatic mockedConnectionUtilsUtils = mockStatic(ConnectionUtils.class)) { + // arrange + ObjectMapper objectMapper = new ObjectMapper(); + GetReleasesResponse mockGetReleasesResponse = objectMapper.readValue( + new File("src/test/resources/sample-response-get-releases1.json"), GetReleasesResponse.class); + + mockedConnectionUtilsUtils.when(() -> ConnectionUtils.openHTTPGetConnection(any(FirebaseHostingApiConfig.class), + any(Class.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) + .thenReturn(mockGetReleasesResponse); + + // act + GetReleasesResponse response = service.getReleases(); + + // assert + assertNotNull(response); + ArgumentCaptor urlCaptor = ArgumentCaptor.forClass(String.class); + mockedConnectionUtilsUtils.verify(() -> ConnectionUtils.openHTTPGetConnection(any(FirebaseHostingApiConfig.class), + any(Class.class), ArgumentMatchers.anyString(), urlCaptor.capture())); + + assertEquals("sites/test/releases", urlCaptor.getValue()); + } + + if (addResponseListener) { + ArgumentCaptor functionCaptor = ArgumentCaptor.forClass(String.class); + verify(config.getServiceResponseListener()).getResponse(functionCaptor.capture(), any()); + assertEquals("getReleases", functionCaptor.getValue()); + } + } +} diff --git a/code/src/test/java/io/github/szrnkapeter/firebase/hosting/service/impl/VersionServiceImplTest.java b/code/src/test/java/io/github/szrnkapeter/firebase/hosting/service/impl/VersionServiceImplTest.java new file mode 100644 index 0000000..ba97d37 --- /dev/null +++ b/code/src/test/java/io/github/szrnkapeter/firebase/hosting/service/impl/VersionServiceImplTest.java @@ -0,0 +1,151 @@ +package io.github.szrnkapeter.firebase.hosting.service.impl; + +import io.github.szrnkapeter.firebase.hosting.config.FirebaseHostingApiConfig; +import io.github.szrnkapeter.firebase.hosting.model.DeployRequest; +import io.github.szrnkapeter.firebase.hosting.model.Release; +import io.github.szrnkapeter.firebase.hosting.model.Version; +import io.github.szrnkapeter.firebase.hosting.util.ConnectionUtils; +import io.github.szrnkapeter.firebase.hosting.util.Constants; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.when; + +/** + * @author Peter Szrnka + * @since 0.8 + */ +class VersionServiceImplTest { + + private FirebaseHostingApiConfig config; + private VersionServiceImpl service; + + @BeforeEach + @SneakyThrows + void setup() { + config = mock(FirebaseHostingApiConfig.class); + service = new VersionServiceImpl(config, "accessToken"); + + when(config.getSiteId()).thenReturn("test"); + } + + @Test + @SneakyThrows + void shouldCreateVersion() { + try (MockedStatic mockedConnectionUtilsUtils = mockStatic(ConnectionUtils.class)) { + // arrange + String randomName = UUID.randomUUID().toString(); + Version mockResponse = new Version(); + mockResponse.setName(randomName); + mockedConnectionUtilsUtils.when(() -> ConnectionUtils.openSimpleHTTPPostConnection(any(FirebaseHostingApiConfig.class), + any(Class.class), anyString(), anyString(), + anyString(), anyString())).thenReturn(mockResponse); + + // act + Version response = service.createVersion(); + + // assert + assertNotNull(response); + mockedConnectionUtilsUtils.verify(() -> ConnectionUtils.openSimpleHTTPPostConnection(any(FirebaseHostingApiConfig.class), + any(Class.class), anyString(), anyString(), + anyString(), anyString())); + } + } + + @Test + @SneakyThrows + void shouldDeleteVersion() { + try (MockedStatic mockedConnectionUtilsUtils = mockStatic(ConnectionUtils.class)) { + // act + service.deleteVersion("1.0"); + + // assert + mockedConnectionUtilsUtils.verify(() -> ConnectionUtils.openSimpleHTTPConnection(eq("DELETE"), + any(FirebaseHostingApiConfig.class), isNull(), eq("accessToken"), anyString(), isNull(), anyString())); + } + } + + @Test + @SneakyThrows + void shouldNotDeletePreviousVersions() { + try (MockedStatic mockedConnectionUtilsUtils = mockStatic(ConnectionUtils.class)) { + // arrange + DeployRequest request = new DeployRequest(); + request.setDeletePreviousVersions(false); + List releaseList = new ArrayList<>(); + + // act + service.deletePreviousVersions(request, releaseList); + + // assert + mockedConnectionUtilsUtils.verify(() -> ConnectionUtils.openSimpleHTTPConnection(eq("DELETE"), + any(FirebaseHostingApiConfig.class), isNull(), eq("accessToken"), anyString(), isNull(), anyString()), never()); + } + } + + @Test + @SneakyThrows + void shouldDeletePreviousVersions() { + try (MockedStatic mockedConnectionUtilsUtils = mockStatic(ConnectionUtils.class)) { + // arrange + DeployRequest request = new DeployRequest(); + request.setDeletePreviousVersions(true); + List releaseList = new ArrayList<>(); + Release release1 = new Release(); + Version version = new Version(); + version.setName("version-name"); + version.setStatus(Constants.FINALIZED); + release1.setVersion(version); + Release release2 = new Release(); + release2.setVersion(version); + releaseList.add(release1); + releaseList.add(release2); + + // act + service.deletePreviousVersions(request, releaseList); + + // assert + mockedConnectionUtilsUtils.verify(() -> ConnectionUtils.openSimpleHTTPConnection(eq("DELETE"), + any(FirebaseHostingApiConfig.class), isNull(), eq("accessToken"), anyString(), isNull(), eq("deleteVersion"))); + } + } + + @Test + @SneakyThrows + void shouldFinalizeVersions() { + try (MockedStatic mockedConnectionUtilsUtils = mockStatic(ConnectionUtils.class)) { + // arrange + String randomName = UUID.randomUUID().toString(); + Version mockResponse = new Version(); + mockResponse.setName(randomName); + mockedConnectionUtilsUtils.when(() -> ConnectionUtils.openSimpleHTTPConnection(eq("PATCH"), + any(FirebaseHostingApiConfig.class), any(Class.class), + eq("accessToken"), eq("sites/test/versions/1.0?update_mask=status"), + eq("{ \"status\": \"FINALIZED\" }"), eq("finalizeVersion"))) + .thenReturn(mockResponse); + + // act + service.finalizeVersion("1.0"); + + // assert + mockedConnectionUtilsUtils.verify(() -> ConnectionUtils.openSimpleHTTPConnection(eq("PATCH"), + any(FirebaseHostingApiConfig.class), any(Class.class), + eq("accessToken"), eq("sites/test/versions/1.0?update_mask=status"), + eq("{ \"status\": \"FINALIZED\" }"), eq("finalizeVersion"))); + } + } +} diff --git a/code/src/test/java/io/github/szrnkapeter/firebase/hosting/util/ConfigValidationUtilsTest.java b/code/src/test/java/io/github/szrnkapeter/firebase/hosting/util/ConfigValidationUtilsTest.java new file mode 100644 index 0000000..f6c0d2d --- /dev/null +++ b/code/src/test/java/io/github/szrnkapeter/firebase/hosting/util/ConfigValidationUtilsTest.java @@ -0,0 +1,76 @@ +package io.github.szrnkapeter.firebase.hosting.util; + +import io.github.szrnkapeter.firebase.hosting.config.FirebaseHostingApiConfig; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; + +import static io.github.szrnkapeter.firebase.hosting.util.TestConfigGenerator.getFirebaseRestApiConfig; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * @author Peter Szrnka + * @since 0.8 + */ +class ConfigValidationUtilsTest { + + @Test + void testPrivateConstructor() { + assertDoesNotThrow(() -> TestUtils.testPrivateConstructor(ConfigValidationUtilsTest.class)); + } + + @Test + @SneakyThrows + void shouldThrowErrorWhenConfigIsMissing() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> ConfigValidationUtils.preValidateConfig(null)); + assertEquals("FirebaseRestApiConfig is mandatory!", exception.getMessage()); + } + + @Test + @SneakyThrows + void shouldThrowErrorWhenSiteIdIsMissing() { + // arrange + FirebaseHostingApiConfig config = getFirebaseRestApiConfig(); + config.setSiteId(null); + + // act & assert + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> ConfigValidationUtils.preValidateConfig(config)); + assertEquals("Site ID is mandatory!", exception.getMessage()); + } + + + @Test + @SneakyThrows + void shouldThrowErrorWhenServiceAccountFileStreamIsMissing() { + // arrange + FirebaseHostingApiConfig config = getFirebaseRestApiConfig(); + config.setServiceAccountFileStream(null); + + // act & assert + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> ConfigValidationUtils.preValidateConfig(config)); + assertEquals("Service account file stream is missing from the configuration!", exception.getMessage()); + } + + @Test + @SneakyThrows + void shouldThrowErrorWhenSerializerIsMissing() { + // arrange + FirebaseHostingApiConfig config = getFirebaseRestApiConfig(); + config.setSerializer(null); + + // act & assert + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> ConfigValidationUtils.preValidateConfig(config)); + assertEquals("Serializer is missing from the configuration!", exception.getMessage()); + } + + @Test + @SneakyThrows + void shouldNotThrowError() { + // arrange + FirebaseHostingApiConfig config = getFirebaseRestApiConfig(); + + // act & assert + assertDoesNotThrow(() -> ConfigValidationUtils.preValidateConfig(config)); + } +} \ No newline at end of file diff --git a/code/src/test/java/io/github/szrnkapeter/firebase/hosting/util/FileUtilsTest.java b/code/src/test/java/io/github/szrnkapeter/firebase/hosting/util/FileUtilsTest.java index e1d50b3..4f32ebe 100644 --- a/code/src/test/java/io/github/szrnkapeter/firebase/hosting/util/FileUtilsTest.java +++ b/code/src/test/java/io/github/szrnkapeter/firebase/hosting/util/FileUtilsTest.java @@ -5,10 +5,19 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import java.io.File; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import java.util.zip.GZIPInputStream; +import io.github.szrnkapeter.firebase.hosting.model.DeployItem; +import lombok.SneakyThrows; import org.junit.jupiter.api.Test; +/** + * @author Peter Szrnka + * @since 0.2 + */ class FileUtilsTest { @Test @@ -17,27 +26,43 @@ void testPrivateConstructor() { } @Test - void test01_GetSHA256Checksum() throws Exception { + @SneakyThrows + void shouldGenerateFileListAndHash() { + // arrange + Set fileList = new HashSet<>(); + fileList.add(new DeployItem("file1.txt", "test".getBytes())); + + // act + Map response = FileUtils.generateFileListAndHash(fileList); + + // assert + assertNotNull(response); + assertEquals(1, response.size()); + assertEquals("f7beb20179aee76b26b8b4b0840a89a70b1fbb72333892df6c54fe1010640cb3", response.get("/file1.txt")); + } + + @Test + void shouldGetSHA256Checksum() throws Exception { String response = FileUtils.getSHA256Checksum("test".getBytes()); assertNotNull(response); assertEquals("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", response); } @Test - void test02_CompressAndReadFile() throws Exception { + void shouldCompressAndReadFile() throws Exception { byte[] response = FileUtils.compressAndReadFile("test".getBytes()); assertEquals(24, response.length); assertEquals((byte) (GZIPInputStream.GZIP_MAGIC >> 8), response[1]); } @Test - void test03_GetRemoteFile_Fail() throws Exception { + void shouldGetRemoteFileFail() throws Exception { byte[] response = FileUtils.getRemoteFile( new File("src/test/resources/test0.txt").toURI().toURL().toString()); assertEquals(0, response.length); } @Test - void test04_GetRemoteFile_Success() throws Exception { + void shouldGetRemoteFileSucceed() throws Exception { byte[] response = FileUtils.getRemoteFile( new File("src/test/resources/test1.txt").toURI().toURL().toString()); assertEquals(7, response.length); } diff --git a/code/src/test/java/io/github/szrnkapeter/firebase/hosting/util/TestConfigGenerator.java b/code/src/test/java/io/github/szrnkapeter/firebase/hosting/util/TestConfigGenerator.java new file mode 100644 index 0000000..99057ea --- /dev/null +++ b/code/src/test/java/io/github/szrnkapeter/firebase/hosting/util/TestConfigGenerator.java @@ -0,0 +1,34 @@ +package io.github.szrnkapeter.firebase.hosting.util; + +import io.github.szrnkapeter.firebase.hosting.builder.FirebaseHostingApiConfigBuilder; +import io.github.szrnkapeter.firebase.hosting.config.FirebaseHostingApiConfig; +import io.github.szrnkapeter.firebase.hosting.listener.ServiceResponseListener; +import io.github.szrnkapeter.firebase.hosting.serializer.GsonSerializer; + +import java.io.FileInputStream; + +/** + * @author Peter Szrnka + * @since 0.8 + */ +public class TestConfigGenerator { + + private static final String SEPARATOR = " / "; + + public static FirebaseHostingApiConfig getFirebaseRestApiConfig() throws Exception { + return FirebaseHostingApiConfigBuilder.builder() + .withServiceAccountFileStream(new FileInputStream("src/test/resources/test.json")) + .withSiteId("test") + .withSerializer(new GsonSerializer()) + .withDefaultConnectionTimeout(60000).withDefaultReadTimeout(60000) + .withHttpResponseListener((function, code, responseMessage) -> System.out.println(function + SEPARATOR + code + SEPARATOR + responseMessage)) + .withServiceResponseListener(new ServiceResponseListener() { + + @Override + public void getResponse(String function, T response) { + System.out.println(function + SEPARATOR + response); + } + }) + .build(); + } +} diff --git a/code/src/test/java/io/github/szrnkapeter/firebase/hosting/util/VersionUtilsTest.java b/code/src/test/java/io/github/szrnkapeter/firebase/hosting/util/VersionUtilsTest.java new file mode 100644 index 0000000..c373350 --- /dev/null +++ b/code/src/test/java/io/github/szrnkapeter/firebase/hosting/util/VersionUtilsTest.java @@ -0,0 +1,84 @@ +package io.github.szrnkapeter.firebase.hosting.util; + +import io.github.szrnkapeter.firebase.hosting.config.FirebaseHostingApiConfig; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static io.github.szrnkapeter.firebase.hosting.util.TestConfigGenerator.getFirebaseRestApiConfig; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * @author Peter Szrnka + * @since 0.8 + */ +class VersionUtilsTest { + + public static final String VERSION_RESULT = "sites/test/versions/1.0"; + + @Test + void testPrivateConstructor() { + assertDoesNotThrow(() -> TestUtils.testPrivateConstructor(VersionUtils.class)); + } + + @ParameterizedTest + @MethodSource("versionIdTestData") + void shouldGetVersionId(String input, String expectedResponse) { + + // act + String response = VersionUtils.getVersionId(input); + + // assert + assertEquals(expectedResponse, response); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("versionNameFailedTestData") + void shouldFailToGetVersionName(String input) { + // arrange + FirebaseHostingApiConfig config = getFirebaseRestApiConfig(); + + // act & assert + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> VersionUtils.getVersionName(config, input)); + assertEquals("Version field is mandatory!", exception.getMessage()); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("versionNameTestData") + void shouldGetVersionName(String input, String expectedResponse) { + // arrange + FirebaseHostingApiConfig config = getFirebaseRestApiConfig(); + + // act + String response = VersionUtils.getVersionName(config, input); + + // assert + assertEquals(expectedResponse, response); + } + + private static Object[][] versionIdTestData() { + return new Object[][] { + { null, null }, + { "", null }, + { "/version", "version" }, + { "version", "version" } + }; + } + + private static Object[] versionNameFailedTestData() { + return new Object[] { null, "" }; + } + + private static Object[][] versionNameTestData() { + return new Object[][] { + { "1.0", VERSION_RESULT}, + {VERSION_RESULT, VERSION_RESULT} + }; + } +}