├── .github
└── workflows
│ └── ci-cd.yml
├── .gitignore
├── CHANGES.md
├── LICENSE
├── README.md
├── pom.xml
├── settings.xml
├── src
├── main
│ ├── java
│ │ └── com
│ │ │ └── tinify
│ │ │ ├── AccountException.java
│ │ │ ├── Client.java
│ │ │ ├── ClientException.java
│ │ │ ├── ConnectionException.java
│ │ │ ├── Exception.java
│ │ │ ├── Options.java
│ │ │ ├── Result.java
│ │ │ ├── ResultMeta.java
│ │ │ ├── ServerException.java
│ │ │ ├── Source.java
│ │ │ ├── TLSContext.java
│ │ │ └── Tinify.java
│ └── resources
│ │ └── cacert.pem
└── test
│ ├── java
│ └── com
│ │ └── tinify
│ │ ├── ClientEndpointTest.java
│ │ ├── ClientErrorTest.java
│ │ ├── ClientTest.java
│ │ ├── Integration.java
│ │ ├── ResultMetaTest.java
│ │ ├── ResultTest.java
│ │ ├── SourceTest.java
│ │ └── TinifyTest.java
│ └── resources
│ ├── dummy.png
│ └── voormedia.png
└── update-cacert.sh
/.github/workflows/ci-cd.yml:
--------------------------------------------------------------------------------
1 | name: Java CI/CD
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | matrix:
10 | java: [11, 17, 21]
11 | distribution: ["temurin", "adopt"]
12 | os: [ubuntu-latest, macOS-latest, windows-latest]
13 |
14 | steps:
15 | - uses: actions/checkout@v4
16 | - name: Set up JDK ${{ matrix.java }}
17 | uses: actions/setup-java@v4
18 | with:
19 | distribution: ${{ matrix.distribution }}
20 | java-version: ${{ matrix.java }}
21 | cache: 'maven'
22 | - name: Build with Maven
23 | run: mvn test --file pom.xml -B
24 |
25 | test-oracle:
26 | runs-on: ${{ matrix.os }}
27 | strategy:
28 | matrix:
29 | java: [17, 21]
30 | distribution: ["oracle"]
31 | os: [ubuntu-latest, macOS-latest, windows-latest]
32 |
33 | steps:
34 | - uses: actions/checkout@v4
35 | - name: Set up JDK ${{ matrix.java }}
36 | uses: actions/setup-java@v4
37 | with:
38 | distribution: ${{ matrix.distribution }}
39 | java-version: ${{ matrix.java }}
40 | cache: 'maven'
41 | - name: Build with Maven
42 | run: mvn test --file pom.xml -B
43 |
44 | test-java-8:
45 | runs-on: ubuntu-latest
46 | steps:
47 | - uses: actions/checkout@v4
48 | - uses: actions/setup-java@v4
49 | with:
50 | distribution: 'adopt'
51 | java-version: '8'
52 | cache: 'maven'
53 | - name: Build with Maven
54 | run: mvn test --file pom.xml -B
55 |
56 | integration-test:
57 | if: github.event_name == 'push'
58 | runs-on: ubuntu-latest
59 | strategy:
60 | matrix:
61 | java: [11, 17, 21]
62 | os: [ubuntu-latest, macOS-latest, windows-latest]
63 | needs:
64 | - "test"
65 | - "test-java-8"
66 | - "test-oracle"
67 | steps:
68 | - uses: actions/checkout@v4
69 | - uses: actions/setup-java@v4
70 | with:
71 | distribution: 'temurin'
72 | java-version: '${{ matrix.java }}'
73 | cache: 'maven'
74 | - name: Build with Maven
75 | env:
76 | TINIFY_KEY: ${{ secrets.TINIFY_KEY }}
77 | run: mvn -Pintegration integration-test -B
78 |
79 | adopt-integration-test-java-8:
80 | if: github.event_name == 'push'
81 | runs-on: ubuntu-latest
82 | needs:
83 | - "test-java-8"
84 | - "test-oracle"
85 | - "test"
86 | steps:
87 | - uses: actions/checkout@v3
88 | - uses: actions/setup-java@v3
89 | with:
90 | distribution: 'adopt'
91 | java-version: '8'
92 | cache: 'maven'
93 | - name: Build with Maven
94 | env:
95 | TINIFY_KEY: ${{ secrets.TINIFY_KEY }}
96 | run: mvn -Pintegration integration-test -B
97 |
98 | record-dependency-graph:
99 | if: github.event_name == 'push'
100 | runs-on: ubuntu-latest
101 | steps:
102 | - uses: actions/checkout@v3
103 | - name: Submit Dependency Snapshot
104 | uses: advanced-security/maven-dependency-submission-action@v3
105 |
106 | publish:
107 | if: |
108 | github.repository == 'tinify/tinify-java' &&
109 | startsWith(github.ref, 'refs/tags') &&
110 | github.event_name == 'push'
111 | needs:
112 | - "adopt-integration-test-java-8"
113 | - "integration-test"
114 | runs-on: "ubuntu-latest"
115 | steps:
116 | - uses: actions/checkout@v3
117 | - name: Set up JDK ${{ matrix.java }}
118 | uses: actions/setup-java@v3
119 | with:
120 | distribution: 'temurin'
121 | java-version: 11
122 | - name: Setup GPG
123 | run: |
124 | # GPG exported with
125 | # gpg -a --export-secret-keys "info@tinify.com" | base64 > gpg.base64
126 | mkdir -p ./private-keys-v1.d
127 | echo -n "${GPG_KEY_BASE64}" | base64 --decode > ./private-keys-v1.d/gpg.asc
128 | echo -n "${GPG_PASSPHRASE}" | gpg --batch --yes --passphrase-fd 0 --import ./private-keys-v1.d/gpg.asc
129 | gpg -k
130 | env:
131 | GPG_KEY_BASE64: ${{ secrets.GPG_KEY_BASE64 }}
132 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
133 | - name: Check if properly tagged
134 | run: |
135 | PACKAGE_VERSION="$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)";
136 | CURRENT_TAG="${GITHUB_REF#refs/*/}";
137 | if [[ "${PACKAGE_VERSION}" != "${CURRENT_TAG}" ]]; then
138 | >&2 echo "Tag mismatch"
139 | >&2 echo "Version in pom.xml (${PACKAGE_VERSION}) does not match the current tag=${CURRENT_TAG}"
140 | >&2 echo "Skipping deploy"
141 | exit 1;
142 | fi
143 | - name: 'Build & publish'
144 | run: |
145 | mvn clean \
146 | compile \
147 | org.apache.felix:maven-bundle-plugin:bundle \
148 | deploy \
149 | -Dmaven.test.skip=true \
150 | -P release \
151 | --settings settings.xml \
152 | --no-transfer-progress \
153 | --batch-mode
154 | env:
155 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
156 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
157 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
158 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | target
3 | *.iml
4 |
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | ## 1.8.8
2 | * Fixed transient dependencies for okhttp
3 |
4 | ## 1.8.7
5 | * Fixed Import-Package manifest for OSGI deployments
6 |
7 | ## 1.8.6
8 | * Fixed Import-Package manifest for OSGI deployments
9 |
10 | ## 1.8.5
11 | * Upgrade Okhttp to version 4.12.0
12 |
13 | ## 1.8.4
14 | * Upgrade Okio to version 3.7.0
15 |
16 | ## 1.8.3
17 | * Fix for requests with empty body
18 |
19 | ## 1.8.2
20 | * Fixed Import-Package manifest for OSGI deployments
21 |
22 | ## 1.8.0
23 | * Added new property extension().
24 | * Added new methods convert(new Options().with("type", "image/webp")) and
25 | transform(new Options().with("background", "black")).
26 |
27 | ## 1.7.0
28 | * Updated dependent libraries.
29 | * Minimum java version is 1.8.
30 |
31 | ## 1.6.1
32 | * Fixes to depedency paths in OSGi imported packages.
33 |
34 | ## 1.6.0
35 | * As of this version dependencies are bundled making this package a valid OSGi bundle.
36 |
37 | ## 1.5.1
38 | * Properly close response body for requests where body is unused.
39 | * Migrate internals to OkHttp3 (minimum version required is 3.3.0).
40 |
41 | ## 1.5.0
42 | * Retry failed requests by default.
43 |
44 | ## 1.4.1
45 | * Bugfix: Avoid java.security.cert.CertPathValidatorException on Android.
46 |
47 | ## 1.4.0
48 | * Support for HTTP proxies.
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2013-2022 Tinify
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.tinify%22%20AND%20a%3A%22tinify%22)
2 | [ ](https://github.com/tinify/tinify-java/blob/main/LICENSE)
3 | [](https://github.com/tinify/tinify-java/actions/workflows/ci-cd.yml)
4 |
5 | # Tinify API client for Java
6 |
7 | Java client for the Tinify API, used for [TinyPNG](https://tinypng.com) and [TinyJPG](https://tinyjpg.com). Tinify compresses your images intelligently. Read more at [http://tinify.com](http://tinify.com).
8 |
9 | ## Documentation
10 |
11 | [Go to the documentation for the Java client](https://tinypng.com/developers/reference/java).
12 |
13 | ## Installation
14 |
15 | Install the API client via Maven:
16 |
17 | ```xml
18 |
19 | com.tinify
20 | tinify
21 | 1.8.8
22 |
23 | ```
24 |
25 | ## Usage
26 |
27 | ```java
28 | import com.tinify.*;
29 | import java.io.IOException;
30 |
31 | public class Compress {
32 | public static void main(String[] args) throws java.io.IOException {
33 | Tinify.setKey("YOUR_API_KEY");
34 | Tinify.fromFile("unoptimized.png").toFile("optimized.png");
35 | }
36 | }
37 | ```
38 |
39 | ## Running tests
40 |
41 | ```
42 | mvn test
43 | ```
44 |
45 | ### Integration tests
46 |
47 | ```
48 | TINIFY_KEY=$YOUR_API_KEY mvn -Pintegration integration-test
49 | ```
50 |
51 | ## License
52 |
53 | This software is licensed under the MIT License. [View the license](LICENSE).
54 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.tinify
5 | tinify
6 | 1.8.8
7 | Tinify
8 | Java client for the Tinify API. Tinify compresses your images intelligently. Read more at https://tinify.com.
9 | https://tinify.com
10 | bundle
11 |
12 | UTF-8
13 | UTF-8
14 | 4.12.0
15 | 2.9.0
16 |
17 |
18 |
19 | MIT License
20 | http://www.opensource.org/licenses/mit-license.php
21 |
22 |
23 |
24 |
25 | Jippe Holwerda
26 | jippeholwerda@voormedia.com
27 | Voormedia B.V.
28 | http://voormedia.com
29 |
30 |
31 | Jari van den Berg
32 | jarivandenberg@voormedia.com
33 | Voormedia B.V.
34 | http://voormedia.com
35 |
36 |
37 | Rolf Timmermans
38 | rolftimmermans@voormedia.com
39 | Voormedia B.V.
40 | http://voormedia.com
41 |
42 |
43 |
44 | scm:git:https://github.com/tinify/tinify-java.git
45 | scm:git:https://github.com/tinify/tinify-java.git
46 | scm:git:https://github.com/tinify/tinify-java.git
47 |
48 |
49 |
50 | ossrh
51 | https://oss.sonatype.org/content/repositories/snapshots
52 |
53 |
54 |
55 |
56 | release
57 |
58 |
59 |
60 | org.apache.maven.plugins
61 | maven-source-plugin
62 | 3.2.0
63 |
64 |
65 | attach-sources
66 |
67 | jar-no-fork
68 |
69 |
70 |
71 |
72 |
73 | org.apache.maven.plugins
74 | maven-javadoc-plugin
75 | 3.3.1
76 |
77 |
78 | attach-javadocs
79 |
80 | jar
81 |
82 |
83 |
84 |
85 |
86 | org.apache.maven.plugins
87 | maven-gpg-plugin
88 | 3.0.1
89 |
90 |
91 | sign-artifacts
92 | verify
93 |
94 | sign
95 |
96 |
97 |
98 |
99 | --pinentry-mode
100 | loopback
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | integration
111 |
112 |
113 |
114 | org.apache.maven.plugins
115 | maven-surefire-plugin
116 | 3.0.0
117 |
118 | true
119 |
120 |
121 |
122 | integration-tests
123 | integration-test
124 |
125 | test
126 |
127 |
128 | false
129 |
130 | **/Integration.java
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 | com.squareup.okhttp3
143 | okhttp
144 | ${okhttp.version}
145 | compile
146 |
147 |
148 | org.jetbrains.kotlin
149 | kotlin-stdlib-jdk8
150 |
151 |
152 |
153 |
154 | com.squareup.okio
155 | okio
156 | 3.7.0
157 | compile
158 |
159 |
160 | com.google.code.gson
161 | gson
162 | 2.9.0
163 | compile
164 |
165 |
166 | org.jmockit
167 | jmockit
168 | 1.49
169 | test
170 |
171 |
172 | junit
173 | junit
174 | 4.13.2
175 | test
176 |
177 |
178 | org.hamcrest
179 | hamcrest-all
180 | 1.3
181 | test
182 |
183 |
184 | com.squareup.okhttp3
185 | mockwebserver
186 | ${okhttp.version}
187 | test
188 |
189 |
190 | commons-codec
191 | commons-codec
192 | 1.15
193 | test
194 |
195 |
196 |
197 |
198 |
199 | org.apache.maven.plugins
200 | maven-dependency-plugin
201 | 2.5.1
202 |
203 |
204 | copy-agent
205 | process-test-classes
206 |
207 | copy
208 |
209 |
210 |
211 |
212 | org.jmockit
213 | jmockit
214 | 1.49
215 | ${project.build.directory}/agents
216 | jmockit-1.49.jar
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 | org.apache.maven.plugins
225 | maven-surefire-plugin
226 | 3.0.0
227 |
228 | -javaagent:"${project.build.directory}/agents/jmockit-1.49.jar"
229 |
230 |
231 |
232 | org.apache.maven.plugins
233 | maven-compiler-plugin
234 | 3.11.0
235 |
236 | 1.8
237 | 1.8
238 |
239 | -Xlint
240 |
241 |
242 |
243 |
244 | org.apache.maven.plugins
245 | maven-jar-plugin
246 | 3.2.2
247 |
248 |
249 |
250 | true
251 | true
252 |
253 |
254 |
255 |
256 |
257 | org.sonatype.plugins
258 | nexus-staging-maven-plugin
259 | 1.6.13
260 | true
261 |
262 | ossrh
263 | https://oss.sonatype.org/
264 | true
265 |
266 |
267 |
268 | org.apache.felix
269 | maven-bundle-plugin
270 | 3.5.1
271 | true
272 |
273 |
274 | !com.tinify.*,!org.bouncycastle.jsse.*,!com.experian.b2b.global.mvdam-project.*,!android.*,!javax.annotation.meta.*,!org.conscrypt.*;resolution:=optional,*
275 |
276 |
277 | *;scope=compile|runtime
278 |
279 | true
280 |
281 |
282 |
283 |
284 | org.apache.maven.plugins
285 | maven-dependency-plugin
286 | 3.6.1
287 |
288 |
289 |
290 |
291 |
--------------------------------------------------------------------------------
/settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ossrh
5 | ${env.SONATYPE_USERNAME}
6 | ${env.SONATYPE_PASSWORD}
7 |
8 |
9 |
10 |
11 | ossrh
12 |
13 | true
14 |
15 |
16 | 14B5D60C
17 | ${env.GPG_PASSPHRASE}
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/main/java/com/tinify/AccountException.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | public class AccountException extends Exception {
4 | public AccountException() {
5 | super();
6 | }
7 |
8 | public AccountException(final Throwable t) {
9 | super(t);
10 | }
11 |
12 | public AccountException(final String message) {
13 | super(message);
14 | }
15 |
16 | public AccountException(final String message, final String type, final int status) {
17 | super(message, type, status);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/tinify/Client.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | import com.google.gson.Gson;
4 |
5 | import okhttp3.OkHttpClient;
6 | import okhttp3.Authenticator;
7 | import okhttp3.Credentials;
8 | import okhttp3.Headers;
9 | import okhttp3.HttpUrl;
10 | import okhttp3.MediaType;
11 | import okhttp3.Request;
12 | import okhttp3.RequestBody;
13 | import okhttp3.ResponseBody;
14 | import okhttp3.Route;
15 |
16 | import java.io.IOException;
17 | import java.net.Proxy;
18 | import java.net.InetSocketAddress;
19 | import java.net.Proxy.Type;
20 | import java.net.URL;
21 | import java.util.concurrent.TimeUnit;
22 |
23 | public class Client {
24 | public class Response {
25 | public Headers headers;
26 | public byte[] body;
27 |
28 | public Response(Headers headers, byte[] body) {
29 | this.headers = headers;
30 | this.body = body;
31 | }
32 | }
33 |
34 | private OkHttpClient client;
35 | private String credentials;
36 | private String userAgent;
37 |
38 | public static final MediaType JSON
39 | = MediaType.parse("application/json; charset=utf-8");
40 |
41 | public static final String API_ENDPOINT = "https://api.tinify.com";
42 |
43 | public static final short RETRY_COUNT = 1;
44 | public static final short RETRY_DELAY = 500;
45 |
46 | public static final String USER_AGENT = "Tinify/"
47 | + Client.class.getPackage().getImplementationVersion()
48 | + " Java/" + System.getProperty("java.version")
49 | + " (" + System.getProperty("java.vendor")
50 | + ", " + System.getProperty("os.name") + " " + System.getProperty("os.arch") + ")";
51 |
52 | public enum Method {
53 | POST,
54 | GET
55 | }
56 |
57 | public Client(final String key) {
58 | this(key, null, null);
59 | }
60 |
61 | public Client(final String key, final String appIdentifier) {
62 | this(key, appIdentifier, null);
63 | }
64 |
65 | public Client(final String key, final String appIdentifier, final String proxy) {
66 | OkHttpClient.Builder builder = new OkHttpClient.Builder();
67 |
68 | if (proxy != null) {
69 | try {
70 | URL url = new URL(proxy);
71 | Proxy proxyAddress = createProxyAddress(url);
72 | Authenticator proxyAuthenticator = createProxyAuthenticator(url);
73 |
74 | if (proxyAddress != null) {
75 | builder.proxy(proxyAddress);
76 | if (proxyAuthenticator != null) {
77 | builder.proxyAuthenticator(proxyAuthenticator);
78 | }
79 | }
80 | } catch (java.lang.Exception e) {
81 | throw new ConnectionException("Invalid proxy: " + e.getMessage(), e);
82 | }
83 | }
84 |
85 | builder.sslSocketFactory(TLSContext.socketFactory, TLSContext.trustManager);
86 | builder.connectTimeout(0, TimeUnit.SECONDS);
87 | builder.readTimeout(0, TimeUnit.SECONDS);
88 | builder.writeTimeout(0, TimeUnit.SECONDS);
89 |
90 | client = builder.build();
91 |
92 | credentials = Credentials.basic("api", key);
93 | if (appIdentifier == null) {
94 | userAgent = USER_AGENT;
95 | } else {
96 | userAgent = USER_AGENT + " " + appIdentifier;
97 | }
98 | }
99 |
100 | public final Response request(final Method method, final String endpoint) throws Exception {
101 | /* OkHttp does not support null request bodies if the method is POST. */
102 | if (method.equals(Method.POST)) {
103 | return request(method, endpoint, RequestBody.create(null, new byte[] {}));
104 | } else {
105 | return request(method, endpoint, (RequestBody) null);
106 | }
107 | }
108 |
109 | public final Response request(final Method method, final String endpoint, final Options options) throws Exception {
110 | /* OkHttp does not support null request bodies if the method is POST. */
111 | if (method.equals(Method.GET)) {
112 | return request(method, endpoint, options.isEmpty() ? null : RequestBody.create(JSON, options.toJson()));
113 | } else {
114 | return request(method, endpoint, RequestBody.create(JSON, options.toJson()));
115 | }
116 | }
117 |
118 | public final Response request(final Method method, final String endpoint, final byte[] body) throws Exception {
119 | return request(method, endpoint, RequestBody.create(null, body));
120 | }
121 |
122 | private Proxy createProxyAddress(final URL proxy) {
123 | if (proxy == null) return null;
124 |
125 | String host = proxy.getHost();
126 | int port = proxy.getPort();
127 |
128 | if (port < 0) {
129 | port = proxy.getDefaultPort();
130 | }
131 |
132 | return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
133 | }
134 |
135 | private Authenticator createProxyAuthenticator(final URL proxy) {
136 | if (proxy == null) return null;
137 |
138 | String user = proxy.getUserInfo();
139 | if (user == null) return null;
140 |
141 | final String username, password;
142 | int c = user.indexOf(':');
143 | if (0 < c) {
144 | username = user.substring(0, c);
145 | password = user.substring(c + 1);
146 | } else {
147 | username = user;
148 | password = null;
149 | }
150 |
151 | return new Authenticator() {
152 | @Override public Request authenticate(Route route, okhttp3.Response response) throws IOException {
153 | String credential = Credentials.basic(username, password);
154 | return response.request().newBuilder().header("Proxy-Authorization", credential).build();
155 | }
156 | };
157 | }
158 |
159 | private Response request(final Method method, final String endpoint, final RequestBody body) throws Exception {
160 | HttpUrl url;
161 | if (endpoint.startsWith("https")) {
162 | url = HttpUrl.parse(endpoint);
163 | } else {
164 | url = HttpUrl.parse(API_ENDPOINT + endpoint);
165 | }
166 |
167 | for (short retries = RETRY_COUNT; retries >= 0; retries--) {
168 | if (retries < RETRY_COUNT) {
169 | try {
170 | Thread.sleep(RETRY_DELAY);
171 | } catch(InterruptedException ex) {
172 | Thread.currentThread().interrupt();
173 | }
174 | }
175 |
176 | Request request = new Request.Builder()
177 | .header("Authorization", credentials)
178 | .header("User-Agent", userAgent)
179 | .url(url)
180 | .method(method.toString(), body)
181 | .build();
182 |
183 | Response response;
184 | int status;
185 |
186 | try {
187 | okhttp3.Response res = client.newCall(request).execute();
188 | status = res.code();
189 | response = new Response(res.headers(), res.body().bytes());
190 | } catch (java.lang.Exception e) {
191 | if (retries > 0) continue;
192 | throw new ConnectionException("Error while connecting: " + e.getMessage(), e);
193 | }
194 |
195 | String compressionCount = response.headers.get("Compression-Count");
196 | if (compressionCount != null && !compressionCount.isEmpty()) {
197 | Tinify.setCompressionCount(Integer.valueOf(compressionCount));
198 | }
199 |
200 | if (status >= 200 && status < 300) {
201 | return response;
202 | }
203 |
204 | Exception.Data data;
205 | Gson gson = new Gson();
206 | try {
207 | data = gson.fromJson(new String(response.body), Exception.Data.class);
208 | if (data == null) {
209 | data = new Exception.Data();
210 | data.setMessage("Error while parsing response: received empty body");
211 | data.setError("ParseError");
212 | }
213 | } catch (com.google.gson.JsonParseException e) {
214 | data = new Exception.Data();
215 | data.setMessage("Error while parsing response: " + e.getMessage());
216 | data.setError("ParseError");
217 | }
218 |
219 | if (retries > 0 && status >= 500) continue;
220 | throw Exception.create(
221 | data.getMessage(),
222 | data.getError(),
223 | status);
224 | }
225 |
226 | return null;
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/src/main/java/com/tinify/ClientException.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | public class ClientException extends Exception {
4 | public ClientException() {
5 | super();
6 | }
7 |
8 | public ClientException(final Throwable t) {
9 | super(t);
10 | }
11 |
12 | public ClientException(final String message, final String type, final int status) {
13 | super(message, type, status);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/tinify/ConnectionException.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | public class ConnectionException extends Exception {
4 | public ConnectionException() {
5 | super();
6 | }
7 |
8 | public ConnectionException(final Throwable t) {
9 | super(t);
10 | }
11 |
12 | public ConnectionException(final String message, final Throwable t) { super(message, t); }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/tinify/Exception.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | public class Exception extends RuntimeException {
4 | public static class Data {
5 | private String message;
6 | private String error;
7 |
8 | public void setMessage(final String message) {
9 | this.message = message;
10 | }
11 |
12 | public final String getMessage() {
13 | return message;
14 | }
15 |
16 | public void setError(final String error) {
17 | this.error = error;
18 | }
19 |
20 | public final String getError() {
21 | return error;
22 | }
23 | }
24 |
25 | public static Exception create(final String message, final String type, final int status) {
26 | if (status == 401 || status == 429) {
27 | return new AccountException(message, type, status);
28 | } else if (status >= 400 && status <= 499) {
29 | return new ClientException(message, type, status);
30 | } else if (status >= 500 && status <= 599) {
31 | return new ServerException(message, type, status);
32 | } else {
33 | return new Exception(message, type, status);
34 | }
35 | }
36 |
37 | int status = 0;
38 |
39 | public Exception() {
40 | super();
41 | }
42 |
43 | public Exception(final Throwable t) {
44 | super(t);
45 | }
46 |
47 | public Exception(final String message) {
48 | super(message);
49 | }
50 |
51 | public Exception(final String message, final Throwable t) {
52 | super(message, t);
53 | }
54 |
55 | public Exception(final String message, final String type, final int status) {
56 | super(message + " (HTTP " + status + "/" + type + ")");
57 | this.status = status;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/com/tinify/Options.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | import com.google.gson.Gson;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | public final class Options {
9 | private Map options;
10 |
11 | public Options() {
12 | this.options = new HashMap<>();
13 | }
14 |
15 | public Options(Options options) {
16 | this.options = new HashMap<>(options.options);
17 | }
18 |
19 | public Options with(final String key, final Object value) {
20 | this.options.put(key, value);
21 | return this;
22 | }
23 |
24 | public Options with(final String key, final Options options) {
25 | this.options.put(key, options.options);
26 | return this;
27 | }
28 |
29 | public final String toJson() {
30 | Gson gson = new Gson();
31 | return gson.toJson(this.options);
32 | }
33 |
34 | public boolean isEmpty() {
35 | return this.options.isEmpty();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/tinify/Result.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | import java.io.IOException;
4 | import java.nio.file.Files;
5 | import java.nio.file.Paths;
6 | import okhttp3.Headers;
7 |
8 | public class Result extends ResultMeta {
9 | private final byte[] data;
10 |
11 | public Result(final Headers meta, final byte[] data) {
12 | super(meta);
13 | this.data = data;
14 | }
15 |
16 | public void toFile(final String path) throws IOException {
17 | Files.write(Paths.get(path), toBuffer());
18 | }
19 |
20 | public final byte[] toBuffer() {
21 | return data;
22 | }
23 |
24 | public final Integer size() {
25 | String value = meta.get("content-length");
26 | return (value == null) ? null : Integer.parseInt(value);
27 | }
28 |
29 | public final String mediaType() {
30 | return meta.get("content-type");
31 | }
32 |
33 | public final String extension() {
34 | return meta.get("content-type") == null ? null : meta.get("content-type").replaceFirst(".*/(.*)", "$1");
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/tinify/ResultMeta.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | import okhttp3.Headers;
4 |
5 | public class ResultMeta {
6 | protected final Headers meta;
7 |
8 | public ResultMeta(final Headers meta) {
9 | this.meta = meta;
10 | }
11 |
12 | public final Integer width() {
13 | String value = meta.get("image-width");
14 | return (value == null) ? null : Integer.parseInt(value);
15 | }
16 |
17 | public final Integer height() {
18 | String value = meta.get("image-height");
19 | return (value == null) ? null : Integer.parseInt(value);
20 | }
21 |
22 | public final String location() {
23 | return meta.get("location");
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/tinify/ServerException.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | public class ServerException extends Exception {
4 | public ServerException() {
5 | super();
6 | }
7 |
8 | public ServerException(final Throwable t) {
9 | super(t);
10 | }
11 |
12 | public ServerException(final String message, final String type, final int status) {
13 | super(message, type, status);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/tinify/Source.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | import java.io.IOException;
4 | import java.nio.file.Files;
5 | import java.nio.file.Paths;
6 |
7 | public class Source {
8 | private String url;
9 | private Options commands;
10 |
11 | public static Source fromFile(final String path) throws IOException {
12 | return fromBuffer(Files.readAllBytes(Paths.get(path)));
13 | }
14 |
15 | public static Source fromBuffer(final byte[] buffer) {
16 | Client.Response response = Tinify.client().request(Client.Method.POST, "/shrink", buffer);
17 | return new Source(response.headers.get("location"), new Options());
18 | }
19 |
20 | public static Source fromUrl(final String url) {
21 | Options body = new Options().with("source", new Options().with("url", url));
22 | Client.Response response = Tinify.client().request(Client.Method.POST, "/shrink", body);
23 | return new Source(response.headers.get("location"), new Options());
24 | }
25 |
26 | public Source(final String url, final Options commands) {
27 | this.url = url;
28 | this.commands = commands;
29 | }
30 |
31 | public final Source preserve(final String... options) {
32 | return new Source(url, new Options(commands).with("preserve", options));
33 | }
34 |
35 | public final Source resize(final Options options) {
36 | return new Source(url, new Options(commands).with("resize", options));
37 | }
38 |
39 | public final Source convert(final Options options) {
40 | return new Source(url, new Options(commands).with("convert", options));
41 | }
42 |
43 | public final Source transform(final Options options) {
44 | return new Source(url, new Options(commands).with("transform", options));
45 | }
46 |
47 | public final ResultMeta store(final Options options) {
48 | Options params = new Options(commands).with("store", options);
49 | Client.Response response = Tinify.client().request(Client.Method.POST, url, params);
50 | return new ResultMeta(response.headers);
51 | }
52 |
53 | public final Result result() throws IOException {
54 | Client.Response response;
55 | if (commands == null || commands.isEmpty()) {
56 | response = Tinify.client().request(Client.Method.GET, url);
57 | } else {
58 | response = Tinify.client().request(Client.Method.POST, url, commands);
59 | }
60 |
61 | /* No need for try(Response response = ...): body().bytes() calls close(). */
62 | return new Result(response.headers, response.body);
63 | }
64 |
65 | public void toFile(final String path) throws IOException {
66 | result().toFile(path);
67 | }
68 |
69 | public final byte[] toBuffer() throws IOException {
70 | return result().toBuffer();
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/com/tinify/TLSContext.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | import javax.net.ssl.KeyManager;
4 | import javax.net.ssl.KeyManagerFactory;
5 | import javax.net.ssl.SSLSocketFactory;
6 | import javax.net.ssl.TrustManager;
7 | import javax.net.ssl.TrustManagerFactory;
8 | import javax.net.ssl.X509TrustManager;
9 | import java.io.IOException;
10 | import java.io.InputStream;
11 | import java.security.GeneralSecurityException;
12 | import java.security.KeyStore;
13 | import java.security.cert.Certificate;
14 | import java.security.cert.CertificateFactory;
15 | import java.util.Collection;
16 |
17 | public class TLSContext {
18 | public static SSLSocketFactory socketFactory;
19 | public static X509TrustManager trustManager;
20 |
21 | static {
22 | try {
23 | KeyStore keyStore = certificateKeyStore();
24 |
25 | KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
26 | KeyManagerFactory.getDefaultAlgorithm());
27 | keyManagerFactory.init(keyStore, null);
28 |
29 | TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
30 | TrustManagerFactory.getDefaultAlgorithm());
31 | trustManagerFactory.init(keyStore);
32 |
33 | KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
34 | TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
35 |
36 | if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
37 | throw new AssertionError("Unexpected default trust managers.");
38 | }
39 |
40 | javax.net.ssl.SSLContext sslContext = javax.net.ssl.SSLContext.getInstance("TLS");
41 | sslContext.init(keyManagers, trustManagers, null);
42 |
43 | socketFactory = sslContext.getSocketFactory();
44 | trustManager = (X509TrustManager) trustManagers[0];
45 |
46 | } catch (GeneralSecurityException err) {
47 | throw new AssertionError("Unexpected error while configuring TLS. No TLS available?", err);
48 | }
49 | }
50 |
51 | private static KeyStore certificateKeyStore() throws GeneralSecurityException {
52 | try {
53 | KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
54 |
55 | /* By convention, a null InputStream creates an empty key store. */
56 | keyStore.load(null, null);
57 |
58 | CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
59 | Collection extends Certificate> certificates = certificateFactory.generateCertificates(certificateStream());
60 |
61 | int index = 0;
62 | for (Certificate certificate : certificates) {
63 | String certificateAlias = Integer.toString(index++);
64 | keyStore.setCertificateEntry(certificateAlias, certificate);
65 | }
66 |
67 | if (keyStore.size() == 0) {
68 | /* The resource stream was empty, no certificates were found. */
69 | throw new AssertionError("Unable to load any CA certificates.");
70 | }
71 |
72 | return keyStore;
73 | } catch (IOException err) {
74 | throw new AssertionError(err);
75 | }
76 | }
77 |
78 | private static InputStream certificateStream() throws IOException {
79 | return TLSContext.class.getResourceAsStream("/cacert.pem");
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/com/tinify/Tinify.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | import java.io.IOException;
4 | import java.net.URL;
5 |
6 | public class Tinify {
7 | private static String key;
8 | private static String appIdentifier;
9 | private static String proxy;
10 | private static int compressionCount = 0;
11 | private static Client client;
12 |
13 | public static Client client() {
14 | if (key == null) {
15 | throw new AccountException("Provide an API key with Tinify.setKey(...)");
16 | }
17 | if (client != null) {
18 | return client;
19 | } else {
20 | synchronized (Tinify.class) {
21 | if (client == null) {
22 | client = new Client(key, appIdentifier, proxy);
23 | }
24 | }
25 | return client;
26 | }
27 | }
28 |
29 | public static void setKey(final String key) {
30 | Tinify.key = key;
31 | client = null;
32 | }
33 |
34 | public static void setProxy(final String proxy) {
35 | Tinify.proxy = proxy;
36 | client = null;
37 | }
38 |
39 | public static void setAppIdentifier(final String identifier) {
40 | Tinify.appIdentifier = identifier;
41 | client = null;
42 | }
43 |
44 | public static Source fromFile(final String path) throws IOException {
45 | return Source.fromFile(path);
46 | }
47 |
48 | public static Source fromBuffer(final byte[] buffer) {
49 | return Source.fromBuffer(buffer);
50 | }
51 |
52 | public static Source fromUrl(final String url) {
53 | return Source.fromUrl(url);
54 | }
55 |
56 | public static boolean validate() {
57 | try {
58 | client().request(Client.Method.POST, "/shrink");
59 | } catch (AccountException ex) {
60 | if (ex.status == 429) return true;
61 | throw ex;
62 | } catch (ClientException ex) {
63 | return true;
64 | }
65 | return false;
66 | }
67 |
68 | public static String key() {
69 | return key;
70 | }
71 |
72 | public static String proxy() {
73 | return proxy;
74 | }
75 |
76 | public static String appIdentifier() {
77 | return appIdentifier;
78 | }
79 |
80 | public static void setCompressionCount(final int count) {
81 | compressionCount = count;
82 | }
83 |
84 | public static int compressionCount() {
85 | return compressionCount;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/test/java/com/tinify/ClientEndpointTest.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | import okhttp3.OkHttpClient;
4 | import okhttp3.Request;
5 | import mockit.Expectations;
6 | import mockit.Mocked;
7 | import mockit.Verifications;
8 | import org.junit.Before;
9 | import org.junit.Test;
10 |
11 | import java.io.IOException;
12 |
13 | import static org.junit.Assert.assertEquals;
14 |
15 | public class ClientEndpointTest {
16 | Client subject;
17 | String key = "key";
18 |
19 | @Mocked
20 | OkHttpClient httpClient;
21 |
22 | @Before
23 | public void setup() throws IOException {
24 | subject = new Client(key);
25 | }
26 |
27 | @Test
28 | public void requestShouldAddApiEndpoint() throws Exception {
29 | new Expectations() {{
30 | httpClient.newCall((Request) any);
31 | result = new ConnectionException();
32 | }};
33 |
34 | try {
35 | subject.request(Client.Method.POST, "/shrink", new byte[] {});
36 | } catch(Exception e) {
37 | // not interested in result
38 | }
39 | new Verifications() {{
40 | Request request;
41 | httpClient.newCall(request = withCapture());
42 | assertEquals("https://api.tinify.com/shrink", request.url().toString());
43 | }};
44 | }
45 |
46 | @Test
47 | public void requestShouldNotAddApiEndpoint() throws Exception {
48 | final String url = "https://api.tinify.com/output/13259adadfq3.png";
49 |
50 | new Expectations() {{
51 | httpClient.newCall((Request) any);
52 | result = new ConnectionException();
53 | }};
54 |
55 | try {
56 | subject.request(Client.Method.POST, url, new byte[] {});
57 | } catch(Exception e) {
58 | // not interested in result
59 | }
60 |
61 | new Verifications() {{
62 | Request request;
63 | httpClient.newCall(request = withCapture());
64 | assertEquals(url, request.url().toString());
65 | }};
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/test/java/com/tinify/ClientErrorTest.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | import okhttp3.mockwebserver.MockWebServer;
4 | import org.junit.After;
5 | import org.junit.Before;
6 | import java.io.IOException;
7 | import java.util.logging.Level;
8 | import java.util.logging.Logger;
9 |
10 | public class ClientErrorTest {
11 | Client subject;
12 | MockWebServer server;
13 | String key = "key";
14 |
15 | @Before
16 | public void setup() throws IOException {
17 | Logger.getLogger(MockWebServer.class.getName()).setLevel(Level.WARNING);
18 | server = new MockWebServer();
19 | subject = new Client(key);
20 | server.start();
21 | }
22 |
23 | @After
24 | public void tearDown() throws IOException {
25 | server.shutdown();
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/test/java/com/tinify/ClientTest.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | import com.google.gson.Gson;
4 | import mockit.Invocation;
5 | import mockit.Mock;
6 | import mockit.MockUp;
7 | import okhttp3.HttpUrl;
8 | import okhttp3.Call;
9 | import okhttp3.Response;
10 | import okhttp3.mockwebserver.MockResponse;
11 | import okhttp3.mockwebserver.MockWebServer;
12 | import okhttp3.mockwebserver.RecordedRequest;
13 | import org.apache.commons.codec.binary.Base64;
14 | import org.junit.After;
15 | import org.junit.Before;
16 | import org.junit.Test;
17 |
18 | import java.io.IOException;
19 | import java.net.URISyntaxException;
20 | import java.nio.file.Files;
21 | import java.nio.file.Paths;
22 | import java.util.HashMap;
23 | import java.util.concurrent.TimeUnit;
24 | import java.util.logging.Level;
25 | import java.util.logging.Logger;
26 |
27 | import static org.junit.Assert.assertEquals;
28 | import static org.junit.Assert.fail;
29 |
30 | public class ClientTest {
31 | Client subject;
32 | MockWebServer server;
33 | String key = "key";
34 |
35 | @Before
36 | public void setup() throws IOException {
37 | Logger.getLogger(MockWebServer.class.getName()).setLevel(Level.WARNING);
38 |
39 | server = new MockWebServer();
40 | server.start();
41 | subject = new Client(key);
42 | new MockUp()
43 | {
44 | @Mock
45 | @SuppressWarnings("unused")
46 | HttpUrl parse(Invocation inv, String url)
47 | {
48 | if (url.contains("localhost")) {
49 | return inv.proceed();
50 | } else {
51 | return new HttpUrl.Builder()
52 | .scheme("http")
53 | .host(server.getHostName())
54 | .port(server.getPort())
55 | .encodedPath("/shrink")
56 | .build();
57 | }
58 | }
59 | };
60 | }
61 |
62 | @After
63 | public void tearDown() throws IOException {
64 | server.shutdown();
65 | }
66 |
67 | public void enqueuShrink() {
68 | server.enqueue(new MockResponse()
69 | .setResponseCode(201)
70 | .addHeader("Location", "https://api.tinify.com/foo.png")
71 | .addHeader("Compression-Count", 12));
72 | }
73 |
74 | @Test
75 | public void requestWhenValidShouldIssueRequest() throws Exception, InterruptedException {
76 | enqueuShrink();
77 | subject.request(Client.Method.POST, "/shrink");
78 | String credentials = new String(Base64.encodeBase64(("api:" + key).getBytes()));
79 |
80 | RecordedRequest request = server.takeRequest(5, TimeUnit.SECONDS);
81 | assertEquals("Basic " + credentials, request.getHeader("Authorization"));
82 | }
83 |
84 | @Test
85 | public void requestWhenValidShouldIssueRequestToEndpoint() throws Exception, InterruptedException {
86 | enqueuShrink();
87 | subject.request(Client.Method.POST, "/shrink");
88 | RecordedRequest request = server.takeRequest(5, TimeUnit.SECONDS);
89 | assertEquals("/shrink", request.getPath());
90 | }
91 |
92 | @Test
93 | public void requestWhenValidShouldIssueRequestWithMethod() throws Exception, InterruptedException {
94 | enqueuShrink();
95 | subject.request(Client.Method.POST, "/shrink");
96 | RecordedRequest request = server.takeRequest(5, TimeUnit.SECONDS);
97 | assertEquals("POST", request.getMethod());
98 | }
99 |
100 | @Test
101 | public void requestWhenValidShouldReturnResponse() throws Exception, InterruptedException, IOException, URISyntaxException {
102 | enqueuShrink();
103 |
104 | byte[] body = Files.readAllBytes(
105 | Paths.get(getClass().getResource("/voormedia.png").toURI()));
106 |
107 | Client.Response response = subject.request(Client.Method.POST, "/shrink", body);
108 | assertEquals("https://api.tinify.com/foo.png", response.headers.get("Location"));
109 | }
110 |
111 | @Test
112 | public void requestWhenValidShouldIssueRequestWithoutBodyWhenOptionsAreEmpty() throws Exception, InterruptedException {
113 | enqueuShrink();
114 | subject.request(Client.Method.GET, "/shrink", new Options());
115 | RecordedRequest request = server.takeRequest(5, TimeUnit.SECONDS);
116 | assertEquals(0, request.getBody().size());
117 | }
118 |
119 | @Test
120 | public void requestWhenValidShouldIssueRequestWithoutContentTypeWhenOptionsAreEmpty() throws Exception, InterruptedException {
121 | enqueuShrink();
122 | subject.request(Client.Method.GET, "/shrink", new Options());
123 | RecordedRequest request = server.takeRequest(5, TimeUnit.SECONDS);
124 | assertEquals(null, request.getHeader("Content-Type"));
125 | }
126 |
127 | @Test
128 | public void requestWhenValidShouldIssueRequestWithJSONBody() throws Exception, InterruptedException {
129 | enqueuShrink();
130 | subject.request(Client.Method.POST, "/shrink", new Options().with("hello", "world"));
131 | RecordedRequest request = server.takeRequest(5, TimeUnit.SECONDS);
132 | Gson gson = new Gson();
133 | assertEquals("world", gson.fromJson(request.getBody().readUtf8(), HashMap.class).get("hello"));
134 | assertEquals("application/json; charset=utf-8", request.getHeader("Content-Type"));
135 | }
136 |
137 | @Test
138 | public void requestWhenValidShouldIssueRequestWithUserAgent() throws Exception, InterruptedException {
139 | enqueuShrink();
140 | subject.request(Client.Method.POST, "/shrink", new byte[] {});
141 | RecordedRequest request = server.takeRequest(5, TimeUnit.SECONDS);
142 | assertEquals(Client.USER_AGENT, request.getHeader("User-Agent"));
143 | }
144 |
145 | @Test
146 | public void requestWhenValidShouldUpdateCompressionCount() throws Exception {
147 | enqueuShrink();
148 | subject.request(Client.Method.POST, "/shrink", new byte[] {});
149 | assertEquals(12, Tinify.compressionCount());
150 | }
151 |
152 | @Test
153 | public void requestWhenValidWithAppIdShouldIssueRequestWithUserAgent() throws Exception, InterruptedException {
154 | enqueuShrink();
155 | Client client = new Client(key, "TestApp/0.1");
156 | client.request(Client.Method.POST, "/shrink");
157 | RecordedRequest request = server.takeRequest(5, TimeUnit.SECONDS);
158 | assertEquals(Client.USER_AGENT + " TestApp/0.1", request.getHeader("User-Agent"));
159 | }
160 |
161 | @Test
162 | public void requestWhenValidWithProxyShouldIssueRequestWithProxyAuthorization() throws Exception, InterruptedException {
163 | server.enqueue(new MockResponse().setResponseCode(407));
164 | enqueuShrink();
165 | Client client = new Client(key, null, "http://user:pass@" + server.getHostName() + ":" + server.getPort());
166 | client.request(Client.Method.POST, "/shrink");
167 | RecordedRequest request1 = server.takeRequest(5, TimeUnit.SECONDS);
168 | RecordedRequest request2 = server.takeRequest(5, TimeUnit.SECONDS);
169 | assertEquals("Basic dXNlcjpwYXNz", request2.getHeader("Proxy-Authorization"));
170 | }
171 |
172 | @Test
173 | public void requestWithTimeoutOnceShouldReturnResponse() throws Exception {
174 | new MockUp() {
175 | int count = 1;
176 |
177 | @Mock
178 | public Response execute(Invocation inv) throws IOException {
179 | if (count == 0) {
180 | return inv.proceed();
181 | } else {
182 | count--;
183 | throw new java.net.SocketTimeoutException("SocketTimeoutException");
184 | }
185 | }
186 | };
187 |
188 | server.enqueue(new MockResponse()
189 | .setResponseCode(201)
190 | .setBody("ok"));
191 |
192 | Client.Response response = new Client(key).request(Client.Method.POST, "/shrink");
193 | assertEquals("ok", new String(response.body));
194 | }
195 |
196 | @Test(expected = ConnectionException.class)
197 | public void requestWithTimeoutRepeatedlyShouldThrowConnectionException() throws Exception {
198 | new MockUp() {
199 | @Mock
200 | public Response execute(Invocation inv) throws IOException {
201 | throw new java.net.SocketTimeoutException("SocketTimeoutException");
202 | }
203 | };
204 |
205 | new Client(key).request(Client.Method.POST, "http://shrink");
206 | }
207 |
208 | @Test
209 | public void requestWithTimeoutRepeatedlyShouldThrowExceptionWithMessage() throws Exception {
210 | new MockUp() {
211 | @Mock
212 | public Response execute(Invocation inv) throws IOException {
213 | throw new java.net.SocketTimeoutException("SocketTimeoutException");
214 | }
215 | };
216 |
217 | try {
218 | new Client(key).request(Client.Method.POST, "http://shrink");
219 | fail("Expected an Exception to be thrown");
220 | } catch (ConnectionException e) {
221 | assertEquals("Error while connecting: SocketTimeoutException", e.getMessage());
222 | }
223 | }
224 |
225 | @Test
226 | public void requestWithSocketErrorOnceShouldReturnResponse() throws Exception {
227 | new MockUp() {
228 | int count = 1;
229 |
230 | @Mock
231 | public Response execute(Invocation inv) throws IOException {
232 | if (count == 0) {
233 | return inv.proceed();
234 | } else {
235 | count--;
236 | throw new java.net.UnknownHostException("UnknownHostException");
237 | }
238 | }
239 | };
240 |
241 | server.enqueue(new MockResponse()
242 | .setResponseCode(201)
243 | .setBody("ok"));
244 |
245 | Client.Response response = new Client(key).request(Client.Method.POST, "/shrink");
246 | assertEquals("ok", new String(response.body));
247 | }
248 |
249 | @Test(expected = ConnectionException.class)
250 | public void requestWithSocketErrorRepeatedlyShouldThrowConnectionException() throws Exception {
251 | new MockUp() {
252 | @Mock
253 | public Response execute(Invocation inv) throws IOException {
254 | throw new java.net.UnknownHostException("UnknownHostException");
255 | }
256 | };
257 |
258 | new Client(key).request(Client.Method.POST, "http://shrink");
259 | }
260 |
261 | @Test
262 | public void requestWithSocketErrorRepeatedlyShouldThrowExceptionWithMessage() throws Exception {
263 | new MockUp() {
264 | @Mock
265 | public Response execute(Invocation inv) throws IOException {
266 | throw new java.net.UnknownHostException("UnknownHostException");
267 | }
268 | };
269 |
270 | try {
271 | new Client(key).request(Client.Method.POST, "http://shrink");
272 | fail("Expected an Exception to be thrown");
273 | } catch (ConnectionException e) {
274 | assertEquals("Error while connecting: UnknownHostException", e.getMessage());
275 | }
276 | }
277 |
278 | @Test
279 | public void requestWithUnexpectedExceptionOnceShouldReturnResponse() throws Exception {
280 | new MockUp() {
281 | int count = 1;
282 |
283 | @Mock
284 | public Response execute(Invocation inv) throws IOException {
285 | if (count == 0) {
286 | return inv.proceed();
287 | } else {
288 | count--;
289 | throw new RuntimeException("Some exception");
290 | }
291 | }
292 | };
293 |
294 | server.enqueue(new MockResponse()
295 | .setResponseCode(201)
296 | .setBody("ok"));
297 |
298 | Client.Response response = new Client(key).request(Client.Method.POST, "/shrink");
299 | assertEquals("ok", new String(response.body));
300 | }
301 |
302 | @Test(expected = ConnectionException.class)
303 | public void requestWithUnexpectedExceptionRepeatedlyShouldThrowConnectionException() throws Exception {
304 | new MockUp() {
305 | @Mock
306 | public Response execute(Invocation inv) throws IOException {
307 | throw new RuntimeException("Some exception");
308 | }
309 | };
310 |
311 | new Client(key).request(Client.Method.POST, "http://shrink");
312 | }
313 |
314 | @Test
315 | public void requestWithUnexpectedExceptionRepeatedlyShouldThrowExceptionWithMessage() throws Exception {
316 | new MockUp() {
317 | @Mock
318 | public Response execute(Invocation inv) throws IOException {
319 | throw new RuntimeException("Some exception");
320 | }
321 | };
322 |
323 | try {
324 | new Client(key).request(Client.Method.POST, "http://shrink");
325 | fail("Expected an Exception to be thrown");
326 | } catch (ConnectionException e) {
327 | assertEquals("Error while connecting: Some exception", e.getMessage());
328 | }
329 | }
330 |
331 | @Test
332 | public void requestWithServerErrorOnceShouldReturnResponse() throws Exception {
333 | server.enqueue(new MockResponse()
334 | .setResponseCode(584)
335 | .setBody("{'error':'InternalServerError','message':'Oops!'}"));
336 | server.enqueue(new MockResponse()
337 | .setResponseCode(201)
338 | .setBody("ok"));
339 |
340 | Client.Response response = new Client(key).request(Client.Method.POST, "/shrink");
341 | assertEquals("ok", new String(response.body));
342 | }
343 |
344 | @Test(expected = ServerException.class)
345 | public void requestWithServerErrorRepeatedlyShouldThrowServerException() throws Exception {
346 | server.enqueue(new MockResponse()
347 | .setResponseCode(584)
348 | .setBody("{'error':'InternalServerError','message':'Oops!'}"));
349 | server.enqueue(new MockResponse()
350 | .setResponseCode(584)
351 | .setBody("{'error':'InternalServerError','message':'Oops!'}"));
352 |
353 | new Client(key).request(Client.Method.POST, "/shrink");
354 | }
355 |
356 | @Test
357 | public void requestWithServerErrorRepeatedlyShouldThrowExceptionWithMessage() throws Exception {
358 | server.enqueue(new MockResponse()
359 | .setResponseCode(584)
360 | .setBody("{'error':'InternalServerError','message':'Oops!'}"));
361 | server.enqueue(new MockResponse()
362 | .setResponseCode(584)
363 | .setBody("{'error':'InternalServerError','message':'Oops!'}"));
364 |
365 | try {
366 | new Client(key).request(Client.Method.POST, "/shrink");
367 | fail("Expected an Exception to be thrown");
368 | } catch (Exception e) {
369 | assertEquals("Oops! (HTTP 584/InternalServerError)", e.getMessage());
370 | }
371 | }
372 |
373 | @Test
374 | public void requestWithBadServerResponseOnceShouldReturnResponse() throws Exception {
375 | server.enqueue(new MockResponse()
376 | .setResponseCode(543)
377 | .setBody(""));
378 | server.enqueue(new MockResponse()
379 | .setResponseCode(201)
380 | .setBody("ok"));
381 |
382 | Client.Response response = new Client(key).request(Client.Method.POST, "/shrink");
383 | assertEquals("ok", new String(response.body));
384 | }
385 |
386 | @Test(expected = ServerException.class)
387 | public void requestWithBadServerResponseRepeatedlyShouldThrowServerException() throws Exception {
388 | server.enqueue(new MockResponse()
389 | .setResponseCode(543)
390 | .setBody(""));
391 | server.enqueue(new MockResponse()
392 | .setResponseCode(543)
393 | .setBody(""));
394 |
395 | new Client(key).request(Client.Method.POST, "/shrink");
396 | }
397 |
398 | @Test
399 | public void requestWithBlankServerResponseRepeatedlyShouldThrowServerException() throws Exception {
400 | server.enqueue(new MockResponse()
401 | .setResponseCode(543)
402 | .setBody(""));
403 | server.enqueue(new MockResponse()
404 | .setResponseCode(543)
405 | .setBody(""));
406 | try {
407 | new Client(key).request(Client.Method.POST, "/shrink");
408 | fail("Expected an Exception to be thrown");
409 | } catch (Exception e) {
410 | assertEquals("Error while parsing response: received empty body (HTTP 543/ParseError)", e.getMessage());
411 | }
412 | }
413 |
414 | @Test
415 | public void requestWithBadServerResponseRepeatedlyShouldThrowExceptionWithMessage() throws Exception {
416 | server.enqueue(new MockResponse()
417 | .setResponseCode(543)
418 | .setBody(""));
419 | server.enqueue(new MockResponse()
420 | .setResponseCode(543)
421 | .setBody(""));
422 |
423 | try {
424 | new Client(key).request(Client.Method.POST, "/shrink");
425 | fail("Expected an Exception to be thrown");
426 | } catch (Exception e) {
427 | assertEquals("Error while parsing response: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $ (HTTP 543/ParseError)", e.getMessage());
428 | }
429 | }
430 |
431 | @Test(expected = ClientException.class)
432 | public void requestWithClientErrorShouldThrowClientException() throws Exception {
433 | server.enqueue(new MockResponse()
434 | .setResponseCode(492)
435 | .setBody("{'error':'BadRequest','message':'Oops!'}"));
436 |
437 | new Client(key).request(Client.Method.POST, "/shrink");
438 | }
439 |
440 | @Test
441 | public void requestWithClientErrorShouldThrowExceptionWithMessage() throws Exception {
442 | server.enqueue(new MockResponse()
443 | .setResponseCode(492)
444 | .setBody("{'error':'BadRequest','message':'Oops!'}"));
445 |
446 | try {
447 | new Client(key).request(Client.Method.POST, "/shrink");
448 | fail("Expected an Exception to be thrown");
449 | } catch (Exception e) {
450 | assertEquals("Oops! (HTTP 492/BadRequest)", e.getMessage());
451 | }
452 | }
453 |
454 | @Test(expected = AccountException.class)
455 | public void requestWithBadCredentialsShouldThrowAccountException() throws Exception {
456 | server.enqueue(new MockResponse()
457 | .setResponseCode(401)
458 | .setBody("{'error':'Unauthorized','message':'Oops!'}"));
459 |
460 | new Client(key).request(Client.Method.POST, "/shrink");
461 | }
462 |
463 | @Test
464 | public void requestWithBadCredentialsShouldThrowExceptionWithMessage() throws Exception {
465 | server.enqueue(new MockResponse()
466 | .setResponseCode(401)
467 | .setBody("{'error':'Unauthorized','message':'Oops!'}"));
468 |
469 | try {
470 | new Client(key).request(Client.Method.POST, "/shrink");
471 | fail("Expected an Exception to be thrown");
472 | } catch (Exception e) {
473 | assertEquals("Oops! (HTTP 401/Unauthorized)", e.getMessage());
474 | }
475 | }
476 | }
477 |
--------------------------------------------------------------------------------
/src/test/java/com/tinify/Integration.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | import org.junit.BeforeClass;
4 | import org.junit.Test;
5 |
6 | import java.net.URISyntaxException;
7 | import java.nio.file.Files;
8 | import java.nio.file.Path;
9 | import java.io.File;
10 | import java.nio.file.Paths;
11 |
12 | import static org.hamcrest.MatcherAssert.assertThat;
13 | import static org.hamcrest.Matchers.containsString;
14 | import static org.hamcrest.Matchers.equalTo;
15 | import static org.hamcrest.Matchers.greaterThan;
16 | import static org.hamcrest.Matchers.is;
17 | import static org.hamcrest.Matchers.lessThan;
18 | import static org.hamcrest.Matchers.not;
19 |
20 | public class Integration {
21 | private static Source optimized;
22 |
23 | @BeforeClass
24 | public static void setup() throws java.io.IOException, URISyntaxException {
25 | String key = System.getenv().get("TINIFY_KEY");
26 | String proxy = System.getenv().get("TINIFY_PROXY");
27 | if (key == null) {
28 | System.out.println("Set the TINIFY_KEY environment variable.");
29 | System.exit(1);
30 | }
31 |
32 | Tinify.setKey(key);
33 | Tinify.setProxy(proxy);
34 |
35 | String unoptimizedPath = Paths.get(Integration.class.getResource("/voormedia.png").toURI()).toAbsolutePath().toString();
36 | optimized = Tinify.fromFile(unoptimizedPath);
37 | }
38 |
39 | @Test
40 | public void shouldCompressFromFile() throws java.lang.Exception {
41 | Path tempFile = Files.createTempFile("tinify_", null);
42 | tempFile.toFile().deleteOnExit();
43 |
44 | Result result = optimized.result();
45 | result.toFile(tempFile.toString());
46 |
47 | long size = new File(tempFile.toString()).length();
48 | String contents = new String(Files.readAllBytes(Paths.get(tempFile.toString())));
49 |
50 | assertThat(result.width(), is(equalTo(137)));
51 | assertThat(result.height(), is(equalTo(21)));
52 |
53 | assertThat(size, greaterThan((long) 1000));
54 | assertThat(size, lessThan((long) 1500));
55 |
56 | /* width == 137 */
57 | assertThat(contents, containsString(new String(new byte[] {0, 0, 0, (byte)0x89})));
58 | assertThat(contents, not(containsString(("Copyright Voormedia"))));
59 | }
60 |
61 | @Test
62 | public void shouldCompressFromUrl() throws java.lang.Exception {
63 | Path tempFile = Files.createTempFile("tinify_", null);
64 | tempFile.toFile().deleteOnExit();
65 |
66 | optimized = Tinify.fromUrl("https://raw.githubusercontent.com/tinify/tinify-java/master/src/test/resources/voormedia.png");
67 |
68 | Result result = optimized.result();
69 | result.toFile(tempFile.toString());
70 |
71 | long size = new File(tempFile.toString()).length();
72 | String contents = new String(Files.readAllBytes(Paths.get(tempFile.toString())));
73 |
74 | assertThat(result.width(), is(equalTo(137)));
75 | assertThat(result.height(), is(equalTo(21)));
76 |
77 | assertThat(size, greaterThan((long) 1000));
78 | assertThat(size, lessThan((long) 1500));
79 |
80 | /* width == 137 */
81 | assertThat(contents, containsString(new String(new byte[] {0, 0, 0, (byte)0x89})));
82 | assertThat(contents, not(containsString(("Copyright Voormedia"))));
83 | }
84 |
85 | @Test
86 | public void shouldResize() throws java.lang.Exception {
87 | Path tempFile = Files.createTempFile("tinify_", null);
88 | tempFile.toFile().deleteOnExit();
89 |
90 | Options options = new Options()
91 | .with("method", "fit")
92 | .with("width", 50)
93 | .with("height", 20);
94 | Result result = optimized.resize(options).result();
95 | result.toFile(tempFile.toString());
96 |
97 | long size = new File(tempFile.toString()).length();
98 | String contents = new String(Files.readAllBytes(Paths.get(tempFile.toString())));
99 |
100 | assertThat(result.width(), is(equalTo(50)));
101 | assertThat(result.height(), is(equalTo(8)));
102 |
103 | assertThat(size, greaterThan((long) 500));
104 | assertThat(size, lessThan((long) 1000));
105 |
106 | /* width == 50 */
107 | assertThat(contents, containsString(new String(new byte[] {0, 0, 0, (byte)0x32})));
108 | assertThat(contents, not(containsString(("Copyright Voormedia"))));
109 | }
110 |
111 | @Test
112 | public void shouldPreserveMetadata() throws java.lang.Exception {
113 | Path tempFile = Files.createTempFile("tinify_", null);
114 | tempFile.toFile().deleteOnExit();
115 |
116 | Result result = optimized.preserve("copyright", "creation").result();
117 | result.toFile(tempFile.toString());
118 |
119 | long size = new File(tempFile.toString()).length();
120 | String contents = new String(Files.readAllBytes(Paths.get(tempFile.toString())));
121 |
122 | assertThat(result.width(), is(equalTo(137)));
123 | assertThat(result.height(), is(equalTo(21)));
124 |
125 | assertThat(size, greaterThan((long) 1000));
126 | assertThat(size, lessThan((long) 2000));
127 |
128 | /* width == 137 */
129 | assertThat(contents, containsString(new String(new byte[] {0, 0, 0, (byte)0x89})));
130 | assertThat(contents, containsString(("Copyright Voormedia")));
131 | }
132 |
133 | @Test
134 | public void shouldConvertFile() throws java.lang.Exception {
135 | Path tempFile = Files.createTempFile("tinify_", null);
136 | tempFile.toFile().deleteOnExit();
137 |
138 | Result result = optimized.convert(new Options().with("type", "image/webp")).result();
139 | result.toFile(tempFile.toString());
140 |
141 | long size = new File(tempFile.toString()).length();
142 |
143 | assertThat(result.width(), is(equalTo(137)));
144 | assertThat(result.height(), is(equalTo(21)));
145 | assertThat(result.mediaType(), is(equalTo("image/webp")));
146 |
147 | assertThat(size, greaterThan((long) 1000));
148 | assertThat(size, lessThan((long) 2000));
149 | }
150 |
151 | @Test
152 | public void shouldTransformFile() throws java.lang.Exception {
153 | Path tempFile = Files.createTempFile("tinify_", null);
154 | tempFile.toFile().deleteOnExit();
155 | Result result = optimized.transform(new Options().with("background", "black")).result();
156 | result.toFile(tempFile.toString());
157 |
158 | long size = new File(tempFile.toString()).length();
159 | String contents = new String(Files.readAllBytes(Paths.get(tempFile.toString())));
160 |
161 | assertThat(result.width(), is(equalTo(137)));
162 | assertThat(result.height(), is(equalTo(21)));
163 |
164 | assertThat(size, greaterThan((long) 1000));
165 | assertThat(size, lessThan((long) 2000));
166 |
167 | /* width == 137 */
168 | assertThat(contents, containsString(new String(new byte[] {0, 0, 0, (byte)0x89})));
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/src/test/java/com/tinify/ResultMetaTest.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import java.util.HashMap;
7 | import okhttp3.Headers;
8 |
9 | import static org.hamcrest.CoreMatchers.equalTo;
10 | import static org.hamcrest.CoreMatchers.is;
11 | import static org.hamcrest.CoreMatchers.nullValue;
12 | import static org.hamcrest.MatcherAssert.assertThat;
13 |
14 | public class ResultMetaTest {
15 | ResultMeta subject;
16 |
17 | @Before
18 | public void setup() {
19 | HashMap meta = new HashMap<>();
20 | meta.put("Image-Width", "100");
21 | meta.put("Image-Height", "60");
22 | meta.put("Location", "https://example.com/image.png");
23 |
24 | subject = new ResultMeta(Headers.of(meta));
25 | }
26 |
27 | @Test
28 | public void withMetadataWidthShouldReturnImageWidth() {
29 | assertThat(subject.width(), is(equalTo(100)));
30 | }
31 |
32 | @Test
33 | public void withMetadataHeightShouldReturnImageHeight() {
34 | assertThat(subject.height(), is(equalTo(60)));
35 | }
36 |
37 | @Test
38 | public void withMetadataLocationShouldReturnLocation() {
39 | assertThat(subject.location(), is(equalTo("https://example.com/image.png")));
40 | }
41 |
42 | @Test
43 | public void withoutMetadataWidthShouldReturnNull() {
44 | subject = new ResultMeta(Headers.of());
45 | assertThat(subject.width(), is(nullValue()));
46 | }
47 |
48 | @Test
49 | public void withoutMetadataHeightShouldReturnNull() {
50 | subject = new ResultMeta(Headers.of());
51 | assertThat(subject.height(), is(nullValue()));
52 | }
53 |
54 | @Test
55 | public void withoutMetadataLocationShouldReturnNull() {
56 | subject = new ResultMeta(Headers.of());
57 | assertThat(subject.location(), is(nullValue()));
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/test/java/com/tinify/ResultTest.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import java.util.HashMap;
7 | import okhttp3.Headers;
8 |
9 | import static org.hamcrest.CoreMatchers.equalTo;
10 | import static org.hamcrest.CoreMatchers.is;
11 | import static org.hamcrest.CoreMatchers.nullValue;
12 | import static org.hamcrest.MatcherAssert.assertThat;
13 |
14 | public class ResultTest {
15 | Result subject;
16 |
17 | @Before
18 | public void setup() {
19 | HashMap meta = new HashMap<>();
20 | meta.put("Image-Width", "100");
21 | meta.put("Image-Height", "60");
22 | meta.put("Content-Length", "450");
23 | meta.put("Content-Type", "image/png");
24 |
25 | subject = new Result(Headers.of(meta), "image data".getBytes());
26 | }
27 |
28 | @Test
29 | public void withMetaAndDataWidthShouldReturnImageWidth() {
30 | assertThat(subject.width(), is(equalTo(100)));
31 | }
32 |
33 | @Test
34 | public void withMetaAndDataHeightShouldReturnImageHeight() {
35 | assertThat(subject.height(), is(equalTo(60)));
36 | }
37 |
38 | @Test
39 | public void withMetaAndDataLocationShouldReturnNull() {
40 | assertThat(subject.location(), is(nullValue()));
41 | }
42 |
43 | @Test
44 | public void withMetaAndDataSizeShouldReturnContentLength() {
45 | assertThat(subject.size(), is(equalTo(450)));
46 | }
47 |
48 | @Test
49 | public void withMetaAndDataMediaTypeShouldReturnContentType() {
50 | assertThat(subject.mediaType(), is(equalTo("image/png")));
51 | }
52 |
53 | @Test
54 | public void withMetaAndDataToBufferShouldReturnImageData() {
55 | assertThat(subject.toBuffer(), is(equalTo("image data".getBytes())));
56 | }
57 |
58 | @Test
59 | public void withMetaAndDataMediaTypeShouldReturnExtension() {
60 | assertThat(subject.extension(), is(equalTo("png")));
61 | }
62 |
63 | @Test
64 | public void withoutMetaAndDataWidthShouldReturnNull() {
65 | subject = new Result(Headers.of(), null);
66 | assertThat(subject.width(), is(nullValue()));
67 | }
68 |
69 | @Test
70 | public void withoutMetaAndDataHeightShouldReturnNull() {
71 | subject = new Result(Headers.of(), null);
72 | assertThat(subject.height(), is(nullValue()));
73 | }
74 |
75 | @Test
76 | public void withoutMetaAndDataLocationShouldReturnNull() {
77 | subject = new Result(Headers.of(), null);
78 | assertThat(subject.location(), is(nullValue()));
79 | }
80 |
81 | @Test
82 | public void withoutMetaAndDataSizeShouldReturnNull() {
83 | subject = new Result(Headers.of(), null);
84 | assertThat(subject.size(), is(nullValue()));
85 | }
86 |
87 | @Test
88 | public void withoutMetaAndDataContentTypeShouldReturnNull() {
89 | subject = new Result(Headers.of(), null);
90 | assertThat(subject.mediaType(), is(nullValue()));
91 | }
92 |
93 | @Test
94 | public void withoutMetaAndDataToBufferShouldReturnNull() {
95 | subject = new Result(Headers.of(), null);
96 | assertThat(subject.toBuffer(), is(nullValue()));
97 | }
98 |
99 | @Test
100 | public void withoutMetaAndDataMediaTypeShouldReturnNull() {
101 | subject = new Result(Headers.of(), null);
102 | assertThat(subject.extension(), is(nullValue()));
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/test/java/com/tinify/SourceTest.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | import com.google.gson.Gson;
4 | import mockit.Invocation;
5 | import mockit.Mock;
6 | import mockit.MockUp;
7 | import okhttp3.HttpUrl;
8 | import okhttp3.mockwebserver.MockResponse;
9 | import okhttp3.mockwebserver.MockWebServer;
10 | import okhttp3.mockwebserver.RecordedRequest;
11 | import org.junit.After;
12 | import org.junit.Before;
13 | import org.junit.Test;
14 |
15 | import java.io.IOException;
16 | import java.net.URISyntaxException;
17 | import java.nio.file.Files;
18 | import java.nio.file.Path;
19 | import java.nio.file.Paths;
20 | import java.util.Map;
21 | import java.util.concurrent.TimeUnit;
22 | import java.util.logging.Level;
23 | import java.util.logging.Logger;
24 |
25 | import static org.hamcrest.MatcherAssert.assertThat;
26 | import static org.hamcrest.Matchers.equalTo;
27 | import static org.hamcrest.Matchers.is;
28 | import static org.hamcrest.Matchers.isA;
29 | import static org.junit.Assert.assertEquals;
30 |
31 | public class SourceTest {
32 | MockWebServer server;
33 |
34 | @Before
35 | public void setup() throws IOException {
36 | Logger.getLogger(MockWebServer.class.getName()).setLevel(Level.WARNING);
37 |
38 | server = new MockWebServer();
39 | server.start();
40 | new MockUp()
41 | {
42 | @Mock
43 | @SuppressWarnings("unused")
44 | HttpUrl parse(Invocation inv, String url)
45 | {
46 | if (url.contains("localhost")) {
47 | return inv.proceed();
48 | } else {
49 | return new HttpUrl.Builder()
50 | .scheme("http")
51 | .host(server.getHostName())
52 | .port(server.getPort())
53 | .encodedPath("/shrink")
54 | .build();
55 | }
56 | }
57 | };
58 | }
59 |
60 | @After
61 | public void tearDown() throws IOException {
62 | server.shutdown();
63 | }
64 |
65 | public void assertJsonEquals(String expected, String actual)
66 | {
67 | Gson gson = new Gson();
68 | @SuppressWarnings("unchecked")
69 | Map expectedMap = gson.fromJson(expected, Map.class);
70 | @SuppressWarnings("unchecked")
71 | Map actualMap = gson.fromJson(actual, Map.class);
72 |
73 | assertEquals(expectedMap, actualMap);
74 | }
75 |
76 | @Test(expected = AccountException.class)
77 | public void withInvalidApiKeyFromFileShouldThrowAccountException() throws Exception, IOException, URISyntaxException {
78 | Tinify.setKey("invalid");
79 |
80 | server.enqueue(new MockResponse()
81 | .setResponseCode(401)
82 | .setBody("{'error':'Unauthorized','message':'Credentials are invalid'}"));
83 |
84 | String filePath = Paths.get(getClass().getResource("/dummy.png").toURI()).toAbsolutePath().toString();
85 | Source.fromFile(filePath);
86 | }
87 |
88 | @Test(expected = AccountException.class)
89 | public void withInvalidApiKeyFromBufferShouldThrowAccountException() throws Exception, IOException {
90 | Tinify.setKey("invalid");
91 |
92 | server.enqueue(new MockResponse()
93 | .setResponseCode(401)
94 | .setBody("{'error':'Unauthorized','message':'Credentials are invalid'}"));
95 |
96 | Source.fromBuffer("png file".getBytes());
97 | }
98 |
99 | @Test(expected = AccountException.class)
100 | public void withInvalidApiKeyFromUrlShouldThrowAccountException() throws Exception, IOException {
101 | Tinify.setKey("invalid");
102 |
103 | server.enqueue(new MockResponse()
104 | .setResponseCode(401)
105 | .setBody("{'error':'Unauthorized','message':'Credentials are invalid'}"));
106 |
107 | Source.fromUrl("http://example.com/test.jpg");
108 | }
109 |
110 | @Test
111 | public void withValidApiKeyFromFileShouldReturnSource() throws IOException, Exception, URISyntaxException {
112 | Tinify.setKey("valid");
113 |
114 | server.enqueue(new MockResponse()
115 | .setResponseCode(201)
116 | .addHeader("Location", "https://api.tinify.com/some/location")
117 | .addHeader("Compression-Count", 12));
118 |
119 | String filePath = Paths.get(getClass().getResource("/dummy.png").toURI()).toAbsolutePath().toString();
120 | assertThat(Source.fromFile(filePath), isA(Source.class));
121 | }
122 |
123 | @Test
124 | public void withValidApiKeyFromFileShouldReturnSourceWithData() throws IOException, Exception, URISyntaxException {
125 | Tinify.setKey("valid");
126 |
127 | server.enqueue(new MockResponse()
128 | .setResponseCode(201)
129 | .addHeader("Location", "https://api.tinify.com/some/location"));
130 |
131 | server.enqueue(new MockResponse()
132 | .setResponseCode(200)
133 | .setBody("compressed file"));
134 |
135 | String filePath = Paths.get(getClass().getResource("/dummy.png").toURI()).toAbsolutePath().toString();
136 | assertThat(Source.fromFile(filePath).toBuffer(), is(equalTo("compressed file".getBytes())));
137 | }
138 |
139 | @Test
140 | public void withValidApiKeyFromBufferShouldReturnSource() throws IOException, Exception {
141 | Tinify.setKey("valid");
142 |
143 | server.enqueue(new MockResponse()
144 | .setResponseCode(201)
145 | .addHeader("Location", "https://api.tinify.com/some/location")
146 | .addHeader("Compression-Count", 12));
147 |
148 | assertThat(Source.fromBuffer("png file".getBytes()), isA(Source.class));
149 | }
150 |
151 | @Test
152 | public void withValidApiKeyFromBufferShouldReturnSourceWithData() throws IOException, Exception {
153 | Tinify.setKey("valid");
154 |
155 | server.enqueue(new MockResponse()
156 | .setResponseCode(201)
157 | .addHeader("Location", "https://api.tinify.com/some/location"));
158 |
159 | server.enqueue(new MockResponse()
160 | .setResponseCode(200)
161 | .setBody("compressed file"));
162 |
163 | assertThat(Source.fromBuffer("png file".getBytes()).toBuffer(),
164 | is(equalTo("compressed file".getBytes())));
165 | }
166 |
167 | @Test
168 | public void withValidApiKeyFromUrlShouldReturnSource() throws IOException, Exception {
169 | Tinify.setKey("valid");
170 |
171 | server.enqueue(new MockResponse()
172 | .setResponseCode(201)
173 | .addHeader("Location", "https://api.tinify.com/some/location")
174 | .addHeader("Compression-Count", 12));
175 |
176 | assertThat(Source.fromUrl("http://example.com/test.jpg"), isA(Source.class));
177 | }
178 |
179 | @Test
180 | public void withValidApiKeyFromUrlShouldReturnSourceWithData() throws IOException, Exception, InterruptedException {
181 | Tinify.setKey("valid");
182 |
183 | server.enqueue(new MockResponse()
184 | .setResponseCode(201)
185 | .addHeader("Location", "https://api.tinify.com/some/location"));
186 |
187 | server.enqueue(new MockResponse()
188 | .setResponseCode(200)
189 | .setBody("compressed file"));
190 |
191 | assertThat(Source.fromUrl("http://example.com/test.jpg").toBuffer(),
192 | is(equalTo("compressed file".getBytes())));
193 |
194 | RecordedRequest request1 = server.takeRequest(3, TimeUnit.SECONDS);
195 | assertJsonEquals("{\"source\":{\"url\":\"http://example.com/test.jpg\"}}", request1.getBody().readUtf8());
196 | }
197 |
198 | @Test(expected = ClientException.class)
199 | public void withValidApiKeyFromUrlShouldThrowExceptionIfRequestIsNotOK() throws IOException, Exception {
200 | Tinify.setKey("valid");
201 |
202 | server.enqueue(new MockResponse()
203 | .setResponseCode(400)
204 | .setBody("{'error':'Source not found','message':'Cannot parse URL'}"));
205 |
206 | Source.fromUrl("file://wrong");
207 | }
208 |
209 | @Test
210 | public void withValidApiKeyResultShouldReturnResult() throws Exception, IOException {
211 | Tinify.setKey("valid");
212 |
213 | server.enqueue(new MockResponse()
214 | .setResponseCode(201)
215 | .addHeader("Location", "https://api.tinify.com/some/location")
216 | .addHeader("Compression-Count", 12));
217 |
218 | server.enqueue(new MockResponse()
219 | .setResponseCode(200)
220 | .setBody("compressed file"));
221 |
222 | assertThat(Source.fromBuffer("png file".getBytes()).result(),
223 | isA(Result.class));
224 | }
225 |
226 | @Test
227 | public void withValidApiKeyPreserveShouldReturnSource() throws Exception, InterruptedException {
228 | Tinify.setKey("valid");
229 |
230 | server.enqueue(new MockResponse()
231 | .setResponseCode(201)
232 | .addHeader("Location", "https://api.tinify.com/some/location"));
233 |
234 | server.enqueue(new MockResponse()
235 | .setResponseCode(200)
236 | .setBody("copyrighted file"));
237 |
238 | assertThat(Source.fromBuffer("png file".getBytes()).preserve("copyright", "location"),
239 | isA(Source.class));
240 |
241 | RecordedRequest request1 = server.takeRequest(3, TimeUnit.SECONDS);
242 | assertEquals("png file", request1.getBody().readUtf8());
243 | }
244 |
245 | @Test
246 | public void withValidApiKeyConvertShouldReturnSource() throws Exception, InterruptedException {
247 | Tinify.setKey("valid");
248 |
249 | server.enqueue(new MockResponse()
250 | .setResponseCode(201)
251 | .addHeader("Location", "https://api.tinify.com/some/location"));
252 |
253 | server.enqueue(new MockResponse()
254 | .setResponseCode(200)
255 | .setBody("copyrighted file"));
256 |
257 | assertThat(Source.fromBuffer("png file".getBytes()).convert(new Options().with("type", "image/webp")),
258 | isA(Source.class));
259 |
260 | RecordedRequest request1 = server.takeRequest(3, TimeUnit.SECONDS);
261 | assertEquals("png file", request1.getBody().readUtf8());
262 | }
263 |
264 | @Test
265 | public void withValidApiKeyPreserveShouldReturnSourceWithData() throws Exception, IOException, InterruptedException {
266 | Tinify.setKey("valid");
267 |
268 | server.enqueue(new MockResponse()
269 | .setResponseCode(201)
270 | .addHeader("Location", "https://api.tinify.com/some/location"));
271 |
272 | server.enqueue(new MockResponse()
273 | .setResponseCode(200)
274 | .setBody("copyrighted file"));
275 |
276 | assertThat(Source.fromBuffer("png file".getBytes()).preserve("copyright", "location").toBuffer(),
277 | is(equalTo("copyrighted file".getBytes())));
278 |
279 | RecordedRequest request1 = server.takeRequest(3, TimeUnit.SECONDS);
280 | assertEquals("png file", request1.getBody().readUtf8());
281 |
282 | RecordedRequest request2 = server.takeRequest(3, TimeUnit.SECONDS);
283 | assertJsonEquals("{\"preserve\":[\"copyright\",\"location\"]}", request2.getBody().readUtf8());
284 | }
285 |
286 | @Test
287 | public void withValidApiKeyPreserveShouldReturnSourceWithDataForArray() throws Exception, IOException, InterruptedException {
288 | Tinify.setKey("valid");
289 |
290 | server.enqueue(new MockResponse()
291 | .setResponseCode(201)
292 | .addHeader("Location", "https://api.tinify.com/some/location"));
293 |
294 | server.enqueue(new MockResponse()
295 | .setResponseCode(200)
296 | .setBody("copyrighted file"));
297 |
298 | String[] options = new String [] {"copyright", "location"};
299 | assertThat(Source.fromBuffer("png file".getBytes()).preserve(options).toBuffer(),
300 | is(equalTo("copyrighted file".getBytes())));
301 |
302 | RecordedRequest request1 = server.takeRequest(3, TimeUnit.SECONDS);
303 | assertEquals("png file", request1.getBody().readUtf8());
304 |
305 | RecordedRequest request2 = server.takeRequest(3, TimeUnit.SECONDS);
306 | assertJsonEquals("{\"preserve\":[\"copyright\",\"location\"]}", request2.getBody().readUtf8());
307 | }
308 |
309 | @Test
310 | public void withValidApiKeyPreserveShouldIncludeOtherOptionsIfSet() throws Exception, IOException, InterruptedException {
311 | Tinify.setKey("valid");
312 |
313 | server.enqueue(new MockResponse()
314 | .setResponseCode(201)
315 | .addHeader("Location", "https://api.tinify.com/some/location"));
316 |
317 | server.enqueue(new MockResponse()
318 | .setResponseCode(200)
319 | .setBody("copyrighted resized file"));
320 |
321 | Options resizeOptions = new Options().with("width", 100).with("height", 60);
322 | String[] preserveOptions = new String [] {"copyright", "location"};
323 | assertThat(Source.fromBuffer("png file".getBytes()).resize(resizeOptions).preserve(preserveOptions).toBuffer(),
324 | is(equalTo("copyrighted resized file".getBytes())));
325 |
326 | RecordedRequest request1 = server.takeRequest(3, TimeUnit.SECONDS);
327 | assertEquals("png file", request1.getBody().readUtf8());
328 |
329 | RecordedRequest request2 = server.takeRequest(3, TimeUnit.SECONDS);
330 | assertJsonEquals("{\"resize\":{\"width\":100,\"height\":60},\"preserve\":[\"copyright\",\"location\"]}", request2.getBody().readUtf8());
331 | }
332 |
333 | @Test
334 | public void withValidApiKeyResizeShouldReturnSource() throws Exception, InterruptedException {
335 | Tinify.setKey("valid");
336 |
337 | server.enqueue(new MockResponse()
338 | .setResponseCode(201)
339 | .addHeader("Location", "https://api.tinify.com/some/location"));
340 |
341 | server.enqueue(new MockResponse()
342 | .setResponseCode(200)
343 | .setBody("small file"));
344 |
345 | Options options = new Options().with("width", 100).with("height", 60);
346 |
347 | assertThat(Source.fromBuffer("png file".getBytes()).resize(options),
348 | isA(Source.class));
349 |
350 | RecordedRequest request1 = server.takeRequest(3, TimeUnit.SECONDS);
351 | assertEquals("png file", request1.getBody().readUtf8());
352 | }
353 |
354 | @Test
355 | public void withValidApiKeyResizeShouldReturnSourceWithData() throws Exception, IOException, InterruptedException {
356 | Tinify.setKey("valid");
357 |
358 | server.enqueue(new MockResponse()
359 | .setResponseCode(201)
360 | .addHeader("Location", "https://api.tinify.com/some/location"));
361 |
362 | server.enqueue(new MockResponse()
363 | .setResponseCode(200)
364 | .setBody("small file"));
365 |
366 | Options options = new Options().with("width", 100).with("height", 60);
367 |
368 | assertThat(Source.fromBuffer("png file".getBytes()).resize(options).toBuffer(),
369 | is(equalTo("small file".getBytes())));
370 |
371 | RecordedRequest request1 = server.takeRequest(3, TimeUnit.SECONDS);
372 | assertEquals("png file", request1.getBody().readUtf8());
373 |
374 | RecordedRequest request2 = server.takeRequest(3, TimeUnit.SECONDS);
375 | assertJsonEquals("{\"resize\":{\"width\":100,\"height\":60}}", request2.getBody().readUtf8());
376 | }
377 |
378 | @Test
379 | public void withValidApiKeyTransformShouldReturnSourceWithData() throws Exception, IOException, InterruptedException {
380 | Tinify.setKey("valid");
381 |
382 | server.enqueue(new MockResponse()
383 | .setResponseCode(201)
384 | .addHeader("Location", "https://api.tinify.com/some/location"));
385 |
386 | server.enqueue(new MockResponse()
387 | .setResponseCode(200)
388 | .setBody("small file"));
389 |
390 | Options options = new Options().with("background", "black");
391 |
392 | assertThat(Source.fromBuffer("png file".getBytes()).transform(options).toBuffer(),
393 | is(equalTo("small file".getBytes())));
394 |
395 | RecordedRequest request1 = server.takeRequest(3, TimeUnit.SECONDS);
396 | assertEquals("png file", request1.getBody().readUtf8());
397 |
398 | RecordedRequest request2 = server.takeRequest(3, TimeUnit.SECONDS);
399 | assertJsonEquals("{\"transform\":{\"background\":\"black\"}}", request2.getBody().readUtf8());
400 | }
401 |
402 | @Test
403 | public void withValidApiKeyStoreShouldReturnResultMeta() throws Exception, InterruptedException {
404 | Tinify.setKey("valid");
405 |
406 | server.enqueue(new MockResponse()
407 | .setResponseCode(201)
408 | .addHeader("Location", "https://api.tinify.com/some/location")
409 | .addHeader("Compression-Count", 12));
410 |
411 | server.enqueue(new MockResponse()
412 | .setResponseCode(200)
413 | .addHeader("Location", "https://bucket.s3.amazonaws.com/example"));
414 |
415 | Options options = new Options().with("service", "s3");
416 |
417 | assertThat(Source.fromBuffer("png file".getBytes()).store(options),
418 | isA(ResultMeta.class));
419 |
420 | RecordedRequest request1 = server.takeRequest(3, TimeUnit.SECONDS);
421 | assertEquals("png file", request1.getBody().readUtf8());
422 |
423 | RecordedRequest request2 = server.takeRequest(3, TimeUnit.SECONDS);
424 | assertJsonEquals("{\"store\":{\"service\":\"s3\"}}", request2.getBody().readUtf8());
425 | }
426 |
427 | @Test
428 | public void withValidApiKeyStoreShouldReturnResultMetaWithLocation() throws Exception, InterruptedException {
429 | Tinify.setKey("valid");
430 |
431 | server.enqueue(new MockResponse()
432 | .setResponseCode(201)
433 | .addHeader("Location", "https://api.tinify.com/some/location")
434 | .addHeader("Compression-Count", 12));
435 |
436 | server.enqueue(new MockResponse()
437 | .setResponseCode(200)
438 | .addHeader("Location", "https://bucket.s3.amazonaws.com/example"));
439 |
440 | Options options = new Options().with("service", "s3");
441 |
442 | assertEquals("https://bucket.s3.amazonaws.com/example",
443 | Source.fromBuffer("png file".getBytes()).store(options).location());
444 |
445 | RecordedRequest request1 = server.takeRequest(3, TimeUnit.SECONDS);
446 | assertEquals("png file", request1.getBody().readUtf8());
447 |
448 | RecordedRequest request2 = server.takeRequest(3, TimeUnit.SECONDS);
449 | assertJsonEquals("{\"store\":{\"service\":\"s3\"}}", request2.getBody().readUtf8());
450 | }
451 |
452 | @Test
453 | public void withValidApiKeyStoreShouldIncludeOtherOptionsIfSet() throws Exception, IOException, InterruptedException {
454 | Tinify.setKey("valid");
455 |
456 | server.enqueue(new MockResponse()
457 | .setResponseCode(201)
458 | .addHeader("Location", "https://api.tinify.com/some/location")
459 | .addHeader("Compression-Count", 12));
460 |
461 | server.enqueue(new MockResponse()
462 | .setResponseCode(200)
463 | .setBody("compressed file"));
464 |
465 | server.enqueue(new MockResponse()
466 | .setResponseCode(200)
467 | .addHeader("Location", "https://bucket.s3.amazonaws.com/example"));
468 |
469 | Options resizeOptions = new Options().with("width", 100);
470 | Options storeOptions = new Options().with("service", "s3");
471 |
472 | Source.fromBuffer("png file".getBytes()).resize(resizeOptions).store(storeOptions);
473 |
474 | RecordedRequest request1 = server.takeRequest(3, TimeUnit.SECONDS);
475 | assertEquals("png file", request1.getBody().readUtf8());
476 |
477 | RecordedRequest request2 = server.takeRequest(3, TimeUnit.SECONDS);
478 | assertJsonEquals("{\"resize\":{\"width\":100},\"store\":{\"service\":\"s3\"}}", request2.getBody().readUtf8());
479 | }
480 |
481 | @Test
482 | public void withValidApiKeyToBufferShouldReturnImageData() throws Exception, IOException {
483 | Tinify.setKey("valid");
484 |
485 | server.enqueue(new MockResponse()
486 | .setResponseCode(201)
487 | .addHeader("Location", "https://api.tinify.com/some/location")
488 | .addHeader("Compression-Count", 12));
489 |
490 | server.enqueue(new MockResponse()
491 | .setResponseCode(200)
492 | .setBody("compressed file"));
493 |
494 | assertThat(Source.fromBuffer("png file".getBytes()).toBuffer(),
495 | is(equalTo("compressed file".getBytes())));
496 | }
497 |
498 | @Test
499 | public void withValidApiKeyToFileShouldStoreImageData() throws Exception, IOException {
500 | Tinify.setKey("valid");
501 |
502 | server.enqueue(new MockResponse()
503 | .setResponseCode(201)
504 | .addHeader("Location", "https://api.tinify.com/some/location")
505 | .addHeader("Compression-Count", 12));
506 |
507 | server.enqueue(new MockResponse()
508 | .setResponseCode(200)
509 | .setBody("compressed file"));
510 |
511 | Path tempFile = Files.createTempFile("tinify_", null);
512 | tempFile.toFile().deleteOnExit();
513 |
514 | Source.fromBuffer("png file".getBytes()).toFile(tempFile.toString());
515 |
516 | assertThat(Files.readAllBytes(tempFile),
517 | is(equalTo("compressed file".getBytes())));
518 | }
519 |
520 |
521 | /*
522 | * The following tests should probably be done with parametrized tests
523 | */
524 | @Test
525 | public void withOptionsNotEmptyResultDoesAPOST() throws Exception, IOException, InterruptedException {
526 | Tinify.setKey("valid");
527 |
528 | server.enqueue(new MockResponse()
529 | .setResponseCode(200)
530 | .setBody("compressed file"));
531 | Result result = new Source("https://api.tinify.com/some/location", new Options().with("I am not", "empty")).result();
532 | RecordedRequest outputRequest = server.takeRequest(1, TimeUnit.SECONDS);
533 | assertEquals("POST", outputRequest.getMethod());
534 | }
535 |
536 | @Test
537 | public void withOptionsNULLResultDoesAGET() throws Exception, IOException, InterruptedException {
538 | Tinify.setKey("valid");
539 |
540 | server.enqueue(new MockResponse()
541 | .setResponseCode(200)
542 | .setBody("compressed file"));
543 | Result result = new Source("https://api.tinify.com/some/location", null).result();
544 | RecordedRequest outputRequest = server.takeRequest(1, TimeUnit.SECONDS);
545 | assertEquals("GET", outputRequest.getMethod());
546 | }
547 |
548 | @Test
549 | public void withOptionsEmptyResultDoesAGET() throws Exception, IOException, InterruptedException {
550 | Tinify.setKey("valid");
551 |
552 | server.enqueue(new MockResponse()
553 | .setResponseCode(200)
554 | .setBody("compressed file"));
555 | Result result = new Source("https://api.tinify.com/some/location", new Options()).result();
556 | RecordedRequest outputRequest = server.takeRequest(1, TimeUnit.SECONDS);
557 | assertEquals("GET", outputRequest.getMethod());
558 | }
559 | }
--------------------------------------------------------------------------------
/src/test/java/com/tinify/TinifyTest.java:
--------------------------------------------------------------------------------
1 | package com.tinify;
2 |
3 | import mockit.Invocation;
4 | import mockit.Mock;
5 | import mockit.MockUp;
6 | import okhttp3.HttpUrl;
7 | import okhttp3.mockwebserver.MockResponse;
8 | import okhttp3.mockwebserver.MockWebServer;
9 | import okhttp3.mockwebserver.RecordedRequest;
10 | import org.apache.commons.codec.binary.Base64;
11 | import org.junit.After;
12 | import org.junit.Before;
13 | import org.junit.Test;
14 |
15 | import java.io.IOException;
16 | import java.net.URISyntaxException;
17 | import java.nio.file.Paths;
18 | import java.util.concurrent.TimeUnit;
19 | import java.util.logging.Level;
20 | import java.util.logging.Logger;
21 |
22 | import static org.hamcrest.CoreMatchers.is;
23 | import static org.hamcrest.CoreMatchers.isA;
24 | import static org.hamcrest.MatcherAssert.assertThat;
25 | import static org.junit.Assert.assertEquals;
26 |
27 | public class TinifyTest {
28 | MockWebServer server;
29 |
30 | @Before
31 | public void setup() throws IOException {
32 | Logger.getLogger(MockWebServer.class.getName()).setLevel(Level.WARNING);
33 |
34 | server = new MockWebServer();
35 | server.start();
36 | new MockUp()
37 | {
38 | @Mock
39 | @SuppressWarnings("unused")
40 | HttpUrl parse(Invocation inv, String url)
41 | {
42 | if (url.contains("localhost")) {
43 | return inv.proceed();
44 | } else {
45 | return new HttpUrl.Builder()
46 | .scheme("http")
47 | .host(server.getHostName())
48 | .port(server.getPort())
49 | .encodedPath(url.replaceFirst(".*(/.*)", "$1"))
50 | .build();
51 | }
52 | }
53 | };
54 | }
55 |
56 | @After
57 | public void tearDown() throws IOException {
58 | Tinify.setKey(null);
59 | Tinify.setProxy(null);
60 | server.shutdown();
61 | }
62 |
63 | @Test
64 | public void keyShouldResetClientWithNewKey() throws Exception, InterruptedException {
65 | server.enqueue(new MockResponse().setResponseCode(200));
66 |
67 | Tinify.setKey("abcde");
68 | Tinify.client();
69 | Tinify.setKey("fghij");
70 | Tinify.client().request(Client.Method.GET, "/");
71 |
72 | RecordedRequest request = server.takeRequest(5, TimeUnit.SECONDS);
73 |
74 | String credentials = new String(Base64.encodeBase64(("api:fghij").getBytes()));
75 | assertEquals("Basic " + credentials, request.getHeader("Authorization"));
76 | }
77 |
78 | @Test
79 | public void appIdentifierShouldResetClientWithNewAppIdentifier() throws InterruptedException {
80 | server.enqueue(new MockResponse().setResponseCode(200));
81 |
82 | Tinify.setKey("abcde");
83 | Tinify.setAppIdentifier("MyApp/1.0");
84 | Tinify.client();
85 | Tinify.setAppIdentifier("MyApp/2.0");
86 | Tinify.client().request(Client.Method.GET, "/");
87 |
88 | RecordedRequest request = server.takeRequest(5, TimeUnit.SECONDS);
89 | assertEquals(Client.USER_AGENT + " MyApp/2.0", request.getHeader("User-Agent"));
90 | }
91 |
92 | @Test
93 | public void proxyShouldResetClientWithNewProxy() throws InterruptedException {
94 | server.enqueue(new MockResponse().setResponseCode(407));
95 | server.enqueue(new MockResponse().setResponseCode(200));
96 |
97 | Tinify.setKey("abcde");
98 | Tinify.setProxy("http://localhost");
99 | Tinify.client();
100 | Tinify.setProxy("http://user:pass@" + server.getHostName() + ":" + server.getPort());
101 | Tinify.client().request(Client.Method.GET, "/");
102 |
103 | RecordedRequest request1 = server.takeRequest(5, TimeUnit.SECONDS);
104 | RecordedRequest request2 = server.takeRequest(5, TimeUnit.SECONDS);
105 | assertEquals("Basic dXNlcjpwYXNz", request2.getHeader("Proxy-Authorization"));
106 | }
107 |
108 | @Test
109 | public void clientWithKeyShouldReturnClient() {
110 | Tinify.setKey("abcde");
111 | assertThat(Tinify.client(), isA(Client.class));
112 | }
113 |
114 | @Test(expected = AccountException.class)
115 | public void clientWithoutKeyShouldThrowException() {
116 | Tinify.client();
117 | }
118 |
119 | @Test(expected = ConnectionException.class)
120 | public void clientWithInvalidProxyShouldThrowException() {
121 | Tinify.setKey("abcde");
122 | Tinify.setProxy("http-bad-url");
123 | Tinify.client();
124 | }
125 |
126 | @Test
127 | public void validateWithValidKeyShouldReturnTrue() throws InterruptedException {
128 | server.enqueue(new MockResponse()
129 | .setResponseCode(400)
130 | .setBody("{'error':'Input missing','message':'No input'}"));
131 |
132 | Tinify.setKey("valid");
133 | assertThat(Tinify.validate(), is(true));
134 |
135 | RecordedRequest request = server.takeRequest();
136 | assertEquals("POST /shrink HTTP/1.1", request.getRequestLine());
137 | assertEquals(0, request.getBody().size());
138 | }
139 |
140 | @Test
141 | public void validateWithLimitedKeyShouldReturnTrue() throws InterruptedException {
142 | server.enqueue(new MockResponse()
143 | .setResponseCode(429)
144 | .setBody("{'error':'Too many requests','message':'Your monthly limit has been exceeded'}"));
145 |
146 | Tinify.setKey("valid");
147 | assertThat(Tinify.validate(), is(true));
148 |
149 | RecordedRequest request = server.takeRequest();
150 | assertEquals("POST /shrink HTTP/1.1", request.getRequestLine());
151 | assertEquals(0, request.getBody().size());
152 | }
153 |
154 | @Test(expected = AccountException.class)
155 | public void validateWithErrorShouldThrowException() throws InterruptedException {
156 | server.enqueue(new MockResponse()
157 | .setResponseCode(401)
158 | .setBody("{'error':'Unauthorized','message':'Credentials are invalid'}"));
159 |
160 | Tinify.setKey("invalid");
161 | Tinify.validate();
162 |
163 | RecordedRequest request = server.takeRequest();
164 | assertEquals("POST /shrink HTTP/1.1", request.getRequestLine());
165 | assertEquals(0, request.getBody().size());
166 | }
167 |
168 | @Test
169 | public void fromBufferShouldReturnSource() {
170 | Tinify.setKey("valid");
171 |
172 | server.enqueue(new MockResponse()
173 | .setResponseCode(201)
174 | .addHeader("Location", "https://api.tinify.com/some/location")
175 | .addHeader("Compression-Count", 12));
176 |
177 | assertThat(Tinify.fromBuffer("png file".getBytes()), isA(Source.class));
178 | }
179 |
180 | @Test
181 | public void fromFileShouldReturnSource() throws IOException, URISyntaxException {
182 | Tinify.setKey("valid");
183 |
184 | server.enqueue(new MockResponse()
185 | .setResponseCode(201)
186 | .addHeader("Location", "https://api.tinify.com/some/location")
187 | .addHeader("Compression-Count", 12));
188 |
189 | String filePath = Paths.get(getClass().getResource("/dummy.png").toURI()).toAbsolutePath().toString();
190 | assertThat(Tinify.fromFile(filePath), isA(Source.class));
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/src/test/resources/dummy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinify/tinify-java/a18df055003a2312f463986d58e880df0329bf10/src/test/resources/dummy.png
--------------------------------------------------------------------------------
/src/test/resources/voormedia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinify/tinify-java/a18df055003a2312f463986d58e880df0329bf10/src/test/resources/voormedia.png
--------------------------------------------------------------------------------
/update-cacert.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | dir=src/main/resources
3 |
4 | cert=0
5 | curl --silent --fail https://curl.se/ca/cacert.pem | while read -r line; do
6 | if [ "-----BEGIN CERTIFICATE-----" == "$line" ]; then
7 | cert=1
8 | echo "$line"
9 | elif [ "-----END CERTIFICATE-----" == "$line" ]; then
10 | cert=0
11 | echo "$line"
12 | else
13 | if [ $cert == 1 ]; then
14 | echo "$line"
15 | fi
16 | fi
17 | done > "$dir/cacert.pem"
18 |
--------------------------------------------------------------------------------