wsd: test: add SaveOnExit test

Refactor UnitWOPIDocumentConflict.cpp into
WOPIUploadConflictCommon.hpp and reuse for both
DocumentConflict and SaveOnExit tests.

Change-Id: I54ec1e37e5e9c6298b12a2b2e596363683fb2e34
Signed-off-by: Ashod Nakashian <ashod.nakashian@collabora.co.uk>
pull/4153/head
Ashod Nakashian 2022-02-05 18:14:38 -05:00 committed by Ashod Nakashian
parent 251df512ec
commit 203a58f6d8
5 changed files with 402 additions and 31 deletions

View File

@ -324,6 +324,7 @@ noinst_HEADERS = $(wsd_headers) $(shared_headers) $(kit_headers) \
test/test.hpp \
test/testlog.hpp \
test/HttpTestServer.hpp \
test/WOPIUploadConflictCommon.hpp \
test/helpers.hpp
dist-hook:

View File

@ -9,7 +9,7 @@ noinst_PROGRAMS = fakesockettest unittest unithttplib
AM_CXXFLAGS = $(CPPUNIT_CFLAGS) -DTDOC=\"$(abs_top_srcdir)/test/data\" \
-I${top_srcdir}/common -I${top_srcdir}/net -I${top_srcdir}/wsd -I${top_srcdir}/kit \
-I${top_srcdir} \
-I${top_srcdir} -I${top_srcdir}/test \
-pthread -DCOOLWSD_DATADIR='"@COOLWSD_DATADIR@"' \
-DCOOLWSD_CONFIGDIR='"@COOLWSD_CONFIGDIR@"' \
-DDEBUG_ABSSRCDIR='"@abs_srcdir@"' \
@ -26,8 +26,8 @@ noinst_LTLIBRARIES = \
unit-wopi-async-upload-close.la unit-wopi-async-upload-modify.la \
unit-wopi-async-upload-modifyclose.la \
unit-wopi-async-slow.la \
unit-wopi-ownertermination.la unit-wopi-versionrestore.la \
unit-wopi-documentconflict.la unit_wopi_renamefile.la unit_wopi_watermark.la \
unit-wopi-ownertermination.la unit-wopi-versionrestore.la unit-wopi-documentconflict.la \
unit-wopi-save-on-exit.la unit_wopi_renamefile.la unit_wopi_watermark.la \
unit-wopi-lock.la \
unit-tiff-load.la \
unit-large-paste.la \
@ -178,6 +178,8 @@ unit_wopi_versionrestore_la_SOURCES = UnitWOPIVersionRestore.cpp
unit_wopi_versionrestore_la_LIBADD = $(CPPUNIT_LIBS)
unit_wopi_documentconflict_la_SOURCES = UnitWOPIDocumentConflict.cpp
unit_wopi_documentconflict_la_LIBADD = $(CPPUNIT_LIBS)
unit_wopi_save_on_exit_la_SOURCES = UnitWOPISaveOnExit.cpp
unit_wopi_save_on_exit_la_LIBADD = $(CPPUNIT_LIBS)
unit_wopi_renamefile_la_SOURCES = UnitWOPIRenameFile.cpp
unit_wopi_renamefile_la_LIBADD = $(CPPUNIT_LIBS)
unit_wopi_lock_la_SOURCES = UnitWOPILock.cpp
@ -255,8 +257,8 @@ TESTS = \
unit-wopi-async-upload-close.la unit-wopi-async-upload-modify.la \
unit-wopi-async-upload-modifyclose.la \
unit-wopi-async-slow.la \
unit-wopi-ownertermination.la unit-wopi-versionrestore.la \
unit-wopi-documentconflict.la unit_wopi_renamefile.la unit_wopi_watermark.la \
unit-wopi-ownertermination.la unit-wopi-versionrestore.la unit-wopi-documentconflict.la \
unit-wopi-save-on-exit.la unit_wopi_renamefile.la unit_wopi_watermark.la \
unit-wopi-lock.la \
unit-http.la \
unit-tiff-load.la \

View File

@ -7,39 +7,23 @@
#include <config.h>
#include "Util.hpp"
#include "WOPIUploadConflictCommon.hpp"
#include "WopiTestServer.hpp"
#include <string>
#include <memory>
#include <Poco/Net/HTTPRequest.h>
#include "Util.hpp"
#include "Log.hpp"
#include "Unit.hpp"
#include "UnitHTTP.hpp"
#include "helpers.hpp"
#include "lokassert.hpp"
#include <Poco/Net/HTTPRequest.h>
#include <string>
/**
* This test asserts that the unsaved changes in the opened document are
* discarded in case document is changed in storage behind our back. We don't
* want to overwrite the document which is in storage when the user asks us to
* upload to storage, without giving the user the opportunity to decide.
*
* There are multiple scenarios to test.
*
* The way this works is as follows:
* 1. Load a document.
* 2. When we get 'status:' in onFilterSendMessage, we modify it.
* 3. Simulate content-change in storage and attempt to save it.
* 4a. Disconnect and the modified data must be discarded.
* 4b. Save and, on getting the documentconflict error, discard.
* 4c. Close and, on getting the documentconflict error, discard.
* 4d. Save and, on getting the documentconflict error, overwrite.
* 5. Load the document again and verify the expected contents.
* 6. Move to the next test scenario.
*/
class UnitWOPIDocumentConflict : public WopiTestServer
class UnitWOPIDocumentConflict : public WOPIUploadConflictCommon
{
protected:
STATES_ENUM(Phase, _phase, Load, WaitLoadStatus, WaitModifiedStatus, WaitDocClose);
STATES_ENUM(Scenario, _scenario, Disconnect, SaveDiscard, CloseDiscard, SaveOverwrite, VerifyOverwrite);
@ -49,7 +33,7 @@ class UnitWOPIDocumentConflict : public WopiTestServer
public:
UnitWOPIDocumentConflict()
: WopiTestServer("UnitWOPIDocumentConflict", OriginalDocContent)
: WOPIUploadConflictCommon("UnitWOPIDocumentConflict", OriginalDocContent)
, _phase(Phase::Load)
, _scenario(Scenario::Disconnect)
{

View File

@ -0,0 +1,107 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* 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 "WOPIUploadConflictCommon.hpp"
#include <string>
#include <memory>
#include <Poco/Net/HTTPRequest.h>
#include "Util.hpp"
#include "Log.hpp"
#include "Unit.hpp"
#include "UnitHTTP.hpp"
#include "helpers.hpp"
#include "lokassert.hpp"
class UnitWOPISaveOnExit : public WOPIUploadConflictCommon
{
using WOPIUploadConflictCommon::Phase;
using WOPIUploadConflictCommon::Scenario;
using WOPIUploadConflictCommon::OriginalDocContent;
using WOPIUploadConflictCommon::ModifiedOriginalDocContent;
using WOPIUploadConflictCommon::ConflictingDocContent;
public:
UnitWOPISaveOnExit()
: WOPIUploadConflictCommon("UnitWOPISaveOnExit", OriginalDocContent)
{
}
void configure(Poco::Util::LayeredConfiguration& config)
{
WopiTestServer::configure(config);
config.setBool("per_document.always_save_on_exit", true);
}
void assertGetFileRequest(const Poco::Net::HTTPRequest& /*request*/)
{
LOG_TST("Testing " << toString(_scenario));
LOK_ASSERT_STATE(_phase, Phase::WaitLoadStatus);
// Note: the expected contents for each scenario
// is the result of the *previous* phase!
std::string expectedContents;
switch (_scenario)
{
case Scenario::Disconnect:
expectedContents = OriginalDocContent;
break;
case Scenario::SaveDiscard:
expectedContents = ModifiedOriginalDocContent; // Disconnect will clobber.
break;
case Scenario::CloseDiscard:
case Scenario::SaveOverwrite:
LOK_ASSERT_EQUAL_MESSAGE("Unexpected contents in storage",
std::string(ConflictingDocContent), getFileContent());
setFileContent(OriginalDocContent); // Reset to test overwriting.
expectedContents = OriginalDocContent;
break;
case Scenario::VerifyOverwrite:
expectedContents = ModifiedOriginalDocContent;
break;
}
LOK_ASSERT_EQUAL_MESSAGE("Unexpected contents in storage", expectedContents,
getFileContent());
}
std::unique_ptr<http::Response> assertPutFileRequest(const Poco::Net::HTTPRequest& /*request*/)
{
LOG_TST("Testing " << toString(_scenario));
LOK_ASSERT_STATE(_phase, Phase::WaitDocClose);
switch (_scenario)
{
case Scenario::Disconnect:
LOG_TST("Clobbered in the disconnect scenario");
break;
case Scenario::SaveDiscard:
case Scenario::CloseDiscard:
case Scenario::VerifyOverwrite:
LOK_ASSERT_FAIL("Unexpectedly overwritting the document in storage");
break;
case Scenario::SaveOverwrite:
LOK_ASSERT_EQUAL_MESSAGE("Unexpected contents in storage",
std::string(ModifiedOriginalDocContent), getFileContent());
LOG_TST("Closing the document to verify its contents after reloading");
WSD_CMD("closedocument");
break;
}
return nullptr;
}
};
UnitBase* unit_create_wsd(void) { return new UnitWOPISaveOnExit(); }
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View File

@ -0,0 +1,277 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* 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 <string>
#include <memory>
#include <Poco/Net/HTTPRequest.h>
#include "WopiTestServer.hpp"
/**
* This is a base class with a number of test cases which assert that the
* unsaved changes in the opened document are discarded in case document
* is changed in storage behind our back. We don't want to overwrite
* the document which is in storage when the user asks us to
* upload to storage, without giving the user the opportunity to decide.
*
* There are multiple scenarios to test.
*
* The way this works is as follows:
* 1. Load a document.
* 2. When we get 'status:' in onFilterSendMessage, we modify it.
* 3. Simulate content-change in storage and attempt to save it.
* 4a. Disconnect and the modified data must be discarded.
* 4b. Save and, on getting the documentconflict error, discard.
* 4c. Close and, on getting the documentconflict error, discard.
* 4d. Save and, on getting the documentconflict error, overwrite.
* 5. Load the document again and verify the expected contents.
* 6. Move to the next test scenario.
*/
class WOPIUploadConflictCommon : public WopiTestServer
{
protected:
STATES_ENUM(Phase, _phase, Load, WaitLoadStatus, WaitModifiedStatus, WaitDocClose);
STATES_ENUM(Scenario, _scenario, Disconnect, SaveDiscard, CloseDiscard, SaveOverwrite,
VerifyOverwrite);
static constexpr auto OriginalDocContent = "Original contents";
static constexpr auto ModifiedOriginalDocContent = "\ufeffaOriginal contents\n";
static constexpr auto ConflictingDocContent = "Modified in-storage contents";
public:
WOPIUploadConflictCommon(std::string testname, const std::string& fileContent)
: WopiTestServer(std::move(testname), fileContent)
, _phase(Phase::Load)
, _scenario(Scenario::Disconnect)
{
}
void assertGetFileRequest(const Poco::Net::HTTPRequest& /*request*/) override
{
LOG_TST("Testing " << toString(_scenario));
LOK_ASSERT_STATE(_phase, Phase::WaitLoadStatus);
// Note: the expected contents for each scenario
// is the result of the *previous* phase!
std::string expectedContents;
switch (_scenario)
{
case Scenario::Disconnect:
expectedContents = OriginalDocContent;
break;
case Scenario::SaveDiscard:
expectedContents = ConflictingDocContent;
break;
case Scenario::CloseDiscard:
case Scenario::SaveOverwrite:
LOK_ASSERT_EQUAL_MESSAGE("Unexpected contents in storage",
std::string(ConflictingDocContent), getFileContent());
setFileContent(OriginalDocContent); // Reset to test overwriting.
expectedContents = OriginalDocContent;
break;
case Scenario::VerifyOverwrite:
expectedContents = ModifiedOriginalDocContent;
break;
}
LOK_ASSERT_EQUAL_MESSAGE("Unexpected contents in storage", expectedContents,
getFileContent());
}
std::unique_ptr<http::Response>
assertPutFileRequest(const Poco::Net::HTTPRequest& /*request*/) override
{
LOG_TST("Testing " << toString(_scenario));
LOK_ASSERT_STATE(_phase, Phase::WaitDocClose);
switch (_scenario)
{
case Scenario::Disconnect:
case Scenario::SaveDiscard:
case Scenario::CloseDiscard:
case Scenario::VerifyOverwrite:
LOK_ASSERT_FAIL("Unexpectedly overwritting the document in storage");
break;
case Scenario::SaveOverwrite:
LOK_ASSERT_EQUAL_MESSAGE("Unexpected contents in storage",
std::string(ModifiedOriginalDocContent), getFileContent());
LOG_TST("Closing the document to verify its contents after reloading");
WSD_CMD("closedocument");
break;
}
return nullptr;
}
bool onDocumentLoaded(const std::string& message) override
{
LOG_TST("Testing " << toString(_scenario) << ": [" << message << ']');
LOK_ASSERT_STATE(_phase, Phase::WaitLoadStatus);
if (_scenario != Scenario::VerifyOverwrite)
{
LOG_TST("Modifying the document");
TRANSITION_STATE(_phase, Phase::WaitModifiedStatus);
// modify the currently opened document; type 'a'
WSD_CMD("key type=input char=97 key=0");
WSD_CMD("key type=up char=0 key=512");
}
else
{
LOG_TST("Closing the document to finish testing");
TRANSITION_STATE_MSG(_phase, Phase::WaitDocClose, "Skipping modifications");
WSD_CMD("closedocument");
}
return true;
}
bool onDocumentModified(const std::string& message) override
{
LOG_TST("Testing " << toString(_scenario) << ": [" << message << ']');
LOK_ASSERT_STATE(_phase, Phase::WaitModifiedStatus);
// Change the underlying document in storage.
LOG_TST("Changing document contents in storage");
setFileContent(ConflictingDocContent);
TRANSITION_STATE(_phase, Phase::WaitDocClose);
switch (_scenario)
{
case Scenario::Disconnect:
LOG_TST("Disconnecting");
deleteSocketAt(0);
break;
case Scenario::SaveDiscard:
case Scenario::SaveOverwrite:
// Save the document; wsd should detect now that document has
// been changed underneath it and send us:
// "error: cmd=storage kind=documentconflict"
LOG_TST("Saving the document");
WSD_CMD("save dontTerminateEdit=0 dontSaveIfUnmodified=0");
break;
case Scenario::CloseDiscard:
// Close the document; wsd should detect now that document has
// been changed underneath it and send us:
// "error: cmd=storage kind=documentconflict"
LOG_TST("Closing the document");
WSD_CMD("closedocument");
break;
case Scenario::VerifyOverwrite:
LOK_ASSERT_FAIL("Unexpected modification in " + toString(_scenario));
break;
}
return true;
}
bool onDocumentError(const std::string& message) override
{
LOG_TST("Testing " << toString(_scenario) << ": [" << message << ']');
LOK_ASSERT_STATE(_phase, Phase::WaitDocClose);
LOK_ASSERT_MESSAGE("Expect only documentconflict errors",
message == "error: cmd=storage kind=documentconflict");
switch (_scenario)
{
case Scenario::Disconnect:
LOK_ASSERT_FAIL("We can't possibly get anything after disconnecting");
break;
case Scenario::SaveDiscard:
case Scenario::CloseDiscard:
LOG_TST("Discarding own changes via closedocument");
WSD_CMD("closedocument");
break;
case Scenario::SaveOverwrite:
LOG_TST("Overwriting with own version via savetostorage");
WSD_CMD("savetostorage force=1");
break;
case Scenario::VerifyOverwrite:
LOK_ASSERT_FAIL("Unexpected error in " + toString(_scenario));
break;
}
return true;
}
// Called when we have modified document data at exit.
void fail(const std::string& reason) override
{
// We expect this to happen only with the disonnection test,
// because only in that case there is no user input.
LOK_ASSERT_MESSAGE("Expected reason to be 'Unsaved data detected'",
Util::startsWith(reason, "Unsaved data detected"));
LOK_ASSERT_MESSAGE("Expected to be in Phase::WaitDocClose but was " + toString(_phase),
_phase == Phase::WaitDocClose);
LOK_ASSERT_MESSAGE("Expected to be in Scenario::Disconnect but was " + toString(_scenario),
_scenario == Scenario::Disconnect);
}
// Wait for clean unloading.
void onDocBrokerDestroy(const std::string& docKey) override
{
LOG_TST("Testing " << toString(_scenario) << " with dockey [" << docKey << "] closed.");
LOK_ASSERT_STATE(_phase, Phase::WaitDocClose);
switch (_scenario)
{
case Scenario::Disconnect:
TRANSITION_STATE(_scenario, Scenario::SaveDiscard);
break;
case Scenario::SaveDiscard:
TRANSITION_STATE(_scenario, Scenario::CloseDiscard);
break;
case Scenario::CloseDiscard:
TRANSITION_STATE(_scenario, Scenario::SaveOverwrite);
break;
case Scenario::SaveOverwrite:
TRANSITION_STATE(_scenario, Scenario::VerifyOverwrite);
break;
case Scenario::VerifyOverwrite:
passTest("Finished all test scenarios without issues");
break;
}
TRANSITION_STATE(_phase, Phase::Load);
}
void invokeWSDTest() override
{
switch (_phase)
{
case Phase::Load:
{
LOG_TST("Loading the document for " << toString(_scenario));
TRANSITION_STATE(_phase, Phase::WaitLoadStatus);
initWebsocket("/wopi/files/0?access_token=anything");
WSD_CMD("load url=" + getWopiSrc());
}
break;
case Phase::WaitLoadStatus:
{
}
break;
case Phase::WaitModifiedStatus:
{
}
break;
case Phase::WaitDocClose:
{
}
break;
}
}
};
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */