{"id":14988288,"url":"https://github.com/apache/sling-org-apache-sling-testing-clients","last_synced_at":"2025-05-08T21:25:45.195Z","repository":{"id":27251332,"uuid":"107435810","full_name":"apache/sling-org-apache-sling-testing-clients","owner":"apache","description":"Apache Sling Testing Clients","archived":false,"fork":false,"pushed_at":"2025-03-25T15:40:17.000Z","size":370,"stargazers_count":8,"open_issues_count":0,"forks_count":25,"subscribers_count":28,"default_branch":"master","last_synced_at":"2025-05-03T09:18:22.314Z","etag":null,"topics":["java","sling"],"latest_commit_sha":null,"homepage":"https://sling.apache.org/","language":"Java","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/apache.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-10-18T16:41:23.000Z","updated_at":"2025-03-25T15:40:21.000Z","dependencies_parsed_at":"2025-01-02T19:16:16.644Z","dependency_job_id":"f3390f62-3cea-4fcb-a9af-75c3fdaf104c","html_url":"https://github.com/apache/sling-org-apache-sling-testing-clients","commit_stats":{"total_commits":229,"total_committers":30,"mean_commits":7.633333333333334,"dds":0.5851528384279476,"last_synced_commit":"8dfe5bc323477b1c609e4497a5c139b00174cfab"},"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apache%2Fsling-org-apache-sling-testing-clients","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apache%2Fsling-org-apache-sling-testing-clients/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apache%2Fsling-org-apache-sling-testing-clients/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apache%2Fsling-org-apache-sling-testing-clients/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/apache","download_url":"https://codeload.github.com/apache/sling-org-apache-sling-testing-clients/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253083761,"owners_count":21851337,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","sling"],"created_at":"2024-09-24T14:16:25.566Z","updated_at":"2025-05-08T21:25:45.175Z","avatar_url":"https://github.com/apache.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Apache Sling](https://sling.apache.org/res/logos/sling.png)](https://sling.apache.org)\n\n\u0026#32;[![Build Status](https://ci-builds.apache.org/job/Sling/job/modules/job/sling-org-apache-sling-testing-clients/job/master/badge/icon)](https://ci-builds.apache.org/job/Sling/job/modules/job/sling-org-apache-sling-testing-clients/job/master/)\u0026#32;[![Test Status](https://img.shields.io/jenkins/tests.svg?jobUrl=https://ci-builds.apache.org/job/Sling/job/modules/job/sling-org-apache-sling-testing-clients/job/master/)](https://ci-builds.apache.org/job/Sling/job/modules/job/sling-org-apache-sling-testing-clients/job/master/test/?width=800\u0026height=600)\u0026#32;[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=apache_sling-org-apache-sling-testing-clients\u0026metric=coverage)](https://sonarcloud.io/dashboard?id=apache_sling-org-apache-sling-testing-clients)\u0026#32;[![Sonarcloud Status](https://sonarcloud.io/api/project_badges/measure?project=apache_sling-org-apache-sling-testing-clients\u0026metric=alert_status)](https://sonarcloud.io/dashboard?id=apache_sling-org-apache-sling-testing-clients)\u0026#32;[![JavaDoc](https://www.javadoc.io/badge/org.apache.sling/org.apache.sling.testing.clients.svg)](https://www.javadoc.io/doc/org.apache.sling/org.apache.sling.testing.clients)\u0026#32;[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.sling/org.apache.sling.testing.clients/badge.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.apache.sling%22%20a%3A%22org.apache.sling.testing.clients%22)\u0026#32;[![testing](https://sling.apache.org/badges/group-testing.svg)](https://github.com/apache/sling-aggregator/blob/master/docs/groups/testing.md) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)\n\n# Apache Sling Testing Clients\n\nThis module is part of the [Apache Sling](https://sling.apache.org) project.\n\n`SlingClient` is a specialized\n[`HttpClient`](https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/client/HttpClient.html)\nthat provides additional functionalities specific to Sling. It is designed to be easy to use out of the box, but also fully customizable.\nThis library comes with a bunch of other specialized clients (built on top of `SlingClient`) that are ready to use.\n\n## \u003ca name=\"architecture\"\u003e\u003c/a\u003e Architecture\n\n`SlingClient`  implements the `HttpClient` interface, but [delegates](https://en.wikipedia.org/wiki/Delegation_pattern)\nthis functionality to a `private final CloseableHttpClient http` field.\nThe config is stored in a `private final SlingClientConfig config` field which is immutable and may be shared across multiple clients\n(more about it in the [How to configure a SlingClient](#config) section).\nThese two objects define the state of the client and are built to make the client thread safe.\n\n`SlingClient` is designed in two layers:\n* The base `class AbstractSlingClient implements HttpClient` provides an overlay of basic http methods such as `doGet()`,\n  `doPost()` \u0026 similar. These are meant to be full replacements of the ones in `HttpClient` for Sling specific needs,\n  and they add specific customizations. One particularity is that they all return `SlingHttpResponse`, an augmented `HttpResponse`.\n\n  Still, all the methods from `HttpClient` are exposed (through inheritance and delegation) in case one needs the raw functionality.\n  Some useful methods to manipulate Sling paths and URLs have also been added (`getUrl()`, `getPath()`).\n\n  This class encapsulates the mechanisms for extensibility (immutable config field, delegate client field, package private constructor,\n  `adaptTo()`), but it is defined as abstract and should never be used directly.\n\n* The main `class SlingClient extends AbstractSlingClient` is the one that adds Sling specific methods (`createNode()`,\n  `deletePath()` etc.). It has no fields, but makes use of everything that was defined in the super class.\n  Another main functionality defined in `SlingClient` are the mechanisms to instantiate a SlingClient (and any other sub-class):\n\n  * constructor: `public SlingClient(URI url, String user, String password) throws ClientException`\n\n  * builder: `public final static class Builder extends InternalBuilder\u003cSlingClient\u003e` (more in [How to write a `Builder`](#builder))\n\nAny client you write should extend `SlingClient` (more in [How to extend `SlingClient`](#extend))\n\n## \u003ca name=\"instantiate\"\u003e\u003c/a\u003e How to instantiate `SlingClient`\nThere are several ways to obtain a SlingClient (and sub-client) instance, depending on the resources available:\n\n* constructor `SlingClient(URI url, String user, String password)` - handy for obtaining a simple client from the url:\n  ```java\n  SlingClient c = new SlingClient(URI.create(\"localhost:8080\"), \"admin\", \"admin\");\n  ```\n\n* builder `class Builder\u003cT extends Builder\u003e extends HttpClientBuilder` - this allows for more complex clients to be created, e.g.\n  with different authentication mechanism, or additional interceptors:\n  ```java\n  SlingClient c = SlingClient.Builder.create(\"localhost:8080\", \"admin\", \"admin\").build();\n  ```\n  This gives the possibility to customize the HttpClient (e.g. add interceptors, change auth method) before constructing it.\n\n* `public \u003cT extends AbstractSlingClient\u003e T adaptTo(Class\u003cT\u003e clientClass)` is the convenient method to obtain another specialized\nclient form an existing one. The advantage is that the two will share the same configuration and http handler, so they will behave\nlike two different \"facets\" of the same client (think about the analogy of a Web browser with multiple tabs).\n\nAlthough the constructor and the builder are very handy, the preferred way of working with clients is to obtain it using one of the\nJunit Rules provided (e.g. `ExistingQuickstart`) and then call `adaptTo()`.\n\n## \u003ca name=\"config\"\u003e\u003c/a\u003e How to configure `SlingClient`\nAll the configs specific to `SlingClient` are stored in `private final SlingClientConfig config` which contains fields such as\n`url`, `cookieStore` and authentication parameters. These fields can be set only indirectly, through constructor or Builder, and only\nbefore constructing the client. They cannot be changed, so if you need to change something, you must instantiate another client.\n\n`SlingClient` was designed to be immutable, so thread safe. You don't have to worry about synchronizing it when running tests in parallel.\nAlso, the immutable config is the base for the `adaptTo()` mechanism, since the two clients can share the same config.\n\n## \u003ca name=\"extend\"\u003e\u003c/a\u003e How to extend `SlingClient`\nThe `SlingClient` was designed with extensibility in mind. That's why it provides only basic functionality, leaving other specialized\nclients to implement the rest. To create a new client class (let's call it `MyClient`), you need to:\n* extend SlingClient: `class MyClient extends SlingClient`\n* implement the two constructors:\n  * the one for simple uses:\n  ```java\n    public MyClient(URI serverUrl, String userName, String password) throws ClientException {\n      super(serverUrl, userName, password);\n    }\n    ```\n  * the one used by `adaptTo()` (so don't forget it!):\n  ```java\n    public MyClient(CloseableHttpClient http, SlingClientConfig config) throws ClientException {\n      super(http, config);\n    }\n    ```\n  * optionally create your `Builder`, but only if needed (more in [How to write a `Builder`](#builder))\n\nA good example of how `SlingClient` can be extended is `OsgiConsoleClient`. Note you can further extend the sub-clients in the same way.\n\n## \u003ca name=\"builder\"\u003e\u003c/a\u003e How to write a `Builder`\nIf you need to make your client customizable you will have to write your own Builder (you were no thinking to break the immutability\nby adding a setter, right?). Below is an example of how to create the Builder mechanism that you can take and adapt for your needs.\nIn this case, we try to expose only one field `foo`, but it can be extended to any number of fields. Although it seems complicated,\nif you follow exactly the example, you cannot fail. Trying to simplify it will burn you (sooner or later), you have been warned!\n\nA short description of the Builder architecture would be: the `InternalBuilder` contains all the logic while staying extensible, while\n`Builder` takes all the credit by exposing the `build()` method. Yet, the `Builder` cannot be extended because all the sub-classes would\nreturn a `SlingClient` when calling `build()` (and not a subclass instance).\n\n```java\n@Immutable\npublic class MyClient extends SlingClient {\n\n    private final String foo;\n\n    public MyClient(URI serverUrl, String user, String password) throws ClientException {\n        super(serverUrl, user, password);\n    }\n\n    /**\n     * Constructor used by Builders and adaptTo(). \u003cb\u003eShould never be called directly from the code.\u003c/b\u003e\n     *\n     * @see AbstractSlingClient#AbstractSlingClient(CloseableHttpClient, SlingClientConfig)\n     */\n    public MyClient(CloseableHttpClient http, SlingClientConfig config) throws ClientException {\n        super(http, config);\n    }\n\n    public static abstract class InternalBuilder\u003cT extends MyClient\u003e extends SlingClient.InternalBuilder\u003cT\u003e {\n        protected String foo;\n\n        protected InternalBuilder(URI url, String user, String password) {\n            super(url, user, password);\n        }\n\n        public InternalBuilder\u003cT\u003e withFoo(String foo) {\n          this.foo = foo;\n        }\n    }\n\n    public final static class Builder extends InternalBuilder\u003cMyClient\u003e {\n\n        private Builder(URI url, String user, String password) {\n            super(url, user, password);\n        }\n\n        @Override\n        public MyClient build() throws ClientException {\n            MyClient client = new MyClient(buildHttpClient(), buildSlingClientConfig());\n            client.foo = this.foo;\n            return client;\n        }\n\n        public static Builder create(URI url, String user, String password) {\n            return new Builder(url, user, password);\n        }\n    }\n}\n```\n## \u003ca name=\"builder\"\u003e\u003c/a\u003e How to retry HTTP Requests\nIn several situations during IT Tests development, HTTP requests must be retried until some assertions are verified.\nIn such cases, a good approach is to use the `Awaitility` library. For example:\n```java\n    public void testDoGetWithRetry() throws ClientException, InterruptedException, TimeoutException {\n        SlingClient c = new SlingClient(httpServer.getURI(), \"user\", \"pass\");\n\n        Awaitility.await(\"doGet until expectedStatus and Assertions are satisfied\")\n            .atMost(Duration.ofSeconds(1000))\n            .pollInterval(Duration.ofMillis(100))\n            .ignoreExceptionsInstanceOf(ClientException.class)\n            .until(() -\u003e {\n                SlingHttpResponse response = c.doGet(\"/\", 200);\n                return SUCCESS_RESPONSE.equals(response.getContent());\n            });\n    }\n    \n```\n\n## FAQ\n##### How can I change the server url of an existing client?\nYou don't. As described in [How to configure a `SlingClient`](#config), you have to instantiate another client to change the config.\n\n##### How can I create a client for a server url with context path?\nThe server `url` (passed in the constructor or builder) must contain all the elements, including protocol, hostname, port and eventually\nthe context path, e.g.: `http://localhost:8080/mycontextpath/`.\nThe url may (or may not) contain the trailing slash. Yet, the client will always store it with a trailing slash:\n```java\nSlingClient client = new SlingClient(\"http://localhost:4502/mycontextpath\", \"user\", \"pass\");\nSystem.out.println(client.getUrl());\n// prints http://localhost:4502/mycontextpath/\n```\n\n##### How can I customize the underlying `HttpClient`?\nThe `SlingClient.Builder` directly exposes the most useful methods from `HttpClientBuilder`, but not all of them.\nFirst, check if you can find it there. If you haven't found your specific method, then the `Builder` exposes an `HttpClientBuilder` through\n`public HttpClientBuilder httpClientBuilder()` which you can use to config it. Note that in this case you cannot chain the methods\nto build the client, so you will need to keep a reference to the `SlingClient.Builder`:\n```java\nSlingClient.Builder builder = SlingClient.Builder.create(\"http://localhost:8080\", \"user\", \"pass\");\nHttpClientBuilder httpBuilder = builder.httpClientBuilder();\nhttpBuilder.setProxy(myProxy);\nbuilder.setUser(\"another\");\nSlingClient client = builder.build();\n```\n\n##### Why is the `Builder` pattern so complicated? Do I really need two classes?\nDon't try to get creative here. Respect the examples provided and don't take shortcuts, otherwise you will hurt yourself.\n\nWe have tried different ways of designing the Builder. This is the best compromise between extensibility and simplicity. The\n`HttpClientBuilder` does not offer any extensibility support, so `SlingClient.Builder` does not extend it, it just uses it internally.\nAlways remember that you don't need to create your Builder, unless you want to add custom fields to the client.\n\n##### Why I cannot use the entity's content InputStream?\n`SlingClient#doRequest()`, `SlingClient#doGet()`, `SlingClient#doPost()` \u0026 co. are all consuming the entity and caching it as\nString. This is by design, since there's a big risk to forget closing the connections and to run out of sockets quickly.\nIf you need the response content as InputStream (e.g. for downloading a binary), you can use `doStreamGet()` or similar. These\nmethods were written specially to not consume the entity so it's the caller's responsibility to close it when finished. Remember to use\nthem with caution and only when needed.\n\n##### Can my client use another authentication method?\nThe username and password required by the constructor and builder are there for convenience (since more than 90% of cases will use\nbasic auth). But you can easily overwrite the `CredentialsProvider` in Builder so those will be ignored. Or do anything you want with\nthat `HttpClientBuilder`...\n\n##### How can I obtain the context path?\n`client.getUrl().getPath()`\n\n##### How can I obtain the \"relative\" url (excluding hostname and port, but including context path)?\n`client.getUrl(path).getPath()`\n\n##### How can I remove the context path from a path?\n`client.getPath(path)`\n\n##### What if I pass an url or a path with or without context path to `getUrl()` or `getPath()`?\nWe have tried to make these methods as robust as possible. Their job is very clear:\n* `getUrl(String path)` to transform a Sling path into a full url\n* `getPath(String url)` to transform a full url into a Sling path\n\nAny input that does not respect the contract might not work. Check `AbstractSlingClientGetPathTest` and `AbstractSlingClientGetUrlTest`\nfor an extensive list of cases that we have considered when writing these methods.\n\n##### How can I impersonate a user?\n* `client.impersonate(userId)` to impersonate\n* `client.impersonate(null)` to clear impersonation\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapache%2Fsling-org-apache-sling-testing-clients","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fapache%2Fsling-org-apache-sling-testing-clients","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapache%2Fsling-org-apache-sling-testing-clients/lists"}