fix: SYNERGY3-203 Synergy 1 business (#7157)

* SYNERGY3-203 Add link to helpdesk

* SYNERGY3-203 Add a new cmake option

* SYNERGY3-203 Validate business key

* SYNERGY3-203 Check license during start

* SYNERGY3-203 Update windows title

* SYNERGY3-203 Unify license errors

* SYNERGY3-203 Fix code smell

* SYNERGY3-203 Update Azure scripts

* SYNERGY3-203 Update GitHub workflows

* SYNERGY3-203 Add UT

* SYNERGY3-203 Add UT
This commit is contained in:
Serhii Hadzhilov
2022-04-26 15:11:09 +03:00
committed by GitHub
parent a3ade33295
commit 8be2e89c81
19 changed files with 280 additions and 68 deletions

View File

@ -10,10 +10,23 @@ jobs:
strategy:
matrix:
enterprise: ["1", ""]
include:
- name: "synergy"
remote_folder: "v1-core-standard"
enterprise: ""
business: ""
- name: "synergy-enterprise"
remote_folder: "v1-core-enterprise"
enterprise: "1"
business: ""
- name: "synergy-business"
remote_folder: "v1-core-business"
enterprise: ""
business: "1"
env:
GIT_COMMIT: ${{ github.sha }}
SYNERGY_ENTERPRISE: ${{ matrix.enterprise }}
SYNERGY_BUSINESS: ${{ matrix.business }}
Qt5_DIR: /usr/local/opt/qt/5.15.2/clang_64
OpenSSL_DIR: /usr/local/ssl
CODESIGN_ID: "Developer ID Application: Symless Ltd (4HX897Y6GJ)"
@ -57,14 +70,8 @@ jobs:
echo "::set-output name=SYNERGY_VERSION::$SYNERGY_VERSION"
echo "::set-output name=SYNERGY_REVISION::$SYNERGY_REVISION"
echo "::set-output name=SYNERGY_DMG_VERSION::$SYNERGY_DMG_VERSION"
if [ "$SYNERGY_ENTERPRISE" == '1' ]
then
SYNERGY_PACKAGE_NAME='synergy-enterprise'
SYNERGY_REMOTE_FOLDER="v1-core-enterprise/${SYNERGY_VERSION}/${SYNERGY_VERSION_STAGE}/b${SYNERGY_VERSION_BUILD}-${SYNERGY_REVISION}"
else
SYNERGY_PACKAGE_NAME='synergy'
SYNERGY_REMOTE_FOLDER="v1-core-standard/${SYNERGY_VERSION}/${SYNERGY_VERSION_STAGE}/b${SYNERGY_VERSION_BUILD}-${SYNERGY_REVISION}"
fi
SYNERGY_PACKAGE_NAME=${{ matrix.name }}
SYNERGY_REMOTE_FOLDER="${{ matrix.remote_folder }}/${SYNERGY_VERSION}/${SYNERGY_VERSION_STAGE}/b${SYNERGY_VERSION_BUILD}-${SYNERGY_REVISION}"
SYNERGY_DMG_FILENAME="${SYNERGY_PACKAGE_NAME}_${SYNERGY_DMG_VERSION}_macos-10.13_x86-64.dmg"
echo "SYNERGY_REMOTE_FOLDER: $SYNERGY_REMOTE_FOLDER"
echo "::set-output name=SYNERGY_REMOTE_FOLDER::$SYNERGY_REMOTE_FOLDER"

View File

@ -14,10 +14,23 @@ jobs:
strategy:
matrix:
enterprise: ["1", ""]
include:
- name: "synergy"
remote_folder: "v1-core-standard"
enterprise: ""
business: ""
- name: "synergy-enterprise"
remote_folder: "v1-core-enterprise"
enterprise: "1"
business: ""
- name: "synergy-business"
remote_folder: "v1-core-business"
enterprise: ""
business: "1"
env:
GIT_COMMIT: ${{ github.sha }}
SYNERGY_ENTERPRISE: ${{ matrix.enterprise }}
SYNERGY_BUSINESS: ${{ matrix.business }}
CODESIGN_ID: "Developer ID Application: Symless Ltd (4HX897Y6GJ)"
steps:
@ -58,14 +71,8 @@ jobs:
echo "::set-output name=SYNERGY_VERSION::$SYNERGY_VERSION"
echo "::set-output name=SYNERGY_REVISION::$SYNERGY_REVISION"
echo "::set-output name=SYNERGY_DMG_VERSION::$SYNERGY_DMG_VERSION"
if [ "$SYNERGY_ENTERPRISE" == '1' ]
then
SYNERGY_PACKAGE_NAME='synergy-enterprise'
SYNERGY_REMOTE_FOLDER="v1-core-enterprise/${SYNERGY_VERSION}/${SYNERGY_VERSION_STAGE}/b${SYNERGY_VERSION_BUILD}-${SYNERGY_REVISION}"
else
SYNERGY_PACKAGE_NAME='synergy'
SYNERGY_REMOTE_FOLDER="v1-core-standard/${SYNERGY_VERSION}/${SYNERGY_VERSION_STAGE}/b${SYNERGY_VERSION_BUILD}-${SYNERGY_REVISION}"
fi
SYNERGY_PACKAGE_NAME=${{ matrix.name }}
SYNERGY_REMOTE_FOLDER="${{ matrix.remote_folder }}/${SYNERGY_VERSION}/${SYNERGY_VERSION_STAGE}/b${SYNERGY_VERSION_BUILD}-${SYNERGY_REVISION}"
SYNERGY_DMG_FILENAME="${SYNERGY_PACKAGE_NAME}_${SYNERGY_DMG_VERSION}_macos-arm64.dmg"
echo "SYNERGY_REMOTE_FOLDER: $SYNERGY_REMOTE_FOLDER"
echo "::set-output name=SYNERGY_REMOTE_FOLDER::$SYNERGY_REMOTE_FOLDER"

View File

@ -6,15 +6,27 @@ on:
jobs:
build-on-pi:
runs-on: ${{ matrix.os }}
runs-on: [[self-hosted, linux, ARM, pi-3], [self-hosted, linux, ARM64, pi-4]]
strategy:
matrix:
os: [[self-hosted, linux, ARM, pi-3], [self-hosted, linux, ARM64, pi-4]]
enterprise: ['1', '']
include:
- name: "synergy"
remote_folder: "v1-core-standard"
enterprise: ""
business: ""
- name: "synergy-enterprise"
remote_folder: "v1-core-enterprise"
enterprise: "1"
business: ""
- name: "synergy-business"
remote_folder: "v1-core-business"
enterprise: ""
business: "1"
env:
GIT_COMMIT: ${{ github.sha }}
DEB_BUILD_OPTIONS: parallel=1
SYNERGY_ENTERPRISE: ${{ matrix.enterprise }}
SYNERGY_BUSINESS: ${{ matrix.business }}
steps:
- uses: actions/checkout@v2
@ -37,14 +49,8 @@ jobs:
echo "::set-output name=SYNERGY_VERSION::${SYNERGY_VERSION}"
echo "::set-output name=SYNERGY_REVISION::${SYNERGY_REVISION}"
echo "::set-output name=SYNERGY_DEB_VERSION::${SYNERGY_DEB_VERSION}"
if [ "$SYNERGY_ENTERPRISE" == '1' ]
then
echo "::set-output name=SYNERGY_REMOTE_FOLDER::v1-core-enterprise/${SYNERGY_VERSION}/${SYNERGY_VERSION_STAGE}/b${SYNERGY_VERSION_BUILD}-${SYNERGY_REVISION}"
echo "::set-output name=SYNERGY_PACKAGE_NAME::synergy-enterprise"
else
echo "::set-output name=SYNERGY_REMOTE_FOLDER::v1-core-standard/${SYNERGY_VERSION}/${SYNERGY_VERSION_STAGE}/b${SYNERGY_VERSION_BUILD}-${SYNERGY_REVISION}"
echo "::set-output name=SYNERGY_PACKAGE_NAME::synergy"
fi
echo "::set-output name=SYNERGY_REMOTE_FOLDER::${{ matrix.remote_folder }}/${SYNERGY_VERSION}/${SYNERGY_VERSION_STAGE}/b${SYNERGY_VERSION_BUILD}-${SYNERGY_REVISION}"
echo "::set-output name=SYNERGY_PACKAGE_NAME::${{ matrix.name }}"
- name: Build deb
env:

View File

@ -54,6 +54,32 @@ steps:
env:
GIT_COMMIT: $(Build.SourceVersion)
- script: |
mkdir build-bussiness
cd build-business
export SYNERGY_BUSINESS=1
if [ -z $(which cmake) ]; then cmake3 -DCMAKE_BUILD_TYPE=Release -DSYNERGY_BUSINESS=ON ..; else cmake -DCMAKE_BUILD_TYPE=Release -DSYNERGY_BUSINESS=ON ..; fi
. ./version
make -j$(makeJobsLimit)
displayName: 'Build business'
condition: eq(variables['packager'],'deb')
env:
GIT_COMMIT: $(Build.SourceVersion)
- script: |
mkdir build-business
cd build-business
if [ -z $(which cmake) ]; then
cmake3 -DCMAKE_BUILD_TYPE=Release -DSYNERGY_BUSINESS=ON -DCMAKE_INSTALL_PREFIX:PATH=$(pwd)/rpm/BUILDROOT/usr ..;
else cmake -DCMAKE_BUILD_TYPE=Release -DSYNERGY_BUSINESS=ON -DCMAKE_INSTALL_PREFIX:PATH=$(pwd)/rpm/BUILDROOT/usr ..;
fi
. ./version
make -j$(makeJobsLimit)
condition: eq(variables['packager'],'rpm')
displayName: 'Build business rpm'
env:
GIT_COMMIT: $(Build.SourceVersion)
- script: |
. ./build-release/version
SYNERGY_VERSION="$SYNERGY_VERSION_MAJOR.$SYNERGY_VERSION_MINOR.$SYNERGY_VERSION_PATCH"
@ -107,6 +133,26 @@ steps:
env:
GIT_COMMIT: $(Build.SourceVersion)
- script: |
export SYNERGY_BUSINESS=1
dch --create --package "synergy-business_" --controlmaint --distribution unstable --newversion $(SYNERGY_DEB_VERSION) "Initial release"
export GPG_TTY=$(tty)
debuild --preserve-envvar SYNERGY_* --preserve-envvar GIT_COMMIT -us -uc
mkdir business_package
cd ..
filename=$(ls synergy_*.deb)
filename_new="synergy-business_${SYNERGY_VERSION}-${SYNERGY_VERSION_STAGE}.${SYNERGY_REVISION}_$(name)${filename##*$(SYNERGY_REVISION)}"
mv $filename $(Build.Repository.LocalPath)/business_package/$filename_new
cd $(Build.Repository.LocalPath)/business_package
md5sum $filename_new >> ${filename_new}.checksum.txt
sha1sum $filename_new >> ${filename_new}.checksum.txt
sha256sum $filename_new >> ${filename_new}.checksum.txt
ls -la
displayName: "Package Binary DEB(Business)"
condition: eq(variables['packager'],'deb')
env:
GIT_COMMIT: $(Build.SourceVersion)
- script: |
cd build-release
make install/strip
@ -144,6 +190,25 @@ steps:
env:
GIT_COMMIT: $(Build.SourceVersion)
- script: |
cd build-business
make install/strip
cd rpm
rpmbuild -bb --define "_topdir $(pwd)" --buildroot $(pwd)/BUILDROOT synergy-business.spec
rpmlint --verbose RPMS/*.rpm
cd RPMS
filename=$(ls *.rpm)
md5sum $filename >> ${filename}.checksum.txt
sha1sum $filename >> ${filename}.checksum.txt
sha256sum $filename >> ${filename}.checksum.txt
cd ..
mv RPMS $(Build.Repository.LocalPath)/business_package
ls -la
displayName: "Package Binary RPM(business)"
condition: eq(variables['packager'],'rpm')
env:
GIT_COMMIT: $(Build.SourceVersion)
- task: CopyFilesOverSSH@0
inputs:
sshEndpoint: 'Binary Storage'
@ -160,4 +225,13 @@ steps:
contents: '*'
targetFolder: '$(BINARIES_DIR)/v1-core-enterprise/$(SYNERGY_VERSION)/$(SYNERGY_VERSION_STAGE)/b$(SYNERGY_VERSION_BUILD)-$(SYNERGY_REVISION)/'
readyTimeout: '20000'
displayName: 'Send enterprise package to Binary Storage'
displayName: 'Send enterprise package to Binary Storage'
- task: CopyFilesOverSSH@0
inputs:
sshEndpoint: 'Binary Storage'
sourceFolder: './business_package'
contents: '*'
targetFolder: '$(BINARIES_DIR)/v1-core-business/$(SYNERGY_VERSION)/$(SYNERGY_VERSION_STAGE)/b$(SYNERGY_VERSION_BUILD)-$(SYNERGY_REVISION)/'
readyTimeout: '20000'
displayName: 'Send business package to Binary Storage'

View File

@ -60,6 +60,25 @@ steps:
GIT_COMMIT: $(Build.SourceVersion)
CMAKE_PREFIX_PATH: "$(Qt5_DIR);/usr/local/opt/openssl"
- task: CmdLine@2
inputs:
script: |
export PATH="$(Qt5_DIR)/bin:$PATH"
export SYNERGY_BUSINESS=1
mkdir build
cd build
cmake \
-DCMAKE_OSX_DEPLOYMENT_TARGET=$(version) \
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CONFIGURATION_TYPES=Release \
-DSYNERGY_BUSINESS=ON ..
displayName: 'Cmake business'
condition: eq(variables['buildType'], 'business')
env:
GIT_COMMIT: $(Build.SourceVersion)
CMAKE_PREFIX_PATH: "$(Qt5_DIR);/usr/local/opt/openssl"
- task: CmdLine@2
inputs:
script: |

View File

@ -60,9 +60,12 @@ steps:
cd build64
IF "$(buildType)"=="enterprise" (
cmake -G "Visual Studio 16 2019" -A %MSARCH% -DCMAKE_BUILD_TYPE=Release -DSYNERGY_ENTERPRISE=ON ..
) else IF "$(buildType)"=="business" (
cmake -G "Visual Studio 16 2019" -A %MSARCH% -DCMAKE_BUILD_TYPE=Release -DSYNERGY_BUSINESS=ON ..
) else (
cmake -G "Visual Studio 16 2019" -A %MSARCH% -DCMAKE_BUILD_TYPE=Release ..
cmake -G "Visual Studio 16 2019" -A %MSARCH% -DCMAKE_BUILD_TYPE=Release ..
)
displayName: 'Cmake x64 Standard'
env:
ENV_BAT: $(ENV_BAT)

View File

@ -38,6 +38,12 @@ else()
option (SYNERGY_ENTERPRISE "Build Enterprise" OFF)
endif()
if ($ENV{SYNERGY_BUSINESS})
option (SYNERGY_BUSINESS "Build Business" ON)
else()
option (SYNERGY_BUSINESS "Build Business" OFF)
endif()
set (CMAKE_CXX_STANDARD 14)
set (CMAKE_CXX_EXTENSIONS OFF)
set (CMAKE_CXX_STANDARD_REQUIRED ON)

View File

@ -2,6 +2,7 @@ v1.14.4-snapshot
===========
Enhancements:
- #7143 Add ability to stop synergy on the login screen
- #7157 Synergy business accepts only business licenses
Github Actions:
- #7148 Fix unstable build for windows core

View File

@ -85,6 +85,12 @@ jobs:
version: 10.14
buildType: "enterprise"
prefix: "synergy-enterprise"
catalina-ent:
image: macOS-10.15
platform: x86-64
version: 10.14
buildType: "business"
prefix: "synergy-business"
pool:
vmImage: $[ variables['image'] ]
@ -108,6 +114,10 @@ jobs:
image: windows-2019
buildType: "enterprise"
prefix: "synergy-enterprise"
vs2019business:
image: windows-2019
buildType: "business"
prefix: "synergy-business"
pool:
vmImage: $[ variables['image'] ]

View File

@ -77,3 +77,7 @@ endif()
if (SYNERGY_ENTERPRISE)
add_definitions (-DSYNERGY_ENTERPRISE=1)
endif()
if (SYNERGY_BUSINESS)
add_definitions(-DSYNERGY_BUSINESS=1)
endif()

31
dist/rpm/synergy-business.spec.in vendored Normal file
View File

@ -0,0 +1,31 @@
%define _rpmfilename %%{NAME}_%%{Version}-%%{Release}.rpm
Name: synergy-business
Version: @SYNERGY_VERSION@
Summary: Keyboard and mouse sharing solution
Group: Applications/Productivity
URL: https://symless.com/synergy
Source: https://symless.com/synergy/downloads
Vendor: Symless
Packager: Symless <engineering@symless.com>
License: GPLv2
Epoch: 1
Release: @SYNERGY_SNAPSHOT_INFO@%{?dist}
Requires: openssl
%description
Synergy allows you to share one mouse and keyboard between multiple computers.
Work seamlessly across Windows, macOS and Linux.
%files
%defattr(755,root,root,-)
%{_bindir}/synergy
%{_bindir}/synergyc
%{_bindir}/synergys
%{_bindir}/syntool
%attr(644,-,-) %{_datarootdir}/applications/synergy.desktop
%attr(644,-,-) %{_datarootdir}/icons/hicolor/scalable/apps/synergy.svg
%changelog
* Wed Apr 26 2017 Symless <engineering@symless.com>
- Initial version of the package

View File

@ -59,24 +59,17 @@ void ActivationDialog::accept()
QMessageBox message;
m_appConfig->activationHasRun(true);
std::pair<bool, QString> result;
try {
SerialKey serialKey (ui->m_pTextEditSerialKey->toPlainText().
trimmed().toStdString());
result = m_LicenseManager->setSerialKey(serialKey);
m_LicenseManager->setSerialKey(serialKey);
}
catch (std::exception& e) {
message.critical(this, "Unknown Error",
tr("An error occurred while trying to activate Synergy. "
"Please contact the helpdesk, and provide the "
"following information:\n\n%1").arg(e.what()));
refreshSerialKey();
return;
}
if (!result.first) {
message.critical(this, "Activation failed",
tr("%1").arg(result.second));
tr("An error occurred while trying to activate Synergy. "
"<a href=\"https://symless.com/synergy/contact-support\" style=\"text-decoration: none; color: #4285F4;\">"
"Please contact the helpdesk</a>, and provide the following information:"
"<br><br>%1").arg(e.what()));
refreshSerialKey();
return;
}

View File

@ -28,16 +28,21 @@ LicenseManager::LicenseManager(AppConfig* appConfig) :
m_serialKey(appConfig->edition()) {
}
std::pair<bool, QString>
void
LicenseManager::setSerialKey(SerialKey serialKey, bool acceptExpired)
{
std::pair<bool, QString> ret (true, "");
time_t currentTime = ::time(0);
if (!acceptExpired && serialKey.isExpired(currentTime)) {
ret.first = false;
ret.second = "Serial key expired";
return ret;
throw std::runtime_error("Serial key expired");
}
if (!serialKey.isValid()) {
#ifdef SYNERGY_BUSINESS
throw std::runtime_error("The serial key is not compatible with the business version of Synergy.");
#else
throw std::runtime_error("The serial key is not compatible with the consumer version of Synergy.");
#endif
}
if (serialKey != m_serialKey) {
@ -54,8 +59,6 @@ LicenseManager::setSerialKey(SerialKey serialKey, bool acceptExpired)
emit editionChanged(m_serialKey.edition());
}
}
return ret;
}
void
@ -93,7 +96,7 @@ LicenseManager::activeEditionName() const
return getEditionName(activeEdition(), m_serialKey.isTrial());
}
SerialKey
const SerialKey&
LicenseManager::serialKey() const
{
return m_serialKey;

View File

@ -31,12 +31,11 @@ class LicenseManager: public QObject
public:
LicenseManager(AppConfig* appConfig);
std::pair<bool, QString> setSerialKey(SerialKey serialKey,
bool acceptExpired = false);
void setSerialKey(SerialKey serialKey, bool acceptExpired = false);
void refresh();
Edition activeEdition() const;
QString activeEditionName() const;
SerialKey serialKey() const;
const SerialKey& serialKey() const;
void skipActivation() const;
void notifyUpdate(QString fromVersion, QString toVersion) const;
static QString getEditionName(Edition edition, bool trial = false);

View File

@ -1416,10 +1416,11 @@ int MainWindow::raiseActivationDialog()
void MainWindow::on_windowShown()
{
#ifndef SYNERGY_ENTERPRISE
if (!m_AppConfig->activationHasRun() &&
!m_LicenseManager->serialKey().isValid()){
raiseActivationDialog();
}
auto serialKey = m_LicenseManager->serialKey();
if (!m_AppConfig->activationHasRun() && !serialKey.isValid()) {
setEdition(Edition::kUnregistered);
raiseActivationDialog();
}
#endif
}

View File

@ -101,7 +101,7 @@ SerialKey::isValid() const
{
bool Valid = true;
if (m_edition.getType() == kUnregistered || isExpired(::time(0)))
if (!m_edition.isValid() || isExpired(::time(0)))
{
Valid = false;
}

View File

@ -10,6 +10,27 @@ const std::string SerialKeyEdition::BASIC_CHINA = "basic_china";
const std::string SerialKeyEdition::BUSINESS = "business";
const std::string SerialKeyEdition::UNREGISTERED = "unregistered";
namespace {
const std::map<std::string, Edition>& getSerialTypes()
{
#ifdef SYNERGY_BUSINESS
static const std::map<std::string, Edition> serialTypes = {
{SerialKeyEdition::BUSINESS, kBusiness}
};
#else
static const std::map<std::string, Edition> serialTypes {
{SerialKeyEdition::BASIC, kBasic},
{SerialKeyEdition::PRO, kPro},
{SerialKeyEdition::BASIC_CHINA, kBasic_China},
{SerialKeyEdition::PRO_CHINA, kPro_China}
};
#endif
return serialTypes;
}
} //namespace
SerialKeyEdition::SerialKeyEdition()
{
@ -103,14 +124,7 @@ SerialKeyEdition::setType(Edition type)
void
SerialKeyEdition::setType(const std::string& type)
{
static const std::map<std::string, Edition> types = {
{BASIC, kBasic},
{PRO, kPro},
{BUSINESS, kBusiness},
{BASIC_CHINA, kBasic_China},
{PRO_CHINA, kPro_China}
};
auto types = getSerialTypes();
const auto& pType = types.find(type);
if (pType != types.end()) {
@ -120,3 +134,9 @@ SerialKeyEdition::setType(const std::string& type)
m_Type = kUnregistered;
}
}
bool SerialKeyEdition::isValid() const
{
auto types = getSerialTypes();
return (types.find(getName()) != types.end());
}

View File

@ -34,6 +34,8 @@ public:
void setType(Edition type);
void setType(const std::string& type);
bool isValid() const;
static const std::string PRO;
static const std::string PRO_CHINA;
static const std::string BASIC;

View File

@ -26,7 +26,7 @@ TEST(SerialKeyEditionTests, DefaultEditionType_Unregistered)
EXPECT_EQ(kUnregistered, edition.getType());
EXPECT_EQ(SerialKeyEdition::UNREGISTERED, edition.getName());
EXPECT_EQ("Synergy 1 (UNREGISTERED)", edition.getDisplayName());
EXPECT_FALSE(edition.isValid());
}
TEST(SerialKeyEditionTests, SetEditionType_edition)
@ -36,6 +36,7 @@ TEST(SerialKeyEditionTests, SetEditionType_edition)
EXPECT_EQ(kPro, edition.getType());
EXPECT_EQ(SerialKeyEdition::PRO, edition.getName());
EXPECT_EQ("Synergy 1 Pro", edition.getDisplayName());
EXPECT_TRUE(edition.isValid());
}
TEST(SerialKeyEditionTests, SetEditionType_string)
@ -74,6 +75,31 @@ TEST(SerialKeyEditionTests, SetEditionProChina)
EXPECT_EQ("Synergy Pro 中文版", edition.getDisplayName());
}
TEST(SerialKeyEditionTests, NameConstructor)
{
SerialKeyEdition edition(SerialKeyEdition::BUSINESS);
EXPECT_EQ(kUnregistered, edition.getType());
EXPECT_FALSE(edition.isValid());
}
TEST(SerialKeyEditionTests, isValid)
{
SerialKeyEdition edition;
edition.setType(Edition::kBasic);
EXPECT_TRUE(edition.isValid());
edition.setType(Edition::kBasic_China);
EXPECT_TRUE(edition.isValid());
edition.setType(Edition::kBusiness);
EXPECT_FALSE(edition.isValid());
edition.setType(Edition::kPro);
EXPECT_TRUE(edition.isValid());
edition.setType(Edition::kPro_China);
EXPECT_TRUE(edition.isValid());
edition.setType(Edition::kUnregistered);
EXPECT_FALSE(edition.isValid());
}