diff --git a/build.gradle b/build.gradle index 5e20d9ad1..7075a5075 100644 --- a/build.gradle +++ b/build.gradle @@ -69,9 +69,13 @@ ext { jedisVersion= '3.3.0' zxingVersion = "3.4.0" huaweiObsVersion = "3.19.7" + githubApiVersion = "1.84" + powermockVersion = "1.6.6" + mockitoVersion = "1.10.19" } dependencies { + implementation "org.kohsuke:github-api:$githubApiVersion" implementation "org.springframework.boot:spring-boot-starter-actuator" implementation "org.springframework.boot:spring-boot-starter-data-jpa" implementation "org.springframework.boot:spring-boot-starter-web" @@ -131,5 +135,13 @@ dependencies { testImplementation "org.springframework.boot:spring-boot-starter-test" + testCompile group: 'org.mockito', name: 'mockito-all', version: "$mockitoVersion" + testCompile group: 'org.powermock', name: 'powermock-api-mockito', version: "$powermockVersion" + testCompile group: 'org.powermock', name: 'powermock-module-junit4', version: "$powermockVersion" + developmentOnly "org.springframework.boot:spring-boot-devtools" } + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/src/main/java/run/halo/app/controller/admin/api/ThemeController.java b/src/main/java/run/halo/app/controller/admin/api/ThemeController.java index 92156fad1..24dc11241 100644 --- a/src/main/java/run/halo/app/controller/admin/api/ThemeController.java +++ b/src/main/java/run/halo/app/controller/admin/api/ThemeController.java @@ -175,10 +175,39 @@ public class ThemeController { return themeService.fetch(uri); } + @PostMapping("fetchingBranches") + @ApiOperation("Fetches all branches") + public List fetchBranches(@RequestParam("uri") String uri) { + return themeService.fetchBranches(uri); + } + + @PostMapping("fetchingReleases") + @ApiOperation("Fetches all releases") + public List fetchReleases(@RequestParam("uri") String uri) { + return themeService.fetchReleases(uri); + } + + @GetMapping("fetchingRelease") + @ApiOperation("Fetches a specific release") + public ThemeProperty fetchRelease(@RequestParam("uri") String uri, @RequestParam("tag") String tagName) { + return themeService.fetchRelease(uri, tagName); + } + + @GetMapping("fetchBranch") + @ApiOperation("Fetch specific branch") + public ThemeProperty fetchBranch(@RequestParam("uri") String uri, @RequestParam("branch") String branchName) { + return themeService.fetchBranch(uri, branchName); + } + + @GetMapping("fetchLatestRelease") + @ApiOperation("Fetch latest release") + public ThemeProperty fetchLatestRelease(@RequestParam("uri") String uri) { + return themeService.fetchLatestRelease(uri); + } + @PutMapping("fetching/{themeId}") @ApiOperation("Upgrades theme by remote") public ThemeProperty updateThemeByFetching(@PathVariable("themeId") String themeId) { - return themeService.update(themeId); } diff --git a/src/main/java/run/halo/app/service/ThemeService.java b/src/main/java/run/halo/app/service/ThemeService.java index 158a48f2e..2d1822d29 100644 --- a/src/main/java/run/halo/app/service/ThemeService.java +++ b/src/main/java/run/halo/app/service/ThemeService.java @@ -88,13 +88,23 @@ public interface ThemeService { /** * Theme provider remote name. */ - String THEME_PROVIDER_REMOTE_NAME = "theme-provider"; + String THEME_PROVIDER_REMOTE_NAME = "origin"; /** * Default remote branch name. */ String DEFAULT_REMOTE_BRANCH = "master"; + /** + * Key to access the zip file url which is in the http response + */ + String ZIP_FILE_KEY = "zipball_url"; + + /** + * Key to access the tag name which is in the http response + */ + String TAG_KEY = "tag_name"; + /** * Get theme property by theme id. * @@ -303,6 +313,53 @@ public interface ThemeService { @NonNull ThemeProperty fetch(@NonNull String uri); + /** + * Fetches the latest release + * + * @param uri theme remote uri must not be null + * @return theme property + */ + @NonNull + ThemeProperty fetchLatestRelease(@NonNull String uri); + + /** + * Fetches all the branches info + * + * @param uri theme remote uri must not be null + * @return list of theme properties + */ + @NonNull + List fetchBranches(@NonNull String uri); + + /** + * Fetches all the release info + * + * @param uri theme remote uri must not be null + * @return list of theme properties + */ + @NonNull + List fetchReleases(@NonNull String uri); + + /** + * Fetches a specific release + * + * @param uri theme remote uri must not be null + * @param tagName release tag name must not be null + * @return theme property + */ + @NonNull + ThemeProperty fetchRelease(@NonNull String uri, @NonNull String tagName); + + /** + * Fetches a specific branch (clone) + * + * @param uri theme remote uri must not be null + * @param branchName wanted branch must not be null + * @return theme property + */ + @NonNull + ThemeProperty fetchBranch(@NonNull String uri, @NonNull String branchName); + /** * Reloads themes */ diff --git a/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java b/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java index 5b21bfa2b..ea7302291 100644 --- a/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/ThemeServiceImpl.java @@ -493,9 +493,9 @@ public class ThemeServiceImpl implements ThemeService { if (StringUtils.endsWithIgnoreCase(uri, ".zip")) { downloadZipAndUnzip(uri, themeTmpPath); } else { - uri = StringUtils.appendIfMissingIgnoreCase(uri, ".git", ".git"); + String repoUrl = StringUtils.appendIfMissingIgnoreCase(uri, ".git", ".git"); // Clone from git - GitUtils.cloneFromGit(uri, themeTmpPath); + GitUtils.cloneFromGit(repoUrl, themeTmpPath); } return add(themeTmpPath); @@ -506,6 +506,125 @@ public class ThemeServiceImpl implements ThemeService { } } + @Override + public ThemeProperty fetchBranch(String uri, String branchName) { + Assert.hasText(uri, "Theme remote uri must not be blank"); + + Path tmpPath = null; + + try { + // Create temp path + tmpPath = FileUtils.createTempDirectory(); + // Create temp path + Path themeTmpPath = tmpPath.resolve(HaloUtils.randomUUIDWithoutDash()); + + String repoUrl = StringUtils.appendIfMissingIgnoreCase(uri, ".git", ".git"); + GitUtils.cloneFromGit(repoUrl, themeTmpPath, branchName); + + return add(themeTmpPath); + } catch (IOException | GitAPIException e) { + throw new ServiceException("主题拉取失败 " + uri, e); + } finally { + FileUtils.deleteFolderQuietly(tmpPath); + } + } + + @Override + public ThemeProperty fetchRelease(@NonNull String uri, @NonNull String tagName) { + Assert.hasText(uri, "Theme remote uri must not be blank"); + Assert.hasText(tagName, "Theme remote tagName must not be blank"); + + Path tmpPath = null; + try { + tmpPath = FileUtils.createTempDirectory(); + + Path themeTmpPath = tmpPath.resolve(HaloUtils.randomUUIDWithoutDash()); + + Map releaseInfo = GithubUtils.getRelease(uri, tagName); + + if (releaseInfo == null) { + throw new ServiceException("主题拉取失败" + uri); + } + + String zipUrl = (String) releaseInfo.get(ZIP_FILE_KEY); + + downloadZipAndUnzip(zipUrl, themeTmpPath); + + return add(themeTmpPath); + } catch (IOException e) { + throw new ServiceException("主题拉取失败 " + uri, e); + } finally { + FileUtils.deleteFolderQuietly(tmpPath); + } + } + + @Override + public ThemeProperty fetchLatestRelease(@NonNull String uri) { + Assert.hasText(uri, "Theme remote uri must not be blank"); + + Path tmpPath = null; + try { + tmpPath = FileUtils.createTempDirectory(); + + Path themeTmpPath = tmpPath.resolve(HaloUtils.randomUUIDWithoutDash()); + + Map releaseInfo = GithubUtils.getLatestRelease(uri); + + if (releaseInfo == null) { + throw new ServiceException("主题拉取失败" + uri); + } + + String zipUrl = (String) releaseInfo.get(ZIP_FILE_KEY); + + downloadZipAndUnzip(zipUrl, themeTmpPath); + + return add(themeTmpPath); + } catch (IOException e) { + throw new ServiceException("主题拉取失败 " + uri, e); + } finally { + FileUtils.deleteFolderQuietly(tmpPath); + } + } + + @Override + public List fetchBranches(String uri) { + Assert.hasText(uri, "Theme remote uri must not be blank"); + + String repoUrl = StringUtils.appendIfMissingIgnoreCase(uri, ".git",".git"); + List branches = GitUtils.getAllBranches(repoUrl); + + List themeProperties = new ArrayList<>(); + + branches.forEach(branch -> { + ThemeProperty themeProperty = new ThemeProperty(); + themeProperty.setBranch(branch); + themeProperties.add(themeProperty); + }); + + return themeProperties; + } + + @Override + public List fetchReleases(@NonNull String uri) { + Assert.hasText(uri, "Theme remote uri must not be blank"); + + List releases = GithubUtils.getReleases(uri); + + List themeProperties = new ArrayList<>(); + + if (releases == null) { + throw new ServiceException("主题拉取失败"); + } + + releases.forEach(tagName -> { + ThemeProperty themeProperty = new ThemeProperty(); + themeProperty.setBranch(tagName); + themeProperties.add(themeProperty); + }); + + return themeProperties; + } + @Override public void reload() { eventPublisher.publishEvent(new ThemeUpdatedEvent(this)); @@ -519,6 +638,7 @@ public class ThemeServiceImpl implements ThemeService { try { pullFromGit(updatingTheme); + } catch (Exception e) { if (e instanceof ThemeNotSupportException) { throw (ThemeNotSupportException) e; @@ -597,7 +717,7 @@ public class ThemeServiceImpl implements ThemeService { // Get branch String branch = StringUtils.isBlank(themeProperty.getBranch()) ? - DEFAULT_REMOTE_BRANCH : themeProperty.getBranch(); + DEFAULT_REMOTE_BRANCH : themeProperty.getBranch(); Git git = null; @@ -606,6 +726,13 @@ public class ThemeServiceImpl implements ThemeService { Repository repository = git.getRepository(); + // Add all changes + git.add() + .addFilepattern(".") + .call(); + // Commit the changes + git.commit().setMessage("Commit by halo automatically").call(); + RevWalk revWalk = new RevWalk(repository); Ref ref = repository.findRef(Constants.HEAD); @@ -617,38 +744,31 @@ public class ThemeServiceImpl implements ThemeService { // Force to set remote name git.remoteRemove().setRemoteName(THEME_PROVIDER_REMOTE_NAME).call(); RemoteConfig remoteConfig = git.remoteAdd() - .setName(THEME_PROVIDER_REMOTE_NAME) - .setUri(new URIish(themeProperty.getRepo())) - .call(); - - // Add all changes - git.add() - .addFilepattern(".") - .call(); - // Commit the changes - git.commit().setMessage("Commit by halo automatically").call(); + .setName(THEME_PROVIDER_REMOTE_NAME) + .setUri(new URIish(themeProperty.getRepo())) + .call(); // Check out to specified branch if (!StringUtils.equalsIgnoreCase(branch, git.getRepository().getBranch())) { boolean present = git.branchList() - .call() - .stream() - .map(Ref::getName) - .anyMatch(name -> StringUtils.equalsIgnoreCase(name, branch)); + .call() + .stream() + .map(Ref::getName) + .anyMatch(name -> StringUtils.equalsIgnoreCase(name, branch)); git.checkout() - .setCreateBranch(true) - .setForced(!present) - .setName(branch) - .call(); + .setCreateBranch(true) + .setForced(!present) + .setName(branch) + .call(); } // Pull with rebasing PullResult pullResult = git.pull() - .setRemote(remoteConfig.getName()) - .setRemoteBranchName(branch) - .setRebase(true) - .call(); + .setRemote(remoteConfig.getName()) + .setRemoteBranchName(branch) + .setRebase(true) + .call(); if (!pullResult.isSuccessful()) { log.debug("Rebase result: [{}]", pullResult.getRebaseResult()); @@ -657,6 +777,9 @@ public class ThemeServiceImpl implements ThemeService { throw new ThemeUpdateException("拉取失败!您与主题作者可能同时更改了同一个文件"); } + String latestTagName = (String) GithubUtils.getLatestRelease(themeProperty.getRepo()).get(TAG_KEY); + git.checkout().setName(latestTagName).call(); + // updated successfully. ThemeProperty updatedThemeProperty = getProperty(Paths.get(themeProperty.getThemePath())); @@ -664,9 +787,9 @@ public class ThemeServiceImpl implements ThemeService { if (StringUtils.isNotEmpty(updatedThemeProperty.getRequire()) && !VersionUtil.compareVersion(HaloConst.HALO_VERSION, updatedThemeProperty.getRequire())) { // reset theme version git.reset() - .setMode(ResetCommand.ResetType.HARD) - .setRef(lastCommit.getName()) - .call(); + .setMode(ResetCommand.ResetType.HARD) + .setRef(lastCommit.getName()) + .call(); throw new ThemeNotSupportException("新版本主题仅支持 Halo " + updatedThemeProperty.getRequire() + " 以上的版本"); } } finally { @@ -696,7 +819,6 @@ public class ThemeServiceImpl implements ThemeService { } log.debug("Downloaded [{}]", zipUrl); - // Unzip it FileUtils.unzip(downloadResponse.getBody(), targetPath); } diff --git a/src/main/java/run/halo/app/utils/FileUtils.java b/src/main/java/run/halo/app/utils/FileUtils.java index f4937431a..0669c387a 100644 --- a/src/main/java/run/halo/app/utils/FileUtils.java +++ b/src/main/java/run/halo/app/utils/FileUtils.java @@ -10,6 +10,7 @@ import run.halo.app.exception.ForbiddenException; import java.io.*; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -131,6 +132,19 @@ public class FileUtils { zipEntry = zis.getNextEntry(); } + File targetDir = targetPath.toFile(); + List files = Arrays.asList(targetDir.listFiles()); + // if zip file has root file + if (files.size() == 1 && files.get(0).isDirectory()) { + String rootPath = files.get(0).toPath().toString(); + String rootFile = rootPath.substring(rootPath.lastIndexOf("/", rootPath.length() - 1) + 1,rootPath.length()); + List propertyFiles = Arrays.asList(files.get(0).listFiles()); + for (File propertyFile : propertyFiles) { + String filePath = propertyFile.toPath().toString(); + String destPath = filePath.replace(rootFile, ""); + Files.copy(propertyFile.toPath(), Paths.get(destPath)); + } + } } /** diff --git a/src/main/java/run/halo/app/utils/GitUtils.java b/src/main/java/run/halo/app/utils/GitUtils.java index 5ae874738..7db0b92b4 100644 --- a/src/main/java/run/halo/app/utils/GitUtils.java +++ b/src/main/java/run/halo/app/utils/GitUtils.java @@ -3,13 +3,17 @@ package run.halo.app.utils; import lombok.extern.slf4j.Slf4j; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.InvalidRemoteException; +import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.storage.file.WindowCacheConfig; import org.springframework.lang.NonNull; import org.springframework.util.Assert; import java.io.IOException; import java.nio.file.Path; +import java.util.*; /** * Git utilities. @@ -59,6 +63,43 @@ public class GitUtils { return git; } + public static void cloneFromGit(@NonNull String repoUrl, @NonNull Path targetPath, @NonNull String branchName) throws GitAPIException { + Assert.hasText(repoUrl, "Repository remote url must not be blank"); + Assert.notNull(targetPath, "Target path must not be null"); + + Git git = null; + try { + git = Git.cloneRepository() + .setURI(repoUrl) + .setDirectory(targetPath.toFile()) + .setBranchesToClone(Arrays.asList("refs/heads/" + branchName)) + .setBranch("refs/heads/" + branchName) + .call(); + } finally { + closeQuietly(git); + } + } + + public static List getAllBranches(@NonNull String repoUrl) { + List branches = new ArrayList(); + try { + Collection refs = Git.lsRemoteRepository() + .setHeads(true) + .setRemote(repoUrl) + .call(); + for (Ref ref : refs) { + branches.add(ref.getName().substring(ref.getName().lastIndexOf("/") + 1, ref.getName().length())); + } + } catch (InvalidRemoteException e) { + log.warn("Git url is not valid", e); + } catch (TransportException e) { + log.warn("Transport exception", e); + } catch (GitAPIException e) { + log.warn("Git api exception", e); + } + return branches; + } + public static void closeQuietly(Git git) { if (git != null) { git.getRepository().close(); diff --git a/src/main/java/run/halo/app/utils/GithubUtils.java b/src/main/java/run/halo/app/utils/GithubUtils.java new file mode 100644 index 000000000..9965f67ca --- /dev/null +++ b/src/main/java/run/halo/app/utils/GithubUtils.java @@ -0,0 +1,368 @@ +package run.halo.app.utils; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.kohsuke.github.*; +import run.halo.app.service.ThemeService; + +import java.io.FileNotFoundException; +import java.util.*; + +/** + * GithubUtils send request to api.github.com + * + * @author bigbang019 + * @date 2020-05-31 + */ +@Slf4j +public class GithubUtils { + + /** + * The prefix need to remove + */ + static final String PREFIX = "https://github.com/"; + + /** + * Get latest release + * @param uri repository url must not be null + * @return the map object containning tagname and zipfile url + */ + public static Map getLatestRelease(String uri) { + String repoUrl = StringUtils.removeStartIgnoreCase(uri, PREFIX); + + try { + GithubLatestRelease githubLatestRelease = new GithubLatestRelease(repoUrl); + + Thread thread = new Thread(githubLatestRelease); + + thread.start(); + + thread.join(10 * 1000); + + return githubLatestRelease.result; + } catch (InterruptedException e) { + log.warn("Interrupted", e); + } + + return null; + } + + /** + * Get release information + * @param uri repository url must not be null + * @return list of tagname of releases + */ + public static List getReleases(String uri) { + String repoUrl = StringUtils.removeStartIgnoreCase(uri, PREFIX); + + try { + GithubReleases githubReleases = new GithubReleases(repoUrl); + + Thread thread = new Thread(githubReleases); + + thread.start(); + + thread.join(10 * 1000); + + return githubReleases.result; + } catch (InterruptedException e) { + log.warn("Interrupted", e); + } + + return null; + } + + /** + * Get release information + * @param uri repository url must not be null + * @param tagName tag must not be null + * @return the map object containning tagname and zipfile url + */ + public static Map getRelease(String uri, String tagName) { + String repoUrl = StringUtils.removeStartIgnoreCase(uri, PREFIX); + + try { + GithubRelease githubRelease = new GithubRelease(repoUrl, tagName); + + Thread thread = new Thread(githubRelease); + + thread.start(); + + thread.join(10 * 1000); + + return githubRelease.result; + } catch (InterruptedException e) { + log.warn("Interrupted", e); + } + return null; + } + + /** + * Get the content of theme.yaml/theme.yml + * @param uri repository url must not be null + * @param branch branch must not be null + * @return content of the file + */ + public static String accessThemeProperty(String uri, String branch) { + String repoUrl = StringUtils.removeStartIgnoreCase(uri, PREFIX); + + try { + GithubFile githubFile = new GithubFile(repoUrl, branch); + + Thread thread = new Thread(githubFile); + + thread.start(); + + thread.join(10 * 1000); + + return githubFile.result; + } catch (InterruptedException e) { + log.warn("Interrupted", e); + } + + return null; + } + + private static class GithubRelease implements Runnable { + + /** + * The return result is zip url and tag name etc. + */ + private HashMap result; + + /** + * should be in format of "username/reponame" + */ + private String repoUrl; + + private String tagName; + + public GithubRelease(String repoUrl, String tagName) { + this.repoUrl = repoUrl; + this.tagName = tagName; + result = null; + } + + @Override + public void run() { + while (true) { + try { + GitHub gitHub = GitHub.connectAnonymously(); + GHRepository ghRepository = gitHub.getRepository(repoUrl); + List ghReleaseList = ghRepository.getReleases(); + + if (ghReleaseList.size() == 0) { + break; + } + + Optional res = ghReleaseList.stream() + .filter(release -> StringUtils.equalsIgnoreCase(release.getTagName(), tagName)) + .findFirst(); + + if (res.isPresent()) { + GHRelease ghRelease = res.get(); + + result = new HashMap() { + { + put(ThemeService.ZIP_FILE_KEY, ghRelease.getZipballUrl()); + put(ThemeService.TAG_KEY, ghRelease.getTagName()); + } + }; + } + + break; + + } catch (Exception e) { + if (e instanceof HttpException) { + int code = ((HttpException) e).getResponseCode(); + if (code != -1) { + break; + } + } else { + break; + } + } + + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + break; + } + } + } + } + + private static class GithubReleases implements Runnable { + + private List result; + + private String repoUrl; + + public GithubReleases(String repoUrl) { + this.repoUrl = repoUrl; + result = null; + } + + @Override + public void run() { + while (true) { + try { + GitHub gitHub = GitHub.connectAnonymously(); + GHRepository ghRepository = gitHub.getRepository(repoUrl); + List ghReleaseList = ghRepository.getReleases(); + + result = new ArrayList(); + + for (GHRelease ghRelease : ghReleaseList) { + result.add(ghRelease.getTagName()); + } + + break; + + } catch (Exception e) { + if (e instanceof HttpException) { + int code = ((HttpException) e).getResponseCode(); + if (code != -1) { + break; + } + } else { + break; + } + } + + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + break; + } + } + } + } + + private static class GithubLatestRelease implements Runnable { + + /** + * The return result is zip url and tag name etc. + */ + private HashMap result; + + /** + * should be in format of "username/reponame" + */ + private String repoUrl; + + public GithubLatestRelease(String repoUrl) { + this.repoUrl = repoUrl; + result = null; + } + + @Override + public void run() { + while (true) { + try { + GitHub gitHub = GitHub.connectAnonymously(); + GHRepository ghRepository = gitHub.getRepository(repoUrl); + List ghReleaseList = ghRepository.getReleases(); + + if (ghReleaseList.size() == 0) { + break; + } + GHRelease ghRelease = ghReleaseList.get(0); + + result = new HashMap() { + { + put(ThemeService.ZIP_FILE_KEY, ghRelease.getZipballUrl()); + put(ThemeService.TAG_KEY, ghRelease.getTagName()); + } + }; + + break; + + } catch (Exception e) { + if (e instanceof HttpException) { + int code = ((HttpException) e).getResponseCode(); + if (code != -1) { + break; + } + } else { + break; + } + } + + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + break; + } + } + } + } + + private static class GithubFile implements Runnable { + + /** + * result is file content + */ + private String result; + + /** + * should be in format of "username/reponame" + */ + private String repoUrl; + + /** + * the branch name + */ + private String branch; + + public GithubFile(String repoUrl, String branch) { + this.repoUrl = repoUrl; + this.branch = branch; + result = null; + } + + @Override + public void run() { + while (true) { + try { + GitHub gitHub = GitHub.connectAnonymously(); + + GHRepository ghRepository = gitHub.getRepository(repoUrl); + + GHContent ghContent = null; + + for (String themePropertyFile : ThemeService.THEME_PROPERTY_FILE_NAMES) { + + try { + ghContent = ghRepository.getFileContent(themePropertyFile, branch); + } catch (FileNotFoundException e) { + } + } + + if (ghContent == null) { + break; + } + + result = ghContent.getContent(); + + break; + } catch (Exception e) { + if (e instanceof HttpException) { + int code = ((HttpException) e).getResponseCode(); + if (code != -1) { + break; + } + + } else { + break; + } + } + + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + } + } + } + } +} diff --git a/src/test/java/run/halo/app/service/impl/OptionServiceImplTest.java b/src/test/java/run/halo/app/service/impl/OptionServiceImplTest.java index 05a13a121..82ff4b420 100644 --- a/src/test/java/run/halo/app/service/impl/OptionServiceImplTest.java +++ b/src/test/java/run/halo/app/service/impl/OptionServiceImplTest.java @@ -5,7 +5,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.runners.MockitoJUnitRunner; import run.halo.app.cache.AbstractStringCacheStore; import run.halo.app.model.entity.Option; import run.halo.app.model.properties.QiniuOssProperties; diff --git a/src/test/java/run/halo/app/service/impl/ThemeServiceImplTest.java b/src/test/java/run/halo/app/service/impl/ThemeServiceImplTest.java new file mode 100644 index 000000000..632378854 --- /dev/null +++ b/src/test/java/run/halo/app/service/impl/ThemeServiceImplTest.java @@ -0,0 +1,130 @@ +package run.halo.app.service.impl; + + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.*; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.web.client.RestTemplate; +import run.halo.app.cache.AbstractStringCacheStore; +import run.halo.app.config.properties.HaloProperties; +import run.halo.app.handler.theme.config.ThemeConfigResolver; +import run.halo.app.handler.theme.config.support.ThemeProperty; +import run.halo.app.service.OptionService; +import run.halo.app.utils.FileUtils; +import run.halo.app.utils.GitUtils; +import run.halo.app.utils.GithubUtils; + +import java.io.File; +import java.nio.file.Path; +import java.util.*; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({GitUtils.class, GithubUtils.class, FileUtils.class, ThemeServiceImpl.class}) +public class ThemeServiceImplTest { + + @Mock + private HaloProperties haloProperties; + + @Mock + private OptionService optionService; + + @Mock + private AbstractStringCacheStore cacheStore; + + @Mock + private ThemeConfigResolver themeConfigResolver; + + @Mock + private RestTemplate restTemplate; + + @Mock + private ApplicationEventPublisher eventPublisher; + + @InjectMocks + public ThemeServiceImpl themeService; + + @Before + public void setUp() throws Exception { + //Static Method + PowerMockito.mockStatic(GithubUtils.class); + PowerMockito.mockStatic(GitUtils.class); + PowerMockito.mockStatic(FileUtils.class); + + PowerMockito.doReturn(Arrays.asList("master", "dev")).when(GitUtils.class, "getAllBranches", Mockito.any(String.class)); + PowerMockito.doNothing().when(GitUtils.class, "cloneFromGit", Mockito.any(String.class), Mockito.any(Path.class)); + + PowerMockito.doReturn("propertyContent").when(GithubUtils.class, "accessThemeProperty", Mockito.any(String.class), Mockito.any(String.class)); + + PowerMockito.doReturn(new File("tmpPath").toPath()).when(FileUtils.class, "createTempDirectory"); + PowerMockito.doNothing().when(FileUtils.class, "deleteFolderQuietly", Mockito.any(Path.class)); + + //Method + themeService = PowerMockito.spy(new ThemeServiceImpl(haloProperties, optionService, cacheStore, themeConfigResolver, restTemplate, eventPublisher)); + + Mockito.doNothing().when(eventPublisher).publishEvent(Mockito.any(String.class)); + } + + @Test + public void fetchGitTest() throws Exception { + String uri = "https://github.com/halo-dev/halo-theme-pinghsu"; + PowerMockito.doNothing().when(themeService, "downloadZipAndUnzip", Mockito.any(String.class), Mockito.any(Path.class)); + PowerMockito.doReturn(new ThemeProperty()).when(themeService, "add", Mockito.any(Path.class)); + ThemeProperty themeProperty = themeService.fetch(uri); + Assert.assertNotNull(themeProperty); + } + + @Test + public void fetchZipTest() throws Exception { + String uri = "https://github.com/halo-dev/halo-theme-pinghsu/archive/master.zip"; + PowerMockito.doNothing().when(themeService, "downloadZipAndUnzip", Mockito.any(String.class), Mockito.any(Path.class)); + PowerMockito.doReturn(new ThemeProperty()).when(themeService, "add", Mockito.any(Path.class)); + ThemeProperty themeProperty = themeService.fetch(uri); + Assert.assertNotNull(themeProperty); + } + + @Test + public void fetchBranchesTest() { + String uri = "https://github.com/halo-dev/halo-theme-hux"; + + List themeProperties = themeService.fetchBranches(uri); + + Assert.assertNotNull(themeProperties); + Assert.assertEquals(themeProperties.size(), 2); + } + + + @Test + public void fetchBranchTest() throws Exception { + String uri = "https://github.com/halo-dev/halo-theme-casper"; + String branch = "master"; + PowerMockito.doNothing().when(themeService, "downloadZipAndUnzip", Mockito.any(String.class), Mockito.any(Path.class)); + PowerMockito.doReturn(new ThemeProperty()).when(themeService, "add", Mockito.any(Path.class)); + ThemeProperty themeProperty = themeService.fetchBranch(uri, branch); + Assert.assertNotNull(themeProperty); + } + + @Test + public void fetchLatestReleaseTest() throws Exception { + String uri = "https://github.com/halo-dev/halo-theme-casper"; + PowerMockito.doNothing().when(themeService, "downloadZipAndUnzip", Mockito.any(String.class), Mockito.any(Path.class)); + PowerMockito.doReturn(new ThemeProperty()).when(themeService, "add", Mockito.any(Path.class)); + ThemeProperty themeProperty = themeService.fetchLatestRelease(uri); + Assert.assertNotNull(themeProperty); + } + + @Test + public void updateTest() throws Exception { + PowerMockito.doNothing().when(themeService, "downloadZipAndUnzip", Mockito.any(String.class), Mockito.any(Path.class)); + PowerMockito.doReturn(new ThemeProperty()).when(themeService, "add", Mockito.any(Path.class)); + PowerMockito.doNothing().when(themeService, "pullFromGit", Mockito.any(ThemeProperty.class)); + PowerMockito.doReturn(new ThemeProperty()).when(themeService, "getThemeOfNonNullBy", Mockito.any(String.class)); + ThemeProperty themeProperty = themeService.update("String"); + Assert.assertNotNull(themeProperty); + } +} diff --git a/src/test/java/run/halo/app/utils/GitTest.java b/src/test/java/run/halo/app/utils/GitTest.java index 77d3a3690..e56bf7c87 100644 --- a/src/test/java/run/halo/app/utils/GitTest.java +++ b/src/test/java/run/halo/app/utils/GitTest.java @@ -7,10 +7,7 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.URIish; -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.*; import java.io.IOException; import java.net.URISyntaxException; @@ -80,6 +77,21 @@ public class GitTest { git.close(); } + @Test + @Ignore + public void getAllBranchesTest() { + List branches = GitUtils.getAllBranches("https://github.com/halo-dev/halo-theme-hux.git"); + Assert.assertNotNull(branches); + } + + @Test + @Ignore + public void getAllBranchesWithInvalidURL() { + List branches = GitUtils.getAllBranches("https://github.com/halo-dev/halo-theme.git"); + Assert.assertNotNull(branches); + Assert.assertEquals(branches.size(), 0); + } + private Git cloneRepository() throws GitAPIException { return Git.cloneRepository() .setURI("https://github.com/halo-dev/halo-theme-pinghsu.git") diff --git a/src/test/java/run/halo/app/utils/GithubUtilsTest.java b/src/test/java/run/halo/app/utils/GithubUtilsTest.java new file mode 100644 index 000000000..d3765c322 --- /dev/null +++ b/src/test/java/run/halo/app/utils/GithubUtilsTest.java @@ -0,0 +1,46 @@ +package run.halo.app.utils; + + +import lombok.extern.slf4j.Slf4j; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.List; +import java.util.Map; + +@Slf4j +public class GithubUtilsTest { + + @Test + @Ignore + public void getLatestReleasesWithValidURL() { + Map map = GithubUtils.getLatestRelease("https://github.com/halo-dev/halo-theme-hux"); + } + + @Test + @Ignore + public void getLatestReleasesWithInvalidURL() { + Map map = GithubUtils.getLatestRelease("https://github.com/halo-dev/halo-theme-hu"); + Assert.assertNull(map); + } + + @Test + @Ignore + public void accessThemePropertyWithValidURL() { + String content = GithubUtils.accessThemeProperty("https://github.com/halo-dev/halo-theme-hux", "master"); + } + + @Test + @Ignore + public void accessThemePropertyWithInvalidURL() { + String content = GithubUtils.accessThemeProperty("https://github.com/halo-dev/halo-theme-hu", "master"); + Assert.assertNull(content); + } + + @Test + @Ignore + public void getReleasesTest() { + List list = GithubUtils.getReleases("https://github.com/halo-dev/halo-theme-hux"); + } +}