diff options
Diffstat (limited to 'test/com')
3 files changed, 962 insertions, 0 deletions
diff --git a/test/com/isode/stroke/network/BOSHConnectionPoolTest.java b/test/com/isode/stroke/network/BOSHConnectionPoolTest.java new file mode 100644 index 0000000..27b6928 --- /dev/null +++ b/test/com/isode/stroke/network/BOSHConnectionPoolTest.java @@ -0,0 +1,509 @@ +/* Copyright (c) 2016, Isode Limited, London, England. + * All rights reserved. + * + * Acquisition and use of this software and related materials for any + * purpose requires a written license agreement from Isode Limited, + * or a written license from an organisation licensed by Isode Limited + * to grant such a license. + * + */ +package com.isode.stroke.network; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.base.SafeByteArray; +import com.isode.stroke.base.SafeString; +import com.isode.stroke.base.URL; +import com.isode.stroke.eventloop.DummyEventLoop; +import com.isode.stroke.eventloop.Event.Callback; +import com.isode.stroke.eventloop.EventLoop; +import com.isode.stroke.network.BOSHConnection.BOSHError; +import com.isode.stroke.parser.PlatformXMLParserFactory; +import com.isode.stroke.signals.Slot; +import com.isode.stroke.signals.Slot1; +import com.isode.stroke.tls.TLSOptions; + +/** + * Tests for {@link BOSHConnectionPool} + */ +public class BOSHConnectionPoolTest { + + private DummyEventLoop eventLoop = new DummyEventLoop(); + private MockConnectionFactory connectionFactory = new MockConnectionFactory(eventLoop); + private List<String> xmppDataRead = new ArrayList<String>(); + private List<String> boshDataRead = new ArrayList<String>(); + private List<String> boshDataWritten = new ArrayList<String>(); + private PlatformXMLParserFactory parserFactory = new PlatformXMLParserFactory(); + private StaticDomainNameResolver resolver = new StaticDomainNameResolver(eventLoop); + private TimerFactory timerFactory = new DummyTimerFactory(); + private String to = "wonderland.lit"; + private String path = "/http-bind"; + private String port = "5280"; + private String sid = "MyShinySID"; + private String initial = "<body wait='60' " + +"inactivity='30' " + +"polling='5' " + +"requests='2' " + +"hold='1' " + +"maxpause='120' " + +"sid='" + sid + "' " + +"ver='1.6' " + +"from='wonderland.lit' " + +"xmlns='http://jabber.org/protocol/httpbind'/>"; + private URL boshURL = new URL("http", to, 5280, path); + private long initialRID = 2349876; + private int sessionStarted = 0; + private int sessionTerminated = 0; + + @Before + public void setUp() { + resolver.addAddress(to, new HostAddress("127.0.0.1")); + } + + @Test + public void testConnectionCount_OneWrite() { + BOSHConnectionPool testling = createTestling(); + assertEquals(1,connectionFactory.connections.size()); + eventLoop.processEvents(); + assertEquals(0,sessionStarted); + readResponse(initial, connectionFactory.connections.get(0)); + assertEquals(1,sessionStarted); + assertEquals(1,connectionFactory.connections.size()); + testling.write(new SafeByteArray("<blah/>")); + eventLoop.processEvents(); + assertEquals(1,connectionFactory.connections.size()); + assertEquals(1,sessionStarted); + } + + @Test + public void testConnectionCount_TwoWrites() { + BOSHConnectionPool testling = createTestling(); + assertEquals(1,connectionFactory.connections.size()); + eventLoop.processEvents(); + readResponse(initial, connectionFactory.connections.get(0)); + eventLoop.processEvents(); + testling.write(new SafeByteArray("<blah/>")); + eventLoop.processEvents(); + assertEquals(1,connectionFactory.connections.size()); + testling.write(new SafeByteArray("<bleh/>")); + eventLoop.processEvents(); + eventLoop.processEvents(); + assertEquals(2,connectionFactory.connections.size()); + } + + @Test + public void testConnectionCount_ThreeWrites() { + BOSHConnectionPool testling = createTestling(); + assertEquals(1,connectionFactory.connections.size()); + eventLoop.processEvents(); + readResponse(initial,connectionFactory.connections.get(0)); + testling.restartStream(); + readResponse("<body/>",connectionFactory.connections.get(0)); + testling.restartStream(); + readResponse("<body/>",connectionFactory.connections.get(0)); + testling.write(new SafeByteArray("<blah/>")); + testling.write(new SafeByteArray("<bleh/>")); + testling.write(new SafeByteArray("<bluh/>")); + eventLoop.processEvents(); + assertTrue("2 < "+connectionFactory.connections.size(), + 2 >= connectionFactory.connections.size()); + } + + @Test + public void testConnectionCount_ThreeWrites_ManualConnect() { + connectionFactory.autoFinishConnect = false; + BOSHConnectionPool testling = createTestling(); + assertEquals(1,connectionFactory.connections.size()); + assertEquals(0,boshDataWritten.size()); // Connection not connected yet, can't send data + + connectionFactory.connections.get(0).onConnectFinished.emit(false); + eventLoop.processEvents(); + assertEquals(1,boshDataWritten.size()); + + readResponse(initial, connectionFactory.connections.get(0)); + eventLoop.processEvents(); + assertEquals(1,connectionFactory.connections.size()); + assertEquals(1,boshDataWritten.size()); // Don't respond to initial data with a holding call + + testling.restartStream();; + eventLoop.processEvents(); + readResponse("<body/>", connectionFactory.connections.get(0)); + eventLoop.processEvents(); + testling.restartStream(); + eventLoop.processEvents(); + + testling.write(new SafeByteArray("<blah/>")); + eventLoop.processEvents(); + assertEquals(2,connectionFactory.connections.size()); + assertEquals(3,boshDataWritten.size()); // New connection isn't up yet + + connectionFactory.connections.get(1).onConnectFinished.emit(false); + eventLoop.processEvents(); + assertEquals(4,boshDataWritten.size()); // New Connection ready + + testling.write(new SafeByteArray("<bleh/>")); + eventLoop.processEvents(); + testling.write(new SafeByteArray("<bluh/>")); + assertEquals(4,boshDataWritten.size()); // New data can't be sent, no free connections + eventLoop.processEvents(); + assertTrue("2 < "+connectionFactory.connections.size(), + 2 >= connectionFactory.connections.size()); + } + + @Test + public void testConnectionCount_ThreeWritesTwoReads() { + MockConnection c0 = null; + MockConnection c1 = null; + long rid = initialRID; + + BOSHConnectionPool testling = createTestling(); + assertEquals(1,connectionFactory.connections.size()); + c0 = connectionFactory.connections.get(0); + eventLoop.processEvents(); + assertEquals(1,boshDataWritten.size()); // header + + rid++; + readResponse(initial, c0); + assertEquals(1,boshDataWritten.size()); + assertEquals(1,connectionFactory.connections.size()); + assertFalse(c0.pending); + + rid++; + testling.restartStream(); + eventLoop.processEvents(); + readResponse("<body/>", connectionFactory.connections.get(0)); + + rid++; + testling.write(new SafeByteArray("<blah/>")); + eventLoop.processEvents(); + assertEquals(2,connectionFactory.connections.size()); // 0 was waiting for response, open and send on 1 + assertEquals(4,boshDataWritten.size()); // data + c1 = connectionFactory.connections.get(1); + String fullBody = "<body rid='" + rid + "' sid='" + sid + + "' xmlns='http://jabber.org/protocol/httpbind'><blah/></body>"; // Check empty write + assertEquals(fullBody,lastBody()); + assertTrue(c0.pending); + assertTrue(c1.pending); + + rid++; + readResponse("<body xmlns='http://jabber.org/protocol/httpbind'><message><splatploing/></message></body>", c0); // Doesn't include necessary attributes - as the support is improved this'll start to fail + eventLoop.processEvents(); + assertFalse(c0.pending); + assertTrue(c1.pending); + assertEquals(4,boshDataWritten.size()); // don't send empty in [0], still have [1] waiting + assertEquals(2,connectionFactory.connections.size()); + + rid++; + readResponse("<body xmlns='http://jabber.org/protocol/httpbind'><message><splatploing><blittlebarg/></splatploing></message></body>", c1); + eventLoop.processEvents(); + assertFalse(c1.pending); + assertTrue(c0.pending); + assertEquals(5,boshDataWritten.size()); // Empty to make room + assertEquals(2,connectionFactory.connections.size()); + + rid++; + testling.write(new SafeByteArray("<bleh/>")); + eventLoop.processEvents(); + assertTrue(c0.pending); + assertTrue(c1.pending); + assertEquals(6,boshDataWritten.size()); + + rid++; + testling.write(new SafeByteArray("<blush/>")); + assertTrue(c0.pending); + assertTrue(c1.pending); + assertEquals(6,boshDataWritten.size()); //Don't send data, no room + eventLoop.processEvents(); + assertEquals(2,connectionFactory.connections.size()); + } + + @Test + public void testSession() { + to = "prosody.doomsong.co.uk"; + resolver.addAddress("prosody.doomsong.co.uk",new HostAddress("127.0.0.1")); + path = "/http-bind/"; + boshURL = new URL("http", to, 5280, path); + + BOSHConnectionPool testling = createTestling(); + assertEquals(1,connectionFactory.connections.size()); + eventLoop.processEvents(); + assertEquals(1,boshDataWritten.size()); // header + assertEquals(1,connectionFactory.connections.size()); + + String response = "<body authid='743da605-4c2e-4de1-afac-ac040dd4a940' xmpp:version='1.0' xmlns:stream='http://etherx.jabber.org/streams' xmlns:xmpp='urn:xmpp:xbosh' inactivity='60' wait='60' polling='5' secure='true' hold='1' from='prosody.doomsong.co.uk' ver='1.6' sid='743da605-4c2e-4de1-afac-ac040dd4a940' requests='2' xmlns='http://jabber.org/protocol/httpbind'><stream:features><auth xmlns='http://jabber.org/features/iq-auth'/><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>SCRAM-SHA-1</mechanism><mechanism>DIGEST-MD5</mechanism></mechanisms></stream:features></body>"; + readResponse(response, connectionFactory.connections.get(0)); + eventLoop.processEvents(); + assertEquals(1,boshDataWritten.size()); + assertEquals(1,connectionFactory.connections.size()); + + String send = "<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" mechanism=\"SCRAM-SHA-1\">biwsbj1hZG1pbixyPWZhOWE5ZDhiLWZmMDctNGE4Yy04N2E3LTg4YWRiNDQxZGUwYg==</auth>"; + testling.write(new SafeByteArray(send)); + eventLoop.processEvents(); + assertEquals(2,boshDataWritten.size()); + assertEquals(1,connectionFactory.connections.size()); + + response = "<body xmlns='http://jabber.org/protocol/httpbind' sid='743da605-4c2e-4de1-afac-ac040dd4a940' xmlns:stream = 'http://etherx.jabber.org/streams'><challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj1mYTlhOWQ4Yi1mZjA3LTRhOGMtODdhNy04OGFkYjQ0MWRlMGJhZmZlMWNhMy1mMDJkLTQ5NzEtYjkyNS0yM2NlNWQ2MDQyMjYscz1OVGd5WkdWaFptTXRaVE15WXkwMFpXUmhMV0ZqTURRdFpqYzRNbUppWmpGa1pqWXgsaT00MDk2</challenge></body>"; + readResponse(response, connectionFactory.connections.get(0)); + eventLoop.processEvents(); + assertEquals(2,boshDataWritten.size()); + assertEquals(1,connectionFactory.connections.size()); + + send = "<response xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">Yz1iaXdzLHI9ZmE5YTlkOGItZmYwNy00YThjLTg3YTctODhhZGI0NDFkZTBiYWZmZTFjYTMtZjAyZC00OTcxLWI5MjUtMjNjZTVkNjA0MjI2LHA9aU11NWt3dDN2VWplU2RqL01Jb3VIRldkZjBnPQ==</response>"; + testling.write(new SafeByteArray(send)); + eventLoop.processEvents(); + assertEquals(3,boshDataWritten.size()); + assertEquals(1,connectionFactory.connections.size()); + + response = "<body xmlns='http://jabber.org/protocol/httpbind' sid='743da605-4c2e-4de1-afac-ac040dd4a940' xmlns:stream = 'http://etherx.jabber.org/streams'><success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj1YNmNBY3BBOWxHNjNOOXF2bVQ5S0FacERrVm89</success></body>"; + readResponse(response, connectionFactory.connections.get(0)); + eventLoop.processEvents(); + assertEquals(3,boshDataWritten.size()); + assertEquals(1,connectionFactory.connections.size()); + + testling.restartStream(); + eventLoop.processEvents(); + assertEquals(4,boshDataWritten.size()); + assertEquals(1,connectionFactory.connections.size()); + + response = "<body xmpp:version='1.0' xmlns:stream='http://etherx.jabber.org/streams' xmlns:xmpp='urn:xmpp:xbosh' inactivity='60' wait='60' polling='5' secure='true' hold='1' from='prosody.doomsong.co.uk' ver='1.6' sid='743da605-4c2e-4de1-afac-ac040dd4a940' requests='2' xmlns='http://jabber.org/protocol/httpbind'><stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><required/></bind><session xmlns='urn:ietf:params:xml:ns:xmpp-session'><optional/></session><sm xmlns='urn:xmpp:sm:2'><optional/></sm></stream:features></body>"; + readResponse(response, connectionFactory.connections.get(0)); + eventLoop.processEvents(); + assertEquals(5,boshDataWritten.size()); // Now we've authed (restarted) we should be keeping one query in flight so the server can reply to us at any time it wants. + assertEquals(1,connectionFactory.connections.size()); + + send = "<body rid='2821988967416214' sid='cf663f6b94279d4f' xmlns='http://jabber.org/protocol/httpbind'><iq id='session-bind' type='set'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>d5a9744036cd20a0</resource></bind></iq></body>"; + testling.write(new SafeByteArray(send)); + eventLoop.processEvents(); + assertEquals(6,boshDataWritten.size()); + assertEquals(2,connectionFactory.connections.size()); + } + + @Test + public void testWrite_Empty() { + MockConnection c0 = null; + + BOSHConnectionPool testling = createTestling(); + assertEquals(1,connectionFactory.connections.size()); + c0 = connectionFactory.connections.get(0); + + readResponse(initial, c0); + eventLoop.processEvents(); + assertEquals(1,boshDataWritten.size()); // Shouldn't have sent anything extra + eventLoop.processEvents(); + testling.restartStream(); + eventLoop.processEvents(); + assertEquals(2,boshDataWritten.size()); + readResponse("<body></body>",c0); + eventLoop.processEvents(); + assertEquals(3,boshDataWritten.size()); + String fullBody = "<body rid='" + (initialRID + 2) + "' sid='" + sid + "' xmlns='http://jabber.org/protocol/httpbind'></body>"; + String response = boshDataWritten.get(2); + int bodyPosition = response.indexOf("\r\n\r\n"); + assertEquals(fullBody,response.substring(bodyPosition+4)); + } + + + private static class MockConnection extends Connection { + + private final EventLoop eventLoop; + private HostAddressPort hostAddressPort; + private final List<HostAddressPort> failingPorts; + private final ByteArray dataWritten = new ByteArray(); + private boolean disconnected; + private boolean pending; + private boolean autoFinishConnect; + + private MockConnection(Collection<? extends HostAddressPort> failingPorts, + EventLoop eventLoop,boolean autoFinishConnect) { + this.eventLoop = eventLoop; + this.failingPorts = new ArrayList<HostAddressPort>(failingPorts); + disconnected = false; + pending = false; + this.autoFinishConnect = autoFinishConnect; + } + + @Override + public void listen() { + fail(); + } + + @Override + public void connect(HostAddressPort address) { + hostAddressPort = address; + final boolean fail = failingPorts.contains(address); + if (autoFinishConnect) { + eventLoop.postEvent(new Callback() { + + @Override + public void run() { + onConnectFinished.emit(fail); + } + }); + } + } + + @Override + public void disconnect() { + disconnected = true; + onDisconnected.emit(null); + } + + @Override + public void write(SafeByteArray data) { + dataWritten.append(data); + pending = true; + } + + @Override + public HostAddressPort getLocalAddress() { + return new HostAddressPort(); + } + + public HostAddressPort getRemoteAddress() { + return new HostAddressPort(); + } + + } + + private static class MockConnectionFactory implements ConnectionFactory { + + private final EventLoop eventLoop; + private List<MockConnection> connections = new ArrayList<MockConnection>(); + private List<HostAddressPort> failingPorts = new ArrayList<HostAddressPort>(); + private boolean autoFinishConnect; + + private MockConnectionFactory(EventLoop eventLoop) { + this(eventLoop,true); + } + + private MockConnectionFactory(EventLoop eventLoop,boolean autoFinishConnect) { + this.eventLoop = eventLoop; + this.autoFinishConnect = autoFinishConnect; + } + + @Override + public Connection createConnection() { + MockConnection connection = + new MockConnection(failingPorts, eventLoop, autoFinishConnect); + connections.add(connection); + return connection; + } + + } + + private BOSHConnectionPool createTestling() { + // make_shared is limited to 9 arguments; instead new is used here. + BOSHConnectionPool pool = new BOSHConnectionPool(boshURL, resolver, connectionFactory, parserFactory, + null, timerFactory, eventLoop, to, initialRID, + new URL(), new SafeString(""), + new SafeString(""), new TLSOptions()); + pool.open(); + pool.onXMPPDataRead.connect(new Slot1<SafeByteArray>() { + + @Override + public void call(SafeByteArray data) { + handleXMPPDataRead(data); + } + + }); + pool.onBOSHDataRead.connect(new Slot1<SafeByteArray>() { + + @Override + public void call(SafeByteArray data) { + handleBOSHDataRead(data); + } + }); + pool.onBOSHDataWritten.connect(new Slot1<SafeByteArray>() { + + @Override + public void call(SafeByteArray data) { + handleBOSHDataWritten(data); + } + + + }); + pool.onSessionStarted.connect(new Slot() { + + @Override + public void call() { + handleSessionStarted(); + } + + }); + pool.onSessionTerminated.connect(new Slot1<BOSHConnection.BOSHError>() { + + @Override + public void call(BOSHError error) { + handleSessionTerminated(); + } + + }); + eventLoop.processEvents(); + eventLoop.processEvents(); + return pool; + } + + private String lastBody() { + String response = boshDataWritten.get(boshDataWritten.size() - 1); + int bodyPosition = response.indexOf("\r\n\r\n"); + return response.substring(bodyPosition+4); + } + + private void handleXMPPDataRead(SafeByteArray d) { + xmppDataRead.add(d.toString()); + } + + private void handleBOSHDataRead(SafeByteArray d) { + boshDataRead.add(d.toString()); + } + + private void handleBOSHDataWritten(SafeByteArray d) { + boshDataWritten.add(d.toString()); + } + + private void handleSessionStarted() { + sessionStarted++; + } + + private void handleSessionTerminated() { + sessionTerminated++; + } + + private void readResponse(String response, MockConnection connection) { + connection.pending = false; + SafeByteArray data1 = new SafeByteArray( + "HTTP/1.1 200 OK\r\n" + +"Content-Type: text/xml; charset=utf-8\r\n" + +"Access-Control-Allow-Origin: *\r\n" + +"Access-Control-Allow-Headers: Content-Type\r\n" + +"Content-Length: "); + connection.onDataRead.emit(data1); + SafeByteArray data2 = new SafeByteArray(String.valueOf(response.length())); + connection.onDataRead.emit(data2); + SafeByteArray data3 = new SafeByteArray("\r\n\r\n"); + connection.onDataRead.emit(data3); + SafeByteArray data4 = new SafeByteArray(response); + connection.onDataRead.emit(data4); + } + + private String fullRequestFor(String data) { + String result = "POST /" + path + " HTTP/1.1\r\n" + + "Host: " + to + ":" + port + "\r\n" + + "Content-Type: text/xml; charset=utf-8\r\n" + + "Content-Length: " + data.length() + "\r\n\r\n" + + data; + return result; + } + +} diff --git a/test/com/isode/stroke/network/BoshConnectionTest.java b/test/com/isode/stroke/network/BoshConnectionTest.java new file mode 100644 index 0000000..0fa4020 --- /dev/null +++ b/test/com/isode/stroke/network/BoshConnectionTest.java @@ -0,0 +1,352 @@ +/* Copyright (c) 2016, Isode Limited, London, England. + * All rights reserved. + * + * Acquisition and use of this software and related materials for any + * purpose requires a written license agreement from Isode Limited, + * or a written license from an organisation licensed by Isode Limited + * to grant such a license. + * + */ +package com.isode.stroke.network; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.junit.After; +import org.junit.Test; + +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.base.SafeByteArray; +import com.isode.stroke.base.URL; +import com.isode.stroke.eventloop.DummyEventLoop; +import com.isode.stroke.eventloop.Event.Callback; +import com.isode.stroke.eventloop.EventLoop; +import com.isode.stroke.network.BOSHConnection.Pair; +import com.isode.stroke.parser.PlatformXMLParserFactory; +import com.isode.stroke.signals.Slot1; +import com.isode.stroke.signals.Slot2; +import com.isode.stroke.tls.TLSContextFactory; +import com.isode.stroke.tls.TLSOptions; + +/** + * Test for {@link BoshConnection} + */ +public class BoshConnectionTest { + + private final DummyEventLoop eventLoop = new DummyEventLoop(); + private final MockConnectionFactory connectionFactory = new MockConnectionFactory(eventLoop); + private boolean connectFinished = false; + private boolean connectFinishedWithError = false; + private boolean disconnected = false; + private boolean disconnectedError = false; + private final ByteArray dataRead = new ByteArray(); + private final PlatformXMLParserFactory parserFactory = new PlatformXMLParserFactory(); + private final StaticDomainNameResolver resolver = new StaticDomainNameResolver(eventLoop); + private final TimerFactory timerFactory = new DummyTimerFactory(); + private final TLSContextFactory tlsContextFactory = null; + private String sid; + + @After + public void tearDown() { + eventLoop.processEvents(); + } + + @Test + public void testHeader() { + BOSHConnection testling = createTestling(); + testling.connect(); + eventLoop.processEvents(); + testling.startStream("wonderland.lit",1); + String initial = "<body wait='60' " + +"inactivity='30' " + +"polling='5' " + +"requests='2' " + +"hold='1' " + +"maxpause='120' " + +"sid='MyShinySID' " + +"ver='1.6' " + +"from='wonderland.lit' " + +"xmlns='http://jabber.org/protocol/httpbind'/>"; + readResponse(initial, connectionFactory.connections.get(0)); + assertEquals("MyShinySID",sid); + assertTrue(testling.isReadyToSend()); + } + + @Test + public void testReadiness_ok() { + BOSHConnection testling = createTestling(); + testling.connect(); + eventLoop.processEvents(); + testling.setSID("blahhhh"); + assertTrue(testling.isReadyToSend()); + } + + @Test + public void testReadiness_pending() { + BOSHConnection testling = createTestling(); + testling.connect(); + eventLoop.processEvents(); + testling.setSID("mySID"); + assertTrue(testling.isReadyToSend()); + testling.write(new SafeByteArray("<mypayload/>")); + assertFalse(testling.isReadyToSend()); + readResponse("<body><blah/></body>", connectionFactory.connections.get(0)); + assertTrue(testling.isReadyToSend()); + } + + @Test + public void testReadiness_disconnect() { + BOSHConnection testling = createTestling(); + testling.connect(); + eventLoop.processEvents(); + testling.setSID("mySID"); + assertTrue(testling.isReadyToSend()); + connectionFactory.connections.get(0).onDisconnected.emit(null); + assertFalse(testling.isReadyToSend()); + } + + @Test + public void testReadiness_noSID() { + BOSHConnection testling = createTestling(); + testling.connect(); + eventLoop.processEvents(); + assertFalse(testling.isReadyToSend()); + } + + @Test + public void testWrite_Receive() { + BOSHConnection testling = createTestling(); + testling.connect(); + eventLoop.processEvents(); + testling.setSID("mySID"); + testling.write(new SafeByteArray("<mypayload/>")); + readResponse("<body><blah/></body>", connectionFactory.connections.get(0)); + assertEquals("<blah/>",dataRead.toString()); + } + + @Test + public void testWrite_ReceiveTwice() { + BOSHConnection testling = createTestling(); + testling.connect(); + eventLoop.processEvents(); + testling.setSID("mySID"); + testling.write(new SafeByteArray("<mypayload/>")); + readResponse("<body><blah/></body>", connectionFactory.connections.get(0)); + assertEquals("<blah/>",dataRead.toString()); + dataRead.clear(); + testling.write(new SafeByteArray("<mypayload2/>")); + readResponse("<body><bleh/></body>", connectionFactory.connections.get(0)); + assertEquals("<bleh/>",dataRead.toString()); + } + + @Test + public void testRead_Fragment() { + BOSHConnection testling = createTestling(); + testling.connect(); + eventLoop.processEvents(); + assertEquals(1, connectionFactory.connections.size()); + MockConnection connection = connectionFactory.connections.get(0); + SafeByteArray data1 = new SafeByteArray( + "HTTP/1.1 200 OK\r\n"+ + "Content-Type: text/xml; charset=utf-8\r\n"+ + "Access-Control-Allow-Origin: *\r\n"+ + "Access-Control-Allow-Headers: Content-Type\r\n"+ + "Content-Length: 64\r\n"); + SafeByteArray data2 = new SafeByteArray( + "\r\n<body xmlns='http://jabber.org/protocol/httpbind'>"+ + "<bl"); + SafeByteArray data3 = new SafeByteArray( + "ah/>"+ + "</body>"); + connection.onDataRead.emit(data1); + connection.onDataRead.emit(data2); + assertTrue(dataRead.isEmpty()); + connection.onDataRead.emit(data3); + assertEquals("<blah/>",dataRead.toString()); + } + + @Test + public void testHTTPRequest() { + String data = "<blah/>"; + String sid = "wigglebloom"; + String fullBody = "<body xmlns='http://jabber.org/protocol/httpbind' sid='" + sid + "' rid='20'>" + data + "</body>"; + Pair<SafeByteArray, Integer> http = + BOSHConnection.createHTTPRequest(new SafeByteArray(data), false, false, + 20, sid, new URL()); + assertEquals(fullBody.length(),http.second.intValue()); + } + + @Test + public void testHTTPRequest_Empty() { + String data = ""; + String sid = "wigglebloomsickle"; + String fullBody = "<body rid='42' sid='" + sid + "' xmlns='http://jabber.org/protocol/httpbind'>" + data + "</body>"; + Pair<SafeByteArray, Integer> http = + BOSHConnection.createHTTPRequest(new SafeByteArray(data), false, false, + 42, sid, new URL()); + assertEquals(fullBody.length(),http.second.intValue()); + String response = http.first.toString(); + int bodyPosition = response.indexOf("\r\n\r\n"); + assertFalse("bodyPosition is equal to -1",-1 == bodyPosition); + assertEquals(fullBody,response.substring(bodyPosition+4)); + } + + private BOSHConnection createTestling() { + resolver.addAddress("wonderland.lit", new HostAddress("127.0.0.1")); + Connector connector = Connector.create("wonderland.lit", 5280, null, resolver, connectionFactory, timerFactory); + BOSHConnection connection = BOSHConnection.create(new URL(), connector, parserFactory, + tlsContextFactory, new TLSOptions()); + connection.onConnectionFinished.connect(new Slot1<Boolean>() { + + @Override + public void call(Boolean hadError) { + handleConnectFinished(hadError.booleanValue()); + } + + }); + connection.onDisconnected.connect(new Slot1<Boolean>() { + + @Override + public void call(Boolean hadError) { + handleDisconnected(hadError.booleanValue()); + } + + }); + connection.onXMPPDataRead.connect(new Slot1<SafeByteArray>() { + + @Override + public void call(SafeByteArray p1) { + handleDataRead(p1); + } + + }); + connection.onSessionStarted.connect(new Slot2<String, Integer>() { + + @Override + public void call(String sid, Integer requests) { + handleSID(sid); + } + + }); + connection.setRID(42); + return connection; + } + + private void handleConnectFinished(boolean hadError) { + connectFinished = true; + connectFinishedWithError = hadError; + } + + private void handleDisconnected(boolean hadError) { + disconnected = true; + disconnectedError = hadError; + } + + private void handleDataRead(SafeByteArray data) { + dataRead.append(data); + } + + private void handleSID(String s) { + sid = s; + } + + private void readResponse(String response,MockConnection connection) { + SafeByteArray data1 = new SafeByteArray( + "HTTP/1.1 200 OK\r\n"+ + "Content-Type: text/xml; charset=utf-8\r\n"+ + "Access-Control-Allow-Origin: *\r\n"+ + "Access-Control-Allow-Headers: Content-Type\r\n"+ + "Content-Length: " + ); + connection.onDataRead.emit(data1); + SafeByteArray data2 = new SafeByteArray(Integer.toString(response.length())); + connection.onDataRead.emit(data2); + SafeByteArray data3 = new SafeByteArray("\r\n\r\n"); + connection.onDataRead.emit(data3); + SafeByteArray data4 = new SafeByteArray(response); + connection.onDataRead.emit(data4); + } + + private static class MockConnection extends Connection { + + public MockConnection(Collection<HostAddressPort> failingPorts, + EventLoop eventLoop) { + this.failingPorts = new ArrayList<HostAddressPort>(failingPorts); + this.eventLoop = eventLoop; + } + + @Override + public void listen() { + fail(); + } + + @Override + public void connect(HostAddressPort address) { + hostAddressPort = address; + final boolean fail = failingPorts.contains(address); + eventLoop.postEvent(new Callback() { + + @Override + public void run() { + onConnectFinished.emit(fail); + } + + }); + } + + @Override + public void disconnect() { + disconnected = true; + onDisconnected.emit(null); + } + + @Override + public void write(SafeByteArray data) { + dataWritten.append(data); + } + + /* (non-Javadoc) + * @see com.isode.stroke.network.Connection#getLocalAddress() + */ + @Override + public HostAddressPort getLocalAddress() { + return new HostAddressPort(); + } + + public HostAddressPort getRemoteAddress() { + return new HostAddressPort(); + } + + private final EventLoop eventLoop; + private HostAddressPort hostAddressPort; + private final List<HostAddressPort> failingPorts; + private final ByteArray dataWritten = new ByteArray(); + private boolean disconnected; + + } + + private static class MockConnectionFactory implements ConnectionFactory { + + public MockConnectionFactory(EventLoop eventLoop) { + this.eventLoop = eventLoop; + } + + @Override + public Connection createConnection() { + MockConnection connection = new MockConnection(failingPorts, eventLoop); + connections.add(connection); + return connection; + } + + private final EventLoop eventLoop; + private List<MockConnection> connections = new ArrayList<MockConnection>(); + private List<HostAddressPort> failingPorts = new ArrayList<HostAddressPort>(); + } + +} diff --git a/test/com/isode/stroke/parser/BOSHBodyExtractorTest.java b/test/com/isode/stroke/parser/BOSHBodyExtractorTest.java new file mode 100644 index 0000000..e8f0db5 --- /dev/null +++ b/test/com/isode/stroke/parser/BOSHBodyExtractorTest.java @@ -0,0 +1,101 @@ +/* Copyright (c) 2016, Isode Limited, London, England. + * All rights reserved. + * + * Acquisition and use of this software and related materials for any + * purpose requires a written license agreement from Isode Limited, + * or a written license from an organisation licensed by Isode Limited + * to grant such a license. + * + */ +package com.isode.stroke.parser; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.isode.stroke.base.ByteArray; + +/** + * Tests for {@link BOSHBodyExtractor} + */ +public class BOSHBodyExtractorTest { + + private final PlatformXMLParserFactory parserFactory = new PlatformXMLParserFactory(); + + @Test + public void testGetBody() { + ByteArray data = new ByteArray("<body a1='a\"1' a2=\"a'2\" boo='bar' >" + +"foo <message> <body> bar" + +"</body > "); + BOSHBodyExtractor testling = new BOSHBodyExtractor(parserFactory, data); + assertNotNull(testling.getBody()); + assertEquals("a\"1",testling.getBody().getAttributes().getAttribute("a1")); + assertEquals("foo <message> <body> bar",testling.getBody().getContent()); + } + + @Test + public void testGetBody_EmptyContent() { + ByteArray data = new ByteArray("<body foo='bar'/>"); + BOSHBodyExtractor testling = new BOSHBodyExtractor(parserFactory, data); + + assertNotNull(testling.getBody()); + assertEquals("bar",testling.getBody().getAttributes().getAttribute("foo")); + assertTrue(testling.getBody().getContent().isEmpty()); + } + + @Test + public void testGetBody_EmptyContent2() { + ByteArray data = new ByteArray("<body foo='bar'></body>"); + BOSHBodyExtractor testling = new BOSHBodyExtractor(parserFactory, data); + + assertNotNull(testling.getBody()); + assertEquals("bar",testling.getBody().getAttributes().getAttribute("foo")); + assertTrue(testling.getBody().getContent().isEmpty()); + } + + @Test + public void testGetBody_EmptyElementEmptyContent() { + ByteArray data = new ByteArray("<body/>"); + BOSHBodyExtractor testling = new BOSHBodyExtractor(parserFactory, data); + assertNotNull(testling.getBody()); + } + + @Test + public void testGetBody_InvalidStartTag() { + ByteArray data = new ByteArray("<bodi></body>"); + BOSHBodyExtractor testling = new BOSHBodyExtractor(parserFactory, data); + assertNull(testling.getBody()); + } + + @Test + public void testGetBody_InvalidStartTag2() { + ByteArray data = new ByteArray("<bodyy></body>"); + BOSHBodyExtractor testling = new BOSHBodyExtractor(parserFactory, data); + assertNull(testling.getBody()); + } + + @Test + public void testGetBody_IncompleteStartTag() { + ByteArray data = new ByteArray("<body"); + BOSHBodyExtractor testling = new BOSHBodyExtractor(parserFactory, data); + assertNull(testling.getBody()); + } + + @Test + public void testGetBody_InvalidEndTag() { + ByteArray data = new ByteArray("<body></bodi>"); + BOSHBodyExtractor testling = new BOSHBodyExtractor(parserFactory, data); + assertNull(testling.getBody()); + } + + @Test + public void testGetBody_InvalidEndTag2() { + ByteArray data = new ByteArray("<body><b/body>"); + BOSHBodyExtractor testling = new BOSHBodyExtractor(parserFactory, data); + assertNull(testling.getBody()); + } + +} |