• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

leeonky / test-charm-java / 156

20 Mar 2025 01:53PM UTC coverage: 74.243% (-0.2%) from 74.475%
156

push

circleci

leeonky
Refactor

14 of 15 new or added lines in 12 files covered. (93.33%)

126 existing lines in 29 files now uncovered.

7947 of 10704 relevant lines covered (74.24%)

0.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.67
/RESTful-cucumber/src/main/java/com/github/leeonky/cucumber/restful/RestfulStep.java
1
package com.github.leeonky.cucumber.restful;
2

3
import com.github.leeonky.dal.Accessors;
4
import com.github.leeonky.dal.extensions.basic.string.jsonsource.org.json.JSONArray;
5
import com.github.leeonky.dal.extensions.basic.string.jsonsource.org.json.JSONObject;
6
import com.github.leeonky.jfactory.JFactory;
7
import com.github.leeonky.jfactory.cucumber.Table;
8
import com.github.leeonky.util.*;
9
import io.cucumber.docstring.DocString;
10
import io.cucumber.java.After;
11
import io.cucumber.java.en.Then;
12
import io.cucumber.java.en.When;
13

14
import java.io.File;
15
import java.io.IOException;
16
import java.io.InputStream;
17
import java.lang.reflect.Field;
18
import java.net.*;
19
import java.nio.file.Files;
20
import java.nio.file.Path;
21
import java.util.*;
22
import java.util.function.BiConsumer;
23
import java.util.function.Consumer;
24
import java.util.function.Function;
25
import java.util.regex.Matcher;
26
import java.util.regex.Pattern;
27
import java.util.stream.Collectors;
28
import java.util.stream.Stream;
29

30
import static com.github.leeonky.dal.Assertions.expect;
31
import static com.github.leeonky.dal.extensions.basic.binary.BinaryExtension.readAllAndClose;
32
import static java.nio.charset.StandardCharsets.UTF_8;
33
import static java.util.stream.Collectors.joining;
34
import static java.util.stream.Collectors.toList;
35

36
public class RestfulStep {
1✔
37
    public static final String CHARSET = "utf-8";
38
    private final Evaluator evaluator = new Evaluator();
1✔
39
    private String baseUrl = "";
1✔
40
    private Request request = new Request();
1✔
41
    private Response response;
42
    private HttpURLConnection connection;
43
    private Function<Object, String> serializer = RestfulStep::toJson;
1✔
44
    private JFactory jFactory;
45

46
    private static Stream<String> getParamString(Map.Entry<String, Object> entry) {
47
        if (entry.getValue() instanceof List) {
1✔
48
            return ((List) entry.getValue()).stream().map(value -> entry.getKey() + "[]=" + value);
1✔
49
        } else {
50
            return Stream.of(entry.getKey() + "=" + entry.getValue());
1✔
51
        }
52
    }
53

54
    public void setJFactory(JFactory jFactory) {
55
        this.jFactory = jFactory;
1✔
56
    }
1✔
57

58
    public void setSerializer(Function<Object, String> serializer) {
UNCOV
59
        this.serializer = serializer;
×
UNCOV
60
    }
×
61

62
    public void setBaseUrl(String baseUrl) {
63
        this.baseUrl = baseUrl;
1✔
64
    }
1✔
65

66
    @When("GET {string}")
67
    public void get(String path) throws IOException, URISyntaxException {
68
        requestAndResponse("GET", path, connection -> {
1✔
69
        });
1✔
70
    }
1✔
71

72
    @When("POST {string}:")
73
    public void post(String path, DocString content) throws IOException, URISyntaxException {
74
        String contentType = content.getContentType();
1✔
75
        if (Objects.equals(contentType, "application/octet-stream")) {
1✔
76
            post(path, getBytesOf(content.getContent()), contentType);
1✔
77
        } else {
78
            post(path, evaluator.eval(content.getContent()), content.getContentType());
1✔
79
        }
80
    }
1✔
81

82
    public void post(String path, byte[] bytes, String contentType) throws IOException, URISyntaxException {
83
        requestAndResponse("POST", path, connection -> buildRequestBody(connection, contentType, bytes));
1✔
84
    }
1✔
85

86
    public void post(String path, String body, String contentType) throws IOException, URISyntaxException {
87
        post(path, body.getBytes(UTF_8), contentType);
1✔
88
    }
1✔
89

90
    public void post(String path, String body) throws IOException, URISyntaxException {
91
        post(path, body, null);
1✔
92
    }
1✔
93

94
    public void post(String path, Object body, String contentType) throws IOException, URISyntaxException {
95
        post(path, serializer.apply(body), contentType);
1✔
96
    }
1✔
97

98
    public static String toJson(Object body) {
99
        String json = new JSONArray(Collections.singleton(body)).toString();
1✔
100
        return json.substring(1, json.length() - 1);
1✔
101
    }
102

103
    public void post(String path, Object body) throws IOException, URISyntaxException {
104
        post(path, body, null);
1✔
105
    }
1✔
106

107
    @When("POST form {string}:")
108
    public void postForm(String path, String form) throws IOException, URISyntaxException {
109
        postForm(path, new JSONObject(evaluator.eval(form)).toMap());
1✔
110
    }
1✔
111

112
    public void postForm(String path, Map<String, ?> params) throws IOException, URISyntaxException {
113
        requestAndResponse("POST", path, connection -> Sneaky.run(() -> {
1✔
114
            connection.setDoOutput(true);
1✔
115
            String boundary = UUID.randomUUID().toString();
1✔
116
            connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
1✔
117
            HttpStream httpStream = new HttpStream(connection.getOutputStream(), UTF_8);
1✔
118
            params.forEach((key, value) -> appendEntry(httpStream, key, Objects.requireNonNull(value).toString(), boundary));
1✔
119
            httpStream.close(boundary);
1✔
120
        }));
1✔
121
    }
1✔
122

123
    @When("POST form {string} {string}:")
124
    @Then("POST form {string} to {string}:")
125
    public void postForm(String spec, String path, String form) throws IOException, URISyntaxException {
126
        Map<String, Object>[] maps = Table.create(evaluator.eval(form)).flatSub();
1✔
127
        String[] delimiters = spec.split("[ ,]");
1✔
128
        Object formObject = jFactory.spec(delimiters).properties(maps[0]).create();
1✔
129
        BeanClass<Object> type = BeanClass.createFrom(formObject);
1✔
130
        postForm(path, new LinkedHashMap<String, Object>() {{
1✔
131
            type.getPropertyReaders().forEach((key, property) -> {
1✔
132
                Optional<PropertyReader<Object>> linkName = ((BeanClass<Object>) property.getType()).getPropertyReaders().values()
1✔
133
                        .stream().filter(p -> p.annotation(FormFileLinkName.class).isPresent()).findFirst();
1✔
134
                Object value = property.getValue(formObject);
1✔
135
                if (linkName.isPresent())
1✔
136
                    put("@" + key, linkName.get().getValue(value));
1✔
137
                else
138
                    put(key, value);
1✔
139
            });
1✔
140
        }});
1✔
141
    }
1✔
142

143
    @When("PUT {string}:")
144
    public void put(String path, DocString content) throws IOException, URISyntaxException {
145
        String contentType = content.getContentType();
1✔
146
        if (Objects.equals(contentType, "application/octet-stream")) {
1✔
147
            put(path, getBytesOf(content.getContent()), contentType);
1✔
148
        } else {
149
            put(path, evaluator.eval(content.getContent()), contentType);
1✔
150
        }
151
    }
1✔
152

153
    public void put(String path, byte[] bytes, String contentType) throws IOException, URISyntaxException {
154
        requestAndResponse("PUT", path, connection -> buildRequestBody(connection, contentType, bytes));
1✔
155
    }
1✔
156

157
    public void put(String path, String body, String contentType) throws IOException, URISyntaxException {
158
        put(path, body.getBytes(UTF_8), contentType);
1✔
159
    }
1✔
160

161
    public void put(String path, String body) throws IOException, URISyntaxException {
162
        put(path, body, null);
1✔
163
    }
1✔
164

165
    public void put(String path, Object body, String contentType) throws IOException, URISyntaxException {
166
        put(path, serializer.apply(body), contentType);
1✔
167
    }
1✔
168

169
    public void put(String path, Object body) throws IOException, URISyntaxException {
170
        put(path, body, null);
1✔
171
    }
1✔
172

173
    @When("PATCH {string}:")
174
    public void patch(String path, DocString content) throws IOException, URISyntaxException {
175
        String contentType = content.getContentType();
1✔
176
        if (Objects.equals(contentType, "application/octet-stream")) {
1✔
177
            patch(path, getBytesOf(content.getContent()), contentType);
1✔
178
        } else {
179
            patch(path, evaluator.eval(content.getContent()), contentType);
1✔
180
        }
181
    }
1✔
182

183
    public void patch(String path, byte[] body, String contentType) throws IOException, URISyntaxException {
184
        requestAndResponse("PATCH", path, connection -> buildRequestBody(connection, contentType, body));
1✔
185
    }
1✔
186

187
    public void patch(String path, String body, String contentType) throws IOException, URISyntaxException {
188
        patch(path, body.getBytes(UTF_8), contentType);
1✔
189
    }
1✔
190

191
    public void patch(String path, String body) throws IOException, URISyntaxException {
192
        patch(path, body, null);
1✔
193
    }
1✔
194

195
    public void patch(String path, Object body, String contentType) throws IOException, URISyntaxException {
196
        patch(path, serializer.apply(body), contentType);
1✔
197
    }
1✔
198

199
    public void patch(String path, Object object) throws IOException, URISyntaxException {
200
        patch(path, object, null);
1✔
201
    }
1✔
202

203
    @When("DELETE {string}")
204
    public void delete(String path) throws IOException, URISyntaxException {
205
        requestAndResponse("DELETE", path, connection -> {
1✔
206
        });
1✔
207
    }
1✔
208

209
    @After
210
    public void reset() {
211
        request = new Request();
1✔
212
        response = null;
1✔
213
        connection = null;
1✔
214
    }
1✔
215

216
    public RestfulStep header(String key, String value) {
217
        request.headers.put(key, value);
1✔
218
        return this;
1✔
219
    }
220

221
    public RestfulStep header(String key, Collection<String> value) {
222
        request.headers.put(key, value);
1✔
223
        return this;
1✔
224
    }
225

226
    public <T> T response(String expression) {
227
        return Accessors.get(expression).from(response);
1✔
228
    }
229

230
    @Then("response should be:")
231
    public void responseShouldBe(String expression) {
232
        expect(response).should(expression);
1✔
233
    }
1✔
234

235
    @Then("data should be saved to {string} with response:")
236
    public void resourceShouldBe(String path, String expression) throws IOException, URISyntaxException {
237
        responseShouldBe(expression);
1✔
238
        getAndResponseShouldBe(path, expression);
1✔
239
    }
1✔
240

241
    public void file(String fileKey, UploadFile file) {
242
        request.files.put(fileKey, file);
1✔
243
    }
1✔
244

245
    @Then("{string} should response:")
246
    public void getAndResponseShouldBe(String path, String expression) throws IOException, URISyntaxException {
247
        get(path);
1✔
248
        responseShouldBe(expression);
1✔
249
    }
1✔
250

251
    @Then("DELETE {string} should response:")
252
    public void deleteAndResponseShouldBe(String path, String expression) throws IOException, URISyntaxException {
253
        delete(path);
1✔
254
        responseShouldBe(expression);
1✔
255
    }
1✔
256

257
    @When("GET {string}:")
258
    public void getWithParams(String path, String params) throws IOException, URISyntaxException {
259
        get(pathWithParams(path, params));
1✔
260
    }
1✔
261

262
    @When("DELETE {string}:")
263
    public void deleteWithParams(String path, String params) throws IOException, URISyntaxException {
264
        delete(pathWithParams(path, params));
1✔
265
    }
1✔
266

267
    @When("POST {string} {string}:")
268
    @Then("POST {string} to {string}:")
269
    public void postWithSpec(String spec, String path, String body) throws IOException {
270
        requestWithSpec(spec, path, body, this::silentPost);
1✔
271
    }
1✔
272

273
    private void requestWithSpec(String spec, String path, String body, BiConsumer<String, String> method) throws IOException {
274
        Object json = new JSONArray("[" + body + "]").get(0);
1✔
275
        Map<String, Object>[] maps = Table.create(evaluator.eval(body)).flatSub();
1✔
276
        String[] delimiters = spec.split("[ ,]");
1✔
277
        if (json instanceof JSONObject) {
1✔
278
            method.accept(path, serializer.apply(jFactory.spec(delimiters).properties(maps[0]).create()));
1✔
279
        } else {
280
            method.accept(path, serializer.apply(Arrays.stream(maps)
1✔
281
                    .map(map -> jFactory.spec(delimiters).properties(map).create())
1✔
282
                    .collect(toList())));
1✔
283
        }
284
    }
1✔
285

286
    private void silentPost(String path, String body) {
287
        try {
288
            post(path, body);
1✔
UNCOV
289
        } catch (IOException | URISyntaxException e) {
×
UNCOV
290
            throw new RuntimeException(e);
×
291
        }
1✔
292
    }
1✔
293

294
    @When("PUT {string} {string}:")
295
    @Then("PUT {string} to {string}:")
296
    public void putWithSpec(String spec, String path, String body) throws IOException {
297
        requestWithSpec(spec, path, body, this::silentPut);
1✔
298
    }
1✔
299

300
    private void silentPut(String path, String body) {
301
        try {
302
            put(path, body);
1✔
UNCOV
303
        } catch (IOException | URISyntaxException e) {
×
UNCOV
304
            throw new RuntimeException(e);
×
305
        }
1✔
306
    }
1✔
307

308
    @When("PATCH {string} {string}:")
309
    @Then("PATCH {string} to {string}:")
310
    public void patchWithSpec(String spec, String path, String body) throws IOException {
311
        requestWithSpec(spec, path, body, this::silentPatch);
1✔
312
    }
1✔
313

314
    private void silentPatch(String path, String body) {
315
        try {
316
            patch(path, body);
1✔
UNCOV
317
        } catch (IOException | URISyntaxException e) {
×
UNCOV
318
            throw new RuntimeException(e);
×
319
        }
1✔
320
    }
1✔
321

322
    private void appendEntry(HttpStream httpStream, String key, String value, String boundary) {
323
        httpStream.bound(boundary, () -> Sneaky.get(() -> key.startsWith("@") ?
1✔
324
                httpStream.appendFile(key, request.files.get(value)) : httpStream.appendField(key, value)));
1✔
325
    }
1✔
326

327
    private void buildRequestBody(HttpURLConnection connection, String contentType, byte[] bytes) {
328
        Sneaky.run(() -> {
1✔
329
            connection.setDoOutput(true);
1✔
330
            connection.setRequestProperty("Content-Type", contentType == null ? String.valueOf(request.headers.getOrDefault("Content-Type", "application/json")) : contentType);
1✔
331
            connection.getOutputStream().write(bytes);
1✔
332
            connection.getOutputStream().close();
1✔
333
        });
1✔
334
    }
1✔
335

336
    private byte[] getBytesOf(String expression) throws IOException {
337
        if (expression.startsWith("@")) {
1✔
UNCOV
338
            return request.files.get(expression.substring(1)).getContent();
×
339
        }
340
        Object obj = Accessors.get(expression).from(request.getContext());
1✔
341
        if (obj instanceof String) {
1✔
342
            return ((String) obj).getBytes(UTF_8);
1✔
343
        } else if (obj instanceof File) {
1✔
344
            return Files.readAllBytes(((File) obj).toPath());
1✔
345
        } else if (obj instanceof Path) {
1✔
346
            return Files.readAllBytes((Path) obj);
1✔
347
        } else {
348
            return (byte[]) obj;
1✔
349
        }
350
    }
351

352
    private String pathWithParams(String path, String params) {
353
        return path + "?" + new JSONObject(evaluator.eval(params)).toMap().entrySet().stream()
1✔
354
                .flatMap(RestfulStep::getParamString)
1✔
355
                .collect(joining("&"));
1✔
356
    }
357

358
    private void requestAndResponse(String method, String path, Consumer<HttpURLConnection> body) throws IOException, URISyntaxException {
359
        URL url = new URL(baseUrl + evaluator.eval(path));
1✔
360
        URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
1✔
361
        connection = request.applyHeader((HttpURLConnection) new URL(uri.toASCIIString()).openConnection());
1✔
362
        setRequestMethod(method);
1✔
363
        body.accept(connection);
1✔
364
        response = new Response(connection);
1✔
365
    }
1✔
366

367
    private void setRequestMethod(String method) throws ProtocolException {
368
        if (method.equals("PATCH")) {
1✔
369
            try {
370
                Field field = getField(connection.getClass(), "method");
1✔
371
                field.setAccessible(true);
1✔
372
                field.set(connection, method);
1✔
UNCOV
373
            } catch (IllegalAccessException e) {
×
UNCOV
374
                throw new RuntimeException("Failed to set method " + method + " to " + connection, e);
×
375
            }
1✔
376
        } else {
377
            connection.setRequestMethod(method);
1✔
378
        }
379
    }
1✔
380

381
    private Field getField(Class clazz, String fieldName) {
382
        try {
383
            return clazz.getDeclaredField(fieldName);
1✔
384
        } catch (NoSuchFieldException e) {
1✔
385
            Class superClass = clazz.getSuperclass();
1✔
386
            if (superClass == null) {
1✔
UNCOV
387
                throw new RuntimeException("Failed to get field " + fieldName + " from " + clazz, e);
×
388
            } else {
389
                return getField(superClass, fieldName);
1✔
390
            }
391
        }
392
    }
393

394
    public interface UploadFile {
395
        static UploadFile content(String fileContent) {
396
            return content(fileContent.getBytes(UTF_8));
1✔
397
        }
398

399
        static UploadFile content(byte[] bytes) {
400
            return () -> bytes;
1✔
401
        }
402

403
        byte[] getContent();
404

405
        default String getName() {
406
            return UUID.randomUUID() + ".upload";
1✔
407
        }
408

409
        default UploadFile name(String fileName) {
410
            return new UploadFile() {
1✔
411
                @Override
412
                public byte[] getContent() {
413
                    return UploadFile.this.getContent();
1✔
414
                }
415

416
                @Override
417
                public String getName() {
418
                    return fileName;
1✔
419
                }
420
            };
421
        }
422
    }
423

424
    private static class Request {
1✔
425
        private final Map<String, UploadFile> files = new HashMap<>();
1✔
426
        private final Map<String, Object> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
1✔
427

428
        public RequestContext getContext() {
429
            return new RequestContext();
1✔
430
        }
431

432
        public class RequestContext {
1✔
433
            public Map<String, UploadFile> getFiles() {
434
                return files;
1✔
435
            }
436
        }
437

438
        private HttpURLConnection applyHeader(HttpURLConnection connection) {
439
            headers.forEach((key, value) -> {
1✔
440
                if (value instanceof String)
1✔
441
                    connection.setRequestProperty(key, (String) value);
1✔
442
                else if (value instanceof Collection)
1✔
443
                    ((Collection<String>) value).forEach(header -> connection.addRequestProperty(key, header));
1✔
444
            });
1✔
445
            return connection;
1✔
446
        }
447
    }
448

449
    public static class Response {
450
        public final int code;
451
        public final byte[] body;
452
        public final HttpURLConnection raw;
453

454
        public Response(HttpURLConnection connection) {
1✔
455
            raw = connection;
1✔
456
            code = Sneaky.get(connection::getResponseCode);
1✔
457
            InputStream stream = Sneaky.get(() -> 100 <= code && code <= 399 ? raw.getInputStream() : raw.getErrorStream());
1✔
458
            body = stream == null ? null : readAllAndClose(stream);
1✔
459
        }
1✔
460

461
        public Map<String, Object> getHeaders() {
462
            return raw.getHeaderFields().entrySet().stream()
1✔
463
                    .map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), entry.getValue() != null && entry.getValue().size() == 1 ? entry.getValue().get(0) : entry.getValue()))
1✔
464
                    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
1✔
465
        }
466

467
        public String fileName() {
468
            String header = raw.getHeaderField("Content-Disposition");
1✔
469
            Matcher matcher = Pattern.compile(".*filename=\"(.*)\".*").matcher(header);
1✔
470
            return Sneaky.get(() -> URLDecoder.decode(matcher.matches() ? matcher.group(1) : header, UTF_8.name()));
1✔
471
        }
472
    }
473
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc