From 997ba46831aef41beca0d00f7860a6140e78ff0e Mon Sep 17 00:00:00 2001 From: Hunter Jackson Date: Mon, 18 Dec 2023 11:12:56 -0500 Subject: [PATCH] get code back to passing all tests --- CODE_OF_CONDUCT.md | 2 +- src/main/java/com/meta/cp4m/Service.java | 43 ++++++++++ .../java/com/meta/cp4m/ServicesRunner.java | 74 ++++++++++++++-- .../meta/cp4m/message/FBMessageHandler.java | 85 ++++++++++++++++++- .../com/meta/cp4m/message/MessageHandler.java | 5 ++ .../meta/cp4m/message/MetaHandlerUtils.java | 42 +++++++++ .../com/meta/cp4m/message/RequestHandler.java | 15 ++++ .../meta/cp4m/message/RequestProcessor.java | 19 +++++ .../meta/cp4m/message/WAMessageHandler.java | 64 +++++++++++++- .../java/com/meta/cp4m/routing/Acceptor.java | 25 ++++++ .../java/com/meta/cp4m/routing/Handler.java | 19 +++++ .../java/com/meta/cp4m/routing/Route.java | 15 ++++ .../com/meta/cp4m/routing/RouteRegistrar.java | 22 +++++ .../cp4m/llm/HuggingFaceLlamaPluginTest.java | 2 +- .../com/meta/cp4m/llm/OpenAIPluginTest.java | 2 +- ...st.java => FBMessageRouteDetailsTest.java} | 25 +++--- ...st.java => WAMessageRouteDetailsTest.java} | 4 +- 17 files changed, 435 insertions(+), 28 deletions(-) create mode 100644 src/main/java/com/meta/cp4m/message/RequestHandler.java create mode 100644 src/main/java/com/meta/cp4m/message/RequestProcessor.java create mode 100644 src/main/java/com/meta/cp4m/routing/Acceptor.java create mode 100644 src/main/java/com/meta/cp4m/routing/Handler.java create mode 100644 src/main/java/com/meta/cp4m/routing/Route.java create mode 100644 src/main/java/com/meta/cp4m/routing/RouteRegistrar.java rename src/test/java/com/meta/cp4m/message/{FBMessageHandlerTest.java => FBMessageRouteDetailsTest.java} (95%) rename src/test/java/com/meta/cp4m/message/{WAMessageHandlerTest.java => WAMessageRouteDetailsTest.java} (97%) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 08b500a..1b4e4bd 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -33,7 +33,7 @@ Examples of unacceptable behavior by participants include: ## Our Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable +Project maintainers are responsible for clarifying the standards of acceptor behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. diff --git a/src/main/java/com/meta/cp4m/Service.java b/src/main/java/com/meta/cp4m/Service.java index 1cb1d68..eadd21b 100644 --- a/src/main/java/com/meta/cp4m/Service.java +++ b/src/main/java/com/meta/cp4m/Service.java @@ -11,13 +11,19 @@ import com.meta.cp4m.llm.LLMPlugin; import com.meta.cp4m.message.Message; import com.meta.cp4m.message.MessageHandler; +import com.meta.cp4m.message.RequestProcessor; import com.meta.cp4m.message.ThreadState; +import com.meta.cp4m.routing.Acceptor; +import com.meta.cp4m.routing.Handler; +import com.meta.cp4m.routing.Route; import com.meta.cp4m.store.ChatStore; import io.javalin.Javalin; import io.javalin.http.Context; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,6 +55,25 @@ void handle(Context ctx) { } } + void handler(Context ctx, IN in, RequestProcessor processor) { + List messages = null; + try { + messages = processor.process(ctx, in); + } catch (Exception e) { + LOGGER + .atError() + .addKeyValue("body", ctx.body()) + .addKeyValue("headers", ctx.headerMap()) + .setMessage("unable to process request") + .log(); + } + // TODO: once we have a non-volatile store, on startup send stored but not replied to messages + for (T m : messages) { + ThreadState thread = store.add(m); + executorService.submit(() -> execute(thread)); + } + } + public void register(Javalin app) { handler.handlers().forEach(m -> app.addHandler(m, path, this::handle)); } @@ -79,4 +104,22 @@ private void execute(ThreadState thread) { LOGGER.error("an error occurred while attempting to respond", e); } } + + private Route toRoute(MessageHandler.RouteDetails routeDetails) { + return new Route<>( + path, + routeDetails.handlerType(), + routeDetails.acceptor(), + (ctx, in) -> handler(ctx, in, routeDetails.requestProcessor())); + } + + List> routes() { + List> routeDetails = handler.routeDetails(); + List> routes = new ArrayList<>(routeDetails.size()); + for (MessageHandler.RouteDetails routeDetail : routeDetails) { + Route route = toRoute(routeDetail); + routes.add(route); + } + return routes; + } } diff --git a/src/main/java/com/meta/cp4m/ServicesRunner.java b/src/main/java/com/meta/cp4m/ServicesRunner.java index 4355cda..b01c4db 100644 --- a/src/main/java/com/meta/cp4m/ServicesRunner.java +++ b/src/main/java/com/meta/cp4m/ServicesRunner.java @@ -9,16 +9,23 @@ package com.meta.cp4m; import com.google.common.base.Preconditions; +import com.meta.cp4m.routing.Route; import io.javalin.Javalin; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; + +import java.util.*; + +import io.javalin.http.BadRequestResponse; +import io.javalin.http.Context; +import io.javalin.http.HandlerType; import org.checkerframework.common.returnsreceiver.qual.This; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ServicesRunner implements AutoCloseable { + + private static final Logger LOGGER = LoggerFactory.getLogger(ServicesRunner.class); private final Javalin app = Javalin.create(); - private final Set> services = new HashSet<>(); + private final Set> services = new LinkedHashSet<>(); private boolean started = false; private int port = 8080; @@ -28,7 +35,59 @@ public static ServicesRunner newInstance() { return new ServicesRunner(); } + private boolean didAcceptAndHandle(Context ctx, Route route) { + Optional acceptorOutput = route.acceptor().accept(ctx); + if (acceptorOutput.isPresent()) { + try { + route.handler().handle(ctx, acceptorOutput.get()); + } catch (Exception e) { + throw new BadRequestResponse("Unable to process request"); + } + return true; + } + return false; + } + + /** + * Find the first route that will accept this payload and then handle the payload + * + * @param ctx context from Javalin + * @param routes the routes to check for acceptability and process if accepted + */ + private void routeSelectorAndHandler(Context ctx, List> routes) { + for (Route route : routes) { + if (didAcceptAndHandle(ctx, route)) { + return; + } + } + LOGGER + .atError() + .setMessage("Unable to handle incoming webhook") + .addKeyValue("body", ctx.body()) + .addKeyValue("headers", ctx.headerMap()) + .log(); + throw new BadRequestResponse("unable to handle webhook"); + } + public @This ServicesRunner start() { + record RouteGroup(String path, HandlerType handlerType) {} + Map>> routeGroups = new HashMap<>(); + for (Service s : services) { // this is not a stream because order matters here + s.routes() + .forEach( + r -> + routeGroups + .computeIfAbsent( + new RouteGroup(r.path(), r.handlerType()), k -> new ArrayList<>()) + .add(r)); + } + routeGroups.forEach( + (routeGroup, routes) -> + app.addHandler( + routeGroup.handlerType(), + routeGroup.path(), + ctx -> this.routeSelectorAndHandler(ctx, routes))); + if (!started) { started = true; app.start(port); @@ -38,9 +97,8 @@ public static ServicesRunner newInstance() { public @This ServicesRunner service(Service service) { Preconditions.checkState(!started, "cannot add service, server already started"); - if (services.add(service)) { - service.register(app); - } + + services.add(service); return this; } diff --git a/src/main/java/com/meta/cp4m/message/FBMessageHandler.java b/src/main/java/com/meta/cp4m/message/FBMessageHandler.java index 2f869f0..2f2da79 100644 --- a/src/main/java/com/meta/cp4m/message/FBMessageHandler.java +++ b/src/main/java/com/meta/cp4m/message/FBMessageHandler.java @@ -9,6 +9,7 @@ package com.meta.cp4m.message; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -88,7 +89,7 @@ public List processRequest(Context ctx) { } catch (JsonProcessingException | NullPointerException e) { LOGGER .atWarn() - .setMessage("Unable to parse message form Meta webhook") + .setMessage("Unable to parse message from Meta webhook") .setCause(e) .addKeyValue("body", ctx.body()) .addKeyValue("headers", ctx.headerMap()) @@ -183,6 +184,59 @@ private List postHandler(Context ctx) throws JsonProcessingException return output; } + private List postHandler(Context ctx, JsonNode body) { + JsonNode entries = body.get("entry"); + ArrayList output = new ArrayList<>(); + for (JsonNode entry : entries) { + @Nullable JsonNode messaging = entry.get("messaging"); + if (messaging == null) { + continue; + } + for (JsonNode message : messaging) { + + Identifier senderId = Identifier.from(message.get("sender").get("id").asLong()); + Identifier recipientId = Identifier.from(message.get("recipient").get("id").asLong()); + Instant timestamp = Instant.ofEpochMilli(message.get("timestamp").asLong()); + @Nullable JsonNode messageObject = message.get("message"); + if (messageObject != null) { + // https://developers.facebook.com/docs/messenger-platform/reference/webhook-events/messages + Identifier messageId = Identifier.from(messageObject.get("mid").textValue()); + if (messageDeduplicator.addAndGetIsDuplicate(messageId)) { + continue; + } + + @Nullable JsonNode textObject = messageObject.get("text"); + if (textObject != null && textObject.isTextual()) { + FBMessage m = + new FBMessage( + timestamp, + messageId, + senderId, + recipientId, + textObject.textValue(), + Message.Role.USER); + output.add(m); + } else { + LOGGER + .atWarn() + .setMessage("received message without text, unable to handle this") + .addKeyValue("body", body) + .log(); + } + } else { + LOGGER + .atWarn() + .setMessage( + "received a message without a 'message' key, unable to handle this message type") + .addKeyValue("body", body) + .log(); + } + } + } + + return output; + } + @TestOnly public @This FBMessageHandler baseURLFactory(Function baseURLFactory) { this.baseURLFactory = Objects.requireNonNull(baseURLFactory); @@ -237,4 +291,33 @@ private void send(String message, Identifier recipient, Identifier sender) throw public Collection handlers() { return List.of(HandlerType.GET, HandlerType.POST); } + + @Override + public List> routeDetails() { + RouteDetails postDetails = + new RouteDetails<>( + HandlerType.POST, + ctx -> { + @Nullable String contentType = ctx.contentType(); + if (contentType != null + && ContentType.parse(contentType).isSameMimeType(ContentType.APPLICATION_JSON) + && MetaHandlerUtils.postHeaderValid(ctx, appSecret)) { + JsonNode body; + try { + body = MAPPER.readTree(ctx.body()); + } catch (JsonProcessingException e) { + throw new BadRequestResponse("unable to parse body"); + } + // TODO: need better validation + @Nullable JsonNode objectNode = body.get("object"); + if (objectNode != null && objectNode.textValue().equals("page")) { + return Optional.of(body); + } + } + return Optional.empty(); + }, + this::postHandler); + + return List.of(MetaHandlerUtils.subscriptionVerificationRouteDetails(verifyToken), postDetails); + } } diff --git a/src/main/java/com/meta/cp4m/message/MessageHandler.java b/src/main/java/com/meta/cp4m/message/MessageHandler.java index 7b852fc..1ab16ee 100644 --- a/src/main/java/com/meta/cp4m/message/MessageHandler.java +++ b/src/main/java/com/meta/cp4m/message/MessageHandler.java @@ -8,6 +8,7 @@ package com.meta.cp4m.message; +import com.meta.cp4m.routing.Acceptor; import io.javalin.http.Context; import io.javalin.http.HandlerType; import java.io.IOException; @@ -15,6 +16,8 @@ import java.util.List; public interface MessageHandler { + record RouteDetails( + HandlerType handlerType, Acceptor acceptor, RequestProcessor requestProcessor) {} /** * Process incoming requests from the messaging service, including messages from the user. @@ -35,4 +38,6 @@ public interface MessageHandler { * @return The different {@link HandlerType}s that this handler expects to receive */ Collection handlers(); + + List> routeDetails(); } diff --git a/src/main/java/com/meta/cp4m/message/MetaHandlerUtils.java b/src/main/java/com/meta/cp4m/message/MetaHandlerUtils.java index 85b2cdf..6fc44d1 100644 --- a/src/main/java/com/meta/cp4m/message/MetaHandlerUtils.java +++ b/src/main/java/com/meta/cp4m/message/MetaHandlerUtils.java @@ -13,9 +13,16 @@ import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.OptionalInt; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; + +import io.javalin.http.HandlerType; import org.apache.hc.client5.http.utils.Hex; +import org.checkerframework.checker.nullness.qual.Nullable; class MetaHandlerUtils { static void subscriptionVerification(Context ctx, String verifyToken) { @@ -27,6 +34,26 @@ static void subscriptionVerification(Context ctx, String verifyToken) { ctx.result(String.valueOf(challenge)); } + static + MessageHandler.RouteDetails subscriptionVerificationRouteDetails( + String verifyToken) { + return new MessageHandler.RouteDetails<>( + HandlerType.GET, + ctx -> + // validateSubscription handles putting challenge into context response if it succeeds + { + if (Objects.equals(ctx.queryParam("hub.mode"), "subscribe") + && Objects.equals(ctx.queryParam("hub.verify_token"), verifyToken)) { + return Optional.of(ctx.queryParamAsClass("hub.challenge", Integer.class).get()); + } + return Optional.empty(); + }, + (ctx, challenge) -> { + ctx.result(String.valueOf(challenge)); + return List.of(); + }); + } + static String hmac(String body, String appSecret) { Mac sha256HMAC; SecretKeySpec secretKey; @@ -65,4 +92,19 @@ static void postHeaderValidator(Context ctx, String appSecret) { "X-Hub-Signature-256 could not be validated") .getOrThrow(ignored -> new ForbiddenResponse("X-Hub-Signature-256 could not be validated")); } + + static boolean postHeaderValid(Context ctx, String appSecret) { + @Nullable String sig = ctx.headerMap().get("X-Hub-Signature-256"); + if (sig == null) { + return false; + } + + String[] hashParts = sig.strip().split("="); + if (hashParts.length != 2) { + return false; + } + + String calculatedHmac = hmac(ctx.body(), appSecret); + return hashParts[1].equals(calculatedHmac); + } } diff --git a/src/main/java/com/meta/cp4m/message/RequestHandler.java b/src/main/java/com/meta/cp4m/message/RequestHandler.java new file mode 100644 index 0000000..097bbec --- /dev/null +++ b/src/main/java/com/meta/cp4m/message/RequestHandler.java @@ -0,0 +1,15 @@ +/* + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.meta.cp4m.message; + +import com.meta.cp4m.routing.Acceptor; +import io.javalin.http.HandlerType; + +public record RequestHandler( + HandlerType type, Acceptor acceptor, RequestProcessor processor) {} diff --git a/src/main/java/com/meta/cp4m/message/RequestProcessor.java b/src/main/java/com/meta/cp4m/message/RequestProcessor.java new file mode 100644 index 0000000..ba29c01 --- /dev/null +++ b/src/main/java/com/meta/cp4m/message/RequestProcessor.java @@ -0,0 +1,19 @@ +/* + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.meta.cp4m.message; + +import io.javalin.http.Context; + +import java.util.List; + +@FunctionalInterface +public interface RequestProcessor { + + List process(Context ctx, IN in) throws Exception; +} diff --git a/src/main/java/com/meta/cp4m/message/WAMessageHandler.java b/src/main/java/com/meta/cp4m/message/WAMessageHandler.java index a2e7ecb..26f617c 100644 --- a/src/main/java/com/meta/cp4m/message/WAMessageHandler.java +++ b/src/main/java/com/meta/cp4m/message/WAMessageHandler.java @@ -21,16 +21,14 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.Function; import org.apache.hc.client5.http.fluent.Request; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.net.URIBuilder; +import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.returnsreceiver.qual.This; import org.jetbrains.annotations.TestOnly; import org.slf4j.Logger; @@ -108,6 +106,39 @@ public List processRequest(Context ctx) { throw new UnsupportedOperationException("Only accepting get and post methods"); } + private List post(Context ctx, WebhookPayload payload) { + List waMessages = new ArrayList<>(); + payload.entry().stream() + .flatMap(e -> e.changes().stream()) + .forEach( + change -> { + Identifier phoneNumberId = change.value().metadata().phoneNumberId(); + for (WebhookMessage message : change.value().messages()) { + if (messageDeduplicator.addAndGetIsDuplicate(message.id())) { + continue; // message is a duplicate + } + if (message.type() != WebhookMessage.WebhookMessageType.TEXT) { + LOGGER.warn( + "received message of type '" + + message.type() + + "', only able to handle text messages at this time"); + continue; + } + TextWebhookMessage textMessage = (TextWebhookMessage) message; + waMessages.add( + new WAMessage( + message.timestamp(), + message.id(), + message.from(), + phoneNumberId, + textMessage.text().body(), + Message.Role.USER)); + readExecutor.execute(() -> markRead(phoneNumberId, textMessage.id().toString())); + } + }); + return waMessages; + } + List postHandler(Context ctx) { MetaHandlerUtils.postHeaderValidator(ctx, appSecret); String bodyString = ctx.body(); @@ -193,6 +224,31 @@ public Collection handlers() { return List.of(HandlerType.GET, HandlerType.POST); } + @Override + public List> routeDetails() { + RouteDetails postDetails = + new RouteDetails<>( + HandlerType.POST, + ctx -> { + @Nullable String contentType = ctx.contentType(); + if (contentType != null && + ContentType.parse(contentType).isSameMimeType(ContentType.APPLICATION_JSON) + && MetaHandlerUtils.postHeaderValid(ctx, appSecret)) { + String bodyString = ctx.body(); + WebhookPayload payload; + try { + payload = MAPPER.readValue(bodyString, WebhookPayload.class); + return Optional.of(payload); + } catch (Exception e) { + return Optional.empty(); + } + } + return Optional.empty(); + }, + this::post); + return List.of(MetaHandlerUtils.subscriptionVerificationRouteDetails(verifyToken), postDetails); + } + private void markRead(Identifier phoneNumberId, String messageId) { ObjectNode body = MAPPER diff --git a/src/main/java/com/meta/cp4m/routing/Acceptor.java b/src/main/java/com/meta/cp4m/routing/Acceptor.java new file mode 100644 index 0000000..f62cdfa --- /dev/null +++ b/src/main/java/com/meta/cp4m/routing/Acceptor.java @@ -0,0 +1,25 @@ +/* + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.meta.cp4m.routing; + +import com.fasterxml.jackson.databind.JsonNode; +import com.meta.cp4m.message.Message; +import io.javalin.http.Context; + +import java.util.Optional; + +@FunctionalInterface +public interface Acceptor { + /** + * @param ctx contex of an incoming message on a webhook + * @return not empty if the {@link com.meta.cp4m.message.MessageHandler} can accept the message, + * empty otherwise + */ + Optional accept(Context ctx); +} diff --git a/src/main/java/com/meta/cp4m/routing/Handler.java b/src/main/java/com/meta/cp4m/routing/Handler.java new file mode 100644 index 0000000..1fabbb2 --- /dev/null +++ b/src/main/java/com/meta/cp4m/routing/Handler.java @@ -0,0 +1,19 @@ +/* + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.meta.cp4m.routing; + +import io.javalin.http.Context; + +import java.util.List; + +@FunctionalInterface +public interface Handler { + + void handle(Context ctx, I input); +} diff --git a/src/main/java/com/meta/cp4m/routing/Route.java b/src/main/java/com/meta/cp4m/routing/Route.java new file mode 100644 index 0000000..d50e3c6 --- /dev/null +++ b/src/main/java/com/meta/cp4m/routing/Route.java @@ -0,0 +1,15 @@ +/* + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.meta.cp4m.routing; + +import com.meta.cp4m.message.Message; +import io.javalin.http.HandlerType; + +public record Route( + String path, HandlerType handlerType, Acceptor acceptor, Handler handler) {} diff --git a/src/main/java/com/meta/cp4m/routing/RouteRegistrar.java b/src/main/java/com/meta/cp4m/routing/RouteRegistrar.java new file mode 100644 index 0000000..4869905 --- /dev/null +++ b/src/main/java/com/meta/cp4m/routing/RouteRegistrar.java @@ -0,0 +1,22 @@ +/* + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.meta.cp4m.routing; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RouteRegistrar { + private final Map>> routeAcceptors = new HashMap<>(); + + void register(Route route) { + routeAcceptors.computeIfAbsent(route.path(), ignored -> new ArrayList<>()).add(route); + } +} diff --git a/src/test/java/com/meta/cp4m/llm/HuggingFaceLlamaPluginTest.java b/src/test/java/com/meta/cp4m/llm/HuggingFaceLlamaPluginTest.java index 4802584..4c525ba 100644 --- a/src/test/java/com/meta/cp4m/llm/HuggingFaceLlamaPluginTest.java +++ b/src/test/java/com/meta/cp4m/llm/HuggingFaceLlamaPluginTest.java @@ -308,7 +308,7 @@ void inPipeline() throws IOException, URISyntaxException, InterruptedException { // TODO: create test harness Request request = - FBMessageHandlerTest.createMessageRequest(FBMessageHandlerTest.SAMPLE_MESSAGE, runner); + FBMessageRouteDetailsTest.createMessageRequest(FBMessageRouteDetailsTest.SAMPLE_MESSAGE, runner); HttpResponse response = request.execute().returnResponse(); assertThat(response.getCode()).isEqualTo(200); @Nullable OutboundRequest or = HuggingFaceLlamaRequests.poll(500, TimeUnit.MILLISECONDS); diff --git a/src/test/java/com/meta/cp4m/llm/OpenAIPluginTest.java b/src/test/java/com/meta/cp4m/llm/OpenAIPluginTest.java index d11aee5..8f59d3b 100644 --- a/src/test/java/com/meta/cp4m/llm/OpenAIPluginTest.java +++ b/src/test/java/com/meta/cp4m/llm/OpenAIPluginTest.java @@ -251,7 +251,7 @@ void inPipeline() throws IOException, URISyntaxException, InterruptedException { // TODO: create test harness Request request = - FBMessageHandlerTest.createMessageRequest(FBMessageHandlerTest.SAMPLE_MESSAGE, runner); + FBMessageRouteDetailsTest.createMessageRequest(FBMessageRouteDetailsTest.SAMPLE_MESSAGE, runner); HttpResponse response = request.execute().returnResponse(); assertThat(response.getCode()).isEqualTo(200); @Nullable OutboundRequest or = openAIRequests.poll(500, TimeUnit.MILLISECONDS); diff --git a/src/test/java/com/meta/cp4m/message/FBMessageHandlerTest.java b/src/test/java/com/meta/cp4m/message/FBMessageRouteDetailsTest.java similarity index 95% rename from src/test/java/com/meta/cp4m/message/FBMessageHandlerTest.java rename to src/test/java/com/meta/cp4m/message/FBMessageRouteDetailsTest.java index 8b335c7..428d72f 100644 --- a/src/test/java/com/meta/cp4m/message/FBMessageHandlerTest.java +++ b/src/test/java/com/meta/cp4m/message/FBMessageRouteDetailsTest.java @@ -50,11 +50,12 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -public class FBMessageHandlerTest { +public class FBMessageRouteDetailsTest { /** Example message collected directly from the messenger webhook */ public static final String SAMPLE_MESSAGE = "{\"object\":\"page\",\"entry\":[{\"id\":\"106195825075770\",\"time\":1692813219204,\"messaging\":[{\"sender\":{\"id\":\"6357858494326947\"},\"recipient\":{\"id\":\"106195825075770\"},\"timestamp\":1692813218705,\"message\":{\"mid\":\"m_kT_mWOSYh_eK3kF8chtyCWfcD9-gomvu4mhaMFQl-gt4D3LjORi6k3BXD6_x9a-FOUt-D2LFuywJN6HfrpAnDg\",\"text\":\"asdfa\"}}]}]}"; + private static final ObjectMapper MAPPER = new ObjectMapper(); private static final String SAMPLE_MESSAGE_HMAC = "sha256=8620d18213fa2612d16117b65168ef97404fa13189528014c5362fec31215985"; @@ -89,7 +90,10 @@ private static Request createMessageRequest( URI uri = URIBuilder.localhost().setScheme("http").setPort(runner.port()).appendPath(path).build(); - Request request = Request.post(uri).bodyString(body, ContentType.APPLICATION_JSON); + Request request = + Request.post(uri) + .bodyString(body, ContentType.APPLICATION_JSON) + .setHeader("Content-Type", "application/json"); if (calculateHmac) { String hmac = messageHandler.hmac(body); request.setHeader("X-Hub-Signature-256", "sha256=" + hmac); @@ -134,7 +138,7 @@ static Stream requestFactory() throws JsonProcessingException { true), new TestArgument( "a non page object type", - 200, + 400, r -> createMessageRequest("{\"object\": \"not a page\"}", r), false), new TestArgument( @@ -201,14 +205,14 @@ static Stream requestFactory() throws JsonProcessingException { r), false), new TestArgument( - "missing hmac", 403, r -> createMessageRequest(SAMPLE_MESSAGE, r, false), false), + "missing hmac", 400, r -> createMessageRequest(SAMPLE_MESSAGE, r, false), false), new TestArgument( "invalid json", 400, r -> createMessageRequest("invalid_json.", r), false), new TestArgument( "valid json, invalid body", 400, r -> createMessageRequest("{}", r), false), new TestArgument( "invalid hmac", - 403, + 400, r -> createMessageRequest(SAMPLE_MESSAGE, r, false) .addHeader("X-Hub-Signature-256", "abcdef0123456789"), @@ -247,12 +251,13 @@ void tearDown() { @Test void validation() throws IOException, URISyntaxException { - String token = "243af3c6-9994-4869-ae13-ad61a38323f5"; // this is fake - int challenge = 1158201444; + final String pageToken = "243af3c6-9994-4869-ae13-ad61a38323f5"; // this is fake + final int challenge = 1158201444; + final String verifyToken = "oamnw9230rjadoia"; Service service = new Service<>( MemoryStoreConfig.of(1, 1).toStore(), - new FBMessageHandler("0", token, "dummy"), + new FBMessageHandler(verifyToken, pageToken, "dummy"), new DummyLLMPlugin("this is a dummy message"), "/testfbmessage"); final ServicesRunner runner = ServicesRunner.newInstance().service(service).port(0); @@ -262,7 +267,7 @@ void validation() throws IOException, URISyntaxException { ImmutableMap.builder() .put("hub.mode", "subscribe") .put("hub.challenge", Integer.toString(challenge)) - .put("hub.verify_token", token) + .put("hub.verify_token", verifyToken) .build(); response = getRequest("testfbmessage", runner.port(), params); } @@ -299,7 +304,7 @@ void invalidMessage( String token = "243af3c6-9994-4869-ae13-ad61a38323f5"; // this is fake don't worry String secret = "f74a638462f975e9eadfcbb84e4aa06b"; // it's been rolled don't worry FBMessageHandler messageHandler = new FBMessageHandler("0", token, secret); - DummyLLMPlugin llmHandler = new DummyLLMPlugin("this is a dummy message"); + DummyLLMPlugin llmHandler = new DummyLLMPlugin<>("this is a dummy message"); MemoryStore memoryStore = MemoryStoreConfig.of(1, 1).toStore(); Service service = new Service<>(memoryStore, messageHandler, llmHandler, path); final ServicesRunner runner = ServicesRunner.newInstance().service(service).port(0); diff --git a/src/test/java/com/meta/cp4m/message/WAMessageHandlerTest.java b/src/test/java/com/meta/cp4m/message/WAMessageRouteDetailsTest.java similarity index 97% rename from src/test/java/com/meta/cp4m/message/WAMessageHandlerTest.java rename to src/test/java/com/meta/cp4m/message/WAMessageRouteDetailsTest.java index 5d8ed75..14fbb29 100644 --- a/src/test/java/com/meta/cp4m/message/WAMessageHandlerTest.java +++ b/src/test/java/com/meta/cp4m/message/WAMessageRouteDetailsTest.java @@ -25,7 +25,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class WAMessageHandlerTest { +class WAMessageRouteDetailsTest { static final String VALID = """ @@ -106,7 +106,7 @@ class WAMessageHandlerTest { private final ServiceTestHarness harness = ServiceTestHarness.newWAServiceTestHarness(); - WAMessageHandlerTest() throws JsonProcessingException {} + WAMessageRouteDetailsTest() throws JsonProcessingException {} @BeforeEach void setUp() {