├── .gitignore
├── src
├── test
│ ├── resources
│ │ ├── mockito-extensions
│ │ │ └── org.mockito.plugins.MockMaker
│ │ └── three
│ │ │ └── days
│ │ │ └── xiola.apk
│ └── java
│ │ └── io
│ │ └── jenkins
│ │ └── plugins
│ │ └── appcenter
│ │ ├── util
│ │ ├── TestFileUtil.java
│ │ ├── TestUtil.java
│ │ ├── MockWebServerUtil.java
│ │ └── RemoteFileUtilsTest.java
│ │ ├── validator
│ │ ├── PathPlaceholderValidatorTest.java
│ │ ├── AppNameValidatorTest.java
│ │ ├── ApiTokenValidatorTest.java
│ │ ├── BranchNameValidatorTest.java
│ │ ├── CommitHashValidatorTest.java
│ │ ├── DistributionGroupsValidatorTest.java
│ │ ├── PathToAppValidatorTest.java
│ │ ├── PathToDebugSymbolsValidatorTest.java
│ │ ├── PathToReleaseNotesValidatorTest.java
│ │ └── UsernameValidatorTest.java
│ │ ├── RoundTripTest.java
│ │ ├── FreestyleTest.java
│ │ ├── NodeTest.java
│ │ ├── task
│ │ └── internal
│ │ │ ├── UpdateReleaseTaskTest.java
│ │ │ ├── SetMetadataTaskTest.java
│ │ │ ├── CreateUploadResourceTaskTest.java
│ │ │ ├── UploadAppToResourceTaskTest.java
│ │ │ ├── FinishReleaseTaskTest.java
│ │ │ └── PollForReleaseTaskTest.java
│ │ ├── ConfigurationTest.java
│ │ ├── ProxyTest.java
│ │ └── EnvInterpolationTest.java
└── main
│ ├── resources
│ ├── index.jelly
│ └── io
│ │ └── jenkins
│ │ └── plugins
│ │ └── appcenter
│ │ ├── AppCenterRecorder
│ │ ├── help-releaseNotes.html
│ │ ├── help-branchName.html
│ │ ├── help-pathToApp.html
│ │ ├── help-pathToDebugSymbols.html
│ │ ├── help-commitHash.html
│ │ ├── help-buildVersion.html
│ │ ├── help-pathToReleaseNotes.html
│ │ ├── help-apiToken.html
│ │ ├── help-distributionGroups.html
│ │ ├── help-appName.html
│ │ ├── help-ownerName.html
│ │ └── config.jelly
│ │ └── Messages.properties
│ └── java
│ └── io
│ └── jenkins
│ └── plugins
│ └── appcenter
│ ├── AppCenterException.java
│ ├── validator
│ ├── ApiTokenValidator.java
│ ├── AppNameValidator.java
│ ├── BranchNameValidator.java
│ ├── CommitHashValidator.java
│ ├── Validator.java
│ ├── BuildVersionValidator.java
│ ├── PathToAppValidator.java
│ ├── PathToDebugSymbolsValidator.java
│ ├── PathToReleaseNotesValidator.java
│ ├── DistributionGroupsValidator.java
│ ├── UsernameValidator.java
│ └── PathPlaceholderValidator.java
│ ├── di
│ ├── AuthModule.java
│ ├── JenkinsModule.java
│ ├── AppCenterComponent.java
│ └── UploadModule.java
│ ├── util
│ ├── ParserFactory.java
│ ├── AndroidParser.java
│ └── RemoteFileUtils.java
│ ├── task
│ ├── internal
│ │ ├── AppCenterTask.java
│ │ ├── UpdateReleaseTask.java
│ │ ├── SetMetadataTask.java
│ │ ├── FinishReleaseTask.java
│ │ ├── PollForReleaseTask.java
│ │ ├── CreateUploadResourceTask.java
│ │ ├── DistributeResourceTask.java
│ │ └── UploadAppToResourceTask.java
│ └── UploadTask.java
│ ├── api
│ ├── UploadService.java
│ ├── AppCenterService.java
│ └── AppCenterServiceFactory.java
│ ├── model
│ └── appcenter
│ │ ├── Failure.java
│ │ ├── SetMetadataResponse.java
│ │ ├── SymbolUploadEndRequest.java
│ │ ├── ReleaseUploadEndRequest.java
│ │ ├── ReleaseDetailsUpdateResponse.java
│ │ ├── DestinationId.java
│ │ ├── UpdateReleaseUploadRequest.java
│ │ ├── UploadedSymbolInfo.java
│ │ ├── SymbolUploadUserInfo.java
│ │ ├── ReleaseUploadEndResponse.java
│ │ ├── ReleaseUploadBeginRequest.java
│ │ ├── ErrorDetails.java
│ │ ├── UpdateReleaseUploadResponse.java
│ │ ├── BuildInfo.java
│ │ ├── DestinationError.java
│ │ ├── SymbolUploadBeginResponse.java
│ │ ├── ReleaseUploadBeginResponse.java
│ │ ├── ReleaseUpdateRequest.java
│ │ ├── ReleaseUpdateError.java
│ │ ├── SymbolUploadBeginRequest.java
│ │ ├── PollForReleaseResponse.java
│ │ └── SymbolUpload.java
│ └── AppCenterLogger.java
├── Jenkinsfile
├── .editorconfig
├── .github
├── release-drafter.yml
└── workflows
│ └── release-drafter.yml
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | *.iml
3 | *.ipr
4 | *.iws
5 | .idea
6 | target
7 | work
8 |
--------------------------------------------------------------------------------
/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker:
--------------------------------------------------------------------------------
1 | mock-maker-inline
--------------------------------------------------------------------------------
/src/test/resources/three/days/xiola.apk:
--------------------------------------------------------------------------------
1 | at this moment, you should be with us...
2 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | // Build the plugin using https://github.com/jenkins-infra/pipeline-library
2 | buildPlugin(useContainerAgent: true)
3 |
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 | Upload new versions of your Android, iOS, MacOS, and (limited) Windows applications to AppCenter.
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | charset = utf-8
3 | end_of_line = lf
4 | insert_final_newline = false
5 | indent_style = space
6 | indent_size = 4
7 | max_line_length = 180
8 |
9 | [*.{json,yml}]
10 | indent_size = 2
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/appcenter/AppCenterRecorder/help-releaseNotes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Supports Markdown syntax. NOTE: Limited to 5000 characters or less.
4 |
5 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | # from https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.yml
2 | _extends: .github
3 | name-template: AppCenter Plugin $NEXT_PATCH_VERSION
4 | tag-template: appcenter-$NEXT_PATCH_VERSION
5 | version-template: $MAJOR.$MINOR.$PATCH
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/appcenter/AppCenterRecorder/help-branchName.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Name of the branch being built. Supports variable substitution.
4 |
5 |
6 |
7 | For example: origin/master or $GIT_BRANCH
8 |
9 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/appcenter/AppCenterRecorder/help-pathToApp.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Relative path to app. Supports variable substitution.
4 |
5 |
6 |
7 | For example: three/days/xiola.apk or three/days/${APP}.apk
8 |
9 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/appcenter/AppCenterRecorder/help-pathToDebugSymbols.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Relative path to debug symbols. Supports variable substitution.
4 |
5 |
6 |
7 | For example: three/days/casey.apk or three/days/${SYMBOLS}.apk
8 |
9 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/appcenter/AppCenterRecorder/help-commitHash.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Commit hash of the commit being build. Supports variable substitution.
4 |
5 |
6 |
7 | For example: 0e62d85530892a9178ff2b55ac30e8ede56c9c0e or $GIT_COMMIT
8 |
9 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/appcenter/AppCenterRecorder/help-buildVersion.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | NOTE: Build version might be mandatory on certain platform releases. Supports variable substitution.
4 |
5 |
6 |
7 | For example: 1.2.0 or ${version}
8 |
9 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/AppCenterException.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter;
2 |
3 | public final class AppCenterException extends Exception {
4 |
5 | public AppCenterException(String s) {
6 | super(s);
7 | }
8 |
9 | public AppCenterException(String s, Throwable throwable) {
10 | super(s, throwable);
11 | }
12 | }
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/appcenter/AppCenterRecorder/help-pathToReleaseNotes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Relative path to release notes. Supports variable substitution. Supports Markdown syntax. NOTE: Limited to 5000 characters or less.
4 |
5 |
6 |
7 | For example: three/days/linearnotes.md or three/days/${NOTES}.md
8 |
9 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/appcenter/AppCenterRecorder/help-apiToken.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/validator/ApiTokenValidator.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.function.Predicate;
5 |
6 | public final class ApiTokenValidator extends Validator {
7 |
8 | @Nonnull
9 | @Override
10 | protected Predicate predicate() {
11 | return value -> !value.contains(" ");
12 | }
13 |
14 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/validator/AppNameValidator.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.function.Predicate;
5 |
6 | public final class AppNameValidator extends Validator {
7 |
8 | @Nonnull
9 | @Override
10 | protected Predicate predicate() {
11 | return value -> !value.contains(" ");
12 | }
13 |
14 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/validator/BranchNameValidator.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.function.Predicate;
5 |
6 | public final class BranchNameValidator extends Validator {
7 |
8 | @Nonnull
9 | @Override
10 | protected Predicate predicate() {
11 | return value -> !value.contains(" ");
12 | }
13 |
14 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/validator/CommitHashValidator.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.function.Predicate;
5 |
6 | public final class CommitHashValidator extends Validator {
7 |
8 | @Nonnull
9 | @Override
10 | protected Predicate predicate() {
11 | return value -> !value.contains(" ");
12 | }
13 |
14 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/validator/Validator.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.function.Predicate;
5 |
6 | public abstract class Validator {
7 |
8 | @Nonnull
9 | protected abstract Predicate predicate();
10 |
11 | public boolean isValid(@Nonnull String value) {
12 | return predicate().test(value);
13 | }
14 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/validator/BuildVersionValidator.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import java.util.function.Predicate;
4 |
5 | import javax.annotation.Nonnull;
6 |
7 | public final class BuildVersionValidator extends Validator {
8 |
9 | @Nonnull
10 | @Override
11 | protected Predicate predicate() {
12 | return value -> !value.contains(" ");
13 | }
14 |
15 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/validator/PathToAppValidator.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import hudson.Util;
4 |
5 | import javax.annotation.Nonnull;
6 | import java.util.function.Predicate;
7 |
8 | public final class PathToAppValidator extends Validator {
9 |
10 | @Nonnull
11 | @Override
12 | protected Predicate predicate() {
13 | return Util::isRelativePath;
14 | }
15 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/validator/PathToDebugSymbolsValidator.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import hudson.Util;
4 |
5 | import javax.annotation.Nonnull;
6 | import java.util.function.Predicate;
7 |
8 | public final class PathToDebugSymbolsValidator extends Validator {
9 |
10 | @Nonnull
11 | @Override
12 | protected Predicate predicate() {
13 | return Util::isRelativePath;
14 | }
15 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/validator/PathToReleaseNotesValidator.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import hudson.Util;
4 |
5 | import javax.annotation.Nonnull;
6 | import java.util.function.Predicate;
7 |
8 | public final class PathToReleaseNotesValidator extends Validator {
9 |
10 | @Nonnull
11 | @Override
12 | protected Predicate predicate() {
13 | return Util::isRelativePath;
14 | }
15 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/validator/DistributionGroupsValidator.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.function.Predicate;
5 |
6 | public final class DistributionGroupsValidator extends Validator {
7 |
8 | @Nonnull
9 | @Override
10 | protected Predicate predicate() {
11 | return value -> !value.replace(",", "").trim().isEmpty();
12 | }
13 |
14 | }
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/appcenter/AppCenterRecorder/help-distributionGroups.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | NOTE: Distribution groups must have permission for this app to be distributed to.
4 |
5 |
6 |
7 | Check your distribution groups at https://appcenter.ms/apps.
8 |
9 |
10 | For example perry, dave, stephen, eric
11 |
12 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/di/AuthModule.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.di;
2 |
3 | import dagger.Module;
4 | import dagger.Provides;
5 | import hudson.util.Secret;
6 | import io.jenkins.plugins.appcenter.AppCenterRecorder;
7 |
8 | import javax.inject.Singleton;
9 |
10 | @Module
11 | final class AuthModule {
12 | @Provides
13 | @Singleton
14 | static Secret provideApiToken(AppCenterRecorder appCenterRecorder) {
15 | return appCenterRecorder.getApiToken();
16 | }
17 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/validator/UsernameValidator.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.function.Predicate;
5 | import java.util.regex.Pattern;
6 |
7 | public final class UsernameValidator extends Validator {
8 |
9 | @Nonnull
10 | @Override
11 | protected Predicate predicate() {
12 | return Pattern.compile("^(?![a-zA-Z0-9-_.]*+$)")
13 | .asPredicate()
14 | .negate();
15 | }
16 |
17 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/validator/PathPlaceholderValidator.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.function.Predicate;
5 | import java.util.regex.Pattern;
6 |
7 | public final class PathPlaceholderValidator extends Validator {
8 |
9 | @Nonnull
10 | @Override
11 | protected Predicate predicate() {
12 | return Pattern.compile("^\\$\\{[^}]+}")
13 | .asPredicate()
14 | .negate();
15 | }
16 |
17 | }
--------------------------------------------------------------------------------
/.github/workflows/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name: Release Drafter
2 |
3 | on:
4 | push:
5 | # branches to consider in the event; optional, defaults to all
6 | branches:
7 | - master
8 |
9 | jobs:
10 | update_release_draft:
11 | runs-on: ubuntu-latest
12 | if: github.repository == 'jenkinsci/appcenter-plugin'
13 | steps:
14 | # Drafts your next Release notes as Pull Requests are merged into "master"
15 | - uses: release-drafter/release-drafter@v5
16 | env:
17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/util/ParserFactory.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.util;
2 |
3 | import javax.annotation.Nonnull;
4 | import javax.inject.Inject;
5 | import java.io.File;
6 | import java.io.Serializable;
7 |
8 | public final class ParserFactory implements Serializable {
9 |
10 | private static final long serialVersionUID = 1L;
11 |
12 | @Inject
13 | ParserFactory() {
14 | }
15 |
16 | @Nonnull
17 | public AndroidParser androidParser(final @Nonnull File file) {
18 | return new AndroidParser(file);
19 | }
20 | }
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/appcenter/AppCenterRecorder/help-appName.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | The name of the app in AppCenter. NOTE: This may differ to the display name that you see in the web UI.
4 |
5 |
6 |
7 | Visit https://appcenter.ms/apps, select your app, inspect the URL.
8 |
9 |
10 | For example the URL in AppCenter might look like: https://appcenter.ms/users/xiola-3/apps/casey-1. Here, the appName is casey-1.
11 |
12 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/appcenter/AppCenterRecorder/help-ownerName.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | The name of the owner in AppCenter. NOTE: This may differ to the display name that you see in the web UI.
4 |
5 |
6 |
7 | Visit https://appcenter.ms/apps, select your app, inspect the URL.
8 |
9 |
10 | For example the URL in AppCenter might look like: https://appcenter.ms/users/xiola-3/apps/casey-1. Here, the ownerName is xiola-3.
11 |
12 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/task/internal/AppCenterTask.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.task.internal;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.io.Serializable;
5 | import java.util.concurrent.CompletableFuture;
6 |
7 | /**
8 | * Task that represents an internal Jenkins AppCenter plugin operation.
9 | *
10 | * @param Request type
11 | */
12 | public interface AppCenterTask extends Serializable {
13 | /**
14 | * Execute a task given a request and returns a result as a CompletableFuture.
15 | *
16 | * @param request T: Request
17 | * @return CompletableFuture: An expectation of a result of type T
18 | */
19 | @Nonnull
20 | CompletableFuture execute(@Nonnull T request);
21 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/util/TestFileUtil.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.util;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.io.File;
5 |
6 | public final class TestFileUtil {
7 |
8 | public static final String TEST_FILE_PATH = "src/test/resources/three/days/xiola.apk";
9 |
10 | @Nonnull
11 | public static File createFileForTesting() {
12 | return new File(TEST_FILE_PATH);
13 | }
14 |
15 | @Nonnull
16 | public static File createLargeFileForTesting() {
17 | return new File(TEST_FILE_PATH) {
18 | @Override
19 | public long length() {
20 | return (1024 * 1024) * 512; // Double the max size allowed to upload.
21 | }
22 | };
23 | }
24 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/api/UploadService.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.api;
2 |
3 | import okhttp3.MultipartBody;
4 | import okhttp3.RequestBody;
5 | import retrofit2.http.Body;
6 | import retrofit2.http.Headers;
7 | import retrofit2.http.Multipart;
8 | import retrofit2.http.POST;
9 | import retrofit2.http.PUT;
10 | import retrofit2.http.Part;
11 | import retrofit2.http.Url;
12 |
13 | import javax.annotation.Nonnull;
14 | import java.util.concurrent.CompletableFuture;
15 |
16 | public interface UploadService {
17 |
18 | @Multipart
19 | @POST
20 | CompletableFuture uploadApp(@Url @Nonnull String url, @Part @Nonnull MultipartBody.Part file);
21 |
22 | @Headers("x-ms-blob-type: BlockBlob")
23 | @PUT
24 | CompletableFuture uploadSymbols(@Url @Nonnull String url, @Body @Nonnull RequestBody file);
25 |
26 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/Failure.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.Objects;
5 |
6 | public final class Failure {
7 | @Nonnull
8 | public final String message;
9 |
10 | public Failure(@Nonnull String message) {
11 | this.message = message;
12 | }
13 |
14 | @Override
15 | public String toString() {
16 | return "Failure{" +
17 | "message='" + message + '\'' +
18 | '}';
19 | }
20 |
21 | @Override
22 | public boolean equals(Object o) {
23 | if (this == o) return true;
24 | if (o == null || getClass() != o.getClass()) return false;
25 | Failure failure = (Failure) o;
26 | return message.equals(failure.message);
27 | }
28 |
29 | @Override
30 | public int hashCode() {
31 | return Objects.hash(message);
32 | }
33 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/SetMetadataResponse.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.Objects;
5 |
6 | public final class SetMetadataResponse {
7 | @Nonnull
8 | public final Integer chunk_size;
9 |
10 | public SetMetadataResponse(@Nonnull Integer chunkSize) {
11 | this.chunk_size = chunkSize;
12 | }
13 |
14 | @Override
15 | public String toString() {
16 | return "SetMetadataResponse{" +
17 | "chunk_size=" + chunk_size +
18 | '}';
19 | }
20 |
21 | @Override
22 | public boolean equals(Object o) {
23 | if (this == o) return true;
24 | if (o == null || getClass() != o.getClass()) return false;
25 | SetMetadataResponse that = (SetMetadataResponse) o;
26 | return chunk_size.equals(that.chunk_size);
27 | }
28 |
29 | @Override
30 | public int hashCode() {
31 | return Objects.hash(chunk_size);
32 | }
33 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/SymbolUploadEndRequest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.Objects;
5 |
6 | public final class SymbolUploadEndRequest {
7 | @Nonnull
8 | public final StatusEnum status;
9 |
10 | public SymbolUploadEndRequest(@Nonnull StatusEnum status) {
11 | this.status = status;
12 | }
13 |
14 | @Override
15 | public String toString() {
16 | return "SymbolUploadEndRequest{" +
17 | "status=" + status +
18 | '}';
19 | }
20 |
21 | @Override
22 | public boolean equals(Object o) {
23 | if (this == o) return true;
24 | if (o == null || getClass() != o.getClass()) return false;
25 | SymbolUploadEndRequest that = (SymbolUploadEndRequest) o;
26 | return status == that.status;
27 | }
28 |
29 | @Override
30 | public int hashCode() {
31 | return Objects.hash(status);
32 | }
33 |
34 | public enum StatusEnum {
35 | committed,
36 | aborted
37 | }
38 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/ReleaseUploadEndRequest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.Objects;
5 |
6 | public final class ReleaseUploadEndRequest {
7 |
8 | @Nonnull
9 | public final StatusEnum status;
10 |
11 | public ReleaseUploadEndRequest(@Nonnull StatusEnum status) {
12 | this.status = status;
13 | }
14 |
15 | @Override
16 | public String toString() {
17 | return "ReleaseUploadEndRequest{" +
18 | "status=" + status +
19 | '}';
20 | }
21 |
22 | @Override
23 | public boolean equals(Object o) {
24 | if (this == o) return true;
25 | if (o == null || getClass() != o.getClass()) return false;
26 | ReleaseUploadEndRequest that = (ReleaseUploadEndRequest) o;
27 | return status == that.status;
28 | }
29 |
30 | @Override
31 | public int hashCode() {
32 | return Objects.hash(status);
33 | }
34 |
35 | public enum StatusEnum {
36 | committed,
37 | aborted
38 | }
39 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/ReleaseDetailsUpdateResponse.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nullable;
4 | import java.util.Objects;
5 |
6 | public final class ReleaseDetailsUpdateResponse {
7 | @Nullable
8 | public final String release_notes;
9 |
10 | public ReleaseDetailsUpdateResponse(@Nullable String releaseNotes) {
11 | this.release_notes = releaseNotes;
12 | }
13 |
14 | @Override
15 | public String toString() {
16 | return "ReleaseDetailsUpdateResponse{" +
17 | "release_notes='" + release_notes + '\'' +
18 | '}';
19 | }
20 |
21 | @Override
22 | public boolean equals(Object o) {
23 | if (this == o) return true;
24 | if (o == null || getClass() != o.getClass()) return false;
25 | ReleaseDetailsUpdateResponse that = (ReleaseDetailsUpdateResponse) o;
26 | return Objects.equals(release_notes, that.release_notes);
27 | }
28 |
29 | @Override
30 | public int hashCode() {
31 | return Objects.hash(release_notes);
32 | }
33 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Mez Pahlan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/DestinationId.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nullable;
4 | import java.util.Objects;
5 |
6 | public final class DestinationId {
7 | @Nullable
8 | public final String name;
9 | @Nullable
10 | public final String id;
11 |
12 | public DestinationId(@Nullable String name, @Nullable String id) {
13 | this.name = name;
14 | this.id = id;
15 | }
16 |
17 | @Override
18 | public String toString() {
19 | return "DestinationId{" +
20 | "name='" + name + '\'' +
21 | ", id='" + id + '\'' +
22 | '}';
23 | }
24 |
25 | @Override
26 | public boolean equals(Object o) {
27 | if (this == o) return true;
28 | if (o == null || getClass() != o.getClass()) return false;
29 | DestinationId that = (DestinationId) o;
30 | return Objects.equals(name, that.name) &&
31 | Objects.equals(id, that.id);
32 | }
33 |
34 | @Override
35 | public int hashCode() {
36 | return Objects.hash(name, id);
37 | }
38 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/validator/PathPlaceholderValidatorTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import static com.google.common.truth.Truth.assertThat;
7 |
8 | public class PathPlaceholderValidatorTest {
9 | private PathPlaceholderValidator validator;
10 |
11 | @Before
12 | public void setUp() {
13 | validator = new PathPlaceholderValidator();
14 | }
15 |
16 | @Test
17 | public void should_ReturnTrue_When_PathDoesNotStartsWithEnvVariable() {
18 | // Given
19 | final String value = "relative/path/to/${SOME_ENV_VAR}.ipa";
20 |
21 | // When
22 | final boolean result = validator.isValid(value);
23 |
24 | // Then
25 | assertThat(result).isTrue();
26 | }
27 |
28 | @Test
29 | public void should_ReturnFalse_When_PathStartsWithEnvVariable() {
30 | // Given
31 | final String value = "${SOME_ENV_VAR}.ipa";
32 |
33 | // When
34 | final boolean result = validator.isValid(value);
35 |
36 | // Then
37 | assertThat(result).isFalse();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/di/JenkinsModule.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.di;
2 |
3 | import dagger.Module;
4 | import dagger.Provides;
5 | import hudson.EnvVars;
6 | import hudson.ProxyConfiguration;
7 | import hudson.model.Run;
8 | import hudson.model.TaskListener;
9 | import jenkins.model.Jenkins;
10 |
11 | import javax.annotation.Nullable;
12 | import javax.inject.Singleton;
13 | import java.io.IOException;
14 | import java.io.PrintStream;
15 |
16 | @Module
17 | final class JenkinsModule {
18 |
19 | @Provides
20 | @Nullable
21 | @Singleton
22 | static ProxyConfiguration provideProxyConfiguration(Jenkins jenkins) {
23 | return jenkins.proxy;
24 | }
25 |
26 | @Provides
27 | @Singleton
28 | static EnvVars provideEnvVars(Run, ?> run, TaskListener taskListener) throws RuntimeException {
29 | final PrintStream logger = taskListener.getLogger();
30 | try {
31 | return run.getEnvironment(taskListener);
32 | } catch (IOException | InterruptedException e) {
33 | e.printStackTrace(logger);
34 | throw new RuntimeException("Failed to get Environment Variables.");
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/UpdateReleaseUploadRequest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.Objects;
5 |
6 | public final class UpdateReleaseUploadRequest {
7 | @Nonnull
8 | public final StatusEnum upload_status;
9 |
10 | public UpdateReleaseUploadRequest(@Nonnull StatusEnum upload_status) {
11 | this.upload_status = upload_status;
12 | }
13 |
14 | @Override
15 | public String toString() {
16 | return "UpdateReleaseUploadRequest{" +
17 | "upload_status=" + upload_status +
18 | '}';
19 | }
20 |
21 | @Override
22 | public boolean equals(Object o) {
23 | if (this == o) return true;
24 | if (o == null || getClass() != o.getClass()) return false;
25 | UpdateReleaseUploadRequest that = (UpdateReleaseUploadRequest) o;
26 | return upload_status == that.upload_status;
27 | }
28 |
29 | @Override
30 | public int hashCode() {
31 | return Objects.hash(upload_status);
32 | }
33 |
34 | public enum StatusEnum {
35 | uploadFinished,
36 | uploadCanceled
37 | }
38 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/util/AndroidParser.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.util;
2 |
3 | import net.dongliu.apk.parser.ApkParsers;
4 | import net.dongliu.apk.parser.bean.ApkMeta;
5 |
6 | import javax.annotation.Nonnull;
7 | import javax.annotation.Nullable;
8 | import java.io.File;
9 | import java.io.IOException;
10 |
11 | public final class AndroidParser {
12 |
13 | @Nonnull
14 | private final File file;
15 | @Nullable
16 | private ApkMeta apkMeta;
17 |
18 | AndroidParser(final @Nonnull File file) {
19 | this.file = file;
20 | }
21 |
22 | @Nonnull
23 | public String versionCode() throws IOException {
24 | return metaInfo().getVersionCode().toString();
25 | }
26 |
27 | @Nonnull
28 | public String versionName() throws IOException {
29 | return metaInfo().getVersionName();
30 | }
31 |
32 | @Nonnull
33 | public String fileName() {
34 | return file.getName();
35 | }
36 |
37 | @Nonnull
38 | private ApkMeta metaInfo() throws IOException {
39 | if (apkMeta != null) return apkMeta;
40 |
41 | apkMeta = ApkParsers.getMetaInfo(file);
42 |
43 | return apkMeta;
44 | }
45 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/UploadedSymbolInfo.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.Objects;
5 |
6 | public final class UploadedSymbolInfo {
7 | @Nonnull
8 | public final String symbol_id;
9 | @Nonnull
10 | public final String platform;
11 |
12 | public UploadedSymbolInfo(@Nonnull String symbolId, @Nonnull String platform) {
13 | this.symbol_id = symbolId;
14 | this.platform = platform;
15 | }
16 |
17 | @Override
18 | public String toString() {
19 | return "UploadedSymbolInfo{" +
20 | "symbol_id='" + symbol_id + '\'' +
21 | ", platform='" + platform + '\'' +
22 | '}';
23 | }
24 |
25 | @Override
26 | public boolean equals(Object o) {
27 | if (this == o) return true;
28 | if (o == null || getClass() != o.getClass()) return false;
29 | UploadedSymbolInfo that = (UploadedSymbolInfo) o;
30 | return symbol_id.equals(that.symbol_id) &&
31 | platform.equals(that.platform);
32 | }
33 |
34 | @Override
35 | public int hashCode() {
36 | return Objects.hash(symbol_id, platform);
37 | }
38 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/di/AppCenterComponent.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.di;
2 |
3 | import dagger.BindsInstance;
4 | import dagger.Component;
5 | import hudson.FilePath;
6 | import hudson.model.Run;
7 | import hudson.model.TaskListener;
8 | import io.jenkins.plugins.appcenter.AppCenterRecorder;
9 | import io.jenkins.plugins.appcenter.task.UploadTask;
10 | import jenkins.model.Jenkins;
11 |
12 | import javax.annotation.Nullable;
13 | import javax.inject.Named;
14 | import javax.inject.Singleton;
15 |
16 | @Singleton
17 | @Component(modules = {JenkinsModule.class, AuthModule.class, UploadModule.class})
18 | public interface AppCenterComponent {
19 |
20 | UploadTask uploadTask();
21 |
22 | @Component.Factory
23 | interface Factory {
24 | AppCenterComponent create(@BindsInstance AppCenterRecorder appCenterRecorder,
25 | @BindsInstance Jenkins jenkins,
26 | @BindsInstance Run, ?> run,
27 | @BindsInstance FilePath filePath,
28 | @BindsInstance TaskListener taskListener,
29 | @BindsInstance @Nullable @Named("baseUrl") String baseUrl);
30 | }
31 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/SymbolUploadUserInfo.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nullable;
4 | import java.util.Objects;
5 |
6 | public final class SymbolUploadUserInfo {
7 | @Nullable
8 | public final String email;
9 | @Nullable
10 | public final String display_name;
11 |
12 | public SymbolUploadUserInfo(@Nullable String email, @Nullable String displayName) {
13 | this.email = email;
14 | this.display_name = displayName;
15 | }
16 |
17 | @Override
18 | public String toString() {
19 | return "SymbolUploadUserInfo{" +
20 | "email='" + email + '\'' +
21 | ", display_name='" + display_name + '\'' +
22 | '}';
23 | }
24 |
25 | @Override
26 | public boolean equals(Object o) {
27 | if (this == o) return true;
28 | if (o == null || getClass() != o.getClass()) return false;
29 | SymbolUploadUserInfo that = (SymbolUploadUserInfo) o;
30 | return Objects.equals(email, that.email) &&
31 | Objects.equals(display_name, that.display_name);
32 | }
33 |
34 | @Override
35 | public int hashCode() {
36 | return Objects.hash(email, display_name);
37 | }
38 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/ReleaseUploadEndResponse.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nullable;
4 | import java.util.Objects;
5 |
6 | public final class ReleaseUploadEndResponse {
7 |
8 | @Nullable
9 | public final Integer release_id;
10 | @Nullable
11 | public final String release_url;
12 |
13 | public ReleaseUploadEndResponse(@Nullable Integer releaseId, @Nullable String releaseUrl) {
14 | this.release_id = releaseId;
15 | this.release_url = releaseUrl;
16 | }
17 |
18 | @Override
19 | public String toString() {
20 | return "ReleaseUploadEndResponse{" +
21 | "release_id='" + release_id + '\'' +
22 | ", release_url='" + release_url + '\'' +
23 | '}';
24 | }
25 |
26 | @Override
27 | public boolean equals(Object o) {
28 | if (this == o) return true;
29 | if (o == null || getClass() != o.getClass()) return false;
30 | ReleaseUploadEndResponse that = (ReleaseUploadEndResponse) o;
31 | return Objects.equals(release_id, that.release_id) &&
32 | Objects.equals(release_url, that.release_url);
33 | }
34 |
35 | @Override
36 | public int hashCode() {
37 | return Objects.hash(release_id, release_url);
38 | }
39 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/ReleaseUploadBeginRequest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nullable;
4 | import java.util.Objects;
5 |
6 | public final class ReleaseUploadBeginRequest {
7 |
8 | @Nullable
9 | public final String build_version;
10 | @Nullable
11 | public final String build_number;
12 |
13 | public ReleaseUploadBeginRequest(@Nullable String buildVersion, @Nullable String buildNumber) {
14 | this.build_version = buildVersion;
15 | this.build_number = buildNumber;
16 | }
17 |
18 | @Override
19 | public String toString() {
20 | return "ReleaseUploadBeginRequest{" +
21 | ", build_version='" + build_version + '\'' +
22 | ", build_number='" + build_number + '\'' +
23 | '}';
24 | }
25 |
26 | @Override
27 | public boolean equals(Object o) {
28 | if (this == o) return true;
29 | if (o == null || getClass() != o.getClass()) return false;
30 | ReleaseUploadBeginRequest that = (ReleaseUploadBeginRequest) o;
31 | return Objects.equals(build_version, that.build_version) &&
32 | Objects.equals(build_number, that.build_number);
33 | }
34 |
35 | @Override
36 | public int hashCode() {
37 | return Objects.hash(build_version, build_number);
38 | }
39 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/ErrorDetails.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.Objects;
5 |
6 | public final class ErrorDetails {
7 |
8 | @Nonnull
9 | public final CodeEnum code;
10 |
11 | @Nonnull
12 | public final String message;
13 |
14 | public ErrorDetails(@Nonnull CodeEnum code, @Nonnull String message) {
15 | this.code = code;
16 | this.message = message;
17 | }
18 |
19 | @Override
20 | public String toString() {
21 | return "ErrorDetails{" +
22 | "code=" + code +
23 | ", message='" + message + '\'' +
24 | '}';
25 | }
26 |
27 | @Override
28 | public boolean equals(Object o) {
29 | if (this == o) return true;
30 | if (o == null || getClass() != o.getClass()) return false;
31 | ErrorDetails that = (ErrorDetails) o;
32 | return code == that.code &&
33 | message.equals(that.message);
34 | }
35 |
36 | @Override
37 | public int hashCode() {
38 | return Objects.hash(code, message);
39 | }
40 |
41 | public enum CodeEnum {
42 | BadRequest,
43 | Conflict,
44 | NotAcceptable,
45 | NotFound,
46 | InternalServerError,
47 | Unauthorized,
48 | TooManyRequests
49 | }
50 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/RoundTripTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter;
2 |
3 | import hudson.util.Secret;
4 | import org.junit.ClassRule;
5 | import org.junit.Test;
6 | import org.jvnet.hudson.test.JenkinsRule;
7 |
8 | import static com.google.common.truth.Truth.assertThat;
9 |
10 | public class RoundTripTest {
11 |
12 | @ClassRule
13 | public static JenkinsRule jenkinsRule = new JenkinsRule();
14 |
15 | @Test
16 | public void should_Configure_AppCenterRecorder_With_Required_Inputs() throws Exception {
17 | // Given
18 | final AppCenterRecorder appCenterRecorder = new AppCenterRecorder(
19 | "at-this-moment-you-should-be-with-us",
20 | "janes-addiction",
21 | "ritual-de-lo-habitual",
22 | "three/days/xiola.apk",
23 | "casey, niccoli"
24 | );
25 |
26 | // When
27 | jenkinsRule.configRoundtrip(appCenterRecorder);
28 |
29 | // Then
30 | assertThat(appCenterRecorder.getApiToken()).isEqualTo(Secret.fromString("at-this-moment-you-should-be-with-us"));
31 | assertThat(appCenterRecorder.getOwnerName()).isEqualTo("janes-addiction");
32 | assertThat(appCenterRecorder.getAppName()).isEqualTo("ritual-de-lo-habitual");
33 | assertThat(appCenterRecorder.getPathToApp()).isEqualTo("three/days/xiola.apk");
34 | assertThat(appCenterRecorder.getDistributionGroups()).isEqualTo("casey, niccoli");
35 | }
36 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/util/TestUtil.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.util;
2 |
3 | import hudson.Launcher;
4 | import hudson.model.AbstractBuild;
5 | import hudson.model.BuildListener;
6 | import org.jvnet.hudson.test.TestBuilder;
7 |
8 | import javax.annotation.Nonnull;
9 | import java.io.IOException;
10 | import java.util.Objects;
11 |
12 | public final class TestUtil {
13 | public static TestBuilder createFile(final @Nonnull String pathToFile) {
14 | return createFile(pathToFile, "all of us with wings");
15 | }
16 |
17 | public static TestBuilder createFile(final @Nonnull String pathToFile, final @Nonnull String content) {
18 | return new TestAppWriter(pathToFile, content);
19 | }
20 |
21 | private static class TestAppWriter extends TestBuilder {
22 |
23 | @Nonnull
24 | private final String pathToFile;
25 | @Nonnull
26 | private final String content;
27 |
28 | private TestAppWriter(final @Nonnull String pathToFile, final @Nonnull String content) {
29 | this.pathToFile = pathToFile;
30 | this.content = content;
31 | }
32 |
33 | public boolean perform(AbstractBuild, ?> build, Launcher launcher, BuildListener listener)
34 | throws InterruptedException, IOException {
35 | Objects.requireNonNull(build.getWorkspace()).child(pathToFile).write(content, "UTF-8");
36 | return true;
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/UpdateReleaseUploadResponse.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.Objects;
5 |
6 | public final class UpdateReleaseUploadResponse {
7 | @Nonnull
8 | public final String id;
9 | @Nonnull
10 | public final StatusEnum upload_status;
11 |
12 | public UpdateReleaseUploadResponse(@Nonnull String id,
13 | @Nonnull StatusEnum upload_status) {
14 |
15 | this.id = id;
16 | this.upload_status = upload_status;
17 | }
18 |
19 | @Override
20 | public String toString() {
21 | return "UpdateReleaseUploadResponse{" +
22 | "id='" + id + '\'' +
23 | ", upload_status=" + upload_status +
24 | '}';
25 | }
26 |
27 | @Override
28 | public boolean equals(Object o) {
29 | if (this == o) return true;
30 | if (o == null || getClass() != o.getClass()) return false;
31 | UpdateReleaseUploadResponse that = (UpdateReleaseUploadResponse) o;
32 | return id.equals(that.id) && upload_status == that.upload_status;
33 | }
34 |
35 | @Override
36 | public int hashCode() {
37 | return Objects.hash(id, upload_status);
38 | }
39 |
40 | public enum StatusEnum {
41 | uploadStarted,
42 | uploadFinished,
43 | uploadCanceled,
44 | readyToBePublished,
45 | malwareDetected,
46 | error
47 | }
48 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/BuildInfo.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nullable;
4 | import java.util.Objects;
5 |
6 | public final class BuildInfo {
7 | @Nullable
8 | public final String branch_name;
9 | @Nullable
10 | public final String commit_hash;
11 | @Nullable
12 | public final String commit_message;
13 |
14 | public BuildInfo(@Nullable String branchName, @Nullable String commitHash, @Nullable String commitMessage) {
15 | this.branch_name = branchName;
16 | this.commit_hash = commitHash;
17 | this.commit_message = commitMessage;
18 | }
19 |
20 | @Override
21 | public String toString() {
22 | return "BuildInfo{" +
23 | "branch_name='" + branch_name + '\'' +
24 | ", commit_hash='" + commit_hash + '\'' +
25 | ", commit_message='" + commit_message + '\'' +
26 | '}';
27 | }
28 |
29 | @Override
30 | public boolean equals(Object o) {
31 | if (this == o) return true;
32 | if (o == null || getClass() != o.getClass()) return false;
33 | BuildInfo buildInfo = (BuildInfo) o;
34 | return Objects.equals(branch_name, buildInfo.branch_name) &&
35 | Objects.equals(commit_hash, buildInfo.commit_hash) &&
36 | Objects.equals(commit_message, buildInfo.commit_message);
37 | }
38 |
39 | @Override
40 | public int hashCode() {
41 | return Objects.hash(branch_name, commit_hash, commit_message);
42 | }
43 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/DestinationError.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nullable;
4 | import java.util.Objects;
5 |
6 | public final class DestinationError {
7 | @Nullable
8 | public final String code;
9 |
10 | @Nullable
11 | public final String message;
12 |
13 | @Nullable
14 | public final String id;
15 |
16 | @Nullable
17 | public final String name;
18 |
19 | public DestinationError(@Nullable String code, @Nullable String message, @Nullable String id, @Nullable String name) {
20 | this.code = code;
21 | this.message = message;
22 | this.id = id;
23 | this.name = name;
24 | }
25 |
26 | @Override
27 | public String toString() {
28 | return "DestinationError{" +
29 | "code='" + code + '\'' +
30 | ", message='" + message + '\'' +
31 | ", id='" + id + '\'' +
32 | ", name='" + name + '\'' +
33 | '}';
34 | }
35 |
36 | @Override
37 | public boolean equals(Object o) {
38 | if (this == o) return true;
39 | if (o == null || getClass() != o.getClass()) return false;
40 | DestinationError that = (DestinationError) o;
41 | return Objects.equals(code, that.code) &&
42 | Objects.equals(message, that.message) &&
43 | Objects.equals(id, that.id) &&
44 | Objects.equals(name, that.name);
45 | }
46 |
47 | @Override
48 | public int hashCode() {
49 | return Objects.hash(code, message, id, name);
50 | }
51 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/di/UploadModule.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.di;
2 |
3 | import dagger.Module;
4 | import dagger.Provides;
5 | import hudson.EnvVars;
6 | import io.jenkins.plugins.appcenter.AppCenterRecorder;
7 | import io.jenkins.plugins.appcenter.task.request.UploadRequest;
8 |
9 | import javax.inject.Singleton;
10 |
11 | @Module
12 | final class UploadModule {
13 |
14 | @Provides
15 | @Singleton
16 | static UploadRequest provideUploadRequest(AppCenterRecorder appCenterRecorder, EnvVars envVars) {
17 | return new UploadRequest.Builder()
18 | .setOwnerName(envVars.expand(appCenterRecorder.getOwnerName()))
19 | .setAppName(envVars.expand(appCenterRecorder.getAppName()))
20 | .setPathToApp(envVars.expand(appCenterRecorder.getPathToApp()))
21 | .setDestinationGroups(envVars.expand(appCenterRecorder.getDistributionGroups()))
22 | .setReleaseNotes(envVars.expand(appCenterRecorder.getReleaseNotes()))
23 | .setPathToReleaseNotes(envVars.expand(appCenterRecorder.getPathToReleaseNotes()))
24 | .setNotifyTesters(appCenterRecorder.getNotifyTesters())
25 | .setMandatoryUpdate(appCenterRecorder.getMandatoryUpdate())
26 | .setBuildVersion(envVars.expand(appCenterRecorder.getBuildVersion()))
27 | .setPathToDebugSymbols(envVars.expand(appCenterRecorder.getPathToDebugSymbols()))
28 | .setCommitHash(envVars.expand(appCenterRecorder.getCommitHash()))
29 | .setBranchName(envVars.expand(appCenterRecorder.getBranchName()))
30 | .build();
31 | }
32 | }
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/appcenter/Messages.properties:
--------------------------------------------------------------------------------
1 | AppCenterRecorder.DescriptorImpl.warnings.mustNotStartWithEnvVar=Paths should not start with placeholders
2 | AppCenterRecorder.DescriptorImpl.errors.upstreamBuildFailure=Skipping due to upstream build failure
3 | AppCenterRecorder.DescriptorImpl.errors.missingApiToken=Please specify an AppCenter API Token
4 | AppCenterRecorder.DescriptorImpl.errors.invalidApiToken=API Token cannot contain whitespace
5 | AppCenterRecorder.DescriptorImpl.errors.missingOwnerName=Please specify an AppCenter Owner Name
6 | AppCenterRecorder.DescriptorImpl.errors.invalidOwnerName=Username can only contain letters, numbers, dashes, underscores, or full stops
7 | AppCenterRecorder.DescriptorImpl.errors.missingAppName=Please specify an AppCenter App Name
8 | AppCenterRecorder.DescriptorImpl.errors.invalidAppName=App name cannot contain whitespace
9 | AppCenterRecorder.DescriptorImpl.errors.invalidBranchName=Branch name cannot contain whitespace
10 | AppCenterRecorder.DescriptorImpl.errors.invalidCommitHash=Commit hash cannot contain whitespace
11 | AppCenterRecorder.DescriptorImpl.errors.missingDistributionGroups=Please specify Distribution Groups
12 | AppCenterRecorder.DescriptorImpl.errors.invalidDistributionGroups=Distribution Groups cannot be empty
13 | AppCenterRecorder.DescriptorImpl.errors.missingPathToApp=Please specify a path to the app you wish to upload
14 | AppCenterRecorder.DescriptorImpl.errors.invalidPath=Invalid path
15 | AppCenterRecorder.DescriptorImpl.errors.invalidBuildVersion=Build version cannot contain whitespace
16 | AppCenterRecorder.DescriptorImpl.DisplayName=Upload app to AppCenter
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/SymbolUploadBeginResponse.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.Objects;
5 |
6 | public final class SymbolUploadBeginResponse {
7 | @Nonnull
8 | public final String symbol_upload_id;
9 | @Nonnull
10 | public final String upload_url;
11 | @Nonnull
12 | public final String expiration_date;
13 |
14 | public SymbolUploadBeginResponse(@Nonnull String symbolUploadId, @Nonnull String uploadUrl, @Nonnull String expirationDate) {
15 | this.symbol_upload_id = symbolUploadId;
16 | this.upload_url = uploadUrl;
17 | this.expiration_date = expirationDate;
18 | }
19 |
20 | @Override
21 | public String toString() {
22 | return "SymbolUploadBeginResponse{" +
23 | "symbol_upload_id='" + symbol_upload_id + '\'' +
24 | ", upload_url='" + upload_url + '\'' +
25 | ", expiration_date='" + expiration_date + '\'' +
26 | '}';
27 | }
28 |
29 | @Override
30 | public boolean equals(Object o) {
31 | if (this == o) return true;
32 | if (o == null || getClass() != o.getClass()) return false;
33 | SymbolUploadBeginResponse that = (SymbolUploadBeginResponse) o;
34 | return symbol_upload_id.equals(that.symbol_upload_id) &&
35 | upload_url.equals(that.upload_url) &&
36 | expiration_date.equals(that.expiration_date);
37 | }
38 |
39 | @Override
40 | public int hashCode() {
41 | return Objects.hash(symbol_upload_id, upload_url, expiration_date);
42 | }
43 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/validator/AppNameValidatorTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 |
4 | import org.junit.Before;
5 | import org.junit.Test;
6 |
7 | import static com.google.common.truth.Truth.assertThat;
8 |
9 | public class AppNameValidatorTest {
10 |
11 | private AppNameValidator validator;
12 |
13 | @Before
14 | public void setUp() {
15 | validator = new AppNameValidator();
16 | }
17 |
18 | @Test
19 | public void should_ReturnFalse_When_AppNameContainsSingleSpace() {
20 | // Given
21 | final String value = " ";
22 |
23 | // When
24 | final boolean result = validator.isValid(value);
25 |
26 | // Then
27 | assertThat(result).isFalse();
28 | }
29 |
30 | @Test
31 | public void should_ReturnFalse_When_AppNameContainsMultipleSpace() {
32 | // Given
33 | final String value = " ";
34 |
35 | // When
36 | final boolean result = validator.isValid(value);
37 |
38 | // Then
39 | assertThat(result).isFalse();
40 | }
41 |
42 | @Test
43 | public void should_ReturnFalse_When_AppNameContainsSpaces() {
44 | // Given
45 | final String value = " my super duper app ";
46 |
47 | // When
48 | final boolean result = validator.isValid(value);
49 |
50 | // Then
51 | assertThat(result).isFalse();
52 | }
53 |
54 | @Test
55 | public void should_ReturnTrue_When_AppNameDoesNotContainSpaces() {
56 | // Given
57 | final String value = "my-super-duper-app";
58 |
59 | // When
60 | final boolean result = validator.isValid(value);
61 |
62 | // Then
63 | assertThat(result).isTrue();
64 | }
65 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/validator/ApiTokenValidatorTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import static com.google.common.truth.Truth.assertThat;
7 |
8 | public class ApiTokenValidatorTest {
9 |
10 | private ApiTokenValidator validator;
11 |
12 | @Before
13 | public void setUp() {
14 | validator = new ApiTokenValidator();
15 | }
16 |
17 | @Test
18 | public void should_ReturnFalse_When_ApiTokenContainsSingleSpace() {
19 | // Given
20 | final String value = " ";
21 |
22 | // When
23 | final boolean result = validator.isValid(value);
24 |
25 | // Then
26 | assertThat(result).isFalse();
27 | }
28 |
29 | @Test
30 | public void should_ReturnFalse_When_ApiTokenContainsMultipleSpace() {
31 | // Given
32 | final String value = " ";
33 |
34 | // When
35 | final boolean result = validator.isValid(value);
36 |
37 | // Then
38 | assertThat(result).isFalse();
39 | }
40 |
41 | @Test
42 | public void should_ReturnFalse_When_ApiTokenContainsSpaces() {
43 | // Given
44 | final String value = " a12b3 4cd 5f89 98av3 ";
45 |
46 | // When
47 | final boolean result = validator.isValid(value);
48 |
49 | // Then
50 | assertThat(result).isFalse();
51 | }
52 |
53 | @Test
54 | public void should_ReturnTrue_When_ApiTokenDoesNotContainSpaces() {
55 | // Given
56 | final String value = "a12b3-4cd-5f89-98av3";
57 |
58 | // When
59 | final boolean result = validator.isValid(value);
60 |
61 | // Then
62 | assertThat(result).isTrue();
63 | }
64 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/validator/BranchNameValidatorTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 |
4 | import org.junit.Before;
5 | import org.junit.Test;
6 |
7 | import static com.google.common.truth.Truth.assertThat;
8 |
9 | public class BranchNameValidatorTest {
10 |
11 | private BranchNameValidator validator;
12 |
13 | @Before
14 | public void setUp() {
15 | validator = new BranchNameValidator();
16 | }
17 |
18 | @Test
19 | public void should_ReturnFalse_When_BranchNameContainsSingleSpace() {
20 | // Given
21 | final String value = " ";
22 |
23 | // When
24 | final boolean result = validator.isValid(value);
25 |
26 | // Then
27 | assertThat(result).isFalse();
28 | }
29 |
30 | @Test
31 | public void should_ReturnFalse_When_BranchNameContainsMultipleSpace() {
32 | // Given
33 | final String value = " ";
34 |
35 | // When
36 | final boolean result = validator.isValid(value);
37 |
38 | // Then
39 | assertThat(result).isFalse();
40 | }
41 |
42 | @Test
43 | public void should_ReturnFalse_When_BranchNameContainsSpaces() {
44 | // Given
45 | final String value = "origin/ invalid branch";
46 |
47 | // When
48 | final boolean result = validator.isValid(value);
49 |
50 | // Then
51 | assertThat(result).isFalse();
52 | }
53 |
54 | @Test
55 | public void should_ReturnTrue_When_BranchNameDoesNotContainSpaces() {
56 | // Given
57 | final String value = "origin/master";
58 |
59 | // When
60 | final boolean result = validator.isValid(value);
61 |
62 | // Then
63 | assertThat(result).isTrue();
64 | }
65 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/validator/CommitHashValidatorTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 |
4 | import org.junit.Before;
5 | import org.junit.Test;
6 |
7 | import static com.google.common.truth.Truth.assertThat;
8 |
9 | public class CommitHashValidatorTest {
10 |
11 | private CommitHashValidator validator;
12 |
13 | @Before
14 | public void setUp() {
15 | validator = new CommitHashValidator();
16 | }
17 |
18 | @Test
19 | public void should_ReturnFalse_When_CommitHashContainsSingleSpace() {
20 | // Given
21 | final String value = " ";
22 |
23 | // When
24 | final boolean result = validator.isValid(value);
25 |
26 | // Then
27 | assertThat(result).isFalse();
28 | }
29 |
30 | @Test
31 | public void should_ReturnFalse_When_CommitHashContainsMultipleSpace() {
32 | // Given
33 | final String value = " ";
34 |
35 | // When
36 | final boolean result = validator.isValid(value);
37 |
38 | // Then
39 | assertThat(result).isFalse();
40 | }
41 |
42 | @Test
43 | public void should_ReturnFalse_When_CommitHashContainsSpaces() {
44 | // Given
45 | final String value = "ffa8d2d2ad619d13 20f94d1865d39647e9e8e278";
46 |
47 | // When
48 | final boolean result = validator.isValid(value);
49 |
50 | // Then
51 | assertThat(result).isFalse();
52 | }
53 |
54 | @Test
55 | public void should_ReturnTrue_When_CommitHashDoesNotContainSpaces() {
56 | // Given
57 | final String value = "ffa8d2d2ad619d1320f94d1865d39647e9e8e278";
58 |
59 | // When
60 | final boolean result = validator.isValid(value);
61 |
62 | // Then
63 | assertThat(result).isTrue();
64 | }
65 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/AppCenterLogger.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter;
2 |
3 | import okhttp3.ResponseBody;
4 | import retrofit2.HttpException;
5 | import retrofit2.Response;
6 |
7 | import javax.annotation.Nonnull;
8 | import java.io.IOException;
9 | import java.io.PrintStream;
10 |
11 | import static java.util.Objects.requireNonNull;
12 |
13 | public interface AppCenterLogger {
14 |
15 | PrintStream getLogger();
16 |
17 | default void log(String message) {
18 | getLogger().println(message);
19 | }
20 |
21 | default AppCenterException logFailure(@Nonnull String message) {
22 | requireNonNull(message, "message cannot be null.");
23 | return new AppCenterException(message);
24 | }
25 |
26 | default AppCenterException logFailure(@Nonnull String message, @Nonnull Throwable throwable) {
27 | requireNonNull(message, "message cannot be null.");
28 | requireNonNull(throwable, "throwable cannot be null.");
29 |
30 | // Error could be an HttPException or it could not be
31 | if (throwable instanceof HttpException) {
32 | try {
33 | final HttpException httpException = (HttpException) throwable;
34 | final Response> response = requireNonNull(httpException.response(), "response cannot be null.");
35 | final ResponseBody responseBody = requireNonNull(response.errorBody(), "errorBody cannot be null.");
36 | final String json = responseBody.string();
37 | return logFailure(String.format("%1$s: %2$s: %3$s", message, httpException.getLocalizedMessage(), json));
38 | } catch (IOException e) {
39 | return new AppCenterException(message, e);
40 | }
41 | }
42 |
43 | return new AppCenterException(message, throwable);
44 | }
45 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/ReleaseUploadBeginResponse.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nonnull;
4 | import java.util.Objects;
5 |
6 | public final class ReleaseUploadBeginResponse {
7 | @Nonnull
8 | public final String id;
9 | @Nonnull
10 | public final String upload_domain;
11 | @Nonnull
12 | public final String token;
13 | @Nonnull
14 | public final String url_encoded_token;
15 | @Nonnull
16 | public final String package_asset_id;
17 |
18 | public ReleaseUploadBeginResponse(@Nonnull String id, @Nonnull String uploadDomain, @Nonnull String token, @Nonnull String urlEncodedToken, @Nonnull String packageAssetId) {
19 | this.id = id;
20 | this.upload_domain = uploadDomain;
21 | this.token = token;
22 | this.url_encoded_token = urlEncodedToken;
23 | this.package_asset_id = packageAssetId;
24 | }
25 |
26 | @Override
27 | public String toString() {
28 | return "ReleaseUploadBeginResponse{" +
29 | "id='" + id + '\'' +
30 | ", upload_domain='" + upload_domain + '\'' +
31 | ", token='" + token + '\'' +
32 | ", url_encoded_token='" + url_encoded_token + '\'' +
33 | ", package_asset_id='" + package_asset_id + '\'' +
34 | '}';
35 | }
36 |
37 | @Override
38 | public boolean equals(Object o) {
39 | if (this == o) return true;
40 | if (o == null || getClass() != o.getClass()) return false;
41 | ReleaseUploadBeginResponse that = (ReleaseUploadBeginResponse) o;
42 | return id.equals(that.id) &&
43 | upload_domain.equals(that.upload_domain) &&
44 | token.equals(that.token) &&
45 | url_encoded_token.equals(that.url_encoded_token) &&
46 | package_asset_id.equals(that.package_asset_id);
47 | }
48 |
49 | @Override
50 | public int hashCode() {
51 | return Objects.hash(id, upload_domain, token, url_encoded_token, package_asset_id);
52 | }
53 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/ReleaseUpdateRequest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nullable;
4 | import java.util.List;
5 | import java.util.Objects;
6 |
7 | public final class ReleaseUpdateRequest {
8 |
9 | @Nullable
10 | public final String release_notes;
11 |
12 | @Nullable
13 | public final Boolean mandatory_update;
14 |
15 | @Nullable
16 | public final List destinations;
17 |
18 | @Nullable
19 | public final BuildInfo build;
20 |
21 | @Nullable
22 | public final Boolean notify_testers;
23 |
24 | public ReleaseUpdateRequest(@Nullable String releaseNotes, @Nullable Boolean mandatoryUpdate, @Nullable List destinations, @Nullable BuildInfo build, @Nullable Boolean notifyTesters) {
25 | this.release_notes = releaseNotes;
26 | this.mandatory_update = mandatoryUpdate;
27 | this.destinations = destinations;
28 | this.build = build;
29 | this.notify_testers = notifyTesters;
30 | }
31 |
32 | @Override
33 | public String toString() {
34 | return "ReleaseUpdateRequest{" +
35 | "release_notes='" + release_notes + '\'' +
36 | ", mandatory_update=" + mandatory_update +
37 | ", destinations=" + destinations +
38 | ", build=" + build +
39 | ", notify_testers=" + notify_testers +
40 | '}';
41 | }
42 |
43 | @Override
44 | public boolean equals(Object o) {
45 | if (this == o) return true;
46 | if (o == null || getClass() != o.getClass()) return false;
47 | ReleaseUpdateRequest that = (ReleaseUpdateRequest) o;
48 | return Objects.equals(release_notes, that.release_notes) &&
49 | Objects.equals(mandatory_update, that.mandatory_update) &&
50 | Objects.equals(destinations, that.destinations) &&
51 | Objects.equals(build, that.build) &&
52 | Objects.equals(notify_testers, that.notify_testers);
53 | }
54 |
55 | @Override
56 | public int hashCode() {
57 | return Objects.hash(release_notes, mandatory_update, destinations, build, notify_testers);
58 | }
59 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/validator/DistributionGroupsValidatorTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import static com.google.common.truth.Truth.assertThat;
7 |
8 | public class DistributionGroupsValidatorTest {
9 |
10 | private Validator validator;
11 |
12 | @Before
13 | public void setUp() {
14 | validator = new DistributionGroupsValidator();
15 | }
16 |
17 | @Test
18 | public void should_ReturnTrue_When_DistributionGroupsContainsAlphanumericCharacters() {
19 | // Given
20 | final String value = "Collaborators";
21 |
22 | // When
23 | final boolean result = validator.isValid(value);
24 |
25 | // Then
26 | assertThat(result).isTrue();
27 | }
28 |
29 | @Test
30 | public void should_ReturnTrue_When_DistributionGroupsContainsMultipleGroups() {
31 | // Given
32 | final String value = "Collaborators, internal,beta-testers ";
33 |
34 | // When
35 | final boolean result = validator.isValid(value);
36 |
37 | // Then
38 | assertThat(result).isTrue();
39 | }
40 |
41 | @Test
42 | public void should_ReturnTrue_When_DistributionGroupsContainsComplexCharacters() {
43 | // Given
44 | final String value = "Internal test group 5 漢字 !";
45 |
46 | // When
47 | final boolean result = validator.isValid(value);
48 |
49 | // Then
50 | assertThat(result).isTrue();
51 | }
52 |
53 | @Test
54 | public void should_ReturnFalse_When_DistributionsGroupIsEmpty() {
55 | // Given
56 | final String value = " ";
57 |
58 | // When
59 | final boolean result = validator.isValid(value);
60 |
61 | // Then
62 | assertThat(result).isFalse();
63 | }
64 |
65 | @Test
66 | public void should_ReturnFalse_When_DistributionsGroupContainsOnlySeparators() {
67 | // Given
68 | final String value = " ,,, ,, ,,, ,,,, , , ,";
69 |
70 | // When
71 | final boolean result = validator.isValid(value);
72 |
73 | // Then
74 | assertThat(result).isFalse();
75 | }
76 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AppCenter Plugin
2 | [](https://plugins.jenkins.io/appcenter)
3 | [](https://github.com/jenkinsci/appcenter-plugin/releases/latest)
4 | [](https://plugins.jenkins.io/appcenter)
5 |
6 | Jenkins plugin to upload artefacts to [AppCenter](https://appcenter.ms). A replacement for the [HockeyApp](https://plugins.jenkins.io/hockeyapp)
7 | plugin.
8 |
9 | ## Roadmap
10 |
11 | This plugin is currently in Alpha and looking for contributors. To begin with it will aim to support the upload
12 | functionality of AppCenter. When the APIs for AppCenter become stable this plugin will be eligible to be moved out of
13 | Alpha.
14 |
15 | These are the [outstanding tickets](https://issues.jenkins-ci.org/issues/?filter=20347) for this project.
16 |
17 | ## Contributing
18 |
19 | If you would like to contribute it would be massively helpful if you followed these steps:
20 |
21 | 1. Create an issue first in the [Jenkins issue tracker](https://issues.jenkins-ci.org).
22 | * Use the component `appcenter-plugin`.
23 | 2. Create a branch from `master` referencing your issue id.
24 | 3. Commit, commit, commit.
25 | 4. Push your changes and file a PR.
26 |
27 | ## Usage Instructions
28 |
29 | Up to date syntax for this plugin can always be found in the Jenkins Pipeline Syntax Generator. However in its
30 | simplest form you can upload an artefact to AppCenter like this:
31 |
32 | ```Groovy
33 | stage('Publish') {
34 | environment {
35 | APPCENTER_API_TOKEN = credentials('at-this-moment-you-should-be-with-us')
36 | }
37 | steps {
38 | appCenter apiToken: APPCENTER_API_TOKEN,
39 | ownerName: 'janes-addiction',
40 | appName: 'ritual-de-lo-habitual',
41 | pathToApp: 'three/days/xiola.apk',
42 | distributionGroups: 'casey, niccoli'
43 | }
44 | }
45 | ```
46 |
47 | It may sound obvious but ensure the file you are trying to upload is available on the node that you are running the
48 | plugin from.
49 |
50 | ## Changelog
51 |
52 | See [release page](https://github.com/jenkinsci/appcenter-plugin/releases).
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/FreestyleTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter;
2 |
3 | import hudson.model.FreeStyleBuild;
4 | import hudson.model.FreeStyleProject;
5 | import hudson.model.Result;
6 | import io.jenkins.plugins.appcenter.util.MockWebServerUtil;
7 | import io.jenkins.plugins.appcenter.util.TestUtil;
8 | import okhttp3.mockwebserver.MockWebServer;
9 | import org.junit.Before;
10 | import org.junit.ClassRule;
11 | import org.junit.Rule;
12 | import org.junit.Test;
13 | import org.jvnet.hudson.test.JenkinsRule;
14 |
15 | import java.io.IOException;
16 |
17 | public class FreestyleTest {
18 |
19 | @ClassRule
20 | public static JenkinsRule jenkinsRule = new JenkinsRule();
21 |
22 | @Rule
23 | public MockWebServer mockWebServer = new MockWebServer();
24 |
25 | private FreeStyleProject freeStyleProject;
26 |
27 | @Before
28 | public void setUp() throws IOException {
29 | freeStyleProject = jenkinsRule.createFreeStyleProject();
30 | freeStyleProject.getBuildersList().add(TestUtil.createFile("three/days/xiola.apk"));
31 |
32 | final AppCenterRecorder appCenterRecorder = new AppCenterRecorder("at-this-moment-you-should-be-with-us", "janes-addiction", "ritual-de-lo-habitual", "three/days/xiola.apk", "casey, niccoli");
33 | appCenterRecorder.setBaseUrl(mockWebServer.url("/").toString());
34 | freeStyleProject.getPublishersList().add(appCenterRecorder);
35 | }
36 |
37 | @Test
38 | public void should_SetBuildResultFailure_When_UploadTaskFails() throws Exception {
39 | // Given
40 | MockWebServerUtil.enqueueFailure(mockWebServer);
41 |
42 | // When
43 | final FreeStyleBuild freeStyleBuild = freeStyleProject.scheduleBuild2(0).get();
44 |
45 | // Then
46 | jenkinsRule.assertBuildStatus(Result.FAILURE, freeStyleBuild);
47 | }
48 |
49 | @Test
50 | public void should_SetBuildResultSuccess_When_AppCenterAcceptsAllRequests() throws Exception {
51 | // Given
52 | MockWebServerUtil.enqueueSuccess(mockWebServer);
53 |
54 | // When
55 | final FreeStyleBuild freeStyleBuild = freeStyleProject.scheduleBuild2(0).get();
56 |
57 | // Then
58 | jenkinsRule.assertBuildStatus(Result.SUCCESS, freeStyleBuild);
59 | }
60 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/NodeTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter;
2 |
3 | import hudson.model.FreeStyleBuild;
4 | import hudson.model.FreeStyleProject;
5 | import hudson.model.Node;
6 | import hudson.model.Result;
7 | import hudson.slaves.RetentionStrategy;
8 | import io.jenkins.plugins.appcenter.util.MockWebServerUtil;
9 | import io.jenkins.plugins.appcenter.util.TestUtil;
10 | import okhttp3.mockwebserver.MockWebServer;
11 | import org.jenkinci.plugins.mock_slave.MockSlave;
12 | import org.junit.Before;
13 | import org.junit.ClassRule;
14 | import org.junit.Rule;
15 | import org.junit.Test;
16 | import org.jvnet.hudson.test.JenkinsRule;
17 |
18 | import java.util.Collections;
19 |
20 | import static com.google.common.truth.Truth.assertThat;
21 |
22 | public class NodeTest {
23 |
24 | @ClassRule
25 | public static JenkinsRule jenkinsRule = new JenkinsRule();
26 |
27 | @Rule
28 | public MockWebServer mockWebServer = new MockWebServer();
29 |
30 | private FreeStyleProject freeStyleProject;
31 |
32 | private Node slave;
33 |
34 | @Before
35 | public void setUp() throws Exception {
36 | freeStyleProject = jenkinsRule.createFreeStyleProject();
37 | freeStyleProject.getBuildersList().add(TestUtil.createFile("three/days/xiola.apk"));
38 |
39 | final AppCenterRecorder appCenterRecorder = new AppCenterRecorder("at-this-moment-you-should-be-with-us", "janes-addiction", "ritual-de-lo-habitual", "three/days/xiola.apk", "casey, niccoli");
40 | appCenterRecorder.setBaseUrl(mockWebServer.url("/").toString());
41 | freeStyleProject.getPublishersList().add(appCenterRecorder);
42 |
43 | slave = new MockSlave("test-slave", 1, Node.Mode.NORMAL, "", RetentionStrategy.Always.INSTANCE, Collections.emptyList());
44 | jenkinsRule.jenkins.addNode(slave);
45 | freeStyleProject.setAssignedNode(slave);
46 | }
47 |
48 | @Test
49 | public void should_BuildFreeStyleProject_When_RunOnANode() throws Exception {
50 | // Given
51 | MockWebServerUtil.enqueueSuccess(mockWebServer);
52 |
53 | // When
54 | final FreeStyleBuild freeStyleBuild = freeStyleProject.scheduleBuild2(0).get();
55 |
56 | // Then
57 | jenkinsRule.assertBuildStatus(Result.SUCCESS, freeStyleBuild);
58 | assertThat(freeStyleBuild.getBuiltOn()).isEqualTo(slave);
59 | }
60 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/ReleaseUpdateError.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nonnull;
4 | import javax.annotation.Nullable;
5 | import java.util.List;
6 | import java.util.Objects;
7 |
8 | public final class ReleaseUpdateError {
9 |
10 | @Nonnull
11 | public final CodeEnum code;
12 |
13 | @Nonnull
14 | public final String message;
15 |
16 | @Nullable
17 | public final String release_notes;
18 |
19 | @Nullable
20 | public final Boolean mandatory_update;
21 |
22 | @Nullable
23 | public final List destinations;
24 |
25 | public ReleaseUpdateError(@Nonnull CodeEnum code, @Nonnull String message, @Nullable String releaseNotes, @Nullable Boolean mandatoryUpdate, @Nullable List destinations) {
26 | this.code = code;
27 | this.message = message;
28 | this.release_notes = releaseNotes;
29 | this.mandatory_update = mandatoryUpdate;
30 | this.destinations = destinations;
31 | }
32 |
33 | @Override
34 | public String toString() {
35 | return "ReleaseUpdateError{" +
36 | "code=" + code +
37 | ", message='" + message + '\'' +
38 | ", release_notes='" + release_notes + '\'' +
39 | ", mandatory_update=" + mandatory_update +
40 | ", destinations=" + destinations +
41 | '}';
42 | }
43 |
44 | @Override
45 | public boolean equals(Object o) {
46 | if (this == o) return true;
47 | if (o == null || getClass() != o.getClass()) return false;
48 | ReleaseUpdateError that = (ReleaseUpdateError) o;
49 | return code == that.code &&
50 | message.equals(that.message) &&
51 | Objects.equals(release_notes, that.release_notes) &&
52 | Objects.equals(mandatory_update, that.mandatory_update) &&
53 | Objects.equals(destinations, that.destinations);
54 | }
55 |
56 | @Override
57 | public int hashCode() {
58 | return Objects.hash(code, message, release_notes, mandatory_update, destinations);
59 | }
60 |
61 | public enum CodeEnum {
62 | BadRequest,
63 | Conflict,
64 | NotAcceptable,
65 | NotFound,
66 | InternalServerError,
67 | Unauthorized,
68 | TooManyRequests
69 | }
70 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/SymbolUploadBeginRequest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nonnull;
4 | import javax.annotation.Nullable;
5 | import java.io.Serializable;
6 | import java.util.Objects;
7 |
8 | public final class SymbolUploadBeginRequest implements Serializable {
9 |
10 | private static final long serialVersionUID = 1L;
11 |
12 | @Nonnull
13 | public final SymbolTypeEnum symbol_type;
14 | @Nullable
15 | public final String client_callback;
16 | @Nullable
17 | public final String file_name;
18 | @Nullable
19 | public final String build;
20 | @Nullable
21 | public final String version;
22 |
23 | public SymbolUploadBeginRequest(@Nonnull SymbolTypeEnum symbolTypeEnum, @Nullable String clientCallback, @Nullable String fileName, @Nullable String build, @Nullable String version) {
24 | this.symbol_type = symbolTypeEnum;
25 | this.client_callback = clientCallback;
26 | this.file_name = fileName;
27 | this.build = build;
28 | this.version = version;
29 | }
30 |
31 | @Override
32 | public String toString() {
33 | return "SymbolUploadBeginRequest{" +
34 | "symbol_type=" + symbol_type +
35 | ", client_callback='" + client_callback + '\'' +
36 | ", file_name='" + file_name + '\'' +
37 | ", build='" + build + '\'' +
38 | ", version='" + version + '\'' +
39 | '}';
40 | }
41 |
42 | @Override
43 | public boolean equals(Object o) {
44 | if (this == o) return true;
45 | if (o == null || getClass() != o.getClass()) return false;
46 | SymbolUploadBeginRequest that = (SymbolUploadBeginRequest) o;
47 | return symbol_type == that.symbol_type &&
48 | Objects.equals(client_callback, that.client_callback) &&
49 | Objects.equals(file_name, that.file_name) &&
50 | Objects.equals(build, that.build) &&
51 | Objects.equals(version, that.version);
52 | }
53 |
54 | @Override
55 | public int hashCode() {
56 | return Objects.hash(symbol_type, client_callback, file_name, build, version);
57 | }
58 |
59 | public enum SymbolTypeEnum {
60 | Apple,
61 | JavaScript,
62 | Breakpad,
63 | AndroidProguard,
64 | UWP
65 | }
66 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/PollForReleaseResponse.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nonnull;
4 | import javax.annotation.Nullable;
5 | import java.util.Objects;
6 |
7 | public final class PollForReleaseResponse {
8 | @Nonnull
9 | public final String id;
10 | @Nonnull
11 | public final StatusEnum upload_status;
12 | @Nullable
13 | public final String error_details;
14 | @Nullable
15 | public final Integer release_distinct_id;
16 | @Nullable
17 | public final String release_url;
18 |
19 | public PollForReleaseResponse(@Nonnull String id,
20 | @Nonnull StatusEnum upload_status,
21 | @Nullable String error_details,
22 | @Nullable Integer release_distinct_id,
23 | @Nullable String release_url) {
24 |
25 | this.id = id;
26 | this.upload_status = upload_status;
27 | this.error_details = error_details;
28 | this.release_distinct_id = release_distinct_id;
29 | this.release_url = release_url;
30 | }
31 |
32 | @Override
33 | public String toString() {
34 | return "PollForReleaseResponse{" +
35 | "id='" + id + '\'' +
36 | ", upload_status=" + upload_status +
37 | ", error_details='" + error_details + '\'' +
38 | ", release_distinct_id=" + release_distinct_id +
39 | ", release_url='" + release_url + '\'' +
40 | '}';
41 | }
42 |
43 | @Override
44 | public boolean equals(Object o) {
45 | if (this == o) return true;
46 | if (o == null || getClass() != o.getClass()) return false;
47 | PollForReleaseResponse that = (PollForReleaseResponse) o;
48 | return id.equals(that.id) && upload_status == that.upload_status && Objects.equals(error_details, that.error_details) && Objects.equals(release_distinct_id, that.release_distinct_id) && Objects.equals(release_url, that.release_url);
49 | }
50 |
51 | @Override
52 | public int hashCode() {
53 | return Objects.hash(id, upload_status, error_details, release_distinct_id, release_url);
54 | }
55 |
56 | public enum StatusEnum {
57 | uploadStarted,
58 | uploadFinished,
59 | readyToBePublished,
60 | malwareDetected,
61 | error
62 | }
63 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/util/RemoteFileUtils.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.util;
2 |
3 | import hudson.FilePath;
4 |
5 | import javax.annotation.Nonnull;
6 | import javax.annotation.Nullable;
7 | import javax.inject.Inject;
8 | import java.io.File;
9 | import java.io.Serializable;
10 |
11 | public class RemoteFileUtils implements Serializable {
12 |
13 | private static final long serialVersionUID = 1L;
14 |
15 | @Nonnull
16 | private final FilePath filePath;
17 |
18 | @Nullable
19 | private File file;
20 |
21 | @Inject
22 | RemoteFileUtils(@Nonnull final FilePath filePath) {
23 | this.filePath = filePath;
24 | }
25 |
26 | @Nonnull
27 | public File getRemoteFile(@Nonnull String pathToRemoteFile) {
28 | if (file == null) {
29 | file = new File(filePath.child(pathToRemoteFile).getRemote());
30 | }
31 |
32 | return file;
33 | }
34 |
35 | @Nonnull
36 | public String getFileName(@Nonnull String pathToRemoveFile) {
37 | return getRemoteFile(pathToRemoveFile).getName();
38 | }
39 |
40 | public long getFileSize(@Nonnull String pathToRemoveFile) {
41 | return getRemoteFile(pathToRemoveFile).length();
42 | }
43 |
44 | @Nonnull
45 | public String getContentType(@Nonnull String pathToApp) {
46 | if (pathToApp.endsWith(".apk") || pathToApp.endsWith(".aab")) return "application/vnd.android.package-archive";
47 | if (pathToApp.endsWith(".msi")) return "application/x-msi";
48 | if (pathToApp.endsWith(".plist")) return "application/xml";
49 | if (pathToApp.endsWith(".aetx")) return "application/c-x509-ca-cert";
50 | if (pathToApp.endsWith(".cer")) return "application/pkix-cert";
51 | if (pathToApp.endsWith("xap")) return "application/x-silverlight-app";
52 | if (pathToApp.endsWith(".appx")) return "application/x-appx";
53 | if (pathToApp.endsWith(".appxbundle")) return "application/x-appxbundle";
54 | if (pathToApp.endsWith(".appxupload") || pathToApp.endsWith(".appxsym")) return "application/x-appxupload";
55 | if (pathToApp.endsWith(".msix")) return "application/x-msix";
56 | if (pathToApp.endsWith(".msixbundle")) return "application/x-msixbundle";
57 | if (pathToApp.endsWith(".msixupload") || pathToApp.endsWith(".msixsym")) return "application/x-msixupload";
58 |
59 | // Otherwise
60 | return "application/octet-stream";
61 | }
62 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/validator/PathToAppValidatorTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import static com.google.common.truth.Truth.assertThat;
7 |
8 | public class PathToAppValidatorTest {
9 |
10 | private PathToAppValidator validator;
11 |
12 | @Before
13 | public void setUp() {
14 | validator = new PathToAppValidator();
15 | }
16 |
17 | @Test
18 | public void should_ReturnFalse_When_PathIsAbsolute_Windows() {
19 | // Given
20 | final String value = "C:\\\\windows\\path\\to\\app.ipa";
21 |
22 | // When
23 | final boolean result = validator.isValid(value);
24 |
25 | // Then
26 | assertThat(result).isFalse();
27 | }
28 |
29 | @Test
30 | public void should_ReturnFalse_When_PathIsAbsolute_UnixLike() {
31 | // Given
32 | final String value = "/unix-like/path/to/app.apk";
33 |
34 | // When
35 | final boolean result = validator.isValid(value);
36 |
37 | // Then
38 | assertThat(result).isFalse();
39 | }
40 |
41 | @Test
42 | public void should_ReturnTrue_When_PathIsRelative_Windows() {
43 | // Given
44 | final String value = "path\\to\\app.ipa";
45 |
46 | // When
47 | final boolean result = validator.isValid(value);
48 |
49 | // Then
50 | assertThat(result).isTrue();
51 | }
52 |
53 | @Test
54 | public void should_ReturnTrue_When_PathIsRelative_UnixLike() {
55 | // Given
56 | final String value = "path/to/app.apk";
57 |
58 | // When
59 | final boolean result = validator.isValid(value);
60 |
61 | // Then
62 | assertThat(result).isTrue();
63 | }
64 |
65 | @Test
66 | public void should_ReturnTrue_When_PathContainsEnvVars() {
67 | // Given
68 | final String value = "path/to/app-${BUILD_NUMBER}.apk";
69 |
70 | // When
71 | final boolean result = validator.isValid(value);
72 |
73 | // Then
74 | assertThat(result).isTrue();
75 | }
76 |
77 | @Test
78 | public void should_ReturnWarning_When_PathStartsWithEnvVars() {
79 | // Given
80 | final String value = "${SOME_ENV_VAR}.apk";
81 |
82 | // When
83 | final boolean result = validator.isValid(value);
84 |
85 | // Then
86 | assertThat(result).isTrue();
87 | }
88 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/validator/PathToDebugSymbolsValidatorTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import static com.google.common.truth.Truth.assertThat;
7 |
8 | public class PathToDebugSymbolsValidatorTest {
9 |
10 | private PathToDebugSymbolsValidator validator;
11 |
12 | @Before
13 | public void setUp() {
14 | validator = new PathToDebugSymbolsValidator();
15 | }
16 |
17 | @Test
18 | public void should_ReturnFalse_When_PathIsAbsolute_Windows() {
19 | // Given
20 | final String value = "C:\\\\windows\\path\\to\\symbols.zip";
21 |
22 | // When
23 | final boolean result = validator.isValid(value);
24 |
25 | // Then
26 | assertThat(result).isFalse();
27 | }
28 |
29 | @Test
30 | public void should_ReturnFalse_When_PathIsAbsolute_UnixLike() {
31 | // Given
32 | final String value = "/unix-like/path/to/mappings.txt";
33 |
34 | // When
35 | final boolean result = validator.isValid(value);
36 |
37 | // Then
38 | assertThat(result).isFalse();
39 | }
40 |
41 | @Test
42 | public void should_ReturnTrue_When_PathIsRelative_Windows() {
43 | // Given
44 | final String value = "path\\to\\symbols.zip";
45 |
46 | // When
47 | final boolean result = validator.isValid(value);
48 |
49 | // Then
50 | assertThat(result).isTrue();
51 | }
52 |
53 | @Test
54 | public void should_ReturnTrue_When_PathIsRelative_UnixLike() {
55 | // Given
56 | final String value = "path/to/mappings.txt";
57 |
58 | // When
59 | final boolean result = validator.isValid(value);
60 |
61 | // Then
62 | assertThat(result).isTrue();
63 | }
64 |
65 | @Test
66 | public void should_ReturnTrue_When_PathContainsEnvVars() {
67 | // Given
68 | final String value = "path/to/mapping-${BUILD_NUMBER}.txt";
69 |
70 | // When
71 | final boolean result = validator.isValid(value);
72 |
73 | // Then
74 | assertThat(result).isTrue();
75 | }
76 |
77 | @Test
78 | public void should_ReturnWarning_When_PathStartsWithEnvVars() {
79 | // Given
80 | final String value = "${SOME_ENV_VAR}.apk";
81 |
82 | // When
83 | final boolean result = validator.isValid(value);
84 |
85 | // Then
86 | assertThat(result).isTrue();
87 | }
88 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/validator/PathToReleaseNotesValidatorTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import static com.google.common.truth.Truth.assertThat;
7 |
8 | public class PathToReleaseNotesValidatorTest {
9 |
10 | private PathToReleaseNotesValidator validator;
11 |
12 | @Before
13 | public void setUp() {
14 | validator = new PathToReleaseNotesValidator();
15 | }
16 |
17 | @Test
18 | public void should_ReturnFalse_When_PathIsAbsolute_Windows() {
19 | // Given
20 | final String value = "C:\\\\windows\\path\\to\\release-notes.md";
21 |
22 | // When
23 | final boolean result = validator.isValid(value);
24 |
25 | // Then
26 | assertThat(result).isFalse();
27 | }
28 |
29 | @Test
30 | public void should_ReturnFalse_When_PathIsAbsolute_UnixLike() {
31 | // Given
32 | final String value = "/unix-like/path/to/release-notes.md";
33 |
34 | // When
35 | final boolean result = validator.isValid(value);
36 |
37 | // Then
38 | assertThat(result).isFalse();
39 | }
40 |
41 | @Test
42 | public void should_ReturnTrue_When_PathIsRelative_Windows() {
43 | // Given
44 | final String value = "path\\to\\release-notes.md";
45 |
46 | // When
47 | final boolean result = validator.isValid(value);
48 |
49 | // Then
50 | assertThat(result).isTrue();
51 | }
52 |
53 | @Test
54 | public void should_ReturnTrue_When_PathIsRelative_UnixLike() {
55 | // Given
56 | final String value = "path/to/release-notes.md";
57 |
58 | // When
59 | final boolean result = validator.isValid(value);
60 |
61 | // Then
62 | assertThat(result).isTrue();
63 | }
64 |
65 | @Test
66 | public void should_ReturnTrue_When_PathContainsEnvVars() {
67 | // Given
68 | final String value = "path/to/release-notes-${BUILD_NUMBER}.md";
69 |
70 | // When
71 | final boolean result = validator.isValid(value);
72 |
73 | // Then
74 | assertThat(result).isTrue();
75 | }
76 |
77 | @Test
78 | public void should_ReturnWarning_When_PathStartsWithEnvVars() {
79 | // Given
80 | final String value = "${SOME_ENV_VAR}.apk";
81 |
82 | // When
83 | final boolean result = validator.isValid(value);
84 |
85 | // Then
86 | assertThat(result).isTrue();
87 | }
88 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/task/internal/UpdateReleaseTask.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.task.internal;
2 |
3 | import hudson.model.TaskListener;
4 | import io.jenkins.plugins.appcenter.AppCenterException;
5 | import io.jenkins.plugins.appcenter.AppCenterLogger;
6 | import io.jenkins.plugins.appcenter.api.AppCenterServiceFactory;
7 | import io.jenkins.plugins.appcenter.model.appcenter.UpdateReleaseUploadRequest;
8 | import io.jenkins.plugins.appcenter.task.request.UploadRequest;
9 |
10 | import javax.annotation.Nonnull;
11 | import javax.inject.Inject;
12 | import javax.inject.Singleton;
13 | import java.io.PrintStream;
14 | import java.util.concurrent.CompletableFuture;
15 |
16 | import static java.util.Objects.requireNonNull;
17 |
18 | @Singleton
19 | public final class UpdateReleaseTask implements AppCenterTask, AppCenterLogger {
20 |
21 | private static final long serialVersionUID = 1L;
22 |
23 | @Nonnull
24 | private final TaskListener taskListener;
25 | @Nonnull
26 | private final AppCenterServiceFactory factory;
27 |
28 | @Inject
29 | UpdateReleaseTask(@Nonnull final TaskListener taskListener,
30 | @Nonnull final AppCenterServiceFactory factory) {
31 | this.taskListener = taskListener;
32 | this.factory = factory;
33 | }
34 |
35 | @Nonnull
36 | @Override
37 | public CompletableFuture execute(@Nonnull UploadRequest request) {
38 | return updateRelease(request);
39 | }
40 |
41 | @Nonnull
42 | private CompletableFuture updateRelease(@Nonnull UploadRequest request) {
43 | final String uploadId = requireNonNull(request.uploadId, "uploadId cannot be null");
44 |
45 | log("Updating release.");
46 |
47 | final CompletableFuture future = new CompletableFuture<>();
48 |
49 | final UpdateReleaseUploadRequest updateReleaseUploadRequest = new UpdateReleaseUploadRequest(UpdateReleaseUploadRequest.StatusEnum.uploadFinished);
50 |
51 | factory.createAppCenterService()
52 | .updateReleaseUpload(request.ownerName, request.appName, uploadId, updateReleaseUploadRequest)
53 | .whenComplete((updateReleaseUploadResponse, throwable) -> {
54 | if (throwable != null) {
55 | final AppCenterException exception = logFailure("Updating release unsuccessful", throwable);
56 | future.completeExceptionally(exception);
57 | } else {
58 | log("Updating release release successful.");
59 | future.complete(request);
60 | }
61 | });
62 |
63 | return future;
64 | }
65 |
66 | @Override
67 | public PrintStream getLogger() {
68 | return taskListener.getLogger();
69 | }
70 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/validator/UsernameValidatorTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.validator;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import static com.google.common.truth.Truth.assertThat;
7 |
8 | public class UsernameValidatorTest {
9 |
10 | private Validator validator;
11 |
12 | @Before
13 | public void setUp() {
14 | validator = new UsernameValidator();
15 | }
16 |
17 | @Test
18 | public void should_ReturnTrue_When_ApiTokenIsLowerCaseLettersOnly() {
19 | // Given
20 | final String value = "johndoe";
21 |
22 | // When
23 | final boolean result = validator.isValid(value);
24 |
25 | // Then
26 | assertThat(result).isTrue();
27 | }
28 |
29 | @Test
30 | public void should_ReturnTrue_When_ApiTokenIsUpperCaseLettersOnly() {
31 | // Given
32 | final String value = "JOHNDOE";
33 |
34 | // When
35 | final boolean result = validator.isValid(value);
36 |
37 | // Then
38 | assertThat(result).isTrue();
39 | }
40 |
41 | @Test
42 | public void should_ReturnTrue_When_ApiTokenIsNumbersOnly() {
43 | // Given
44 | final String value = "1234567890";
45 |
46 | // When
47 | final boolean result = validator.isValid(value);
48 |
49 | // Then
50 | assertThat(result).isTrue();
51 | }
52 |
53 | @Test
54 | public void should_ReturnTrue_When_ApiTokenIsMixOfNumbersAndLetters() {
55 | // Given
56 | final String value = "j0hNDo3";
57 |
58 | // When
59 | final boolean result = validator.isValid(value);
60 |
61 | // Then
62 | assertThat(result).isTrue();
63 | }
64 |
65 | @Test
66 | public void should_ReturnTrue_When_ApiTokenIsMixOfNumbersAndLettersAndDashesAndDotsAndUnderscores() {
67 | // Given
68 | final String value = "j0hNDo3-._";
69 |
70 | // When
71 | final boolean result = validator.isValid(value);
72 |
73 | // Then
74 | assertThat(result).isTrue();
75 | }
76 |
77 | @Test
78 | public void should_ReturnFalse_When_ApiTokenIsBlank() {
79 | // Given
80 | final String value = " ";
81 |
82 | // When
83 | final boolean result = validator.isValid(value);
84 |
85 | // Then
86 | assertThat(result).isFalse();
87 | }
88 |
89 | @Test
90 | public void should_ReturnFalse_When_ApiTokenContainsExclamation() {
91 | // Given
92 | final String value = "johndoe!";
93 |
94 | // When
95 | final boolean result = validator.isValid(value);
96 |
97 | // Then
98 | assertThat(result).isFalse();
99 | }
100 | }
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/appcenter/AppCenterRecorder/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
16 |
17 |
18 |
19 |
21 |
22 |
23 |
24 |
26 |
27 |
28 |
29 |
31 |
32 |
33 |
34 |
36 |
37 |
38 |
39 |
41 |
42 |
43 |
44 |
46 |
47 |
48 |
49 |
51 |
52 |
53 |
54 |
56 |
57 |
58 |
59 |
60 |
61 |
63 |
64 |
65 |
66 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/task/UploadTask.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.task;
2 |
3 | import io.jenkins.plugins.appcenter.AppCenterException;
4 | import io.jenkins.plugins.appcenter.task.internal.*;
5 | import io.jenkins.plugins.appcenter.task.request.UploadRequest;
6 | import jenkins.security.MasterToSlaveCallable;
7 |
8 | import javax.inject.Inject;
9 | import javax.inject.Singleton;
10 | import java.util.concurrent.CompletableFuture;
11 |
12 | @Singleton
13 | public final class UploadTask extends MasterToSlaveCallable {
14 |
15 | private final PrerequisitesTask prerequisitesTask;
16 | private final CreateUploadResourceTask createUploadResource;
17 | private final SetMetadataTask setMetadataTask;
18 | private final UploadAppToResourceTask uploadAppToResource;
19 | private final FinishReleaseTask finishReleaseTask;
20 | private final UpdateReleaseTask updateReleaseTask;
21 | private final PollForReleaseTask pollForReleaseTask;
22 | private final DistributeResourceTask distributeResource;
23 | private final UploadRequest originalRequest;
24 |
25 | @Inject
26 | UploadTask(final PrerequisitesTask prerequisitesTask,
27 | final CreateUploadResourceTask createUploadResource,
28 | final SetMetadataTask setMetadataTask,
29 | final UploadAppToResourceTask uploadAppToResource,
30 | final FinishReleaseTask finishReleaseTask,
31 | final UpdateReleaseTask updateReleaseTask,
32 | final PollForReleaseTask pollForReleaseTask,
33 | final DistributeResourceTask distributeResource,
34 | final UploadRequest request) {
35 | this.prerequisitesTask = prerequisitesTask;
36 | this.createUploadResource = createUploadResource;
37 | this.setMetadataTask = setMetadataTask;
38 | this.uploadAppToResource = uploadAppToResource;
39 | this.finishReleaseTask = finishReleaseTask;
40 | this.updateReleaseTask = updateReleaseTask;
41 | this.pollForReleaseTask = pollForReleaseTask;
42 | this.distributeResource = distributeResource;
43 | this.originalRequest = request;
44 | }
45 |
46 | @Override
47 | public Boolean call() {
48 | final CompletableFuture future = new CompletableFuture<>();
49 |
50 | prerequisitesTask.execute(originalRequest)
51 | .thenCompose(createUploadResource::execute)
52 | .thenCompose(setMetadataTask::execute)
53 | .thenCompose(uploadAppToResource::execute)
54 | .thenCompose(finishReleaseTask::execute)
55 | .thenCompose(updateReleaseTask::execute)
56 | .thenCompose(pollForReleaseTask::execute)
57 | .thenCompose(distributeResource::execute)
58 | .whenComplete((uploadRequest, throwable) -> {
59 | if (throwable != null) {
60 | future.completeExceptionally(throwable);
61 | } else {
62 | future.complete(true);
63 | }
64 | })
65 | .join();
66 |
67 | return future.join();
68 | }
69 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/api/AppCenterService.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.api;
2 |
3 | import io.jenkins.plugins.appcenter.model.appcenter.*;
4 | import okhttp3.RequestBody;
5 | import okhttp3.ResponseBody;
6 | import retrofit2.http.*;
7 |
8 | import javax.annotation.Nonnull;
9 | import java.util.concurrent.CompletableFuture;
10 |
11 | public interface AppCenterService {
12 |
13 | @POST("v0.1/apps/{owner_name}/{app_name}/uploads/releases")
14 | CompletableFuture releaseUploadsCreate(
15 | @Path("owner_name") @Nonnull String user,
16 | @Path("app_name") @Nonnull String appName,
17 | @Body @Nonnull ReleaseUploadBeginRequest releaseUploadBeginRequest);
18 |
19 | @POST
20 | CompletableFuture setMetaData(@Url @Nonnull String url);
21 |
22 | @Headers("Content-Type: application/octet-stream")
23 | @POST
24 | CompletableFuture uploadApp(@Url @Nonnull String url, @Body @Nonnull RequestBody file);
25 |
26 | @POST
27 | CompletableFuture finishRelease(@Url @Nonnull String url);
28 |
29 | @PATCH("v0.1/apps/{owner_name}/{app_name}/uploads/releases/{upload_id}")
30 | CompletableFuture updateReleaseUpload(
31 | @Path("owner_name") @Nonnull String user,
32 | @Path("app_name") @Nonnull String appName,
33 | @Path("upload_id") @Nonnull String uploadId,
34 | @Body @Nonnull UpdateReleaseUploadRequest updateReleaseUploadRequest);
35 |
36 | @GET("v0.1/apps/{owner_name}/{app_name}/uploads/releases/{upload_id}")
37 | CompletableFuture pollForRelease(
38 | @Path("owner_name") @Nonnull String user,
39 | @Path("app_name") @Nonnull String appName,
40 | @Path("upload_id") @Nonnull String uploadId);
41 |
42 | @PATCH("v0.1/apps/{owner_name}/{app_name}/release_uploads/{upload_id}")
43 | CompletableFuture releaseUploadsComplete(
44 | @Path("owner_name") @Nonnull String user,
45 | @Path("app_name") @Nonnull String appName,
46 | @Path("upload_id") @Nonnull String uploadId,
47 | @Body @Nonnull ReleaseUploadEndRequest releaseUploadEndRequest);
48 |
49 | @PATCH("v0.1/apps/{owner_name}/{app_name}/releases/{release_id}")
50 | CompletableFuture releasesUpdate(
51 | @Path("owner_name") @Nonnull String user,
52 | @Path("app_name") @Nonnull String appName,
53 | @Path("release_id") @Nonnull Integer releaseId,
54 | @Body @Nonnull ReleaseUpdateRequest releaseUpdateRequest);
55 |
56 | @POST("v0.1/apps/{owner_name}/{app_name}/symbol_uploads")
57 | CompletableFuture symbolUploadsCreate(
58 | @Path("owner_name") @Nonnull String user,
59 | @Path("app_name") @Nonnull String appName,
60 | @Body @Nonnull SymbolUploadBeginRequest symbolUploadBeginRequest);
61 |
62 | @PATCH("v0.1/apps/{owner_name}/{app_name}/symbol_uploads/{symbol_upload_id}")
63 | CompletableFuture symbolUploadsComplete(
64 | @Path("owner_name") @Nonnull String user,
65 | @Path("app_name") @Nonnull String appName,
66 | @Path("symbol_upload_id") @Nonnull String uploadId,
67 | @Body @Nonnull SymbolUploadEndRequest symbolUploadEndRequest);
68 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/task/internal/SetMetadataTask.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.task.internal;
2 |
3 | import hudson.model.TaskListener;
4 | import io.jenkins.plugins.appcenter.AppCenterException;
5 | import io.jenkins.plugins.appcenter.AppCenterLogger;
6 | import io.jenkins.plugins.appcenter.api.AppCenterServiceFactory;
7 | import io.jenkins.plugins.appcenter.task.request.UploadRequest;
8 | import io.jenkins.plugins.appcenter.util.RemoteFileUtils;
9 |
10 | import javax.annotation.Nonnull;
11 | import javax.inject.Inject;
12 | import javax.inject.Singleton;
13 | import java.io.PrintStream;
14 | import java.util.concurrent.CompletableFuture;
15 |
16 | import static java.util.Objects.requireNonNull;
17 |
18 | @Singleton
19 | public final class SetMetadataTask implements AppCenterTask, AppCenterLogger {
20 |
21 | private static final long serialVersionUID = 1L;
22 |
23 | @Nonnull
24 | private final TaskListener taskListener;
25 | @Nonnull
26 | private final AppCenterServiceFactory factory;
27 | @Nonnull
28 | private final RemoteFileUtils remoteFileUtils;
29 |
30 | @Inject
31 | SetMetadataTask(@Nonnull final TaskListener taskListener,
32 | @Nonnull final AppCenterServiceFactory factory,
33 | @Nonnull final RemoteFileUtils remoteFileUtils) {
34 | this.taskListener = taskListener;
35 | this.factory = factory;
36 | this.remoteFileUtils = remoteFileUtils;
37 | }
38 |
39 | @Nonnull
40 | @Override
41 | public CompletableFuture execute(@Nonnull UploadRequest request) {
42 | return setMetadata(request);
43 | }
44 |
45 | @Nonnull
46 | private CompletableFuture setMetadata(@Nonnull UploadRequest request) {
47 | final String uploadDomain = requireNonNull(request.uploadDomain, "uploadDomain cannot be null");
48 | final String packageAssetId = requireNonNull(request.packageAssetId, "packageAssetId cannot be null");
49 | final String token = requireNonNull(request.token, "token cannot be null");
50 |
51 | log("Setting metadata.");
52 |
53 | final CompletableFuture future = new CompletableFuture<>();
54 |
55 | final String url = getUrl(request.pathToApp, uploadDomain, packageAssetId, token);
56 |
57 | factory.createAppCenterService()
58 | .setMetaData(url)
59 | .whenComplete((setMetadataResponse, throwable) -> {
60 | if (throwable != null) {
61 | final AppCenterException exception = logFailure("Setting metadata unsuccessful", throwable);
62 | future.completeExceptionally(exception);
63 | } else {
64 | log("Setting metadata successful.");
65 |
66 | final UploadRequest uploadRequest = request.newBuilder()
67 | .setChunkSize(setMetadataResponse.chunk_size)
68 | .build();
69 | future.complete(uploadRequest);
70 | }
71 | });
72 |
73 | return future;
74 | }
75 |
76 |
77 | @Nonnull
78 | private String getUrl(@Nonnull String pathToApp, @Nonnull String uploadDomain, @Nonnull String packageAssetId, @Nonnull String token) {
79 | final String fileName = remoteFileUtils.getFileName(pathToApp);
80 | final long fileSize = remoteFileUtils.getFileSize(pathToApp);
81 | final String contentType = remoteFileUtils.getContentType(pathToApp);
82 |
83 | return String.format("%1$s/upload/set_metadata/%2$s?file_name=%3$s&file_size=%4$d&token=%5$s&content_type=%6$s", uploadDomain, packageAssetId, fileName, fileSize, token, contentType);
84 | }
85 |
86 | @Override
87 | public PrintStream getLogger() {
88 | return taskListener.getLogger();
89 | }
90 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/model/appcenter/SymbolUpload.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.model.appcenter;
2 |
3 | import javax.annotation.Nonnull;
4 | import javax.annotation.Nullable;
5 | import java.util.List;
6 | import java.util.Objects;
7 |
8 | public final class SymbolUpload {
9 | @Nonnull
10 | public final String symbol_upload_id;
11 |
12 | @Nonnull
13 | public final String app_id;
14 |
15 | @Nullable
16 | public final SymbolUploadUserInfo user;
17 |
18 | @Nonnull
19 | public final StatusEnum status;
20 |
21 | @Nonnull
22 | public final SymbolTypeEnum symbol_type;
23 |
24 | @Nullable
25 | public final List symbols_uploaded;
26 |
27 | @Nullable
28 | public final OriginEnum origin;
29 |
30 | @Nullable
31 | public final String file_name;
32 |
33 | @Nullable
34 | public final Integer file_size;
35 |
36 | @Nullable
37 | public final String timestamp;
38 |
39 | public SymbolUpload(@Nonnull String symbolUploadId, @Nonnull String appId, @Nullable SymbolUploadUserInfo user, @Nonnull StatusEnum status, @Nonnull SymbolTypeEnum symbolType, @Nullable List symbolsUploaded, @Nullable OriginEnum origin, @Nullable String fileName, @Nullable Integer fileSize, @Nullable String timestamp) {
40 | this.symbol_upload_id = symbolUploadId;
41 | this.app_id = appId;
42 | this.user = user;
43 | this.status = status;
44 | this.symbol_type = symbolType;
45 | this.symbols_uploaded = symbolsUploaded;
46 | this.origin = origin;
47 | this.file_name = fileName;
48 | this.file_size = fileSize;
49 | this.timestamp = timestamp;
50 | }
51 |
52 | @Override
53 | public String toString() {
54 | return "SymbolUpload{" +
55 | "symbol_upload_id='" + symbol_upload_id + '\'' +
56 | ", app_id='" + app_id + '\'' +
57 | ", user=" + user +
58 | ", status=" + status +
59 | ", symbol_type=" + symbol_type +
60 | ", symbols_uploaded=" + symbols_uploaded +
61 | ", origin=" + origin +
62 | ", file_name='" + file_name + '\'' +
63 | ", file_size=" + file_size +
64 | ", timestamp='" + timestamp + '\'' +
65 | '}';
66 | }
67 |
68 | @Override
69 | public boolean equals(Object o) {
70 | if (this == o) return true;
71 | if (o == null || getClass() != o.getClass()) return false;
72 | SymbolUpload that = (SymbolUpload) o;
73 | return symbol_upload_id.equals(that.symbol_upload_id) &&
74 | app_id.equals(that.app_id) &&
75 | Objects.equals(user, that.user) &&
76 | status == that.status &&
77 | symbol_type == that.symbol_type &&
78 | Objects.equals(symbols_uploaded, that.symbols_uploaded) &&
79 | origin == that.origin &&
80 | Objects.equals(file_name, that.file_name) &&
81 | Objects.equals(file_size, that.file_size) &&
82 | Objects.equals(timestamp, that.timestamp);
83 | }
84 |
85 | @Override
86 | public int hashCode() {
87 | return Objects.hash(symbol_upload_id, app_id, user, status, symbol_type, symbols_uploaded, origin, file_name, file_size, timestamp);
88 | }
89 |
90 | public enum StatusEnum {
91 | created,
92 | committed,
93 | aborted,
94 | processing,
95 | indexed,
96 | failed
97 | }
98 |
99 | public enum SymbolTypeEnum {
100 | Apple,
101 | JavaScript,
102 | Breakpad,
103 | AndroidProguard,
104 | UWP
105 | }
106 |
107 | public enum OriginEnum {
108 | User,
109 | System
110 | }
111 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/task/internal/UpdateReleaseTaskTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.task.internal;
2 |
3 | import hudson.ProxyConfiguration;
4 | import hudson.model.TaskListener;
5 | import hudson.util.Secret;
6 | import io.jenkins.plugins.appcenter.AppCenterException;
7 | import io.jenkins.plugins.appcenter.api.AppCenterServiceFactory;
8 | import io.jenkins.plugins.appcenter.task.request.UploadRequest;
9 | import okhttp3.mockwebserver.MockResponse;
10 | import okhttp3.mockwebserver.MockWebServer;
11 | import org.junit.Before;
12 | import org.junit.Rule;
13 | import org.junit.Test;
14 | import org.junit.function.ThrowingRunnable;
15 | import org.junit.runner.RunWith;
16 | import org.mockito.Mock;
17 | import org.mockito.junit.MockitoJUnitRunner;
18 |
19 | import java.io.PrintStream;
20 | import java.util.concurrent.ExecutionException;
21 |
22 | import static com.google.common.truth.Truth.assertThat;
23 | import static org.junit.Assert.assertThrows;
24 | import static org.mockito.BDDMockito.given;
25 |
26 | @RunWith(MockitoJUnitRunner.class)
27 | public class UpdateReleaseTaskTest {
28 |
29 | @Rule
30 | public MockWebServer mockWebServer = new MockWebServer();
31 |
32 | @Mock
33 | TaskListener mockTaskListener;
34 |
35 | @Mock
36 | PrintStream mockLogger;
37 |
38 | @Mock
39 | ProxyConfiguration mockProxyConfig;
40 |
41 | private UploadRequest baseRequest;
42 |
43 | private UpdateReleaseTask task;
44 |
45 | @Before
46 | public void setUp() {
47 | baseRequest = new UploadRequest.Builder()
48 | .setOwnerName("owner-name")
49 | .setAppName("app-name")
50 | .setPathToApp("path-to-app")
51 | .build();
52 | given(mockTaskListener.getLogger()).willReturn(mockLogger);
53 | final AppCenterServiceFactory factory = new AppCenterServiceFactory(Secret.fromString("secret-token"), mockWebServer.url("/").toString(), mockProxyConfig);
54 | task = new UpdateReleaseTask(mockTaskListener, factory);
55 | }
56 |
57 | @Test
58 | public void should_ReturnException_When_UploadIdIsMissing() {
59 | // Given
60 | final UploadRequest uploadRequest = baseRequest.newBuilder()
61 | .build();
62 |
63 | // When
64 | final ThrowingRunnable throwingRunnable = () -> task.execute(uploadRequest).get();
65 |
66 | // Then
67 | final NullPointerException exception = assertThrows(NullPointerException.class, throwingRunnable);
68 | assertThat(exception).hasMessageThat().contains("uploadId cannot be null");
69 | }
70 |
71 | @Test
72 | public void should_ReturnResponse_When_RequestIsSuccessful() throws Exception {
73 | // Given
74 | final UploadRequest uploadRequest = baseRequest.newBuilder()
75 | .setUploadId("upload_id")
76 | .build();
77 | mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody("{" +
78 | "\"id\": \"upload_id\",\n" +
79 | "\"upload_status\": \"uploadFinished\"\n" +
80 | "}")
81 | );
82 |
83 | // When
84 | final UploadRequest actual = task.execute(uploadRequest).get();
85 |
86 | // Then
87 | assertThat(actual)
88 | .isEqualTo(uploadRequest);
89 | }
90 |
91 | @Test
92 | public void should_ReturnException_When_RequestIsUnSuccessful() {
93 | // Given
94 | final UploadRequest uploadRequest = baseRequest.newBuilder()
95 | .setUploadId("upload_id")
96 | .build();
97 | mockWebServer.enqueue(new MockResponse().setResponseCode(400));
98 |
99 | // When
100 | final ThrowingRunnable throwingRunnable = () -> task.execute(uploadRequest).get();
101 |
102 | // Then
103 | final ExecutionException exception = assertThrows(ExecutionException.class, throwingRunnable);
104 | assertThat(exception).hasCauseThat().isInstanceOf(AppCenterException.class);
105 | assertThat(exception).hasCauseThat().hasMessageThat().contains("Updating release unsuccessful");
106 | }
107 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/ConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter;
2 |
3 | import com.gargoylesoftware.htmlunit.html.HtmlForm;
4 | import hudson.model.FreeStyleProject;
5 | import org.junit.Before;
6 | import org.junit.ClassRule;
7 | import org.junit.Test;
8 | import org.jvnet.hudson.test.JenkinsRule;
9 |
10 | public class ConfigurationTest {
11 |
12 | @ClassRule
13 | public static JenkinsRule jenkinsRule = new JenkinsRule();
14 |
15 | private FreeStyleProject freeStyleProject;
16 |
17 | @Before
18 | public void setUp() throws Exception {
19 | freeStyleProject = jenkinsRule.createFreeStyleProject();
20 | }
21 |
22 | @Test
23 | public void should_Configure_RequiredParameters_ViaWebForm() throws Exception {
24 | // Given
25 | final AppCenterRecorder appCenterRecorder = new AppCenterRecorder("at-this-moment-you-should-be-with-us", "janes-addiction", "ritual-de-lo-habitual", "three/days/xiola.apk", "casey, niccoli");
26 | freeStyleProject.getPublishersList().add(appCenterRecorder);
27 |
28 | final HtmlForm htmlForm = jenkinsRule.createWebClient().getPage(freeStyleProject, "configure").getFormByName("config");
29 | jenkinsRule.submit(htmlForm);
30 |
31 | // When
32 | final AppCenterRecorder configuredAppCenterRecorder = freeStyleProject.getPublishersList().get(AppCenterRecorder.class);
33 |
34 | // Then
35 | jenkinsRule.assertEqualDataBoundBeans(appCenterRecorder, configuredAppCenterRecorder);
36 | }
37 |
38 | @Test
39 | public void should_Configure_OptionalReleaseNotes_ViaWebForm() throws Exception {
40 | // Given
41 | final AppCenterRecorder appCenterRecorder = new AppCenterRecorder("at-this-moment-you-should-be-with-us", "janes-addiction", "ritual-de-lo-habitual", "three/days/xiola.apk", "casey, niccoli");
42 | appCenterRecorder.setReleaseNotes("I miss you my dear Xiola");
43 | freeStyleProject.getPublishersList().add(appCenterRecorder);
44 |
45 | final HtmlForm htmlForm = jenkinsRule.createWebClient().getPage(freeStyleProject, "configure").getFormByName("config");
46 | jenkinsRule.submit(htmlForm);
47 |
48 | // When
49 | final AppCenterRecorder configuredAppCenterRecorder = freeStyleProject.getPublishersList().get(AppCenterRecorder.class);
50 |
51 | // Then
52 | jenkinsRule.assertEqualDataBoundBeans(appCenterRecorder, configuredAppCenterRecorder);
53 | }
54 |
55 | @Test
56 | public void should_Configure_OptionalNotifyTesters_ViaWebForm() throws Exception {
57 | // Given
58 | final AppCenterRecorder appCenterRecorder = new AppCenterRecorder("at-this-moment-you-should-be-with-us", "janes-addiction", "ritual-de-lo-habitual", "three/days/xiola.apk", "casey, niccoli");
59 | appCenterRecorder.setNotifyTesters(false);
60 | freeStyleProject.getPublishersList().add(appCenterRecorder);
61 |
62 | final HtmlForm htmlForm = jenkinsRule.createWebClient().getPage(freeStyleProject, "configure").getFormByName("config");
63 | jenkinsRule.submit(htmlForm);
64 |
65 | // When
66 | final AppCenterRecorder configuredAppCenterRecorder = freeStyleProject.getPublishersList().get(AppCenterRecorder.class);
67 |
68 | // Then
69 | jenkinsRule.assertEqualDataBoundBeans(appCenterRecorder, configuredAppCenterRecorder);
70 | }
71 |
72 | @Test
73 | public void should_Configure_OptionalDebugSymbols_ViaWebForm() throws Exception {
74 | // Given
75 | final AppCenterRecorder appCenterRecorder = new AppCenterRecorder("at-this-moment-you-should-be-with-us", "janes-addiction", "ritual-de-lo-habitual", "three/days/xiola.apk", "casey, niccoli");
76 | appCenterRecorder.setPathToDebugSymbols("path/to/linear-notes.txt");
77 | freeStyleProject.getPublishersList().add(appCenterRecorder);
78 |
79 | final HtmlForm htmlForm = jenkinsRule.createWebClient().getPage(freeStyleProject, "configure").getFormByName("config");
80 | jenkinsRule.submit(htmlForm);
81 |
82 | // When
83 | final AppCenterRecorder configuredAppCenterRecorder = freeStyleProject.getPublishersList().get(AppCenterRecorder.class);
84 |
85 | // Then
86 | jenkinsRule.assertEqualDataBoundBeans(appCenterRecorder, configuredAppCenterRecorder);
87 | }
88 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/task/internal/FinishReleaseTask.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.task.internal;
2 |
3 | import hudson.model.TaskListener;
4 | import io.jenkins.plugins.appcenter.AppCenterException;
5 | import io.jenkins.plugins.appcenter.AppCenterLogger;
6 | import io.jenkins.plugins.appcenter.api.AppCenterServiceFactory;
7 | import io.jenkins.plugins.appcenter.model.appcenter.SymbolUploadEndRequest;
8 | import io.jenkins.plugins.appcenter.task.request.UploadRequest;
9 |
10 | import javax.annotation.Nonnull;
11 | import javax.inject.Inject;
12 | import javax.inject.Singleton;
13 | import java.io.PrintStream;
14 | import java.util.concurrent.CompletableFuture;
15 |
16 | import static java.util.Objects.requireNonNull;
17 |
18 | @Singleton
19 | public final class FinishReleaseTask implements AppCenterTask, AppCenterLogger {
20 |
21 | private static final long serialVersionUID = 1L;
22 |
23 | @Nonnull
24 | private final TaskListener taskListener;
25 | @Nonnull
26 | private final AppCenterServiceFactory factory;
27 |
28 | @Inject
29 | FinishReleaseTask(@Nonnull final TaskListener taskListener,
30 | @Nonnull final AppCenterServiceFactory factory) {
31 | this.taskListener = taskListener;
32 | this.factory = factory;
33 | }
34 |
35 | @Nonnull
36 | @Override
37 | public CompletableFuture execute(@Nonnull UploadRequest request) {
38 |
39 |
40 | if (request.symbolUploadId == null) {
41 | return finishRelease(request);
42 | } else {
43 | return finishRelease(request)
44 | .thenCompose(this::finishSymbolRelease);
45 | }
46 | }
47 |
48 | @Nonnull
49 | private CompletableFuture finishRelease(@Nonnull UploadRequest request) {
50 | final String uploadDomain = requireNonNull(request.uploadDomain, "uploadDomain cannot be null");
51 | final String packageAssetId = requireNonNull(request.packageAssetId, "packageAssetId cannot be null");
52 | final String token = requireNonNull(request.token, "token cannot be null");
53 |
54 | log("Finishing release.");
55 |
56 | final CompletableFuture future = new CompletableFuture<>();
57 |
58 | final String url = getUrl(uploadDomain, packageAssetId, token);
59 |
60 | factory.createAppCenterService()
61 | .finishRelease(url)
62 | .whenComplete((finishReleaseResponse, throwable) -> {
63 | if (throwable != null) {
64 | final AppCenterException exception = logFailure("Finishing release unsuccessful", throwable);
65 | future.completeExceptionally(exception);
66 | } else {
67 | log("Finishing release successful.");
68 | future.complete(request);
69 | }
70 | });
71 |
72 | return future;
73 | }
74 |
75 | @Nonnull
76 | private String getUrl(@Nonnull String uploadDomain, @Nonnull String packageAssetId, @Nonnull String token) {
77 | return String.format("%1$s/upload/finished/%2$s?token=%3$s", uploadDomain, packageAssetId, token);
78 | }
79 |
80 | @Nonnull
81 | private CompletableFuture finishSymbolRelease(@Nonnull UploadRequest request) {
82 | final String symbolUploadId = requireNonNull(request.symbolUploadId, "symbolUploadId cannot be null");
83 |
84 | log("Finishing symbol release.");
85 |
86 | final CompletableFuture future = new CompletableFuture<>();
87 | final SymbolUploadEndRequest symbolUploadEndRequest = new SymbolUploadEndRequest(SymbolUploadEndRequest.StatusEnum.committed);
88 |
89 | factory.createAppCenterService()
90 | .symbolUploadsComplete(request.ownerName, request.appName, symbolUploadId, symbolUploadEndRequest)
91 | .whenComplete((symbolUploadEndResponse, throwable) -> {
92 | if (throwable != null) {
93 | final AppCenterException exception = logFailure("Finishing symbol release unsuccessful: ", throwable);
94 | future.completeExceptionally(exception);
95 | } else {
96 | log("Finishing symbol release successful.");
97 | future.complete(request);
98 | }
99 | });
100 |
101 | return future;
102 | }
103 |
104 | @Override
105 | public PrintStream getLogger() {
106 | return taskListener.getLogger();
107 | }
108 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/task/internal/PollForReleaseTask.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.task.internal;
2 |
3 | import hudson.model.TaskListener;
4 | import io.jenkins.plugins.appcenter.AppCenterException;
5 | import io.jenkins.plugins.appcenter.AppCenterLogger;
6 | import io.jenkins.plugins.appcenter.api.AppCenterServiceFactory;
7 | import io.jenkins.plugins.appcenter.task.request.UploadRequest;
8 |
9 | import javax.annotation.Nonnull;
10 | import javax.inject.Inject;
11 | import javax.inject.Singleton;
12 | import java.io.PrintStream;
13 | import java.util.concurrent.CompletableFuture;
14 | import java.util.concurrent.TimeUnit;
15 |
16 | import static java.util.Objects.requireNonNull;
17 |
18 | @Singleton
19 | public final class PollForReleaseTask implements AppCenterTask, AppCenterLogger {
20 |
21 | private static final long serialVersionUID = 1L;
22 |
23 | @Nonnull
24 | private final TaskListener taskListener;
25 | @Nonnull
26 | private final AppCenterServiceFactory factory;
27 |
28 | @Inject
29 | PollForReleaseTask(@Nonnull final TaskListener taskListener,
30 | @Nonnull final AppCenterServiceFactory factory) {
31 | this.taskListener = taskListener;
32 | this.factory = factory;
33 | }
34 |
35 | @Nonnull
36 | @Override
37 | public CompletableFuture execute(@Nonnull UploadRequest request) {
38 | return pollForRelease(request);
39 | }
40 |
41 | @Nonnull
42 | private CompletableFuture pollForRelease(@Nonnull UploadRequest request) {
43 | final String uploadId = requireNonNull(request.uploadId, "uploadId cannot be null");
44 |
45 | log("Polling for app release.");
46 |
47 | final CompletableFuture future = new CompletableFuture<>();
48 |
49 | poll(request, uploadId, future);
50 |
51 | return future;
52 | }
53 |
54 | private void poll(@Nonnull UploadRequest request, @Nonnull String uploadId, @Nonnull CompletableFuture future) {
55 | poll(request, uploadId, future, 0);
56 | }
57 |
58 | private void poll(@Nonnull UploadRequest request, @Nonnull String uploadId, @Nonnull CompletableFuture future, int timeoutExponent) {
59 | factory.createAppCenterService()
60 | .pollForRelease(request.ownerName, request.appName, uploadId)
61 | .whenComplete((pollForReleaseResponse, throwable) -> {
62 | if (throwable != null) {
63 | final AppCenterException exception = logFailure("Polling for app release unsuccessful", throwable);
64 | future.completeExceptionally(exception);
65 | } else {
66 | switch (pollForReleaseResponse.upload_status) {
67 | case uploadStarted:
68 | case uploadFinished:
69 | retryPolling(request, uploadId, future, timeoutExponent);
70 | break;
71 | case readyToBePublished:
72 | log("Polling for app release successful.");
73 | final UploadRequest uploadRequest = request.newBuilder()
74 | .setReleaseId(pollForReleaseResponse.release_distinct_id)
75 | .build();
76 | future.complete(uploadRequest);
77 | break;
78 | case malwareDetected:
79 | case error:
80 | future.completeExceptionally(logFailure("Polling for app release successful however was rejected by server: " + pollForReleaseResponse.error_details));
81 | break;
82 | default:
83 | future.completeExceptionally(logFailure("Polling for app release successful however unexpected enum returned from server: " + pollForReleaseResponse.upload_status));
84 | }
85 | }
86 | });
87 | }
88 |
89 | private void retryPolling(@Nonnull UploadRequest request, @Nonnull String uploadId, @Nonnull CompletableFuture future, int timeoutExponent) {
90 | try {
91 | final double pow = Math.pow(2, timeoutExponent);
92 | final long timeout = Double.valueOf(pow).longValue();
93 | log(String.format("Polling for app release successful however not yet ready will try again in %d seconds.", timeout));
94 | TimeUnit.SECONDS.sleep(timeout);
95 | poll(request, uploadId, future, timeoutExponent + 1);
96 | } catch (InterruptedException e) {
97 | e.printStackTrace();
98 | }
99 | }
100 |
101 | @Override
102 | public PrintStream getLogger() {
103 | return taskListener.getLogger();
104 | }
105 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/task/internal/CreateUploadResourceTask.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.task.internal;
2 |
3 | import hudson.model.TaskListener;
4 | import io.jenkins.plugins.appcenter.AppCenterException;
5 | import io.jenkins.plugins.appcenter.AppCenterLogger;
6 | import io.jenkins.plugins.appcenter.api.AppCenterServiceFactory;
7 | import io.jenkins.plugins.appcenter.model.appcenter.ReleaseUploadBeginRequest;
8 | import io.jenkins.plugins.appcenter.model.appcenter.SymbolUploadBeginRequest;
9 | import io.jenkins.plugins.appcenter.task.request.UploadRequest;
10 |
11 | import javax.annotation.Nonnull;
12 | import javax.inject.Inject;
13 | import javax.inject.Singleton;
14 | import java.io.PrintStream;
15 | import java.util.concurrent.CompletableFuture;
16 |
17 | import static java.util.Objects.requireNonNull;
18 |
19 | @Singleton
20 | public final class CreateUploadResourceTask implements AppCenterTask, AppCenterLogger {
21 |
22 | private static final long serialVersionUID = 1L;
23 |
24 | @Nonnull
25 | private final TaskListener taskListener;
26 | @Nonnull
27 | private final AppCenterServiceFactory factory;
28 |
29 | @Inject
30 | CreateUploadResourceTask(@Nonnull final TaskListener taskListener,
31 | @Nonnull final AppCenterServiceFactory factory) {
32 | this.taskListener = taskListener;
33 | this.factory = factory;
34 | }
35 |
36 | @Nonnull
37 | @Override
38 | public CompletableFuture execute(@Nonnull UploadRequest request) {
39 | if (request.symbolUploadRequest == null) {
40 | return createUploadResourceForApp(request);
41 | } else {
42 | return createUploadResourceForApp(request)
43 | .thenCompose(this::createUploadResourceForDebugSymbols);
44 | }
45 | }
46 |
47 | @Nonnull
48 | private CompletableFuture createUploadResourceForApp(@Nonnull UploadRequest request) {
49 | log("Creating an upload resource for app.");
50 |
51 | final CompletableFuture future = new CompletableFuture<>();
52 |
53 | final ReleaseUploadBeginRequest releaseUploadBeginRequest = new ReleaseUploadBeginRequest(request.buildVersion, null);
54 | factory.createAppCenterService()
55 | .releaseUploadsCreate(request.ownerName, request.appName, releaseUploadBeginRequest)
56 | .whenComplete((releaseUploadBeginResponse, throwable) -> {
57 | if (throwable != null) {
58 | final AppCenterException exception = logFailure("Create upload resource for app unsuccessful", throwable);
59 | future.completeExceptionally(exception);
60 | } else {
61 | log("Create upload resource for app successful.");
62 | final UploadRequest uploadRequest = request.newBuilder()
63 | .setUploadId(releaseUploadBeginResponse.id)
64 | .setUploadDomain(releaseUploadBeginResponse.upload_domain)
65 | .setToken(releaseUploadBeginResponse.url_encoded_token)
66 | .setPackageAssetId(releaseUploadBeginResponse.package_asset_id)
67 | .build();
68 | future.complete(uploadRequest);
69 | }
70 | });
71 |
72 | return future;
73 | }
74 |
75 | @Nonnull
76 | private CompletableFuture createUploadResourceForDebugSymbols(@Nonnull UploadRequest request) {
77 | final SymbolUploadBeginRequest symbolUploadRequest = requireNonNull(request.symbolUploadRequest, "symbolUploadRequest cannot be null");
78 |
79 | log("Creating an upload resource for debug symbols.");
80 |
81 | final CompletableFuture future = new CompletableFuture<>();
82 |
83 | factory.createAppCenterService()
84 | .symbolUploadsCreate(request.ownerName, request.appName, symbolUploadRequest)
85 | .whenComplete((symbolsUploadBeginResponse, throwable) -> {
86 | if (throwable != null) {
87 | final AppCenterException exception = logFailure("Create upload resource for debug symbols unsuccessful: ", throwable);
88 | future.completeExceptionally(exception);
89 | } else {
90 | log("Create upload resource for debug symbols successful.");
91 | final UploadRequest uploadRequest = request.newBuilder()
92 | .setSymbolUploadUrl(symbolsUploadBeginResponse.upload_url)
93 | .setSymbolUploadId(symbolsUploadBeginResponse.symbol_upload_id)
94 | .build();
95 | future.complete(uploadRequest);
96 | }
97 | });
98 |
99 | return future;
100 | }
101 |
102 | @Override
103 | public PrintStream getLogger() {
104 | return taskListener.getLogger();
105 | }
106 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/task/internal/DistributeResourceTask.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.task.internal;
2 |
3 | import hudson.FilePath;
4 | import hudson.model.TaskListener;
5 | import io.jenkins.plugins.appcenter.AppCenterException;
6 | import io.jenkins.plugins.appcenter.AppCenterLogger;
7 | import io.jenkins.plugins.appcenter.api.AppCenterServiceFactory;
8 | import io.jenkins.plugins.appcenter.model.appcenter.BuildInfo;
9 | import io.jenkins.plugins.appcenter.model.appcenter.DestinationId;
10 | import io.jenkins.plugins.appcenter.model.appcenter.ReleaseUpdateRequest;
11 | import io.jenkins.plugins.appcenter.task.request.UploadRequest;
12 | import org.apache.commons.lang.StringUtils;
13 |
14 | import javax.annotation.Nonnull;
15 | import javax.annotation.Nullable;
16 | import javax.inject.Inject;
17 | import javax.inject.Singleton;
18 | import java.io.IOException;
19 | import java.io.PrintStream;
20 | import java.util.List;
21 | import java.util.concurrent.CompletableFuture;
22 | import java.util.stream.Collectors;
23 | import java.util.stream.Stream;
24 |
25 | import static java.util.Objects.requireNonNull;
26 |
27 | @Singleton
28 | public final class DistributeResourceTask implements AppCenterTask, AppCenterLogger {
29 |
30 | private static final long serialVersionUID = 1L;
31 |
32 | @Nonnull
33 | private final TaskListener taskListener;
34 | @Nonnull
35 | private final FilePath filePath;
36 | @Nonnull
37 | private final AppCenterServiceFactory factory;
38 |
39 | @Inject
40 | DistributeResourceTask(@Nonnull final TaskListener taskListener,
41 | @Nonnull final FilePath filePath,
42 | @Nonnull final AppCenterServiceFactory factory) {
43 | this.taskListener = taskListener;
44 | this.filePath = filePath;
45 | this.factory = factory;
46 | }
47 |
48 | @Nonnull
49 | @Override
50 | public CompletableFuture execute(@Nonnull UploadRequest request) {
51 | final Integer releaseId = requireNonNull(request.releaseId, "releaseId cannot be null");
52 |
53 | log("Distributing resource.");
54 |
55 | final CompletableFuture future = new CompletableFuture<>();
56 |
57 | final String releaseNotes = parseReleaseNotes(request);
58 | final boolean mandatoryUpdate = request.mandatoryUpdate;
59 | final List destinations = Stream.of(request.destinationGroups.split(","))
60 | .map(String::trim)
61 | .map(name -> new DestinationId(name, null))
62 | .collect(Collectors.toList());
63 | final boolean notifyTesters = request.notifyTesters;
64 |
65 | final ReleaseUpdateRequest releaseDetailsUpdateRequest = new ReleaseUpdateRequest(releaseNotes, mandatoryUpdate, destinations, createBuildInfo(request), notifyTesters);
66 |
67 | factory.createAppCenterService()
68 | .releasesUpdate(request.ownerName, request.appName, releaseId, releaseDetailsUpdateRequest)
69 | .whenComplete((releaseUploadBeginResponse, throwable) -> {
70 | if (throwable != null) {
71 | final AppCenterException exception = logFailure("Distributing resource unsuccessful", throwable);
72 | future.completeExceptionally(exception);
73 | } else {
74 | log("Distributing resource successful.");
75 | future.complete(request);
76 | }
77 | });
78 |
79 | return future;
80 | }
81 |
82 | @Nonnull
83 | private String parseReleaseNotes(@Nonnull UploadRequest request) {
84 | final String releaseNotesFromFile = parseReleaseNotesFromFile(request.pathToReleaseNotes);
85 | final String separator = (!request.releaseNotes.isEmpty() && !releaseNotesFromFile.isEmpty()) ? "\n\n" : "";
86 | final String combinedReleaseNotes = request.releaseNotes + separator + releaseNotesFromFile;
87 |
88 | return StringUtils.left(combinedReleaseNotes, 5000);
89 | }
90 |
91 | @Nonnull
92 | private String parseReleaseNotesFromFile(@Nonnull String pathToReleaseNotes) {
93 | if (pathToReleaseNotes.isEmpty()) return "";
94 |
95 | final FilePath releaseNotesFilePath = filePath.child(pathToReleaseNotes);
96 | try {
97 | return releaseNotesFilePath.readToString();
98 | } catch (IOException | InterruptedException e) {
99 | log(String.format("Unable to read release note file due to: %1$s", e));
100 | return "";
101 | }
102 |
103 | }
104 |
105 | @Nullable
106 | private BuildInfo createBuildInfo(@Nonnull UploadRequest request) {
107 | if (request.branchName == null && request.commitHash == null) return null;
108 | return new BuildInfo(request.branchName, request.commitHash, null);
109 | }
110 |
111 | @Override
112 | public PrintStream getLogger() {
113 | return taskListener.getLogger();
114 | }
115 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/ProxyTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter;
2 |
3 | import com.google.common.net.HttpHeaders;
4 | import hudson.ProxyConfiguration;
5 | import hudson.model.FreeStyleBuild;
6 | import hudson.model.FreeStyleProject;
7 | import hudson.model.Result;
8 | import io.jenkins.plugins.appcenter.util.MockWebServerUtil;
9 | import io.jenkins.plugins.appcenter.util.TestUtil;
10 | import okhttp3.Credentials;
11 | import okhttp3.mockwebserver.MockWebServer;
12 | import org.junit.Before;
13 | import org.junit.ClassRule;
14 | import org.junit.Rule;
15 | import org.junit.Test;
16 | import org.jvnet.hudson.test.JenkinsRule;
17 |
18 | import java.io.IOException;
19 |
20 | import static com.google.common.truth.Truth.assertThat;
21 |
22 | public class ProxyTest {
23 |
24 | @ClassRule
25 | public static JenkinsRule jenkinsRule = new JenkinsRule();
26 |
27 | @Rule
28 | public MockWebServer mockWebServer = new MockWebServer();
29 |
30 | @Rule
31 | public MockWebServer proxyWebServer = new MockWebServer();
32 |
33 | private FreeStyleProject freeStyleProject;
34 |
35 | @Before
36 | public void setUp() throws IOException {
37 | freeStyleProject = jenkinsRule.createFreeStyleProject();
38 | freeStyleProject.getBuildersList().add(TestUtil.createFile("three/days/xiola.apk"));
39 |
40 | final AppCenterRecorder appCenterRecorder = new AppCenterRecorder("at-this-moment-you-should-be-with-us", "janes-addiction", "ritual-de-lo-habitual", "three/days/xiola.apk", "casey, niccoli");
41 | appCenterRecorder.setBaseUrl(mockWebServer.url("/").toString()); // Notice this is *not* set to the proxy address
42 | freeStyleProject.getPublishersList().add(appCenterRecorder);
43 | }
44 |
45 | @Test
46 | public void should_SendRequestsDirectly_When_NoProxyConfigurationFound() throws Exception {
47 | // Given
48 | jenkinsRule.jenkins.proxy = null;
49 | MockWebServerUtil.enqueueSuccess(mockWebServer);
50 |
51 | // When
52 | final FreeStyleBuild freeStyleBuild = freeStyleProject.scheduleBuild2(0).get();
53 |
54 | // Then
55 | jenkinsRule.assertBuildStatus(Result.SUCCESS, freeStyleBuild);
56 | assertThat(proxyWebServer.getRequestCount()).isEqualTo(0);
57 | assertThat(mockWebServer.getRequestCount()).isEqualTo(7);
58 | }
59 |
60 | @Test
61 | public void should_SendRequestsToProxy_When_ProxyConfigurationFound() throws Exception {
62 | // Given
63 | jenkinsRule.jenkins.proxy = new ProxyConfiguration(proxyWebServer.getHostName(), proxyWebServer.getPort());
64 | MockWebServerUtil.enqueueSuccess(proxyWebServer);
65 |
66 | // When
67 | final FreeStyleBuild freeStyleBuild = freeStyleProject.scheduleBuild2(0).get();
68 |
69 | // Then
70 | jenkinsRule.assertBuildStatus(Result.SUCCESS, freeStyleBuild);
71 | assertThat(proxyWebServer.getRequestCount()).isEqualTo(7);
72 | assertThat(mockWebServer.getRequestCount()).isEqualTo(0);
73 | }
74 |
75 |
76 | @Test
77 | public void should_SendProxyAuthorizationHeader_When_ProxyCredentialsConfigured() throws Exception {
78 | // Given
79 | final String userName = "user";
80 | final String password = "password";
81 | jenkinsRule.jenkins.proxy = new ProxyConfiguration(proxyWebServer.getHostName(), proxyWebServer.getPort(), userName, password);
82 |
83 | MockWebServerUtil.enqueueProxyAuthRequired(proxyWebServer); // first request rejected and proxy authentication requested
84 | MockWebServerUtil.enqueueSuccess(proxyWebServer);
85 |
86 | // When
87 | final FreeStyleBuild freeStyleBuild = freeStyleProject.scheduleBuild2(0).get();
88 |
89 | // Then
90 | jenkinsRule.assertBuildStatus(Result.SUCCESS, freeStyleBuild);
91 | assertThat(proxyWebServer.getRequestCount()).isEqualTo(8);
92 | assertThat(mockWebServer.getRequestCount()).isEqualTo(0);
93 | // proxy auth is performed on second request
94 | assertThat(proxyWebServer.takeRequest().getHeader(HttpHeaders.PROXY_AUTHORIZATION)).isNull();
95 | assertThat(proxyWebServer.takeRequest().getHeader(HttpHeaders.PROXY_AUTHORIZATION)).isEqualTo(Credentials.basic(userName, password));
96 | }
97 |
98 | @Test
99 | public void should_SendAllRequestsDirectly_When_NoProxyHostConfigured() throws Exception {
100 | // Given
101 | final String noProxyHost = mockWebServer.url("/").url().getHost();
102 | jenkinsRule.jenkins.proxy = new ProxyConfiguration(proxyWebServer.getHostName(), proxyWebServer.getPort(), null, null, noProxyHost);
103 |
104 | MockWebServerUtil.enqueueSuccess(mockWebServer);
105 |
106 | // When
107 | final FreeStyleBuild freeStyleBuild = freeStyleProject.scheduleBuild2(0).get();
108 |
109 | // Then
110 | jenkinsRule.assertBuildStatus(Result.SUCCESS, freeStyleBuild);
111 | assertThat(proxyWebServer.getRequestCount()).isEqualTo(0);
112 | assertThat(mockWebServer.getRequestCount()).isEqualTo(7);
113 | }
114 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/task/internal/SetMetadataTaskTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.task.internal;
2 |
3 | import hudson.ProxyConfiguration;
4 | import hudson.model.TaskListener;
5 | import hudson.util.Secret;
6 | import io.jenkins.plugins.appcenter.AppCenterException;
7 | import io.jenkins.plugins.appcenter.api.AppCenterServiceFactory;
8 | import io.jenkins.plugins.appcenter.task.request.UploadRequest;
9 | import io.jenkins.plugins.appcenter.util.RemoteFileUtils;
10 | import okhttp3.mockwebserver.MockResponse;
11 | import okhttp3.mockwebserver.MockWebServer;
12 | import org.junit.Before;
13 | import org.junit.Rule;
14 | import org.junit.Test;
15 | import org.junit.function.ThrowingRunnable;
16 | import org.junit.runner.RunWith;
17 | import org.mockito.Mock;
18 | import org.mockito.junit.MockitoJUnitRunner;
19 |
20 | import java.io.PrintStream;
21 | import java.util.concurrent.ExecutionException;
22 |
23 | import static com.google.common.truth.Truth.assertThat;
24 | import static org.junit.Assert.assertThrows;
25 | import static org.mockito.BDDMockito.given;
26 |
27 | @RunWith(MockitoJUnitRunner.class)
28 | public class SetMetadataTaskTest {
29 |
30 | @Rule
31 | public MockWebServer mockWebServer = new MockWebServer();
32 |
33 | @Mock
34 | TaskListener mockTaskListener;
35 |
36 | @Mock
37 | RemoteFileUtils remoteFileUtils;
38 |
39 | @Mock
40 | PrintStream mockLogger;
41 |
42 | @Mock
43 | ProxyConfiguration mockProxyConfig;
44 |
45 | private UploadRequest baseRequest;
46 |
47 | private SetMetadataTask task;
48 |
49 | @Before
50 | public void setUp() {
51 | baseRequest = new UploadRequest.Builder()
52 | .setOwnerName("owner-name")
53 | .setAppName("app-name")
54 | .setPathToApp("path-to-app")
55 | .build();
56 | given(mockTaskListener.getLogger()).willReturn(mockLogger);
57 | final AppCenterServiceFactory factory = new AppCenterServiceFactory(Secret.fromString("secret-token"), mockWebServer.url("/").toString(), mockProxyConfig);
58 | task = new SetMetadataTask(mockTaskListener, factory, remoteFileUtils);
59 | }
60 |
61 | @Test
62 | public void should_ReturnException_When_UploadDomainIsMissing() {
63 | // Given
64 | final UploadRequest uploadRequest = baseRequest.newBuilder()
65 | .build();
66 |
67 | // When
68 | final ThrowingRunnable throwingRunnable = () -> task.execute(uploadRequest).get();
69 |
70 | // Then
71 | final NullPointerException exception = assertThrows(NullPointerException.class, throwingRunnable);
72 | assertThat(exception).hasMessageThat().contains("uploadDomain cannot be null");
73 | }
74 |
75 | @Test
76 | public void should_ReturnException_When_PackageAssetIdIsMissing() {
77 | // Given
78 | final UploadRequest uploadRequest = baseRequest.newBuilder()
79 | .setUploadDomain("upload-domain")
80 | .build();
81 |
82 | // When
83 | final ThrowingRunnable throwingRunnable = () -> task.execute(uploadRequest).get();
84 |
85 | // Then
86 | final NullPointerException exception = assertThrows(NullPointerException.class, throwingRunnable);
87 | assertThat(exception).hasMessageThat().contains("packageAssetId cannot be null");
88 | }
89 |
90 | @Test
91 | public void should_ReturnException_When_TokenIsMissing() {
92 | // Given
93 | final UploadRequest uploadRequest = baseRequest.newBuilder()
94 | .setUploadDomain("upload-domain")
95 | .setPackageAssetId("package_asset_id")
96 | .build();
97 |
98 | // When
99 | final ThrowingRunnable throwingRunnable = () -> task.execute(uploadRequest).get();
100 |
101 | // Then
102 | final NullPointerException exception = assertThrows(NullPointerException.class, throwingRunnable);
103 | assertThat(exception).hasMessageThat().contains("token cannot be null");
104 | }
105 |
106 | @Test
107 | public void should_ReturnResponse_When_RequestIsSuccessful() throws Exception {
108 | // Given
109 | final UploadRequest uploadRequest = baseRequest.newBuilder()
110 | .setUploadDomain("upload-domain")
111 | .setPackageAssetId("package_asset_id")
112 | .setToken("token")
113 | .build();
114 | final UploadRequest expected = uploadRequest.newBuilder().setChunkSize(4098).build();
115 | mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody("{\n" +
116 | " \"chunk_size\": 4098\n" +
117 | "}"));
118 |
119 | // When
120 | final UploadRequest actual = task.execute(uploadRequest).get();
121 |
122 | // Then
123 | assertThat(actual)
124 | .isEqualTo(expected);
125 | }
126 |
127 | @Test
128 | public void should_ReturnException_When_RequestIsUnSuccessful() {
129 | // Given
130 | final UploadRequest uploadRequest = baseRequest.newBuilder()
131 | .setUploadDomain("upload-domain")
132 | .setPackageAssetId("package_asset_id")
133 | .setToken("token")
134 | .build();
135 | mockWebServer.enqueue(new MockResponse().setResponseCode(400));
136 |
137 | // When
138 | final ThrowingRunnable throwingRunnable = () -> task.execute(uploadRequest).get();
139 |
140 | // Then
141 | final ExecutionException exception = assertThrows(ExecutionException.class, throwingRunnable);
142 | assertThat(exception).hasCauseThat().isInstanceOf(AppCenterException.class);
143 | assertThat(exception).hasCauseThat().hasMessageThat().contains("Setting metadata unsuccessful");
144 | }
145 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/task/internal/CreateUploadResourceTaskTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.task.internal;
2 |
3 | import hudson.ProxyConfiguration;
4 | import hudson.model.TaskListener;
5 | import hudson.util.Secret;
6 | import io.jenkins.plugins.appcenter.AppCenterException;
7 | import io.jenkins.plugins.appcenter.api.AppCenterServiceFactory;
8 | import io.jenkins.plugins.appcenter.model.appcenter.SymbolUploadBeginRequest;
9 | import io.jenkins.plugins.appcenter.task.request.UploadRequest;
10 | import okhttp3.mockwebserver.MockResponse;
11 | import okhttp3.mockwebserver.MockWebServer;
12 | import org.junit.Before;
13 | import org.junit.Rule;
14 | import org.junit.Test;
15 | import org.junit.function.ThrowingRunnable;
16 | import org.junit.runner.RunWith;
17 | import org.mockito.Mock;
18 | import org.mockito.junit.MockitoJUnitRunner;
19 |
20 | import java.io.PrintStream;
21 | import java.util.concurrent.ExecutionException;
22 |
23 | import static com.google.common.truth.Truth.assertThat;
24 | import static org.junit.Assert.assertThrows;
25 | import static org.mockito.BDDMockito.given;
26 |
27 | @RunWith(MockitoJUnitRunner.class)
28 | public class CreateUploadResourceTaskTest {
29 |
30 | @Rule
31 | public MockWebServer mockWebServer = new MockWebServer();
32 |
33 | @Mock
34 | TaskListener mockTaskListener;
35 |
36 | @Mock
37 | PrintStream mockLogger;
38 |
39 | @Mock
40 | ProxyConfiguration mockProxyConfig;
41 |
42 | private UploadRequest baseRequest;
43 |
44 | private CreateUploadResourceTask task;
45 |
46 | @Before
47 | public void setUp() {
48 | baseRequest = new UploadRequest.Builder()
49 | .setOwnerName("owner-name")
50 | .setAppName("app-name")
51 | .build();
52 | given(mockTaskListener.getLogger()).willReturn(mockLogger);
53 | final AppCenterServiceFactory factory = new AppCenterServiceFactory(Secret.fromString("secret-token"), mockWebServer.url("/").toString(), mockProxyConfig);
54 | task = new CreateUploadResourceTask(mockTaskListener, factory);
55 | }
56 |
57 | @Test
58 | public void should_ReturnResponse_When_RequestIsSuccessful() throws Exception {
59 | // Given
60 | final UploadRequest expected = baseRequest.newBuilder().setUploadId("string").setUploadDomain("string").setToken("string").setPackageAssetId("string").build();
61 | mockWebServer.enqueue(new MockResponse().setResponseCode(201).setBody("{\n" +
62 | " \"id\": \"string\",\n" +
63 | " \"upload_domain\": \"string\",\n" +
64 | " \"url_encoded_token\": \"string\",\n" +
65 | " \"package_asset_id\": \"string\"\n" +
66 | "}"));
67 |
68 | // When
69 | final UploadRequest actual = task.execute(baseRequest).get();
70 |
71 | // Then
72 | assertThat(actual)
73 | .isEqualTo(expected);
74 | }
75 |
76 | @Test
77 | public void should_ReturnResponse_When_DebugSymbolsAreFound() throws Exception {
78 | // Given
79 | final UploadRequest request = baseRequest.newBuilder()
80 | .setPathToDebugSymbols("path/to/mappings.txt")
81 | .setSymbolUploadRequest(new SymbolUploadBeginRequest(SymbolUploadBeginRequest.SymbolTypeEnum.AndroidProguard, null, "mappings.txt", "1", "1.0.0"))
82 | .build();
83 | final UploadRequest expected = request.newBuilder().setUploadId("string").setUploadDomain("string").setToken("string").setPackageAssetId("string").setSymbolUploadId("string").setSymbolUploadUrl("string").build();
84 | mockWebServer.enqueue(new MockResponse().setResponseCode(201).setBody("{\n" +
85 | " \"id\": \"string\",\n" +
86 | " \"upload_domain\": \"string\",\n" +
87 | " \"url_encoded_token\": \"string\",\n" +
88 | " \"package_asset_id\": \"string\"\n" +
89 | "}"));
90 | mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody("{\n" +
91 | " \"symbol_upload_id\": \"string\",\n" +
92 | " \"upload_url\": \"string\",\n" +
93 | " \"expiration_date\": \"2019-11-17T12:01:43.953Z\"\n" +
94 | "}"));
95 |
96 | // When
97 | final UploadRequest actual = task.execute(request).get();
98 |
99 | // Then
100 | assertThat(actual)
101 | .isEqualTo(expected);
102 | }
103 |
104 | @Test
105 | public void should_ReturnResponse_When_RequestIsSuccessful_NonAsciiCharactersInFileName() throws Exception {
106 | // Given
107 | final UploadRequest request = baseRequest.newBuilder().setAppName("åþþ ñåmë").build();
108 | final UploadRequest expected = request.newBuilder().setUploadId("string").setUploadDomain("string").setToken("string").setPackageAssetId("string").build();
109 | mockWebServer.enqueue(new MockResponse().setResponseCode(201).setBody("{\n" +
110 | " \"id\": \"string\",\n" +
111 | " \"upload_domain\": \"string\",\n" +
112 | " \"url_encoded_token\": \"string\",\n" +
113 | " \"package_asset_id\": \"string\"\n" +
114 | "}"));
115 |
116 | // When
117 | final UploadRequest actual = task.execute(request).get();
118 |
119 | // Then
120 | assertThat(actual)
121 | .isEqualTo(expected);
122 | }
123 |
124 | @Test
125 | public void should_ReturnException_When_RequestIsUnSuccessful() {
126 | // Given
127 | mockWebServer.enqueue(new MockResponse().setResponseCode(500));
128 |
129 | // When
130 | final ThrowingRunnable throwingRunnable = () -> task.execute(baseRequest).get();
131 |
132 | // Then
133 | final ExecutionException exception = assertThrows(ExecutionException.class, throwingRunnable);
134 | assertThat(exception).hasCauseThat().isInstanceOf(AppCenterException.class);
135 | assertThat(exception).hasCauseThat().hasMessageThat().contains("Create upload resource for app unsuccessful: HTTP 500 Server Error: ");
136 | }
137 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/api/AppCenterServiceFactory.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.api;
2 |
3 | import com.azure.core.http.okhttp.OkHttpAsyncHttpClientBuilder;
4 | import com.azure.storage.blob.BlobClient;
5 | import com.azure.storage.blob.BlobClientBuilder;
6 | import com.google.common.net.HttpHeaders;
7 | import hudson.ProxyConfiguration;
8 | import hudson.util.Secret;
9 | import okhttp3.*;
10 | import okhttp3.logging.HttpLoggingInterceptor;
11 | import retrofit2.Retrofit;
12 | import retrofit2.converter.moshi.MoshiConverterFactory;
13 |
14 | import javax.annotation.Nonnull;
15 | import javax.annotation.Nullable;
16 | import javax.inject.Inject;
17 | import javax.inject.Named;
18 | import javax.inject.Singleton;
19 | import java.io.Serializable;
20 | import java.net.Proxy;
21 | import java.util.concurrent.TimeUnit;
22 |
23 | import static org.apache.commons.lang.StringUtils.isNotBlank;
24 |
25 | @Singleton
26 | public final class AppCenterServiceFactory implements Serializable {
27 |
28 | private static final String APPCENTER_BASE_URL = "https://api.appcenter.ms/";
29 |
30 | private static final long serialVersionUID = 1L;
31 | private static final int timeoutSeconds = 60;
32 |
33 | @Nonnull
34 | private final Secret apiToken;
35 | @Nonnull
36 | private final String baseUrl;
37 | @Nullable
38 | private final ProxyConfiguration proxyConfiguration;
39 |
40 | @Inject
41 | public AppCenterServiceFactory(@Nonnull Secret apiToken, @Nullable @Named("baseUrl") String baseUrl, @Nullable ProxyConfiguration proxyConfiguration) {
42 | this.apiToken = apiToken;
43 | this.baseUrl = baseUrl != null ? baseUrl : APPCENTER_BASE_URL;
44 | this.proxyConfiguration = proxyConfiguration;
45 | }
46 |
47 | public AppCenterService createAppCenterService() {
48 | final HttpUrl baseHttpUrl = HttpUrl.get(baseUrl);
49 |
50 | final OkHttpClient.Builder builder = createHttpClientBuilder(baseHttpUrl)
51 | .addInterceptor(chain -> {
52 | final Request request = chain.request();
53 |
54 | final Headers newHeaders = request.headers().newBuilder()
55 | .add("Accept", "application/json")
56 | .add("Content-Type", "application/json")
57 | .add("X-API-Token", Secret.toString(apiToken))
58 | .build();
59 |
60 | final Request newRequest = request.newBuilder()
61 | .headers(newHeaders)
62 | .build();
63 |
64 | return chain.proceed(newRequest);
65 | });
66 |
67 | final Retrofit retrofit = new Retrofit.Builder()
68 | .baseUrl(baseHttpUrl)
69 | .client(builder.build())
70 | .addConverterFactory(MoshiConverterFactory.create())
71 | .build();
72 |
73 | return retrofit.create(AppCenterService.class);
74 | }
75 |
76 | public UploadService createUploadService(@Nonnull final String uploadUrl) {
77 | final HttpUrl httpUploadUrl = HttpUrl.get(uploadUrl);
78 | final HttpUrl baseUrl = HttpUrl.get(String.format("%1$s://%2$s/", httpUploadUrl.scheme(), httpUploadUrl.host()));
79 |
80 | final Retrofit retrofit = new Retrofit.Builder()
81 | .baseUrl(baseUrl)
82 | .client(createHttpClientBuilder(baseUrl).build())
83 | .addConverterFactory(MoshiConverterFactory.create())
84 | .build();
85 |
86 | return retrofit.create(UploadService.class);
87 | }
88 |
89 | public BlobClient createBlobUploadService(@Nonnull final String uploadUrl) {
90 | final HttpUrl httpUploadUrl = HttpUrl.get(uploadUrl);
91 | final OkHttpAsyncHttpClientBuilder okHttpAsyncHttpClientBuilder = new OkHttpAsyncHttpClientBuilder(createHttpClientBuilder(httpUploadUrl).build());
92 |
93 | return new BlobClientBuilder()
94 | .endpoint(workaroundAzureSdkForJava15827(httpUploadUrl))
95 | .httpClient(okHttpAsyncHttpClientBuilder.build())
96 | .buildClient();
97 | }
98 |
99 | @Nonnull
100 | private String workaroundAzureSdkForJava15827(@Nonnull HttpUrl httpUploadUrl) {
101 | // Workaround for bug in Azure Blob Storage, as AppCenter returns the upload URL with a port attached.
102 | // See https://github.com/Azure/azure-sdk-for-java/issues/15827
103 |
104 | // toString() will remove the well known HTTPS of 443 or keep it if there is a custom port. Hence this results in stripping the port if needed (e.g. if we are trying to
105 | // connect to https://.blob.core.windows.net:443) or keeps it (e.g. if we are trying to connect to test servers https://127.0.0.1:1234)
106 | return httpUploadUrl.toString();
107 | }
108 |
109 | private OkHttpClient.Builder createHttpClientBuilder(@Nonnull final HttpUrl httpUrl) {
110 | final HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
111 | logging.setLevel(HttpLoggingInterceptor.Level.HEADERS);
112 |
113 | return new OkHttpClient.Builder()
114 | .addInterceptor(logging)
115 | .proxy(setProxy(proxyConfiguration, httpUrl.host()))
116 | .proxyAuthenticator(setProxyAuthenticator(proxyConfiguration))
117 | .connectTimeout(timeoutSeconds, TimeUnit.SECONDS)
118 | .readTimeout(timeoutSeconds, TimeUnit.SECONDS)
119 | .writeTimeout(timeoutSeconds, TimeUnit.SECONDS);
120 | }
121 |
122 | private Proxy setProxy(@Nullable final ProxyConfiguration proxyConfiguration,
123 | @Nonnull final String host) {
124 | if (proxyConfiguration != null) {
125 | return proxyConfiguration.createProxy(host);
126 | } else {
127 | return Proxy.NO_PROXY;
128 | }
129 | }
130 |
131 | private Authenticator setProxyAuthenticator(@Nullable final ProxyConfiguration proxyConfiguration) {
132 | if (proxyConfiguration != null) {
133 | final String username = proxyConfiguration.getUserName();
134 | final String password = proxyConfiguration.getPassword();
135 |
136 | if (isNotBlank(username) && isNotBlank(password)) {
137 | final String credentials = Credentials.basic(username, password);
138 |
139 | return (route, response) -> response
140 | .request()
141 | .newBuilder()
142 | .header(HttpHeaders.PROXY_AUTHORIZATION, credentials)
143 | .build();
144 | }
145 |
146 | return Authenticator.NONE;
147 | }
148 |
149 | return Authenticator.NONE;
150 | }
151 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/util/MockWebServerUtil.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.util;
2 |
3 | import okhttp3.mockwebserver.MockResponse;
4 | import okhttp3.mockwebserver.MockWebServer;
5 |
6 | import javax.annotation.Nonnull;
7 |
8 | import static java.net.HttpURLConnection.*;
9 |
10 | public class MockWebServerUtil {
11 |
12 | public static void enqueueSuccess(final @Nonnull MockWebServer mockWebServer) {
13 | enqueueSuccess(mockWebServer, mockWebServer);
14 | }
15 |
16 | public static void enqueueUploadViaProxy(final @Nonnull MockWebServer mockWebServer, final @Nonnull MockWebServer proxyWebServer) {
17 | enqueueSuccess(mockWebServer, proxyWebServer);
18 | }
19 |
20 | public static void enqueueAppCenterViaProxy(final @Nonnull MockWebServer mockWebServer, final @Nonnull MockWebServer proxyWebServer) {
21 | enqueueSuccess(proxyWebServer, mockWebServer);
22 | }
23 |
24 | private static void enqueueSuccess(final @Nonnull MockWebServer mockAppCenterServer, final @Nonnull MockWebServer mockUploadServer) {
25 | // Create upload resource for app
26 | mockAppCenterServer.enqueue(new MockResponse().setResponseCode(HTTP_CREATED).setBody("{\n" +
27 | " \"id\": \"string\",\n" +
28 | " \"upload_domain\": \"" + mockUploadServer.url("/").toString() + "\",\n" +
29 | " \"asset_domain\": \"string\",\n" +
30 | " \"url_encoded_token\": \"string\",\n" +
31 | " \"package_asset_id\": \"string\"\n" +
32 | "}"));
33 |
34 | // Set Metadata
35 | mockAppCenterServer.enqueue(new MockResponse().setResponseCode(HTTP_OK).setBody("{\n" +
36 | " \"chunk_size\": 1234\n" +
37 | "}"));
38 |
39 | // Upload app
40 | mockAppCenterServer.enqueue(new MockResponse().setResponseCode(HTTP_OK));
41 |
42 | // Finish Release
43 | mockAppCenterServer.enqueue(new MockResponse().setResponseCode(HTTP_OK));
44 |
45 | // Update Release
46 | mockAppCenterServer.enqueue(new MockResponse().setResponseCode(HTTP_OK).setBody("{\n" +
47 | " \"id\": \"1234\",\n" +
48 | " \"upload_status\": \"uploadFinished\"\n" +
49 | "}"));
50 |
51 | // Poll For Release
52 | mockAppCenterServer.enqueue(new MockResponse().setResponseCode(HTTP_OK).setBody("{\n" +
53 | " \"id\": \"1234\",\n" +
54 | " \"upload_status\": \"readyToBePublished\",\n" +
55 | " \"release_distinct_id\": \"4321\",\n" +
56 | " \"release_url\": \"string\"\n" +
57 | "}"));
58 |
59 | // Distribute Resource
60 | mockAppCenterServer.enqueue(new MockResponse().setResponseCode(HTTP_OK).setBody("{\n" +
61 | " \"release_notes\": \"string\"\n" +
62 | "}"));
63 | }
64 |
65 | public static void enqueueSuccessWithSymbols(final @Nonnull MockWebServer mockAppCenterServer) {
66 | // Create upload resource for app
67 | mockAppCenterServer.enqueue(new MockResponse().setResponseCode(HTTP_CREATED).setBody("{\n" +
68 | " \"id\": \"string\",\n" +
69 | " \"upload_domain\": \"" + mockAppCenterServer.url("/").toString() + "\",\n" +
70 | " \"asset_domain\": \"string\",\n" +
71 | " \"url_encoded_token\": \"string\",\n" +
72 | " \"package_asset_id\": \"string\"\n" +
73 | "}"));
74 |
75 | // Create upload resource for debug symbols
76 | mockAppCenterServer.enqueue(new MockResponse().setResponseCode(HTTP_CREATED).setBody("{\n" +
77 | " \"symbol_upload_id\": \"string\",\n" +
78 | " \"upload_url\": \"" + mockAppCenterServer.url("/").toString() + "\",\n" +
79 | " \"expiration_date\": \"2020-03-18T21:16:22.188Z\"\n" +
80 | "}"));
81 |
82 | // Set Metadata
83 | mockAppCenterServer.enqueue(new MockResponse().setResponseCode(HTTP_OK).setBody("{\n" +
84 | " \"chunk_size\": 1234\n" +
85 | "}"));
86 |
87 | // Upload app
88 | mockAppCenterServer.enqueue(new MockResponse().setResponseCode(HTTP_OK));
89 |
90 | // Upload debug symbols
91 | mockAppCenterServer.enqueue(new MockResponse().setResponseCode(HTTP_OK));
92 |
93 | // Finish Release
94 | mockAppCenterServer.enqueue(new MockResponse().setResponseCode(HTTP_OK));
95 |
96 | // Finish symbol release
97 | mockAppCenterServer.enqueue(new MockResponse().setResponseCode(HTTP_OK).setBody("{\n" +
98 | " \"symbol_upload_id\": \"string\",\n" +
99 | " \"app_id\": \"string\",\n" +
100 | " \"user\": {\n" +
101 | " \"email\": \"string\",\n" +
102 | " \"display_name\": \"string\"\n" +
103 | " },\n" +
104 | " \"status\": \"created\",\n" +
105 | " \"symbol_type\": \"AndroidProguard\",\n" +
106 | " \"symbols_uploaded\": [\n" +
107 | " {\n" +
108 | " \"symbol_id\": \"string\",\n" +
109 | " \"platform\": \"string\"\n" +
110 | " }\n" +
111 | " ],\n" +
112 | " \"origin\": \"User\",\n" +
113 | " \"file_name\": \"string\",\n" +
114 | " \"file_size\": 0,\n" +
115 | " \"timestamp\": \"2019-11-17T12:12:06.701Z\"\n" +
116 | "}"));
117 |
118 | // Update Release
119 | mockAppCenterServer.enqueue(new MockResponse().setResponseCode(HTTP_OK).setBody("{\n" +
120 | " \"id\": \"1234\",\n" +
121 | " \"upload_status\": \"uploadFinished\"\n" +
122 | "}"));
123 |
124 | // Poll For Release
125 | mockAppCenterServer.enqueue(new MockResponse().setResponseCode(HTTP_OK).setBody("{\n" +
126 | " \"id\": \"1234\",\n" +
127 | " \"upload_status\": \"readyToBePublished\",\n" +
128 | " \"release_distinct_id\": \"4321\",\n" +
129 | " \"release_url\": \"string\"\n" +
130 | "}"));
131 |
132 | // Distribute app
133 | mockAppCenterServer.enqueue(new MockResponse().setResponseCode(HTTP_OK).setBody("{\n" +
134 | " \"release_notes\": \"string\"\n" +
135 | "}"));
136 | }
137 |
138 | public static void enqueueFailure(final @Nonnull MockWebServer mockWebServer) {
139 | mockWebServer.enqueue(new MockResponse().setResponseCode(HTTP_INTERNAL_ERROR));
140 | }
141 |
142 | public static void enqueueProxyAuthRequired(final @Nonnull MockWebServer proxyWebServer) {
143 | proxyWebServer.enqueue(new MockResponse().setResponseCode(HTTP_PROXY_AUTH));
144 | }
145 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/task/internal/UploadAppToResourceTaskTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.task.internal;
2 |
3 | import hudson.ProxyConfiguration;
4 | import hudson.model.TaskListener;
5 | import hudson.util.Secret;
6 | import io.jenkins.plugins.appcenter.AppCenterException;
7 | import io.jenkins.plugins.appcenter.api.AppCenterServiceFactory;
8 | import io.jenkins.plugins.appcenter.task.request.UploadRequest;
9 | import io.jenkins.plugins.appcenter.util.RemoteFileUtils;
10 | import io.jenkins.plugins.appcenter.util.TestFileUtil;
11 | import okhttp3.Headers;
12 | import okhttp3.mockwebserver.MockResponse;
13 | import okhttp3.mockwebserver.MockWebServer;
14 | import okhttp3.mockwebserver.RecordedRequest;
15 | import org.junit.Before;
16 | import org.junit.Rule;
17 | import org.junit.Test;
18 | import org.junit.function.ThrowingRunnable;
19 | import org.junit.runner.RunWith;
20 | import org.mockito.Mock;
21 | import org.mockito.junit.MockitoJUnitRunner;
22 |
23 | import java.io.PrintStream;
24 | import java.util.concurrent.ExecutionException;
25 |
26 | import static com.google.common.truth.Truth.assertThat;
27 | import static org.junit.Assert.assertThrows;
28 | import static org.mockito.ArgumentMatchers.anyString;
29 | import static org.mockito.BDDMockito.given;
30 |
31 | @RunWith(MockitoJUnitRunner.class)
32 | public class UploadAppToResourceTaskTest {
33 |
34 | @Rule
35 | public MockWebServer mockWebServer = new MockWebServer();
36 |
37 | @Mock
38 | TaskListener mockTaskListener;
39 |
40 | @Mock
41 | PrintStream mockLogger;
42 |
43 | @Mock
44 | ProxyConfiguration mockProxyConfig;
45 |
46 | @Mock
47 | RemoteFileUtils mockRemoteFileUtils;
48 |
49 | private UploadRequest baseRequest;
50 |
51 | private UploadAppToResourceTask task;
52 |
53 | @Before
54 | public void setUp() {
55 | baseRequest = new UploadRequest.Builder()
56 | .setUploadDomain(mockWebServer.url("upload").toString())
57 | .setPackageAssetId("package-asset-id")
58 | .setToken("token")
59 | .setChunkSize(4098)
60 | .setUploadId("upload-id")
61 | .setPathToApp("three/days/xiola.apk")
62 | .build();
63 | given(mockTaskListener.getLogger()).willReturn(mockLogger);
64 | given(mockRemoteFileUtils.getRemoteFile(anyString())).willReturn(TestFileUtil.createFileForTesting());
65 | final AppCenterServiceFactory factory = new AppCenterServiceFactory(Secret.fromString("secret-token"), mockWebServer.url("/").toString(), mockProxyConfig);
66 | task = new UploadAppToResourceTask(mockTaskListener, factory, mockRemoteFileUtils);
67 | }
68 |
69 | @Test
70 | public void should_ReturnUploadId_When_RequestIsSuccess() throws Exception {
71 | // Given
72 | mockWebServer.enqueue(new MockResponse().setResponseCode(200));
73 |
74 | // When
75 | final UploadRequest result = task.execute(baseRequest).get();
76 |
77 | // Then
78 | assertThat(result)
79 | .isEqualTo(baseRequest);
80 | }
81 |
82 | @Test
83 | public void should_ReturnDebugSymbolUploadId_When_DebugSymbolsAreFound() throws Exception {
84 | // Given
85 | final UploadRequest request = baseRequest.newBuilder()
86 | .setPathToDebugSymbols("string")
87 | .setSymbolUploadUrl(mockWebServer.url("upload-debug-symbols").toString())
88 | .setSymbolUploadId("string")
89 | .build();
90 |
91 | mockWebServer.enqueue(new MockResponse().setResponseCode(200));
92 | mockWebServer.enqueue(new MockResponse().setResponseCode(200));
93 |
94 | // When
95 | final UploadRequest result = task.execute(request).get();
96 |
97 | // Then
98 | assertThat(result)
99 | .isEqualTo(request);
100 | }
101 |
102 | @Test
103 | public void should_ReturnDebugSymbolUploadId_When_DebugSymbolsAreFound_ChunkedMode() throws Exception {
104 | // Given
105 | final UploadRequest request = baseRequest.newBuilder()
106 | .setPathToDebugSymbols("string")
107 | .setSymbolUploadUrl(mockWebServer.url("perry/casey/xiola").toString())
108 | .setSymbolUploadId("string")
109 | .build();
110 |
111 | mockWebServer.enqueue(new MockResponse().setResponseCode(200));
112 | mockWebServer.enqueue(new MockResponse().setResponseCode(201)
113 | .setHeaders(Headers.of(
114 | "ETag", "0x8CB171BA9E94B0B",
115 | "Last-Modified", "Thu, 01 Jan 1970 00:00:00 GMT",
116 | "Content-MD5", "sQqNsWTgdUEFt6mb5y4/5Q==",
117 | "x-ms-request-server-encrypted", "false",
118 | "x-ms-version-id", "Thu, 01 Jan 1970 00:00:00 GMT"
119 | ))
120 | .setChunkedBody("", 1)
121 | );
122 |
123 | given(mockRemoteFileUtils.getRemoteFile(anyString())).willReturn(TestFileUtil.createFileForTesting(), TestFileUtil.createLargeFileForTesting());
124 |
125 | // When
126 | final UploadRequest result = task.execute(request).get();
127 |
128 | // Then
129 | assertThat(result)
130 | .isEqualTo(request);
131 | }
132 |
133 | @Test
134 | public void should_ReturnException_When_RequestIsUnSuccessful() {
135 | // Given
136 | mockWebServer.enqueue(new MockResponse().setResponseCode(500));
137 |
138 | // When
139 | final ThrowingRunnable throwingRunnable = () -> task.execute(baseRequest).get();
140 |
141 | // Then
142 | final ExecutionException exception = assertThrows(ExecutionException.class, throwingRunnable);
143 | assertThat(exception).hasCauseThat().isInstanceOf(AppCenterException.class);
144 | assertThat(exception).hasCauseThat().hasMessageThat().contains("Upload app to resource unsuccessful: HTTP 500 Server Error: ");
145 | }
146 |
147 | @Test
148 | public void should_SendRequestToUploadUrl() throws Exception {
149 | // Given
150 | mockWebServer.enqueue(new MockResponse().setResponseCode(200));
151 | task.execute(baseRequest).get();
152 |
153 | // When
154 | final RecordedRequest recordedRequest = mockWebServer.takeRequest();
155 |
156 | // Then
157 | assertThat(recordedRequest.getPath())
158 | .isEqualTo("/upload/upload/upload_chunk/package-asset-id?token=token&block_number=1");
159 | }
160 |
161 | @Test
162 | public void should_SendRequestAsPost() throws Exception {
163 | // Given
164 | mockWebServer.enqueue(new MockResponse().setResponseCode(200));
165 | task.execute(baseRequest).get();
166 |
167 | // When
168 | final RecordedRequest recordedRequest = mockWebServer.takeRequest();
169 |
170 | // Then
171 | assertThat(recordedRequest.getMethod())
172 | .isEqualTo("POST");
173 | }
174 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/EnvInterpolationTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter;
2 |
3 | import hudson.EnvVars;
4 | import hudson.model.FreeStyleBuild;
5 | import hudson.model.FreeStyleProject;
6 | import hudson.model.Result;
7 | import hudson.slaves.EnvironmentVariablesNodeProperty;
8 | import io.jenkins.plugins.appcenter.util.MockWebServerUtil;
9 | import io.jenkins.plugins.appcenter.util.TestUtil;
10 | import okhttp3.mockwebserver.MockWebServer;
11 | import okhttp3.mockwebserver.RecordedRequest;
12 | import org.junit.Before;
13 | import org.junit.ClassRule;
14 | import org.junit.Rule;
15 | import org.junit.Test;
16 | import org.jvnet.hudson.test.JenkinsRule;
17 |
18 | import java.io.IOException;
19 |
20 | import static com.google.common.truth.Truth.assertThat;
21 |
22 | public class EnvInterpolationTest {
23 |
24 | @ClassRule
25 | public static JenkinsRule jenkinsRule = new JenkinsRule();
26 |
27 | @Rule
28 | public MockWebServer mockWebServer = new MockWebServer();
29 |
30 | private FreeStyleProject freeStyleProject;
31 |
32 | @Before
33 | public void setUp() throws IOException {
34 | freeStyleProject = jenkinsRule.createFreeStyleProject();
35 | freeStyleProject.getBuildersList().add(TestUtil.createFile("three/days/xiola.ipa"));
36 | freeStyleProject.getBuildersList().add(TestUtil.createFile("three/days/blue.zip"));
37 | freeStyleProject.getBuildersList().add(TestUtil.createFile("three/days/linear-notes.md", "I prepared the room tonight with Christmas lights."));
38 |
39 | final EnvironmentVariablesNodeProperty prop = new EnvironmentVariablesNodeProperty();
40 | final EnvVars envVars = prop.getEnvVars();
41 | envVars.put("OWNER_NAME", "janes-addiction");
42 | envVars.put("APP_NAME", "ritual-de-lo-habitual");
43 | envVars.put("PATH_TO_APP", "three/days/xiola.ipa");
44 | envVars.put("BUILD_VERSION", "1.2.3");
45 | envVars.put("PATH_TO_DEBUG_SYMBOLS", "three/days/blue.zip");
46 | envVars.put("DISTRIBUTION_GROUPS", "casey, niccoli");
47 | envVars.put("RELEASE_NOTES", "I miss you my dear Xiola");
48 | envVars.put("PATH_TO_RELEASE_NOTES", "three/days/linear-notes.md");
49 |
50 | jenkinsRule.jenkins.getGlobalNodeProperties().add(prop);
51 |
52 | final AppCenterRecorder appCenterRecorder = new AppCenterRecorder(
53 | "at-this-moment-you-should-be-with-us",
54 | "${OWNER_NAME}",
55 | "${APP_NAME}",
56 | "${PATH_TO_APP}",
57 | "${DISTRIBUTION_GROUPS}"
58 | );
59 | appCenterRecorder.setBuildVersion("${BUILD_VERSION}");
60 | appCenterRecorder.setPathToDebugSymbols("${PATH_TO_DEBUG_SYMBOLS}");
61 | appCenterRecorder.setReleaseNotes("${RELEASE_NOTES}");
62 | appCenterRecorder.setPathToReleaseNotes("${PATH_TO_RELEASE_NOTES}");
63 | appCenterRecorder.setBaseUrl(mockWebServer.url("/").toString());
64 |
65 | freeStyleProject.getPublishersList().add(appCenterRecorder);
66 | }
67 |
68 | @Test
69 | public void should_InterpolateEnv_InOwnerName() throws Exception {
70 | // Given
71 | MockWebServerUtil.enqueueSuccessWithSymbols(mockWebServer);
72 |
73 | // When
74 | final FreeStyleBuild freeStyleBuild = freeStyleProject.scheduleBuild2(0).get();
75 |
76 | // Then
77 | jenkinsRule.assertBuildStatus(Result.SUCCESS, freeStyleBuild);
78 | final RecordedRequest recordedRequest = mockWebServer.takeRequest();
79 | assertThat(recordedRequest.getPath()).contains("janes-addiction");
80 | }
81 |
82 | @Test
83 | public void should_InterpolateEnv_InAppName() throws Exception {
84 | // Given
85 | MockWebServerUtil.enqueueSuccessWithSymbols(mockWebServer);
86 |
87 | // When
88 | final FreeStyleBuild freeStyleBuild = freeStyleProject.scheduleBuild2(0).get();
89 |
90 | // Then
91 | jenkinsRule.assertBuildStatus(Result.SUCCESS, freeStyleBuild);
92 | final RecordedRequest recordedRequest = mockWebServer.takeRequest();
93 | assertThat(recordedRequest.getPath()).contains("ritual-de-lo-habitual");
94 | }
95 |
96 | @Test
97 | public void should_InterpolateEnv_InAppPath() throws Exception {
98 | // Given
99 | MockWebServerUtil.enqueueSuccessWithSymbols(mockWebServer);
100 |
101 | // When
102 | final FreeStyleBuild freeStyleBuild = freeStyleProject.scheduleBuild2(0).get();
103 |
104 | // Then
105 | jenkinsRule.assertBuildStatus(Result.SUCCESS, freeStyleBuild);
106 | mockWebServer.takeRequest();
107 | mockWebServer.takeRequest();
108 | final RecordedRequest recordedRequest = mockWebServer.takeRequest();
109 | assertThat(recordedRequest.getPath().contains("file_name=xiola.ipa"));
110 | }
111 |
112 | @Test
113 | public void should_InterpolateEnv_InDebugSymbolPath() throws Exception {
114 | // Given
115 | MockWebServerUtil.enqueueSuccessWithSymbols(mockWebServer);
116 |
117 | // When
118 | final FreeStyleBuild freeStyleBuild = freeStyleProject.scheduleBuild2(0).get();
119 |
120 | // Then
121 | jenkinsRule.assertBuildStatus(Result.SUCCESS, freeStyleBuild);
122 | mockWebServer.takeRequest();
123 | final RecordedRequest recordedRequest = mockWebServer.takeRequest();
124 | assertThat(recordedRequest.getBody().readUtf8()).contains("\"symbol_type\":\"Apple\"");
125 | }
126 |
127 | @Test
128 | public void should_InterpolateEnv_InDestinationGroups() throws Exception {
129 | // Given
130 | MockWebServerUtil.enqueueSuccessWithSymbols(mockWebServer);
131 |
132 | // When
133 | final FreeStyleBuild freeStyleBuild = freeStyleProject.scheduleBuild2(0).get();
134 |
135 | // Then
136 | jenkinsRule.assertBuildStatus(Result.SUCCESS, freeStyleBuild);
137 | mockWebServer.takeRequest();
138 | mockWebServer.takeRequest();
139 | mockWebServer.takeRequest();
140 | mockWebServer.takeRequest();
141 | mockWebServer.takeRequest();
142 | mockWebServer.takeRequest();
143 | mockWebServer.takeRequest();
144 | mockWebServer.takeRequest();
145 | mockWebServer.takeRequest();
146 | final RecordedRequest recordedRequest = mockWebServer.takeRequest();
147 | assertThat(recordedRequest.getBody().readUtf8()).contains("[{\"name\":\"casey\"},{\"name\":\"niccoli\"}]");
148 | }
149 |
150 | @Test
151 | public void should_InterpolateEnv_InReleaseNotes() throws Exception {
152 | // Given
153 | MockWebServerUtil.enqueueSuccessWithSymbols(mockWebServer);
154 |
155 | // When
156 | final FreeStyleBuild freeStyleBuild = freeStyleProject.scheduleBuild2(0).get();
157 |
158 | // Then
159 | jenkinsRule.assertBuildStatus(Result.SUCCESS, freeStyleBuild);
160 | mockWebServer.takeRequest();
161 | mockWebServer.takeRequest();
162 | mockWebServer.takeRequest();
163 | mockWebServer.takeRequest();
164 | mockWebServer.takeRequest();
165 | mockWebServer.takeRequest();
166 | mockWebServer.takeRequest();
167 | mockWebServer.takeRequest();
168 | mockWebServer.takeRequest();
169 | final RecordedRequest recordedRequest = mockWebServer.takeRequest();
170 | assertThat(recordedRequest.getBody().readUtf8()).contains("\"release_notes\":\"I miss you my dear Xiola\\n\\nI prepared the room tonight with Christmas lights.\"");
171 | }
172 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/task/internal/FinishReleaseTaskTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.task.internal;
2 |
3 | import hudson.ProxyConfiguration;
4 | import hudson.model.TaskListener;
5 | import hudson.util.Secret;
6 | import io.jenkins.plugins.appcenter.AppCenterException;
7 | import io.jenkins.plugins.appcenter.api.AppCenterServiceFactory;
8 | import io.jenkins.plugins.appcenter.task.request.UploadRequest;
9 | import okhttp3.mockwebserver.MockResponse;
10 | import okhttp3.mockwebserver.MockWebServer;
11 | import org.junit.Before;
12 | import org.junit.Rule;
13 | import org.junit.Test;
14 | import org.junit.function.ThrowingRunnable;
15 | import org.junit.runner.RunWith;
16 | import org.mockito.Mock;
17 | import org.mockito.junit.MockitoJUnitRunner;
18 |
19 | import java.io.PrintStream;
20 | import java.util.concurrent.ExecutionException;
21 |
22 | import static com.google.common.truth.Truth.assertThat;
23 | import static org.junit.Assert.assertThrows;
24 | import static org.mockito.BDDMockito.given;
25 |
26 | @RunWith(MockitoJUnitRunner.class)
27 | public class FinishReleaseTaskTest {
28 |
29 | @Rule
30 | public MockWebServer mockWebServer = new MockWebServer();
31 |
32 | @Mock
33 | TaskListener mockTaskListener;
34 |
35 | @Mock
36 | PrintStream mockLogger;
37 |
38 | @Mock
39 | ProxyConfiguration mockProxyConfig;
40 |
41 | private UploadRequest baseRequest;
42 |
43 | private FinishReleaseTask task;
44 |
45 | @Before
46 | public void setUp() {
47 | baseRequest = new UploadRequest.Builder()
48 | .setOwnerName("owner-name")
49 | .setAppName("app-name")
50 | .build();
51 | given(mockTaskListener.getLogger()).willReturn(mockLogger);
52 | final AppCenterServiceFactory factory = new AppCenterServiceFactory(Secret.fromString("secret-token"), mockWebServer.url("/").toString(), mockProxyConfig);
53 | task = new FinishReleaseTask(mockTaskListener, factory);
54 | }
55 |
56 | @Test
57 | public void should_ReturnException_When_UploadDomainIsMissing() {
58 | // Given
59 | final UploadRequest uploadRequest = baseRequest.newBuilder()
60 | .build();
61 |
62 | // When
63 | final ThrowingRunnable throwingRunnable = () -> task.execute(uploadRequest).get();
64 |
65 | // Then
66 | final NullPointerException exception = assertThrows(NullPointerException.class, throwingRunnable);
67 | assertThat(exception).hasMessageThat().contains("uploadDomain cannot be null");
68 | }
69 |
70 | @Test
71 | public void should_ReturnException_When_PackageAssetIdIsMissing() {
72 | // Given
73 | final UploadRequest uploadRequest = baseRequest.newBuilder()
74 | .setUploadDomain("upload-domain")
75 | .build();
76 |
77 | // When
78 | final ThrowingRunnable throwingRunnable = () -> task.execute(uploadRequest).get();
79 |
80 | // Then
81 | final NullPointerException exception = assertThrows(NullPointerException.class, throwingRunnable);
82 | assertThat(exception).hasMessageThat().contains("packageAssetId cannot be null");
83 | }
84 |
85 | @Test
86 | public void should_ReturnException_When_TokenIsMissing() {
87 | // Given
88 | final UploadRequest uploadRequest = baseRequest.newBuilder()
89 | .setUploadDomain("upload-domain")
90 | .setPackageAssetId("package_asset_id")
91 | .build();
92 |
93 | // When
94 | final ThrowingRunnable throwingRunnable = () -> task.execute(uploadRequest).get();
95 |
96 | // Then
97 | final NullPointerException exception = assertThrows(NullPointerException.class, throwingRunnable);
98 | assertThat(exception).hasMessageThat().contains("token cannot be null");
99 | }
100 |
101 | @Test
102 | public void should_ReturnResponse_When_RequestIsSuccessful() throws Exception {
103 | // Given
104 | final UploadRequest uploadRequest = baseRequest.newBuilder()
105 | .setUploadDomain("upload-domain")
106 | .setPackageAssetId("package_asset_id")
107 | .setToken("token")
108 | .build();
109 | mockWebServer.enqueue(new MockResponse().setResponseCode(200));
110 |
111 | // When
112 | final UploadRequest actual = task.execute(uploadRequest).get();
113 |
114 | // Then
115 | assertThat(actual)
116 | .isEqualTo(uploadRequest);
117 | }
118 |
119 | @Test
120 | public void should_ReturnException_When_RequestIsUnSuccessful() {
121 | // Given
122 | final UploadRequest uploadRequest = baseRequest.newBuilder()
123 | .setUploadDomain("upload-domain")
124 | .setPackageAssetId("package_asset_id")
125 | .setToken("token")
126 | .build();
127 | mockWebServer.enqueue(new MockResponse().setResponseCode(400));
128 |
129 | // When
130 | final ThrowingRunnable throwingRunnable = () -> task.execute(uploadRequest).get();
131 |
132 | // Then
133 | final ExecutionException exception = assertThrows(ExecutionException.class, throwingRunnable);
134 | assertThat(exception).hasCauseThat().isInstanceOf(AppCenterException.class);
135 | assertThat(exception).hasCauseThat().hasMessageThat().contains("Finishing release unsuccessful: HTTP 400 Client Error: ");
136 | }
137 |
138 | @Test
139 | public void should_ReturnResponse_When_SymbolRequestIsSuccessful() throws Exception {
140 | // Given
141 | final UploadRequest uploadRequest = baseRequest.newBuilder()
142 | .setUploadDomain("upload-domain")
143 | .setPackageAssetId("package_asset_id")
144 | .setToken("token")
145 | .setSymbolUploadId("symbol_upload_id")
146 | .build();
147 | mockWebServer.enqueue(new MockResponse().setResponseCode(200));
148 | mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody("{\n" +
149 | " \"symbol_upload_id\": \"string\",\n" +
150 | " \"app_id\": \"string\",\n" +
151 | " \"user\": {\n" +
152 | " \"email\": \"string\",\n" +
153 | " \"display_name\": \"string\"\n" +
154 | " },\n" +
155 | " \"status\": \"created\",\n" +
156 | " \"symbol_type\": \"AndroidProguard\",\n" +
157 | " \"symbols_uploaded\": [\n" +
158 | " {\n" +
159 | " \"symbol_id\": \"string\",\n" +
160 | " \"platform\": \"string\"\n" +
161 | " }\n" +
162 | " ],\n" +
163 | " \"origin\": \"User\",\n" +
164 | " \"file_name\": \"string\",\n" +
165 | " \"file_size\": 0,\n" +
166 | " \"timestamp\": \"2019-11-17T12:12:06.701Z\"\n" +
167 | "}"));
168 |
169 | // When
170 | final UploadRequest actual = task.execute(uploadRequest).get();
171 |
172 | // Then
173 | assertThat(actual)
174 | .isEqualTo(uploadRequest);
175 | }
176 |
177 | @Test
178 | public void should_ReturnException_When_SymbolRequestIsUnSuccessful() {
179 | // Given
180 | final UploadRequest uploadRequest = baseRequest.newBuilder()
181 | .setUploadDomain("upload-domain")
182 | .setPackageAssetId("package_asset_id")
183 | .setToken("token")
184 | .setSymbolUploadId("symbol_upload_id")
185 | .build();
186 | mockWebServer.enqueue(new MockResponse().setResponseCode(200));
187 | mockWebServer.enqueue(new MockResponse().setResponseCode(400));
188 |
189 | // When
190 | final ThrowingRunnable throwingRunnable = () -> task.execute(uploadRequest).get();
191 |
192 | // Then
193 | final ExecutionException exception = assertThrows(ExecutionException.class, throwingRunnable);
194 | assertThat(exception).hasCauseThat().isInstanceOf(AppCenterException.class);
195 | assertThat(exception).hasCauseThat().hasMessageThat().contains("Finishing symbol release unsuccessful: ");
196 | }
197 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/task/internal/PollForReleaseTaskTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.task.internal;
2 |
3 | import hudson.ProxyConfiguration;
4 | import hudson.model.TaskListener;
5 | import hudson.util.Secret;
6 | import io.jenkins.plugins.appcenter.AppCenterException;
7 | import io.jenkins.plugins.appcenter.api.AppCenterServiceFactory;
8 | import io.jenkins.plugins.appcenter.task.request.UploadRequest;
9 | import okhttp3.mockwebserver.MockResponse;
10 | import okhttp3.mockwebserver.MockWebServer;
11 | import org.junit.Before;
12 | import org.junit.Rule;
13 | import org.junit.Test;
14 | import org.junit.function.ThrowingRunnable;
15 | import org.junit.runner.RunWith;
16 | import org.mockito.Mock;
17 | import org.mockito.junit.MockitoJUnitRunner;
18 |
19 | import java.io.PrintStream;
20 | import java.util.concurrent.ExecutionException;
21 |
22 | import static com.google.common.truth.Truth.assertThat;
23 | import static org.junit.Assert.assertThrows;
24 | import static org.mockito.BDDMockito.given;
25 |
26 | @RunWith(MockitoJUnitRunner.class)
27 | public class PollForReleaseTaskTest {
28 |
29 | @Rule
30 | public MockWebServer mockWebServer = new MockWebServer();
31 |
32 | @Mock
33 | TaskListener mockTaskListener;
34 |
35 | @Mock
36 | PrintStream mockLogger;
37 |
38 | @Mock
39 | ProxyConfiguration mockProxyConfig;
40 |
41 | private UploadRequest baseRequest;
42 |
43 | private PollForReleaseTask task;
44 |
45 | @Before
46 | public void setUp() {
47 | baseRequest = new UploadRequest.Builder()
48 | .setOwnerName("owner-name")
49 | .setAppName("app-name")
50 | .build();
51 | given(mockTaskListener.getLogger()).willReturn(mockLogger);
52 | final AppCenterServiceFactory factory = new AppCenterServiceFactory(Secret.fromString("secret-token"), mockWebServer.url("/").toString(), mockProxyConfig);
53 | task = new PollForReleaseTask(mockTaskListener, factory);
54 | }
55 |
56 | @Test
57 | public void should_ReturnException_When_UploadIdIsMissing() {
58 | // Given
59 | final UploadRequest uploadRequest = baseRequest.newBuilder()
60 | .build();
61 |
62 | // When
63 | final ThrowingRunnable throwingRunnable = () -> task.execute(uploadRequest).get();
64 |
65 | // Then
66 | final NullPointerException exception = assertThrows(NullPointerException.class, throwingRunnable);
67 | assertThat(exception).hasMessageThat().contains("uploadId cannot be null");
68 | }
69 |
70 | @Test
71 | public void should_RetryPolling_When_StatusIsStartedOrFinished() throws Exception {
72 | // Given
73 | final UploadRequest uploadRequest = baseRequest.newBuilder()
74 | .setUploadId("upload_id")
75 | .build();
76 | mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody("{\n" +
77 | " \"upload_status\": \"uploadStarted\" \n" +
78 | "}"));
79 | mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody("{\n" +
80 | " \"upload_status\": \"uploadFinished\" \n" +
81 | "}"));
82 | mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody("{\n" +
83 | " \"upload_status\": \"readyToBePublished\",\n" +
84 | " \"release_distinct_id\": 1234\n" +
85 | "}"));
86 |
87 | // When
88 | task.execute(uploadRequest).get();
89 |
90 | // Then
91 | assertThat(mockWebServer.getRequestCount())
92 | .isEqualTo(3);
93 | }
94 |
95 | @Test
96 | public void should_ReturnResponse_When_RequestIsSuccessful() throws Exception {
97 | // Given
98 | final UploadRequest uploadRequest = baseRequest.newBuilder()
99 | .setUploadId("upload_id")
100 | .build();
101 | final UploadRequest expected = uploadRequest.newBuilder().setReleaseId(1234).build();
102 | mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody("{\n" +
103 | " \"upload_status\": \"readyToBePublished\",\n" +
104 | " \"release_distinct_id\": 1234\n" +
105 | "}"));
106 |
107 | // When
108 | final UploadRequest actual = task.execute(uploadRequest).get();
109 |
110 | // Then
111 | assertThat(actual)
112 | .isEqualTo(expected);
113 | }
114 |
115 | @Test
116 | public void should_ReturnException_When_StatusIsMalware() {
117 | // Given
118 | final UploadRequest uploadRequest = baseRequest.newBuilder()
119 | .setUploadId("upload_id")
120 | .build();
121 | mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody("{\n" +
122 | " \"upload_status\": \"malwareDetected\",\n" +
123 | " \"error_details\": \"we found this does it belong to you?\"\n" +
124 | "}"));
125 |
126 | // When
127 | final ThrowingRunnable throwingRunnable = () -> task.execute(uploadRequest).get();
128 |
129 | // Then
130 | final ExecutionException exception = assertThrows(ExecutionException.class, throwingRunnable);
131 | assertThat(exception).hasCauseThat().isInstanceOf(AppCenterException.class);
132 | assertThat(exception).hasCauseThat().hasMessageThat().contains("Polling for app release successful however was rejected by server: we found this does it belong to you?");
133 | }
134 |
135 | @Test
136 | public void should_ReturnException_When_StatusIsError() {
137 | // Given
138 | final UploadRequest uploadRequest = baseRequest.newBuilder()
139 | .setUploadId("upload_id")
140 | .build();
141 | mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody("{\n" +
142 | " \"upload_status\": \"error\",\n" +
143 | " \"error_details\": \"error error error\"\n" +
144 | "}"));
145 |
146 | // When
147 | final ThrowingRunnable throwingRunnable = () -> task.execute(uploadRequest).get();
148 |
149 | // Then
150 | final ExecutionException exception = assertThrows(ExecutionException.class, throwingRunnable);
151 | assertThat(exception).hasCauseThat().isInstanceOf(AppCenterException.class);
152 | assertThat(exception).hasCauseThat().hasMessageThat().contains("Polling for app release successful however was rejected by server: error error error");
153 | }
154 |
155 | @Test
156 | public void should_ReturnException_When_StatusIsUnknown() {
157 | // Given
158 | final UploadRequest uploadRequest = baseRequest.newBuilder()
159 | .setUploadId("upload_id")
160 | .build();
161 | mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody("{\n" +
162 | " \"upload_status\": \"huggobilly\",\n" +
163 | " \"error_details\": \"hubally goobally hobbilly goobilly\"\n" +
164 | "}"));
165 |
166 | // When
167 | final ThrowingRunnable throwingRunnable = () -> task.execute(uploadRequest).get();
168 |
169 | // Then
170 | final ExecutionException exception = assertThrows(ExecutionException.class, throwingRunnable);
171 | assertThat(exception).hasCauseThat().isInstanceOf(AppCenterException.class);
172 | assertThat(exception).hasCauseThat().hasMessageThat().contains("Polling for app release unsuccessful");
173 | }
174 |
175 | @Test
176 | public void should_ReturnException_When_RequestIsUnSuccessful() {
177 | // Given
178 | final UploadRequest uploadRequest = baseRequest.newBuilder()
179 | .setUploadId("upload_id")
180 | .build();
181 | mockWebServer.enqueue(new MockResponse().setResponseCode(400));
182 |
183 | // When
184 | final ThrowingRunnable throwingRunnable = () -> task.execute(uploadRequest).get();
185 |
186 | // Then
187 | final ExecutionException exception = assertThrows(ExecutionException.class, throwingRunnable);
188 | assertThat(exception).hasCauseThat().isInstanceOf(AppCenterException.class);
189 | assertThat(exception).hasCauseThat().hasMessageThat().contains("Polling for app release unsuccessful");
190 | }
191 | }
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/appcenter/util/RemoteFileUtilsTest.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.util;
2 |
3 | import hudson.FilePath;
4 | import org.apache.commons.lang3.SystemUtils;
5 | import org.junit.Before;
6 | import org.junit.Test;
7 | import org.junit.runner.RunWith;
8 | import org.mockito.Mock;
9 | import org.mockito.junit.MockitoJUnitRunner;
10 |
11 | import java.io.File;
12 |
13 | import static com.google.common.truth.Truth.assertThat;
14 | import static io.jenkins.plugins.appcenter.util.TestFileUtil.TEST_FILE_PATH;
15 | import static org.mockito.ArgumentMatchers.anyString;
16 | import static org.mockito.BDDMockito.given;
17 |
18 | @RunWith(MockitoJUnitRunner.class)
19 | public class RemoteFileUtilsTest {
20 |
21 | @Mock
22 | FilePath filePath;
23 |
24 | private RemoteFileUtils remoteFileUtils;
25 |
26 | @Before
27 | public void setUp() {
28 | given(filePath.child(anyString())).willReturn(filePath);
29 | remoteFileUtils = new RemoteFileUtils(filePath);
30 | }
31 |
32 | @Test
33 | public void should_ReturnFile_When_FileExists() {
34 | // Given
35 | final File expected = new File(TEST_FILE_PATH);
36 | given(filePath.getRemote()).willReturn(TEST_FILE_PATH);
37 |
38 | // When
39 | final File remoteFile = remoteFileUtils.getRemoteFile(TEST_FILE_PATH);
40 |
41 | // Then
42 | assertThat(remoteFile).isEqualTo(expected);
43 | }
44 |
45 | @Test
46 | public void should_ReturnFileName_When_FileExists() {
47 | // Given
48 | given(filePath.getRemote()).willReturn(TEST_FILE_PATH);
49 |
50 | // When
51 | final String fileName = remoteFileUtils.getFileName(TEST_FILE_PATH);
52 |
53 | // Then
54 | assertThat(fileName).isEqualTo("xiola.apk");
55 | }
56 |
57 | @Test
58 | public void should_ReturnFileSize_When_FileExists() {
59 | // Given
60 | given(filePath.getRemote()).willReturn(TEST_FILE_PATH);
61 |
62 | // When
63 | final long fileSize = remoteFileUtils.getFileSize(TEST_FILE_PATH);
64 |
65 | // Then
66 | // Windows reports the file size differently so for the sake of simplicity we adjust our assertion here.
67 | assertThat(fileSize).isEqualTo(SystemUtils.IS_OS_UNIX ? 41 : 42);
68 | }
69 |
70 | @Test
71 | public void should_ReturnContentType_When_APK() {
72 | // Given
73 | final String pathToFile = "test.apk";
74 |
75 | // When
76 | final String contentType = remoteFileUtils.getContentType(pathToFile);
77 |
78 | // Then
79 | assertThat(contentType).isEqualTo("application/vnd.android.package-archive");
80 | }
81 |
82 | @Test
83 | public void should_ReturnContentType_When_AAB() {
84 | // Given
85 | final String pathToFile = "test.aab";
86 |
87 | // When
88 | final String contentType = remoteFileUtils.getContentType(pathToFile);
89 |
90 | // Then
91 | assertThat(contentType).isEqualTo("application/vnd.android.package-archive");
92 | }
93 |
94 | @Test
95 | public void should_ReturnContentType_When_MSI() {
96 | // Given
97 | final String pathToFile = "test.msi";
98 |
99 | // When
100 | final String contentType = remoteFileUtils.getContentType(pathToFile);
101 |
102 | // Then
103 | assertThat(contentType).isEqualTo("application/x-msi");
104 | }
105 |
106 | @Test
107 | public void should_ReturnContentType_When_PLIST() {
108 | // Given
109 | final String pathToFile = "test.plist";
110 |
111 | // When
112 | final String contentType = remoteFileUtils.getContentType(pathToFile);
113 |
114 | // Then
115 | assertThat(contentType).isEqualTo("application/xml");
116 | }
117 |
118 | @Test
119 | public void should_ReturnContentType_When_AETX() {
120 | // Given
121 | final String pathToFile = "test.aetx";
122 |
123 | // When
124 | final String contentType = remoteFileUtils.getContentType(pathToFile);
125 |
126 | // Then
127 | assertThat(contentType).isEqualTo("application/c-x509-ca-cert");
128 | }
129 |
130 | @Test
131 | public void should_ReturnContentType_When_CER() {
132 | // Given
133 | final String pathToFile = "test.cer";
134 |
135 | // When
136 | final String contentType = remoteFileUtils.getContentType(pathToFile);
137 |
138 | // Then
139 | assertThat(contentType).isEqualTo("application/pkix-cert");
140 | }
141 |
142 | @Test
143 | public void should_ReturnContentType_When_XAP() {
144 | // Given
145 | final String pathToFile = "test.xap";
146 |
147 | // When
148 | final String contentType = remoteFileUtils.getContentType(pathToFile);
149 |
150 | // Then
151 | assertThat(contentType).isEqualTo("application/x-silverlight-app");
152 | }
153 |
154 | @Test
155 | public void should_ReturnContentType_When_APPX() {
156 | // Given
157 | final String pathToFile = "test.appx";
158 |
159 | // When
160 | final String contentType = remoteFileUtils.getContentType(pathToFile);
161 |
162 | // Then
163 | assertThat(contentType).isEqualTo("application/x-appx");
164 | }
165 |
166 | @Test
167 | public void should_ReturnContentType_When_APPXBUNDLE() {
168 | // Given
169 | final String pathToFile = "test.appxbundle";
170 |
171 | // When
172 | final String contentType = remoteFileUtils.getContentType(pathToFile);
173 |
174 | // Then
175 | assertThat(contentType).isEqualTo("application/x-appxbundle");
176 | }
177 |
178 | @Test
179 | public void should_ReturnContentType_When_APPXUPLOAD() {
180 | // Given
181 | final String pathToFile = "test.appxupload";
182 |
183 | // When
184 | final String contentType = remoteFileUtils.getContentType(pathToFile);
185 |
186 | // Then
187 | assertThat(contentType).isEqualTo("application/x-appxupload");
188 | }
189 |
190 | @Test
191 | public void should_ReturnContentType_When_APPXSYM() {
192 | // Given
193 | final String pathToFile = "test.appxsym";
194 |
195 | // When
196 | final String contentType = remoteFileUtils.getContentType(pathToFile);
197 |
198 | // Then
199 | assertThat(contentType).isEqualTo("application/x-appxupload");
200 | }
201 |
202 | @Test
203 | public void should_ReturnContentType_When_MSIX() {
204 | // Given
205 | final String pathToFile = "test.msix";
206 |
207 | // When
208 | final String contentType = remoteFileUtils.getContentType(pathToFile);
209 |
210 | // Then
211 | assertThat(contentType).isEqualTo("application/x-msix");
212 | }
213 |
214 | @Test
215 | public void should_ReturnContentType_When_MSIXBUNDLE() {
216 | // Given
217 | final String pathToFile = "test.msixbundle";
218 |
219 | // When
220 | final String contentType = remoteFileUtils.getContentType(pathToFile);
221 |
222 | // Then
223 | assertThat(contentType).isEqualTo("application/x-msixbundle");
224 | }
225 |
226 | @Test
227 | public void should_ReturnContentType_When_MSIXUPLOAD() {
228 | // Given
229 | final String pathToFile = "test.msixupload";
230 |
231 | // When
232 | final String contentType = remoteFileUtils.getContentType(pathToFile);
233 |
234 | // Then
235 | assertThat(contentType).isEqualTo("application/x-msixupload");
236 | }
237 |
238 | @Test
239 | public void should_ReturnContentType_When_MSIXSYM() {
240 | // Given
241 | final String pathToFile = "test.msixsym";
242 |
243 | // When
244 | final String contentType = remoteFileUtils.getContentType(pathToFile);
245 |
246 | // Then
247 | assertThat(contentType).isEqualTo("application/x-msixupload");
248 | }
249 |
250 | @Test
251 | public void should_ReturnContentType_When_UNKNOWN() {
252 | // Given
253 | final String pathToFile = "test.foo";
254 |
255 | // When
256 | final String contentType = remoteFileUtils.getContentType(pathToFile);
257 |
258 | // Then
259 | assertThat(contentType).isEqualTo("application/octet-stream");
260 | }
261 | }
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/appcenter/task/internal/UploadAppToResourceTask.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.appcenter.task.internal;
2 |
3 | import hudson.model.TaskListener;
4 | import io.jenkins.plugins.appcenter.AppCenterException;
5 | import io.jenkins.plugins.appcenter.AppCenterLogger;
6 | import io.jenkins.plugins.appcenter.api.AppCenterServiceFactory;
7 | import io.jenkins.plugins.appcenter.task.request.UploadRequest;
8 | import io.jenkins.plugins.appcenter.util.RemoteFileUtils;
9 | import okhttp3.RequestBody;
10 | import okio.Buffer;
11 | import okio.BufferedSource;
12 | import okio.Okio;
13 |
14 | import javax.annotation.Nonnull;
15 | import javax.inject.Inject;
16 | import javax.inject.Singleton;
17 | import java.io.File;
18 | import java.io.IOException;
19 | import java.io.PrintStream;
20 | import java.util.concurrent.CompletableFuture;
21 |
22 | import static java.util.Objects.requireNonNull;
23 |
24 | @Singleton
25 | public final class UploadAppToResourceTask implements AppCenterTask, AppCenterLogger {
26 |
27 | private static final long serialVersionUID = 1L;
28 | private static final int MAX_NON_CHUNKED_UPLOAD_SIZE = (1024 * 1024) * 256; // 256 MB in bytes
29 |
30 | @Nonnull
31 | private final TaskListener taskListener;
32 | @Nonnull
33 | private final AppCenterServiceFactory factory;
34 | @Nonnull
35 | private final RemoteFileUtils remoteFileUtils;
36 |
37 | @Inject
38 | UploadAppToResourceTask(@Nonnull final TaskListener taskListener,
39 | @Nonnull final AppCenterServiceFactory factory,
40 | @Nonnull final RemoteFileUtils remoteFileUtils) {
41 | this.taskListener = taskListener;
42 | this.factory = factory;
43 | this.remoteFileUtils = remoteFileUtils;
44 | }
45 |
46 | @Nonnull
47 | @Override
48 | public CompletableFuture execute(@Nonnull UploadRequest request) {
49 | if (request.symbolUploadUrl == null) {
50 | return uploadApp(request);
51 | } else {
52 | return uploadApp(request)
53 | .thenCompose(this::uploadSymbols);
54 | }
55 | }
56 |
57 | @Nonnull
58 | private CompletableFuture uploadApp(@Nonnull UploadRequest request) {
59 | requireNonNull(request.uploadDomain, "uploadDomain cannot be null");
60 | requireNonNull(request.packageAssetId, "packageAssetId cannot be null");
61 | requireNonNull(request.token, "token cannot be null");
62 | final Integer chunkSize = requireNonNull(request.chunkSize, "chunkSize cannot be null");
63 |
64 | log("Uploading app to resource.");
65 |
66 | final CompletableFuture future = new CompletableFuture<>();
67 |
68 | int offset = 0;
69 | int blockNumber = 1;
70 | calculateChunks(request, offset, chunkSize, blockNumber, future);
71 |
72 | return future;
73 | }
74 |
75 | private void calculateChunks(@Nonnull UploadRequest request, int offset, int chunkSize, int blockNumber, @Nonnull CompletableFuture future) {
76 | // TODO: Retrofit (via OkHttp) is supposed to be able to do this natively if you set the contentLength to -1. Investigate
77 | final String url = getUrl(request);
78 | final File file = remoteFileUtils.getRemoteFile(request.pathToApp);
79 | final long fileSize = file.length();
80 | final int noOfBlocks = (int) Math.ceil((double) fileSize / chunkSize);
81 |
82 | try (final BufferedSource bufferedSource = Okio.buffer(Okio.source(file))) {
83 | bufferedSource.skip(offset);
84 | final Buffer buffer = new Buffer();
85 | bufferedSource.request(chunkSize);
86 | bufferedSource.read(buffer, chunkSize);
87 | final RequestBody requestFile = RequestBody.create(buffer.readByteArray(), null);
88 | upload(request, offset, chunkSize, url, requestFile, blockNumber, noOfBlocks, future);
89 | } catch (IOException e) {
90 | final AppCenterException exception = logFailure("Upload app to resource unsuccessful", e);
91 | future.completeExceptionally(exception);
92 | }
93 | }
94 |
95 | private void upload(@Nonnull UploadRequest request, int offset, int chunkSize, @Nonnull String url, @Nonnull RequestBody requestFile, int blockNumber, int noOfBlocks, @Nonnull CompletableFuture future) {
96 | factory.createAppCenterService()
97 | .uploadApp(url + "&block_number=" + blockNumber, requestFile)
98 | .whenComplete((responseBody, throwable) -> {
99 | if (throwable != null) {
100 | final AppCenterException exception = logFailure("Upload app to resource unsuccessful", throwable);
101 | future.completeExceptionally(exception);
102 | } else {
103 | log(String.format("Upload app to resource chunk %1$d / %2$d successful.", blockNumber, noOfBlocks));
104 | if (blockNumber == noOfBlocks) {
105 | future.complete(request);
106 | } else {
107 | calculateChunks(request, offset + chunkSize, chunkSize, blockNumber + 1, future);
108 | }
109 |
110 | }
111 | });
112 | }
113 |
114 | @Nonnull
115 | private String getUrl(@Nonnull UploadRequest request) {
116 | return String.format("%1$s/upload/upload_chunk/%2$s?token=%3$s", request.uploadDomain, request.packageAssetId, request.token);
117 | }
118 |
119 | @Nonnull
120 | private CompletableFuture uploadSymbols(@Nonnull UploadRequest request) {
121 | final String pathToDebugSymbols = request.pathToDebugSymbols;
122 | final String symbolUploadUrl = requireNonNull(request.symbolUploadUrl, "symbolUploadUrl cannot be null");
123 |
124 | final File file = remoteFileUtils.getRemoteFile(pathToDebugSymbols);
125 | if (file.length() > MAX_NON_CHUNKED_UPLOAD_SIZE) {
126 | return uploadSymbolsChunked(request, symbolUploadUrl, file);
127 | } else {
128 | return uploadSymbolsComplete(request, symbolUploadUrl, file);
129 | }
130 | }
131 |
132 | @Nonnull
133 | private CompletableFuture uploadSymbolsChunked(@Nonnull UploadRequest request, @Nonnull String symbolUploadUrl, @Nonnull File file) {
134 | log("Uploading symbols to resource chunked.");
135 |
136 | final CompletableFuture future = new CompletableFuture<>();
137 |
138 | CompletableFuture.runAsync(() -> factory.createBlobUploadService(symbolUploadUrl).uploadFromFile(file.getPath(), true))
139 | .whenComplete((responseBody, throwable) -> {
140 | if (throwable != null) {
141 | final AppCenterException exception = logFailure("Upload symbols to resource chunked unsuccessful: ", throwable);
142 | future.completeExceptionally(exception);
143 | } else {
144 | log("Upload symbols to resource chunked successful.");
145 | future.complete(request);
146 | }
147 | });
148 |
149 | return future;
150 | }
151 |
152 | @Nonnull
153 | private CompletableFuture uploadSymbolsComplete(@Nonnull UploadRequest request, @Nonnull String symbolUploadUrl, @Nonnull File file) {
154 | log("Uploading all symbols at once to resource.");
155 |
156 | final CompletableFuture future = new CompletableFuture<>();
157 | final RequestBody requestFile = RequestBody.create(file, null);
158 |
159 | factory.createUploadService(symbolUploadUrl)
160 | .uploadSymbols(symbolUploadUrl, requestFile)
161 | .whenComplete((responseBody, throwable) -> {
162 | if (throwable != null) {
163 | final AppCenterException exception = logFailure("Upload symbols to resource unsuccessful: ", throwable);
164 | future.completeExceptionally(exception);
165 | } else {
166 | log("Upload symbols to resource successful.");
167 | future.complete(request);
168 | }
169 | });
170 |
171 | return future;
172 | }
173 |
174 |
175 | @Override
176 | public PrintStream getLogger() {
177 | return taskListener.getLogger();
178 | }
179 | }
--------------------------------------------------------------------------------