From e4249c0aa5232bac25d0c71db934075bfb1a37af Mon Sep 17 00:00:00 2001 From: Hunter Jackson Date: Wed, 7 Aug 2024 17:20:12 -0400 Subject: [PATCH] Make forwarded fields optional on WA messages --- .../whatsapp/WebhookMessageContext.java | 8 +- .../cp4m/message/WAMessageHandlerTest.java | 96 +++++++++++++++---- 2 files changed, 80 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/meta/cp4m/message/webhook/whatsapp/WebhookMessageContext.java b/src/main/java/com/meta/cp4m/message/webhook/whatsapp/WebhookMessageContext.java index 0ab545c..b573b77 100644 --- a/src/main/java/com/meta/cp4m/message/webhook/whatsapp/WebhookMessageContext.java +++ b/src/main/java/com/meta/cp4m/message/webhook/whatsapp/WebhookMessageContext.java @@ -29,13 +29,13 @@ public final class WebhookMessageContext { @JsonCreator public WebhookMessageContext( - @JsonProperty("forwarded") boolean forwarded, - @JsonProperty("frequently_forwarded") boolean frequentlyForwarded, + @JsonProperty("forwarded") @Nullable Boolean forwarded, + @JsonProperty("frequently_forwarded") @Nullable Boolean frequentlyForwarded, @JsonProperty("from") String from, @JsonProperty("id") String id, @JsonProperty("referred_product") @Nullable ReferredProduct referredProduct) { - this.forwarded = forwarded; - this.frequentlyForwarded = frequentlyForwarded; + this.forwarded = Objects.requireNonNullElse(forwarded, false); + this.frequentlyForwarded = Objects.requireNonNullElse(frequentlyForwarded, false); this.from = Identifier.from(Objects.requireNonNull(from)); this.id = Identifier.from(Objects.requireNonNull(id)); this.referredProduct = referredProduct; diff --git a/src/test/java/com/meta/cp4m/message/WAMessageHandlerTest.java b/src/test/java/com/meta/cp4m/message/WAMessageHandlerTest.java index 3c9789b..16b1059 100644 --- a/src/test/java/com/meta/cp4m/message/WAMessageHandlerTest.java +++ b/src/test/java/com/meta/cp4m/message/WAMessageHandlerTest.java @@ -15,9 +15,7 @@ import com.google.common.base.Stopwatch; import com.meta.cp4m.DummyWebServer.ReceivedRequest; import com.meta.cp4m.Identifier; -import com.meta.cp4m.message.webhook.whatsapp.GetMediaIdBody; -import com.meta.cp4m.message.webhook.whatsapp.SendResponse; -import com.meta.cp4m.message.webhook.whatsapp.Utils; +import com.meta.cp4m.message.webhook.whatsapp.*; import io.javalin.http.HandlerType; import java.io.IOException; import java.net.URI; @@ -32,11 +30,57 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; class WAMessageHandlerTest { - static String BODY_TEXT = "this is a text message!@#$%^&*()’"; + static final String VALID2 = + """ +{ + "object": "whatsapp_business_account", + "entry": [ + { + "id": "362118040319556", + "changes": [ + { + "value": { + "messaging_product": "whatsapp", + "metadata": { + "display_phone_number": "13137829971", + "phone_number_id": "390095987512130" + }, + "contacts": [ + { + "profile": { + "name": "Benjamin Broadstone" + }, + "wa_id": "12027330824" + } + ], + "messages": [ + { + "context": { + "from": "13137829971", + "id": "wamid.HBgLMTIwMjczMzA4MjQVAgARGBJCRjg3QkEwRjZGRTY5MERBMTUA" + }, + "from": "12027330824", + "id": "wamid.HBgLMTIwMjczMzA4MjQVAgASGBQzQUFFNTkxMDIzQjk5RjNFQjQ5MgA=", + "timestamp": "1723063165", + "text": { + "body": "Cool" + }, + "type": "text" + } + ] + }, + "field": "messages" + } + ] + } + ] +} +"""; static final String VALID = """ { @@ -68,11 +112,7 @@ class WAMessageHandlerTest { "timestamp": "1504902988", "type": "text", "text": { - "body": """ - + "\"" - + BODY_TEXT - + "\"" - + """ + "body": \"this is a text message!@#$%^&*()’\" } } ] @@ -164,8 +204,13 @@ void welcomeMessage(final @Nullable String welcomeMessage) } } - @Test - void valid() throws IOException, InterruptedException { + static Stream validWAPayloads() { + return Stream.of(VALID, VALID2); + } + + @ParameterizedTest + @MethodSource("validWAPayloads") + void valid(String payload) throws IOException, InterruptedException { final String sendResponse = """ { @@ -183,9 +228,13 @@ void valid() throws IOException, InterruptedException { } ] }"""; + + // make sure we can parse the payload + WebhookPayload payloadParsed = Utils.JSON_MAPPER.readValue(payload, WebhookPayload.class); + harness.dummyWebServer().response(ctx -> ctx.body().contains("\"type\""), sendResponse); harness.start(); - Response request = harness.post(VALID).execute(); + Response request = harness.post(payload).execute(); assertThat(request.returnResponse().getCode()).isEqualTo(200); Collection responses = @@ -205,25 +254,32 @@ void valid() throws IOException, InterruptedException { List> threads = harness.chatStore().list(); assertThat(threads).hasSize(1); ThreadState thread = threads.get(0); + Value valueParsed = + payloadParsed.entry().stream().findAny().orElseThrow().changes().stream() + .findAny() + .orElseThrow() + .value(); + TextWebhookMessage parsedMessage = + (TextWebhookMessage) valueParsed.messages().stream().findAny().orElseThrow(); assertThat(thread.messages()) .hasSize(2) .anySatisfy( m -> { - assertThat(m.message()).isEqualTo(BODY_TEXT); - assertThat(m.instanceId()).isEqualTo(Identifier.from("ABGGFlA5Fpa")); - assertThat(m.senderId()).isEqualTo(Identifier.from("16315551181")); - assertThat(m.recipientId()).isEqualTo(Identifier.from("123456123")); + assertThat(m.message()).isEqualTo(parsedMessage.text().body()); + assertThat(m.instanceId()).isEqualTo(parsedMessage.id()); + assertThat(m.senderId()).isEqualTo(parsedMessage.from()); + assertThat(m.recipientId()).isEqualTo(valueParsed.metadata().phoneNumberId()); assertThat(m.role()).isEqualTo(Message.Role.USER); }) .anySatisfy( m -> { assertThat(m.role()).isEqualTo(Message.Role.ASSISTANT); - assertThat(m.senderId()).isEqualTo(Identifier.from("123456123")); - assertThat(m.recipientId()).isEqualTo(Identifier.from("16315551181")); + assertThat(m.senderId()).isEqualTo(valueParsed.metadata().phoneNumberId()); + assertThat(m.recipientId()).isEqualTo(parsedMessage.from()); }); String testUserName = MAPPER - .readTree(VALID) + .readTree(payload) .get("entry") .get(0) .get("changes") @@ -246,7 +302,7 @@ void valid() throws IOException, InterruptedException { .satisfies(u -> assertThat(u.phoneNumber()).get().isEqualTo("16315551181")); // repeat and show that it is not processed again because it is a duplicate - request = harness.post(VALID).execute(); + request = harness.post(payload).execute(); assertThat(request.returnResponse().getCode()).isEqualTo(200); assertThat(harness.pollWebserver(500)).isNull(); }