diff --git a/build.gradle.kts b/build.gradle.kts index e227b82c..3dae26f1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { testImplementation("org.mockito:mockito-core:5.2.0") testImplementation("org.mockito:mockito-inline:5.2.0") testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") + testImplementation("com.squareup.okhttp3:mockwebserver:4.10.0") testImplementation("org.junit.jupiter:junit-jupiter-engine:5.8.1") implementation("com.didalgo:gpt3-tokenizer:0.1.5") diff --git a/src/main/java/com/couchbase/intellij/tree/iq/CapellaApiMethods.java b/src/main/java/com/couchbase/intellij/tree/iq/CapellaApiMethods.java index f0a542b0..b97ed3c0 100644 --- a/src/main/java/com/couchbase/intellij/tree/iq/CapellaApiMethods.java +++ b/src/main/java/com/couchbase/intellij/tree/iq/CapellaApiMethods.java @@ -11,12 +11,16 @@ import java.util.Base64; public class CapellaApiMethods { - // DEV: - //public static final String CAPELLA_DOMAIN = "https://api.dev.nonprod-project-avengers.com"; - // PROD: - public static final String CAPELLA_DOMAIN = "https://api.cloud.couchbase.com"; - private static final String AUTH_URL = CAPELLA_DOMAIN + "/sessions"; - public static final String ORGANIZATIONS_URL = CAPELLA_DOMAIN + "/v2/organizations"; + private static String CAPELLA_DOMAIN; + private static String AUTH_URL; + private static String ORGANIZATIONS_URL; + + static { + // DEV: + // setCapellaDomain(System.getenv().getOrDefault("CAPELLA_DOMAIN", "https://api.dev.nonprod-project-avengers.com")); + // PROD: + setCapellaDomain(System.getenv().getOrDefault("CAPELLA_DOMAIN", "https://api.cloud.couchbase.com")); + } public static CapellaAuth login(String username, String password) throws IOException, IllegalStateException { @@ -69,4 +73,14 @@ public static CapellaOrganizationList loadOrganizations(CapellaAuth auth) throws CapellaOrganizationList resultList = objectMapper.readValue(result, CapellaOrganizationList.class); return resultList; } + + public static String getCapellaDomain() { + return CAPELLA_DOMAIN; + } + + public static void setCapellaDomain(String domain) { + CAPELLA_DOMAIN = domain; + AUTH_URL = domain + "/sessions"; + ORGANIZATIONS_URL = domain + "/v2/organizations"; + } } diff --git a/src/main/java/com/couchbase/intellij/tree/iq/IQWindowContent.java b/src/main/java/com/couchbase/intellij/tree/iq/IQWindowContent.java index 73fac902..186272ea 100644 --- a/src/main/java/com/couchbase/intellij/tree/iq/IQWindowContent.java +++ b/src/main/java/com/couchbase/intellij/tree/iq/IQWindowContent.java @@ -25,10 +25,11 @@ import java.io.InputStream; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import java.util.stream.Collectors; public class IQWindowContent extends JPanel implements LoginPanel.Listener, ChatPanel.LogoutListener, ChatPanel.OrganizationListener { - private static final String IQ_URL = CapellaApiMethods.CAPELLA_DOMAIN + "/v2/organizations/%s/integrations/iq/"; + private static final Supplier IQ_URL = () -> CapellaApiMethods.getCapellaDomain() + "/v2/organizations/%s/integrations/iq/"; private final Project project; private IQCredentials credentials = new IQCredentials(); private CapellaOrganizationList organizationList; @@ -173,7 +174,7 @@ public void onOrgSelected(CapellaOrganization organization) { this.updateUI(); SwingUtilities.invokeLater(() -> { IQStorage.getInstance().getState().setActiveOrganization(organization.getId()); - final String iqUrl = String.format(IQ_URL, organization.getId()); + final String iqUrl = String.format(IQ_URL.get(), organization.getId()); iqGptConfig = new OpenAISettingsState.OpenAIConfig(); OpenAISettingsState.getInstance().setGpt4Config(iqGptConfig); OpenAISettingsState.getInstance().setEnableInitialMessage(true); diff --git a/src/main/java/com/couchbase/intellij/tree/iq/chat/ChatGptHandler.java b/src/main/java/com/couchbase/intellij/tree/iq/chat/ChatGptHandler.java index 25d6a91e..db7f97d7 100644 --- a/src/main/java/com/couchbase/intellij/tree/iq/chat/ChatGptHandler.java +++ b/src/main/java/com/couchbase/intellij/tree/iq/chat/ChatGptHandler.java @@ -79,7 +79,7 @@ public Consumer onNext() { public Consumer onError() { return cause -> { - listener.exchangeFailed(event.failed(cause)); + listener.exchangeFailed(event == null ? null : event.failed(cause)); }; } diff --git a/src/main/java/com/couchbase/intellij/tree/iq/spi/iq/CouchbaseIQServiceProvider.java b/src/main/java/com/couchbase/intellij/tree/iq/spi/iq/CouchbaseIQServiceProvider.java index fc80e145..7417e43d 100644 --- a/src/main/java/com/couchbase/intellij/tree/iq/spi/iq/CouchbaseIQServiceProvider.java +++ b/src/main/java/com/couchbase/intellij/tree/iq/spi/iq/CouchbaseIQServiceProvider.java @@ -1,5 +1,6 @@ package com.couchbase.intellij.tree.iq.spi.iq; +import com.couchbase.intellij.tree.iq.CapellaApiMethods; import com.couchbase.intellij.tree.iq.settings.OpenAISettingsState; import com.couchbase.intellij.tree.iq.spi.OpenAiServiceProvider; import com.couchbase.intellij.workbench.Log; @@ -25,7 +26,8 @@ public boolean supportsEndpoint(String url) { try { URI uri = new URI(url); return uri.getHost().endsWith("couchbase.com") - || uri.getHost().endsWith("project-avengers.com"); + || uri.getHost().endsWith("project-avengers.com") + || url.toLowerCase().contains(CapellaApiMethods.getCapellaDomain().toLowerCase()); } catch (URISyntaxException e) { return false; } diff --git a/src/main/java/com/couchbase/intellij/tree/iq/spi/iq/IQAuthenticationInterceptor.java b/src/main/java/com/couchbase/intellij/tree/iq/spi/iq/IQAuthenticationInterceptor.java index 756e5112..53a894bb 100644 --- a/src/main/java/com/couchbase/intellij/tree/iq/spi/iq/IQAuthenticationInterceptor.java +++ b/src/main/java/com/couchbase/intellij/tree/iq/spi/iq/IQAuthenticationInterceptor.java @@ -1,11 +1,16 @@ package com.couchbase.intellij.tree.iq.spi.iq; +import com.couchbase.client.java.json.JsonObject; import com.couchbase.intellij.tree.iq.settings.OpenAISettingsState; +import com.theokanning.openai.OpenAiError; +import com.theokanning.openai.OpenAiHttpException; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; +import retrofit2.HttpException; import java.io.IOException; +import java.util.concurrent.TimeUnit; public class IQAuthenticationInterceptor implements Interceptor { @@ -15,11 +20,20 @@ public IQAuthenticationInterceptor() { @Override public Response intercept(Chain chain) throws IOException { String token = OpenAISettingsState.getInstance().getGpt4Config().getApiKey(); - Request request = chain.request() + Request request = chain.withReadTimeout(10, TimeUnit.SECONDS) + .request() .newBuilder() .header("Authorization", String.format("Bearer %s", token)) .header("Content-Type", "application/json") .build(); - return chain.proceed(request); + Response response = chain.proceed(request); + if (response.code() > 399) { + JsonObject body = JsonObject.fromJson(response.body().string()); + if (!body.containsKey("error") && body.containsKey("message")) { + OpenAiError error = new OpenAiError(new OpenAiError.OpenAiErrorDetails(body.getString("message"), "", "", String.valueOf(body.getInt("code")))); + throw new OpenAiHttpException(error, null, body.getInt("code")); + } + } + return response; } } diff --git a/src/main/java/com/couchbase/intellij/tree/iq/ui/ChatPanel.java b/src/main/java/com/couchbase/intellij/tree/iq/ui/ChatPanel.java index 69a3b317..33432af8 100644 --- a/src/main/java/com/couchbase/intellij/tree/iq/ui/ChatPanel.java +++ b/src/main/java/com/couchbase/intellij/tree/iq/ui/ChatPanel.java @@ -1,5 +1,6 @@ package com.couchbase.intellij.tree.iq.ui; +import com.couchbase.client.java.json.JsonArray; import com.couchbase.client.java.json.JsonObject; import com.couchbase.intellij.tree.iq.CapellaOrganization; import com.couchbase.intellij.tree.iq.CapellaOrganizationList; @@ -37,6 +38,8 @@ import javax.swing.text.AbstractDocument; import java.awt.*; import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -45,6 +48,7 @@ public class ChatPanel extends OnePixelSplitter implements ChatMessageListener { + public static final String FAKE_CONFUSION = "Sorry, I couldn't quite get that. Could you restate the request?"; private final ExpandableTextFieldExt searchTextField; private final JButton button; private final JButton stopGenerating; @@ -259,16 +263,41 @@ public void responseCompleted(ChatMessageEvent.ResponseArrived event) { List response = event.getResponseChoices(); response.forEach(message -> Log.info(String.format("IQ response message: %s", message.toString()))); if (response.size() == 1 && response.get(0).getContent().startsWith("{")) { - IntentProcessor intentProcessor = ApplicationManager.getApplication().getService(IntentProcessor.class); JsonObject intents = JsonObject.fromJson(response.get(0).getContent()); - getQuestion().addIntentResponse(intents); - intentProcessor.process(this, event.getUserMessage(), intents); - } else { - setContent(event.getResponseChoices()); - SwingUtilities.invokeLater(() -> { - aroundRequest(false); - }); + if (isEmptyResponse(intents) || containsNoneIntent(intents)) { + response = Arrays.asList(new ChatMessage("assistant", FAKE_CONFUSION) ); + } else { + IntentProcessor intentProcessor = ApplicationManager.getApplication().getService(IntentProcessor.class); + getQuestion().addIntentResponse(intents); + intentProcessor.process(this, event.getUserMessage(), intents); + return; + } + } + + setContent(response); + SwingUtilities.invokeLater(() -> { + aroundRequest(false); + }); + } + + private boolean containsNoneIntent(JsonObject intents) { + JsonArray actions = intents.getArray("actions"); + for (int i = 0; i < actions.size(); i++) { + JsonObject action = actions.getObject(i); + + if (!action.containsKey("action") || action.getString("action").equalsIgnoreCase("none")) { + return true; + } } + + return false; + } + + private boolean isEmptyResponse(JsonObject intents) { + return intents.size() == 0 + || !intents.containsKey("actions") + || !(intents.get("actions") instanceof JsonArray) + || intents.getArray("actions").size() == 0; } public void setContent(List content) { diff --git a/src/main/java/com/couchbase/intellij/workbench/Log.java b/src/main/java/com/couchbase/intellij/workbench/Log.java index c62fc8f9..c4720a7a 100644 --- a/src/main/java/com/couchbase/intellij/workbench/Log.java +++ b/src/main/java/com/couchbase/intellij/workbench/Log.java @@ -58,8 +58,11 @@ public static ConsoleView getView() { return console; } - public static void info(String message) { + public static void info(String message, Object... arguments) { if (logLevel >= 2) { + if (arguments.length > 0) { + message = String.format(message, arguments); + } getLogger().print("\n" + message, ConsoleViewContentType.LOG_INFO_OUTPUT); } } diff --git a/src/test/java/com/couchbase/intellij/tree/iq/AbstractCapellaTest.java b/src/test/java/com/couchbase/intellij/tree/iq/AbstractCapellaTest.java new file mode 100644 index 00000000..31f0a445 --- /dev/null +++ b/src/test/java/com/couchbase/intellij/tree/iq/AbstractCapellaTest.java @@ -0,0 +1,37 @@ +package com.couchbase.intellij.tree.iq; + +import com.couchbase.intellij.tree.iq.chat.ChatLinkService; +import com.couchbase.intellij.tree.iq.chat.ChatLinkState; +import com.couchbase.intellij.tree.iq.chat.ConfigurationPage; +import com.couchbase.intellij.tree.iq.core.IQCredentials; +import com.couchbase.intellij.tree.iq.settings.OpenAISettingsState; +import com.couchbase.intellij.workbench.Log; + +public abstract class AbstractCapellaTest extends AbstractIQTest { + @Override + protected void setUp() throws Exception { + super.setUp(); + assertTrue("Please set Capella domain using `CAPELLA_DOMAIN` envvar", System.getenv().containsKey("CAPELLA_DOMAIN")); + CapellaApiMethods.setCapellaDomain(System.getenv("CAPELLA_DOMAIN")); + IQCredentials credentials = new IQCredentials(System.getenv("IQ_ORG_LOGIN"), System.getenv("IQ_ORG_PASSWD")); + assertTrue("Please set IQ credentials using `IQ_ORG_ID`, `IQ_ORG_LOGIN`, and `IQ_ORG_PASSWD` envvars", credentials.doLogin()); + String orgId = System.getenv("IQ_ORG_ID"); + assertTrue("Please set IQ org id using `IQ_ORG_ID` envvar", orgId != null && orgId.length() > 0); + final String iqUrl = String.format(IQ_URL, orgId); + OpenAISettingsState.OpenAIConfig iqGptConfig = new OpenAISettingsState.OpenAIConfig(); + OpenAISettingsState.getInstance().setGpt4Config(iqGptConfig); + OpenAISettingsState.getInstance().setEnableInitialMessage(false); + iqGptConfig.setApiKey(credentials.getAuth().getJwt()); + iqGptConfig.setEnableStreamResponse(false); + iqGptConfig.setModelName("gpt-4"); + iqGptConfig.setApiEndpointUrl(iqUrl); + iqGptConfig.setEnableCustomApiEndpointUrl(true); + ConfigurationPage cp = iqGptConfig.withSystemPrompt(IQWindowContent::systemPrompt); + Log.setLevel(3); + Log.setPrinter(new Log.StdoutPrinter()); + + link = new ChatLinkService(getProject(), null, cp); + ctx = new ChatLinkState(cp); + } + +} diff --git a/src/test/java/com/couchbase/intellij/tree/iq/intents/AbstractIQTest.java b/src/test/java/com/couchbase/intellij/tree/iq/AbstractIQTest.java similarity index 75% rename from src/test/java/com/couchbase/intellij/tree/iq/intents/AbstractIQTest.java rename to src/test/java/com/couchbase/intellij/tree/iq/AbstractIQTest.java index 928b5f3c..150f5b8d 100644 --- a/src/test/java/com/couchbase/intellij/tree/iq/intents/AbstractIQTest.java +++ b/src/test/java/com/couchbase/intellij/tree/iq/AbstractIQTest.java @@ -1,8 +1,7 @@ -package com.couchbase.intellij.tree.iq.intents; +package com.couchbase.intellij.tree.iq; import com.couchbase.client.java.json.JsonArray; import com.couchbase.client.java.json.JsonObject; -import com.couchbase.intellij.tree.iq.IQWindowContent; import com.couchbase.intellij.tree.iq.chat.ChatExchangeAbortException; import com.couchbase.intellij.tree.iq.chat.ChatGptHandler; import com.couchbase.intellij.tree.iq.chat.ChatLink; @@ -23,41 +22,21 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.function.Consumer; public abstract class AbstractIQTest extends BasePlatformTestCase { - private static final String IQ_URL = System.getenv("CAPELLA_DOMAIN") + "/v2/organizations/%s/integrations/iq/"; + protected static final String IQ_URL = System.getenv("CAPELLA_DOMAIN") + "/v2/organizations/%s/integrations/iq/"; private static final ChatGptHandler handler = new ChatGptHandler(); - private static ConversationContext ctx; - private static ChatLink link; - - @Override - protected void setUp() throws Exception { - super.setUp(); - IQCredentials credentials = new IQCredentials(System.getenv("IQ_ORG_LOGIN"), System.getenv("IQ_ORG_PASSWD")); - assertTrue("Please set capella domain and IQ credentials using `CAPELLA_DOMAIN`, `IQ_ORG_ID`, `IQ_ORG_LOGIN`, and `IQ_ORG_PASSWD` envvars", credentials.doLogin()); - String orgId = System.getenv("IQ_ORG_ID"); - final String iqUrl = String.format(IQ_URL, orgId); - OpenAISettingsState.OpenAIConfig iqGptConfig = new OpenAISettingsState.OpenAIConfig(); - OpenAISettingsState.getInstance().setGpt4Config(iqGptConfig); - OpenAISettingsState.getInstance().setEnableInitialMessage(false); - iqGptConfig.setApiKey(credentials.getAuth().getJwt()); - iqGptConfig.setEnableStreamResponse(false); - iqGptConfig.setModelName("gpt-4"); - iqGptConfig.setApiEndpointUrl(iqUrl); - iqGptConfig.setEnableCustomApiEndpointUrl(true); - ConfigurationPage cp = iqGptConfig.withSystemPrompt(IQWindowContent::systemPrompt); - Log.setLevel(3); - Log.setPrinter(new Log.StdoutPrinter()); - - link = new ChatLinkService(getProject(), null, cp); - ctx = new ChatLinkState(cp); - } + protected static ConversationContext ctx; + protected static ChatLink link; protected void send(String message, Consumer listener) { send(message, false, listener); } protected void send(String message, boolean isSystem, Consumer listener) { + assertNotNull(ctx); + assertNotNull(link); ChatMessage chatMessage = new ChatMessage( isSystem ? ChatMessageRole.SYSTEM.value() : ChatMessageRole.USER.value(), message @@ -99,7 +78,7 @@ public void responseCompleted(ChatMessageEvent.ResponseArrived event) { @Override public void exchangeFailed(ChatMessageEvent.Failed event) { - throw new RuntimeException("IQ Exchange failed", event.getCause()); + throw new RuntimeException("IQ Exchange failed", event == null ? null : event.getCause()); } @Override @@ -142,4 +121,23 @@ protected List getIntents(ChatMessageEvent.ResponseArrived response, } return results; } + + public void assertCauseMessage(Throwable e, String msg) { + while (e != null && e.getCause() != e) { + if (e.getMessage().equals(msg)) { + return; + } + if (e.getCause() == e) { + break; + } + e = e.getCause(); + } + throw new AssertionError("Exception was not caused by an error with message '" + msg + "'"); + } + + protected void assertResponseTextEquals(ChatMessageEvent.ResponseArrived responseArrived, String fakeConfusion) { + assertTrue(responseArrived.getResponseChoices().stream() + .filter(Objects::nonNull) + .anyMatch(choice -> choice.getContent() != null && choice.getContent().equals(fakeConfusion))); + } } diff --git a/src/test/java/com/couchbase/intellij/tree/iq/AbstractMockedIQTest.java b/src/test/java/com/couchbase/intellij/tree/iq/AbstractMockedIQTest.java new file mode 100644 index 00000000..286547a5 --- /dev/null +++ b/src/test/java/com/couchbase/intellij/tree/iq/AbstractMockedIQTest.java @@ -0,0 +1,94 @@ +package com.couchbase.intellij.tree.iq; + +import com.couchbase.client.java.json.JsonArray; +import com.couchbase.client.java.json.JsonObject; +import com.couchbase.intellij.tree.iq.chat.ChatLinkService; +import com.couchbase.intellij.tree.iq.chat.ChatLinkState; +import com.couchbase.intellij.tree.iq.chat.ConfigurationPage; +import com.couchbase.intellij.tree.iq.core.CapellaAuth; +import com.couchbase.intellij.tree.iq.core.IQCredentials; +import com.couchbase.intellij.tree.iq.settings.OpenAISettingsState; +import com.couchbase.intellij.workbench.Log; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.mockito.Mockito; + +import java.util.UUID; + +public abstract class AbstractMockedIQTest extends AbstractIQTest { + private static final String GPT_MODEL = "gpt-3.5-turbo-0125"; + protected static final String IQ_URL = "%s/v2/organizations/%s/integrations/iq/"; + private int port; + private MockWebServer server; + @Override + protected void setUp() throws Exception { + super.setUp(); + + server = new MockWebServer(); + int port = 42000; + do { + try { + server.start(port); + break; + } catch (Exception e) { + + } + } while (++port < 43000); + Log.info("Mock IQ server started at port %d", port); + + String domain = String.format("http://localhost:%d", port); + CapellaApiMethods.setCapellaDomain(domain); + IQCredentials credentials = Mockito.mock(IQCredentials.class); + CapellaAuth auth = Mockito.mock(CapellaAuth.class); + Mockito.when(credentials.getAuth()).thenReturn(auth); + Mockito.when(auth.getJwt()).thenReturn("fakejwt"); + String orgId = UUID.randomUUID().toString(); + final String iqUrl = String.format(IQ_URL, domain, orgId); + OpenAISettingsState.OpenAIConfig iqGptConfig = new OpenAISettingsState.OpenAIConfig(); + OpenAISettingsState.getInstance().setGpt4Config(iqGptConfig); + OpenAISettingsState.getInstance().setEnableInitialMessage(false); + iqGptConfig.setApiKey(credentials.getAuth().getJwt()); + iqGptConfig.setEnableStreamResponse(false); + iqGptConfig.setModelName("gpt-4"); + iqGptConfig.setApiEndpointUrl(iqUrl); + iqGptConfig.setEnableCustomApiEndpointUrl(true); + ConfigurationPage cp = iqGptConfig.withSystemPrompt(IQWindowContent::systemPrompt); + Log.setLevel(3); + Log.setPrinter(new Log.StdoutPrinter()); + + link = new ChatLinkService(getProject(), null, cp); + ctx = new ChatLinkState(cp); + } + + protected void enqueueResponse(String text) { + JsonObject packet = JsonObject.create(); + packet.put("id", UUID.randomUUID().toString()); + packet.put("object", "chat.completion"); + packet.put("created", (int) System.currentTimeMillis() / 1000); + packet.put("model", GPT_MODEL); + JsonArray choices = JsonArray.create(); + JsonObject choice = JsonObject.create(); + choice.put("index", 0); + JsonObject message = JsonObject.create(); + message.put("role", "assistant"); + message.put("content", text); + choice.put("message", message); + choice.put("finish_reason", "stop"); + packet.put("choices", choices); + + this.server.enqueue(new MockResponse().setResponseCode(200).setBody(packet.toString())); + } + + protected void enqueueResponse(JsonObject json) { + enqueueResponse(json.toString()); + } + + protected void enqueueError(int httpCode, int errCode, String error) { + JsonObject packet = JsonObject.create(); + packet.put("httpStatusCode", httpCode); + packet.put("code", errCode); + packet.put("message", error); + packet.put("hint", "ruserious?"); + this.server.enqueue(new MockResponse().setResponseCode(httpCode).setBody(packet.toString())); + } +} diff --git a/src/test/java/com/couchbase/intellij/tree/iq/chat/ChatLinkServiceTest.java b/src/test/java/com/couchbase/intellij/tree/iq/chat/ChatLinkServiceTest.java new file mode 100644 index 00000000..2dd1bdff --- /dev/null +++ b/src/test/java/com/couchbase/intellij/tree/iq/chat/ChatLinkServiceTest.java @@ -0,0 +1,19 @@ +package com.couchbase.intellij.tree.iq.chat; + +import com.couchbase.intellij.tree.iq.AbstractIQTest; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ChatLinkServiceTest extends AbstractIQTest { + + @Test + void testGoofErrorHandler() { + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + } +} \ No newline at end of file diff --git a/src/test/java/com/couchbase/intellij/tree/iq/intents/IntentProcessorTest.java b/src/test/java/com/couchbase/intellij/tree/iq/intents/CapellaIntentProcessorTest.java similarity index 77% rename from src/test/java/com/couchbase/intellij/tree/iq/intents/IntentProcessorTest.java rename to src/test/java/com/couchbase/intellij/tree/iq/intents/CapellaIntentProcessorTest.java index ac21f200..73fbcaa0 100644 --- a/src/test/java/com/couchbase/intellij/tree/iq/intents/IntentProcessorTest.java +++ b/src/test/java/com/couchbase/intellij/tree/iq/intents/CapellaIntentProcessorTest.java @@ -1,15 +1,15 @@ package com.couchbase.intellij.tree.iq.intents; +import com.couchbase.intellij.tree.iq.AbstractCapellaTest; import org.junit.Test; import static org.junit.jupiter.api.Assertions.assertTrue; -public class IntentProcessorTest extends AbstractIQTest { +public class CapellaIntentProcessorTest extends AbstractCapellaTest { @Test public void testRespondsWithJson() throws Exception { send("list all boolean fields in the airport collection", this::assertJsonResponse); send("create a collection named 'test'", this::assertJsonResponse); send("open an airport with id 'UULL'", this::assertJsonResponse); } - } \ No newline at end of file diff --git a/src/test/java/com/couchbase/intellij/tree/iq/intents/MockedIntentProcessorTest.java b/src/test/java/com/couchbase/intellij/tree/iq/intents/MockedIntentProcessorTest.java new file mode 100644 index 00000000..bdd29e69 --- /dev/null +++ b/src/test/java/com/couchbase/intellij/tree/iq/intents/MockedIntentProcessorTest.java @@ -0,0 +1,64 @@ +package com.couchbase.intellij.tree.iq.intents; + +import com.couchbase.client.java.json.JsonArray; +import com.couchbase.client.java.json.JsonObject; +import com.couchbase.intellij.tree.iq.AbstractMockedIQTest; +import com.couchbase.intellij.tree.iq.ui.ChatPanel; + +public class MockedIntentProcessorTest extends AbstractMockedIQTest { + + public void testErrorHandling() throws Exception { + enqueueError(500, 500, "test error"); + try { + send("boop", responseArrived -> { + }); + } catch (Throwable e) { + assertCauseMessage(e, "test error"); + return; + } + assertFalse(true); + } + + public void testEmptyJsonResponse() throws Exception { + enqueueResponse(""); + send("boop", responseArrived -> { + assertResponseTextEquals(responseArrived, ChatPanel.FAKE_CONFUSION); + }); + enqueueResponse("{ }"); + send("boop", responseArrived -> { + assertResponseTextEquals(responseArrived, ChatPanel.FAKE_CONFUSION); + }); + enqueueResponse("{\n}"); + send("boop", responseArrived -> { + assertResponseTextEquals(responseArrived, ChatPanel.FAKE_CONFUSION); + }); + + JsonObject response = JsonObject.create(); + response.putNull("actions"); + enqueueResponse(response); + send("boop me!", responseArrived -> { + assertResponseTextEquals(responseArrived, ChatPanel.FAKE_CONFUSION); + }); + + JsonArray actions = JsonArray.create(); + response.put("actions", actions); + enqueueResponse(response); + send("boop me!", responseArrived -> { + assertResponseTextEquals(responseArrived, ChatPanel.FAKE_CONFUSION); + }); + + JsonObject action = JsonObject.create(); + actions.add(action); + enqueueResponse(response); + send("boop me!", responseArrived -> { + assertResponseTextEquals(responseArrived, ChatPanel.FAKE_CONFUSION); + }); + + // validate that regular responses are passed correctly + enqueueResponse("boop"); + send("beep", responseArrived -> { + assertResponseTextEquals(responseArrived, "boop"); + }); + } + +} diff --git a/src/test/java/com/couchbase/intellij/tree/iq/intents/actions/CreateCollectionTest.java b/src/test/java/com/couchbase/intellij/tree/iq/intents/actions/CreateCollectionTest.java index 53f2d457..c5c3d182 100644 --- a/src/test/java/com/couchbase/intellij/tree/iq/intents/actions/CreateCollectionTest.java +++ b/src/test/java/com/couchbase/intellij/tree/iq/intents/actions/CreateCollectionTest.java @@ -4,13 +4,14 @@ import com.couchbase.client.java.json.JsonObject; import com.couchbase.intellij.database.ActiveCluster; import com.couchbase.intellij.testutil.TestActiveCluster; -import com.couchbase.intellij.tree.iq.intents.AbstractIQTest; +import com.couchbase.intellij.tree.iq.AbstractCapellaTest; +import com.couchbase.intellij.tree.iq.AbstractIQTest; import org.junit.Test; import org.mockito.Mockito; import java.util.List; -public class CreateCollectionTest extends AbstractIQTest { +public class CreateCollectionTest extends AbstractCapellaTest { @Override protected void setUp() throws Exception { super.setUp();