Added InspectionTests and related fixes.

This commit is contained in:
Tracy Boehrer 2019-09-26 09:16:19 -05:00
Родитель cb54c1c77c
Коммит a708abdcf9
7 изменённых файлов: 418 добавлений и 79 удалений

Просмотреть файл

@ -81,7 +81,7 @@ public abstract class BotState implements PropertyManager {
if (force || cachedState == null || cachedState.getState() == null) { if (force || cachedState == null || cachedState.getState() == null) {
return storage.read(new String[]{storageKey}) return storage.read(new String[]{storageKey})
.thenApply(val -> { .thenApply(val -> {
turnContext.getTurnState().put(contextServiceKey, new CachedBotState((Map<String, Object>)val.get(storageKey))); turnContext.getTurnState().replace(contextServiceKey, new CachedBotState((Map<String, Object>)val.get(storageKey)));
return null; return null;
}); });
} }
@ -141,7 +141,7 @@ public abstract class BotState implements PropertyManager {
throw new IllegalArgumentException("turnContext cannot be null"); throw new IllegalArgumentException("turnContext cannot be null");
} }
turnContext.getTurnState().put(contextServiceKey, new CachedBotState()); turnContext.getTurnState().replace(contextServiceKey, new CachedBotState());
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
@ -179,7 +179,7 @@ public abstract class BotState implements PropertyManager {
throw new IllegalArgumentException("turnContext cannot be null"); throw new IllegalArgumentException("turnContext cannot be null");
} }
String stateKey = getClass().getName(); String stateKey = getClass().getSimpleName();
CachedBotState cachedState = turnContext.getTurnState().get(stateKey); CachedBotState cachedState = turnContext.getTurnState().get(stateKey);
return new ObjectMapper().valueToTree(cachedState.state); return new ObjectMapper().valueToTree(cachedState.state);
} }

Просмотреть файл

@ -11,13 +11,15 @@ import java.util.Map;
/** /**
* Represents a set of collection of services associated with the {@link TurnContext}. * Represents a set of collection of services associated with the {@link TurnContext}.
*/ */
public class TurnContextStateCollection extends HashMap<String, Object> implements AutoCloseable { public class TurnContextStateCollection implements AutoCloseable {
private Map<String, Object> state = new HashMap<>();
public <T> T get(String key) throws IllegalArgumentException { public <T> T get(String key) throws IllegalArgumentException {
if (key == null) { if (key == null) {
throw new IllegalArgumentException("key"); throw new IllegalArgumentException("key");
} }
Object service = super.get(key); Object service = state.get(key);
try { try {
T result = (T) service; T result = (T) service;
} catch (ClassCastException e) { } catch (ClassCastException e) {
@ -30,11 +32,12 @@ public class TurnContextStateCollection extends HashMap<String, Object> implemen
/** /**
* Get a service by type using its full type name as the key. * Get a service by type using its full type name as the key.
* *
* @param type The type of service to be retrieved. * @param type The type of service to be retrieved. This will use the value returned
* by Class.getSimpleName as the key.
* @return The service stored under the specified key. * @return The service stored under the specified key.
*/ */
public <T> T get(Class<T> type) throws IllegalArgumentException { public <T> T get(Class<T> type) throws IllegalArgumentException {
return get(type.getName()); return get(type.getSimpleName());
} }
public <T> void add(String key, T value) throws IllegalArgumentException { public <T> void add(String key, T value) throws IllegalArgumentException {
@ -43,21 +46,36 @@ public class TurnContextStateCollection extends HashMap<String, Object> implemen
} }
if (value == null) { if (value == null) {
throw new IllegalArgumentException("service"); throw new IllegalArgumentException("value");
} }
if (containsKey(key)) if (state.containsKey(key)) {
throw new IllegalArgumentException(String.format("Key %s already exists", key)); throw new IllegalArgumentException(String.format("Key %s already exists", key));
put(key, value); }
state.put(key, value);
} }
/** /**
* Add a service using its full type name as the key. * Add a service using its type name ({@link Class#getSimpleName()} as the key.
* *
* @param value The service to add. * @param value The service to add.
*/ */
public <T> void add(T value) throws IllegalArgumentException { public <T> void add(T value) throws IllegalArgumentException {
add(value.getClass().getName(), value); if (value == null) {
throw new IllegalArgumentException("value");
}
add(value.getClass().getSimpleName(), value);
}
public void remove(String key) {
state.remove(key);
}
public void replace(String key, Object value) {
state.remove(key);
add(key, value);
} }
@Override @Override
@ -71,7 +89,7 @@ public class TurnContextStateCollection extends HashMap<String, Object> implemen
@Override @Override
public void close() throws Exception { public void close() throws Exception {
for (Map.Entry entry : entrySet()) { for (Map.Entry entry : state.entrySet()) {
if (entry.getValue() instanceof AutoCloseable) { if (entry.getValue() instanceof AutoCloseable) {
if (entry.getValue() instanceof ConnectorClient) { if (entry.getValue() instanceof ConnectorClient) {
continue; continue;

Просмотреть файл

@ -11,7 +11,6 @@ import com.microsoft.bot.schema.Activity;
import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.ActivityTypes;
import com.microsoft.bot.schema.ConversationReference; import com.microsoft.bot.schema.ConversationReference;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
@ -31,16 +30,14 @@ public class InspectionMiddleware extends InterceptionMiddleware {
this(withInspectionState, this(withInspectionState,
null, null,
null, null,
null, null);
LoggerFactory.getLogger(InspectionMiddleware.class));
} }
public InspectionMiddleware(InspectionState withInspectionState, public InspectionMiddleware(InspectionState withInspectionState,
UserState withUserState, UserState withUserState,
ConversationState withConversationState, ConversationState withConversationState,
MicrosoftAppCredentials withCredentials, MicrosoftAppCredentials withCredentials) {
Logger withLogger) { super(LoggerFactory.getLogger(InspectionMiddleware.class));
super(withLogger);
inspectionState = withInspectionState; inspectionState = withInspectionState;
userState = withUserState; userState = withUserState;
@ -49,7 +46,7 @@ public class InspectionMiddleware extends InterceptionMiddleware {
} }
public CompletableFuture<Boolean> processCommand(TurnContext turnContext) { public CompletableFuture<Boolean> processCommand(TurnContext turnContext) {
if (!StringUtils.equals(turnContext.getActivity().getType(), ActivityTypes.MESSAGE) if (!turnContext.getActivity().isType(ActivityTypes.MESSAGE)
|| StringUtils.isEmpty(turnContext.getActivity().getText())) { || StringUtils.isEmpty(turnContext.getActivity().getText())) {
return CompletableFuture.completedFuture(false); return CompletableFuture.completedFuture(false);
@ -58,7 +55,7 @@ public class InspectionMiddleware extends InterceptionMiddleware {
String text = Activity.removeRecipientMentionImmutable(turnContext.getActivity()); String text = Activity.removeRecipientMentionImmutable(turnContext.getActivity());
String[] command = text.split(" "); String[] command = text.split(" ");
if (command.length > 1 && StringUtils.equals(command[1], COMMAND)) { if (command.length > 1 && StringUtils.equals(command[0], COMMAND)) {
if (command.length == 2 && StringUtils.equals(command[1], "open")) { if (command.length == 2 && StringUtils.equals(command[1], "open")) {
return processOpenCommand(turnContext) return processOpenCommand(turnContext)
.thenApply((result) -> true); .thenApply((result) -> true);
@ -76,14 +73,25 @@ public class InspectionMiddleware extends InterceptionMiddleware {
@Override @Override
protected CompletableFuture<Intercept> inbound(TurnContext turnContext, Activity activity) { protected CompletableFuture<Intercept> inbound(TurnContext turnContext, Activity activity) {
return processCommand(turnContext) return processCommand(turnContext)
.thenCombine(findSession(turnContext), (processResult, session) -> { .thenCompose(processResult -> {
if (session != null) { if (processResult) {
if (invokeSend(turnContext, session, activity).join()) { return CompletableFuture.completedFuture(new Intercept(false, false));
return new Intercept(true, true);
}
} }
return new Intercept(true, false); return findSession(turnContext)
.thenCompose(session -> {
if (session == null) {
return CompletableFuture.completedFuture(new Intercept(true, false));
}
return invokeSend(turnContext, session, activity)
.thenCompose(invokeResult -> {
if (invokeResult) {
return CompletableFuture.completedFuture(new Intercept(true, true));
}
return CompletableFuture.completedFuture(new Intercept(true, false));
});
});
}); });
} }
@ -108,30 +116,34 @@ public class InspectionMiddleware extends InterceptionMiddleware {
@Override @Override
protected CompletableFuture<Void> traceState(TurnContext turnContext) { protected CompletableFuture<Void> traceState(TurnContext turnContext) {
return findSession(turnContext) return findSession(turnContext)
.thenAccept(session -> { .thenCompose(session -> {
if (session != null) { if (session == null) {
CompletableFuture<Void> userLoad = userState == null return CompletableFuture.completedFuture(null);
? CompletableFuture.completedFuture(null)
: userState.load(turnContext);
CompletableFuture<Void> conversationLoad = conversationState == null
? CompletableFuture.completedFuture(null)
: conversationState.load(turnContext);
CompletableFuture.allOf(userLoad, conversationLoad).join();
ObjectNode botState = JsonNodeFactory.instance.objectNode();
if (userState != null) {
botState.set("userState", userState.get(turnContext));
}
if (conversationState != null) {
botState.set("conversationState", conversationState.get(turnContext));
}
invokeSend(turnContext, session, InspectionActivityExtensions.traceActivity(botState)).join();
} }
});
CompletableFuture<Void> userLoad = userState == null
? CompletableFuture.completedFuture(null)
: userState.load(turnContext);
CompletableFuture<Void> conversationLoad = conversationState == null
? CompletableFuture.completedFuture(null)
: conversationState.load(turnContext);
return CompletableFuture.allOf(userLoad, conversationLoad)
.thenCompose(loadResult -> {
ObjectNode botState = JsonNodeFactory.instance.objectNode();
if (userState != null) {
botState.set("userState", userState.get(turnContext));
}
if (conversationState != null) {
botState.set("conversationState", conversationState.get(turnContext));
}
return invokeSend(turnContext, session, InspectionActivityExtensions.traceActivity(botState))
.thenCompose(invokeResult -> CompletableFuture.completedFuture(null));
});
});
} }
private CompletableFuture<Void> processOpenCommand(TurnContext turnContext) { private CompletableFuture<Void> processOpenCommand(TurnContext turnContext) {
@ -139,16 +151,14 @@ public class InspectionMiddleware extends InterceptionMiddleware {
inspectionState.createProperty(InspectionSessionsByStatus.class.getName()); inspectionState.createProperty(InspectionSessionsByStatus.class.getName());
return accessor.get(turnContext, InspectionSessionsByStatus::new) return accessor.get(turnContext, InspectionSessionsByStatus::new)
.thenAccept(result -> { .thenCompose(result -> {
InspectionSessionsByStatus sessions = (InspectionSessionsByStatus) result; InspectionSessionsByStatus sessions = (InspectionSessionsByStatus) result;
String sessionId = openCommand(sessions, turnContext.getActivity().getConversationReference()); String sessionId = openCommand(sessions, turnContext.getActivity().getConversationReference());
String command = String.format("%s attach %s", COMMAND, sessionId); String command = String.format("%s attach %s", COMMAND, sessionId);
turnContext.sendActivity(InspectionActivityExtensions.makeCommandActivity(command)).join(); return turnContext.sendActivity(InspectionActivityExtensions.makeCommandActivity(command));
}) })
.thenRun(() -> { .thenCompose(resourceResponse -> inspectionState.saveChanges(turnContext));
inspectionState.saveChanges(turnContext);
});
} }
private CompletableFuture<Void> processAttachCommand(TurnContext turnContext, String sessionId) { private CompletableFuture<Void> processAttachCommand(TurnContext turnContext, String sessionId) {
@ -156,20 +166,16 @@ public class InspectionMiddleware extends InterceptionMiddleware {
inspectionState.createProperty(InspectionSessionsByStatus.class.getName()); inspectionState.createProperty(InspectionSessionsByStatus.class.getName());
return accessor.get(turnContext, InspectionSessionsByStatus::new) return accessor.get(turnContext, InspectionSessionsByStatus::new)
.thenAccept(result -> { .thenCompose(sessions -> {
InspectionSessionsByStatus sessions = (InspectionSessionsByStatus) result;
if (attachCommand(turnContext.getActivity().getConversation().getId(), sessions, sessionId)) { if (attachCommand(turnContext.getActivity().getConversation().getId(), sessions, sessionId)) {
turnContext.sendActivity(MessageFactory.text( return turnContext.sendActivity(MessageFactory.text(
"Attached to session, all traffic is being replicated for inspection.")).join(); "Attached to session, all traffic is being replicated for inspection."));
} else { } else {
turnContext.sendActivity(MessageFactory.text( return turnContext.sendActivity(MessageFactory.text(
String.format("Open session with id %s does not exist.", sessionId))).join(); String.format("Open session with id %s does not exist.", sessionId)));
} }
}) })
.thenRun(() -> { .thenCompose(resourceResponse -> inspectionState.saveChanges(turnContext));
inspectionState.saveChanges(turnContext);
});
} }
private String openCommand(InspectionSessionsByStatus sessions, ConversationReference conversationReference) { private String openCommand(InspectionSessionsByStatus sessions, ConversationReference conversationReference) {
@ -189,6 +195,10 @@ public class InspectionMiddleware extends InterceptionMiddleware {
return true; return true;
} }
protected InspectionSession createSession(ConversationReference reference, MicrosoftAppCredentials credentials) {
return new InspectionSession(reference, credentials);
}
private CompletableFuture<InspectionSession> findSession(TurnContext turnContext) { private CompletableFuture<InspectionSession> findSession(TurnContext turnContext) {
StatePropertyAccessor<InspectionSessionsByStatus> accessor = StatePropertyAccessor<InspectionSessionsByStatus> accessor =
inspectionState.createProperty(InspectionSessionsByStatus.class.getName()); inspectionState.createProperty(InspectionSessionsByStatus.class.getName());
@ -201,7 +211,7 @@ public class InspectionMiddleware extends InterceptionMiddleware {
.get(turnContext.getActivity().getConversation().getId()); .get(turnContext.getActivity().getConversation().getId());
if (reference != null) { if (reference != null) {
return new InspectionSession(reference, credentials, getLogger()); return createSession(reference, credentials);
} }
return null; return null;
@ -213,13 +223,13 @@ public class InspectionMiddleware extends InterceptionMiddleware {
Activity activity) { Activity activity) {
return session.send(activity) return session.send(activity)
.thenApply(result -> { .thenCompose(result -> {
if (result) { if (result) {
return true; return CompletableFuture.completedFuture(true);
} }
cleanupSession(turnContext).join(); return cleanupSession(turnContext)
return false; .thenCompose(cleanupResult -> CompletableFuture.completedFuture(false));
}); });
} }

Просмотреть файл

@ -46,13 +46,31 @@ public abstract class InterceptionMiddleware implements Middleware {
turnContext.onSendActivities((sendContext, sendActivities, sendNext) -> { turnContext.onSendActivities((sendContext, sendActivities, sendNext) -> {
List<Activity> traceActivities = sendActivities.stream() List<Activity> traceActivities = sendActivities.stream()
.map(a -> .map(a ->
InspectionActivityExtensions.traceActivity(a, "SentActivity", "Sent Activity")) InspectionActivityExtensions.traceActivity(a,
"SentActivity", "Sent Activity"))
.collect(Collectors.toList()); .collect(Collectors.toList());
return invokeOutbound(sendContext, traceActivities) return invokeOutbound(sendContext, traceActivities)
.thenCompose(response -> { .thenCompose(response -> {
return sendNext.get(); return sendNext.get();
}); });
}); });
turnContext.onUpdateActivity((updateContext, updateActivity, updateNext) -> {
Activity traceActivity = InspectionActivityExtensions.traceActivity(updateActivity,
"MessageUpdate", "Message Update");
return invokeOutbound(turnContext, traceActivity)
.thenCompose(response -> {
return updateNext.get();
});
});
turnContext.onDeleteActivity((deleteContext, deleteReference, deleteNext) -> {
Activity traceActivity = InspectionActivityExtensions.traceActivity(deleteReference);
return invokeOutbound(turnContext, traceActivity)
.thenCompose(response -> {
return deleteNext.get();
});
});
} }
if (intercept.shouldForwardToApplication) { if (intercept.shouldForwardToApplication) {
@ -68,7 +86,7 @@ public abstract class InterceptionMiddleware implements Middleware {
return invokeTraceState(turnContext); return invokeTraceState(turnContext);
} }
return null; return CompletableFuture.completedFuture(null);
}); });
} }
@ -89,9 +107,9 @@ public abstract class InterceptionMiddleware implements Middleware {
private CompletableFuture<Void> invokeOutbound(TurnContext turnContext, List<Activity> traceActivities) { private CompletableFuture<Void> invokeOutbound(TurnContext turnContext, List<Activity> traceActivities) {
return outbound(turnContext, traceActivities) return outbound(turnContext, traceActivities)
.exceptionally(exception -> { .exceptionally(exception -> {
logger.warn("Exception in outbound interception {}", exception.getMessage()); logger.warn("Exception in outbound interception {}", exception.getMessage());
return null; return null;
}); });
} }
private CompletableFuture<Void> invokeOutbound(TurnContext turnContext, Activity activity) { private CompletableFuture<Void> invokeOutbound(TurnContext turnContext, Activity activity) {
@ -109,8 +127,8 @@ public abstract class InterceptionMiddleware implements Middleware {
private CompletableFuture<Void> invokeTraceException(TurnContext turnContext, Activity traceActivity) { private CompletableFuture<Void> invokeTraceException(TurnContext turnContext, Activity traceActivity) {
return outbound(turnContext, Collections.singletonList(Activity.createContactRelationUpdateActivity())) return outbound(turnContext, Collections.singletonList(Activity.createContactRelationUpdateActivity()))
.exceptionally(exception -> { .exceptionally(exception -> {
logger.warn("Exception in exception interception {}", exception.getMessage()); logger.warn("Exception in exception interception {}", exception.getMessage());
return null; return null;
}); });
} }
} }

Просмотреть файл

@ -0,0 +1,272 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.builder;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.microsoft.bot.builder.adapters.TestAdapter;
import com.microsoft.bot.builder.inspection.InspectionMiddleware;
import com.microsoft.bot.builder.inspection.InspectionSession;
import com.microsoft.bot.builder.inspection.InspectionState;
import com.microsoft.bot.connector.Channels;
import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials;
import com.microsoft.bot.schema.Activity;
import com.microsoft.bot.schema.ConversationReference;
import com.microsoft.bot.schema.Entity;
import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class InspectionTests {
@Test
public void ScenarioWithInspectionMiddlewarePassthrough() {
InspectionState inspectionState = new InspectionState(new MemoryStorage());
InspectionMiddleware inspectionMiddleware = new InspectionMiddleware(inspectionState);
TestAdapter adapter = new TestAdapter()
.use(inspectionMiddleware);
Activity inboundActivity = MessageFactory.text("hello");
adapter.processActivity(inboundActivity, turnContext -> {
turnContext.sendActivity(MessageFactory.text("hi")).join();
return CompletableFuture.completedFuture(null);
}).join();
Activity outboundActivity = adapter.activeQueue().poll();
Assert.assertEquals("hi", outboundActivity.getText());
}
@Test
public void ScenarioWithInspectionMiddlewareOpenAttach() throws IOException {
// any bot state should be returned as trace messages per turn
MemoryStorage storage = new MemoryStorage();
InspectionState inspectionState = new InspectionState(storage);
UserState userState = new UserState(storage);
ConversationState conversationState = new ConversationState(storage);
TestInspectionMiddleware inspectionMiddleware = new TestInspectionMiddleware(
inspectionState,
userState,
conversationState,
null);
// (1) send the /INSPECT open command from the emulator to the middleware
Activity openActivity = MessageFactory.text("/INSPECT open");
TestAdapter inspectionAdapter = new TestAdapter(Channels.TEST);
inspectionAdapter.processActivity(openActivity, turnContext -> {
inspectionMiddleware.processCommand(turnContext).join();
return CompletableFuture.completedFuture(null);
}).join();
Activity inspectionOpenResultActivity = inspectionAdapter.activeQueue().poll();
// (2) send the resulting /INSPECT attach command from the channel to the middleware
TestAdapter applicationAdapter = new TestAdapter(Channels.TEST);
applicationAdapter.use(inspectionMiddleware);
String attachCommand = inspectionOpenResultActivity.getValue().toString();
applicationAdapter.processActivity(MessageFactory.text(attachCommand), turnContext -> {
// nothing happens - just attach the inspector
return CompletableFuture.completedFuture(null);
}).join();
Activity attachResponse = applicationAdapter.activeQueue().poll();
// (3) send an application messaage from the channel, it should get the reply and then so should the emulator http endpioint
applicationAdapter.processActivity(MessageFactory.text("hi"), turnContext -> {
turnContext.sendActivity(MessageFactory.text("echo: " + turnContext.getActivity().getText())).join();
userState.<Scratch>createProperty("x").get(turnContext, Scratch::new).join().setProperty("hello");
conversationState.<Scratch>createProperty("y").get(turnContext, Scratch::new).join().setProperty("world");
userState.saveChanges(turnContext).join();
conversationState.saveChanges(turnContext).join();
return CompletableFuture.completedFuture(null);
}).join();
Activity outboundActivity = applicationAdapter.activeQueue().poll();
Assert.assertEquals("echo: hi", outboundActivity.getText());
Assert.assertEquals(3, inspectionMiddleware.recordingSession.requests.size());
ObjectMapper mapper = new ObjectMapper();
mapper.findAndRegisterModules();
JsonNode inboundTrace = mapper.readTree(inspectionMiddleware.recordingSession.requests.get(0));
Assert.assertEquals("trace", inboundTrace.get("type").textValue());
Assert.assertEquals("ReceivedActivity", inboundTrace.get("name").textValue());
Assert.assertEquals("message", inboundTrace.get("value").get("type").textValue());
Assert.assertEquals("hi", inboundTrace.get("value").get("text").textValue());
JsonNode outboundTrace = mapper.readTree(inspectionMiddleware.recordingSession.requests.get(1));
Assert.assertEquals("trace", outboundTrace.get("type").textValue());
Assert.assertEquals("SentActivity", outboundTrace.get("name").textValue());
Assert.assertEquals("message", outboundTrace.get("value").get("type").textValue());
Assert.assertEquals("echo: hi", outboundTrace.get("value").get("text").textValue());
JsonNode stateTrace = mapper.readTree(inspectionMiddleware.recordingSession.requests.get(2));
Assert.assertEquals("trace", stateTrace.get("type").textValue());
Assert.assertEquals("BotState", stateTrace.get("name").textValue());
Assert.assertEquals("hello", stateTrace.get("value").get("userState").get("x").get("property").textValue());
Assert.assertEquals("world", stateTrace.get("value").get("conversationState").get("y").get("property").textValue());
}
@Test
public void ScenarioWithInspectionMiddlewareOpenAttachWithMention() throws IOException {
// any bot state should be returned as trace messages per turn
MemoryStorage storage = new MemoryStorage();
InspectionState inspectionState = new InspectionState(storage);
UserState userState = new UserState(storage);
ConversationState conversationState = new ConversationState(storage);
TestInspectionMiddleware inspectionMiddleware = new TestInspectionMiddleware(
inspectionState,
userState,
conversationState,
null);
// (1) send the /INSPECT open command from the emulator to the middleware
Activity openActivity = MessageFactory.text("/INSPECT open");
TestAdapter inspectionAdapter = new TestAdapter(Channels.TEST);
inspectionAdapter.processActivity(openActivity, turnContext -> {
inspectionMiddleware.processCommand(turnContext).join();
return CompletableFuture.completedFuture(null);
}).join();
Activity inspectionOpenResultActivity = inspectionAdapter.activeQueue().poll();
// (2) send the resulting /INSPECT attach command from the channel to the middleware
TestAdapter applicationAdapter = new TestAdapter(Channels.TEST);
applicationAdapter.use(inspectionMiddleware);
// some channels - for example Microsoft Teams - adds an @ mention to the text - this should be taken into account when evaluating the INSPECT
String recipientId = "bot";
String attachCommand = "<at>" + recipientId + "</at> " + inspectionOpenResultActivity.getValue();
Activity attachActivity = MessageFactory.text(attachCommand);
attachActivity.getEntities().add(new Entity() {{
setType("mention");
getProperties().put("text", JsonNodeFactory.instance.textNode("<at>" + recipientId + "</at>"));
getProperties().put("mentioned", JsonNodeFactory.instance.objectNode().put("id", "bot"));
}});
applicationAdapter.processActivity(attachActivity, turnContext -> {
// nothing happens - just attach the inspector
return CompletableFuture.completedFuture(null);
}).join();
Activity attachResponse = applicationAdapter.activeQueue().poll();
// (3) send an application messaage from the channel, it should get the reply and then so should the emulator http endpioint
applicationAdapter.processActivity(MessageFactory.text("hi"), turnContext -> {
turnContext.sendActivity(MessageFactory.text("echo: " + turnContext.getActivity().getText())).join();
userState.<Scratch>createProperty("x").get(turnContext, Scratch::new).join().setProperty("hello");
conversationState.<Scratch>createProperty("y").get(turnContext, Scratch::new).join().setProperty("world");
userState.saveChanges(turnContext).join();
conversationState.saveChanges(turnContext).join();
return CompletableFuture.completedFuture(null);
}).join();
Activity outboundActivity = applicationAdapter.activeQueue().poll();
Assert.assertEquals("echo: hi", outboundActivity.getText());
Assert.assertEquals(3, inspectionMiddleware.recordingSession.requests.size());
ObjectMapper mapper = new ObjectMapper();
mapper.findAndRegisterModules();
JsonNode inboundTrace = mapper.readTree(inspectionMiddleware.recordingSession.requests.get(0));
Assert.assertEquals("trace", inboundTrace.get("type").textValue());
Assert.assertEquals("ReceivedActivity", inboundTrace.get("name").textValue());
Assert.assertEquals("message", inboundTrace.get("value").get("type").textValue());
Assert.assertEquals("hi", inboundTrace.get("value").get("text").textValue());
JsonNode outboundTrace = mapper.readTree(inspectionMiddleware.recordingSession.requests.get(1));
Assert.assertEquals("trace", outboundTrace.get("type").textValue());
Assert.assertEquals("SentActivity", outboundTrace.get("name").textValue());
Assert.assertEquals("message", outboundTrace.get("value").get("type").textValue());
Assert.assertEquals("echo: hi", outboundTrace.get("value").get("text").textValue());
JsonNode stateTrace = mapper.readTree(inspectionMiddleware.recordingSession.requests.get(2));
Assert.assertEquals("trace", stateTrace.get("type").textValue());
Assert.assertEquals("BotState", stateTrace.get("name").textValue());
Assert.assertEquals("hello", stateTrace.get("value").get("userState").get("x").get("property").textValue());
Assert.assertEquals("world", stateTrace.get("value").get("conversationState").get("y").get("property").textValue());
}
// We can't currently supply a custom httpclient like dotnet. So instead, these test differ from dotnet by
// supplying a custom InspectionSession that records what is sent through it.
private static class TestInspectionMiddleware extends InspectionMiddleware {
public RecordingInspectionSession recordingSession = null;
public TestInspectionMiddleware(InspectionState withInspectionState) {
super(withInspectionState);
}
public TestInspectionMiddleware(InspectionState withInspectionState, UserState withUserState, ConversationState withConversationState, MicrosoftAppCredentials withCredentials) {
super(withInspectionState, withUserState, withConversationState, withCredentials);
}
@Override
protected InspectionSession createSession(ConversationReference reference, MicrosoftAppCredentials credentials) {
if (recordingSession == null) {
recordingSession = new RecordingInspectionSession(reference, credentials);
}
return recordingSession;
}
}
private static class RecordingInspectionSession extends InspectionSession {
private List<String> requests = new ArrayList<>();
ObjectMapper mapper = new ObjectMapper();
public RecordingInspectionSession(ConversationReference withConversationReference, MicrosoftAppCredentials withCredentials) {
super(withConversationReference, withCredentials);
mapper.findAndRegisterModules();
}
public RecordingInspectionSession(ConversationReference withConversationReference, MicrosoftAppCredentials withCredentials, Logger withLogger) {
super(withConversationReference, withCredentials, withLogger);
mapper.findAndRegisterModules();
}
public List<String> getRequests() {
return requests;
}
@Override
public CompletableFuture<Boolean> send(Activity activity) {
try {
requests.add(mapper.writeValueAsString(activity));
} catch (Throwable t) {
//noop
}
return CompletableFuture.completedFuture(true);
}
}
private static class Scratch {
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
private String property;
}
}

Просмотреть файл

@ -19,9 +19,28 @@ public class TestAdapter extends BotAdapter {
private ConversationReference conversationReference; private ConversationReference conversationReference;
public TestAdapter() { public TestAdapter() {
this(null); this((ConversationReference) null);
} }
public TestAdapter(String channelId) {
setConversationReference(new ConversationReference() {{
setChannelId(channelId);
setServiceUrl("https://test.com");
setUser(new ChannelAccount() {{
setId("user1");
setName("User1");
}});
setBot(new ChannelAccount() {{
setId("bot");
setName("Bot");
}});
setConversation(new ConversationAccount() {{
setIsGroup(false);
setConversationType("convo1");
setId("Conversation1");
}});
}});
}
public TestAdapter(ConversationReference reference) { public TestAdapter(ConversationReference reference) {
if (reference != null) { if (reference != null) {

Просмотреть файл

@ -8,6 +8,7 @@ package com.microsoft.bot.schema;
import java.util.UUID; import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
@ -76,6 +77,7 @@ public class ConversationReference {
/** /**
* Creates {@link Activity} from conversation reference as it is posted to bot. * Creates {@link Activity} from conversation reference as it is posted to bot.
*/ */
@JsonIgnore
public Activity getContinuationActivity() { public Activity getContinuationActivity() {
Activity activity = Activity.createEventActivity(); Activity activity = Activity.createEventActivity();
activity.setName("ContinueConversation"); activity.setName("ContinueConversation");