libreoffice-online/test/httpwstest.cpp
2016-11-25 09:58:57 +00:00

2145 lines
82 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include "config.h"
#include <algorithm>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <regex>
#include <vector>
#include <Poco/Dynamic/Var.h>
#include <Poco/FileStream.h>
#include <Poco/JSON/JSON.h>
#include <Poco/JSON/Parser.h>
#include <Poco/Net/AcceptCertificateHandler.h>
#include <Poco/Net/HTTPClientSession.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/Net/HTTPSClientSession.h>
#include <Poco/Net/InvalidCertificateHandler.h>
#include <Poco/Net/NetException.h>
#include <Poco/Net/PrivateKeyPassphraseHandler.h>
#include <Poco/Net/SSLManager.h>
#include <Poco/Net/Socket.h>
#include <Poco/Path.h>
#include <Poco/RegularExpression.h>
#include <Poco/StreamCopier.h>
#include <Poco/StringTokenizer.h>
#include <Poco/Thread.h>
#include <Poco/URI.h>
#include <cppunit/extensions/HelperMacros.h>
#include "Common.hpp"
#include "Protocol.hpp"
#include <LOOLWebSocket.hpp>
#include "Png.hpp"
#include "UserMessages.hpp"
#include "Util.hpp"
#include "countloolkits.hpp"
#include "helpers.hpp"
using namespace helpers;
/// Tests the HTTP WebSocket API of loolwsd. The server has to be started manually before running this test.
class HTTPWSTest : public CPPUNIT_NS::TestFixture
{
const Poco::URI _uri;
Poco::Net::HTTPResponse _response;
static int InitialLoolKitCount;
CPPUNIT_TEST_SUITE(HTTPWSTest);
CPPUNIT_TEST(testBadRequest);
CPPUNIT_TEST(testHandShake);
CPPUNIT_TEST(testCloseAfterClose);
CPPUNIT_TEST(testConnectNoLoad); // This fails most of the times but occasionally succeeds
CPPUNIT_TEST(testLoad);
CPPUNIT_TEST(testBadLoad);
CPPUNIT_TEST(testReload);
CPPUNIT_TEST(testGetTextSelection);
CPPUNIT_TEST(testSaveOnDisconnect);
CPPUNIT_TEST(testReloadWhileDisconnecting);
CPPUNIT_TEST(testExcelLoad);
CPPUNIT_TEST(testPaste);
CPPUNIT_TEST(testLargePaste);
CPPUNIT_TEST(testRenderingOptions);
CPPUNIT_TEST(testPasswordProtectedDocumentWithoutPassword);
CPPUNIT_TEST(testPasswordProtectedDocumentWithWrongPassword);
CPPUNIT_TEST(testPasswordProtectedDocumentWithCorrectPassword);
CPPUNIT_TEST(testPasswordProtectedDocumentWithCorrectPasswordAgain);
CPPUNIT_TEST(testInsertDelete);
CPPUNIT_TEST(testSlideShow);
CPPUNIT_TEST(testInactiveClient);
CPPUNIT_TEST(testMaxColumn);
CPPUNIT_TEST(testMaxRow);
CPPUNIT_TEST(testInsertAnnotationWriter);
CPPUNIT_TEST(testEditAnnotationWriter);
CPPUNIT_TEST(testInsertAnnotationCalc);
CPPUNIT_TEST(testCalcEditRendering);
CPPUNIT_TEST(testFontList);
CPPUNIT_TEST(testStateUnoCommand);
CPPUNIT_TEST(testColumnRowResize);
CPPUNIT_TEST(testOptimalResize);
CPPUNIT_TEST(testInvalidateViewCursor);
CPPUNIT_TEST(testViewCursorVisible);
CPPUNIT_TEST(testCellViewCursor);
CPPUNIT_TEST(testGraphicViewSelection);
CPPUNIT_TEST(testGraphicInvalidate);
CPPUNIT_TEST(testCursorPosition);
CPPUNIT_TEST(testAlertAllUsers);
CPPUNIT_TEST(testViewInfoMsg);
CPPUNIT_TEST_SUITE_END();
void testCountHowManyLoolkits();
void testBadRequest();
void testHandShake();
void testCloseAfterClose();
void testConnectNoLoad();
void testLoad();
void testBadLoad();
void testReload();
void testGetTextSelection();
void testSaveOnDisconnect();
void testReloadWhileDisconnecting();
void testExcelLoad();
void testPaste();
void testLargePaste();
void testRenderingOptions();
void testPasswordProtectedDocumentWithoutPassword();
void testPasswordProtectedDocumentWithWrongPassword();
void testPasswordProtectedDocumentWithCorrectPassword();
void testPasswordProtectedDocumentWithCorrectPasswordAgain();
void testInsertDelete();
void testNoExtraLoolKitsLeft();
void testSlideShow();
void testInactiveClient();
void testMaxColumn();
void testMaxRow();
void testInsertAnnotationWriter();
void testEditAnnotationWriter();
void testInsertAnnotationCalc();
void testCalcEditRendering();
void testFontList();
void testStateUnoCommand();
void testColumnRowResize();
void testOptimalResize();
void testInvalidateViewCursor();
void testViewCursorVisible();
void testCellViewCursor();
void testGraphicViewSelection();
void testGraphicInvalidate();
void testCursorPosition();
void testAlertAllUsers();
void testViewInfoMsg();
void loadDoc(const std::string& documentURL, const std::string& testname);
void getPartHashCodes(const std::string response,
std::vector<std::string>& parts);
void getCursor(const std::string& message,
int& cursorX,
int& cursorY,
int& cursorWidth,
int& cursorHeight);
void limitCursor(std::function<void(const std::shared_ptr<LOOLWebSocket>& socket,
int cursorX, int cursorY,
int cursorWidth, int cursorHeight,
int docWidth, int docHeight)> keyhandler,
std::function<void(int docWidth, int docHeight,
int newWidth, int newHeight)> checkhandler,
const std::string& testname);
std::string getFontList(const std::string& message);
void testStateChanged(const std::string& filename, std::vector<std::string>& vecComands);
double getColRowSize(const std::string& property, const std::string& message, int index);
double getColRowSize(const std::shared_ptr<LOOLWebSocket>& socket, const std::string& item, int index);
void testEachView(const std::string& doc, const std::string& type, const std::string& protocol, const std::string& view, const std::string& testname);
public:
HTTPWSTest()
: _uri(helpers::getTestServerURI())
{
#if ENABLE_SSL
Poco::Net::initializeSSL();
// Just accept the certificate anyway for testing purposes
Poco::SharedPtr<Poco::Net::InvalidCertificateHandler> invalidCertHandler = new Poco::Net::AcceptCertificateHandler(false);
Poco::Net::Context::Params sslParams;
Poco::Net::Context::Ptr sslContext = new Poco::Net::Context(Poco::Net::Context::CLIENT_USE, sslParams);
Poco::Net::SSLManager::instance().initializeClient(0, invalidCertHandler, sslContext);
#endif
}
#if ENABLE_SSL
~HTTPWSTest()
{
Poco::Net::uninitializeSSL();
}
#endif
void setUp()
{
testCountHowManyLoolkits();
}
void tearDown()
{
testNoExtraLoolKitsLeft();
}
};
int HTTPWSTest::InitialLoolKitCount = 1;
void HTTPWSTest::testCountHowManyLoolkits()
{
InitialLoolKitCount = countLoolKitProcesses(InitialLoolKitCount);
CPPUNIT_ASSERT(InitialLoolKitCount > 0);
}
void HTTPWSTest::testBadRequest()
{
try
{
// Try to load a fake document and get its status.
const std::string documentURL = "/lool/file%3A%2F%2F%2Ffake.doc/ws";
Poco::Net::HTTPResponse response;
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
std::unique_ptr<Poco::Net::HTTPClientSession> session(helpers::createSession(_uri));
// This should result in Bad Request, but results in:
// WebSocket Exception: Missing Sec-WebSocket-Key in handshake request
// So Service Unavailable is returned.
request.set("Connection", "Upgrade");
request.set("Upgrade", "websocket");
request.set("Sec-WebSocket-Version", "13");
request.set("Sec-WebSocket-Key", "");
request.setChunkedTransferEncoding(false);
session->setKeepAlive(true);
session->sendRequest(request);
session->receiveResponse(response);
CPPUNIT_ASSERT_EQUAL(Poco::Net::HTTPResponse::HTTPResponse::HTTP_SERVICE_UNAVAILABLE, response.getStatus());
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testHandShake()
{
try
{
std::string documentPath, documentURL;
getDocumentPathAndURL("hello.odt", documentPath, documentURL);
// NOTE: Do not replace with wrappers. This has to be explicit.
Poco::Net::HTTPResponse response;
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
std::unique_ptr<Poco::Net::HTTPClientSession> session(helpers::createSession(_uri));
LOOLWebSocket socket(*session, request, response);
socket.setReceiveTimeout(0);
int flags = 0;
char buffer[1024] = {0};
int bytes = socket.receiveFrame(buffer, sizeof(buffer), flags);
CPPUNIT_ASSERT_EQUAL(std::string("statusindicator: find"), std::string(buffer, bytes));
bytes = socket.receiveFrame(buffer, sizeof(buffer), flags);
if (bytes > 0 && !std::strstr(buffer, "error:"))
{
CPPUNIT_ASSERT_EQUAL(std::string("statusindicator: connect"), std::string(buffer, bytes));
bytes = socket.receiveFrame(buffer, sizeof(buffer), flags);
if (!std::strstr(buffer, "error:"))
{
CPPUNIT_ASSERT_EQUAL(std::string("statusindicator: ready"), std::string(buffer, bytes));
}
else
{
// check error message
CPPUNIT_ASSERT(std::strstr(SERVICE_UNAVAILABLE_INTERNAL_ERROR, buffer) != nullptr);
// close frame message
bytes = socket.receiveFrame(buffer, sizeof(buffer), flags);
CPPUNIT_ASSERT((flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) == Poco::Net::WebSocket::FRAME_OP_CLOSE);
}
}
else
{
// check error message
CPPUNIT_ASSERT(std::strstr(SERVICE_UNAVAILABLE_INTERNAL_ERROR, buffer) != nullptr);
// close frame message
bytes = socket.receiveFrame(buffer, sizeof(buffer), flags);
CPPUNIT_ASSERT((flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) == Poco::Net::WebSocket::FRAME_OP_CLOSE);
}
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testCloseAfterClose()
{
const auto testname = "closeAfterClose ";
try
{
auto socket = loadDocAndGetSocket("hello.odt", _uri, testname);
// send normal socket shutdown
socket->shutdown();
// 5 seconds timeout
socket->setReceiveTimeout(5000000);
// receive close frame handshake
int bytes;
int flags;
char buffer[READ_BUFFER_SIZE];
do
{
bytes = socket->receiveFrame(buffer, sizeof(buffer), flags);
}
while (bytes && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE);
// no more messages is received.
bytes = socket->receiveFrame(buffer, sizeof(buffer), flags);
std::cerr << "Received " << bytes << " bytes, flags: "<< std::hex << flags << std::dec << std::endl;
CPPUNIT_ASSERT_EQUAL(0, bytes);
CPPUNIT_ASSERT_EQUAL(0, flags);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::loadDoc(const std::string& documentURL, const std::string& testname)
{
try
{
// Load a document and wait for the status.
// Don't replace with helpers, so we catch status.
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
auto socket = connectLOKit(_uri, request, _response, testname);
sendTextFrame(socket, "load url=" + documentURL, testname);
assertResponseString(socket, "status:", testname);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testConnectNoLoad()
{
std::string documentPath, documentURL;
getDocumentPathAndURL("hello.odt", documentPath, documentURL);
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
auto socket = connectLOKit(_uri, request, _response);
CPPUNIT_ASSERT_MESSAGE("Failed to connect.", socket);
socket.reset();
// Connect and load first view.
auto socket1 = connectLOKit(_uri, request, _response);
CPPUNIT_ASSERT_MESSAGE("Failed to connect.", socket1);
sendTextFrame(socket1, "load url=" + documentURL);
CPPUNIT_ASSERT_MESSAGE("cannot load the document " + documentURL, isDocumentLoaded(socket1));
// Connect but don't load second view.
auto socket2 = connectLOKit(_uri, request, _response);
CPPUNIT_ASSERT_MESSAGE("Failed to connect.", socket2);
socket2.reset();
sendTextFrame(socket1, "status");
assertResponseString(socket1, "status:");
}
void HTTPWSTest::testLoad()
{
std::string documentPath, documentURL;
getDocumentPathAndURL("hello.odt", documentPath, documentURL);
loadDoc(documentURL, "load ");
}
void HTTPWSTest::testBadLoad()
{
try
{
// Load a document and get its status.
std::string documentPath, documentURL;
getDocumentPathAndURL("hello.odt", documentPath, documentURL);
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
auto socket = connectLOKit(_uri, request, _response);
// Before loading request status.
sendTextFrame(socket, "status");
const auto line = assertResponseString(socket, "error:");
CPPUNIT_ASSERT_EQUAL(std::string("error: cmd=status kind=nodocloaded"), line);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testReload()
{
std::string documentPath, documentURL;
getDocumentPathAndURL("hello.odt", documentPath, documentURL);
for (auto i = 0; i < 3; ++i)
{
loadDoc(documentURL, "reload ");
}
}
void HTTPWSTest::testGetTextSelection()
{
const auto testname = "getTextSelection ";
try
{
std::string documentPath, documentURL;
getDocumentPathAndURL("hello.odt", documentPath, documentURL);
auto socket = loadDocAndGetSocket(_uri, documentURL, testname);
auto socket2 = loadDocAndGetSocket(_uri, documentURL, testname);
sendTextFrame(socket, "uno .uno:SelectAll", testname);
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8", testname);
const auto selection = assertResponseString(socket, "textselectioncontent:", testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: Hello world"), selection);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testSaveOnDisconnect()
{
const auto testname = "saveOnDisconnect ";
const auto text = helpers::genRandomString(40);
std::cerr << "Test string: [" << text << "]." << std::endl;
std::string documentPath, documentURL;
getDocumentPathAndURL("hello.odt", documentPath, documentURL);
int kitcount = -1;
try
{
auto socket = loadDocAndGetSocket(_uri, documentURL, testname);
auto socket2 = loadDocAndGetSocket(_uri, documentURL, testname);
sendTextFrame(socket2, "userinactive");
sendTextFrame(socket, "uno .uno:SelectAll", testname);
sendTextFrame(socket, "uno .uno:Delete", testname);
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\n" + text, testname);
// Check if the document contains the pasted text.
sendTextFrame(socket, "uno .uno:SelectAll", testname);
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8", testname);
const auto selection = assertResponseString(socket, "textselectioncontent:", testname);
CPPUNIT_ASSERT_EQUAL("textselectioncontent: " + text, selection);
// Closing connection too fast might not flush buffers.
// Often nothing more than the SelectAll reaches the server before
// the socket is closed, when the doc is not even modified yet.
getResponseMessage(socket, "statechanged", testname);
kitcount = getLoolKitProcessCount();
// Shutdown abruptly.
std::cerr << "Closing connection after pasting." << std::endl;
socket->shutdown();
socket2->shutdown();
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
// Allow time to save and destroy before we connect again.
testNoExtraLoolKitsLeft();
std::cerr << "Loading again." << std::endl;
try
{
// Load the same document and check that the last changes (pasted text) is saved.
auto socket = loadDocAndGetSocket(_uri, documentURL, testname);
// Should have no new instances.
CPPUNIT_ASSERT_EQUAL(kitcount, countLoolKitProcesses(kitcount));
// Check if the document contains the pasted text.
sendTextFrame(socket, "uno .uno:SelectAll", testname);
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8", testname);
const auto selection = assertResponseString(socket, "textselectioncontent:", testname);
CPPUNIT_ASSERT_EQUAL("textselectioncontent: " + text, selection);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testReloadWhileDisconnecting()
{
const auto testname = "reloadWhileDisconnecting ";
try
{
std::string documentPath, documentURL;
getDocumentPathAndURL("hello.odt", documentPath, documentURL);
auto socket = loadDocAndGetSocket(_uri, documentURL, testname);
sendTextFrame(socket, "uno .uno:SelectAll", testname);
sendTextFrame(socket, "uno .uno:Delete", testname);
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\naaa bbb ccc", testname);
// Closing connection too fast might not flush buffers.
// Often nothing more than the SelectAll reaches the server before
// the socket is closed, when the doc is not even modified yet.
getResponseMessage(socket, "statechanged", testname);
const auto kitcount = getLoolKitProcessCount();
// Shutdown abruptly.
std::cerr << "Closing connection after pasting." << std::endl;
socket->shutdown();
// Load the same document and check that the last changes (pasted text) is saved.
std::cerr << "Loading again." << std::endl;
socket = loadDocAndGetSocket(_uri, documentURL, testname);
// Should have no new instances.
CPPUNIT_ASSERT_EQUAL(kitcount, countLoolKitProcesses(kitcount));
// Check if the document contains the pasted text.
sendTextFrame(socket, "uno .uno:SelectAll", testname);
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8", testname);
const auto selection = assertResponseString(socket, "textselectioncontent:", testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: aaa bbb ccc"), selection);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testExcelLoad()
{
const auto testname = "excelLoad ";
try
{
// Load a document and get status.
auto socket = loadDocAndGetSocket("timeline.xlsx", _uri, testname);
sendTextFrame(socket, "status", testname);
const auto status = assertResponseString(socket, "status:", testname);
// Expected format is something like 'status: type=text parts=2 current=0 width=12808 height=1142'.
Poco::StringTokenizer tokens(status, " ", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM);
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(8), tokens.count());
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testPaste()
{
const auto testname = "paste ";
try
{
// Load a document and make it empty, then paste some text into it.
auto socket = loadDocAndGetSocket("hello.odt", _uri, testname);
sendTextFrame(socket, "uno .uno:SelectAll", testname);
sendTextFrame(socket, "uno .uno:Delete", testname);
// Paste some text into it.
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\naaa bbb ccc", testname);
// Check if the document contains the pasted text.
sendTextFrame(socket, "uno .uno:SelectAll", testname);
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8", testname);
const auto selection = assertResponseString(socket, "textselectioncontent:", testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: aaa bbb ccc"), selection);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testLargePaste()
{
const auto testname = "LargePaste ";
try
{
// Load a document and make it empty, then paste some text into it.
auto socket = loadDocAndGetSocket("hello.odt", _uri, testname);
sendTextFrame(socket, "uno .uno:SelectAll", testname);
sendTextFrame(socket, "uno .uno:Delete", testname);
// Paste some text into it.
std::ostringstream oss;
for (auto i = 0; i < 1000; ++i)
{
oss << Util::encodeId(Util::rng::getNext(), 6);
}
const auto documentContents = oss.str();
std::cerr << "Pasting " << documentContents.size() << " characters into document." << std::endl;
sendTextFrame(socket, "paste mimetype=text/html\n" + documentContents, testname);
// Check if the server is still alive.
// This resulted first in a hang, as respose for the message never arrived, then a bit later in a Poco::TimeoutException.
sendTextFrame(socket, "uno .uno:SelectAll", testname);
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8", testname);
const auto selection = assertResponseString(socket, "textselectioncontent:", testname);
CPPUNIT_ASSERT_MESSAGE("Pasted text was either corrupted or couldn't be read back",
"textselectioncontent: " + documentContents == selection);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testRenderingOptions()
{
try
{
// Load a document and get its size.
std::string documentPath, documentURL;
getDocumentPathAndURL("hide-whitespace.odt", documentPath, documentURL);
const std::string options = "{\"rendering\":{\".uno:HideWhitespace\":{\"type\":\"boolean\",\"value\":\"true\"}}}";
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
auto socket = connectLOKit(_uri, request, _response);
sendTextFrame(socket, "load url=" + documentURL + " options=" + options);
sendTextFrame(socket, "status");
const auto status = assertResponseString(socket, "status:");
// Expected format is something like 'status: type=text parts=2 current=0 width=12808 height=1142'.
Poco::StringTokenizer tokens(status, " ", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM);
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(7), tokens.count());
const std::string token = tokens[5];
const std::string prefix = "height=";
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), token.find(prefix));
const int height = std::stoi(token.substr(prefix.size()));
// HideWhitespace was ignored, this was 32532, should be around 16706.
CPPUNIT_ASSERT(height < 20000);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testPasswordProtectedDocumentWithoutPassword()
{
const auto testname = "passwordProtectedDocumentWithoutPassword ";
try
{
std::string documentPath, documentURL;
getDocumentPathAndURL("password-protected.ods", documentPath, documentURL);
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
auto socket = connectLOKit(_uri, request, _response);
// Send a load request without password first
sendTextFrame(socket, "load url=" + documentURL);
const auto response = getResponseString(socket, "error:", testname);
Poco::StringTokenizer tokens(response, " ", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM);
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), tokens.count());
std::string errorCommand;
std::string errorKind;
LOOLProtocol::getTokenString(tokens[1], "cmd", errorCommand);
LOOLProtocol::getTokenString(tokens[2], "kind", errorKind);
CPPUNIT_ASSERT_EQUAL(std::string("load"), errorCommand);
CPPUNIT_ASSERT_EQUAL(std::string("passwordrequired:to-view"), errorKind);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testPasswordProtectedDocumentWithWrongPassword()
{
const auto testname = "passwordProtectedDocumentWithWrongPassword ";
try
{
std::string documentPath, documentURL;
getDocumentPathAndURL("password-protected.ods", documentPath, documentURL);
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
auto socket = connectLOKit(_uri, request, _response);
// Send a load request with incorrect password
sendTextFrame(socket, "load url=" + documentURL + " password=2");
const auto response = getResponseString(socket, "error:", testname);
Poco::StringTokenizer tokens(response, " ", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM);
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), tokens.count());
std::string errorCommand;
std::string errorKind;
LOOLProtocol::getTokenString(tokens[1], "cmd", errorCommand);
LOOLProtocol::getTokenString(tokens[2], "kind", errorKind);
CPPUNIT_ASSERT_EQUAL(std::string("load"), errorCommand);
CPPUNIT_ASSERT_EQUAL(std::string("wrongpassword"), errorKind);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testPasswordProtectedDocumentWithCorrectPassword()
{
try
{
std::string documentPath, documentURL;
getDocumentPathAndURL("password-protected.ods", documentPath, documentURL);
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
auto socket = connectLOKit(_uri, request, _response);
// Send a load request with correct password
sendTextFrame(socket, "load url=" + documentURL + " password=1");
CPPUNIT_ASSERT_MESSAGE("cannot load the document with correct password " + documentURL, isDocumentLoaded(socket));
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testPasswordProtectedDocumentWithCorrectPasswordAgain()
{
testPasswordProtectedDocumentWithCorrectPassword();
}
void HTTPWSTest::testInsertDelete()
{
try
{
std::vector<std::string> parts;
std::string response;
// Load a document
std::string documentPath, documentURL;
getDocumentPathAndURL("insert-delete.odp", documentPath, documentURL);
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
auto socket = connectLOKit(_uri, request, _response);
sendTextFrame(socket, "load url=" + documentURL);
CPPUNIT_ASSERT_MESSAGE("cannot load the document " + documentURL, isDocumentLoaded(socket));
// check total slides 1
std::cerr << "Expecting 1 slide." << std::endl;
sendTextFrame(socket, "status");
response = getResponseString(socket, "status:");
CPPUNIT_ASSERT_MESSAGE("did not receive a status: message as expected", !response.empty());
getPartHashCodes(response.substr(7), parts);
CPPUNIT_ASSERT_EQUAL(1, (int)parts.size());
const auto slide1Hash = parts[0];
// insert 10 slides
std::cerr << "Inserting 10 slides." << std::endl;
for (size_t it = 1; it <= 10; it++)
{
sendTextFrame(socket, "uno .uno:InsertPage");
response = getResponseString(socket, "status:");
CPPUNIT_ASSERT_MESSAGE("did not receive a status: message as expected", !response.empty());
getPartHashCodes(response.substr(7), parts);
CPPUNIT_ASSERT_EQUAL(it + 1, parts.size());
}
CPPUNIT_ASSERT_MESSAGE("Hash code of slide #1 changed after inserting extra slides.", parts[0] == slide1Hash);
const std::vector<std::string> parts_after_insert(parts.begin(), parts.end());
// delete 10 slides
std::cerr << "Deleting 10 slides." << std::endl;
for (size_t it = 1; it <= 10; it++)
{
sendTextFrame(socket, "uno .uno:DeletePage");
response = getResponseString(socket, "status:");
CPPUNIT_ASSERT_MESSAGE("did not receive a status: message as expected", !response.empty());
getPartHashCodes(response.substr(7), parts);
CPPUNIT_ASSERT_EQUAL(11 - it, parts.size());
}
CPPUNIT_ASSERT_MESSAGE("Hash code of slide #1 changed after deleting extra slides.", parts[0] == slide1Hash);
// undo delete slides
std::cerr << "Undoing 10 slide deletes." << std::endl;
for (size_t it = 1; it <= 10; it++)
{
sendTextFrame(socket, "uno .uno:Undo");
response = getResponseString(socket, "status:");
CPPUNIT_ASSERT_MESSAGE("did not receive a status: message as expected", !response.empty());
getPartHashCodes(response.substr(7), parts);
CPPUNIT_ASSERT_EQUAL(it + 1, parts.size());
}
CPPUNIT_ASSERT_MESSAGE("Hash code of slide #1 changed after undoing slide delete.", parts[0] == slide1Hash);
const std::vector<std::string> parts_after_undo(parts.begin(), parts.end());
CPPUNIT_ASSERT_MESSAGE("Hash codes changed between deleting and undo.", parts_after_insert == parts_after_undo);
// redo inserted slides
std::cerr << "Redoing 10 slide deletes." << std::endl;
for (size_t it = 1; it <= 10; it++)
{
sendTextFrame(socket, "uno .uno:Redo");
response = getResponseString(socket, "status:");
CPPUNIT_ASSERT_MESSAGE("did not receive a status: message as expected", !response.empty());
getPartHashCodes(response.substr(7), parts);
CPPUNIT_ASSERT_EQUAL(11 - it, parts.size());
}
CPPUNIT_ASSERT_MESSAGE("Hash code of slide #1 changed after redoing slide delete.", parts[0] == slide1Hash);
// check total slides 1
std::cerr << "Expecting 1 slide." << std::endl;
sendTextFrame(socket, "status");
response = getResponseString(socket, "status:");
CPPUNIT_ASSERT_MESSAGE("did not receive a status: message as expected", !response.empty());
getPartHashCodes(response.substr(7), parts);
CPPUNIT_ASSERT_EQUAL(1, (int)parts.size());
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testSlideShow()
{
const auto testname = "slideshow ";
try
{
// Load a document
std::string documentPath, documentURL;
std::string response;
getDocumentPathAndURL("setclientpart.odp", documentPath, documentURL);
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
auto socket = connectLOKit(_uri, request, _response);
sendTextFrame(socket, "load url=" + documentURL, testname);
CPPUNIT_ASSERT_MESSAGE("cannot load the document " + documentURL, isDocumentLoaded(socket));
// request slide show
sendTextFrame(socket, "downloadas name=slideshow.svg id=slideshow format=svg options=", testname);
response = getResponseString(socket, "downloadas:", testname);
CPPUNIT_ASSERT_MESSAGE("did not receive a downloadas: message as expected", !response.empty());
Poco::StringTokenizer tokens(response.substr(11), " ", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM);
// "downloadas: jail= dir= name=slideshow.svg port= id=slideshow"
const std::string jail = tokens[0].substr(std::string("jail=").size());
const std::string dir = tokens[1].substr(std::string("dir=").size());
const std::string name = tokens[2].substr(std::string("name=").size());
const int port = std::stoi(tokens[3].substr(std::string("port=").size()));
const std::string id = tokens[4].substr(std::string("id=").size());
CPPUNIT_ASSERT(!jail.empty());
CPPUNIT_ASSERT(!dir.empty());
CPPUNIT_ASSERT_EQUAL(std::string("slideshow.svg"), name);
CPPUNIT_ASSERT_EQUAL(static_cast<int>(_uri.getPort()), port);
CPPUNIT_ASSERT_EQUAL(std::string("slideshow"), id);
std::string encodedDoc;
Poco::URI::encode(documentPath, ":/?", encodedDoc);
const std::string path = "/lool/" + encodedDoc + "/" + jail + "/" + dir + "/" + name;
std::unique_ptr<Poco::Net::HTTPClientSession> session(helpers::createSession(_uri));
Poco::Net::HTTPRequest requestSVG(Poco::Net::HTTPRequest::HTTP_GET, path);
session->sendRequest(requestSVG);
Poco::Net::HTTPResponse responseSVG;
session->receiveResponse(responseSVG);
CPPUNIT_ASSERT_EQUAL(Poco::Net::HTTPResponse::HTTP_OK, responseSVG.getStatus());
CPPUNIT_ASSERT_EQUAL(std::string("image/svg+xml"), responseSVG.getContentType());
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testInactiveClient()
{
try
{
std::string documentPath, documentURL;
getDocumentPathAndURL("hello.odt", documentPath, documentURL);
auto socket1 = loadDocAndGetSocket(_uri, documentURL, "inactiveClient-1 ");
// Connect another and go inactive.
std::cerr << "Connecting second client." << std::endl;
auto socket2 = loadDocAndGetSocket(_uri, documentURL, "inactiveClient-2 ", true);
sendTextFrame(socket2, "userinactive", "inactiveClient-2 ");
// While second is inactive, make some changes.
sendTextFrame(socket1, "uno .uno:SelectAll", "inactiveClient-1 ");
sendTextFrame(socket1, "uno .uno:Delete", "inactiveClient-1 ");
// Activate second.
sendTextFrame(socket2, "useractive", "inactiveClient-2 ");
SocketProcessor("Second ", socket2, [&](const std::string& msg)
{
const auto token = LOOLProtocol::getFirstToken(msg);
CPPUNIT_ASSERT_MESSAGE("unexpected message: " + msg,
token == "cursorvisible:" ||
token == "graphicselection:" ||
token == "graphicviewselection:" ||
token == "invalidatecursor:" ||
token == "invalidatetiles:" ||
token == "invalidateviewcursor:" ||
token == "setpart:" ||
token == "statechanged:" ||
token == "textselection:" ||
token == "textselectionend:" ||
token == "textselectionstart:" ||
token == "textviewselection:" ||
token == "viewcursorvisible:" ||
token == "viewinfo:");
// End when we get state changed.
return (token != "statechanged:");
});
std::cerr << "Second client finished." << std::endl;
socket1->shutdown();
socket2->shutdown();
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testMaxColumn()
{
try
{
limitCursor(
// move cursor to last column
[](const std::shared_ptr<LOOLWebSocket>& socket,
int cursorX, int cursorY, int cursorWidth, int cursorHeight,
int docWidth, int docHeight)
{
CPPUNIT_ASSERT(cursorX >= 0);
CPPUNIT_ASSERT(cursorY >= 0);
CPPUNIT_ASSERT(cursorWidth >= 0);
CPPUNIT_ASSERT(cursorHeight >= 0);
CPPUNIT_ASSERT(docWidth >= 0);
CPPUNIT_ASSERT(docHeight >= 0);
const std::string text = "key type=input char=0 key=1027";
while ( cursorX <= docWidth )
{
sendTextFrame(socket, text);
cursorX += cursorWidth;
}
},
// check new document width
[](int docWidth, int docHeight, int newWidth, int newHeight)
{
CPPUNIT_ASSERT_EQUAL(docHeight, newHeight);
CPPUNIT_ASSERT(newWidth > docWidth);
},
"maxColumn"
);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testMaxRow()
{
try
{
limitCursor(
// move cursor to last row
[](const std::shared_ptr<LOOLWebSocket>& socket,
int cursorX, int cursorY, int cursorWidth, int cursorHeight,
int docWidth, int docHeight)
{
CPPUNIT_ASSERT(cursorX >= 0);
CPPUNIT_ASSERT(cursorY >= 0);
CPPUNIT_ASSERT(cursorWidth >= 0);
CPPUNIT_ASSERT(cursorHeight >= 0);
CPPUNIT_ASSERT(docWidth >= 0);
CPPUNIT_ASSERT(docHeight >= 0);
const std::string text = "key type=input char=0 key=1024";
while ( cursorY <= docHeight )
{
sendTextFrame(socket, text);
cursorY += cursorHeight;
}
},
// check new document height
[](int docWidth, int docHeight, int newWidth, int newHeight)
{
CPPUNIT_ASSERT_EQUAL(docWidth, newWidth);
CPPUNIT_ASSERT(newHeight > docHeight);
},
"maxRow"
);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testNoExtraLoolKitsLeft()
{
const auto countNow = countLoolKitProcesses(InitialLoolKitCount);
CPPUNIT_ASSERT_EQUAL(InitialLoolKitCount, countNow);
}
void HTTPWSTest::getPartHashCodes(const std::string status,
std::vector<std::string>& parts)
{
std::string line;
std::istringstream istr(status);
std::getline(istr, line);
std::cerr << "Reading parts from [" << status << "]." << std::endl;
// Expected format is something like 'type= parts= current= width= height= viewid='.
Poco::StringTokenizer tokens(line, " ", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM);
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(6), tokens.count());
const auto type = tokens[0].substr(std::string("type=").size());
CPPUNIT_ASSERT_MESSAGE("Expected presentation or spreadsheet type to read part names/codes.",
type == "presentation" || type == "spreadsheet");
const int totalParts = std::stoi(tokens[1].substr(std::string("parts=").size()));
std::cerr << "Status reports " << totalParts << " parts." << std::endl;
Poco::RegularExpression endLine("[^\n\r]+");
Poco::RegularExpression number("^[0-9]+$");
Poco::RegularExpression::MatchVec matches;
int offset = 0;
parts.clear();
while (endLine.match(status, offset, matches) > 0)
{
CPPUNIT_ASSERT_EQUAL(1, (int)matches.size());
const auto str = status.substr(matches[0].offset, matches[0].length);
if (number.match(str, 0) > 0)
{
parts.push_back(str);
}
offset = static_cast<int>(matches[0].offset + matches[0].length);
}
std::cerr << "Found " << parts.size() << " part names/codes." << std::endl;
// Validate that Core is internally consistent when emitting status messages.
CPPUNIT_ASSERT_EQUAL(totalParts, (int)parts.size());
}
void HTTPWSTest::getCursor(const std::string& message,
int& cursorX, int& cursorY, int& cursorWidth, int& cursorHeight)
{
Poco::JSON::Parser parser;
const auto result = parser.parse(message);
const auto& command = result.extract<Poco::JSON::Object::Ptr>();
auto text = command->get("commandName").toString();
CPPUNIT_ASSERT_EQUAL(std::string(".uno:CellCursor"), text);
text = command->get("commandValues").toString();
CPPUNIT_ASSERT(!text.empty());
Poco::StringTokenizer position(text, ",", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM);
cursorX = std::stoi(position[0]);
cursorY = std::stoi(position[1]);
cursorWidth = std::stoi(position[2]);
cursorHeight = std::stoi(position[3]);
CPPUNIT_ASSERT(cursorX >= 0);
CPPUNIT_ASSERT(cursorY >= 0);
CPPUNIT_ASSERT(cursorWidth >= 0);
CPPUNIT_ASSERT(cursorHeight >= 0);
}
void HTTPWSTest::limitCursor(std::function<void(const std::shared_ptr<LOOLWebSocket>& socket,
int cursorX, int cursorY,
int cursorWidth, int cursorHeight,
int docWidth, int docHeight)> keyhandler,
std::function<void(int docWidth, int docHeight,
int newWidth, int newHeight)> checkhandler,
const std::string& testname)
{
int docSheet = -1;
int docSheets = 0;
int docHeight = 0;
int docWidth = 0;
int docViewId = -1;
int newSheet = -1;
int newSheets = 0;
int newHeight = 0;
int newWidth = 0;
int cursorX = 0;
int cursorY = 0;
int cursorWidth = 0;
int cursorHeight = 0;
std::string response;
auto socket = loadDocAndGetSocket("empty.ods", _uri, testname);
// check document size
sendTextFrame(socket, "status", testname);
response = assertResponseString(socket, "status:", testname);
parseDocSize(response.substr(7), "spreadsheet", docSheet, docSheets, docWidth, docHeight, docViewId);
// Send an arrow key to initialize the CellCursor, otherwise we get "EMPTY".
sendTextFrame(socket, "key type=input char=0 key=1027", testname);
std::string text;
Poco::format(text, "commandvalues command=.uno:CellCursor?outputHeight=%d&outputWidth=%d&tileHeight=%d&tileWidth=%d",
256, 256, 3840, 3840);
sendTextFrame(socket, text, testname);
const auto cursor = getResponseString(socket, "commandvalues:", testname);
getCursor(cursor.substr(14), cursorX, cursorY, cursorWidth, cursorHeight);
// move cursor
keyhandler(socket, cursorX, cursorY, cursorWidth, cursorHeight, docWidth, docHeight);
// filter messages, and expect to receive new document size
response = assertResponseString(socket, "status:", testname);
parseDocSize(response.substr(7), "spreadsheet", newSheet, newSheets, newWidth, newHeight, docViewId);
CPPUNIT_ASSERT_EQUAL(docSheets, newSheets);
CPPUNIT_ASSERT_EQUAL(docSheet, newSheet);
// check new document size
checkhandler(docWidth, docHeight, newWidth, newHeight);
}
void HTTPWSTest::testInsertAnnotationWriter()
{
const auto testname = "insertAnnotationWriter ";
std::string documentPath, documentURL;
getDocumentPathAndURL("hello.odt", documentPath, documentURL);
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
auto socket = loadDocAndGetSocket(_uri, documentURL, testname);
// Insert comment.
sendTextFrame(socket, "uno .uno:InsertAnnotation");
// Paste some text.
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\nxxx yyy zzzz");
// Read it back.
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8");
auto res = getResponseString(socket, "textselectioncontent:", "insertAnnotationWriter ");
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: xxx yyy zzzz"), res);
// Can we edit the coment?
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\naaa bbb ccc");
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8");
res = getResponseString(socket, "textselectioncontent:", "insertAnnotationWriter ");
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: aaa bbb ccc"), res);
// Confirm that the text is in the comment and not doc body.
// Click in the body.
sendTextFrame(socket, "mouse type=buttondown x=1600 y=1600 count=1 buttons=1 modifier=0");
sendTextFrame(socket, "mouse type=buttonup x=1600 y=1600 count=1 buttons=1 modifier=0");
// Read body text.
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8");
res = getResponseString(socket, "textselectioncontent:", "insertAnnotationWriter ");
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: Hello world"), res);
// Confirm that the comment is still intact.
sendTextFrame(socket, "mouse type=buttondown x=13855 y=1893 count=1 buttons=1 modifier=0");
sendTextFrame(socket, "mouse type=buttonup x=13855 y=1893 count=1 buttons=1 modifier=0");
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8");
res = getResponseString(socket, "textselectioncontent:", "insertAnnotationWriter ");
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: aaa bbb ccc"), res);
// Can we still edit the coment?
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\nand now for something completely different");
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8");
res = getResponseString(socket, "textselectioncontent:", "insertAnnotationWriter ");
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: and now for something completely different"), res);
// Close and reopen the same document and test again.
socket->shutdown();
std::cerr << "Reloading " << std::endl;
socket = loadDocAndGetSocket(_uri, documentURL, testname);
// Confirm that the text is in the comment and not doc body.
// Click in the body.
sendTextFrame(socket, "mouse type=buttondown x=1600 y=1600 count=1 buttons=1 modifier=0");
sendTextFrame(socket, "mouse type=buttonup x=1600 y=1600 count=1 buttons=1 modifier=0");
// Read body text.
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8");
res = getResponseString(socket, "textselectioncontent:", "insertAnnotationWriter ");
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: Hello world"), res);
// Confirm that the comment is still intact.
sendTextFrame(socket, "mouse type=buttondown x=13855 y=1893 count=1 buttons=1 modifier=0");
sendTextFrame(socket, "mouse type=buttonup x=13855 y=1893 count=1 buttons=1 modifier=0");
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8");
res = getResponseString(socket, "textselectioncontent:", "insertAnnotationWriter ");
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: and now for something completely different"), res);
// Can we still edit the coment?
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\nblah blah xyz");
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8");
res = getResponseString(socket, "textselectioncontent:", "insertAnnotationWriter ");
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: blah blah xyz"), res);
}
void HTTPWSTest::testEditAnnotationWriter()
{
const auto testname = "editAnnotationWriter ";
std::string documentPath, documentURL;
getDocumentPathAndURL("with_comment.odt", documentPath, documentURL);
auto socket = loadDocAndGetSocket(_uri, documentURL, testname);
// Click in the body.
sendTextFrame(socket, "mouse type=buttondown x=1600 y=1600 count=1 buttons=1 modifier=0");
sendTextFrame(socket, "mouse type=buttonup x=1600 y=1600 count=1 buttons=1 modifier=0");
// Read body text.
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8");
auto res = getResponseString(socket, "textselectioncontent:", testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: Hello world"), res);
// Confirm that the comment is intact.
sendTextFrame(socket, "mouse type=buttondown x=13855 y=1893 count=1 buttons=1 modifier=0");
sendTextFrame(socket, "mouse type=buttonup x=13855 y=1893 count=1 buttons=1 modifier=0");
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8");
res = getResponseString(socket, "textselectioncontent:", testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: blah blah xyz"), res);
// Can we still edit the coment?
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\nand now for something completely different");
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8");
res = getResponseString(socket, "textselectioncontent:", testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: and now for something completely different"), res);
const auto kitcount = getLoolKitProcessCount();
// Close and reopen the same document and test again.
std::cerr << "Closing connection after pasting." << std::endl;
socket->shutdown();
std::cerr << "Reloading " << std::endl;
socket = loadDocAndGetSocket(_uri, documentURL, testname);
// Should have no new instances.
CPPUNIT_ASSERT_EQUAL(kitcount, countLoolKitProcesses(kitcount));
// Confirm that the text is in the comment and not doc body.
// Click in the body.
sendTextFrame(socket, "mouse type=buttondown x=1600 y=1600 count=1 buttons=1 modifier=0");
sendTextFrame(socket, "mouse type=buttonup x=1600 y=1600 count=1 buttons=1 modifier=0");
// Read body text.
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8");
res = getResponseString(socket, "textselectioncontent:", testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: Hello world"), res);
// Confirm that the comment is still intact.
sendTextFrame(socket, "mouse type=buttondown x=13855 y=1893 count=1 buttons=1 modifier=0");
sendTextFrame(socket, "mouse type=buttonup x=13855 y=1893 count=1 buttons=1 modifier=0");
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8");
res = getResponseString(socket, "textselectioncontent:", testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: and now for something completely different"), res);
// Can we still edit the coment?
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\nnew text different");
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8");
res = getResponseString(socket, "textselectioncontent:", testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: new text different"), res);
}
void HTTPWSTest::testInsertAnnotationCalc()
{
const auto testname = "insertAnnotationCalc ";
auto socket = loadDocAndGetSocket("setclientpart.ods", _uri, testname);
// Insert comment.
sendTextFrame(socket, "uno .uno:InsertAnnotation");
// Paste some text.
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\naaa bbb ccc");
// Read it back.
sendTextFrame(socket, "uno .uno:SelectAll");
sendTextFrame(socket, "gettextselection mimetype=text/plain;charset=utf-8");
auto res = getResponseString(socket, "textselectioncontent:", testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: aaa bbb ccc"), res);
}
void HTTPWSTest::testCalcEditRendering()
{
const auto testname = "calcEditRendering ";
auto socket = loadDocAndGetSocket("calc_render.xls", _uri, testname);
sendTextFrame(socket, "mouse type=buttondown x=5000 y=5 count=1 buttons=1 modifier=0");
sendTextFrame(socket, "key type=input char=97 key=0");
sendTextFrame(socket, "key type=input char=98 key=0");
sendTextFrame(socket, "key type=input char=99 key=0");
assertResponseString(socket, "cellformula: abc", testname);
const auto req = "tilecombine part=0 width=512 height=512 tileposx=3840 tileposy=0 tilewidth=7680 tileheight=7680";
sendTextFrame(socket, req);
const auto tile = getResponseMessage(socket, "tile:", testname);
std::cerr << "size: " << tile.size() << std::endl;
// Return early for now when on LO >= 5.2.
std::string clientVersion = "loolclient 0.1";
sendTextFrame(socket, clientVersion);
std::vector<char> loVersion = getResponseMessage(socket, "lokitversion");
std::string line = LOOLProtocol::getFirstLine(loVersion.data(), loVersion.size());
line = line.substr(strlen("lokitversion "));
Poco::JSON::Parser parser;
Poco::Dynamic::Var loVersionVar = parser.parse(line);
const Poco::SharedPtr<Poco::JSON::Object>& loVersionObject = loVersionVar.extract<Poco::JSON::Object::Ptr>();
std::string loProductVersion = loVersionObject->get("ProductVersion").toString();
std::istringstream stream(loProductVersion);
int major = 0;
stream >> major;
assert(stream.get() == '.');
int minor = 0;
stream >> minor;
const std::string firstLine = LOOLProtocol::getFirstLine(tile);
std::vector<char> res(tile.begin() + firstLine.size() + 1, tile.end());
std::stringstream streamRes;
std::copy(res.begin(), res.end(), std::ostream_iterator<char>(streamRes));
std::fstream outStream("/tmp/res.png", std::ios::out);
outStream.write(res.data(), res.size());
outStream.close();
png_uint_32 height = 0;
png_uint_32 width = 0;
png_uint_32 rowBytes = 0;
auto rows = png::decodePNG(streamRes, height, width, rowBytes);
const std::vector<char> exp = readDataFromFile("calc_render_0_512x512.3840,0.7680x7680.png");
std::stringstream streamExp;
std::copy(exp.begin(), exp.end(), std::ostream_iterator<char>(streamExp));
png_uint_32 heightExp = 0;
png_uint_32 widthExp = 0;
png_uint_32 rowBytesExp = 0;
auto rowsExp = png::decodePNG(streamExp, heightExp, widthExp, rowBytesExp);
CPPUNIT_ASSERT_EQUAL(heightExp, height);
CPPUNIT_ASSERT_EQUAL(widthExp, width);
CPPUNIT_ASSERT_EQUAL(rowBytesExp, rowBytes);
for (png_uint_32 itRow = 0; itRow < height; ++itRow)
{
const bool eq = std::equal(rowsExp[itRow], rowsExp[itRow] + rowBytes, rows[itRow]);
if (!eq)
{
// This is a very strict test that breaks often/easily due to slight rendering
// differences. So for now just keep it informative only.
//CPPUNIT_ASSERT_MESSAGE("Tile not rendered as expected @ row #" + std::to_string(itRow), eq);
std::cerr << "\nFAILURE: Tile not rendered as expected @ row #" << itRow << std::endl;
break;
}
}
}
std::string HTTPWSTest::getFontList(const std::string& message)
{
Poco::JSON::Parser parser;
const auto result = parser.parse(message);
const auto& command = result.extract<Poco::JSON::Object::Ptr>();
auto text = command->get("commandName").toString();
CPPUNIT_ASSERT_EQUAL(std::string(".uno:CharFontName"), text);
text = command->get("commandValues").toString();
return text;
}
void HTTPWSTest::testFontList()
{
const auto testname = "fontList ";
try
{
// Load a document
auto socket = loadDocAndGetSocket("setclientpart.odp", _uri, testname);
sendTextFrame(socket, "commandvalues command=.uno:CharFontName", testname);
const auto response = getResponseMessage(socket, "commandvalues:", testname);
CPPUNIT_ASSERT_MESSAGE("did not receive a commandvalues: message as expected", !response.empty());
std::stringstream streamResponse;
std::copy(response.begin() + std::string("commandvalues:").length() + 1, response.end(), std::ostream_iterator<char>(streamResponse));
CPPUNIT_ASSERT(!getFontList(streamResponse.str()).empty());
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testStateChanged(const std::string& filename, std::vector<std::string>& vecCommands)
{
const auto testname = "stateChanged ";
auto socket = loadDocAndGetSocket(filename, _uri, testname);
SocketProcessor(testname, socket,
[&](const std::string& msg)
{
Poco::RegularExpression::MatchVec matches;
Poco::RegularExpression reUno("\\.[a-zA-Z]*\\:[a-zA-Z]*\\=");
if (reUno.match(msg, 0, matches) > 0 && matches.size() == 1)
{
const auto str = msg.substr(matches[0].offset, matches[0].length);
auto result = std::find(std::begin(vecCommands), std::end(vecCommands), str);
if (result != std::end(vecCommands))
{
vecCommands.erase(result);
}
}
if (vecCommands.size() == 0)
return false;
return true;
});
if (vecCommands.size() > 0 )
{
std::ostringstream ostr;
ostr << filename << " : Missing Uno Commands: " << std::endl;
for (auto itUno : vecCommands)
ostr << itUno << std::endl;
CPPUNIT_FAIL(ostr.str());
}
}
void HTTPWSTest::testStateUnoCommand()
{
std::vector<std::string> calcCommands
{
".uno:BackgroundColor=",
".uno:Bold=",
".uno:CenterPara=",
".uno:CharBackColor=",
".uno:CharFontName=",
".uno:Color=",
".uno:FontHeight=",
".uno:Italic=",
".uno:JustifyPara=",
".uno:OutlineFont=",
".uno:LeftPara=",
".uno:RightPara=",
".uno:Shadowed=",
".uno:SubScript=",
".uno:SuperScript=",
".uno:Strikeout=",
".uno:StyleApply=",
".uno:Underline=",
".uno:ModifiedStatus=",
".uno:Undo=",
".uno:Redo=",
".uno:Cut=",
".uno:Copy=",
".uno:Paste=",
".uno:SelectAll=",
".uno:InsertAnnotation=",
".uno:InsertRowsBefore=",
".uno:InsertRowsAfter=",
".uno:InsertColumnsBefore=",
".uno:InsertColumnsAfter=",
".uno:DeleteRows=",
".uno:DeleteColumns=",
".uno:MergeCells=",
".uno:StatusDocPos=",
".uno:RowColSelCount=",
".uno:StatusPageStyle=",
".uno:InsertMode=",
".uno:StatusSelectionMode=",
".uno:StateTableCell=",
".uno:StatusBarFunc=",
".uno:WrapText=",
".uno:ToggleMergeCells=",
".uno:NumberFormatCurrency=",
".uno:NumberFormatPercent=",
".uno:NumberFormatDate="
};
std::vector<std::string> writerCommands
{
".uno:BackColor=",
".uno:BackgroundColor=",
".uno:Bold=",
".uno:CenterPara=",
".uno:CharBackColor=",
".uno:CharBackgroundExt=",
".uno:CharFontName=",
".uno:Color=",
".uno:DefaultBullet=",
".uno:DefaultNumbering=",
".uno:FontColor=",
".uno:FontHeight=",
".uno:Italic=",
".uno:JustifyPara=",
".uno:OutlineFont=",
".uno:LeftPara=",
".uno:RightPara=",
".uno:Shadowed=",
".uno:SubScript=",
".uno:SuperScript=",
".uno:Strikeout=",
".uno:StyleApply=",
".uno:Underline=",
".uno:ModifiedStatus=",
".uno:Undo=",
".uno:Redo=",
".uno:Cut=",
".uno:Copy=",
".uno:Paste=",
".uno:SelectAll=",
".uno:InsertAnnotation=",
".uno:InsertRowsBefore=",
".uno:InsertRowsAfter=",
".uno:InsertColumnsBefore=",
".uno:InsertColumnsAfter=",
".uno:DeleteRows=",
".uno:DeleteColumns=",
".uno:DeleteTable=",
".uno:SelectTable=",
".uno:EntireRow=",
".uno:EntireColumn=",
".uno:EntireCell=",
".uno:MergeCells=",
".uno:InsertMode=",
".uno:StateTableCell=",
".uno:StatePageNumber=",
".uno:StateWordCount=",
".uno:SelectionMode=",
".uno:NumberFormatCurrency=",
".uno:NumberFormatPercent=",
".uno:NumberFormatDate="
};
std::vector<std::string> impressCommands
{
".uno:Bold=",
".uno:CenterPara=",
".uno:CharBackColor=",
".uno:CharFontName=",
".uno:Color=",
".uno:DefaultBullet=",
".uno:DefaultNumbering=",
".uno:FontHeight=",
".uno:Italic=",
".uno:JustifyPara=",
".uno:OutlineFont=",
".uno:LeftPara=",
".uno:RightPara=",
".uno:Shadowed=",
".uno:SubScript=",
".uno:SuperScript=",
".uno:Strikeout=",
".uno:StyleApply=",
".uno:Underline=",
".uno:ModifiedStatus=",
".uno:Undo=",
".uno:Redo=",
".uno:InsertPage=",
".uno:DeletePage=",
".uno:DuplicatePage=",
".uno:Cut=",
".uno:Copy=",
".uno:Paste=",
".uno:SelectAll=",
".uno:InsertAnnotation=",
".uno:InsertRowsBefore=",
".uno:InsertRowsAfter=",
".uno:InsertColumnsBefore=",
".uno:InsertColumnsAfter=",
".uno:DeleteRows=",
".uno:DeleteColumns=",
".uno:SelectTable=",
".uno:EntireRow=",
".uno:EntireColumn=",
".uno:MergeCells=",
".uno:AssignLayout=",
".uno:PageStatus=",
".uno:LayoutStatus=",
".uno:Context=",
};
try
{
testStateChanged(std::string("setclientpart.ods"), calcCommands);
testStateChanged(std::string("hello.odt"), writerCommands);
testStateChanged(std::string("setclientpart.odp"), impressCommands);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
double HTTPWSTest::getColRowSize(const std::string& property, const std::string& message, int index)
{
Poco::JSON::Parser parser;
const auto result = parser.parse(message);
const auto& command = result.extract<Poco::JSON::Object::Ptr>();
auto text = command->get("commandName").toString();
CPPUNIT_ASSERT_EQUAL(std::string(".uno:ViewRowColumnHeaders"), text);
CPPUNIT_ASSERT(command->isArray(property));
auto array = command->getArray(property);
CPPUNIT_ASSERT(array->isObject(index));
auto item = array->getObject(index);
CPPUNIT_ASSERT(item->has("size"));
return item->getValue<double>("size");
}
double HTTPWSTest::getColRowSize(const std::shared_ptr<LOOLWebSocket>& socket, const std::string& item, int index)
{
std::vector<char> response;
response = getResponseMessage(socket, "commandvalues:", "testColumnRowResize ");
CPPUNIT_ASSERT_MESSAGE("did not receive a commandvalues: message as expected", !response.empty());
std::vector<char> json(response.begin() + std::string("commandvalues:").length(), response.end());
json.push_back(0);
return getColRowSize(item, json.data(), index);
}
void HTTPWSTest::testColumnRowResize()
{
try
{
std::vector<char> response;
std::string documentPath, documentURL;
double oldHeight, oldWidth;
getDocumentPathAndURL("setclientpart.ods", documentPath, documentURL);
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
auto socket = loadDocAndGetSocket(_uri, documentURL, "testColumnRowResize ");
const std::string commandValues = "commandvalues command=.uno:ViewRowColumnHeaders";
sendTextFrame(socket, commandValues);
response = getResponseMessage(socket, "commandvalues:", "testColumnRowResize ");
CPPUNIT_ASSERT_MESSAGE("did not receive a commandvalues: message as expected", !response.empty());
{
std::vector<char> json(response.begin() + std::string("commandvalues:").length(), response.end());
json.push_back(0);
// get column 2
oldHeight = getColRowSize("rows", json.data(), 1);
// get row 2
oldWidth = getColRowSize("columns", json.data(), 1);
}
// send column width
{
std::ostringstream oss;
Poco::JSON::Object objJSON, objColumn, objWidth;
double newWidth;
// change column 2
objColumn.set("type", "unsigned short");
objColumn.set("value", 2);
objWidth.set("type", "unsigned short");
objWidth.set("value", oldWidth + 100);
objJSON.set("Column", objColumn);
objJSON.set("Width", objWidth);
Poco::JSON::Stringifier::stringify(objJSON, oss);
sendTextFrame(socket, "uno .uno:ColumnWidth " + oss.str());
sendTextFrame(socket, commandValues);
response = getResponseMessage(socket, "commandvalues:", "testColumnRowResize ");
CPPUNIT_ASSERT_MESSAGE("did not receive a commandvalues: message as expected", !response.empty());
std::vector<char> json(response.begin() + std::string("commandvalues:").length(), response.end());
json.push_back(0);
newWidth = getColRowSize("columns", json.data(), 1);
CPPUNIT_ASSERT(newWidth > oldWidth);
}
// send row height
{
std::ostringstream oss;
Poco::JSON::Object objJSON, objRow, objHeight;
double newHeight;
// change row 2
objRow.set("type", "unsigned short");
objRow.set("value", 2);
objHeight.set("type", "unsigned short");
objHeight.set("value", oldHeight + 100);
objJSON.set("Row", objRow);
objJSON.set("Height", objHeight);
Poco::JSON::Stringifier::stringify(objJSON, oss);
sendTextFrame(socket, "uno .uno:RowHeight " + oss.str());
sendTextFrame(socket, commandValues);
response = getResponseMessage(socket, "commandvalues:", "testColumnRowResize ");
CPPUNIT_ASSERT_MESSAGE("did not receive a commandvalues: message as expected", !response.empty());
std::vector<char> json(response.begin() + std::string("commandvalues:").length(), response.end());
json.push_back(0);
newHeight = getColRowSize("rows", json.data(), 1);
CPPUNIT_ASSERT(newHeight > oldHeight);
}
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testOptimalResize()
{
try
{
//std::vector<char> response;
std::string documentPath, documentURL;
double newWidth, newHeight;
Poco::JSON::Object objIndex, objSize, objModifier;
// row/column index 0
objIndex.set("type", "unsigned short");
objIndex.set("value", 1);
// size in twips
objSize.set("type", "unsigned short");
objSize.set("value", 3840);
// keyboard modifier
objModifier.set("type", "unsigned short");
objModifier.set("value", 0);
getDocumentPathAndURL("empty.ods", documentPath, documentURL);
auto socket = loadDocAndGetSocket(_uri, documentURL, "testOptimalResize ");
const std::string commandValues = "commandvalues command=.uno:ViewRowColumnHeaders";
// send new column width
{
std::ostringstream oss;
Poco::JSON::Object objJSON;
objJSON.set("Column", objIndex);
objJSON.set("Width", objSize);
Poco::JSON::Stringifier::stringify(objJSON, oss);
sendTextFrame(socket, "uno .uno:ColumnWidth " + oss.str());
sendTextFrame(socket, commandValues);
newWidth = getColRowSize(socket, "columns", 0);
}
// send new row height
{
std::ostringstream oss;
Poco::JSON::Object objJSON;
objJSON.set("Row", objIndex);
objJSON.set("Height", objSize);
Poco::JSON::Stringifier::stringify(objJSON, oss);
sendTextFrame(socket, "uno .uno:RowHeight " + oss.str());
sendTextFrame(socket, commandValues);
newHeight = getColRowSize(socket, "rows", 0);
}
objIndex.set("value", 0);
// send optimal column width
{
std::ostringstream oss;
Poco::JSON::Object objJSON;
double optimalWidth;
objJSON.set("Col", objIndex);
objJSON.set("Modifier", objModifier);
Poco::JSON::Stringifier::stringify(objJSON, oss);
sendTextFrame(socket, "uno .uno:SelectColumn " + oss.str());
sendTextFrame(socket, "uno .uno:SetOptimalColumnWidthDirect");
sendTextFrame(socket, commandValues);
optimalWidth = getColRowSize(socket, "columns", 0);
CPPUNIT_ASSERT(optimalWidth < newWidth);
}
// send optimal row height
{
std::ostringstream oss;
Poco::JSON::Object objSelect, objOptHeight, objExtra;
double optimalHeight;
objSelect.set("Row", objIndex);
objSelect.set("Modifier", objModifier);
objExtra.set("type", "unsigned short");
objExtra.set("value", 0);
objOptHeight.set("aExtraHeight", objExtra);
Poco::JSON::Stringifier::stringify(objSelect, oss);
sendTextFrame(socket, "uno .uno:SelectRow " + oss.str());
oss.str(""); oss.clear();
Poco::JSON::Stringifier::stringify(objOptHeight, oss);
sendTextFrame(socket, "uno .uno:SetOptimalRowHeight " + oss.str());
sendTextFrame(socket, commandValues);
optimalHeight = getColRowSize(socket, "rows", 0);
CPPUNIT_ASSERT(optimalHeight < newHeight);
}
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testEachView(const std::string& doc, const std::string& type,
const std::string& protocol, const std::string& protocolView,
const std::string& testname)
{
const std::string view = testname + "view %d -> ";
const std::string load = testname + "view %d, cannot load the document ";
const std::string error = testname + "view %d, did not receive a %s message as expected";
try
{
// Load a document
std::string documentPath, documentURL;
getDocumentPathAndURL(doc, documentPath, documentURL);
int itView = 0;
auto socket = loadDocAndGetSocket(_uri, documentURL, Poco::format(view, itView));
// Check document size
sendTextFrame(socket, "status", Poco::format(view, itView));
auto response = getResponseString(socket, "status:", Poco::format(view, itView));
CPPUNIT_ASSERT_MESSAGE(Poco::format(error, itView, std::string("status:")), !response.empty());
int docPart = -1;
int docParts = 0;
int docHeight = 0;
int docWidth = 0;
int docViewId = -1;
parseDocSize(response.substr(7), type, docPart, docParts, docWidth, docHeight, docViewId);
// Send click message
std::string text;
Poco::format(text, "mouse type=%s x=%d y=%d count=1 buttons=1 modifier=0", std::string("buttondown"), docWidth/2, docHeight/6);
sendTextFrame(socket, text, Poco::format(view, itView));
text.clear();
Poco::format(text, "mouse type=%s x=%d y=%d count=1 buttons=1 modifier=0", std::string("buttonup"), docWidth/2, docHeight/6);
sendTextFrame(socket, text, Poco::format(view, itView));
response = getResponseString(socket, protocol, Poco::format(view, itView));
CPPUNIT_ASSERT_MESSAGE(Poco::format(error, itView, protocol), !response.empty());
// Connect and load 0..N Views, where N<=limit
std::vector<std::shared_ptr<LOOLWebSocket>> views;
#if MAX_DOCUMENTS > 0
const auto limit = std::min(5, MAX_DOCUMENTS - 1); // +1 connection above
#else
constexpr auto limit = 5;
#endif
for (itView = 0; itView < limit; ++itView)
{
views.emplace_back(loadDocAndGetSocket(_uri, documentURL, Poco::format(view, itView)));
}
// main view should receive response each view
itView = 0;
for (auto socketView : views)
{
getResponseString(socket, protocolView, Poco::format(view, itView));
CPPUNIT_ASSERT_MESSAGE(Poco::format(error, itView, protocolView), !response.empty());
++itView;
}
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
testNoExtraLoolKitsLeft();
}
void HTTPWSTest::testInvalidateViewCursor()
{
testEachView("viewcursor.odp", "presentation", "invalidatecursor:", "invalidateviewcursor:", "invalidateViewCursor ");
}
void HTTPWSTest::testViewCursorVisible()
{
testEachView("viewcursor.odp", "presentation", "cursorvisible:", "viewcursorvisible:", "viewCursorVisible ");
}
void HTTPWSTest::testCellViewCursor()
{
testEachView("empty.ods", "spreadsheet", "cellcursor:", "cellviewcursor:", "cellViewCursor");
}
void HTTPWSTest::testGraphicViewSelection()
{
testEachView("graphicviewselection.odp", "presentation", "graphicselection:", "graphicviewselection:", "graphicViewSelection-odp ");
testEachView("graphicviewselection.odt", "text", "graphicselection:", "graphicviewselection:", "graphicViewSelection-odt ");
testEachView("graphicviewselection.ods", "spreadsheet", "graphicselection:", "graphicviewselection:", "graphicViewSelection-ods ");
}
void HTTPWSTest::testGraphicInvalidate()
{
const auto testname = "graphicInvalidate ";
try
{
// Load a document.
auto socket = loadDocAndGetSocket("shape.ods", _uri, testname);
// Send click message
sendTextFrame(socket, "mouse type=buttondown x=1035 y=400 count=1 buttons=1 modifier=0", testname);
sendTextFrame(socket, "mouse type=buttonup x=1035 y=400 count=1 buttons=1 modifier=0", testname);
getResponseString(socket, "graphicselection:", testname);
// Drag & drop graphic
sendTextFrame(socket, "mouse type=buttondown x=1035 y=400 count=1 buttons=1 modifier=0", testname);
sendTextFrame(socket, "mouse type=move x=1035 y=450 count=1 buttons=1 modifier=0", testname);
sendTextFrame(socket, "mouse type=buttonup x=1035 y=450 count=1 buttons=1 modifier=0", testname);
const auto message = getResponseString(socket, "invalidatetiles:", testname);
CPPUNIT_ASSERT_MESSAGE("Drag & Drop graphic invalidate all tiles", message.find("EMPTY") == std::string::npos);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testCursorPosition()
{
try
{
const auto testname = "cursorPosition ";
// Load a document.
std::string docPath;
std::string docURL;
std::string response;
getDocumentPathAndURL("Example.odt", docPath, docURL);
auto socket0 = loadDocAndGetSocket(_uri, docURL, testname);
// receive cursor position
response = getResponseString(socket0, "invalidatecursor:", testname);
Poco::StringTokenizer cursorTokens(response.substr(17), ",", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM);
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), cursorTokens.count());
// Create second view
auto socket1 = loadDocAndGetSocket(_uri, docURL, testname);
//receive view cursor position
response = getResponseString(socket1, "invalidateviewcursor:", testname);
Poco::JSON::Parser parser;
const auto result = parser.parse(response.substr(21));
const auto& command = result.extract<Poco::JSON::Object::Ptr>();
CPPUNIT_ASSERT_MESSAGE("missing property rectangle", command->has("rectangle"));
Poco::StringTokenizer viewTokens(command->get("rectangle").toString(), ",", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM);
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), viewTokens.count());
// check both cursor should be equal
CPPUNIT_ASSERT_EQUAL(cursorTokens[0], viewTokens[0]);
CPPUNIT_ASSERT_EQUAL(cursorTokens[1], viewTokens[1]);
CPPUNIT_ASSERT_EQUAL(cursorTokens[2], viewTokens[2]);
CPPUNIT_ASSERT_EQUAL(cursorTokens[3], viewTokens[3]);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testAlertAllUsers()
{
// Load two documents, each in two sessions. Tell one session to fake a disk full
// situation. Expect to get the corresponding error back in all sessions.
const auto testname = "alertAllUsers ";
try
{
std::string docPath[2];
std::string docURL[2];
getDocumentPathAndURL("Example.odt", docPath[0], docURL[0]);
getDocumentPathAndURL("hello.odt", docPath[1], docURL[1]);
Poco::Net::HTTPRequest* request[2];
for (int i = 0; i < 2; i++)
request[i] = new Poco::Net::HTTPRequest(Poco::Net::HTTPRequest::HTTP_GET, docURL[i]);
std::shared_ptr<LOOLWebSocket> socket[4];
for (int i = 0; i < 4; i++)
{
socket[i] = connectLOKit(_uri, *(request[i/2]), _response);
sendTextFrame(socket[i], "load url=" + docURL[i/2], testname);
}
sendTextFrame(socket[0], "uno .uno:fakeDiskFull", testname);
for (int i = 0; i < 4; i++)
{
std::string response = getResponseString(socket[i], "error:", testname);
Poco::StringTokenizer tokens(response.substr(6), " ", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM);
std::string cmd;
LOOLProtocol::getTokenString(tokens, "cmd", cmd);
CPPUNIT_ASSERT_EQUAL(std::string("internal"), cmd);
std::string kind;
LOOLProtocol::getTokenString(tokens, "kind", kind);
CPPUNIT_ASSERT_EQUAL(std::string("diskfull"), kind);
}
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testViewInfoMsg()
{
// Load 2 documents, cross-check the viewid received by each of them in their status message
// with the one sent in viewinfo message to itself as well as to other one
const std::string testname = "testViewInfoMsg-";
std::string docPath;
std::string docURL;
getDocumentPathAndURL("hello.odt", docPath, docURL);
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, docURL);
auto socket0 = connectLOKit(_uri, request, _response);
auto socket1 = connectLOKit(_uri, request, _response);
std::string response;
int part, parts, width, height;
int viewid[2];
try
{
// Load first view and remember the viewid
sendTextFrame(socket0, "load url=" + docURL);
response = getResponseString(socket0, "status:", testname + "0 ");
parseDocSize(response.substr(7), "text", part, parts, width, height, viewid[0]);
// Check if viewinfo message also mentions the same viewid
response = getResponseString(socket0, "viewinfo: ", testname + "0 ");
Poco::JSON::Parser parser0;
Poco::JSON::Array::Ptr array = parser0.parse(response.substr(9)).extract<Poco::JSON::Array::Ptr>();
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), array->size());
Poco::JSON::Object::Ptr viewInfoObj0 = array->getObject(0);
int viewid0 = viewInfoObj0->get("id").convert<int>();
CPPUNIT_ASSERT_EQUAL(viewid[0], viewid0);
// Load second view and remember the viewid
sendTextFrame(socket1, "load url=" + docURL);
response = getResponseString(socket1, "status:", testname + "1 ");
parseDocSize(response.substr(7), "text", part, parts, width, height, viewid[1]);
// Check if viewinfo message in this view mentions
// viewid of both first loaded view and this view
response = getResponseString(socket1, "viewinfo: ", testname + "1 ");
Poco::JSON::Parser parser1;
array = parser1.parse(response.substr(9)).extract<Poco::JSON::Array::Ptr>();
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), array->size());
viewInfoObj0 = array->getObject(0);
Poco::JSON::Object::Ptr viewInfoObj1 = array->getObject(1);
viewid0 = viewInfoObj0->get("id").convert<int>();
int viewid1 = viewInfoObj1->get("id").convert<int>();
if (viewid[0] == viewid0)
CPPUNIT_ASSERT_EQUAL(viewid[1], viewid1);
else if (viewid[0] == viewid1)
CPPUNIT_ASSERT_EQUAL(viewid[1], viewid0);
else
CPPUNIT_FAIL("Inconsistent viewid in viewinfo and status messages");
// Check if first view also got the same viewinfo message
const auto response1 = getResponseString(socket0, "viewinfo: ", testname + "0 ");
CPPUNIT_ASSERT_EQUAL(response, response1);
}
catch(const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
CPPUNIT_TEST_SUITE_REGISTRATION(HTTPWSTest);
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */