Smack/smack-integration-test/src/main/java/org/jivesoftware/smackx/commands/AdHocCommandIntegrationTest...

354 lines
14 KiB
Java

/**
*
* Copyright 2023 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.commands;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smackx.commands.packet.AdHocCommandData;
import org.jivesoftware.smackx.commands.packet.AdHocCommandDataBuilder;
import org.jivesoftware.smackx.commands.packet.AdHocCommandDataBuilder.NextStage;
import org.jivesoftware.smackx.commands.packet.AdHocCommandDataBuilder.PreviousStage;
import org.jivesoftware.smackx.xdata.FormField;
import org.jivesoftware.smackx.xdata.form.FillableForm;
import org.jivesoftware.smackx.xdata.form.SubmitForm;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
public class AdHocCommandIntegrationTest extends AbstractSmackIntegrationTest {
public AdHocCommandIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}
@SmackIntegrationTest
public void singleStageAdHocCommandTest() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
AdHocCommandManager manOne = AdHocCommandManager.getInstance(conOne);
AdHocCommandManager manTwo = AdHocCommandManager.getInstance(conTwo);
String commandNode = "test-list";
String commandName = "Return a list for testing purposes";
AdHocCommandHandlerFactory factory = (String node, String name, String sessionId) -> {
return new AdHocCommandHandler.SingleStage(node, name, sessionId) {
@Override
public AdHocCommandData executeSingleStage(AdHocCommandDataBuilder response) {
FormField field = FormField.textPrivateBuilder("my-field").build();
DataForm form = DataForm.builder(DataForm.Type.result).addField(field).build();
response.setForm(form);
return response.build();
}
};
};
manOne.registerCommand(commandNode, commandName, factory);
try {
AdHocCommand command = manTwo.getRemoteCommand(conOne.getUser(), commandNode);
AdHocCommandResult result = command.execute();
AdHocCommandData response = result.getResponse();
DataForm form = response.getForm();
FormField field = form.getField("my-field");
assertNotNull(field);
} finally {
manOne.unregisterCommand(commandNode);
}
}
private static class MyMultiStageAdHocCommandServer extends AdHocCommandHandler {
private Integer a;
private Integer b;
private static DataForm createDataForm(String variableName) {
FormField field = FormField.textSingleBuilder(variableName).setRequired().build();
return DataForm.builder(DataForm.Type.form)
.setTitle("Variable " + variableName)
.setInstructions("Please provide an integer variable " + variableName)
.addField(field)
.build();
}
private static DataForm createDataFormOp() {
FormField field = FormField.listSingleBuilder("op")
.setLabel("Arthimetic Operation")
.setRequired()
.addOption("+")
.addOption("-")
.build();
return DataForm.builder(DataForm.Type.form)
.setTitle("Operation")
.setInstructions("Please select the arithmetic operation to be performed with a and b")
.addField(field)
.build();
}
private static final DataForm dataFormAskingForA = createDataForm("a");
private static final DataForm dataFormAskingForB = createDataForm("b");
private static final DataForm dataFormAskingForOp = createDataFormOp();
MyMultiStageAdHocCommandServer(String node, String name, String sessionId) {
super(node, name, sessionId);
}
@Override
protected AdHocCommandData execute(AdHocCommandDataBuilder response) throws XMPPErrorException {
return response.setForm(dataFormAskingForA).setStatusExecuting(PreviousStage.none,
NextStage.nonFinal).build();
}
// TODO: Add API for every case where we return null or throw below.
private static Integer extractIntegerField(SubmitForm form, String fieldName) throws XMPPErrorException {
FormField field = form.getField(fieldName);
if (field == null)
throw newBadRequestException("Submitted form does not contain a field of name " + fieldName);
String fieldValue = field.getFirstValue();
if (fieldValue == null)
throw newBadRequestException("Submitted form contains field of name " + fieldName + " without value");
try {
return Integer.parseInt(fieldValue);
} catch (NumberFormatException e) {
throw newBadRequestException("Submitted form contains field of name " + fieldName + " with value " + fieldValue + " that is not an integer");
}
}
@Override
protected AdHocCommandData next(AdHocCommandDataBuilder response, SubmitForm submittedForm)
throws XMPPErrorException {
DataForm form;
switch (getCurrentStage()) {
case 2:
a = extractIntegerField(submittedForm, "a");
form = dataFormAskingForB;
response.setStatusExecuting(PreviousStage.exists, NextStage.nonFinal);
break;
case 3:
b = extractIntegerField(submittedForm, "b");
form = dataFormAskingForOp;
response.setStatusExecuting(PreviousStage.exists, NextStage.isFinal);
break;
case 4:
// Ad-Hoc Commands particularity: Can get to 'complete' via 'next'.
return complete(response, submittedForm);
default:
throw new IllegalStateException();
}
return response.setForm(form).build();
}
@Override
protected AdHocCommandData complete(AdHocCommandDataBuilder response, SubmitForm submittedForm)
throws XMPPErrorException {
if (getCurrentStage() != 4) {
throw new IllegalStateException();
}
if (a == null || b == null) {
throw new IllegalStateException();
}
String op = submittedForm.getField("op").getFirstValue();
int result;
switch (op) {
case "+":
result = a + b;
break;
case "-":
result = a - b;
break;
default:
throw newBadRequestException("Submitted operation " + op + " is neither + nor -");
}
response.setStatusCompleted();
FormField field = FormField.textSingleBuilder("result").setValue(result).build();
DataForm form = DataForm.builder(DataForm.Type.result).setTitle("Result").addField(field).build();
return response.setForm(form).build();
}
@Override
protected AdHocCommandData prev(AdHocCommandDataBuilder response) throws XMPPErrorException {
switch (getCurrentStage()) {
case 1:
return execute(response);
case 2:
return response.setForm(dataFormAskingForA)
.setStatusExecuting(PreviousStage.exists, NextStage.nonFinal)
.build();
case 3:
return response.setForm(dataFormAskingForB)
.setStatusExecuting(PreviousStage.exists, NextStage.isFinal)
.build();
default:
throw new IllegalStateException();
}
}
@Override
public void cancel() {
}
}
@SmackIntegrationTest
public void multiStageAdHocCommandTest() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
AdHocCommandManager manOne = AdHocCommandManager.getInstance(conOne);
AdHocCommandManager manTwo = AdHocCommandManager.getInstance(conTwo);
String commandNode = "my-multi-stage-command";
String commandName = "An example multi-sage ad-hoc command";
AdHocCommandHandlerFactory factory = (String node, String name, String sessionId) -> {
return new MyMultiStageAdHocCommandServer(node, name, sessionId);
};
manOne.registerCommand(commandNode, commandName, factory);
try {
AdHocCommand command = manTwo.getRemoteCommand(conOne.getUser(), commandNode);
AdHocCommandResult.StatusExecuting result = command.execute().asExecutingOrThrow();
FillableForm form = result.getFillableForm();
form.setAnswer("a", 42);
SubmitForm submitForm = form.getSubmitForm();
result = command.next(submitForm).asExecutingOrThrow();
form = result.getFillableForm();
form.setAnswer("b", 23);
submitForm = form.getSubmitForm();
result = command.next(submitForm).asExecutingOrThrow();
form = result.getFillableForm();
form.setAnswer("op", "+");
submitForm = form.getSubmitForm();
AdHocCommandResult.StatusCompleted completed = command.complete(submitForm).asCompletedOrThrow();
String operationResult = completed.getResponse().getForm().getField("result").getFirstValue();
assertEquals("65", operationResult);
} finally {
manTwo.unregisterCommand(commandNode);
}
}
@SmackIntegrationTest
public void multiStageWithPrevAdHocCommandTest() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
AdHocCommandManager manOne = AdHocCommandManager.getInstance(conOne);
AdHocCommandManager manTwo = AdHocCommandManager.getInstance(conTwo);
String commandNode = "my-multi-stage-with-prev-command";
String commandName = "An example multi-sage ad-hoc command";
AdHocCommandHandlerFactory factory = (String node, String name, String sessionId) -> {
return new MyMultiStageAdHocCommandServer(node, name, sessionId);
};
manOne.registerCommand(commandNode, commandName, factory);
try {
AdHocCommand command = manTwo.getRemoteCommand(conOne.getUser(), commandNode);
AdHocCommandResult.StatusExecuting result = command.execute().asExecutingOrThrow();
FillableForm form = result.getFillableForm();
form.setAnswer("a", 42);
SubmitForm submitForm = form.getSubmitForm();
command.next(submitForm).asExecutingOrThrow();
// Ups, I wanted a different value for 'a', lets execute 'prev' to get back to the previous stage.
result = command.prev().asExecutingOrThrow();
form = result.getFillableForm();
form.setAnswer("a", 77);
submitForm = form.getSubmitForm();
result = command.next(submitForm).asExecutingOrThrow();
form = result.getFillableForm();
form.setAnswer("b", 23);
submitForm = form.getSubmitForm();
result = command.next(submitForm).asExecutingOrThrow();
form = result.getFillableForm();
form.setAnswer("op", "+");
submitForm = form.getSubmitForm();
AdHocCommandResult.StatusCompleted completed = command.complete(submitForm).asCompletedOrThrow();
String operationResult = completed.getResponse().getForm().getField("result").getFirstValue();
assertEquals("100", operationResult);
} finally {
manTwo.unregisterCommand(commandNode);
}
}
@SmackIntegrationTest
public void multiStageInvalidArgAdHocCommandTest() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
AdHocCommandManager manOne = AdHocCommandManager.getInstance(conOne);
AdHocCommandManager manTwo = AdHocCommandManager.getInstance(conTwo);
String commandNode = "my-multi-stage-invalid-arg-command";
String commandName = "An example multi-sage ad-hoc command";
AdHocCommandHandlerFactory factory = (String node, String name, String sessionId) -> {
return new MyMultiStageAdHocCommandServer(node, name, sessionId);
};
manOne.registerCommand(commandNode, commandName, factory);
try {
AdHocCommand command = manTwo.getRemoteCommand(conOne.getUser(), commandNode);
AdHocCommandResult.StatusExecuting result = command.execute().asExecutingOrThrow();
FillableForm form = result.getFillableForm();
form.setAnswer("a", "forty-two");
SubmitForm submitForm = form.getSubmitForm();
XMPPErrorException exception = assertThrows(XMPPErrorException.class, () -> command.next(submitForm));
assertEquals(exception.getStanzaError().getCondition(), StanzaError.Condition.bad_request);
} finally {
manTwo.unregisterCommand(commandNode);
}
}
}