Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api-testing-bot/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ dependencies {
implementation "org.kohsuke:github-api:1.308"
implementation "io.jsonwebtoken:jjwt-impl:0.11.5"
implementation "io.jsonwebtoken:jjwt-jackson:0.11.5"

implementation "com.github.javaparser:javaparser-core:3.24.4"
}

configurations {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public Response modelTest(String body) {

StringBuilder responseMessageSB = new StringBuilder();
APITestingBot service = (APITestingBot) Context.get().getService();
String codexAPIToken = service.getCodexAPIToken();

// setup message handler
MessageHandler messageHandler;
Expand All @@ -68,6 +69,22 @@ public Response modelTest(String body) {
handleNextState = messageHandler.handleInit(responseMessageSB, context);
}

if(handleNextState && context.getState() == API_TEST_FAMILIARITY_QUESTION) {
handleNextState = messageHandler.handleAPITestFamiliarityQuestion(responseMessageSB);
}

if(initialState == API_TEST_FAMILIARITY_QUESTION) {
handleNextState = messageHandler.handleAPITestFamiliarityQuestionAnswer(responseMessageSB, context, intent);
}

if(handleNextState && context.getState() == ENTER_TEST_CASE_DESCRIPTION) {
handleNextState = messageHandler.handleTestCaseDescriptionQuestion(responseMessageSB);
}

if(initialState == ENTER_TEST_CASE_DESCRIPTION) {
handleNextState = messageHandler.handleTestCaseDescription(responseMessageSB, context, message, codexAPIToken);
}

if (handleNextState && context.getState() == RC_SELECT_PROJECT) {
handleNextState = ((RCMessageHandler) messageHandler).handleProjectSelectionQuestion(responseMessageSB, context, channel);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package i5.las2peer.services.apiTestingBot.chat;

import i5.las2peer.apiTestModel.BodyAssertion;
import i5.las2peer.apiTestModel.RequestAssertion;
import i5.las2peer.apiTestModel.StatusCodeAssertion;
import i5.las2peer.apiTestModel.TestRequest;
import i5.las2peer.services.apiTestingBot.context.TestModelingContext;

import static i5.las2peer.services.apiTestingBot.chat.MessageHandlerUtil.handleYesNoQuestion;
import static i5.las2peer.services.apiTestingBot.chat.Messages.*;
import static i5.las2peer.services.apiTestingBot.context.TestModelingState.*;

Expand All @@ -10,18 +15,17 @@
*/
public class GHMessageHandler extends MessageHandler {

/**
* Reacts to the initial message of the user that starts a test modeling conversation.
*
* @param responseMessageSB StringBuilder
* @param context Current test modeling context
* @return Whether the next state should be handled too.
*/
@Override
public boolean handleInit(StringBuilder responseMessageSB, TestModelingContext context) {
responseMessageSB.append(MODEL_TEST_CASE_INTRO);
context.setState(NAME_TEST_CASE);
return true;
public boolean handleAPITestFamiliarityQuestionAnswer(StringBuilder responseMessageSB, TestModelingContext context, String intent) {
return handleYesNoQuestion(responseMessageSB, intent, () -> {
responseMessageSB.append(OK);
context.setState(ENTER_TEST_CASE_DESCRIPTION);
return true;
}, () -> {
responseMessageSB.append(OK);
context.setState(NAME_TEST_CASE);
return true;
});
}

/**
Expand Down Expand Up @@ -99,4 +103,37 @@ public boolean handlePath(StringBuilder responseMessageSB, TestModelingContext c
context.setState(BODY_QUESTION);
return true;
}

@Override
public String getTestDescription(TestRequest request) {
return getGitHubTestDescription(request);
}

public static String getGitHubTestDescription(TestRequest request) {
String description = "";
String url = getRequestUrlWithPathParamValues(request);
description += "**Method & Path:** `" + request.getType() + "` `" + url + "`\n";

// request auth info
String auth = request.getAgent() == -1 ? "None" : "User Agent";
description += "**Authorization:** " + auth + "\n";

// request body (if exists)
if(request.getBody() != null && !request.getBody().isEmpty()) {
description += "**Body:**\n" + "```json" + request.getBody() + "```\n";
}

// list assertions
description += "**Assertions:**\n";
for(RequestAssertion assertion : request.getAssertions()) {
description += "- ";
if(assertion instanceof StatusCodeAssertion) {
description += "Expected status code: " + ((StatusCodeAssertion) assertion).getStatusCodeValue();
} else {
description += ((BodyAssertion) assertion).getOperator().toString();
}
description += "\n";
}
return description;
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package i5.las2peer.services.apiTestingBot.chat;

import i5.las2peer.apiTestModel.BodyAssertion;
import i5.las2peer.apiTestModel.BodyAssertionOperator;
import i5.las2peer.apiTestModel.RequestAssertion;
import i5.las2peer.apiTestModel.StatusCodeAssertion;
import i5.las2peer.apiTestModel.*;
import i5.las2peer.services.apiTestingBot.codex.CodeToTestModel;
import i5.las2peer.services.apiTestingBot.codex.CodexAPI;
import i5.las2peer.services.apiTestingBot.codex.CodexTestGen;
import i5.las2peer.services.apiTestingBot.context.BodyAssertionType;
import i5.las2peer.services.apiTestingBot.context.TestModelingContext;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;

import java.io.IOException;
import java.util.List;

import static i5.las2peer.services.apiTestingBot.chat.MessageHandlerUtil.*;
Expand All @@ -25,7 +27,68 @@ public abstract class MessageHandler {
* @param context Current test modeling context
* @return Whether the next state should be handled too.
*/
public abstract boolean handleInit(StringBuilder responseMessageSB, TestModelingContext context);
public boolean handleInit(StringBuilder responseMessageSB, TestModelingContext context) {
responseMessageSB.append(MODEL_TEST_CASE_INTRO);
context.setState(API_TEST_FAMILIARITY_QUESTION);
return true;
}

public boolean handleAPITestFamiliarityQuestion(StringBuilder responseMessageSB) {
if(!responseMessageSB.isEmpty()) responseMessageSB.append(" ");
responseMessageSB.append(API_TEST_FAMILIARITY_QUESTION_TEXT);
return false;
}

public abstract boolean handleAPITestFamiliarityQuestionAnswer(StringBuilder responseMessageSB, TestModelingContext context, String intent);

public boolean handleTestCaseDescriptionQuestion(StringBuilder responseMessageSB) {
if(!responseMessageSB.isEmpty()) responseMessageSB.append(" ");
responseMessageSB.append(ENTER_TEST_CASE_DESCRIPTION_TEXT);
return false;
}

public boolean handleTestCaseDescription(StringBuilder responseMessageSB, TestModelingContext context, String message,
String codexAPIToken) {
TestRequest generatedRequest = null;
try {
generatedRequest = new CodexTestGen(codexAPIToken).descriptionToTestModel(message);
} catch (CodexAPI.CodexAPIException | IOException e) {
e.printStackTrace();
return false;
} catch (CodeToTestModel.CodeToTestModelException e) {
e.printStackTrace();
responseMessageSB.append(ERROR_TEST_CASE_GENERATION);
return false;
}
context.setRequestMethod(generatedRequest.getType());
context.setRequestPath(generatedRequest.getUrl());
context.setPathParamValues(generatedRequest.getPathParams());
context.setTestCaseName("TestCaseName");
context.setRequestBody(generatedRequest.getBody());
context.getAssertions().addAll(generatedRequest.getAssertions());
responseMessageSB.append(getTestDescription(generatedRequest));
context.setState(FINAL);
return false;
}

public abstract String getTestDescription(TestRequest request);

/**
* Returns the request url of the given test request where the path parameters are replaced with their values.
*
* @param request TestRequest
* @return Request url of the given test request where the path parameters are replaced with their values.
*/
protected static String getRequestUrlWithPathParamValues(TestRequest request) {
String url = request.getUrl();
JSONObject pathParams = request.getPathParams();
for(Object key : pathParams.keySet()) {
String paramValue = String.valueOf(pathParams.get(key));
if(paramValue.isEmpty()) paramValue = "<Enter " + key + ">";
url = url.replace("{" + key + "}", paramValue);
}
return url;
}

/**
* Ask user to enter a name for the test case.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
public class Messages {

public static String MODEL_TEST_CASE_INTRO = "Ok, let's model a test case.";
public static String API_TEST_FAMILIARITY_QUESTION_TEXT = "Are you familiar with REST API testing?";
public static String ENTER_TEST_CASE_DESCRIPTION_TEXT = "How would you describe the test case? Please give me a few sentences about it.";
public static String SELECT_PROJECT_FOR_TEST_CASE = "Which project should the test case be added to? Please enter a number:";
public static String SELECT_MICROSERVICE_FOR_TEST_CASE = "Which microservice should the test case be added to? Please enter a number:";
public static String ENTER_TEST_CASE_NAME = "Please enter a name for the test case:";
Expand Down Expand Up @@ -38,6 +40,7 @@ public class Messages {
public static String ERROR_COULD_NOT_UNDERSTAND = "I could not understand that. Please try again.";
public static String ERROR_COULD_NOT_UNDERSTAND_TYPE = "I could not understand that. Please enter a valid type.";
public static String ERROR_BODY_NO_VALID_JSON = "Entered body is no valid JSON! Please try again.";
public static String ERROR_TEST_CASE_GENERATION = "Unfortunately I was not able to generate a test case based on your description. Please try again.";

public static String TEST_ADD_TO_PROJECT(String projectName) {
return "The test will be added to the project \"" + projectName + "\".";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package i5.las2peer.services.apiTestingBot.chat;

import i5.las2peer.apiTestModel.BodyAssertion;
import i5.las2peer.apiTestModel.RequestAssertion;
import i5.las2peer.apiTestModel.StatusCodeAssertion;
import i5.las2peer.apiTestModel.TestRequest;
import i5.las2peer.services.apiTestingBot.context.TestModelingContext;
import i5.las2peer.services.apiTestingBot.util.ProjectServiceHelper;
import io.swagger.v3.oas.models.Operation;
Expand All @@ -15,8 +19,7 @@
import java.util.List;
import java.util.Map;

import static i5.las2peer.services.apiTestingBot.chat.MessageHandlerUtil.getFirstUnsetPathParam;
import static i5.las2peer.services.apiTestingBot.chat.MessageHandlerUtil.handleNumberSelectionQuestion;
import static i5.las2peer.services.apiTestingBot.chat.MessageHandlerUtil.*;
import static i5.las2peer.services.apiTestingBot.chat.Messages.*;
import static i5.las2peer.services.apiTestingBot.chat.Messages.SELECT_PROJECT_FOR_TEST_CASE;
import static i5.las2peer.services.apiTestingBot.context.TestModelingState.*;
Expand All @@ -32,18 +35,17 @@ public RCMessageHandler(String caeBackendURL) {
this.caeBackendURL = caeBackendURL;
}

/**
* Reacts to the initial message of the user that starts a test modeling conversation.
*
* @param responseMessageSB StringBuilder
* @param context Current test modeling context
* @return Whether the next state should be handled too.
*/
@Override
public boolean handleInit(StringBuilder responseMessageSB, TestModelingContext context) {
responseMessageSB.append(MODEL_TEST_CASE_INTRO);
context.setState(RC_SELECT_PROJECT);
return true;
public boolean handleAPITestFamiliarityQuestionAnswer(StringBuilder responseMessageSB, TestModelingContext context, String intent) {
return handleYesNoQuestion(responseMessageSB, intent, () -> {
responseMessageSB.append(OK);
context.setState(ENTER_TEST_CASE_DESCRIPTION);
return true;
}, () -> {
responseMessageSB.append(OK);
context.setState(RC_SELECT_PROJECT);
return true;
});
}

/**
Expand Down Expand Up @@ -324,4 +326,33 @@ public boolean handlePathParams(StringBuilder responseMessageSB, TestModelingCon

return false;
}

@Override
public String getTestDescription(TestRequest request) {
String description = "";
String url = getRequestUrlWithPathParamValues(request);
description += "Method & Path: `" + request.getType() + "` `" + url + "`\n";

// request auth info
String auth = request.getAgent() == -1 ? "None" : "User Agent";
description += "Authorization: " + auth + "\n";

// request body (if exists)
if(request.getBody() != null && !request.getBody().isEmpty()) {
description += "Body:\n" + "`" + request.getBody() + "`\n";
}

// list assertions
description += "Assertions:\n";
for(RequestAssertion assertion : request.getAssertions()) {
description += "- ";
if(assertion instanceof StatusCodeAssertion) {
description += "Expected status code: " + ((StatusCodeAssertion) assertion).getStatusCodeValue();
} else {
description += ((BodyAssertion) assertion).getOperator().toString();
}
description += "\n";
}
return description;
}
}
Loading