Added InspectionTests and related fixes.
This commit is contained in:
Родитель
cb54c1c77c
Коммит
a708abdcf9
|
@ -81,7 +81,7 @@ public abstract class BotState implements PropertyManager {
|
|||
if (force || cachedState == null || cachedState.getState() == null) {
|
||||
return storage.read(new String[]{storageKey})
|
||||
.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;
|
||||
});
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ public abstract class BotState implements PropertyManager {
|
|||
throw new IllegalArgumentException("turnContext cannot be null");
|
||||
}
|
||||
|
||||
turnContext.getTurnState().put(contextServiceKey, new CachedBotState());
|
||||
turnContext.getTurnState().replace(contextServiceKey, new CachedBotState());
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
|
@ -179,7 +179,7 @@ public abstract class BotState implements PropertyManager {
|
|||
throw new IllegalArgumentException("turnContext cannot be null");
|
||||
}
|
||||
|
||||
String stateKey = getClass().getName();
|
||||
String stateKey = getClass().getSimpleName();
|
||||
CachedBotState cachedState = turnContext.getTurnState().get(stateKey);
|
||||
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}.
|
||||
*/
|
||||
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 {
|
||||
if (key == null) {
|
||||
throw new IllegalArgumentException("key");
|
||||
}
|
||||
|
||||
Object service = super.get(key);
|
||||
Object service = state.get(key);
|
||||
try {
|
||||
T result = (T) service;
|
||||
} 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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
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 {
|
||||
|
@ -43,21 +46,36 @@ public class TurnContextStateCollection extends HashMap<String, Object> implemen
|
|||
}
|
||||
|
||||
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));
|
||||
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.
|
||||
*/
|
||||
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
|
||||
|
@ -71,7 +89,7 @@ public class TurnContextStateCollection extends HashMap<String, Object> implemen
|
|||
|
||||
@Override
|
||||
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 ConnectorClient) {
|
||||
continue;
|
||||
|
|
|
@ -11,7 +11,6 @@ import com.microsoft.bot.schema.Activity;
|
|||
import com.microsoft.bot.schema.ActivityTypes;
|
||||
import com.microsoft.bot.schema.ConversationReference;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -31,16 +30,14 @@ public class InspectionMiddleware extends InterceptionMiddleware {
|
|||
this(withInspectionState,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
LoggerFactory.getLogger(InspectionMiddleware.class));
|
||||
null);
|
||||
}
|
||||
|
||||
public InspectionMiddleware(InspectionState withInspectionState,
|
||||
UserState withUserState,
|
||||
ConversationState withConversationState,
|
||||
MicrosoftAppCredentials withCredentials,
|
||||
Logger withLogger) {
|
||||
super(withLogger);
|
||||
MicrosoftAppCredentials withCredentials) {
|
||||
super(LoggerFactory.getLogger(InspectionMiddleware.class));
|
||||
|
||||
inspectionState = withInspectionState;
|
||||
userState = withUserState;
|
||||
|
@ -49,7 +46,7 @@ public class InspectionMiddleware extends InterceptionMiddleware {
|
|||
}
|
||||
|
||||
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())) {
|
||||
|
||||
return CompletableFuture.completedFuture(false);
|
||||
|
@ -58,7 +55,7 @@ public class InspectionMiddleware extends InterceptionMiddleware {
|
|||
String text = Activity.removeRecipientMentionImmutable(turnContext.getActivity());
|
||||
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")) {
|
||||
return processOpenCommand(turnContext)
|
||||
.thenApply((result) -> true);
|
||||
|
@ -76,14 +73,25 @@ public class InspectionMiddleware extends InterceptionMiddleware {
|
|||
@Override
|
||||
protected CompletableFuture<Intercept> inbound(TurnContext turnContext, Activity activity) {
|
||||
return processCommand(turnContext)
|
||||
.thenCombine(findSession(turnContext), (processResult, session) -> {
|
||||
if (session != null) {
|
||||
if (invokeSend(turnContext, session, activity).join()) {
|
||||
return new Intercept(true, true);
|
||||
}
|
||||
.thenCompose(processResult -> {
|
||||
if (processResult) {
|
||||
return CompletableFuture.completedFuture(new Intercept(false, false));
|
||||
}
|
||||
|
||||
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
|
||||
protected CompletableFuture<Void> traceState(TurnContext turnContext) {
|
||||
return findSession(turnContext)
|
||||
.thenAccept(session -> {
|
||||
if (session != null) {
|
||||
CompletableFuture<Void> userLoad = userState == 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();
|
||||
.thenCompose(session -> {
|
||||
if (session == null) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
});
|
||||
|
||||
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) {
|
||||
|
@ -139,16 +151,14 @@ public class InspectionMiddleware extends InterceptionMiddleware {
|
|||
inspectionState.createProperty(InspectionSessionsByStatus.class.getName());
|
||||
|
||||
return accessor.get(turnContext, InspectionSessionsByStatus::new)
|
||||
.thenAccept(result -> {
|
||||
.thenCompose(result -> {
|
||||
InspectionSessionsByStatus sessions = (InspectionSessionsByStatus) result;
|
||||
String sessionId = openCommand(sessions, turnContext.getActivity().getConversationReference());
|
||||
|
||||
String command = String.format("%s attach %s", COMMAND, sessionId);
|
||||
turnContext.sendActivity(InspectionActivityExtensions.makeCommandActivity(command)).join();
|
||||
return turnContext.sendActivity(InspectionActivityExtensions.makeCommandActivity(command));
|
||||
})
|
||||
.thenRun(() -> {
|
||||
inspectionState.saveChanges(turnContext);
|
||||
});
|
||||
.thenCompose(resourceResponse -> inspectionState.saveChanges(turnContext));
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> processAttachCommand(TurnContext turnContext, String sessionId) {
|
||||
|
@ -156,20 +166,16 @@ public class InspectionMiddleware extends InterceptionMiddleware {
|
|||
inspectionState.createProperty(InspectionSessionsByStatus.class.getName());
|
||||
|
||||
return accessor.get(turnContext, InspectionSessionsByStatus::new)
|
||||
.thenAccept(result -> {
|
||||
InspectionSessionsByStatus sessions = (InspectionSessionsByStatus) result;
|
||||
|
||||
.thenCompose(sessions -> {
|
||||
if (attachCommand(turnContext.getActivity().getConversation().getId(), sessions, sessionId)) {
|
||||
turnContext.sendActivity(MessageFactory.text(
|
||||
"Attached to session, all traffic is being replicated for inspection.")).join();
|
||||
return turnContext.sendActivity(MessageFactory.text(
|
||||
"Attached to session, all traffic is being replicated for inspection."));
|
||||
} else {
|
||||
turnContext.sendActivity(MessageFactory.text(
|
||||
String.format("Open session with id %s does not exist.", sessionId))).join();
|
||||
return turnContext.sendActivity(MessageFactory.text(
|
||||
String.format("Open session with id %s does not exist.", sessionId)));
|
||||
}
|
||||
})
|
||||
.thenRun(() -> {
|
||||
inspectionState.saveChanges(turnContext);
|
||||
});
|
||||
.thenCompose(resourceResponse -> inspectionState.saveChanges(turnContext));
|
||||
}
|
||||
|
||||
private String openCommand(InspectionSessionsByStatus sessions, ConversationReference conversationReference) {
|
||||
|
@ -189,6 +195,10 @@ public class InspectionMiddleware extends InterceptionMiddleware {
|
|||
return true;
|
||||
}
|
||||
|
||||
protected InspectionSession createSession(ConversationReference reference, MicrosoftAppCredentials credentials) {
|
||||
return new InspectionSession(reference, credentials);
|
||||
}
|
||||
|
||||
private CompletableFuture<InspectionSession> findSession(TurnContext turnContext) {
|
||||
StatePropertyAccessor<InspectionSessionsByStatus> accessor =
|
||||
inspectionState.createProperty(InspectionSessionsByStatus.class.getName());
|
||||
|
@ -201,7 +211,7 @@ public class InspectionMiddleware extends InterceptionMiddleware {
|
|||
.get(turnContext.getActivity().getConversation().getId());
|
||||
|
||||
if (reference != null) {
|
||||
return new InspectionSession(reference, credentials, getLogger());
|
||||
return createSession(reference, credentials);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -213,13 +223,13 @@ public class InspectionMiddleware extends InterceptionMiddleware {
|
|||
Activity activity) {
|
||||
|
||||
return session.send(activity)
|
||||
.thenApply(result -> {
|
||||
.thenCompose(result -> {
|
||||
if (result) {
|
||||
return true;
|
||||
return CompletableFuture.completedFuture(true);
|
||||
}
|
||||
|
||||
cleanupSession(turnContext).join();
|
||||
return false;
|
||||
return cleanupSession(turnContext)
|
||||
.thenCompose(cleanupResult -> CompletableFuture.completedFuture(false));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -46,13 +46,31 @@ public abstract class InterceptionMiddleware implements Middleware {
|
|||
turnContext.onSendActivities((sendContext, sendActivities, sendNext) -> {
|
||||
List<Activity> traceActivities = sendActivities.stream()
|
||||
.map(a ->
|
||||
InspectionActivityExtensions.traceActivity(a, "SentActivity", "Sent Activity"))
|
||||
InspectionActivityExtensions.traceActivity(a,
|
||||
"SentActivity", "Sent Activity"))
|
||||
.collect(Collectors.toList());
|
||||
return invokeOutbound(sendContext, traceActivities)
|
||||
.thenCompose(response -> {
|
||||
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) {
|
||||
|
@ -68,7 +86,7 @@ public abstract class InterceptionMiddleware implements Middleware {
|
|||
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) {
|
||||
return outbound(turnContext, traceActivities)
|
||||
.exceptionally(exception -> {
|
||||
logger.warn("Exception in outbound interception {}", exception.getMessage());
|
||||
return null;
|
||||
});
|
||||
logger.warn("Exception in outbound interception {}", exception.getMessage());
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
return outbound(turnContext, Collections.singletonList(Activity.createContactRelationUpdateActivity()))
|
||||
.exceptionally(exception -> {
|
||||
logger.warn("Exception in exception interception {}", exception.getMessage());
|
||||
return null;
|
||||
});
|
||||
logger.warn("Exception in exception interception {}", exception.getMessage());
|
||||
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;
|
||||
|
||||
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) {
|
||||
if (reference != null) {
|
||||
|
|
|
@ -8,6 +8,7 @@ package com.microsoft.bot.schema;
|
|||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
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.
|
||||
*/
|
||||
@JsonIgnore
|
||||
public Activity getContinuationActivity() {
|
||||
Activity activity = Activity.createEventActivity();
|
||||
activity.setName("ContinueConversation");
|
||||
|
|
Загрузка…
Ссылка в новой задаче