-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding application class for health checking #152
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package org.microshed.testing; | ||
|
||
import org.microshed.testing.health.Health; | ||
import org.microshed.testing.jaxrs.JsonBProvider; | ||
|
||
import javax.ws.rs.client.Client; | ||
import javax.ws.rs.client.ClientBuilder; | ||
import javax.ws.rs.client.WebTarget; | ||
import javax.ws.rs.core.MediaType; | ||
import java.net.URI; | ||
import java.util.Objects; | ||
|
||
/** | ||
* Provides access to an application that implements MicroProfile Health. | ||
* This class can be sub-classed and extended for type-safe, business-specific methods within the project's test scope. | ||
* <p> | ||
* <p> | ||
* Usage: | ||
* <pre> | ||
* MicroShedApplication app = MicroShedApplication.withBaseUri(baseUri).build(); | ||
* Health health = app.health(); | ||
* </pre> | ||
* <p> | ||
* Or a sub-classed version: | ||
* <pre> | ||
* class MyApplication extends MicroShedApplication { | ||
* | ||
* MyApplication() { | ||
* super(URI.create("http://my-app:8080/")); | ||
* } | ||
* | ||
* // add business-related methods | ||
* | ||
* public List<MyCustomer> getMyCustomers { ... } | ||
* } | ||
* | ||
* // in the test code, access health checks, metrics, or business-related methods | ||
* | ||
* MyApplication app = new MyApplication(); | ||
* Health health = app.health(); | ||
* ... | ||
* app.getMyCustomers(); | ||
* ... | ||
* </pre> | ||
*/ | ||
public class MicroShedApplication { | ||
|
||
private static final String HEALTH_PATH = "/health"; | ||
|
||
private final String healthPath; | ||
|
||
protected final Client client; | ||
protected final WebTarget rootTarget; | ||
|
||
protected MicroShedApplication(URI baseUri) { | ||
this(baseUri, HEALTH_PATH); | ||
} | ||
|
||
private MicroShedApplication(URI baseUri, String healthPath) { | ||
this.healthPath = healthPath; | ||
|
||
client = ClientBuilder.newBuilder() | ||
.register(JsonBProvider.class) | ||
.build(); | ||
this.rootTarget = client.target(baseUri); | ||
} | ||
|
||
public Health health() { | ||
return rootTarget.path(healthPath) | ||
.request(MediaType.APPLICATION_JSON_TYPE) | ||
.get(Health.class); | ||
} | ||
|
||
public static Builder withBaseUri(String baseUri) { | ||
Objects.requireNonNull(baseUri, "Base URI must not be null"); | ||
Builder builder = new Builder(); | ||
builder.baseUri = URI.create(baseUri); | ||
return builder; | ||
} | ||
|
||
public static Builder withBaseUri(URI baseUri) { | ||
Objects.requireNonNull(baseUri, "Base URI must not be null"); | ||
Builder builder = new Builder(); | ||
builder.baseUri = baseUri; | ||
return builder; | ||
} | ||
|
||
public static class Builder { | ||
|
||
private URI baseUri; | ||
private String healthPath = HEALTH_PATH; | ||
|
||
public Builder healthPath(String healthPath) { | ||
this.healthPath = healthPath; | ||
return this; | ||
} | ||
|
||
public MicroShedApplication build() { | ||
return new MicroShedApplication(baseUri, healthPath); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package org.microshed.testing.health; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
public class Health { | ||
|
||
public Status status; | ||
public List<Check> checks = new ArrayList<>(); | ||
|
||
public Check getCheck(String name) { | ||
return checks.stream() | ||
.filter(c -> c.name.equalsIgnoreCase(name)) | ||
.findAny().orElse(null); | ||
} | ||
|
||
public static class Check { | ||
public String name; | ||
public Status status; | ||
} | ||
|
||
public enum Status { | ||
UP, DOWN; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package org.example.app; | ||
|
||
import org.eclipse.microprofile.health.HealthCheck; | ||
import org.eclipse.microprofile.health.HealthCheckResponse; | ||
import org.eclipse.microprofile.health.Readiness; | ||
|
||
import javax.enterprise.context.ApplicationScoped; | ||
|
||
@Readiness | ||
@ApplicationScoped | ||
public class Health implements HealthCheck { | ||
|
||
@Override | ||
public HealthCheckResponse call() { | ||
return HealthCheckResponse.named("test-app").up().build(); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package org.example.app; | ||
|
||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.microshed.testing.health.Health; | ||
|
||
import static org.hamcrest.CoreMatchers.is; | ||
import static org.junit.Assert.assertThat; | ||
|
||
/** | ||
* Requires an local environment which is already running. | ||
* Thus enables to decouple the life cycles of the tests and results in a smaller turnaround time. | ||
* <p> | ||
* Note that this is a different approach to {@link LibertySmokeIT}. | ||
*/ | ||
public class LibertyAppSmokeIT { | ||
|
||
private TestApplication application; | ||
|
||
@BeforeEach | ||
void setUp() { | ||
application = new TestApplication(); | ||
} | ||
|
||
@Test | ||
void testIsSystemUp() { | ||
assertThat(application.health().status, is(org.microshed.testing.health.Health.Status.UP)); | ||
assertThat(application.health().getCheck("test-app").status, is(Health.Status.UP)); | ||
} | ||
|
||
@Test | ||
void testGetAllPeople() { | ||
assertThat(application.getAllPeople().size(), is(2)); | ||
} | ||
|
||
@Test | ||
void testGetPerson() { | ||
Person person = application.getAllPeople().iterator().next(); | ||
|
||
assertThat(application.getPerson(person.id), is(person)); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package org.example.app; | ||
|
||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.microshed.testing.MicroShedApplication; | ||
import org.microshed.testing.health.Health; | ||
|
||
import static org.hamcrest.CoreMatchers.is; | ||
import static org.junit.Assert.assertThat; | ||
|
||
/** | ||
* Requires an local environment which is already running at {@code localhost:9080}. | ||
* Thus enables to decouple the life cycles of the tests and results in a smaller turnaround time. | ||
* <p> | ||
* Note that this is a different approach to {@link LibertyAppSmokeIT}. | ||
*/ | ||
public class LibertySmokeIT { | ||
|
||
private MicroShedApplication application; | ||
|
||
@BeforeEach | ||
void setUp() { | ||
application = MicroShedApplication.withBaseUri("http://localhost:9080").build(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the application is already running at That last part (also running in CI) is important IMO. If I understand this code correctly, these tests would fail in CI because they require something else to start the server? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, wasn't sure how to fix that, please change so it doesn't interfere with the projects pipeline. In a real-world project I'd also rather make it aware of e.g. System properties or so :) |
||
} | ||
|
||
@Test | ||
void testIsSystemUp() { | ||
assertThat(application.health().status, is(Health.Status.UP)); | ||
assertThat(application.health().getCheck("test-app").status, is(Health.Status.UP)); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package org.example.app; | ||
|
||
import org.microshed.testing.MicroShedApplication; | ||
import org.microshed.testing.jaxrs.RestClientBuilder; | ||
|
||
import javax.ws.rs.core.MediaType; | ||
import javax.ws.rs.core.Response; | ||
import java.net.URI; | ||
import java.util.Collection; | ||
|
||
import static org.hamcrest.CoreMatchers.is; | ||
import static org.junit.Assert.assertThat; | ||
|
||
public class TestApplication extends MicroShedApplication { | ||
|
||
private PersonService personClient; | ||
|
||
public TestApplication() { | ||
super(URI.create("http://localhost:9080")); | ||
|
||
// we might use either the type-safe RestClient or the rootTarget and client contained in MicroShedApplication | ||
|
||
personClient = new RestClientBuilder() | ||
.withAppContextRoot("http://localhost:9080/myservice") | ||
.build(PersonService.class); | ||
} | ||
|
||
public Collection<Person> getAllPeople() { | ||
// different approach to built-in client | ||
return personClient.getAllPeople(); | ||
} | ||
|
||
public Person getPerson(long id) { | ||
// different approach to person client | ||
Response response = rootTarget.path("/myservice") | ||
.path(String.valueOf(id)) | ||
.request(MediaType.APPLICATION_JSON_TYPE) | ||
.get(); | ||
|
||
assertThat(response.getStatus(), is(200)); | ||
|
||
return response.readEntity(Person.class); | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this class is the only new class needed and we could remove the
MicroShedApplication
class entirely. A lot of what theMicroShedApplication
does is already done via theApplicationContainer
class (such as knowing hostname, port, and app context root).If we annotated this class with JAX-RS annotations, then users could inject and use it just like other REST clients from their application. For example:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So what'd be valuable from my PoV is that folks can programmatically create a class that provides access to the app/system, especially if they can extend it and use it as a nice delegate / abstraction layer to hide complexity. The JAX-RS interfaces kind of go into that direction, but not always fully. Also, I'd love some way to enable health without
@MicroShedTest
(think of it similar to whatRestClientBuilder
does).There's certainly room for adding a solely declarative way, like you're proposing, I also thought about that. However, when is the health check performed, if it's injected here, especially for manual envs? At test startup time? I think that'd make more sense if the user has some interface / "proxy object" to query: "please perform the health check now".