libreoffice-online/test/httpwstest.cpp
Miklos Vajna 0580f51f85 Convert doc load tests to a new-style one
So that they are in-process, which means it's easier to debug when they
fail.

Change-Id: I2dcef46ec76e1f971172d1c29adf09103cfeaa7b
2019-11-22 09:07:33 +01:00

1590 lines
62 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 <vector>
#include <Poco/Net/AcceptCertificateHandler.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/Net/InvalidCertificateHandler.h>
#include <Poco/Net/SSLManager.h>
#include <Poco/RegularExpression.h>
#include <Poco/URI.h>
#include <cppunit/extensions/HelperMacros.h>
#include <Common.hpp>
#include <Protocol.hpp>
#include <LOOLWebSocket.hpp>
#include <Png.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;
CPPUNIT_TEST_SUITE(HTTPWSTest);
CPPUNIT_TEST(testCloseAfterClose);
CPPUNIT_TEST(testGetTextSelection);
CPPUNIT_TEST(testSaveOnDisconnect);
CPPUNIT_TEST(testSavePassiveOnDisconnect);
CPPUNIT_TEST(testReloadWhileDisconnecting);
CPPUNIT_TEST(testPasteBlank);
CPPUNIT_TEST(testInsertDelete);
CPPUNIT_TEST(testInactiveClient);
CPPUNIT_TEST(testMaxColumn);
CPPUNIT_TEST(testMaxRow);
// CPPUNIT_TEST(testInsertAnnotationWriter);
// CPPUNIT_TEST(testEditAnnotationWriter);
// FIXME CPPUNIT_TEST(testInsertAnnotationCalc);
CPPUNIT_TEST(testCalcEditRendering);
CPPUNIT_TEST(testCalcRenderAfterNewView51);
CPPUNIT_TEST(testCalcRenderAfterNewView53);
CPPUNIT_TEST(testFontList);
// FIXME CPPUNIT_TEST(testColumnRowResize);
// FIXME CPPUNIT_TEST(testOptimalResize);
CPPUNIT_TEST(testGraphicInvalidate);
CPPUNIT_TEST(testCursorPosition);
CPPUNIT_TEST(testAlertAllUsers);
CPPUNIT_TEST(testViewInfoMsg);
CPPUNIT_TEST(testUndoConflict);
CPPUNIT_TEST_SUITE_END();
void testCloseAfterClose();
void testGetTextSelection();
void testSaveOnDisconnect();
void testSavePassiveOnDisconnect();
void testReloadWhileDisconnecting();
void testPasteBlank();
void testInsertDelete();
void testInactiveClient();
void testMaxColumn();
void testMaxRow();
void testInsertAnnotationWriter();
void testEditAnnotationWriter();
void testInsertAnnotationCalc();
void testCalcEditRendering();
void testCalcRenderAfterNewView51();
void testCalcRenderAfterNewView53();
void testFontList();
void testColumnRowResize();
void testOptimalResize();
void testGraphicInvalidate();
void testCursorPosition();
void testAlertAllUsers();
void testViewInfoMsg();
void testUndoConflict();
void getPartHashCodes(const std::string& testname,
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(const std::function<void(const std::shared_ptr<LOOLWebSocket>& socket,
int cursorX, int cursorY,
int cursorWidth, int cursorHeight,
int docWidth, int docHeight)>& keyhandler,
const std::function<void(int docWidth, int docHeight,
int newWidth, int newHeight)>& checkhandler,
const std::string& testname);
std::string getFontList(const std::string& message);
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, 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(nullptr, invalidCertHandler, sslContext);
#endif
}
#if ENABLE_SSL
~HTTPWSTest()
{
Poco::Net::uninitializeSSL();
}
#endif
void setUp()
{
resetTestStartTime();
testCountHowManyLoolkits();
resetTestStartTime();
}
void tearDown()
{
resetTestStartTime();
testNoExtraLoolKitsLeft();
resetTestStartTime();
}
};
void HTTPWSTest::testCloseAfterClose()
{
const char* testname = "closeAfterClose ";
try
{
TST_LOG("Connecting and loading.");
std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket("hello.odt", _uri, testname);
// send normal socket shutdown
TST_LOG("Disconnecting.");
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);
TST_LOG("Received [" << std::string(buffer, bytes) << "], flags: "<< std::hex << flags << std::dec);
}
while (bytes > 0 && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE);
TST_LOG("Received " << bytes << " bytes, flags: "<< std::hex << flags << std::dec);
try
{
// no more messages is received.
bytes = socket->receiveFrame(buffer, sizeof(buffer), flags);
TST_LOG("Received " << bytes << " bytes, flags: "<< std::hex << flags << std::dec);
CPPUNIT_ASSERT_EQUAL(0, bytes);
CPPUNIT_ASSERT_EQUAL(0, flags);
}
catch (const Poco::Exception& exc)
{
// This is not unexpected, since WSD will close the socket after
// echoing back the shutdown status code. However, if it doesn't
// we assert above that it doesn't send any more data.
TST_LOG("Error: " << exc.displayText());
}
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testGetTextSelection()
{
const char* testname = "getTextSelection ";
try
{
std::string documentPath, documentURL;
getDocumentPathAndURL("hello.odt", documentPath, documentURL, testname);
std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket(_uri, documentURL, testname);
std::shared_ptr<LOOLWebSocket> socket2 = loadDocAndGetSocket(_uri, documentURL, testname);
static const std::string expected = "Hello world";
const std::string selection = getAllText(socket, testname, expected);
CPPUNIT_ASSERT_EQUAL("textselectioncontent: " + expected, selection);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testSaveOnDisconnect()
{
const char* testname = "saveOnDisconnect ";
const std::string text = helpers::genRandomString(40);
TST_LOG("Test string: [" << text << "].");
std::string documentPath, documentURL;
getDocumentPathAndURL("hello.odt", documentPath, documentURL, testname);
int kitcount = -1;
try
{
std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket(_uri, documentURL, testname);
std::shared_ptr<LOOLWebSocket> socket2 = loadDocAndGetSocket(_uri, documentURL, testname);
sendTextFrame(socket2, "userinactive");
deleteAll(socket, testname);
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\n" + text, testname);
TST_LOG("Validating what we sent before disconnecting.");
// Check if the document contains the pasted text.
const std::string selection = getAllText(socket, 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.
TST_LOG("Closing connection after pasting.");
socket->shutdown();
socket2->shutdown();
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
// Allow time to save and destroy before we connect again.
testNoExtraLoolKitsLeft();
TST_LOG("Loading again.");
try
{
// Load the same document and check that the last changes (pasted text) is saved.
std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket(_uri, documentURL, testname);
// Should have no new instances.
CPPUNIT_ASSERT_EQUAL(kitcount, countLoolKitProcesses(kitcount));
// Check if the document contains the pasted text.
const std::string selection = getAllText(socket, testname, text);
CPPUNIT_ASSERT_EQUAL("textselectioncontent: " + text, selection);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testSavePassiveOnDisconnect()
{
const char* testname = "savePassiveOnDisconnect ";
const std::string text = helpers::genRandomString(40);
TST_LOG("Test string: [" << text << "].");
std::string documentPath, documentURL;
getDocumentPathAndURL("hello.odt", documentPath, documentURL, testname);
int kitcount = -1;
try
{
std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket(_uri, documentURL, testname);
getResponseMessage(socket, "textselection", testname);
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
std::shared_ptr<LOOLWebSocket> socket2 = connectLOKit(_uri, request, _response, testname);
deleteAll(socket, testname);
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\n" + text, testname);
getResponseMessage(socket, "textselection:", testname);
// Check if the document contains the pasted text.
const std::string selection = getAllText(socket, 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.
TST_LOG("Closing connection after pasting.");
socket->shutdown(); // Should trigger saving.
socket2->shutdown();
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
// Allow time to save and destroy before we connect again.
testNoExtraLoolKitsLeft();
TST_LOG("Loading again.");
try
{
// Load the same document and check that the last changes (pasted text) is saved.
std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket(_uri, documentURL, testname);
getResponseMessage(socket, "textselection", testname);
// Should have no new instances.
CPPUNIT_ASSERT_EQUAL(kitcount, countLoolKitProcesses(kitcount));
// Check if the document contains the pasted text.
const std::string selection = getAllText(socket, testname);
CPPUNIT_ASSERT_EQUAL("textselectioncontent: " + text, selection);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testReloadWhileDisconnecting()
{
const char* testname = "reloadWhileDisconnecting ";
try
{
std::string documentPath, documentURL;
getDocumentPathAndURL("hello.odt", documentPath, documentURL, testname);
std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket(_uri, documentURL, testname);
deleteAll(socket, 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 int kitcount = getLoolKitProcessCount();
// Shutdown abruptly.
TST_LOG("Closing connection after pasting.");
socket->shutdown();
// Load the same document and check that the last changes (pasted text) is saved.
TST_LOG("Loading again.");
socket = loadDocAndGetSocket(_uri, documentURL, testname);
// Should have no new instances.
CPPUNIT_ASSERT_EQUAL(kitcount, countLoolKitProcesses(kitcount));
// Check if the document contains the pasted text.
const std::string expected = "aaa bbb ccc";
const std::string selection = getAllText(socket, testname, expected);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: ") + expected, selection);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testPasteBlank()
{
const char* testname = "pasteBlank ";
try
{
// Load a document and make it empty, then paste nothing into it.
std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket("hello.odt", _uri, testname);
deleteAll(socket, testname);
// Paste nothing into it.
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\n", testname);
// Check if the document contains the pasted text.
const std::string selection = getAllText(socket, testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: "), selection);
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testInsertDelete()
{
const char* testname = "insertDelete ";
try
{
std::vector<std::string> parts;
std::string response;
// Load a document
std::string documentPath, documentURL;
getDocumentPathAndURL("insert-delete.odp", documentPath, documentURL, testname);
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
std::shared_ptr<LOOLWebSocket> socket = connectLOKit(_uri, request, _response, testname);
sendTextFrame(socket, "load url=" + documentURL);
CPPUNIT_ASSERT_MESSAGE("cannot load the document " + documentURL, isDocumentLoaded(socket, testname));
// check total slides 1
TST_LOG("Expecting 1 slide.");
sendTextFrame(socket, "status");
response = getResponseString(socket, "status:", testname);
CPPUNIT_ASSERT_MESSAGE("did not receive a status: message as expected", !response.empty());
getPartHashCodes(testname, response.substr(7), parts);
CPPUNIT_ASSERT_EQUAL(1, (int)parts.size());
const std::string slide1Hash = parts[0];
// insert 10 slides
TST_LOG("Inserting 10 slides.");
for (size_t it = 1; it <= 10; it++)
{
sendTextFrame(socket, "uno .uno:InsertPage");
response = getResponseString(socket, "status:", testname);
CPPUNIT_ASSERT_MESSAGE("did not receive a status: message as expected", !response.empty());
getPartHashCodes(testname, 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
TST_LOG("Deleting 10 slides.");
for (size_t it = 1; it <= 10; it++)
{
// Explicitly delete the nth slide.
sendTextFrame(socket, "setclientpart part=" + std::to_string(it));
sendTextFrame(socket, "uno .uno:DeletePage");
response = getResponseString(socket, "status:", testname);
CPPUNIT_ASSERT_MESSAGE("did not receive a status: message as expected", !response.empty());
getPartHashCodes(testname, 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
TST_LOG("Undoing 10 slide deletes.");
for (size_t it = 1; it <= 10; it++)
{
sendTextFrame(socket, "uno .uno:Undo");
response = getResponseString(socket, "status:", testname);
CPPUNIT_ASSERT_MESSAGE("did not receive a status: message as expected", !response.empty());
getPartHashCodes(testname, 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
TST_LOG("Redoing 10 slide deletes.");
for (size_t it = 1; it <= 10; it++)
{
sendTextFrame(socket, "uno .uno:Redo");
response = getResponseString(socket, "status:", testname);
CPPUNIT_ASSERT_MESSAGE("did not receive a status: message as expected", !response.empty());
getPartHashCodes(testname, 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
TST_LOG("Expecting 1 slide.");
sendTextFrame(socket, "status");
response = getResponseString(socket, "status:", testname);
CPPUNIT_ASSERT_MESSAGE("did not receive a status: message as expected", !response.empty());
getPartHashCodes(testname, response.substr(7), parts);
CPPUNIT_ASSERT_EQUAL(1, (int)parts.size());
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testInactiveClient()
{
const char* testname = "inactiveClient ";
try
{
std::string documentPath, documentURL;
getDocumentPathAndURL("hello.odt", documentPath, documentURL, testname);
std::shared_ptr<LOOLWebSocket> socket1 = loadDocAndGetSocket(_uri, documentURL, "inactiveClient-1 ");
// Connect another and go inactive.
TST_LOG_NAME("inactiveClient-2 ", "Connecting second client.");
std::shared_ptr<LOOLWebSocket> socket2 = loadDocAndGetSocket(_uri, documentURL, "inactiveClient-2 ", true);
sendTextFrame(socket2, "userinactive", "inactiveClient-2 ");
// While second is inactive, make some changes.
deleteAll(socket1, "inactiveClient-1 ");
// Activate second.
sendTextFrame(socket2, "useractive", "inactiveClient-2 ");
SocketProcessor("Second ", socket2, [&](const std::string& msg)
{
const auto token = LOOLProtocol::getFirstToken(msg);
// 'window:' is e.g. 'window: {"id":"4","action":"invalidate","rectangle":"0, 0,
// 0, 0"}', which is probably fine, given that other invalidations are also
// expected.
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:" ||
token == "editor:" ||
token == "context:" ||
token == "window:" ||
token == "tableselected:");
// End when we get state changed.
return (token != "statechanged:");
});
TST_LOG("Second client finished.");
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::getPartHashCodes(const std::string& testname,
const std::string& response,
std::vector<std::string>& parts)
{
std::string line;
std::istringstream istr(response);
std::getline(istr, line);
TST_LOG("Reading parts from [" << response << "].");
// Expected format is something like 'type= parts= current= width= height= viewid= [hiddenparts=]'.
std::vector<std::string> tokens(LOOLProtocol::tokenize(line, ' '));
#if defined CPPUNIT_ASSERT_GREATEREQUAL
CPPUNIT_ASSERT_GREATEREQUAL(static_cast<size_t>(7), tokens.size());
#else
CPPUNIT_ASSERT_MESSAGE("Expected at least 7 tokens.", static_cast<size_t>(7) <= tokens.size());
#endif
const std::string 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()));
TST_LOG("Status reports " << totalParts << " parts.");
Poco::RegularExpression endLine("[^\n\r]+");
Poco::RegularExpression number("^[0-9]+$");
Poco::RegularExpression::MatchVec matches;
int offset = 0;
parts.clear();
while (endLine.match(response, offset, matches) > 0)
{
CPPUNIT_ASSERT_EQUAL(1, (int)matches.size());
const std::string str = response.substr(matches[0].offset, matches[0].length);
if (number.match(str, 0))
{
parts.push_back(str);
}
offset = static_cast<int>(matches[0].offset + matches[0].length);
}
TST_LOG("Found " << parts.size() << " part names/codes.");
// 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 Poco::Dynamic::Var result = parser.parse(message);
const auto& command = result.extract<Poco::JSON::Object::Ptr>();
std::string text = command->get("commandName").toString();
CPPUNIT_ASSERT_EQUAL(std::string(".uno:CellCursor"), text);
text = command->get("commandValues").toString();
CPPUNIT_ASSERT(!text.empty());
std::vector<std::string> position(LOOLProtocol::tokenize(text, ','));
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(const std::function<void(const std::shared_ptr<LOOLWebSocket>& socket,
int cursorX, int cursorY,
int cursorWidth, int cursorHeight,
int docWidth, int docHeight)>& keyhandler,
const 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;
std::shared_ptr<LOOLWebSocket> 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 char* testname = "insertAnnotationWriter ";
std::string documentPath, documentURL;
getDocumentPathAndURL("hello.odt", documentPath, documentURL, testname);
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket(_uri, documentURL, testname);
// Insert comment.
sendTextFrame(socket, "uno .uno:InsertAnnotation", testname);
assertResponseString(socket, "invalidatetiles:", testname);
// Paste some text.
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\nxxx yyy zzzz", testname);
// Read it back.
std::string res = getAllText(socket, testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: xxx yyy zzzz"), res);
// Can we edit the comment?
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\naaa bbb ccc", testname);
res = getAllText(socket, testname);
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", testname);
sendTextFrame(socket, "mouse type=buttonup x=1600 y=1600 count=1 buttons=1 modifier=0", testname);
// Read body text.
res = getAllText(socket, 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", testname);
sendTextFrame(socket, "mouse type=buttonup x=13855 y=1893 count=1 buttons=1 modifier=0", testname);
res = getAllText(socket, testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: aaa bbb ccc"), res);
// Can we still edit the comment?
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\nand now for something completely different", testname);
res = getAllText(socket, testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: and now for something completely different"), res);
// Close and reopen the same document and test again.
socket->shutdown();
// Make sure the document is fully unloaded.
testNoExtraLoolKitsLeft();
TST_LOG("Reloading ");
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", testname);
sendTextFrame(socket, "mouse type=buttonup x=1600 y=1600 count=1 buttons=1 modifier=0", testname);
// Read body text.
res = getAllText(socket, 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", testname);
sendTextFrame(socket, "mouse type=buttonup x=13855 y=1893 count=1 buttons=1 modifier=0", testname);
res = getAllText(socket, testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: and now for something completely different"), res);
// Can we still edit the comment?
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\nblah blah xyz", testname);
res = getAllText(socket, testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: blah blah xyz"), res);
}
void HTTPWSTest::testEditAnnotationWriter()
{
const char* testname = "editAnnotationWriter ";
std::string documentPath, documentURL;
getDocumentPathAndURL("with_comment.odt", documentPath, documentURL, testname);
std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket(_uri, documentURL, testname);
// Click in the body.
sendTextFrame(socket, "mouse type=buttondown x=1600 y=1600 count=1 buttons=1 modifier=0", testname);
sendTextFrame(socket, "mouse type=buttonup x=1600 y=1600 count=1 buttons=1 modifier=0", testname);
// Read body text.
std::string res = getAllText(socket, 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", testname);
sendTextFrame(socket, "mouse type=buttonup x=13855 y=1893 count=1 buttons=1 modifier=0", testname);
res = getAllText(socket, testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: blah blah xyz"), res);
// Can we still edit the comment?
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\nand now for something completely different", testname);
res = getAllText(socket, testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: and now for something completely different"), res);
const int kitcount = getLoolKitProcessCount();
// Close and reopen the same document and test again.
TST_LOG("Closing connection after pasting.");
socket->shutdown();
TST_LOG("Reloading ");
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", testname);
sendTextFrame(socket, "mouse type=buttonup x=1600 y=1600 count=1 buttons=1 modifier=0", testname);
// Read body text.
res = getAllText(socket, 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", testname);
sendTextFrame(socket, "mouse type=buttonup x=13855 y=1893 count=1 buttons=1 modifier=0", testname);
res = getAllText(socket, testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: and now for something completely different"), res);
// Can we still edit the comment?
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\nnew text different", testname);
res = getAllText(socket, testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: new text different"), res);
}
void HTTPWSTest::testInsertAnnotationCalc()
{
const char* testname = "insertAnnotationCalc ";
std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket("setclientpart.ods", _uri, testname);
// Insert comment.
sendTextFrame(socket, "uno .uno:InsertAnnotation", testname);
// Paste some text.
sendTextFrame(socket, "paste mimetype=text/plain;charset=utf-8\naaa bbb ccc", testname);
// Read it back.
std::string res = getAllText(socket, testname);
CPPUNIT_ASSERT_EQUAL(std::string("textselectioncontent: aaa bbb ccc"), res);
}
void HTTPWSTest::testCalcEditRendering()
{
const char* testname = "calcEditRendering ";
std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket("calc_render.xls", _uri, testname);
sendTextFrame(socket, "mouse type=buttondown x=5000 y=5 count=1 buttons=1 modifier=0", testname);
sendTextFrame(socket, "key type=input char=97 key=0", testname);
sendTextFrame(socket, "key type=input char=98 key=0", testname);
sendTextFrame(socket, "key type=input char=99 key=0", testname);
assertResponseString(socket, "cellformula: abc", testname);
const char* req = "tilecombine nviewid=0 part=0 width=512 height=512 tileposx=3840 tileposy=0 tilewidth=7680 tileheight=7680";
sendTextFrame(socket, req, testname);
const std::vector<char> tile = getResponseMessage(socket, "tile:", testname);
TST_LOG("size: " << tile.size());
// Return early for now when on LO >= 5.2.
int major = 0;
int minor = 0;
getServerVersion(socket, major, minor, testname);
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;
std::vector<png_bytep> 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;
std::vector<png_bytep> 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);
TST_LOG("\nFAILURE: Tile not rendered as expected @ row #" << itRow);
break;
}
}
}
/// When a second view is loaded to a Calc doc,
/// the first stops rendering correctly.
/// This only happens at high rows.
void HTTPWSTest::testCalcRenderAfterNewView51()
{
const char* testname = "calcRenderAfterNewView51 ";
// Load a doc with the cursor saved at a top row.
std::string documentPath, documentURL;
getDocumentPathAndURL("empty.ods", documentPath, documentURL, testname);
std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket(_uri, documentURL, testname);
int major = 0;
int minor = 0;
getServerVersion(socket, major, minor, testname);
if (major != 5 || minor != 1)
{
TST_LOG("Skipping test on incompatible client ["
<< major << '.' << minor << "], expected [5.1].");
return;
}
// Page Down until we get to the bottom of the doc.
for (int i = 0; i < 40; ++i)
{
sendTextFrame(socket, "key type=input char=0 key=1031", testname);
}
// Wait for status due to doc resize.
assertResponseString(socket, "status:", testname);
const char* req = "tilecombine nviewid=0 part=0 width=256 height=256 tileposx=0 tileposy=253440 tilewidth=3840 tileheight=3840";
// Get tile.
const std::vector<char> tile1 = getTileAndSave(socket, req, "/tmp/calc_render_51_orig.png", testname);
// Connect second client, which will load at the top.
TST_LOG("Connecting second client.");
std::shared_ptr<LOOLWebSocket> socket2 = loadDocAndGetSocket(_uri, documentURL, testname);
// Up one row on the first view to trigger the bug.
TST_LOG("Up.");
sendTextFrame(socket, "key type=input char=0 key=1025", testname);
assertResponseString(socket, "invalidatetiles:", testname); // Up invalidates.
// Get same tile again.
const std::vector<char> tile2 = getTileAndSave(socket, req, "/tmp/calc_render_51_sec.png", testname);
CPPUNIT_ASSERT(tile1 == tile2);
}
void HTTPWSTest::testCalcRenderAfterNewView53()
{
const char* testname = "calcRenderAfterNewView53 ";
// Load a doc with the cursor saved at a top row.
std::string documentPath, documentURL;
getDocumentPathAndURL("calc-render.ods", documentPath, documentURL, testname);
std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket(_uri, documentURL, testname);
int major = 0;
int minor = 0;
getServerVersion(socket, major, minor, testname);
if (major < 5 || minor < 3)
{
TST_LOG("Skipping test on incompatible client ["
<< major << '.' << minor << "], expected [>=5.3].");
return;
}
sendTextFrame(socket, "clientvisiblearea x=750 y=1861 width=20583 height=6997", testname);
sendTextFrame(socket, "key type=input char=0 key=1031", testname);
// Get tile.
const char* req = "tilecombine nviewid=0 part=0 width=256 height=256 tileposx=0 tileposy=291840 tilewidth=3840 tileheight=3840 oldwid=0";
const std::vector<char> tile1 = getTileAndSave(socket, req, "/tmp/calc_render_53_orig.png", testname);
// Connect second client, which will load at the top.
TST_LOG("Connecting second client.");
std::shared_ptr<LOOLWebSocket> socket2 = loadDocAndGetSocket(_uri, documentURL, testname);
TST_LOG("Waiting for cellviewcursor of second on first.");
assertResponseString(socket, "cellviewcursor:", testname);
// Get same tile again.
const std::vector<char> tile2 = getTileAndSave(socket, req, "/tmp/calc_render_53_sec.png", testname);
CPPUNIT_ASSERT(tile1 == tile2);
// Don't let them go out of scope and disconnect.
socket2->shutdown();
socket->shutdown();
}
std::string HTTPWSTest::getFontList(const std::string& message)
{
Poco::JSON::Parser parser;
const Poco::Dynamic::Var result = parser.parse(message);
const auto& command = result.extract<Poco::JSON::Object::Ptr>();
std::string text = command->get("commandName").toString();
CPPUNIT_ASSERT_EQUAL(std::string(".uno:CharFontName"), text);
text = command->get("commandValues").toString();
return text;
}
void HTTPWSTest::testFontList()
{
const char* testname = "fontList ";
try
{
// Load a document
std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket("setclientpart.odp", _uri, testname);
sendTextFrame(socket, "commandvalues command=.uno:CharFontName", testname);
const std::vector<char> 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());
}
}
double HTTPWSTest::getColRowSize(const std::string& property, const std::string& message, int index)
{
Poco::JSON::Parser parser;
const Poco::Dynamic::Var result = parser.parse(message);
const auto& command = result.extract<Poco::JSON::Object::Ptr>();
std::string text = command->get("commandName").toString();
CPPUNIT_ASSERT_EQUAL(std::string(".uno:ViewRowColumnHeaders"), text);
CPPUNIT_ASSERT(command->isArray(property));
Poco::JSON::Array::Ptr array = command->getArray(property);
CPPUNIT_ASSERT(array->isObject(index));
Poco::SharedPtr<Poco::JSON::Object> 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, const std::string& testname)
{
std::vector<char> response;
response = getResponseMessage(socket, "commandvalues:", testname);
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()
{
const char* testname = "columnRowResize ";
try
{
std::vector<char> response;
std::string documentPath, documentURL;
double oldHeight, oldWidth;
getDocumentPathAndURL("setclientpart.ods", documentPath, documentURL, testname);
std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket(_uri, documentURL, testname);
const std::string commandValues = "commandvalues command=.uno:ViewRowColumnHeaders";
sendTextFrame(socket, commandValues);
response = getResponseMessage(socket, "commandvalues:", testname);
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(), testname);
sendTextFrame(socket, commandValues, testname);
response = getResponseMessage(socket, "commandvalues:", testname);
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(), testname);
sendTextFrame(socket, commandValues, testname);
response = getResponseMessage(socket, "commandvalues:", testname);
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()
{
const char* testname = "optimalResize ";
try
{
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);
std::string documentPath, documentURL;
getDocumentPathAndURL("empty.ods", documentPath, documentURL, testname);
std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket(_uri, documentURL, testname);
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(), testname);
sendTextFrame(socket, commandValues, testname);
newWidth = getColRowSize(socket, "columns", 0, testname);
}
// 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(), testname);
sendTextFrame(socket, commandValues, testname);
newHeight = getColRowSize(socket, "rows", 0, testname);
}
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(), testname);
sendTextFrame(socket, "uno .uno:SetOptimalColumnWidthDirect", testname);
sendTextFrame(socket, commandValues, testname);
optimalWidth = getColRowSize(socket, "columns", 0, testname);
CPPUNIT_ASSERT(optimalWidth < newWidth);
}
// send optimal row height
{
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);
std::ostringstream oss;
Poco::JSON::Stringifier::stringify(objSelect, oss);
sendTextFrame(socket, "uno .uno:SelectRow " + oss.str(), testname);
oss.str("");
oss.clear();
Poco::JSON::Stringifier::stringify(objOptHeight, oss);
sendTextFrame(socket, "uno .uno:SetOptimalRowHeight " + oss.str(), testname);
sendTextFrame(socket, commandValues, testname);
optimalHeight = getColRowSize(socket, "rows", 0, testname);
CPPUNIT_ASSERT(optimalHeight < newHeight);
}
}
catch (const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
void HTTPWSTest::testGraphicInvalidate()
{
const char* testname = "graphicInvalidate ";
try
{
// Load a document.
std::shared_ptr<LOOLWebSocket> 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 char* testname = "cursorPosition ";
// Load a document.
std::string docPath;
std::string docURL;
std::string response;
getDocumentPathAndURL("Example.odt", docPath, docURL, testname);
std::shared_ptr<LOOLWebSocket> socket0 = loadDocAndGetSocket(_uri, docURL, testname);
// receive cursor position
response = getResponseString(socket0, "invalidatecursor:", testname);
Poco::JSON::Parser parser0;
const Poco::Dynamic::Var result0 = parser0.parse(response.substr(17));
const auto& command0 = result0.extract<Poco::JSON::Object::Ptr>();
CPPUNIT_ASSERT_MESSAGE("missing property rectangle", command0->has("rectangle"));
std::vector<std::string> cursorTokens(LOOLProtocol::tokenize(command0->get("rectangle").toString(), ','));
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), cursorTokens.size());
// Create second view
std::shared_ptr<LOOLWebSocket> socket1 = loadDocAndGetSocket(_uri, docURL, testname);
//receive view cursor position
response = getResponseString(socket1, "invalidateviewcursor:", testname);
Poco::JSON::Parser parser;
const Poco::Dynamic::Var result = parser.parse(response.substr(21));
const auto& command = result.extract<Poco::JSON::Object::Ptr>();
CPPUNIT_ASSERT_MESSAGE("missing property rectangle", command->has("rectangle"));
std::vector<std::string> viewTokens(LOOLProtocol::tokenize(command->get("rectangle").toString(), ','));
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), viewTokens.size());
// 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.
static_assert(MAX_DOCUMENTS >= 2, "MAX_DOCUMENTS must be at least 2");
const char* testname = "alertAllUsers ";
try
{
std::shared_ptr<LOOLWebSocket> socket[4];
socket[0] = loadDocAndGetSocket("hello.odt", _uri, testname);
socket[1] = loadDocAndGetSocket("Example.odt", _uri, testname);
// Simulate disk full.
sendTextFrame(socket[0], "uno .uno:fakeDiskFull", testname);
// Assert that both clients get the error.
for (int i = 0; i < 2; i++)
{
const std::string response = assertResponseString(socket[i], "error:", testname);
std::vector<std::string> tokens(LOOLProtocol::tokenize(response.substr(6), ' '));
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, testname);
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, docURL);
std::shared_ptr<LOOLWebSocket> socket0 = connectLOKit(_uri, request, _response, testname);
std::shared_ptr<LOOLWebSocket> socket1 = connectLOKit(_uri, request, _response, testname);
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());
}
}
void HTTPWSTest::testUndoConflict()
{
const std::string testname = "testUndoConflict-";
Poco::JSON::Parser parser;
std::string docPath;
std::string docURL;
int conflict;
getDocumentPathAndURL("empty.odt", docPath, docURL, testname);
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, docURL);
std::shared_ptr<LOOLWebSocket> socket0 = connectLOKit(_uri, request, _response, testname);
std::shared_ptr<LOOLWebSocket> socket1 = connectLOKit(_uri, request, _response, testname);
std::string response;
try
{
// Load first view
sendTextFrame(socket0, "load url=" + docURL, testname + "0 ");
response = getResponseString(socket0, "invalidatecursor:", testname + "0 ");
// Load second view
sendTextFrame(socket1, "load url=" + docURL, testname + "1 ");
response = getResponseString(socket1, "invalidatecursor:", testname + "1 ");
// edit first view
sendChar(socket0, 'A', skNone, testname + "0 ");
response = getResponseString(socket0, "invalidateviewcursor: ", testname + "0 ");
response = getResponseString(socket0, "invalidateviewcursor: ", testname + "0 ");
// edit second view
sendChar(socket1, 'B', skNone, testname + "1 ");
response = getResponseString(socket1, "invalidateviewcursor: ", testname + "1 ");
response = getResponseString(socket1, "invalidateviewcursor: ", testname + "1 ");
// try to undo first view
sendTextFrame(socket0, "uno .uno:Undo", testname + "0 ");
// undo conflict
response = getResponseString(socket0, "unocommandresult:", testname + "0 ");
Poco::JSON::Object::Ptr objJSON = parser.parse(response.substr(17)).extract<Poco::JSON::Object::Ptr>();
CPPUNIT_ASSERT_EQUAL(std::string(".uno:Undo"), objJSON->get("commandName").toString());
CPPUNIT_ASSERT_EQUAL(std::string("true"), objJSON->get("success").toString());
CPPUNIT_ASSERT(objJSON->has("result"));
const Poco::Dynamic::Var parsedResultJSON = objJSON->get("result");
const auto& resultObj = parsedResultJSON.extract<Poco::JSON::Object::Ptr>();
CPPUNIT_ASSERT_EQUAL(std::string("long"), resultObj->get("type").toString());
CPPUNIT_ASSERT(Poco::strToInt(resultObj->get("value").toString(), conflict, 10));
CPPUNIT_ASSERT(conflict > 0); /*UNDO_CONFLICT*/
}
catch(const Poco::Exception& exc)
{
CPPUNIT_FAIL(exc.displayText());
}
}
CPPUNIT_TEST_SUITE_REGISTRATION(HTTPWSTest);
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */