25784d8677
Change-Id: Ibd8589e3d6a2ca226578258c3cc1b9d85cad2d2c Signed-off-by: Ashod Nakashian <ashod.nakashian@collabora.co.uk>
354 lines
13 KiB
C++
354 lines
13 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
/*
|
|
* Copyright the Collabora Online contributors.
|
|
*
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
*
|
|
* 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/.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
|
|
#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 onFilterSendWebSocketMessage, 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
|
|
{
|
|
private:
|
|
std::size_t _expectedCheckFileInfo;
|
|
std::size_t _expectedGetFile;
|
|
std::size_t _expectedPutRelative;
|
|
std::size_t _expectedPutFile;
|
|
|
|
protected:
|
|
STATE_ENUM(Phase, Load, WaitLoadStatus, WaitModifiedStatus, WaitDocClose) _phase;
|
|
|
|
/// The different test scenarios. All but VerifyOverwrite modify the document.
|
|
STATE_ENUM(Scenario, Disconnect, SaveDiscard, CloseDiscard, SaveOverwrite, VerifyOverwrite)
|
|
_scenario;
|
|
|
|
static constexpr auto OriginalDocContent = "Original contents";
|
|
static constexpr auto ModifiedOriginalDocContent = "\ufeffaOriginal contents\n";
|
|
static constexpr auto ConflictingDocContent = "Modified in-storage contents";
|
|
|
|
std::size_t getExpectedCheckFileInfo() const { return _expectedCheckFileInfo; }
|
|
void setExpectedCheckFileInfo(std::size_t value)
|
|
{
|
|
_expectedCheckFileInfo = value;
|
|
LOG_TST("Expecting " << _expectedCheckFileInfo << " CheckFileInfo requests.");
|
|
}
|
|
|
|
std::size_t getExpectedGetFile() const { return _expectedGetFile; }
|
|
void setExpectedGetFile(std::size_t value)
|
|
{
|
|
_expectedGetFile = value;
|
|
LOG_TST("Expecting " << _expectedGetFile << " GetFile requests.");
|
|
}
|
|
|
|
std::size_t getExpectedPutRelative() const { return _expectedPutRelative; }
|
|
void setExpectedPutRelative(std::size_t value)
|
|
{
|
|
_expectedPutRelative = value;
|
|
LOG_TST("Expecting " << _expectedPutRelative << " PutRelative requests.");
|
|
}
|
|
|
|
std::size_t getExpectedPutFile() const { return _expectedPutFile; }
|
|
void setExpectedPutFile(std::size_t value)
|
|
{
|
|
_expectedPutFile = value;
|
|
LOG_TST("Expecting " << _expectedPutFile << " PutFile requests.");
|
|
}
|
|
|
|
public:
|
|
WOPIUploadConflictCommon(const std::string& name, const std::string& fileContent)
|
|
: WopiTestServer(name, fileContent)
|
|
, _expectedCheckFileInfo(0)
|
|
, _expectedGetFile(0)
|
|
, _expectedPutRelative(0)
|
|
, _expectedPutFile(0)
|
|
, _phase(Phase::Load)
|
|
, _scenario(Scenario::Disconnect)
|
|
{
|
|
// We have multiple scenarios to cover.
|
|
setTimeout(std::chrono::seconds(90));
|
|
}
|
|
|
|
void startNewTest()
|
|
{
|
|
LOG_TST("===== Starting " << name(_scenario) << " test scenario =====");
|
|
|
|
LOG_TST("Resetting the document in storage");
|
|
setFileContent(OriginalDocContent); // Reset to test overwriting.
|
|
|
|
resetCountCheckFileInfo();
|
|
resetCountGetFile();
|
|
resetCountPutFile();
|
|
resetCountPutRelative();
|
|
|
|
// We always load once per scenario.
|
|
setExpectedCheckFileInfo(1);
|
|
setExpectedGetFile(1); // All the tests GetFile once.
|
|
setExpectedPutRelative(0); // No renaming in these tests.
|
|
|
|
if (_scenario == Scenario::VerifyOverwrite)
|
|
{
|
|
// By default, we don't upload when verifying (unless always_save_on_exit is set).
|
|
setExpectedPutFile(0);
|
|
}
|
|
else if (_scenario == Scenario::Disconnect || _scenario == Scenario::SaveDiscard ||
|
|
_scenario == Scenario::CloseDiscard)
|
|
{
|
|
// When there is no client connected, there is no way
|
|
// to decide how to resolve the conflict externally.
|
|
// So we quarantine and let it be.
|
|
// Similarly, when the client decides to discard changes.
|
|
setExpectedPutFile(1);
|
|
}
|
|
else
|
|
{
|
|
// With conflicts, we typically do two PutFile requests.
|
|
setExpectedPutFile(2);
|
|
}
|
|
}
|
|
|
|
void assertGetFileCount()
|
|
{
|
|
if (getExpectedCheckFileInfo() < getCountCheckFileInfo())
|
|
{
|
|
LOK_ASSERT_EQUAL_MESSAGE("Too many CheckFileInfo requests", getExpectedCheckFileInfo(),
|
|
getCountCheckFileInfo());
|
|
}
|
|
|
|
if (getExpectedGetFile() < getCountGetFile())
|
|
{
|
|
LOK_ASSERT_EQUAL_MESSAGE("Too many GetFile requests", getExpectedGetFile(),
|
|
getCountGetFile());
|
|
}
|
|
}
|
|
|
|
void assertPutFileCount()
|
|
{
|
|
LOG_TST("Testing " << toString(_scenario));
|
|
LOK_ASSERT_STATE(_phase, Phase::WaitDocClose);
|
|
|
|
if (getExpectedPutRelative() < getCountPutRelative())
|
|
{
|
|
LOK_ASSERT_EQUAL_MESSAGE("Too many PutRelative requests", getExpectedPutRelative(),
|
|
getCountPutRelative());
|
|
}
|
|
|
|
if (getExpectedPutFile() < getCountPutFile())
|
|
{
|
|
//FIXME: unreliable in SaveOnExit, which sometimes does 2 PutFile requests.
|
|
LOK_ASSERT_EQUAL_MESSAGE("Too many PutFile requests", getExpectedPutFile(),
|
|
getCountPutFile());
|
|
}
|
|
}
|
|
|
|
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_EQUAL_MESSAGE("Expect only documentconflict errors",
|
|
std::string("error: cmd=storage kind=documentconflict"), message);
|
|
|
|
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.
|
|
bool onDataLoss(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 'Data-loss detected'",
|
|
reason.starts_with("Data-loss detected"));
|
|
LOK_ASSERT_STATE(_phase, Phase::WaitDocClose);
|
|
|
|
// In SaveOverwrite, we should not be in modified state, because we do save
|
|
// and upload. But because we don't wait for the modified=false, we can end-up
|
|
// here. Since we will verify after reloading that we have no data-loss, it's OK.
|
|
LOK_ASSERT_MESSAGE(
|
|
"Expected to be in Scenario::Disconnect OR Scenario::SaveOverwrite but was " +
|
|
toString(_scenario),
|
|
(_scenario == Scenario::Disconnect) || (_scenario == Scenario::SaveOverwrite));
|
|
|
|
return failed();
|
|
}
|
|
|
|
// Wait for clean unloading.
|
|
void onDocBrokerDestroy(const std::string& docKey) override
|
|
{
|
|
LOG_TST("Testing " << name(_scenario) << " with dockey [" << docKey << "] closed.");
|
|
LOK_ASSERT_STATE(_phase, Phase::WaitDocClose);
|
|
|
|
LOK_ASSERT_EQUAL(getExpectedCheckFileInfo(), getCountCheckFileInfo());
|
|
LOK_ASSERT_EQUAL(getExpectedGetFile(), getCountGetFile());
|
|
LOK_ASSERT_EQUAL(getExpectedPutRelative(), getCountPutRelative());
|
|
// LOK_ASSERT_EQUAL(getExpectedPutFile(), getCountPutFile()); //FIXME: unreliable for some tests.
|
|
|
|
LOG_TST("===== Finished " << name(_scenario) << " test scenario =====");
|
|
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:
|
|
{
|
|
startNewTest();
|
|
|
|
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: */
|