Add bugfix for ATTN
This commit is contained in:
@@ -0,0 +1,359 @@
|
||||
package haus.nightmare.lib3270j.macro;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.ecl.ECLOIA;
|
||||
import haus.nightmare.lib3270j.ecl.ECLPS;
|
||||
import haus.nightmare.lib3270j.ecl.ECLScreenDesc;
|
||||
import haus.nightmare.lib3270j.ecl.ECLSession;
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.macro.action.*;
|
||||
import haus.nightmare.lib3270j.macro.variable.MacroExpressionParser;
|
||||
import haus.nightmare.lib3270j.macro.variable.MacroVariables;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
|
||||
/**
|
||||
* Test suite for Phase 24: IBM HoD XML Macro Runtime (<HASCRIPT>) & Expression Evaluator.
|
||||
*/
|
||||
public class Phase24MacroRuntimeTest {
|
||||
|
||||
private ScreenBuffer screen;
|
||||
private InputProcessor inputProcessor;
|
||||
private EbcdicTranslator translator;
|
||||
private ECLPS ps;
|
||||
private ECLOIA oia;
|
||||
private ECLSession session;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
session = new ECLSession();
|
||||
ps = session.GetPS();
|
||||
oia = session.GetOIA();
|
||||
screen = ps.getScreenBuffer();
|
||||
translator = ps.getTranslator();
|
||||
inputProcessor = ps.getInputProcessor();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("24.2: MacroVariables variable creation, types, and canonical resolution")
|
||||
public void testMacroVariables() {
|
||||
MacroVariables vars = new MacroVariables();
|
||||
vars.createVariable("$username", "string", "IBMUSER");
|
||||
vars.createVariable("$counter$", "integer", "10");
|
||||
vars.createVariable("$rate", "double", "3.14159");
|
||||
vars.createVariable("$flag", "boolean", "true");
|
||||
vars.createVariable("$pos", "field", "5,10");
|
||||
|
||||
assertTrue(vars.isUseVars());
|
||||
assertTrue(vars.containsKey("$username"));
|
||||
assertTrue(vars.containsKey("$username$"));
|
||||
assertTrue(vars.containsKey("username"));
|
||||
|
||||
assertEquals("IBMUSER", vars.getString("$username"));
|
||||
assertEquals(10, vars.getInt("$counter"));
|
||||
assertEquals(3.14159, vars.getDouble("$rate"), 0.0001);
|
||||
assertTrue(vars.getBoolean("$flag"));
|
||||
assertEquals("5,10", vars.getString("$pos"));
|
||||
|
||||
// Update variable
|
||||
vars.setVariable("$counter", 25);
|
||||
assertEquals(25, vars.getInt("$counter"));
|
||||
|
||||
// Reset restores initial values
|
||||
vars.reset();
|
||||
assertEquals(10, vars.getInt("$counter"));
|
||||
|
||||
// Validation
|
||||
assertTrue(MacroVariables.isValidName("$myVar"));
|
||||
assertTrue(MacroVariables.isValidName("$my_var$"));
|
||||
assertFalse(MacroVariables.isValidName("invalidVar"));
|
||||
assertTrue(MacroVariables.isValidType("string"));
|
||||
assertTrue(MacroVariables.isValidType("integer"));
|
||||
assertFalse(MacroVariables.isValidType("unknown"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("24.2: MacroExpressionParser arithmetic, comparisons, logic, and concatenation")
|
||||
public void testMacroExpressionParser() {
|
||||
MacroVariables vars = new MacroVariables();
|
||||
vars.createVariable("$a", "integer", "15");
|
||||
vars.createVariable("$b", "integer", "5");
|
||||
vars.createVariable("$name", "string", "World");
|
||||
|
||||
// Arithmetic
|
||||
assertEquals(20, MacroExpressionParser.evaluateInt(vars, "$a + $b"));
|
||||
assertEquals(10, MacroExpressionParser.evaluateInt(vars, "$a - $b"));
|
||||
assertEquals(75, MacroExpressionParser.evaluateInt(vars, "$a * $b"));
|
||||
assertEquals(3, MacroExpressionParser.evaluateInt(vars, "$a / $b"));
|
||||
assertEquals(1, MacroExpressionParser.evaluateInt(vars, "$a % 2"));
|
||||
assertEquals(35, MacroExpressionParser.evaluateInt(vars, "$a + $b * 4")); // Precedence 15 + (5*4) = 35
|
||||
assertEquals(80, MacroExpressionParser.evaluateInt(vars, "($a + $b) * 4")); // Parentheses (15+5)*4 = 80
|
||||
|
||||
// String Concatenation
|
||||
assertEquals("Hello World", MacroExpressionParser.evaluateString(vars, "'Hello ' + $name"));
|
||||
assertEquals("Count: 15", MacroExpressionParser.evaluateString(vars, "'Count: ' + $a"));
|
||||
|
||||
// Comparisons
|
||||
assertTrue(MacroExpressionParser.evaluateBoolean(vars, "$a > $b"));
|
||||
assertTrue(MacroExpressionParser.evaluateBoolean(vars, "$a >= 15"));
|
||||
assertTrue(MacroExpressionParser.evaluateBoolean(vars, "$b < 10"));
|
||||
assertTrue(MacroExpressionParser.evaluateBoolean(vars, "$b <= 5"));
|
||||
assertTrue(MacroExpressionParser.evaluateBoolean(vars, "$a == 15"));
|
||||
assertTrue(MacroExpressionParser.evaluateBoolean(vars, "$name == 'world'")); // Case-insensitive per HoD
|
||||
assertTrue(MacroExpressionParser.evaluateBoolean(vars, "$a != 99"));
|
||||
|
||||
// Logical Operators
|
||||
assertTrue(MacroExpressionParser.evaluateBoolean(vars, "$a > 10 && $b < 10"));
|
||||
assertTrue(MacroExpressionParser.evaluateBoolean(vars, "$a > 20 || $b == 5"));
|
||||
assertTrue(MacroExpressionParser.evaluateBoolean(vars, "!($a < 10)"));
|
||||
assertTrue(MacroExpressionParser.evaluateBoolean(vars, "$a == 15 AND $b == 5"));
|
||||
assertTrue(MacroExpressionParser.evaluateBoolean(vars, "$a == 99 OR $b == 5"));
|
||||
|
||||
// resolveText
|
||||
assertEquals("Logon for World with ID 15", MacroExpressionParser.resolveText(vars, "Logon for $name with ID $a"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("24.1: MacroXmlParser parsing <HASCRIPT> AST and roundtrip XML formatting")
|
||||
public void testMacroXmlParser() {
|
||||
String xml = "<HASCRIPT name=\"TestMacro\" description=\"A unit test macro\" timeout=\"8000\" pausetime=\"200\" usevars=\"true\">\n"
|
||||
+ " <vars>\n"
|
||||
+ " <create name=\"$acct\" type=\"string\" value=\"12345\" />\n"
|
||||
+ " <create name=\"$retry\" type=\"integer\" value=\"0\" />\n"
|
||||
+ " </vars>\n"
|
||||
+ " <screen name=\"Screen1\" entryscreen=\"true\" pausetime=\"150\">\n"
|
||||
+ " <description>\n"
|
||||
+ " <string value=\"TSO/E LOGON\" row=\"1\" col=\"35\" />\n"
|
||||
+ " <cursor row=\"5\" col=\"10\" />\n"
|
||||
+ " </description>\n"
|
||||
+ " <actions>\n"
|
||||
+ " <input value=\"$acct + '[enter]'\" row=\"5\" col=\"10\" movecursor=\"true\" xlatehostkeys=\"true\" />\n"
|
||||
+ " <varupdate name=\"$retry\" value=\"$retry + 1\" />\n"
|
||||
+ " </actions>\n"
|
||||
+ " <nextscreens timeout=\"5000\">\n"
|
||||
+ " <nextscreen name=\"Screen2\" />\n"
|
||||
+ " </nextscreens>\n"
|
||||
+ " <recolimit value=\"3\" goto=\"ScreenError\" />\n"
|
||||
+ " </screen>\n"
|
||||
+ " <screen name=\"Screen2\" exitscreen=\"true\">\n"
|
||||
+ " <description>\n"
|
||||
+ " <oia status=\"NOTINHIBITED\" />\n"
|
||||
+ " </description>\n"
|
||||
+ " <actions>\n"
|
||||
+ " <message title=\"Done\" value=\"Success!\" />\n"
|
||||
+ " </actions>\n"
|
||||
+ " </screen>\n"
|
||||
+ "</HASCRIPT>";
|
||||
|
||||
MacroScreens screens = MacroXmlParser.parse(xml);
|
||||
|
||||
assertNotNull(screens);
|
||||
assertEquals("TestMacro", screens.getName());
|
||||
assertEquals("A unit test macro", screens.getDescription());
|
||||
assertEquals(8000, screens.getTimeout());
|
||||
assertEquals(200, screens.getPauseTime());
|
||||
assertTrue(screens.isUseVars());
|
||||
|
||||
// Check variables
|
||||
MacroVariables vars = screens.getVariables();
|
||||
assertNotNull(vars);
|
||||
assertEquals("12345", vars.getString("$acct"));
|
||||
assertEquals(0, vars.getInt("$retry"));
|
||||
|
||||
// Check screens
|
||||
assertEquals(2, screens.size());
|
||||
MacroScreen s1 = screens.getByName("Screen1");
|
||||
assertNotNull(s1);
|
||||
assertTrue(s1.isEntryScreen());
|
||||
assertEquals(150, s1.getPauseTime());
|
||||
assertEquals(3, s1.getRecoLimit());
|
||||
assertEquals("ScreenError", s1.getRecoGoto());
|
||||
|
||||
// Descriptors
|
||||
assertEquals(2, s1.getDescription().size());
|
||||
|
||||
// Actions
|
||||
assertEquals(2, s1.getActions().size());
|
||||
assertTrue(s1.getActions().get(0) instanceof MacroActionInput);
|
||||
MacroActionInput inputAct = (MacroActionInput) s1.getActions().get(0);
|
||||
assertEquals("$acct + '[enter]'", inputAct.getValue());
|
||||
assertEquals(5, inputAct.getRow());
|
||||
assertEquals(10, inputAct.getCol());
|
||||
|
||||
assertTrue(s1.getActions().get(1) instanceof MacroActionVarUpdate);
|
||||
MacroActionVarUpdate vuAct = (MacroActionVarUpdate) s1.getActions().get(1);
|
||||
assertEquals("$retry", vuAct.getName());
|
||||
assertEquals("$retry + 1", vuAct.getValue());
|
||||
|
||||
// Next screens
|
||||
assertEquals(1, s1.getNextScreens().size());
|
||||
assertTrue(s1.getNextScreens().containsName("Screen2"));
|
||||
assertEquals(5000, s1.getNextScreens().getTimeout());
|
||||
|
||||
// Screen 2
|
||||
MacroScreen s2 = screens.getByName("Screen2");
|
||||
assertNotNull(s2);
|
||||
assertTrue(s2.isExitScreen());
|
||||
assertEquals(1, s2.getActions().size());
|
||||
assertTrue(s2.getActions().get(0) instanceof MacroActionMessage);
|
||||
|
||||
// Roundtrip serialization
|
||||
String formatted = screens.format();
|
||||
assertTrue(formatted.contains("<HASCRIPT"));
|
||||
assertTrue(formatted.contains("name=\"TestMacro\""));
|
||||
assertTrue(formatted.contains("<screen name=\"Screen1\""));
|
||||
assertTrue(formatted.contains("<actions>"));
|
||||
|
||||
MacroScreens reparsed = MacroXmlParser.parse(formatted);
|
||||
assertEquals("TestMacro", reparsed.getName());
|
||||
assertEquals(2, reparsed.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("24.3: PlayThread finite state machine loop execution")
|
||||
public void testPlayThreadStateMachineExecution() throws Exception {
|
||||
// Setup initial screen in PS
|
||||
ps.SetText("TSO/E LOGON", 1, 35);
|
||||
ps.SetCursorPos(5, 10); // row 5, col 10 (1-based)
|
||||
|
||||
String xml = "<HASCRIPT name=\"FlowTest\" timeout=\"5000\" pausetime=\"50\" usevars=\"true\">\n"
|
||||
+ " <vars>\n"
|
||||
+ " <create name=\"$step\" type=\"integer\" value=\"0\" />\n"
|
||||
+ " <create name=\"$extracted\" type=\"string\" value=\"\" />\n"
|
||||
+ " </vars>\n"
|
||||
+ " <screen name=\"Logon\" entryscreen=\"true\">\n"
|
||||
+ " <description>\n"
|
||||
+ " <string value=\"TSO/E LOGON\" row=\"1\" col=\"35\" />\n"
|
||||
+ " </description>\n"
|
||||
+ " <actions>\n"
|
||||
+ " <varupdate name=\"$step\" value=\"1\" />\n"
|
||||
+ " <input value=\"USER01[enter]\" />\n"
|
||||
+ " </actions>\n"
|
||||
+ " <nextscreens timeout=\"3000\">\n"
|
||||
+ " <nextscreen name=\"Welcome\" />\n"
|
||||
+ " </nextscreens>\n"
|
||||
+ " </screen>\n"
|
||||
+ " <screen name=\"Welcome\" exitscreen=\"true\">\n"
|
||||
+ " <description>\n"
|
||||
+ " <string value=\"READY TO WORK\" row=\"2\" col=\"10\" />\n"
|
||||
+ " </description>\n"
|
||||
+ " <actions>\n"
|
||||
+ " <extract row=\"2\" col=\"10\" erow=\"2\" ecol=\"22\" variable=\"$extracted\" />\n"
|
||||
+ " <varupdate name=\"$step\" value=\"2\" />\n"
|
||||
+ " </actions>\n"
|
||||
+ " </screen>\n"
|
||||
+ "</HASCRIPT>";
|
||||
|
||||
Macro macro = new Macro(session);
|
||||
macro.load(xml);
|
||||
|
||||
AtomicReference<String> matched = new AtomicReference<>("");
|
||||
macro.addMacroListener(new Macro.MacroListener() {
|
||||
@Override public void macroStateChanged(Macro m, int oldState, int newState) {}
|
||||
@Override public void macroScreenMatching(MacroScreen screen) { matched.set(screen.getName()); }
|
||||
@Override public void macroActionExecuting(MacroAction action) {}
|
||||
@Override public void macroMessage(String title, String message) {}
|
||||
@Override public void macroError(String error) {}
|
||||
});
|
||||
|
||||
// Launch macro in background thread
|
||||
macro.play();
|
||||
|
||||
// Wait for Screen1 to match and execute actions
|
||||
Thread.sleep(200);
|
||||
assertEquals(1, macro.getVariables().getInt("$step"));
|
||||
assertEquals("Logon", matched.get());
|
||||
|
||||
// Now simulate host transition to second screen
|
||||
ps.SetText("READY TO WORK", 2, 10);
|
||||
|
||||
// Wait for Welcome screen to match and complete
|
||||
long start = System.currentTimeMillis();
|
||||
while (macro.getState() == Macro.STATE_PLAYING && System.currentTimeMillis() - start < 2000) {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
|
||||
assertEquals(Macro.STATE_STOPPED, macro.getState());
|
||||
assertEquals("Welcome", matched.get());
|
||||
assertEquals(2, macro.getVariables().getInt("$step"));
|
||||
assertEquals("READY TO WORK", macro.getVariables().getString("$extracted").trim());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("24.3: MacroActionIf conditional branching during macro execution")
|
||||
public void testMacroActionIfBranching() {
|
||||
Macro macro = new Macro(session);
|
||||
MacroVariables vars = new MacroVariables();
|
||||
vars.createVariable("$flag", "boolean", "true");
|
||||
vars.createVariable("$result", "string", "");
|
||||
macro.getScreens().setVariables(vars);
|
||||
macro.getScreens().setUseVars(true);
|
||||
|
||||
MacroActionIf ifAct = new MacroActionIf("$flag == true");
|
||||
ifAct.addIfAction(new MacroActionVarUpdate("$result", "'THEN_BRANCH'"));
|
||||
ifAct.addElseAction(new MacroActionVarUpdate("$result", "'ELSE_BRANCH'"));
|
||||
|
||||
ifAct.execute(macro, session);
|
||||
assertEquals("THEN_BRANCH", vars.getString("$result"));
|
||||
|
||||
// Switch flag and re-execute
|
||||
vars.setVariable("$flag", false);
|
||||
ifAct.execute(macro, session);
|
||||
assertEquals("ELSE_BRANCH", vars.getString("$result"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("24.3: MacroActionPrompt modal input provider")
|
||||
public void testMacroActionPrompt() {
|
||||
Macro macro = new Macro(session);
|
||||
MacroVariables vars = new MacroVariables();
|
||||
vars.createVariable("$secret", "string", "");
|
||||
macro.getScreens().setVariables(vars);
|
||||
macro.getScreens().setUseVars(true);
|
||||
|
||||
// Set mock prompt handler
|
||||
macro.setPromptHandler(prompt -> {
|
||||
assertEquals("Password Prompt", prompt.getTitle());
|
||||
return "Pass123";
|
||||
});
|
||||
|
||||
MacroActionPrompt promptAct = new MacroActionPrompt();
|
||||
promptAct.setTitle("Password Prompt");
|
||||
promptAct.setDescription("Enter Password:");
|
||||
promptAct.setEncrypted(true);
|
||||
promptAct.setVariable("$secret");
|
||||
promptAct.setDontSend(true); // Don't write to PS, only store variable
|
||||
|
||||
promptAct.execute(macro, session);
|
||||
assertEquals("Pass123", vars.getString("$secret"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("24.1 & 24.2: com.ibm.eNetwork HOD facades compatibility")
|
||||
public void testHodFacades() {
|
||||
com.ibm.eNetwork.beans.HOD.Macro hodMacro = new com.ibm.eNetwork.beans.HOD.Macro(session);
|
||||
com.ibm.eNetwork.beans.HOD.MacroScreens hodScreens = new com.ibm.eNetwork.beans.HOD.MacroScreens();
|
||||
com.ibm.eNetwork.beans.HOD.MacroScreen hodScreen = new com.ibm.eNetwork.beans.HOD.MacroScreen("TestScreen");
|
||||
hodScreens.add(hodScreen);
|
||||
hodMacro.setScreens(hodScreens);
|
||||
|
||||
assertEquals(1, hodMacro.getScreens().size());
|
||||
assertEquals("TestScreen", hodMacro.getScreens().get(0).getName());
|
||||
|
||||
com.ibm.eNetwork.ECL.macrovariable.MacroVariables hodVars = new com.ibm.eNetwork.ECL.macrovariable.MacroVariables();
|
||||
hodVars.createVariable("$test", "integer", "42");
|
||||
assertEquals(42, hodVars.getInt("$test"));
|
||||
|
||||
int res = com.ibm.eNetwork.ECL.macrovariable.MacroExpressionParser.evaluateInt(hodVars, "$test * 2");
|
||||
assertEquals(84, res);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package haus.nightmare.lib3270j.telnet;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||
import haus.nightmare.lib3270j.ConnectionState;
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.datastream.DataStreamProcessor;
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.protocol.DS3270Constants;
|
||||
import haus.nightmare.lib3270j.protocol.TelnetConstants;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static haus.nightmare.lib3270j.protocol.TelnetConstants.*;
|
||||
|
||||
/**
|
||||
* Tests verifying proper transmission of the ATTN key in accordance with
|
||||
* RFC 2355, RFC 1576, and IBM Host On-Demand parity.
|
||||
*/
|
||||
public class AttnTransmissionTest {
|
||||
|
||||
private ConnectionConfig config;
|
||||
private ScreenBuffer screenBuffer;
|
||||
private DataStreamProcessor dsProcessor;
|
||||
private InputProcessor inputProcessor;
|
||||
private TelnetFSM fsm;
|
||||
private MockConnection connection;
|
||||
private EbcdicTranslator translator;
|
||||
|
||||
private static class MockConnection extends TelnetConnection {
|
||||
final List<byte[]> sentData = new ArrayList<>();
|
||||
boolean connected = true;
|
||||
|
||||
MockConnection(ConnectionConfig config, TelnetFSM fsm) {
|
||||
super(config, fsm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void sendRaw(byte[] data) {
|
||||
sentData.add(data.clone());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void sendRaw(byte[] data, int offset, int length) {
|
||||
byte[] b = new byte[length];
|
||||
System.arraycopy(data, offset, b, 0, length);
|
||||
sentData.add(b);
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
translator = new EbcdicTranslator();
|
||||
config = new ConnectionConfig("localhost", 23, TerminalModel.IBM_3279_4);
|
||||
screenBuffer = new ScreenBuffer(TerminalModel.IBM_3279_4, translator);
|
||||
dsProcessor = new DataStreamProcessor(screenBuffer, translator);
|
||||
fsm = new TelnetFSM(config, screenBuffer, dsProcessor);
|
||||
inputProcessor = new InputProcessor(screenBuffer, translator, fsm);
|
||||
dsProcessor.setInputProcessor(inputProcessor);
|
||||
connection = new MockConnection(config, fsm);
|
||||
fsm.setConnection(connection);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttnInTn3270eBoundModeSendsRawIacIp() {
|
||||
fsm.setConnectionState(ConnectionState.CONNECTED_TN3270E);
|
||||
fsm.setTn3270eNegotiated(true);
|
||||
connection.sentData.clear();
|
||||
|
||||
inputProcessor.processAttn();
|
||||
|
||||
// Must send exactly 1 packet with 2 bytes: IAC IP (0xFF, 0xF4)
|
||||
// Must NOT escape IAC into IAC IAC (0xFF 0xFF) or add IAC EOR (0xFF 0xEF)
|
||||
assertEquals(1, connection.sentData.size(), "Should have transmitted exactly one packet");
|
||||
byte[] packet = connection.sentData.get(0);
|
||||
assertEquals(2, packet.length, "ATTN packet in TN3270E bound mode must be exactly 2 bytes (IAC IP)");
|
||||
assertArrayEquals(new byte[] { (byte) IAC, (byte) IP }, packet);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttnInStandardTn3270ModeSendsPa1AndBreak() {
|
||||
fsm.setConnectionState(ConnectionState.CONNECTED_3270);
|
||||
fsm.setTn3270eNegotiated(false);
|
||||
connection.sentData.clear();
|
||||
|
||||
inputProcessor.processAttn();
|
||||
|
||||
// HoD parity: In standard TN3270 mode, sends PA1 record + IAC BREAK:
|
||||
// { 108, -1, -17, -1, -13 } -> { 0x6C, IAC, EOR, IAC, BREAK }
|
||||
assertEquals(1, connection.sentData.size(), "Should have transmitted exactly one packet");
|
||||
byte[] packet = connection.sentData.get(0);
|
||||
assertEquals(5, packet.length, "ATTN packet in TN3270 mode must be 5 bytes (PA1 + IAC EOR + IAC BREAK)");
|
||||
assertArrayEquals(new byte[] {
|
||||
(byte) DS3270Constants.AID_PA1,
|
||||
(byte) IAC, (byte) EOR,
|
||||
(byte) IAC, (byte) BREAK
|
||||
}, packet);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttnInSscpModeDoesNotTransmitAndResetsState() {
|
||||
fsm.setConnectionState(ConnectionState.CONNECTED_SSCP);
|
||||
fsm.setTn3270eNegotiated(true);
|
||||
inputProcessor.setKeyboardLocked(true);
|
||||
connection.sentData.clear();
|
||||
|
||||
inputProcessor.processAttn();
|
||||
|
||||
// In SSCP-LU mode, host does not receive ATTN; client resets and unlocks
|
||||
assertTrue(connection.sentData.isEmpty(), "ATTN in SSCP-LU mode must not transmit to host");
|
||||
assertFalse(inputProcessor.isKeyboardLocked(), "Keyboard should be unlocked in SSCP-LU mode on ATTN");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttnInNvtModeSendsRawIacIp() {
|
||||
fsm.setConnectionState(ConnectionState.CONNECTED_NVT);
|
||||
connection.sentData.clear();
|
||||
|
||||
inputProcessor.processAttn();
|
||||
|
||||
assertEquals(1, connection.sentData.size(), "Should transmit one packet in NVT mode");
|
||||
byte[] packet = connection.sentData.get(0);
|
||||
assertArrayEquals(new byte[] { (byte) IAC, (byte) IP }, packet);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttnResetsInsertModeIfInsertOffOnAid() {
|
||||
fsm.setConnectionState(ConnectionState.CONNECTED_TN3270E);
|
||||
fsm.setTn3270eNegotiated(true);
|
||||
inputProcessor.setInsertOffOnAid(true);
|
||||
inputProcessor.setInsertMode(true);
|
||||
assertTrue(inputProcessor.isInsertMode());
|
||||
|
||||
inputProcessor.processAttn();
|
||||
|
||||
assertFalse(inputProcessor.isInsertMode(), "ATTN should reset insert mode when insertOffOnAid is enabled");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirectFsmHandleAttn() {
|
||||
fsm.setConnectionState(ConnectionState.CONNECTED_TN3270E);
|
||||
fsm.setTn3270eNegotiated(true);
|
||||
connection.sentData.clear();
|
||||
|
||||
fsm.processAttn();
|
||||
|
||||
assertEquals(1, connection.sentData.size());
|
||||
assertArrayEquals(new byte[] { (byte) IAC, (byte) IP }, connection.sentData.get(0));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user