Skip to content
This repository was archived by the owner on Dec 4, 2023. It is now read-only.

Commit 24c2769

Browse files
Implemented HealthCheck feature (#840)
* Implemented HealthCheck feature * Removed test code that was commented out
1 parent edf96ef commit 24c2769

File tree

7 files changed

+476
-0
lines changed

7 files changed

+476
-0
lines changed

libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010

1111
import org.apache.commons.lang3.StringUtils;
1212

13+
import com.microsoft.bot.connector.ConnectorClient;
1314
import com.microsoft.bot.schema.Activity;
1415
import com.microsoft.bot.schema.ActivityTypes;
1516
import com.microsoft.bot.schema.ChannelAccount;
17+
import com.microsoft.bot.schema.HealthCheckResponse;
1618
import com.microsoft.bot.schema.MessageReaction;
1719
import com.microsoft.bot.schema.ResourceResponse;
1820
import com.microsoft.bot.schema.SignInConstants;
@@ -405,6 +407,10 @@ protected CompletableFuture<InvokeResponse> onInvokeActivity(TurnContext turnCon
405407
}
406408
return new InvokeResponse(HttpURLConnection.HTTP_INTERNAL_ERROR, null);
407409
});
410+
} else if (StringUtils.equals(turnContext.getActivity().getName(), "healthCheck")) {
411+
CompletableFuture<InvokeResponse> result = new CompletableFuture<>();
412+
result.complete(new InvokeResponse(HttpURLConnection.HTTP_OK, onHealthCheck(turnContext)));
413+
return result;
408414
}
409415

410416
CompletableFuture<InvokeResponse> result = new CompletableFuture<>();
@@ -436,6 +442,18 @@ protected CompletableFuture<Void> onSignInInvoke(TurnContext turnContext) {
436442
return result;
437443
}
438444

445+
/**
446+
* Invoked when a 'healthCheck' event is
447+
* received when the base behavior of onInvokeActivity is used.
448+
*
449+
* @param turnContext The current TurnContext.
450+
* @return A task that represents a HealthCheckResponse.
451+
*/
452+
protected CompletableFuture<HealthCheckResponse> onHealthCheck(TurnContext turnContext) {
453+
ConnectorClient client = turnContext.getTurnState().get(BotFrameworkAdapter.CONNECTOR_CLIENT_KEY);
454+
return CompletableFuture.completedFuture(HealthCheck.createHealthCheckResponse(client));
455+
}
456+
439457
/**
440458
* Creates a success InvokeResponse with the specified body.
441459
*
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.bot.builder;
5+
6+
import java.util.concurrent.ExecutionException;
7+
8+
import com.microsoft.bot.connector.ConnectorClient;
9+
import com.microsoft.bot.connector.authentication.AppCredentials;
10+
import com.microsoft.bot.schema.HealthCheckResponse;
11+
import com.microsoft.bot.schema.HealthResults;
12+
13+
/**
14+
* A class to process a HealthCheck request.
15+
*/
16+
public final class HealthCheck {
17+
18+
private HealthCheck() {
19+
// not called
20+
}
21+
22+
/**
23+
* @param connector the ConnectorClient instance for this request
24+
* @return HealthCheckResponse
25+
*/
26+
public static HealthCheckResponse createHealthCheckResponse(ConnectorClient connector) {
27+
HealthResults healthResults = new HealthResults();
28+
healthResults.setSuccess(true);
29+
30+
if (connector != null) {
31+
healthResults.setUserAgent(connector.getUserAgent());
32+
AppCredentials credentials = (AppCredentials) connector.credentials();
33+
try {
34+
healthResults.setAuthorization(credentials.getToken().get());
35+
} catch (InterruptedException | ExecutionException ignored) {
36+
// An exception here may happen when you have a valid appId but invalid or blank secret.
37+
// No callbacks will be possible, although the bot maybe healthy in other respects.
38+
}
39+
}
40+
41+
if (healthResults.getAuthorization() != null) {
42+
healthResults.setMessages(new String[]{"Health check succeeded."});
43+
} else {
44+
healthResults.setMessages(new String[]{"Health check succeeded.", "Callbacks are not authorized."});
45+
}
46+
47+
HealthCheckResponse response = new HealthCheckResponse();
48+
response.setHealthResults(healthResults);
49+
return response;
50+
}
51+
}

libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import java.util.ArrayList;
88
import java.util.List;
99
import java.util.concurrent.CompletableFuture;
10+
import java.util.concurrent.ExecutionException;
11+
import java.util.concurrent.atomic.AtomicReference;
1012

1113
public class ActivityHandlerTests {
1214
@Test
@@ -346,6 +348,98 @@ public void TestUnrecognizedActivityType() {
346348
Assert.assertEquals("onUnrecognizedActivityType", bot.getRecord().get(0));
347349
}
348350

351+
@Test
352+
public void TestHealthCheckAsyncOverride() {
353+
Activity activity = new Activity() {
354+
{
355+
setType(ActivityTypes.INVOKE);
356+
setName("healthCheck");
357+
}
358+
};
359+
360+
TurnContext turnContext = new TurnContextImpl(new TestInvokeAdapter(), activity);
361+
362+
TestActivityHandler bot = new TestActivityHandler();
363+
bot.onTurn(turnContext).join();
364+
365+
Assert.assertEquals(2, bot.getRecord().size());
366+
Assert.assertEquals("onInvokeActivity", bot.getRecord().get(0));
367+
Assert.assertEquals("onHealthCheck", bot.getRecord().get(1));
368+
}
369+
370+
@Test
371+
public void TestHealthCheckAsync() {
372+
Activity activity = new Activity() {
373+
{
374+
setType(ActivityTypes.INVOKE);
375+
setName("healthCheck");
376+
}
377+
};
378+
379+
AtomicReference<List<Activity>> activitiesToSend = new AtomicReference<>();
380+
TurnContext turnContext = new TurnContextImpl(new SimpleAdapter(activitiesToSend::set), activity);
381+
382+
ActivityHandler bot = new ActivityHandler();
383+
bot.onTurn(turnContext).join();
384+
385+
Assert.assertNotNull(activitiesToSend.get());
386+
Assert.assertEquals(1, activitiesToSend.get().size());
387+
Assert.assertTrue(activitiesToSend.get().get(0).getValue() instanceof InvokeResponse);
388+
Assert.assertEquals(200, ((InvokeResponse) activitiesToSend.get().get(0).getValue()).getStatus());
389+
CompletableFuture future = ((CompletableFuture) ((InvokeResponse) activitiesToSend.get().get(0).getValue())
390+
.getBody());
391+
HealthCheckResponse result = new HealthCheckResponse();
392+
result = (HealthCheckResponse) future.join();
393+
Assert.assertTrue(result.getHealthResults().getSuccess());
394+
String[] messages = result.getHealthResults().getMessages();
395+
Assert.assertEquals(messages[0], "Health check succeeded.");
396+
}
397+
398+
@Test
399+
public void TestHealthCheckWithConnectorAsync() {
400+
Activity activity = new Activity() {
401+
{
402+
setType(ActivityTypes.INVOKE);
403+
setName("healthCheck");
404+
}
405+
};
406+
407+
AtomicReference<List<Activity>> activitiesToSend = new AtomicReference<>();
408+
TurnContext turnContext = new TurnContextImpl(new SimpleAdapter(activitiesToSend::set), activity);
409+
MockConnectorClient mockConnector = new MockConnectorClient("Windows/3.1", new MockAppCredentials("awesome"));
410+
turnContext.getTurnState().add(BotFrameworkAdapter.CONNECTOR_CLIENT_KEY, mockConnector);
411+
ActivityHandler bot = new ActivityHandler();
412+
bot.onTurn(turnContext).join();
413+
414+
Assert.assertNotNull(activitiesToSend.get());
415+
Assert.assertEquals(1, activitiesToSend.get().size());
416+
Assert.assertTrue(activitiesToSend.get().get(0).getValue() instanceof InvokeResponse);
417+
Assert.assertEquals(
418+
200,
419+
((InvokeResponse) activitiesToSend.get().get(0).getValue()).getStatus()
420+
);
421+
CompletableFuture<HealthCheckResponse> future =
422+
((CompletableFuture<HealthCheckResponse>)
423+
((InvokeResponse) activitiesToSend.get().get(0).getValue()).getBody());
424+
HealthCheckResponse result = new HealthCheckResponse();
425+
result = (HealthCheckResponse) future.join();
426+
Assert.assertTrue(result.getHealthResults().getSuccess());
427+
Assert.assertEquals(result.getHealthResults().getAuthorization(), "awesome");
428+
Assert.assertEquals(result.getHealthResults().getUserAgent(), "Windows/3.1");
429+
String[] messages = result.getHealthResults().getMessages();
430+
Assert.assertEquals(messages[0], "Health check succeeded.");
431+
}
432+
433+
private static class TestInvokeAdapter extends NotImplementedAdapter {
434+
@Override
435+
public CompletableFuture<ResourceResponse[]> sendActivities(
436+
TurnContext context,
437+
List<Activity> activities
438+
) {
439+
return CompletableFuture.completedFuture(new ResourceResponse[0]);
440+
}
441+
}
442+
349443
private static class NotImplementedAdapter extends BotAdapter {
350444
@Override
351445
public CompletableFuture<ResourceResponse[]> sendActivities(
@@ -455,6 +549,18 @@ protected CompletableFuture onEvent(TurnContext turnContext) {
455549
return super.onEvent(turnContext);
456550
}
457551

552+
@Override
553+
protected CompletableFuture<InvokeResponse> onInvokeActivity(TurnContext turnContext) {
554+
record.add("onInvokeActivity");
555+
return super.onInvokeActivity(turnContext);
556+
}
557+
558+
@Override
559+
protected CompletableFuture<HealthCheckResponse> onHealthCheck(TurnContext turnContext) {
560+
record.add("onHealthCheck");
561+
return super.onHealthCheck(turnContext);
562+
}
563+
458564
@Override
459565
protected CompletableFuture onInstallationUpdate(TurnContext turnContext) {
460566
record.add("onInstallationUpdate");
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.microsoft.bot.builder;
2+
3+
import java.util.concurrent.CompletableFuture;
4+
5+
import com.microsoft.bot.connector.authentication.AppCredentials;
6+
import com.microsoft.bot.connector.authentication.Authenticator;
7+
8+
public class MockAppCredentials extends AppCredentials {
9+
10+
private String token;
11+
12+
public MockAppCredentials(String token) {
13+
super(null);
14+
this.token = token;
15+
}
16+
17+
@Override
18+
public CompletableFuture<String> getToken() {
19+
CompletableFuture<String> result;
20+
21+
result = new CompletableFuture<String>();
22+
result.complete(this.token);
23+
return result;
24+
}
25+
26+
/**
27+
* Returns an appropriate Authenticator that is provided by a subclass.
28+
*
29+
* @return An Authenticator object.
30+
* @throws MalformedURLException If the endpoint isn't valid.
31+
*/
32+
protected Authenticator buildAuthenticator(){
33+
return null;
34+
};
35+
36+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.microsoft.bot.builder;
2+
3+
import com.microsoft.bot.connector.Attachments;
4+
import com.microsoft.bot.connector.ConnectorClient;
5+
import com.microsoft.bot.connector.Conversations;
6+
import com.microsoft.bot.connector.authentication.AppCredentials;
7+
import com.microsoft.bot.rest.RestClient;
8+
import com.microsoft.bot.rest.credentials.ServiceClientCredentials;
9+
10+
public class MockConnectorClient implements ConnectorClient {
11+
12+
private AppCredentials credentials;
13+
private String userAgent;
14+
15+
public MockConnectorClient(String userAgent, AppCredentials credentials) {
16+
this.userAgent = userAgent;
17+
this.credentials = credentials;
18+
}
19+
20+
private MemoryConversations conversations = new MemoryConversations();
21+
22+
@Override
23+
public RestClient getRestClient() {
24+
return null;
25+
}
26+
27+
@Override
28+
public String baseUrl() {
29+
return null;
30+
}
31+
32+
@Override
33+
public ServiceClientCredentials credentials() {
34+
return credentials;
35+
}
36+
37+
@Override
38+
public String getUserAgent() {
39+
return userAgent;
40+
}
41+
42+
@Override
43+
public String getAcceptLanguage() {
44+
return null;
45+
}
46+
47+
@Override
48+
public void setAcceptLanguage(String acceptLanguage) {
49+
50+
}
51+
52+
@Override
53+
public int getLongRunningOperationRetryTimeout() {
54+
return 0;
55+
}
56+
57+
@Override
58+
public void setLongRunningOperationRetryTimeout(int timeout) {
59+
60+
}
61+
62+
@Override
63+
public boolean getGenerateClientRequestId() {
64+
return false;
65+
}
66+
67+
@Override
68+
public void setGenerateClientRequestId(boolean generateClientRequestId) {
69+
70+
}
71+
72+
@Override
73+
public Attachments getAttachments() {
74+
return null;
75+
}
76+
77+
@Override
78+
public Conversations getConversations() {
79+
return conversations;
80+
}
81+
82+
@Override
83+
public void close() throws Exception {
84+
85+
}
86+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.bot.schema;
5+
6+
import com.fasterxml.jackson.annotation.JsonInclude;
7+
import com.fasterxml.jackson.annotation.JsonProperty;
8+
9+
/**
10+
* Defines the structure that is returned as the result of a health check on the bot.
11+
* The health check is sent to the bot as an {@link Activity} of type "invoke" and this class along
12+
* with {@link HealthResults} defines the structure of the body of the response.
13+
* The name of the invoke Activity is "healthCheck".
14+
*/
15+
public class HealthCheckResponse {
16+
/**
17+
* The health check results.
18+
*/
19+
@JsonProperty(value = "healthResults")
20+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
21+
private HealthResults healthResults;
22+
23+
/**
24+
* Gets the healthResults value.
25+
*
26+
* @return The healthResults value.
27+
*/
28+
public HealthResults getHealthResults() {
29+
return this.healthResults;
30+
}
31+
32+
/**
33+
* Sets the healthResults value.
34+
*
35+
* @param withHealthResults The healthResults value to set.
36+
*/
37+
public void setHealthResults(HealthResults withHealthResults) {
38+
this.healthResults = withHealthResults;
39+
}
40+
}

0 commit comments

Comments
 (0)