├── .travis.yml
├── .gitignore
├── LICENSE
├── README.md
├── src
├── main
│ └── java
│ │ └── net
│ │ └── codestory
│ │ └── rest
│ │ ├── ShouldChain.java
│ │ ├── Should.java
│ │ ├── Response.java
│ │ ├── misc
│ │ └── PostBody.java
│ │ ├── RestResponse.java
│ │ ├── FluentRestTest.java
│ │ ├── RestResponseShould.java
│ │ └── RestAssert.java
└── test
│ └── java
│ └── net
│ └── codestory
│ └── rest
│ ├── OptionsTest.java
│ ├── DeleteTest.java
│ ├── HeadTest.java
│ ├── AbstractTest.java
│ ├── PutTest.java
│ ├── PatchTest.java
│ ├── PostTest.java
│ └── GetTest.java
├── .github
└── workflows
│ └── maven.yml
└── pom.xml
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | sudo: false
3 | install: true
4 |
5 | cache:
6 | directories:
7 | - '$HOME/.m2/repository'
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | *.iml
3 | *.iws
4 | target/
5 | pom.xml.releaseBackup
6 | release.properties
7 | snapshots
8 | coverage-error.log
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2013-2015 all@code-story.net
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fluent
2 |
3 | Unit-test Rest applications.
4 |
5 | # Build status
6 |
7 | [](https://github.com/CodeStory/fluent-rest-test/actions/workflows/maven.yml)
8 |
9 | # Environment
10 |
11 | - `java-1.8`
12 |
13 | ## Maven
14 |
15 | Release versions are deployed on Maven Central:
16 |
17 | ```xml
18 |
19 | net.code-story
20 | fluent-rest-test
21 | 1.7
22 |
23 | ```
24 |
25 | # Build
26 |
27 | ```bash
28 | mvn clean verify
29 | ```
30 |
31 | # Deploy on Maven Central
32 |
33 | Build the release:
34 |
35 | ```bash
36 | mvn release:clean release:prepare release:perform
37 | ```
38 |
--------------------------------------------------------------------------------
/src/main/java/net/codestory/rest/ShouldChain.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2015 all@code-story.net
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | package net.codestory.rest;
17 |
18 | public interface ShouldChain extends Should {
19 | ShouldChain should();
20 | }
21 |
--------------------------------------------------------------------------------
/.github/workflows/maven.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
3 |
4 | # This workflow uses actions that are not certified by GitHub.
5 | # They are provided by a third-party and are governed by
6 | # separate terms of service, privacy policy, and support
7 | # documentation.
8 |
9 | name: Java CI with Maven
10 |
11 | on:
12 | push:
13 | branches: [ "master" ]
14 | pull_request:
15 | branches: [ "master" ]
16 |
17 | jobs:
18 | build:
19 |
20 | runs-on: ubuntu-latest
21 |
22 | steps:
23 | - uses: actions/checkout@v4
24 | - name: Set up JDK 17
25 | uses: actions/setup-java@v4
26 | with:
27 | java-version: '17'
28 | distribution: 'temurin'
29 | cache: maven
30 | - name: Build with Maven
31 | run: mvn -B package --file pom.xml
32 |
--------------------------------------------------------------------------------
/src/test/java/net/codestory/rest/OptionsTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2015 all@code-story.net
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | package net.codestory.rest;
17 |
18 | import org.junit.Test;
19 |
20 | public class OptionsTest extends AbstractTest {
21 | @Test
22 | public void options() {
23 | configure(routes -> routes
24 | .options("/", "Hello")
25 | );
26 |
27 | options("/").should().respond(200).contain("Hello");
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/test/java/net/codestory/rest/DeleteTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2015 all@code-story.net
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | package net.codestory.rest;
17 |
18 | import net.codestory.http.payload.Payload;
19 | import org.junit.Test;
20 |
21 | public class DeleteTest extends AbstractTest {
22 | @Test
23 | public void delete() {
24 | configure(routes -> routes
25 | .delete("/", () -> Payload.ok())
26 | );
27 |
28 | delete("/").should().respond(200);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/test/java/net/codestory/rest/HeadTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2015 all@code-story.net
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | package net.codestory.rest;
17 |
18 | import net.codestory.http.payload.Payload;
19 | import org.junit.Test;
20 |
21 | public class HeadTest extends AbstractTest {
22 | @Test
23 | public void head() {
24 | configure(routes -> routes
25 | .get("/get", "Hello")
26 | .head("/head", Payload.ok())
27 | );
28 |
29 | head("/get").should().respond(200).contain("");
30 | head("/head").should().respond(200).contain("");
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/net/codestory/rest/Should.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2015 all@code-story.net
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | package net.codestory.rest;
17 |
18 | public interface Should {
19 | Should not();
20 |
21 | ShouldChain respond(int statusCode);
22 |
23 | ShouldChain succeed();
24 |
25 | ShouldChain fail();
26 |
27 | ShouldChain contain(String content);
28 |
29 | ShouldChain beEmpty();
30 |
31 | ShouldChain haveType(String contentType);
32 |
33 | ShouldChain haveCookie(String name, String value);
34 |
35 | ShouldChain haveHeader(String name, String value);
36 | }
37 |
--------------------------------------------------------------------------------
/src/test/java/net/codestory/rest/AbstractTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2015 all@code-story.net
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | package net.codestory.rest;
17 |
18 | import net.codestory.http.Configuration;
19 | import net.codestory.http.WebServer;
20 | import net.codestory.http.misc.*;
21 |
22 | import org.junit.Rule;
23 | import org.junit.rules.ExpectedException;
24 |
25 | public abstract class AbstractTest implements FluentRestTest {
26 | private static WebServer server = new WebServer() {
27 | @Override
28 | protected Env createEnv() {
29 | return Env.prod();
30 | }
31 | }.startOnRandomPort();
32 |
33 | @Rule
34 | public ExpectedException thrown = ExpectedException.none();
35 |
36 | @Override
37 | public int port() {
38 | return server.port();
39 | }
40 |
41 | protected void configure(Configuration configuration) {
42 | server.configure(configuration);
43 | }
44 |
45 | protected void shouldFail(String message) {
46 | thrown.expect(AssertionError.class);
47 | thrown.expectMessage(message);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/test/java/net/codestory/rest/PutTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2015 all@code-story.net
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | package net.codestory.rest;
17 |
18 | import net.codestory.http.payload.Payload;
19 | import org.junit.Test;
20 |
21 | public class PutTest extends AbstractTest {
22 | @Test
23 | public void put_empty() {
24 | configure(routes -> routes
25 | .put("/", () -> Payload.created())
26 | );
27 |
28 | put("/").should()
29 | .respond(201)
30 | .contain("");
31 | }
32 |
33 | @Test
34 | public void put_body() {
35 | configure(routes -> routes
36 | .put("/", context -> new Payload("text/plain", context.request().content(), 201))
37 | );
38 |
39 | put("/", "
").should().respond(201).contain("");
40 | }
41 |
42 | @Test
43 | public void put_form() {
44 | configure(routes -> routes
45 | .put("/", context -> new Payload("text/plain", context.get("key1") + "&" + context.get("key2"), 201))
46 | );
47 |
48 | put("/", "key1", "1st", "key2", "2nd").should().respond(201).contain("1st&2nd");
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/net/codestory/rest/Response.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2015 all@code-story.net
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | package net.codestory.rest;
17 |
18 | import java.util.HashMap;
19 | import java.util.List;
20 | import java.util.Map;
21 |
22 | public class Response {
23 | private final String contentType;
24 | private final String content;
25 | private final int code;
26 | private final Map> headers;
27 |
28 | public Response(String contentType, String content, int code) {
29 | this(contentType, content, code, new HashMap<>());
30 | }
31 |
32 | public Response(String contentType, String content, int code, Map> headers) {
33 | this.contentType = contentType;
34 | this.content = content;
35 | this.code = code;
36 | this.headers = headers;
37 | }
38 |
39 | public String contentType() {
40 | return contentType;
41 | }
42 |
43 | public String content() {
44 | return content;
45 | }
46 |
47 | public int code() {
48 | return code;
49 | }
50 |
51 | public List header(String headerName) {
52 | return headers.get(headerName);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/net/codestory/rest/misc/PostBody.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2015 all@code-story.net
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | package net.codestory.rest.misc;
17 |
18 | import okhttp3.FormBody;
19 | import okhttp3.MediaType;
20 | import okhttp3.RequestBody;
21 |
22 | public class PostBody {
23 | private PostBody() {
24 | // Static class
25 | }
26 |
27 | public static RequestBody empty() {
28 | return RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), "");
29 | }
30 |
31 | public static RequestBody json(String body) {
32 | return RequestBody.create(MediaType.parse("application/json; charset=utf-8"), body);
33 | }
34 |
35 | public static RequestBody form(String firstParameterName, Object firstParameterValue, Object... parameterNameValuePairs) {
36 | FormBody.Builder form = new FormBody.Builder();
37 |
38 | form.add(firstParameterName, firstParameterValue.toString());
39 | for (int i = 0; i < parameterNameValuePairs.length; i += 2) {
40 | form.add(parameterNameValuePairs[i].toString(), parameterNameValuePairs[i + 1].toString());
41 | }
42 |
43 | return form.build();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/test/java/net/codestory/rest/PatchTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2015 all@code-story.net
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | package net.codestory.rest;
17 |
18 | import net.codestory.http.payload.Payload;
19 | import net.codestory.http.routes.NoParamRoute;
20 | import org.junit.Test;
21 |
22 | public class PatchTest extends AbstractTest {
23 | @Test
24 | public void patch_empty() {
25 | configure(routes -> routes
26 | .patch("/", (NoParamRoute) Payload::created)
27 | );
28 |
29 | patch("/").should()
30 | .respond(201)
31 | .contain("");
32 | }
33 |
34 | @Test
35 | public void patch_body() {
36 | configure(routes -> routes
37 | .patch("/", context -> new Payload("text/plain", context.request().content(), 201))
38 | );
39 |
40 | patch("/", "").should().respond(201).contain("");
41 | }
42 |
43 | @Test
44 | public void patch_form() {
45 | configure(routes -> routes
46 | .patch("/", context -> new Payload("text/plain", context.get("key1") + "&" + context.get("key2"), 201))
47 | );
48 |
49 | patch("/", "key1", "1st", "key2", "2nd").should().respond(201).contain("1st&2nd");
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/test/java/net/codestory/rest/PostTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2015 all@code-story.net
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | package net.codestory.rest;
17 |
18 | import net.codestory.http.payload.Payload;
19 | import org.junit.Test;
20 |
21 | public class PostTest extends AbstractTest {
22 | @Test
23 | public void post_empty() {
24 | configure(routes -> routes
25 | .post("/", () -> Payload.created())
26 | );
27 |
28 | post("/").should()
29 | .respond(201)
30 | .contain("");
31 | }
32 |
33 | @Test
34 | public void post_body() {
35 | configure(routes -> routes
36 | .post("/", context -> new Payload("text/plain", context.request().content(), 201))
37 | );
38 |
39 | post("/", "").should().respond(201).contain("");
40 | }
41 |
42 | @Test
43 | public void post_form() {
44 | configure(routes -> routes
45 | .post("/", context -> new Payload("text/plain", context.get("key1") + "&" + context.get("key2"), 201))
46 | );
47 |
48 | post("/", "key1", "1st", "key2", "2nd").should().respond(201).contain("1st&2nd");
49 | }
50 |
51 | @Test
52 | public void post_multipart_raw_one_part() {
53 | configure(routes -> routes
54 | .post("/", context -> new Payload("text/plain", "parts=" + context.parts().size(), 200))
55 | );
56 | postRaw("/", "multipart/form-data; boundary=boundary", "--boundary\r\n" +
57 | "Content-Disposition: form-data; name=\"field\"\r\n" +
58 | "\r\n" +
59 | "value\r\n" +
60 | "--boundary--").should().respond(200).contain("parts=1");
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/net/codestory/rest/RestResponse.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2015 all@code-story.net
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | package net.codestory.rest;
17 |
18 |
19 | import okhttp3.*;
20 |
21 | import java.io.IOException;
22 | import java.util.*;
23 | import java.util.function.Function;
24 | import java.util.stream.Collectors;
25 |
26 | class RestResponse {
27 | private final MemoryCookieJar cookieJar;
28 | private final okhttp3.Response response;
29 |
30 | private String bodyAsString;
31 |
32 | private RestResponse(MemoryCookieJar cookieJar, okhttp3.Response response) {
33 | this.cookieJar = cookieJar;
34 | this.response = response;
35 | }
36 |
37 | static RestResponse call(String url, Function configureClient, Function configureRequest) throws IOException {
38 | MemoryCookieJar cookieJar = new MemoryCookieJar();
39 | Request.Builder request = configureRequest.apply(new Request.Builder().url(url));
40 | OkHttpClient.Builder client = configureClient.apply(new OkHttpClient.Builder().cookieJar(cookieJar));
41 |
42 | OkHttpClient okHttpClient = client.build();
43 | okhttp3.Response response = okHttpClient.newCall(request.build()).execute();
44 |
45 | return new RestResponse(cookieJar, response);
46 | }
47 |
48 | public int code() {
49 | return response.code();
50 | }
51 |
52 | public String bodyAsString() {
53 | if (bodyAsString == null) {
54 | try {
55 | bodyAsString = response.body().string();
56 | } catch (IOException e) {
57 | throw new RuntimeException("Unable to read response as String", e);
58 | }
59 | }
60 | return bodyAsString;
61 | }
62 |
63 | public String contentType() {
64 | return header("Content-Type");
65 | }
66 |
67 | public String header(String name) {
68 | return response.header(name);
69 | }
70 | public Map> headers() {return response.headers().toMultimap();}
71 |
72 | public String cookie(String name) {
73 | List cookies = cookieJar.map.values().stream().flatMap(List::stream).collect(Collectors.toList());
74 | return cookies.stream().filter(cookie -> cookie.name().equals(name)).findFirst().map(Cookie::value).orElse(null);
75 | }
76 |
77 | static class MemoryCookieJar implements CookieJar {
78 | Map> map = Collections.synchronizedMap(new HashMap<>());
79 | @Override
80 | public void saveFromResponse(HttpUrl httpUrl, List cookies) {
81 | map.put(httpUrl, cookies);
82 | }
83 |
84 | @Override
85 | public List loadForRequest(HttpUrl httpUrl) {
86 | return map.getOrDefault(httpUrl, new ArrayList<>());
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/net/codestory/rest/FluentRestTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2015 all@code-story.net
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | package net.codestory.rest;
17 |
18 | import net.codestory.rest.misc.PostBody;
19 | import okhttp3.MediaType;
20 | import okhttp3.RequestBody;
21 |
22 | public interface FluentRestTest {
23 | int port();
24 |
25 | default String baseUrl() {
26 | return "http://localhost:" + port();
27 | }
28 |
29 | // GET
30 | default RestAssert get(String path) {
31 | return new RestAssert(baseUrl() + path);
32 | }
33 |
34 | // DELETE
35 | default RestAssert delete(String path) {
36 | return get(path).withRequest(request -> request.delete());
37 | }
38 |
39 | // HEAD
40 | default RestAssert head(String path) {
41 | return get(path).withRequest(request -> request.head());
42 | }
43 |
44 | // POST
45 | default RestAssert post(String path) {
46 | return get(path).withRequest(request -> request.post(PostBody.empty()));
47 | }
48 |
49 | default RestAssert post(String path, String body) {
50 | return get(path).withRequest(request -> request.post(PostBody.json(body)));
51 | }
52 |
53 | default RestAssert post(String path, String firstParameterName, Object firstParameterValue, Object... parameterNameValuePairs) {
54 | return get(path).withRequest(request -> request.post(PostBody.form(firstParameterName, firstParameterValue, parameterNameValuePairs)));
55 | }
56 |
57 | default RestAssert postRaw(String path, String contentType, String content) {
58 | return get(path).withRequest(request -> request.post(RequestBody.create(MediaType.parse(contentType), content)));
59 | }
60 |
61 | // PUT
62 | default RestAssert put(String path) {
63 | return get(path).withRequest(request -> request.put(PostBody.empty()));
64 | }
65 |
66 | default RestAssert put(String path, String body) {
67 | return get(path).withRequest(request -> request.put(PostBody.json(body)));
68 | }
69 |
70 | default RestAssert put(String path, String firstParameterName, Object firstParameterValue, Object... parameterNameValuePairs) {
71 | return get(path).withRequest(request -> request.put(PostBody.form(firstParameterName, firstParameterValue, parameterNameValuePairs)));
72 | }
73 |
74 | // OPTIONS
75 | default RestAssert options(String path) {
76 | return get(path).withRequest(request -> request.method("OPTIONS", null));
77 | }
78 |
79 | // PATCH
80 | default RestAssert patch(String path) {
81 | return get(path).withRequest(request -> request.patch(PostBody.empty()));
82 | }
83 |
84 | default RestAssert patch(String path, String body) {
85 | return get(path).withRequest(request -> request.patch(PostBody.json(body)));
86 | }
87 |
88 | default RestAssert patch(String path, String firstParameterName, Object firstParameterValue, Object... parameterNameValuePairs) {
89 | return get(path).withRequest(request -> request.patch(PostBody.form(firstParameterName, firstParameterValue, parameterNameValuePairs)));
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/java/net/codestory/rest/RestResponseShould.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2015 all@code-story.net
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | package net.codestory.rest;
17 |
18 | import java.util.Objects;
19 |
20 | import static java.lang.String.format;
21 |
22 | class RestResponseShould implements ShouldChain {
23 | private final RestResponse response;
24 | private final boolean negate;
25 |
26 | RestResponseShould(RestResponse response, boolean negate) {
27 | this.response = response;
28 | this.negate = negate;
29 | }
30 |
31 | // Modifiers
32 |
33 | @Override
34 | public RestResponseShould not() {
35 | return new RestResponseShould(response, !negate);
36 | }
37 |
38 | // Verifications
39 |
40 | @Override
41 | public RestResponseShould should() {
42 | return new RestResponseShould(response, false);
43 | }
44 |
45 | @Override
46 | public RestResponseShould respond(int statusCode) {
47 | return assertEquals("status code", response.code(), statusCode);
48 | }
49 |
50 | @Override
51 | public RestResponseShould succeed() {
52 | return assertBetween("status code", response.code(), 200, 299);
53 | }
54 |
55 | @Override
56 | public RestResponseShould fail() {
57 | return assertBetween("status code", response.code(), 400, 599);
58 | }
59 |
60 | @Override
61 | public RestResponseShould contain(String content) {
62 | return assertContains(response.bodyAsString(), content);
63 | }
64 |
65 | @Override
66 | public RestResponseShould beEmpty() {
67 | return assertEmpty(response.bodyAsString());
68 | }
69 |
70 | @Override
71 | public RestResponseShould haveType(String contentType) {
72 | return assertContains(response.contentType(), contentType);
73 | }
74 |
75 | @Override
76 | public RestResponseShould haveCookie(String name, String value) {
77 | return assertEquals("cookie " + name, response.cookie(name), value);
78 | }
79 |
80 | @Override
81 | public RestResponseShould haveHeader(String name, String value) {
82 | return assertEquals("header " + name, response.header(name), value);
83 | }
84 |
85 | // Verifications
86 | private RestResponseShould assertEquals(String what, Object actual, Object expected) {
87 | if (negate == Objects.equals(expected, actual)) {
88 | throw new AssertionError(format("Expecting [%s] to be [%s]. It was [%s]", what, expected, actual));
89 | }
90 | return this;
91 | }
92 |
93 | private RestResponseShould assertContains(String actual, String expected) {
94 | if (negate == actual.contains(expected)) {
95 | throw new AssertionError(format("Expecting [%s] to contain [%s]", actual, expected));
96 | }
97 | return this;
98 | }
99 |
100 | private RestResponseShould assertEmpty(String actual) {
101 | if (negate == actual.isEmpty()) {
102 | throw new AssertionError(format("Expecting [%s] to be empty", actual));
103 | }
104 | return this;
105 | }
106 |
107 | private RestResponseShould assertBetween(String what, int actual, int lowerBound, int higherBound) {
108 | if (negate == ((actual >= lowerBound) && (actual <= higherBound))) {
109 | throw new AssertionError(format("Expecting [%s] to be between [%d] and [%d]. It was [%s]", what, lowerBound, higherBound, actual));
110 | }
111 | return this;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/main/java/net/codestory/rest/RestAssert.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2015 all@code-story.net
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | package net.codestory.rest;
17 |
18 |
19 | import okhttp3.*;
20 |
21 | import java.io.IOException;
22 | import java.util.concurrent.atomic.AtomicInteger;
23 | import java.util.function.UnaryOperator;
24 |
25 | import static java.util.function.UnaryOperator.identity;
26 |
27 | public class RestAssert {
28 | private final String url;
29 | private final UnaryOperator configureClient;
30 | private final UnaryOperator configureRequest;
31 |
32 | RestAssert(String url) {
33 | this(url, identity(), identity());
34 | }
35 |
36 | private RestAssert(String url, UnaryOperator configureClient, UnaryOperator configureRequest) {
37 | this.url = url;
38 | this.configureRequest = configureRequest;
39 | this.configureClient = configureClient;
40 | }
41 |
42 | // Creation
43 | public RestAssert withRequest(UnaryOperator configure) {
44 | return new RestAssert(url, configureClient, request -> configure.apply(configureRequest.apply(request)));
45 | }
46 |
47 | public RestAssert withClient(UnaryOperator configure) {
48 | return new RestAssert(url, client -> configure.apply(configureClient.apply(client)), configureRequest);
49 | }
50 |
51 | // Extraction
52 |
53 | public Response response() {
54 | try {
55 | RestResponse call = call();
56 | return new Response(call.contentType(), call.bodyAsString(), call.code(), call.headers());
57 | } catch (IOException e) {
58 | throw new RuntimeException("Unable to query: " + url, e);
59 | }
60 | }
61 |
62 | // Configuration
63 | public RestAssert withHeader(String name, String value) {
64 | return withRequest(addHeader(name, value));
65 | }
66 |
67 | public RestAssert withPreemptiveAuthentication(String login, String password) {
68 | return withRequest(addBasicAuthHeader(login, password));
69 | }
70 |
71 | public RestAssert withAuthentication(String login, String password) {
72 | return withClient(setAuthenticator(new Authenticator() {
73 | AtomicInteger tries = new AtomicInteger(0);
74 |
75 | @Override
76 | public Request authenticate(Route route, okhttp3.Response response) throws IOException {
77 | if (tries.getAndIncrement() > 0) {
78 | return null;
79 | }
80 | return addBasicAuthHeader(login, password).apply(response.request().newBuilder()).build();
81 | }
82 | }));
83 | }
84 |
85 | // Assertions
86 | public Should should() {
87 | try {
88 | return new RestResponseShould(call(), false);
89 | } catch (IOException e) {
90 | throw new RuntimeException("Unable to query: " + url, e);
91 | }
92 | }
93 |
94 | // Client modification
95 | private static UnaryOperator setAuthenticator(Authenticator authenticator) {
96 | return client -> client.authenticator(authenticator);
97 | }
98 |
99 | // Call
100 | private RestResponse call() throws IOException {
101 | return RestResponse.call(url, configureClient, configureRequest);
102 | }
103 |
104 | // Request configuration
105 | private static UnaryOperator addBasicAuthHeader(String login, String password) {
106 | return addHeader("Authorization", Credentials.basic(login, password));
107 | }
108 |
109 | private static UnaryOperator addHeader(String name, String value) {
110 | return request -> request.addHeader(name, value);
111 | }
112 |
113 | public RestAssert withFollowRedirect(boolean b) {
114 | return withClient(setFollowRedirect(b));
115 | }
116 |
117 | private static UnaryOperator setFollowRedirect(boolean followRedirect) {
118 | return client -> client.followRedirects(followRedirect);
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/test/java/net/codestory/rest/GetTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013-2015 all@code-story.net
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 | package net.codestory.rest;
17 |
18 | import net.codestory.http.filters.basic.BasicAuthFilter;
19 | import net.codestory.http.payload.Payload;
20 | import org.junit.Test;
21 |
22 | import java.util.Collections;
23 |
24 | import static net.codestory.http.security.Users.singleUser;
25 | import static org.assertj.core.api.Assertions.assertThat;
26 |
27 | public class GetTest extends AbstractTest {
28 | @Test
29 | public void get() {
30 | configure(routes -> routes
31 | .get("/", "hello World")
32 | );
33 |
34 | get("/").should().respond(200).succeed().haveType("text/html").contain("hello");
35 | }
36 |
37 | @Test
38 | public void fail_to_get() {
39 | configure(routes -> routes
40 | .get("/", "hello")
41 | );
42 |
43 | shouldFail("Expecting [hello] to contain [good bye]");
44 |
45 | get("/").should().contain("good bye");
46 | }
47 |
48 | @Test
49 | public void get_with_header() {
50 | configure(routes -> routes
51 | .get("/", context -> context.header("name"))
52 | );
53 |
54 | get("/").withHeader("name", "value").should().contain("value");
55 | }
56 |
57 | @Test
58 | public void get_with_headers() {
59 | configure(routes -> routes
60 | .get("/", context -> context.header("first") + context.header("second"))
61 | );
62 |
63 | get("/").withHeader("first", "1").withHeader("second", "2").should().contain("12");
64 | }
65 |
66 | @Test
67 | public void get_with_preemptive_authentication() {
68 | configure(routes -> routes
69 | .filter(new BasicAuthFilter("/", "realm", singleUser("login", "pwd")))
70 | .get("/", context -> "Secret")
71 | );
72 |
73 | get("/").withPreemptiveAuthentication("login", "pwd").should().contain("Secret");
74 | get("/").withPreemptiveAuthentication("", "").should().respond(401);
75 | get("/").should().respond(401);
76 | }
77 |
78 | @Test
79 | public void get_with_basic_authentication() {
80 | configure(routes -> routes
81 | .filter(new BasicAuthFilter("/", "realm", singleUser("login", "pwd")))
82 | .get("/", context -> "Secret")
83 | );
84 |
85 | get("/").withAuthentication("login", "pwd").should().contain("Secret");
86 | get("/").withAuthentication("", "").should().respond(401);
87 | get("/").should().respond(401).fail();
88 | }
89 |
90 | @Test
91 | public void get_cookie() {
92 | configure(routes -> routes
93 | .get("/", context -> new Payload("Hello").withCookie("name", "value"))
94 | );
95 |
96 | get("/").should().haveCookie("name", "value");
97 | }
98 |
99 | @Test
100 | public void get_cookies() {
101 | configure(routes -> routes
102 | .get("/", context -> new Payload("").withCookie("first", "1st").withCookie("second", "2nd"))
103 | );
104 |
105 | get("/").should().haveCookie("first", "1st").haveCookie("second", "2nd");
106 | }
107 |
108 | @Test
109 | public void fail_without_cookie() {
110 | configure(routes -> routes
111 | .get("/", context -> "")
112 | );
113 |
114 | shouldFail("Expecting [cookie ??] to be [??]. It was [null]");
115 |
116 | get("/").should().haveCookie("??", "??");
117 | }
118 |
119 | @Test
120 | public void fail_with_wrong_cookie() {
121 | configure(routes -> routes
122 | .get("/", context -> new Payload("").withCookie("name", "value"))
123 | );
124 |
125 | shouldFail("Expecting [cookie name] to be [??]. It was [value]");
126 |
127 | get("/").should().haveCookie("name", "??");
128 | }
129 |
130 | @Test
131 | public void get_header() {
132 | configure(routes -> routes
133 | .get("/", context -> new Payload("").withHeader("name", "value"))
134 | );
135 |
136 | get("/").should().haveHeader("name", "value");
137 | }
138 |
139 | @Test
140 | public void fail_without_header() {
141 | configure(routes -> routes
142 | .get("/", context -> new Payload(""))
143 | );
144 |
145 | shouldFail("Expecting [header name] to be [??]. It was [null]");
146 |
147 | get("/").should().haveHeader("name", "??");
148 | }
149 |
150 | @Test
151 | public void fail_with_wrong_header() {
152 | configure(routes -> routes
153 | .get("/", context -> new Payload("").withHeader("name", "value"))
154 | );
155 |
156 | shouldFail("Expecting [header name] to be [??]. It was [value]");
157 |
158 | get("/").should().haveHeader("name", "??");
159 | }
160 |
161 | @Test
162 | public void empty() {
163 | configure(routes -> routes
164 | .get("/", "")
165 | );
166 |
167 | get("/").should().beEmpty();
168 | }
169 |
170 | @Test
171 | public void not() {
172 | configure(routes -> routes
173 | .get("/", "Hello")
174 | );
175 |
176 | get("/").should().not().beEmpty();
177 | get("/").should().not().contain("Bye");
178 | }
179 |
180 | @Test
181 | public void chain() {
182 | configure(routes -> routes
183 | .get("/", "Hello")
184 | );
185 |
186 | get("/")
187 | .should().contain("Hello")
188 | .should().not().beEmpty()
189 | .should().not().contain("Bye");
190 | }
191 |
192 | @Test
193 | public void get_response() {
194 | configure(routes -> routes
195 | .get("/", "Hello")
196 | );
197 |
198 | Response response = get("/").response();
199 |
200 | assertThat(response.contentType()).isEqualTo("text/html;charset=UTF-8");
201 | assertThat(response.content()).isEqualTo("Hello");
202 | assertThat(response.code()).isEqualTo(200);
203 | }
204 |
205 | @Test
206 | public void get_response_with_headers() {
207 | configure(routes -> routes
208 | .get("/", (context) -> {
209 | context.response().setHeader("foo", "bar");
210 | context.response().setHeader("baz", "qux");
211 | return "Hello foo";
212 | })
213 | );
214 | Response response = get("/").response();
215 | assertThat(response.content()).isEqualTo("Hello foo");
216 | assertThat(response.header("foo")).isEqualTo(Collections.singletonList("bar"));
217 | assertThat(response.header("baz")).isEqualTo(Collections.singletonList("qux"));
218 | }
219 |
220 | @Test
221 | public void get_response_with_follow_redirect_false() {
222 | configure(routes -> routes
223 | .get("/", (context) -> Payload.temporaryRedirect("http://foo.bar"))
224 | );
225 | Response response = get("/").withFollowRedirect(false).response();
226 | assertThat(response.code()).isEqualTo(307);
227 | assertThat(response.header("Location")).isEqualTo(Collections.singletonList("http://foo.bar"));
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | net.code-story
6 | fluent-rest-test
7 | 1.8-SNAPSHOT
8 | jar
9 |
10 | Fluent-Rest-Test
11 | Library to write REST tests
12 | https://github.com/CodeStory/fluent-rest-test
13 |
14 |
15 | org.sonatype.oss
16 | oss-parent
17 | 9
18 |
19 |
20 |
21 |
22 | scm:git:git@github.com:CodeStory/fluent-rest-test.git
23 | scm:git:git@github.com:CodeStory/fluent-rest-test.git
24 | scm:git:git@github.com:CodeStory/fluent-rest-test.git
25 | HEAD
26 |
27 |
28 |
29 |
30 | Apache 2
31 | http://www.apache.org/licenses/LICENSE-2.0.txt
32 | repo
33 | A business-friendly OSS license
34 |
35 |
36 |
37 |
38 | 3.0.4
39 |
40 |
41 |
42 | UTF-8
43 | 1.8
44 | 1.8
45 | true
46 |
47 |
48 |
49 |
50 | jitpack.io
51 | https://jitpack.io
52 |
53 |
54 |
55 |
56 |
57 | release
58 |
59 |
60 |
61 | maven-gpg-plugin
62 | 1.6
63 |
64 |
65 | sign-artifacts
66 | verify
67 |
68 | sign
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | readmeVersion
78 |
79 |
80 |
81 | com.google.code.maven-replacer-plugin
82 | replacer
83 | 1.5.3
84 |
85 | README.md
86 | ${project.groupId}\s*${project.artifactId}\s*)(\d|.)*()]]>
87 | $1${project.version}$3
88 |
89 |
90 |
91 | maven-scm-plugin
92 | 1.9.4
93 |
94 | README.md
95 | Update version in the README.md
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | maven-clean-plugin
108 | 2.6.1
109 |
110 |
111 | maven-compiler-plugin
112 | 3.3
113 |
114 |
115 | maven-deploy-plugin
116 | 2.8.2
117 |
118 |
119 | maven-install-plugin
120 | 2.5.2
121 |
122 |
123 | maven-jar-plugin
124 | 2.6
125 |
126 |
127 | maven-resources-plugin
128 | 2.7
129 |
130 |
131 | maven-site-plugin
132 | 3.4
133 |
134 |
135 | maven-source-plugin
136 | 2.4
137 |
138 |
139 | maven-release-plugin
140 | 2.5.2
141 |
142 |
143 | maven-surefire-plugin
144 | 2.18.1
145 |
146 |
147 |
148 |
149 |
150 | org.sonatype.plugins
151 | nexus-staging-maven-plugin
152 | 1.6.6
153 | true
154 |
155 | ossrh
156 | https://oss.sonatype.org/
157 | true
158 |
159 |
160 |
161 | maven-release-plugin
162 |
163 | release
164 | -Psonatype-oss-release,readmeVersion
165 | replacer:replace scm:checkin
166 |
167 |
168 |
169 | maven-source-plugin
170 |
171 |
172 | attach-sources
173 |
174 | jar
175 |
176 |
177 |
178 |
179 |
180 | false
181 | com.mycila.maven-license-plugin
182 | maven-license-plugin
183 | 1.9.0
184 |
185 | ${project.basedir}/LICENSE
186 | true
187 | true
188 | true
189 |
190 | **/*.java
191 |
192 |
193 | JAVADOC_STYLE
194 |
195 |
196 |
197 |
198 | enforce-license-headers
199 | validate
200 |
201 | check
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 | com.squareup.okhttp3
212 | okhttp
213 | 3.14.2
214 |
215 |
216 |
217 |
218 | junit
219 | junit
220 | 4.12
221 | test
222 |
223 |
224 | com.github.CodeStory
225 | fluent-http
226 | 4cf7708
227 | jar
228 |
229 |
230 | org.assertj
231 | assertj-core
232 | 3.2.0
233 | test
234 |
235 |
236 |
237 |
--------------------------------------------------------------------------------