text: # -*- coding: utf-8 -*-
  # Usage: python test.py [TestTypeWorld.test_name] [mothershipURL]
  
  import sys
  import os
  import copy
  import time
  import ssl
  import certifi
  import json
  import unittest
  import tempfile
  
  # Use local code for local testing, and rely on system-installed module for CI-testing
  CI = os.getenv("CI", "false").lower() != "false"
  if not CI:
      path = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
      if path not in sys.path:
          sys.path.append(path)
  
  import typeworld.client  # noqa: E402
  
  from typeworld.client import (  # noqa: E402
      APIClient,
      JSON,
      AppKitNSUserDefaults,
  )
  
  if CI:
      SECRETKEY = os.getenv("REVOKEAPPINSTANCEAUTHKEY")
  else:
      import keyring
  
      SECRETKEY = keyring.get_password("http://127.0.0.1:8080/api", "revokeAppInstance")
  
  assert SECRETKEY
  
  
  # Data Types
  from typeworld.api import (  # noqa: E402
      HexColorDataType,
      FontEncodingDataType,
      EmailDataType,
      BooleanDataType,
      WebURLDataType,
      IntegerDataType,
      FloatDataType,
      DateDataType,
      VersionDataType,
      MultiLanguageText,
      MultiLanguageLongText,
  )
  
  # Classes
  from typeworld.api import (  # noqa: E402
      InstallFontAsset,
      UninstallFontAsset,
      FontListProxy,
      RootResponse,
      EndpointResponse,
      Designer,
      LicenseDefinition,
      Version,
      LicenseUsage,
      Font,
      Family,
      Foundry,
      InstallableFontsResponse,
      InstallFontsResponse,
      UninstallFontsResponse,
      FontPackage,
  )
  
  from typeworld.api import (  # noqa: E402
      # LanguageSupportDataType,
      OpenTypeFeatureDataType,
      OpenSourceLicenseIdentifierDataType,
      SupportedAPICommandsDataType,
      FontPurposeDataType,
      FontMimeType,
      FontStatusDataType,
      InstallableFontsResponseType,
      InstallFontAssetResponseType,
      InstallFontResponseType,
      UninstallFontAssedResponseType,
      UninstallFontResponseType,
  )
  
  # Constants
  from typeworld.api import COMMANDS, MAC, PUBLISHERTYPES  # noqa: E402
  
  # Methods
  from typeworld.api import makeSemVer  # noqa: E402
  
  from inspect import currentframe, getframeinfo  # noqa: E402
  
  
  ONLINE = True
  
  print("Started...")
  
  sslcontext = ssl.create_default_context(cafile=certifi.where())
  
  
  # Inspection/Interactive shell
  # import code; code.interact(local=locals())
  
  
  freeSubscription = "typeworld://json+https//typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
  flatFreeSubscription = "typeworld://json+https//typeworldserver.com/flatapi/q8JZfYn9olyUvcCOiqHq/"
  protectedSubscription = (
      "typeworld://json+https//s9lWvayTEOaB9eIIMA67:OxObIWDJjW95SkeL3BNr:"
      "qncMnRXZLvHfLLwteTsX@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
  )
  protectedSubscriptionWithoutAccessToken = (
      "typeworld://json+https//s9lWvayTEOaB9eIIMA67:OxObIWDJjW95SkeL3BNr@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
  )
  freeNamedSubscription = "typeworld://json+https//s9lWvayTEOaB9eIIMA67@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
  testUser1 = ("test1@type.world", "12345678")
  testUser2 = ("test2@type.world", "01234567")
  testUser3 = ("test3@type.world", "23456789")
  
  
  # Travis CI
  if "TRAVIS" in os.environ:
      MOTHERSHIP = "https://api.type.world/v1"
  
  # Local testing
  else:
      MOTHERSHIP = "https://api.type.world/v1"  # nocoverage (for local testing only)
  
      # Testing specific version
      for arg in sys.argv:  # nocoverage (for local testing only)
          if arg.startswith("http"):  # nocoverage (for local testing only)
              MOTHERSHIP = arg  # nocoverage (for local testing only)
              if not MOTHERSHIP.endswith("/v1"):  # nocoverage (for local testing only)
                  MOTHERSHIP += "/v1"  # nocoverage (for local testing only)
  
  
  print("Testing on %s" % MOTHERSHIP)
  
  global errors, failures
  errors = []
  failures = []
  
  print("setting up objects started...")
  
  
  # EndpointResponse
  root = EndpointResponse()
  root.name.en = "Font Publisher"
  root.canonicalURL = "http://fontpublisher.com/api/"
  root.adminEmail = "admin@fontpublisher.com"
  root.supportedCommands = [x["keyword"] for x in COMMANDS]  # this API supports all commands
  root.publisherTypes = PUBLISHERTYPES
  root.backgroundColor = "AABBCC"
  root.licenseIdentifier = "CC-BY-NC-ND-4.0"
  root.logoURL = "https://typeworldserver.com/?page=outputDataBaseFile&className=TWFS_Foundry&ID=8&field=logo"
  root.privacyPolicyURL = "https://type.world/legal/privacy.html"
  root.termsOfServiceURL = "https://type.world/legal/terms.html"
  root.public = False
  root.version = "0.1.7-alpha"
  root.websiteURL = "https://typeworldserver.com"
  
  # InstallableFontsResponse
  designer = Designer()
  designer.description.en = "Yanone is a type designer based in Germany."
  designer.keyword = "yanone"
  designer.name.en = "Yanone"
  designer.websiteURL = "https://yanone.de"
  
  designer2 = Designer()
  designer2.description.en = "Yanone is a type designer based in Germany."
  designer2.keyword = "yanone2"
  designer2.name.en = "Yanone"
  designer2.websiteURL = "https://yanone.de"
  
  # License
  license = LicenseDefinition()
  license.URL = "https://yanone.de/eula.html"
  license.keyword = "yanoneEULA"
  license.name.en = "Yanone EULA"
  
  # Font-specific Version
  fontVersion = Version()
  fontVersion.description.en = "Initial release"
  fontVersion.description.de = "Erstveröffentlichung"
  fontVersion.number = 1.0
  fontVersion.releaseDate = "2004-10-10"
  
  # FontPackage
  desktopFontsPackage = FontPackage()
  desktopFontsPackage.keyword = "desktop"
  desktopFontsPackage.name.en = "Desktop Fonts"
  desktopFontsPackage.name.de = "Desktop-Schriften"
  
  # LicenseUsage
  usedLicense = LicenseUsage()
  usedLicense.allowanceDescription.en = "N/A"
  usedLicense.dateAddedForUser = "2019-10-01"
  usedLicense.keyword = "yanoneEULA"
  usedLicense.seatsAllowed = 5
  usedLicense.seatsInstalled = 1
  usedLicense.upgradeURL = "https://yanone.de/buy/kaffeesatz/upgrade?customerID=123456"
  
  # Font
  font = Font()
  font.dateFirstPublished = "2004-10-10"
  font.designerKeywords.append("yanone")
  font.designerKeywords = ["yanone", "yanone2"]
  assert len(font.designerKeywords) == 2
  font.format = "otf"
  font.free = True
  font.name.en = "Regular"
  font.name.de = "Normale"
  font.pdfURL = "https://yanone.de/fonts/kaffeesatz.pdf"
  font.postScriptName = "YanoneKaffeesatz-Regular"
  font.protected = False
  font.purpose = "desktop"
  font.packageKeywords.append("desktop")
  font.status = "stable"
  font.uniqueID = "yanone-kaffeesatz-regular"
  font.usedLicenses.append(usedLicense)
  font.usedLicenses = [usedLicense]
  assert len(font.usedLicenses) == 1
  font.variableFont = False
  font.versions.append(fontVersion)
  font.versions = [fontVersion]
  assert len(font.versions) == 1
  font.languageSupport = {"latn": ["DEU"]}
  font.features = ["aalt", "liga"]
  
  # Font 2
  font2 = Font()
  font2.billboardURLs = [
      "https://typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=2&field=image"
  ]
  font2.billboardURLs.append(
      "https://typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=6&field=image"
  )
  font2.dateFirstPublished = "2004-10-10"
  font2.designerKeywords.append("yanone")
  font2.designerKeywords = ["yanone"]
  assert len(font2.designerKeywords) == 1
  font2.format = "otf"
  font2.free = True
  font2.name.en = "Bold"
  font2.name.de = "Fette"
  font2.pdfURL = "https://yanone.de/fonts/kaffeesatz.pdf"
  font2.postScriptName = "YanoneKaffeesatz-Bold"
  font2.protected = False
  font2.purpose = "desktop"
  font.packageKeywords.append("desktop")
  font2.status = "stable"
  font2.uniqueID = "yanone-kaffeesatz-bold"
  font2.usedLicenses.append(usedLicense)
  font2.usedLicenses = [usedLicense]
  assert len(font2.usedLicenses) == 1
  font2.variableFont = False
  font2.versions.append(fontVersion)
  font2.versions = [fontVersion]
  assert len(font2.versions) == 1
  
  # Family-specific Version
  familyVersion = Version()
  familyVersion.description.en = "Initial release"
  familyVersion.description.de = "Erstveröffentlichung"
  familyVersion.number = 1.0
  familyVersion.releaseDate = "2004-10-10"
  
  # Family
  family = Family()
  family.billboardURLs = [
      "https://typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=2&field=image"
  ]
  family.billboardURLs.append(
      "https://typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=6&field=image"
  )
  family.dateFirstPublished = "2019-10-01"
  family.description.en = "Kaffeesatz is a free font classic"
  family.designerKeywords.append("yanone")
  family.designerKeywords = ["yanone"]  # same as above
  assert len(family.designerKeywords) == 1
  family.galleryURL = "https://fontsinuse.com/kaffeesatz"
  family.issueTrackerURL = "https://github.com/yanone/kaffeesatzfont/issues"
  family.name.en = "Yanone Kaffeesatz"
  family.pdfURL = "https://yanone.de/fonts/kaffeesatz.pdf"
  family.sourceURL = "https://github.com/yanone/kaffeesatzfont"
  family.uniqueID = "yanone-yanonekaffeesatz"
  family.versions.append(familyVersion)
  family.versions = [familyVersion]
  assert len(family.versions) == 1
  family.fonts.append(font)
  family.fonts = [font]
  assert len(family.fonts) == 1
  family.fonts.append(font2)
  assert len(family.fonts) == 2
  family.fonts = [font, font2]
  assert len(family.fonts) == 2
  family.packages.append(desktopFontsPackage)
  assert len(family.packages) == 1
  
  print(family.billboardURLs)
  print(font2.billboardURLs)
  print(font2.getBillboardURLs())
  assert font2.getBillboardURLs() == font2.billboardURLs
  # str because objects are not identical, only content:
  assert str(font2.getBillboardURLs()) == str(family.billboardURLs)
  
  # Foundry
  foundry = Foundry()
  foundry.backgroundColor = "AABBCC"
  foundry.description.en = "Yanone is a foundry lead by German type designer Yanone."
  foundry.email = "font@yanone.de"
  foundry.licenses.append(license)
  foundry.name.en = "Awesome Fonts"
  foundry.name.de = "Tolle Schriften"
  
  foundry.socialURLs.append("https://facebook.com/pages/YanoneYanone")
  foundry.socialURLs.append("https://twitter.com/yanone")
  
  foundry.supportEmail = "support@yanone.de"
  foundry.supportTelephone = "+49123456789"
  foundry.supportURL = "https://yanone.de/support/"
  foundry.telephone = "+49123456789"
  foundry.twitter = "yanone"
  foundry.uniqueID = "yanone"
  foundry.websiteURL = "https://yanone.de"
  foundry.families.append(family)
  foundry.families = [family]
  assert len(foundry.families) == 1
  foundry.styling = json.loads(
      """{"light": {
              "headerColor": "F20D5E",
              "headerTextColor": "000000",
              "headerLinkColor": "E5F20D",
  
              "backgroundColor": "E5F20D",
              "textColor": "000000",
              "linkColor": "F7AD22",
  
              "selectionColor": "0D79F2",
              "selectionTextColor": "E5F20D",
  
              "buttonColor": "197AA3",
              "buttonTextColor": "FFFFFF",
  
              "informationViewBackgroundColor": "469BF5",
              "informationViewTextColor": "000000",
              "informationViewLinkColor": "E5F20D",
  
              "informationViewButtonColor": "E5F20D",
              "informationViewButtonTextColor": "000000"
  
          }, "dark": {
              "headerColor": "B10947",
              "headerTextColor": "000000",
              "headerLinkColor": "E5F20D",
  
              "backgroundColor": "1A1A1A",
              "textColor": "E5F20D",
              "linkColor": "C07F07",
  
              "selectionColor": "B10947",
              "selectionTextColor": "E5F20D",
  
              "buttonColor": "22A4DC",
              "buttonTextColor": "000000",
  
              "informationViewBackgroundColor": "000000",
              "informationViewTextColor": "999999",
              "informationViewLinkColor": "E5F20D",
  
              "informationViewButtonColor": "1E90C1",
              "informationViewButtonTextColor": "000000"
  
          } }"""
  )
  
  # InstallableFontsResponse
  installableFonts = InstallableFontsResponse()
  installableFonts.designers.append(designer)
  installableFonts.designers.append(designer2)
  installableFonts.designers.remove(designer2)
  installableFonts.designers.extend([designer2])
  installableFonts.name.en = "Commercial Fonts"
  installableFonts.name.de = "Kommerzielle Schriften"
  installableFonts.prefersRevealedUserIdentity = True
  installableFonts.response = "success"
  installableFonts.userEmail = "post@yanone.de"
  installableFonts.userName.en = "Yanone"
  installableFonts.version = "0.1.7-alpha"
  installableFonts.foundries.append(foundry)
  # print(installableFonts.designers)
  
  
  class User(object):
      def __init__(
          self,
          login=None,
          online=True,
          delegate=None,
          createUserAccount=True,
          testScenario=None,
      ):
          self.login = login
          self.prefFile = os.path.join(tempFolder, str(id(self)) + ".json")
          self.online = online
          self.credentials = ()
          self.delegate = delegate
          self.createUserAccount = createUserAccount
  
          self.loadClient(testScenario=testScenario)
  
          if self.login:
  
              if self.createUserAccount:
                  success, message = self.client.deleteUserAccount(*self.login)
                  if not success and message != [
                      "#(response.userUnknown)",
                      "#(response.userUnknown.headline)",
                  ]:
                      raise ValueError(message)
                  print("Deleted user account for %s" % self.login[0], success, message)
                  print("Creating user account for %s" % self.login[0])
                  success, message = self.client.createUserAccount(
                      "Test User", self.login[0], self.login[1], self.login[1]
                  )
                  if not success:
                      raise ValueError(message)
                  else:
                      print(
                          "Successfully created user account for",
                          self.login[0],
                          "/",
                          self.client.user(),
                      )
              else:
                  success, message = self.client.logInUserAccount(self.login[0], self.login[1])
                  assert success
  
              self.credentials = (self.client.user(), self.client.secretKey())
  
              self.clearInvitations()
              self.clearSubscriptions()
  
      # def linkUser(self):
      # 	self.unlinkUser()
      # 	if self.login:
      # 		return self.client.linkUser(*self.credentials)
  
      def expiringTestFont(self):
          return (
              self.client.publishers()[0]
              .subscriptions()[-1]
              .protocol.installableFontsCommand()[1]
              .foundries[-1]
              .families[0]
              .fonts[-1]
          )
  
      def testFont(self):
          publisher = self.client.publishers()[0]
          subscription = publisher.subscriptions()[-1]
          installableFontsCommand = subscription.protocol.installableFontsCommand()[1]
          foundry = installableFontsCommand.foundries[-1]
          family = foundry.families[-1]
          font = family.fonts[-1]
          return font
  
      def testFonts(self):
          publisher = self.client.publishers()[0]
          subscription = publisher.subscriptions()[-1]
          installableFontsCommand = subscription.protocol.installableFontsCommand()[1]
          foundry = installableFontsCommand.foundries[-1]
          family = foundry.families[-1]
          return family.fonts
  
      def clearSubscriptions(self):
          self.client.testScenario = None
          for publisher in self.client.publishers():
              success, message = publisher.delete()
              assert success
  
      def clearInvitations(self):
          self.client.testScenario = None
          for invitation in self.client.pendingInvitations():
              invitation.decline()
  
      def takeDown(self):
          self.client.testScenario = None
          self.clearInvitations()
          self.clearSubscriptions()
          if self.login and self.createUserAccount:
              self.client.deleteUserAccount(*self.login)
  
      # def unlinkUser(self):
      # 	self.client.testScenario = None
      # 	if self.login:
      # 		if self.client.user():
      # 			self.client.unlinkUser()
  
      def loadClient(self, testScenario=None):
          self.client = APIClient(
              preferences=AppKitNSUserDefaults("world.type.test%s" % id(self)) if MAC else JSON(self.prefFile),
              mothership=MOTHERSHIP,
              zmqSubscriptions=True,
              online=self.online,
              testing=True,
              delegate=self.delegate,
              secretServerAuthKey=SECRETKEY,
              commercial=True,
              appID="world.type.app",
          )
          self.client.testScenario = testScenario
  
  
  print("setting up objects finished...")
  
  
  class TestTypeWorld(unittest.TestCase):
  
      currentResult = None
      maxDiff = None
  
      def test_updateNotifications(self):
  
          print("test_updateNotifications() started...")
  
          class TestDelegate(typeworld.client.TypeWorldClientDelegate):
              def initialize(self):
                  self._subscriptionsUpdated = []
                  self._accountUpdateCheck = False
  
              def reset(self):
                  self._subscriptionsUpdated = []
                  self._accountUpdateCheck = False
  
              def subscriptionUpdateNotificationHasBeenReceived(self, subscription):
                  assert type(subscription) == typeworld.client.APISubscription
                  self._subscriptionsUpdated.append(subscription)
                  print("subscriptionUpdateNotificationHasBeenReceived", subscription)
  
              def userAccountUpdateNotificationHasBeenReceived(self):
                  print("userAccountUpdateNotificationHasBeenReceived")
                  self._accountUpdateCheck = True
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          user1.client.delegate = TestDelegate()
          user1.clearInvitations()
          user1.clearSubscriptions()
  
          user4 = User(testUser1, createUserAccount=False, testScenario="simulateTestUser1IsPro")
          self.assertEqual(user1.client.user(), user4.client.user())
          user4.client.delegate = TestDelegate()
          self.assertTrue(user4.client.requiresMessageQueueConnection())
          self.assertTrue(user4.client._zmqRunning)
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          # Reset
          user1.client.delegate.reset()
  
          # Add protected subscription
          user1.client.testScenario = "simulateTestUser1IsPro"
          result = user1.client.addSubscription(protectedSubscription)
          success, message, publisher, subscription = result
          if not success:
              print(message)
          self.assertEqual(success, True)
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          loop = 0
          while user4.client.delegate._accountUpdateCheck is False and loop < 60:  # wait
              print(
                  f"Waiting for user account to be updated... {loop}s",
                  user1.client.delegate._accountUpdateCheck,
              )
              time.sleep(1)
              loop += 1
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          # Send Update Subscription Notification
          parameters = {
              "subscriptionURL": protectedSubscriptionWithoutAccessToken,
              "APIKey": "I3ZYbDwYgG3S7lpOGI6LjEylQWt6tPS7MJtN1d3T",
              "testing": "true",
              "testScenario": "simulateUpdateSubscriptionQuotaReached",
          }
          success, response, responseObject = typeworld.client.request(MOTHERSHIP + "/updateSubscription", parameters)
          if not success:
              print(response)
          self.assertEqual(success, True)
          response = json.loads(response)
          self.assertEqual(response["response"], "paidSubscriptionRequired")
  
          # Send Update Subscription Notification
          parameters = {
              "subscriptionURL": protectedSubscriptionWithoutAccessToken,
              "APIKey": "I3ZYbDwYgG3S7lpOGI6LjEylQWt6tPS7MJtN1d3T",
              "testing": "true",
              "testScenario": "simulateAddedFontVersion",
          }
          success, response, responseObject = typeworld.client.request(MOTHERSHIP + "/updateSubscription", parameters)
          self.assertEqual(success, True)
  
          parameters = {
              "subscriptionURL": protectedSubscriptionWithoutAccessToken,
              "APIKey": "I3ZYbDwYgG3S7lpOGI6LjEylQWt6tPS7MJtN1d3T",
              "testing": "true",
              "testScenario": "simulateAddedFont",
          }
          success, response, responseObject = typeworld.client.request(MOTHERSHIP + "/updateSubscription", parameters)
          self.assertEqual(success, True)
  
          # Send Update Subscription Notification
          parameters = {
              "subscriptionURL": protectedSubscriptionWithoutAccessToken,
              "APIKey": "I3ZYbDwYgG3S7lpOGI6LjEylQWt6tPS7MJtN1d3T",
              "testing": "true",
          }
          success, response, responseObject = typeworld.client.request(MOTHERSHIP + "/updateSubscription", parameters)
          if not success:
              print(response)
          self.assertEqual(success, True)
          response = json.loads(response)
          self.assertEqual(response["response"], "success")
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          loop = 0
          while subscription not in user1.client.delegate._subscriptionsUpdated and loop < 60:  # wait
              print(f"Waiting for subscription to be updated... {loop}s")
              time.sleep(1)
              loop += 1
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          # User1 hasn't been notified to pull user account updates
          # Because it's the origin user account of the subbscription addition
          self.assertFalse(user1.client.delegate._accountUpdateCheck)
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          # User4 has been notified to pull user account updates
          # because it's the same user account but on another machine
          self.assertTrue(user4.client.delegate._accountUpdateCheck)
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          # This subscription has received an update notification
          self.assertIn(subscription, user1.client.delegate._subscriptionsUpdated)
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          user1.clearSubscriptions()
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          user2.clearSubscriptions()
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          user4.takeDown()
          user4.client.quit()
  
      def test_emptyValues(self):
  
          print("test_emptyValues() started...")
  
          self.assertTrue(HexColorDataType().valid())
          self.assertTrue(FontEncodingDataType().valid())
          self.assertTrue(EmailDataType().valid())
          self.assertTrue(BooleanDataType().valid())
          self.assertTrue(WebURLDataType().valid())
          self.assertTrue(IntegerDataType().valid())
          self.assertTrue(FloatDataType().valid())
          self.assertTrue(DateDataType().valid())
          self.assertTrue(VersionDataType().valid())
  
          # self.assertTrue(LanguageSupportDataType().valid())
          self.assertTrue(OpenTypeFeatureDataType().valid())
          self.assertTrue(OpenSourceLicenseIdentifierDataType().valid())
          self.assertTrue(SupportedAPICommandsDataType().valid())
          self.assertTrue(FontPurposeDataType().valid())
          self.assertTrue(FontMimeType().valid())
          self.assertTrue(FontStatusDataType().valid())
  
          self.assertTrue(InstallableFontsResponseType().valid())
          self.assertTrue(InstallFontAssetResponseType().valid())
          self.assertTrue(InstallFontResponseType().valid())
          self.assertTrue(UninstallFontAssedResponseType().valid())
          self.assertTrue(UninstallFontResponseType().valid())
  
      def test_EndpointResponse(self):
  
          print("test_EndpointResponse() started...")
  
          print(root)
          # Dump and reload
          json = root.dumpJSON()
          root2 = EndpointResponse()
          root2.loadJSON(json)
          self.assertTrue(root.sameContent(root2))
  
          # name
          r2 = copy.deepcopy(root)
          r2.name.en = ""
          self.assertEqual(
              r2.validate()[2],
              ["<EndpointResponse>.name is a required attribute, but empty"],
          )
  
          # canonicalURL
          r2 = copy.deepcopy(root)
          try:
              r2.canonicalURL = (
                  "typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=2&field=image"
              )
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with http:// or https://")
  
          # adminEmail
          r2 = copy.deepcopy(root)
          try:
              r2.adminEmail = "post_at_yanone.de"
          except ValueError as e:
              self.assertEqual(str(e), "Not a valid email format: post_at_yanone.de")
  
          # supportedCommands
          r2 = copy.deepcopy(root)
          try:
              r2.supportedCommands = ["unsupportedCommand"]
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "Unknown API command: 'unsupportedCommand'. "
                  "Possible: ['endpoint', 'installableFonts', 'installFonts', "
                  "'uninstallFonts']",
              )
  
          # publisherTypes
          r2 = copy.deepcopy(root)
          try:
              r2.publisherTypes = ["unsupportedCommand"]
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "Unknown publisher type: 'unsupportedCommand'. Possible: ['free', 'retail', 'custom', 'undefined']",
              )
  
          # publisherTypes
          r2 = copy.deepcopy(root)
          try:
              r2.publisherTypes = ["undefined"]
              r2.public = True
              validate = r2.validate()
              print(validate[2])
              self.assertEqual(
                  validate[2],
                  [
                      "<EndpointResponse> --> When EndpointResponse.public is set "
                      "to True, then only a restricted set of types is allowed for "
                      "EndpointResponse.publisherTypes: ['free', 'retail', 'custom']."
                      " You have 'undefined'"
                  ],
              )
  
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "Unknown publisher type: 'unsupportedCommand'. Possible: ['free', 'retail', 'custom', 'undefined']",
              )
  
          # backgroundColor
          r2 = copy.deepcopy(root)
          try:
              r2.backgroundColor = "CDEFGH"
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "Not a valid hex color of format RRGGBB (like FF0000 for red): CDEFGH",
              )
  
          # licenseIdentifier
          r2 = copy.deepcopy(root)
          try:
              r2.licenseIdentifier = "unsupportedLicense"
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "Unknown license identifier: 'unsupportedLicense'. See https://spdx.org/licenses/",
              )
  
          # logo
          r2 = copy.deepcopy(root)
          try:
              r2.logoURL = (
                  "typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=2&field=image"
              )
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with http:// or https://")
  
          # privacyPolicyURL
          r2 = copy.deepcopy(root)
          try:
              r2.privacyPolicyURL = (
                  "typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=2&field=image"
              )
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with http:// or https://")
  
          # termsOfServiceURL
          r2 = copy.deepcopy(root)
          try:
              r2.termsOfServiceURL = (
                  "typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=2&field=image"
              )
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with http:// or https://")
  
          # public
          r2 = copy.deepcopy(root)
          try:
              r2.public = "True"
          except ValueError as e:
              self.assertEqual(str(e), "Wrong data type. Is <class 'str'>, should be: <class 'bool'>.")
  
          # website
          r2 = copy.deepcopy(root)
          try:
              r2.websiteURL = (
                  "typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=2&field=image"
              )
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with http:// or https://")
  
          print("test_EndpointResponse() finished...")
  
      def test_copy(self):
  
          print("test_copy() started...")
  
          i2 = copy.copy(installableFonts)
          self.assertTrue(installableFonts.sameContent(i2))
  
          i3 = copy.deepcopy(installableFonts)
          self.assertTrue(installableFonts.sameContent(i3))
  
          print("test_copy() finished...")
  
      def test_InstallableFontsResponse(self):
  
          print("test_InstallableFontsResponse()")
  
          print(installableFonts)
          # Dump and reload
          json = installableFonts.dumpJSON()
          installableFonts2 = InstallableFontsResponse()
          installableFonts2.loadJSON(json)
          self.assertTrue(installableFonts.sameContent(installableFonts2))
  
          # designers
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.designers = "yanone"
          except ValueError as e:
              self.assertEqual(str(e), "Wrong data type. Is <class 'str'>, should be: <class 'list'>.")
  
          # error
          i2 = copy.deepcopy(installableFonts)
          i2.response = "error"
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              ["<InstallableFontsResponse> --> .response is 'error', but .errorMessage is missing."],
          )
  
          # error
          i2 = copy.deepcopy(installableFonts)
          i2.response = "error"
          try:
              data = i2.dumpDict()  # noqa: F841
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "<InstallableFontsResponse> --> .response is 'error', but .errorMessage is missing.",
              )
  
          # name
          # allowed to be emtpy
          i2 = copy.deepcopy(installableFonts)
          i2.name.en = ""
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(validate[2], [])
  
          # prefersRevealedUserIdentity
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.prefersRevealedUserIdentity = "True"
          except ValueError as e:
              self.assertEqual(str(e), "Wrong data type. Is <class 'str'>, should be: <class 'bool'>.")
  
          # type
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.response = "abc"
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "Unknown response type: 'abc'. Possible: ['success', 'error', "
                  "'noFontsAvailable', 'insufficientPermission', "
                  "'temporarilyUnavailable', 'validTypeWorldUserAccountRequired']",
              )
  
          # userEmail
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.userEmail = "post_at_yanone.de"
          except ValueError as e:
              self.assertEqual(str(e), "Not a valid email format: post_at_yanone.de")
  
          # userName
          i2 = copy.deepcopy(installableFonts)
          i2.userName.en = ""
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(validate[2], [])
  
          # foundries
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries = "yanone"
          except ValueError as e:
              self.assertEqual(str(e), "Wrong data type. Is <class 'str'>, should be: <class 'list'>.")
  
          i2 = copy.deepcopy(installableFonts)
          i2.name.en = ""
          i2.name.de = ""
          validate = i2.validate()
          print(validate[2])
          # 		print(validate)
          self.assertEqual(
              validate[1],
              [
                  "<InstallableFontsResponse> --> The response has no .name value. "
                  "It is not required, but highly recommended, to describe the "
                  "purpose of this subscription to the user (such as 'Commercial "
                  "Fonts', 'Free Fonts', etc. This is especially useful if you offer "
                  "several different subscriptions to the same user."
              ],
          )
  
          i2 = copy.deepcopy(installableFonts)
          i2.response = "error"
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              ["<InstallableFontsResponse> --> .response is 'error', but .errorMessage is missing."],
          )
  
      def test_Designer(self):
  
          print("test_Designer()")
  
          # name
          i2 = copy.deepcopy(installableFonts)
          i2.designers[0].name.en = ""
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              ["<InstallableFontsResponse>.designers --> <Designer 'None'>.name is a required attribute, but empty"],
          )
  
          # description
          # is optional, so this will pass
          i2 = copy.deepcopy(installableFonts)
          i2.designers[0].description.en = ""
          self.assertEqual(i2.validate()[2], [])
  
          # keyword
          i2 = copy.deepcopy(installableFonts)
          i2.designers[0].keyword = ""
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse>.designers --> <Designer 'Yanone'>."
                  "keyword is a required attribute, but empty",
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
                  "'YanoneKaffeesatz-Regular'> --> Has designer 'yanone', but "
                  "<InstallableFontsResponse>.designers has no matching designer.",
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
                  "'YanoneKaffeesatz-Bold'> --> Has designer 'yanone', but "
                  "<InstallableFontsResponse>.designers has no matching designer.",
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  ".families --> <Family 'Yanone Kaffeesatz'> --> Has designer "
                  "'yanone', but <InstallableFontsResponse>.designers has no "
                  "matching designer.",
              ],
          )
  
          # name
          i2 = copy.deepcopy(installableFonts)
          i2.designers[0].name.en = ""
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              ["<InstallableFontsResponse>.designers --> <Designer 'None'>.name is a required attribute, but empty"],
          )
  
          # website
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.designers[
                  0
              ].websiteURL = (
                  "typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=2&field=image"
              )
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with http:// or https://")
  
      def test_LicenseDefinition(self):
  
          print("test_LicenseDefinition()")
  
          # name
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].licenses[0].name.en = ""
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  ".licenses --> <LicenseDefinition 'None'>.name is a required "
                  "attribute, but empty"
              ],
          )
  
          # URL
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].licenses[
                  0
              ].URL = "typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=2&field=image"
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with http:// or https://")
  
          # keyword
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].licenses[0].keyword = ""
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  ".licenses --> <LicenseDefinition 'Yanone EULA'>.keyword is a "
                  "required attribute, but empty",
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> "
                  "<Font 'YanoneKaffeesatz-Regular'>.usedLicenses --> <LicenseUsage"
                  " 'yanoneEULA'> --> Has license 'yanoneEULA', but <Foundry "
                  "'Awesome Fonts'> has no matching license.",
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
                  "'YanoneKaffeesatz-Bold'>.usedLicenses --> <LicenseUsage "
                  "'yanoneEULA'> --> Has license 'yanoneEULA', but <Foundry "
                  "'Awesome Fonts'> has no matching license.",
              ],
          )
  
          i2 = copy.deepcopy(installableFonts)
          print(i2.foundries[0].licenses[0])
          assert i2.foundries[0].licenses[0].parent == i2.foundries[0]
  
      def test_Version(self):
  
          print("test_Version()")
  
          # description
          # is optional, so this will pass
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].families[0].versions[0].description.en = ""
          i2.foundries[0].families[0].versions[0].description.de = ""
          self.assertEqual(i2.validate()[2], [])
  
          # number
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].versions[0].number = "1.1.2.3"
          except ValueError as e:
              self.assertEqual(str(e), "1.1.2.3 is not valid SemVer string")
  
          # number
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].versions[0].number = "a"
          except ValueError as e:
              self.assertEqual(str(e), "False")
  
          # releaseDate
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].versions[0].releaseDate = "2010-20-21"
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "ValueError: time data '2010-20-21' does not match format '%Y-%m-%d'",
              )
  
          i2 = copy.deepcopy(installableFonts)
          print(i2.foundries[0].families[0].versions[0])
          assert i2.foundries[0].families[0].versions[0].isFontSpecific() is False
          assert i2.foundries[0].families[0].versions[0].parent == i2.foundries[0].families[0]
          assert i2.foundries[0].families[0].fonts[0].versions[0].isFontSpecific() is True
          assert i2.foundries[0].families[0].fonts[0].versions[0].parent == i2.foundries[0].families[0].fonts[0]
  
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].fonts[0].filename(i2.foundries[0].families[0].fonts[0].getVersions()[-1])
          except ValueError as e:
              self.assertEqual(str(e), "Supplied version must be str or int or float")
  
      def test_LicenseUsage(self):
  
          print("test_LicenseUsage()")
  
          # allowanceDescription
          # is optional, so this will pass
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].families[0].fonts[0].usedLicenses[0].allowanceDescription.en = None
          self.assertEqual(i2.validate()[2], [])
  
          # dateAddedForUser
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].fonts[0].usedLicenses[0].dateAddedForUser = "2010-20-21"
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "ValueError: time data '2010-20-21' does not match format '%Y-%m-%d'",
              )
  
          # keyword
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].families[0].fonts[0].usedLicenses[0].keyword = ""
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
                  "'YanoneKaffeesatz-Regular'>.usedLicenses --> <LicenseUsage ''>."
                  "keyword is a required attribute, but empty"
              ],
          )
  
          # seatsAllowed
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].fonts[0].usedLicenses[0].seatsAllowed = "5,0"
          except ValueError as e:
              self.assertEqual(str(e), "invalid literal for int() with base 10: '5,0'")
          # try the same, but modify the json string, the re-import
          json = installableFonts.dumpJSON()
          json = json.replace('"seatsAllowed": 5', '"seatsAllowed": "5,0"')
          try:
              i2.loadJSON(json)
          except ValueError as e:
              self.assertEqual(str(e), "invalid literal for int() with base 10: '5,0'")
  
          # seatsInstalled
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].fonts[0].usedLicenses[0].seatsInstalled = "1,0"
          except ValueError as e:
              self.assertEqual(str(e), "invalid literal for int() with base 10: '1,0'")
  
          # URL
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].fonts[0].usedLicenses[
                  0
              ].upgradeURL = (
                  "typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=2&field=image"
              )
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with http:// or https://")
  
          i2 = copy.deepcopy(installableFonts)
          print(i2.foundries[0].families[0].fonts[0].usedLicenses[0])
  
      def test_FontPackage(self):
  
          print("test_FontPackage()")
  
          i2 = copy.deepcopy(installableFonts)
          print(i2.foundries[0].families[0].packages[0])
  
      def test_Font(self):
  
          print("test_Font()")
  
          # billboardURLs
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].fonts[1].billboardURLs[
                  0
              ] = "typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=2&field=image"
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with http:// or https://")
  
          # dateAddedForUser
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].fonts[0].dateFirstPublished = "2010-20-21"
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "ValueError: time data '2010-20-21' does not match format '%Y-%m-%d'",
              )
  
          # designers
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].families[0].fonts[0].designerKeywords = ["gfknlergerg"]
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
                  "'YanoneKaffeesatz-Regular'> --> Has designer 'gfknlergerg', but "
                  "<InstallableFontsResponse>.designers has no matching designer."
              ],
          )
  
          # format
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].fonts[0].format = "abc"
          except ValueError as e:
              self.assertTrue("Unknown font extension: 'abc'." in str(e))
  
          # free
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].fonts[0].free = "True"
          except ValueError as e:
              self.assertEqual(str(e), "Wrong data type. Is <class 'str'>, should be: <class 'bool'>.")
  
          # name
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].families[0].fonts[0].name.en = ""
          i2.foundries[0].families[0].fonts[0].name.de = ""
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
                  "'YanoneKaffeesatz-Regular'>.name is a required attribute, "
                  "but empty"
              ],
          )
  
          # pdf
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].fonts[
                  0
              ].pdfURL = "typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=2&field=image"
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with http:// or https://")
  
          # postScriptName
          # TODO: write check for postScriptName
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].families[0].fonts[0].postScriptName = "YanoneKaffeesatzRegular"
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(validate[2], [])
  
          # protected
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].fonts[0].protected = "True"
          except ValueError as e:
              self.assertEqual(str(e), "Wrong data type. Is <class 'str'>, should be: <class 'bool'>.")
  
          # purpose
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].fonts[0].purpose = "anything"
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "Unknown font type: 'anything'. Possible: ['desktop', 'web', 'app']",
              )
  
          # status
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].fonts[0].status = "instable"
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "Unknown Font Status: 'instable'. Possible: ['prerelease', 'trial', 'stable']",
              )
  
          # uniqueID
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].families[0].fonts[0].uniqueID = ""
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
                  "'YanoneKaffeesatz-Regular'>.uniqueID is a required attribute, "
                  "but empty"
              ],
          )
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].families[0].fonts[0].uniqueID = "a" * 255
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
                  "'YanoneKaffeesatz-Regular'> --> The suggested file name is longer "
                  "than 220 characters: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
                  "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
                  "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
                  "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
                  "aaaaaaaaaaaa_1.0.otf"
              ],
          )
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].families[0].fonts[0].uniqueID = "abc:def"
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
                  "'YanoneKaffeesatz-Regular'> --> .uniqueID must not contain the "
                  "character ':' because it will be used for the font’s file name "
                  "on disk."
              ],
          )
  
          # usedLicenses
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].families[0].fonts[0].usedLicenses = []
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
                  "'YanoneKaffeesatz-Regular'>.usedLicenses is a required "
                  "attribute, but empty"
              ],
          )
          try:
              i2.foundries[0].families[0].fonts[0].usedLicenses = ["hergerg"]
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "Wrong data type. Is <class 'str'>, should be: <class 'typeworld.api.LicenseUsage'>.",
              )
  
          # variableFont
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].fonts[0].variableFont = "True"
          except ValueError as e:
              self.assertEqual(str(e), "Wrong data type. Is <class 'str'>, should be: <class 'bool'>.")
  
          # versions
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].families[0].versions = []
          i2.foundries[0].families[0].fonts[0].versions = []
          try:
              validate = i2.validate()
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "<Font 'YanoneKaffeesatz-Regular'> has no version information, and "
                  "neither has its family <Family 'Yanone Kaffeesatz'>. Either one "
                  "needs to carry version information.",
              )
  
          # other
          i2 = copy.deepcopy(installableFonts)
          print(i2.foundries[0].families[0].fonts[0])
          assert i2.foundries[0].families[0].fonts[0].parent == i2.foundries[0].families[0]
          assert type(i2.foundries[0].families[0].fonts[0].getDesigners()) == list
  
          # filename and purpose
          i2 = copy.deepcopy(installableFonts)
          self.assertEqual(
              i2.foundries[0]
              .families[0]
              .fonts[0]
              .filename(i2.foundries[0].families[0].fonts[0].getVersions()[-1].number),
              "yanone-kaffeesatz-regular_1.0.otf",
          )
          i2.foundries[0].families[0].fonts[0].format = ""
          self.assertEqual(
              i2.foundries[0]
              .families[0]
              .fonts[0]
              .filename(i2.foundries[0].families[0].fonts[0].getVersions()[-1].number),
              "yanone-kaffeesatz-regular_1.0",
          )
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
                  "'YanoneKaffeesatz-Regular'> --> Is a desktop font (see .purpose), "
                  "but has no .format value."
              ],
          )
  
          # language support
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].fonts[0].languageSupport = {"LATN": ["DEU"]}
          except ValueError as e:
              self.assertEqual(str(e), "Script tag 'LATN' needs to be a four-letter lowercase tag.")
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].fonts[0].languageSupport = {"latn": ["de"]}
          except ValueError as e:
              self.assertEqual(str(e), "Language tag 'de' needs to be a three-letter uppercase tag.")
  
          # features
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].fonts[0].features = ["aal", "liga"]
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "OpenType feature tag 'aal' needs to be a four-letter lowercase tag.",
              )
  
      def test_Family(self):
  
          print("test_Family()")
  
          # billboardURLs
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].billboardURLs[
                  0
              ] = "typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=2&field=image"
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with http:// or https://")
  
          # dateFirstPublished
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].dateFirstPublished = "2010-20-21"
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "ValueError: time data '2010-20-21' does not match format '%Y-%m-%d'",
              )
  
          # description
          # allowed to be empty
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].families[0].description.en = ""
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(validate[2], [])
  
          # designers
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[0].designerKeywords = "yanone"
          except ValueError as e:
              self.assertEqual(str(e), "Wrong data type. Is <class 'str'>, should be: <class 'list'>.")
          i2.foundries[0].families[0].designerKeywords = ["awieberg"]
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  ".families --> <Family 'Yanone Kaffeesatz'> --> Has designer "
                  "'awieberg', but <InstallableFontsResponse>.designers has no "
                  "matching designer."
              ],
          )
  
          # galleryURL
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[
                  0
              ].galleryURL = (
                  "typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=2&field=image"
              )
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with http:// or https://")
  
          # issueTrackerURL
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[
                  0
              ].issueTrackerURL = (
                  "typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=2&field=image"
              )
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with http:// or https://")
  
          # name
          i2.foundries[0].families[0].name.en = ""
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  ".families --> <Family 'None'>.name is a required attribute, "
                  "but empty"
              ],
          )
  
          # pdf
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[
                  0
              ].pdfURL = "typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=2&field=image"
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with http:// or https://")
  
          # sourceURL
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].families[
                  0
              ].sourceURL = (
                  "typeworldserver.com/?page=outputDataBaseFile&className=TWFS_FamilyBillboards&ID=2&field=image"
              )
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with http:// or https://")
  
          # uniqueID
          # TODO
  
          # versions
          # already check at font level
  
          i2 = copy.deepcopy(installableFonts)
          print(i2.foundries[0].families[0])
          assert i2.foundries[0].families[0].parent == i2.foundries[0]
          assert type(i2.foundries[0].families[0].getDesigners()) == list
          assert type(i2.foundries[0].families[0].getAllDesigners()) == list
  
          # Package Names
          i2 = copy.deepcopy(installableFonts)
  
          self.assertEqual(i2.foundries[0].families[0].getPackages()[-1].name.de, "Desktop-Schriften")
          self.assertEqual(i2.foundries[0].families[0].getPackages()[-1].name.en, "Desktop Fonts")
          self.assertEqual(i2.foundries[0].families[0].getPackages()[-1].getFormats(), ["otf"])
  
          self.assertEqual(i2.foundries[0].families[0].fonts[0].getPackageKeywords(), ["desktop"])
          self.assertEqual(
              i2.foundries[0].families[0].fonts[1].getPackageKeywords(),
              [typeworld.api.DEFAULT],
          )
  
      def test_Foundry(self):
  
          print("test_Foundry()")
  
          # description
          # allowed to be empty
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].description.en = ""
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(validate[2], [])
  
          # styling
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].styling = json.loads(
              """{"whatevs": {
              "headerColor": "F20D5E",
              "headerTextColor": "000000",
              "headerLinkColor": "E5F20D",
  
              "backgroundColor": "E5F20D",
              "textColor": "000000",
              "linkColor": "F7AD22",
  
              "selectionColor": "0D79F2",
              "selectionTextColor": "E5F20D",
  
              "buttonColor": "197AA3",
              "buttonTextColor": "FFFFFF",
  
              "informationViewBackgroundColor": "469BF5",
              "informationViewTextColor": "000000",
              "informationViewLinkColor": "E5F20D",
  
              "informationViewButtonColor": "E5F20D",
              "informationViewButtonTextColor": "000000"
  
          }, "dark": {
              "headerColor": "B10947",
              "headerTextColor": "000000",
              "headerLinkColor": "E5F20D",
  
              "backgroundColor": "1A1A1A",
              "textColor": "E5F20D",
              "linkColor": "C07F07",
  
              "selectionColor": "B10947",
              "selectionTextColor": "E5F20D",
  
              "buttonColor": "22A4DC",
              "buttonTextColor": "000000",
  
              "informationViewBackgroundColor": "000000",
              "informationViewTextColor": "999999",
              "informationViewLinkColor": "E5F20D",
  
              "informationViewButtonColor": "1E90C1",
              "informationViewButtonTextColor": "000000"
  
          } }"""
          )
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  " --> Styling keyword 'whatevs' is unknown. Known are "
                  "['light', 'dark']."
              ],
          )
  
          # styling
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].styling = json.loads(
              """{"light": {
              "headerColor": "F20D5E",
              "headerTextColor": "000000",
              "headerLinkColor": "E5F20D",
  
              "backgroundColor": "E5F20D",
              "textColor": "000000",
              "linkColor": "F7AD22",
  
              "selectionColor": "0D79F2",
              "selectionTextColor": "E5F20D",
  
              "buttonColor": "197AA3",
              "buttonTextColor": "FFFFFF",
  
              "informationViewBackgroundColor": "469BF5",
              "informationViewTextColor": "000000",
              "informationViewLinkColor": "E5F20D",
  
              "informationViewButtonColor": "E5F20D",
              "informationViewButtonTextColor": "000000",
  
              "logoURL": "awesomefonts.com/logo.svg"
  
          }, "dark": {
              "headerColor": "B10947",
              "headerTextColor": "000000",
              "headerLinkColor": "E5F20D",
  
              "backgroundColor": "1A1A1A",
              "textColor": "E5F20D",
              "linkColor": "C07F07",
  
              "selectionColor": "B10947",
              "selectionTextColor": "E5F20D",
  
              "buttonColor": "22A4DC",
              "buttonTextColor": "000000",
  
              "informationViewBackgroundColor": "000000",
              "informationViewTextColor": "999999",
              "informationViewLinkColor": "E5F20D",
  
              "informationViewButtonColor": "1E90C1",
              "informationViewButtonTextColor": "000000"
  
          } }"""
          )
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  " --> .styling 'logoURL' attribute: Needs to start with http:// or "
                  "https://"
              ],
          )
  
          # styling
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].styling = json.loads(
              """{"light": {
              "headerColor": "F20D5I",
              "headerTextColor": "000000",
              "headerLinkColor": "E5F20D",
  
              "backgroundColor": "E5F20D",
              "textColor": "000000",
              "linkColor": "F7AD22",
  
              "selectionColor": "0D79F2",
              "selectionTextColor": "E5F20D",
  
              "buttonColor": "197AA3",
              "buttonTextColor": "FFFFFF",
  
              "informationViewBackgroundColor": "469BF5",
              "informationViewTextColor": "000000",
              "informationViewLinkColor": "E5F20D",
  
              "informationViewButtonColor": "E5F20D",
              "informationViewButtonTextColor": "000000"
  
          }, "dark": {
              "headerColor": "B10947",
              "headerTextColor": "000000",
              "headerLinkColor": "E5F20D",
  
              "backgroundColor": "1A1A1A",
              "textColor": "E5F20D",
              "linkColor": "C07F07",
  
              "selectionColor": "B10947",
              "selectionTextColor": "E5F20D",
  
              "buttonColor": "22A4DC",
              "buttonTextColor": "000000",
  
              "informationViewBackgroundColor": "000000",
              "informationViewTextColor": "999999",
              "informationViewLinkColor": "E5F20D",
  
              "informationViewButtonColor": "1E90C1",
              "informationViewButtonTextColor": "000000"
  
          } }"""
          )
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
                  " --> .styling color attribute 'headerColor': Not a valid hex color"
                  " of format RRGGBB (like FF0000 for red): F20D5I"
              ],
          )
  
          # email
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].email = "post_at_yanone.de"
          except ValueError as e:
              self.assertEqual(str(e), "Not a valid email format: post_at_yanone.de")
  
          # licenses
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].licenses = "yanoneEULA"
          except ValueError as e:
              self.assertEqual(str(e), "Wrong data type. Is <class 'str'>, should be: <class 'list'>.")
  
          # name
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].name.en = ""
          i2.foundries[0].name.de = ""
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              ["<InstallableFontsResponse>.foundries --> <Foundry 'None'>.name is a required attribute, but empty"],
          )
  
          # supportEmail
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].supportEmail = "post_at_yanone.de"
          except ValueError as e:
              self.assertEqual(str(e), "Not a valid email format: post_at_yanone.de")
  
          # telephone
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].telephone = "+49176123456a456"
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with + and contain only numbers 0-9")
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].telephone = "0049176123456456"
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with + and contain only numbers 0-9")
          try:
              i2.foundries[0].telephone = "a"
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with + and contain only numbers 0-9")
  
          # socialURLs
          self.assertEqual(
              str(foundry.socialURLs),
              "['https://facebook.com/pages/YanoneYanone', 'https://twitter.com/yanone']",
          )
  
          # website
          i2 = copy.deepcopy(installableFonts)
          try:
              i2.foundries[0].websiteURL = 1
          except ValueError as e:
              self.assertEqual(str(e), "Needs to start with http:// or https://")
  
          i2 = copy.deepcopy(installableFonts)
          print(i2.foundries[0])
          assert i2.foundries[0].name.getTextAndLocale("en") == ("Awesome Fonts", "en")
          assert i2.foundries[0].name.getTextAndLocale(["en"]) == ("Awesome Fonts", "en")
          assert i2.foundries[0].name.getTextAndLocale("de") == ("Tolle Schriften", "de")
          assert i2.foundries[0].name.getTextAndLocale(["de"]) == (
              "Tolle Schriften",
              "de",
          )
          assert i2.foundries[0].name.getTextAndLocale(["ar"]) == ("Awesome Fonts", "en")
  
          i2 = copy.deepcopy(installableFonts)
          i2.foundries[0].name.en = "abc" * 1000
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse>.foundries --> <Foundry 'abcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
                  "abcabcabcabcabc'>.name --> <MultiLanguageText> --> Language entry "
                  "'en' is too long. Allowed are 100 characters."
              ],
          )
  
          i2 = copy.deepcopy(installableFonts)
          foundry2 = copy.deepcopy(i2.foundries[0])
          i2.foundries.append(foundry2)
          validate = i2.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              [
                  "<InstallableFontsResponse> --> Duplicate unique foundry IDs: ['yanone']",
                  "<InstallableFontsResponse> --> Duplicate unique family IDs: ['yanone-yanonekaffeesatz']",
                  "<InstallableFontsResponse> --> Duplicate unique family IDs: "
                  "['yanone-kaffeesatz-regular', 'yanone-kaffeesatz-bold']",
              ],
          )
  
      def test_otherStuff(self):
  
          print("test_otherStuff()")
  
          # __repr__
          s = typeworld.api.StringDataType()
          s.value = "a"
          self.assertEqual(str(s), "<StringDataType 'a'>")
  
          assert type(root.supportedCommands.index("installableFonts")) == int
          assert installableFonts.designers[0].parent == installableFonts
  
          success, message = user1.client.endpointCommand(protectedSubscription)
          self.assertTrue(success)
  
          success, message = user1.client.endpointCommand(protectedSubscription[1:])
          self.assertFalse(success)
          self.assertEqual(message, "Unknown custom protocol, known are: ['typeworld']")
  
          user1.client.testScenario = "simulateInvalidAPIJSONResponse"
          success, message = user1.client.endpointCommand(protectedSubscription)
          self.assertFalse(success)
          self.assertEqual(
              message,
              "Unknown license identifier: 'mefowefbhrf'. See https://spdx.org/licenses/",
          )
  
          user1.client.testScenario = "simulateFaultyAPIJSONResponse"
          success, message = user1.client.endpointCommand(protectedSubscription)
          self.assertFalse(success)
          print(message)  # nocoverage
          self.assertTrue("Invalid control character at: line 9 column 47 (char 300)" in message)
  
          # TODO: Invite user to subscription with API endpoint as source
  
          self.assertEqual(
              typeworld.client.helpers.addAttributeToURL("https://type.world?hello=world#xyz", "hello=type&world=type"),
              "https://type.world?hello=type&world=type#xyz",
          )
  
          # # load json/dict
          # d = {"en": "Hello World", "de": "Hallo Welt"}
          # j = json.dumps(d)
          # d1 = typeworld.api.MultiLanguageText(json=j).dumpDict()
          # d2 = typeworld.api.MultiLanguageText(dict=d).dumpDict()
  
          # URL parsing and constructing
          success, protocol = typeworld.client.getProtocol(freeNamedSubscription)
          self.assertEqual(protocol.secretURL(), freeNamedSubscription)
          self.assertEqual(protocol.unsecretURL(), freeNamedSubscription)
  
          self.assertEqual(
              protocol.shortUnsecretURL(),
              "typeworld://json+https//s9lWvayTEOaB9eIIMA67@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/",
          )
  
          self.assertEqual(
              typeworld.client.URL(freeNamedSubscription).HTTPURL(),
              "https://typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/",
          )
  
          typeworld.client.helpers.Garbage(20, uppercase=True, lowercase=True, numbers=True, punctuation=True)
  
      def test_InstallFontsResponse(self):
  
          print("test_InstallFontsResponse()")
  
          installFonts = InstallFontsResponse()
          asset = InstallFontAsset()
          installFonts.assets.append(asset)
          asset.uniqueID = "abc"
          asset.response = "success"
          asset.mimeType = "font/otf"
          asset.version = "1.0"
          # 		asset.encoding = 'base64' # missing
          asset.data = b"ABC"
          validate = asset.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              ["<InstallFontAsset> --> .data is set, but .encoding is missing"],
          )
  
          installFonts = InstallFontsResponse()
          asset = InstallFontAsset()
          installFonts.assets.append(asset)
          asset.uniqueID = "abc"
          asset.response = "success"
          asset.encoding = "base64"
          asset.version = "1.0"
          # 		asset.mimeType = 'font/otf' # missing
          asset.data = b"ABC"
          validate = asset.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              ["<InstallFontAsset> --> .data is set, but .mimeType is missing"],
          )
  
          installFonts = InstallFontsResponse()
          asset = InstallFontAsset()
          installFonts.assets.append(asset)
          asset.uniqueID = "abc"
          asset.response = "success"
          asset.encoding = "base64"
          asset.version = "1.0"
          # 		asset.mimeType = 'font/otf' # missing
          asset.dataURL = "https://awesomefonts.com/font.otf"
          validate = asset.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              ["<InstallFontAsset> --> .dataURL is set, but .mimeType is missing"],
          )
  
          installFonts = InstallFontsResponse()
          asset = InstallFontAsset()
          installFonts.assets.append(asset)
          asset.uniqueID = "abc"
          asset.response = "success"
          asset.encoding = "base64"
          asset.version = "1.0"
          asset.mimeType = "font/otf"  # missing
          asset.dataURL = "https://awesomefonts.com/font.otf"
          asset.data = b"ABC"
          validate = asset.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              ["<InstallFontAsset> --> Either .dataURL or .data can be defined, not both"],
          )
  
          installFonts = InstallFontsResponse()
          asset = InstallFontAsset()
          installFonts.assets.append(asset)
          asset.uniqueID = "abc"
          asset.response = "success"
          asset.mimeType = "font/otf"
          asset.encoding = "base64"
          asset.version = "1.0"
          # 		asset.data = b'ABC' # missing
          validate = asset.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              ["<InstallFontAsset> --> .response is set to success, but neither .data nor .dataURL are set."],
          )
  
          installFonts = InstallFontsResponse()
          asset = InstallFontAsset()
          installFonts.assets.append(asset)
          asset.uniqueID = "abc"
          asset.mimeType = "font/otf"
          asset.response = "error"
          asset.version = "1.0"
          validate = asset.validate()
          print(validate[2])
          self.assertEqual(
              validate[2],
              ["<InstallFontAsset> --> .response is 'error', but .errorMessage is missing."],
          )
  
          installFonts = InstallFontsResponse()
          asset = InstallFontAsset()
          try:
              asset.mimeType = "font/whatevs"
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "Unknown font MIME Type: 'font/whatevs'. Possible: "
                  "['font/collection', 'font/otf', 'font/sfnt', 'font/ttf']",
              )
  
          asset = InstallFontAsset()
          try:
              asset.response = "a"
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "Unknown response type: 'a'. Possible: ['success', 'error', "
                  "'unknownFont', 'insufficientPermission', "
                  "'temporarilyUnavailable', 'validTypeWorldUserAccountRequired', "
                  "'loginRequired', 'revealedUserIdentityRequired', "
                  "'seatAllowanceReached', 'fontExpired']",
              )
  
      def test_UninstallFontsResponse(self):
  
          print("test_UninstallFontsResponse()")
  
          asset = UninstallFontAsset()
          try:
              asset.response = "a"
          except ValueError as e:
              self.assertEqual(
                  str(e),
                  "Unknown response type: 'a'. Possible: ['success', 'error', "
                  "'unknownFont', 'insufficientPermission', "
                  "'temporarilyUnavailable', 'validTypeWorldUserAccountRequired', "
                  "'loginRequired', 'unknownInstallation']",
              )
  
      def test_InstallableFontsResponse_Old(self):
  
          # DOCU
          RootResponse().docu()
          EndpointResponse().docu()
          InstallableFontsResponse().docu()
          InstallFontsResponse().docu()
          UninstallFontsResponse().docu()
  
          # Data types
          try:
              BooleanDataType().put("abc")
          except ValueError:
              pass
  
          try:
              IntegerDataType().put("abc")
          except ValueError:
              pass
  
          try:
              FloatDataType().put("abc")
          except ValueError:
              pass
  
          FontEncodingDataType().valid()
          try:
              FontEncodingDataType().put("abc")
          except ValueError:
              pass
  
          try:
              VersionDataType().put("0.1.2.3")
          except ValueError:
              pass
  
          try:
              WebURLDataType().put("yanone.de")
          except ValueError:
              pass
  
          try:
              EmailDataType().put("post@yanone")
          except ValueError:
              pass
  
          try:
              HexColorDataType().put("ABCDEF")  # pass
              HexColorDataType().put("ABCDEX")  # error
          except ValueError:
              pass
  
          try:
              HexColorDataType().put("012345678")
          except ValueError:
              pass
  
          try:
              DateDataType().put("2018-02-28")  # pass
              DateDataType().put("2018-02-30")  # error
          except ValueError:
              pass
  
          text = MultiLanguageText()
          self.assertEqual(text.customValidation()[2], ["Needs to contain at least one language field"])
          self.assertEqual(bool(text), False)
          text.en = "Hello"
          self.assertEqual(bool(text), True)
  
          text.en = "Hello"
          self.assertEqual(text.customValidation()[2], [])
  
          # HTML in Text
          text.en = "Hello, <b>world</b>"
          self.assertNotEqual(text.customValidation()[2], [])
  
          # Markdown in Text
          text.en = "Hello, _world_"
          self.assertNotEqual(text.customValidation()[2], [])
  
          description = MultiLanguageLongText()
          self.assertEqual(bool(description), False)
          description.en = "Hello"
          self.assertEqual(bool(description), True)
  
          description.en = "Hello"
          self.assertEqual(description.customValidation()[2], [])
  
          # HTML in Text
          description.en = "Hello, <b>world</b>"
          self.assertNotEqual(description.customValidation()[2], [])
  
          # Markdown in Text
          description.en = "Hello, _world_"
          self.assertEqual(description.customValidation()[2], [])
  
          i2 = copy.deepcopy(installableFonts)
          font = i2.foundries[0].families[0].fonts[0]
  
          # __repr__
          print(font.uniqueID)
  
          _list = FontListProxy()
          print(_list)
          _list.append(Font())
          _list[0] = Font()
  
          _list = font.nonListProxyBasedKeys()
  
          font.doesntHaveThisAttribute = "a"
          print(font.doesntHaveThisAttribute)
          try:
              print(font.doesntHaveThatAttribute)
          except AttributeError:
              pass
  
          font.postScriptName = ""
  
          font.name = MultiLanguageText()
          font.name.en = None
          print(font.name.parent)
          # try:
          print(installableFonts.validate())
          # except:
          # 	pass
  
          usedLicense = LicenseUsage()
          usedLicense.keyword = "awesomeFontsEULAAAAA"
          font.usedLicenses.append(usedLicense)
          # try:
          print(installableFonts.validate())
          # except:
          # 	pass
  
          font.versions = []
          font.parent.versions = []
          # try:
          print(installableFonts.validate())
          # except:
          # 	pass
  
          font.designerKeywords.append("maxx")
          # try:
          print(installableFonts.validate())
          # except:
          # 	pass
  
      def test_helpers(self):
  
          print("test_helpers()")
  
          # makeSemVer(a.number)
          self.assertEqual(makeSemVer(2.1), "2.1.0")
  
          # urlIsValid()
          self.assertEqual(
              typeworld.client.urlIsValid(
                  "typeworld://json+https//s9lWvayTEOaB9eIIMA67:bN0QnnNsaE4LfHlOMGkm"
                  "@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
              )[0],
              True,
          )
          self.assertEqual(
              typeworld.client.urlIsValid(
                  "https//s9lWvayTEOaB9eIIMA67:bN0QnnNsaE4LfHlOMGkm@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
              )[0],
              False,
          )
          self.assertEqual(
              typeworld.client.urlIsValid(
                  "typeworldjson://json+https//s9lWvayTEOaB9eIIMA67:"
                  "bN0QnnNsaE4LfHlOMGkm@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
              )[0],
              False,
          )
  
          # splitJSONURL()
          self.assertEqual(
              typeworld.client.splitJSONURL(
                  "typeworld://json+https//s9lWvayTEOaB9eIIMA67:bN0QnnNsaE4LfHlOMGkm"
                  ":accessToken@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
              ),
              (
                  "typeworld://",
                  "json",
                  "https://",
                  "s9lWvayTEOaB9eIIMA67",
                  "bN0QnnNsaE4LfHlOMGkm",
                  "accessToken",
                  "typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/",
              ),
          )
          self.assertEqual(
              typeworld.client.splitJSONURL(
                  "typeworld://json+https//s9lWvayTEOaB9eIIMA67@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
              ),
              (
                  "typeworld://",
                  "json",
                  "https://",
                  "s9lWvayTEOaB9eIIMA67",
                  "",
                  "",
                  "typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/",
              ),
          )
          self.assertEqual(
              typeworld.client.splitJSONURL("typeworld://json+https//typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"),
              (
                  "typeworld://",
                  "json",
                  "https://",
                  "",
                  "",
                  "",
                  "typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/",
              ),
          )
          self.assertEqual(
              typeworld.client.splitJSONURL("typeworld://json+http//typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"),
              (
                  "typeworld://",
                  "json",
                  "http://",
                  "",
                  "",
                  "",
                  "typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/",
              ),
          )
  
          if ONLINE:
              # Locale
              self.assertTrue("en" in user0.client.locale())
              user0.client.set("localizationType", "systemLocale")
              self.assertTrue("en" in user0.client.locale())
              user0.client.set("localizationType", "customLocale")
              self.assertTrue("en" in user0.client.locale())
              user0.client.set("customLocaleChoice", "de")
              self.assertEqual(user0.client.locale(), ["de", "en"])
  
          from typeworld.client.helpers import addAttributeToURL
  
          self.assertEqual(
              addAttributeToURL("https://type.world/", "hello=world"),
              "https://type.world/?hello=world",
          )
          self.assertEqual(
              addAttributeToURL("https://type.world/?foo=bar", "hello=world"),
              "https://type.world/?foo=bar&hello=world",
          )
  
      def test_simulateExternalScenarios(self):
  
          print("test_simulateExternalScenarios()")
  
          user0.takeDown()
  
          user0.client.testScenario = "simulateEndpointDoesntSupportInstallFontCommand"
          success, message, publisher, subscription = user0.client.addSubscription(freeSubscription)
          self.assertEqual(success, False)
  
          user0.client.testScenario = "simulateBreakingAPIVersion"
          success, message, publisher, subscription = user0.client.addSubscription(freeSubscription)
          self.assertEqual(success, False)
          self.assertEqual(
              message,
              [
                  "#(response.appUpdateRequired)",
                  "#(response.appUpdateRequired.headline)",
              ],
          )
  
          # Supposed to pass
          user0.client.testScenario = "simulateNonBreakingHigherAPIVersion"
          success, message, publisher, subscription = user0.client.addSubscription(freeSubscription)
          if not success:
              print(message)
          self.assertEqual(success, True)
          user0.client.publishers()[0].delete()
  
          user0.client.testScenario = "simulateEndpointDoesntSupportInstallableFontsCommand"
          success, message, publisher, subscription = user0.client.addSubscription(freeSubscription)
          self.assertEqual(success, False)
  
          user0.client.testScenario = "simulateCustomError"
          success, message, publisher, subscription = user0.client.addSubscription(freeSubscription)
          self.assertEqual(success, False)
          self.assertEqual(message.getText(), "simulateCustomError")
  
          user0.client.testScenario = "simulateNotOnline"
          success, message, publisher, subscription = user0.client.addSubscription(freeSubscription)
          self.assertEqual(success, False)
  
          user0.client.testScenario = "simulateProgrammingError"
          success, message, publisher, subscription = user0.client.addSubscription(freeSubscription)
          self.assertEqual(success, False)
          self.assertEqual(message, "HTTP Error 500")
  
          success, message, publisher, subscription = user0.client.addSubscription(
              "typeworld://unknownprotocol+https//typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
          )
          self.assertEqual(success, False)
          self.assertEqual(message, "Protocol unknownprotocol doesn’t exist in this app (yet).")
  
          success, message, publisher, subscription = user0.client.addSubscription(
              "typeworld://json+https://s9lWvayTEOaB9eIIMA67:bN0QnnNsaE4LfHlOMGkm@"
              "typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/@"
          )
          self.assertEqual(success, False)
          self.assertEqual(message, "URL contains more than one @ sign, so don’t know how to parse it.")
  
          success, message, publisher, subscription = user0.client.addSubscription(
              "typeworldjson://json+https://s9lWvayTEOaB9eIIMA67:bN0QnnNsaE4LfHlOMGkm"
              "@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
          )
          self.assertEqual(success, False)
          self.assertEqual(message, "Unknown custom protocol, known are: ['typeworld']")
  
          success, message, publisher, subscription = user0.client.addSubscription(
              "typeworldjson//json+https://s9lWvayTEOaB9eIIMA67:bN0QnnNsaE4LfHlOMGkm"
              "@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
          )
          self.assertEqual(success, False)
          self.assertEqual(message, "Unknown custom protocol, known are: ['typeworld']")
  
          success, message, publisher, subscription = user0.client.addSubscription(
              "typeworld://json+https://s9lWvayTEOaB9eIIMA67:bN0QnnNsaE4LfHlOMGkm@"
              "typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/:"
          )
          self.assertEqual(success, False)
          self.assertEqual(
              message,
              "URL contains more than one :// combination, so don’t know how to parse it.",
          )
  
          success, message, publisher, subscription = user0.client.addSubscription(
              "typeworld://json+s9lWvayTEOaB9eIIMA67:bN0QnnNsaE4LfHlOMGkm@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
          )
          self.assertEqual(success, False)
          self.assertEqual(message, "URL is malformed.")
  
      def test_normalSubscription(self):
  
          print("test_normalSubscription() started...")
  
          # Reset Test Conditions
          success, response, responseObject = typeworld.client.request("https://typeworldserver.com/resetTestConditions")
          print(response)
          self.assertTrue(success)
  
          self.assertTrue(user0.client.online(MOTHERSHIP.split("//")[1].split("/")[0].split(":")[0]))
  
          self.assertTrue(user2.client.user())
  
          # verifyCredentials without subscriptionURL
          parameters = {
              "anonymousAppID": user2.client.anonymousAppID(),
              "anonymousTypeWorldUserID": user2.client.user(),
              "APIKey": "I3ZYbDwYgG3S7lpOGI6LjEylQWt6tPS7MJtN1d3T",
              "testing": "true",
          }
          success, response, responseObject = typeworld.client.request(MOTHERSHIP + "/verifyCredentials", parameters)
          print(response)
          response = json.loads(response)
          self.assertEqual(success, True)
          self.assertEqual(response["response"], "success")
  
          # test for key being hidden from reply in case of bad request
          parameters = {
              "anonymousAppID": user2.client.anonymousAppID(),
              "anonymousTypeWorldUserID": user2.client.user(),
              "APIKey": "I3ZYbDwYgG3S7lpOGI6LjEylQWt6tPS7MJtN1d3T",
              "testing": "true",
          }
          success, response, responseObject = typeworld.client.request(
              MOTHERSHIP.replace("://", ":/") + "/verifyCredentials", parameters
          )
          print("hidden key test:", response)
          self.assertEqual(success, False)
          self.assertIn("'APIKey': '*****'", response)
  
          # General stuff
          self.assertEqual(type(user0.client.locale()), list)
          self.assertTrue(typeworld.client.urlIsValid(freeSubscription)[0])
  
          # Scenario 1:
          # Test a simple subscription of free fonts without Type.World user account
  
          result = user0.client.addSubscription(freeSubscription)
          print("Scenario 1:", result)
          success, message, publisher, subscription = result
          self.assertEqual(success, True)
          self.assertEqual(
              user0.client.publishers()[0].canonicalURL,
              "https://typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/",
          )
          self.assertEqual(len(user0.client.publishers()[0].subscriptions()), 1)
          self.assertEqual(
              len(user0.client.publishers()[0].subscriptions()[-1].protocol.installableFontsCommand()[1].foundries),
              1,
          )
          self.assertEqual(
              user0.client.publishers()[0]
              .subscriptions()[-1]
              .protocol.installableFontsCommand()[1]
              .foundries[0]
              .name.getTextAndLocale(),
              ("Test Foundry", "en"),
          )
  
          self.assertFalse(subscription.hasProtectedFonts())
  
          self.assertEqual(
              user0.client.publishers()[0].subscriptions()[-1].protocol.protocolName(),
              "Type.World JSON Protocol",
          )
  
          # Name
          self.assertEqual(user0.client.publishers()[0].name()[0], "Test Publisher (Don’t Touch)")
          self.assertEqual(user0.client.publishers()[0].subscriptions()[0].name(), "Free Fonts")
  
          # Reload client
          # Equal to closing the app and re-opening,
          # so code gets loaded from disk/defaults
          user0.loadClient()
  
          user0.clearSubscriptions()
  
          # Flat subscription
          result = user0.client.addSubscription(flatFreeSubscription)
          success, message, publisher, subscription = result
          if not success:
              print(message)  # nocoverage
          self.assertEqual(success, True)
  
          data = user0.client.get(f"subscription({flatFreeSubscription})")["data"]
          self.assertTrue("installFonts" in data and data["installFonts"])
          i = typeworld.api.InstallFontsResponse()
          i.loadJSON(data["installFonts"])
          self.assertEqual(i.validate()[2], [])
  
          # Update Flat subscription
          result = user0.client.publishers()[0].subscriptions()[0].update()
          success, message, changed = result
          self.assertEqual(success, True)
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          # Install All 4 Fonts
          user0.client.publishers()[0].subscriptions()[0].set("acceptedTermsOfService", True)
          fonts = [[x.uniqueID, x.getVersions()[-1].number] for x in user0.testFonts()]
          success, message = user0.client.publishers()[0].subscriptions()[0].installFonts(fonts)
          if not success:
              print(message)  # nocoverage
          self.assertEqual(success, True)
          self.assertEqual(user0.client.publishers()[0].subscriptions()[0].amountInstalledFonts(), 4)
  
          # Remove Font
          user0.client.testScenario = "simulateNoPath"
          success, message = user0.client.publishers()[0].subscriptions()[0].removeFonts([user0.testFont().uniqueID])
          if not success:
              print(message)  # nocoverage
          self.assertEqual(success, False)
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          # Remove all 4 fonts
          user0.client.testScenario = None
          fonts = [x.uniqueID for x in user0.testFonts()]
          success, message = user0.client.publishers()[0].subscriptions()[0].removeFonts(fonts)
          if not success:
              print(message)  # nocoverage
          self.assertEqual(success, True)
          self.assertEqual(user0.client.publishers()[0].subscriptions()[0].amountInstalledFonts(), 0)
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          user0.loadClient()
  
          user0.clearSubscriptions()
  
          # #
  
          # Scenario 2:
          # Protected subscription, installation on machine without user account
          # This is supposed to fail because accessing protected subscriptions requires
          # a valid Type.World user account, but user0 is not linked with a user account
          result = user0.client.addSubscription(protectedSubscription)
          print("Scenario 2:", result)
          success, message, publisher, subscription = result
          self.assertEqual(success, False)
          self.assertEqual(
              message,
              [
                  "#(response.validTypeWorldUserAccountRequired)",
                  "#(response.validTypeWorldUserAccountRequired.headline)",
              ],
          )
  
          # #
  
          # verifyCredentials with subscriptionURL
          parameters = {
              "anonymousAppID": user1.client.anonymousAppID(),
              "anonymousTypeWorldUserID": user1.client.user(),
              "APIKey": "I3ZYbDwYgG3S7lpOGI6LjEylQWt6tPS7MJtN1d3T",
              "subscriptionURL": protectedSubscriptionWithoutAccessToken,
              "testing": "true",
          }
          success, response, responseObject = typeworld.client.request(MOTHERSHIP + "/verifyCredentials", parameters)
          print(success, response)
          self.assertEqual(success, True)
          response = json.loads(response)
          print(response)
          self.assertEqual(response["response"], "invalid")
  
          # Scenario 3:
          # Protected subscription,
          # installation on first machine with Type.World user account
          result = user1.client.addSubscription(protectedSubscription)
          print("Scenario 3:", result)
          success, message, publisher, subscription = result
  
          self.assertEqual(success, True)
          self.assertEqual(len(user1.client.publishers()[0].subscriptions()), 1)
          self.assertEqual(
              len(user1.client.publishers()[0].subscriptions()[-1].protocol.installableFontsCommand()[1].foundries),
              1,
          )
          self.assertTrue(subscription.hasProtectedFonts())
  
          # verifyCredentials with subscriptionURL
          parameters = {
              "anonymousAppID": user1.client.anonymousAppID(),
              "anonymousTypeWorldUserID": user1.client.user(),
              "APIKey": "I3ZYbDwYgG3S7lpOGI6LjEylQWt6tPS7MJtN1d3T",
              "subscriptionURL": protectedSubscriptionWithoutAccessToken,
              "testing": "true",
          }
          success, response, responseObject = typeworld.client.request(MOTHERSHIP + "/verifyCredentials", parameters)
          self.assertEqual(success, True)
          response = json.loads(response)
          print(response)
          self.assertEqual(response["response"], "success")
  
          # verifyCredentials with subscriptionURL
          parameters = {
              "anonymousAppID": user1.client.anonymousAppID(),
              "anonymousTypeWorldUserID": user1.client.user(),
              "APIKey": "I3ZYbDwYgG3S7lpOGI6LjEylQWt6tPS7MJtN1d3T",
              "subscriptionURL": protectedSubscriptionWithoutAccessToken.replace("@", "a@"),  # change URL slightly
              "testing": "true",
          }
          success, response, responseObject = typeworld.client.request(MOTHERSHIP + "/verifyCredentials", parameters)
          self.assertEqual(success, True)
          response = json.loads(response)
          print(response)
          self.assertEqual(response["response"], "invalid")
  
          # Clear
          user1.clearSubscriptions()
  
          # verifyCredentials with subscriptionURL
          parameters = {
              "anonymousAppID": user1.client.anonymousAppID(),
              "anonymousTypeWorldUserID": user1.client.user(),
              "APIKey": "I3ZYbDwYgG3S7lpOGI6LjEylQWt6tPS7MJtN1d3T",
              "subscriptionURL": protectedSubscriptionWithoutAccessToken,
              "testing": "true",
          }
          success, response, responseObject = typeworld.client.request(MOTHERSHIP + "/verifyCredentials", parameters)
          self.assertEqual(success, True)
          response = json.loads(response)
          print(response)
          self.assertEqual(response["response"], "invalid")
  
          # Re-add subscription
          result = user1.client.addSubscription(protectedSubscription)
          success, message, publisher, subscription = result
          self.assertEqual(success, True)
  
          # verifyCredentials with subscriptionURL
          parameters = {
              "anonymousAppID": user1.client.anonymousAppID(),
              "anonymousTypeWorldUserID": user1.client.user(),
              "APIKey": "I3ZYbDwYgG3S7lpOGI6LjEylQWt6tPS7MJtN1d3T",
              "subscriptionURL": protectedSubscriptionWithoutAccessToken,
              "testing": "true",
          }
          success, response, responseObject = typeworld.client.request(MOTHERSHIP + "/verifyCredentials", parameters)
          self.assertEqual(success, True)
          response = json.loads(response)
          print(response)
          self.assertEqual(response["response"], "success")
  
          parameters = {
              "subscriptionURL": protectedSubscriptionWithoutAccessToken,
              "APIKey": "I3ZYbDwYgG3S7lpOGI6LjEylQWt6tPS7MJtN1d3T",
              "testing": "true",
          }
          success, response, responseObject = typeworld.client.request(MOTHERSHIP + "/updateSubscription", parameters)
          if not success:
              print(response)
          self.assertEqual(success, True)
          response = json.loads(response)
          self.assertEqual(response["response"], "success")
  
          time.sleep(5)
  
          # Protocol
          success, protocol = typeworld.client.getProtocol(protectedSubscription)
          self.assertEqual(protocol.secretURL(), protectedSubscriptionWithoutAccessToken)
          self.assertEqual(
              protocol.unsecretURL(),
              protectedSubscriptionWithoutAccessToken.replace(":OxObIWDJjW95SkeL3BNr@", ":secretKey@"),
          )
  
          # saveURL
          self.assertEqual(
              user1.client.publishers()[0].subscriptions()[-1].protocol.unsecretURL(),
              "typeworld://json+https//s9lWvayTEOaB9eIIMA67:secretKey@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/",
          )
          # completeURL
          self.assertEqual(
              user1.client.publishers()[0].subscriptions()[-1].protocol.secretURL(),
              protectedSubscriptionWithoutAccessToken,
          )
  
          # Reload client
          # Equal to closing the app and re-opening, so code gets
          # loaded from disk/defaults
          user1.loadClient()
          self.assertEqual(
              user1.client.publishers()[0].subscriptions()[-1].protocol.secretURL(),
              protectedSubscriptionWithoutAccessToken,
          )
  
          user1.client.testScenario = "simulateCentralServerNotReachable"
          user1.client.publishers()[0].subscriptions()[0].stillAlive()
          user1.client.testScenario = "simulateCentralServerProgrammingError"
          user1.client.publishers()[0].subscriptions()[0].stillAlive()
          user1.client.testScenario = "simulateCentralServerErrorInResponse"
          user1.client.publishers()[0].subscriptions()[0].stillAlive()
          user1.client.testScenario = "simulateNotOnline"
          user1.client.publishers()[0].subscriptions()[0].stillAlive()
          user1.client.testScenario = None
          user1.client.publishers()[0].subscriptions()[0].stillAlive()
  
          user1.client.testScenario = "simulateFaultyClientVersion"
          success, message = user1.client.downloadSubscriptions()
          self.assertEqual(success, False)
          self.assertEqual(
              message,
              [
                  "#(response.clientVersionInvalid)",
                  "#(response.clientVersionInvalid.headline)",
              ],
          )
  
          user1.client.testScenario = "simulateNoClientVersion"
          success, message = user1.client.downloadSubscriptions()
          self.assertEqual(success, False)
          self.assertEqual(
              message,
              [
                  "#(response.Required parameter clientVersion is missing.)",
                  "#(response.Required parameter clientVersion is missing..headline)",
              ],
          )
  
          user1.client.testScenario = "simulateCentralServerNotReachable"
          self.assertEqual(user1.client.downloadSubscriptions()[0], False)
          user1.client.testScenario = "simulateCentralServerProgrammingError"
          self.assertEqual(user1.client.downloadSubscriptions()[0], False)
          user1.client.testScenario = "simulateCentralServerErrorInResponse"
          self.assertEqual(user1.client.downloadSubscriptions()[0], False)
          user1.client.testScenario = "simulateNotOnline"
          self.assertEqual(user1.client.downloadSubscriptions()[0], False)
          user1.client.testScenario = None
          self.assertEqual(user1.client.downloadSubscriptions()[0], True)
          success, message, changes = user1.client.publishers()[0].update()
          print("Updating publisher:", success, message, changes)
          self.assertEqual(success, True)
          print("changes", changes)
          self.assertFalse(changes)
          success, message, changes = user1.client.publishers()[0].subscriptions()[0].update()
          print("Updating subscription 1:", success, message, changes)
          self.assertEqual(success, True)
          print("changes", changes)
          self.assertFalse(changes)
          self.assertEqual(user1.client.publishers()[0].stillUpdating(), False)
          self.assertEqual(user1.client.publishers()[0].subscriptions()[0].stillUpdating(), False)
          print(user1.client.publishers()[0].updatingProblem())
          self.assertEqual(user1.client.allSubscriptionsUpdated(), True)
  
          # Install Font
          # First it's meant to fail because the user hasn't
          # accepted the Terms & Conditions
          self.assertEqual(
              user1.client.publishers()[0]
              .subscriptions()[-1]
              .installFonts([[user1.testFont().uniqueID, user1.testFont().getVersions()[-1].number]]),
              (
                  False,
                  [
                      "#(response.termsOfServiceNotAccepted)",
                      "#(response.termsOfServiceNotAccepted.headline)",
                  ],
              ),
          )
          user1.client.publishers()[0].subscriptions()[-1].set("acceptedTermsOfService", True)
          # Then it's supposed to fail because the server requires the revealted user
          # identity for this subscription
          self.assertEqual(
              user1.client.publishers()[0]
              .subscriptions()[-1]
              .protocol.installableFontsCommand()[1]
              .prefersRevealedUserIdentity,
              True,
          )
          self.assertEqual(
              user1.client.publishers()[0]
              .subscriptions()[-1]
              .installFonts([[user1.testFont().uniqueID, user1.testFont().getVersions()[-1].number]]),
              (
                  False,
                  [
                      "#(response.revealedUserIdentityRequired)",
                      "#(response.revealedUserIdentityRequired.headline)",
                  ],
              ),
          )
          user1.client.publishers()[0].subscriptions()[-1].set("revealIdentity", True)
  
          # Finally supposed to pass
          self.assertEqual(
              user1.client.publishers()[0]
              .subscriptions()[-1]
              .installFonts([[user1.testFont().uniqueID, user1.testFont().getVersions()[-1].number]]),
              (True, None),
          )
          self.assertEqual(user1.client.publishers()[0].amountInstalledFonts(), 1)
          self.assertEqual(user1.client.publishers()[0].subscriptions()[0].amountInstalledFonts(), 1)
  
          # Test for version
          self.assertEqual(
              user1.client.publishers()[0].subscriptions()[0].installedFontVersion(fontID=user1.testFont().uniqueID),
              user1.testFont().getVersions()[-1].number,
          )
          self.assertEqual(
              user1.client.publishers()[0].subscriptions()[0].installedFontVersion(font=user1.testFont()),
              user1.testFont().getVersions()[-1].number,
          )
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          # Simulations
          user1.client.testScenario = "simulateNotOnline"
          success, message, changes = user1.client.publishers()[0].update()
          self.assertEqual(success, False)
          success, message, changes = user1.client.publishers()[0].subscriptions()[0].update()
          self.assertEqual(success, False)
  
          user1.client.testScenario = "simulateImpersonatedCanonicalURL"
          success, message, changes = user1.client.publishers()[0].update()
          self.assertEqual(success, False)
          self.assertEqual(message, "'url' must begin with 'canonicalURL'")
          success, message, changes = user1.client.publishers()[0].subscriptions()[0].update()
          self.assertEqual(success, False)
          self.assertEqual(message, "'url' must begin with 'canonicalURL'")
  
          user1.client.testScenario = "simulateProgrammingError"
          success, message, changes = user1.client.publishers()[0].update()
          self.assertEqual(success, False)
          success, message, changes = user1.client.publishers()[0].subscriptions()[0].update()
          self.assertEqual(success, False)
          user1.client.testScenario = "simulateInsufficientPermissions"
          success, message, changes = user1.client.publishers()[0].update()
          self.assertEqual(success, False)
          success, message, changes = user1.client.publishers()[0].subscriptions()[0].update()
          self.assertEqual(success, False)
          user1.client.testScenario = "simulateCustomError"
          success, message, changes = user1.client.publishers()[0].update()
          self.assertEqual(success, False)
          self.assertEqual(message.getText(), "simulateCustomError")
          success, message, changes = user1.client.publishers()[0].subscriptions()[0].update()
          self.assertEqual(success, False)
          self.assertEqual(message.getText(), "simulateCustomError")
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          # Uninstall font here
          user1.client.testScenario = None
          success, message = user1.client.publishers()[0].subscriptions()[-1].removeFonts([user1.testFont().uniqueID])
          if success is False:
              print("Uninstall font:", message)  # nocoverage
          self.assertEqual(success, True)
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          # Repeat font installation
          user1.client.testScenario = "simulateLoginRequired"
          success, message = (
              user1.client.publishers()[0]
              .subscriptions()[-1]
              .installFonts([[user1.testFont().uniqueID, user1.testFont().getVersions()[-1].number]])
          )
          if success is False:
              print(message)  # nocoverage
          self.assertEqual(success, False)
          self.assertEqual(message, ["#(response.loginRequired)", "#(response.loginRequired.headline)"])
  
          # Repeat font installation
          user1.client.testScenario = "simulateProgrammingError"
          success, message = (
              user1.client.publishers()[0]
              .subscriptions()[-1]
              .installFonts([[user1.testFont().uniqueID, user1.testFont().getVersions()[-1].number]])
          )
          if not success:
              print(message)  # nocoverage
          self.assertEqual(success, False)
  
          user1.client.testScenario = "simulatePermissionError"
          success, message = (
              user1.client.publishers()[0]
              .subscriptions()[-1]
              .installFonts([[user1.testFont().uniqueID, user1.testFont().getVersions()[-1].number]])
          )
          if not success:
              print(message)  # nocoverage
          self.assertEqual(success, False)
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          user1.client.testScenario = "simulateCustomError"
          success, message = (
              user1.client.publishers()[0]
              .subscriptions()[-1]
              .installFonts([[user1.testFont().uniqueID, user1.testFont().getVersions()[-1].number]])
          )
          if not success:
              print(message)  # nocoverage
          self.assertEqual(success, False)
          self.assertEqual(message.getText(), "simulateCustomError")
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          user1.client.testScenario = "simulateInsufficientPermissions"
          publisher = user1.client.publishers()[0]
          subscription = publisher.subscriptions()[-1]
          print(user1.testFont().getVersions())
          success, message = subscription.installFonts(
              [[user1.testFont().uniqueID, user1.testFont().getVersions()[-1].number]]
          )
          if not success:
              print(message)  # nocoverage
          self.assertEqual(success, False)
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          # Repeat font installation
          user1.client.testScenario = "simulateWrongAssetVersion"
          success, message = (
              user1.client.publishers()[0]
              .subscriptions()[-1]
              .installFonts([[user1.testFont().uniqueID, user1.testFont().getVersions()[-1].number]])
          )
          if success is False:
              print(message)  # nocoverage
          self.assertEqual(success, False)
          self.assertEqual(
              message,
              "Font Test_Foundry-Runya-Runya-Regular-otf-StandardSet with version 1.1 not found in assets",
          )
          # Uninstall (because server has recorded successful installation here, as the
          # asset version number is set to a non-matching number, but still a correct
          # formatting as far as the server is concerned)
          # We're using the protocol’s removeFonts() method here and not the client’s
          # one because the actual font file is missing from disk and that causes a
          # whole bag of problems in testing
          success, payload = (
              user1.client.publishers()[0]
              .subscriptions()[-1]
              .protocol.removeFonts(
                  ["Test_Foundry-Runya-Runya-Regular-otf-StandardSet"],
                  updateSubscription=True,
              )
          )
          self.assertEqual(success, True)
  
          # success, message = (
          #     user1.client.publishers()[0]
          #     .subscriptions()[-1]
          #     .removeFonts([user1.testFont().uniqueID])
          # )
          # if not success:
          #     print(message)
          # self.assertEqual(success, True)
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          # Supposed to pass
          user1.client.testScenario = None
          success, message = (
              user1.client.publishers()[0]
              .subscriptions()[-1]
              .installFonts([[user1.testFont().uniqueID, user1.testFont().getVersions()[-1].number]])
          )
          if not success:
              print(message)  # nocoverage
          self.assertEqual(success, True)
          self.assertEqual(user1.client.publishers()[0].amountInstalledFonts(), 1)
          self.assertEqual(user1.client.publishers()[0].subscriptions()[0].amountInstalledFonts(), 1)
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          # Simulate unexpected empty subscription
          user1.client.testScenario = "simulateNoFontsAvailable"
          success, message, changes = user1.client.publishers()[0].subscriptions()[0].update()
          print("Updating subscription 2:", success, message, changes)
          self.assertEqual(success, True)
          self.assertEqual(changes, {"removedFonts": 2, "overallChanges": True})
          self.assertEqual(user1.client.publishers()[0].subscriptions()[0].amountInstalledFonts(), 0)
  
          # Return to normal subscription
          user1.client.testScenario = None
          success, message, changes = user1.client.publishers()[0].subscriptions()[0].update()
          self.assertEqual(success, True)
          self.assertEqual(changes, {"addedFonts": 2, "overallChanges": True})
  
          # Simulated Added Version
          user1.client.testScenario = "simulateAddedFontVersion"
          success, message, changes = user1.client.publishers()[0].subscriptions()[0].update()
          self.assertEqual(success, True)
          self.assertEqual(changes, {"fontsWithAddedVersions": 2, "overallChanges": True})
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          # Return to normal subscription
          user1.client.testScenario = None
          success, message, changes = user1.client.publishers()[0].subscriptions()[0].update()
          self.assertEqual(success, True)
  
          # Install font again
          user1.client.testScenario = None
          self.assertEqual(
              user1.client.publishers()[0]
              .subscriptions()[-1]
              .installFonts([[user1.testFont().uniqueID, user1.testFont().getVersions()[-1].number]]),
              (True, None),
          )
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          self.assertEqual(user1.client.publishers()[0].amountInstalledFonts(), 1)
          self.assertEqual(user1.client.publishers()[0].subscriptions()[0].amountInstalledFonts(), 1)
          self.assertEqual(len(user1.client.publishers()), 1)
          self.assertEqual(len(user1.client.publishers()[0].subscriptions()), 1)
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          # Remove font again
          user1.client.testScenario = None
          self.assertEqual(
              user1.client.publishers()[0].subscriptions()[-1].removeFonts([user1.testFont().uniqueID]),
              (True, None),
          )
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          # linkedAppInstances
          success, instances = user1.client.linkedAppInstances()
          self.assertEqual(success, True)
          self.assertTrue(len(instances) >= 1)
  
          # Revoke app instance
          success, response = user1.client.revokeAppInstance(user1.client.anonymousAppID())
          if not success:
              print(response)
          self.assertEqual(success, True)
  
          # Save test font's uniqueID and version for later
          testFontID = user1.testFont().uniqueID
          testFontVersion = user1.testFont().getVersions()[-1].number
  
          print("\nLine %s" % getframeinfo(currentframe()).lineno)
  
          # Can't access app instances information while revoked
          success, response = user1.client.linkedAppInstances()
          self.assertEqual(
              response,
              [
                  "#(response.appInstanceRevoked)",
                  "#(response.appInstanceRevoked.headline)",
              ],
          )
  
          # Update subscriptions
          success, message, changed = user1.client.publishers()[0].subscriptions()[-1].update()
          self.assertEqual(success, True)
  
          # Reinstall font, fails because no permissions
          user1.client.testScenario = None
          response = user1.client.publishers()[0].subscriptions()[-1].installFonts([[testFontID, testFontVersion]])
          success, message = response
          print(response)
          self.assertEqual(success, False)
          self.assertEqual(
              message,
              [
                  "#(response.insufficientPermission)",
                  "#(response.insufficientPermission.headline)",
              ],
          )
  
          # Reactivate app instance
          success, response = user1.client.reactivateAppInstance(user1.client.anonymousAppID())
          if not success:
              print(response)
          self.assertEqual(success, True)
  
          self.assertEqual(user1.client.downloadSubscriptions(), (True, None))
  
          # Reinstall font
          user1.client.testScenario = None
          success, message = (
              user1.client.publishers()[0]
              .subscriptions()[-1]
              .installFonts([[user1.testFont().uniqueID, user1.testFont().getVersions()[-1].number]])
          )
          if not success:
              print(message)  # nocoverage
          self.assertEqual(success, True)
          self.assertEqual(user1.client.publishers()[0].amountInstalledFonts(), 1)
          self.assertEqual(user1.client.publishers()[0].subscriptions()[0].amountInstalledFonts(), 1)
  
          # This is also supposed to delete the installed protected font
          user1.client.testScenario = "simulateCentralServerNotReachable"
          self.assertEqual(user1.client.unlinkUser()[0], False)
          user1.client.testScenario = "simulateCentralServerProgrammingError"
          self.assertEqual(user1.client.unlinkUser()[0], False)
          user1.client.testScenario = "simulateCentralServerErrorInResponse"
          self.assertEqual(user1.client.unlinkUser()[0], False)
          user1.client.testScenario = "simulateNotOnline"
          self.assertEqual(user1.client.unlinkUser()[0], False)
          self.assertTrue(user1.client.syncProblems())
          user1.client.testScenario = None
          success, message = user1.client.unlinkUser()
          self.assertEqual(success, True)
          self.assertFalse(user1.client.syncProblems())
          self.assertEqual(user1.client.userEmail(), None)
          self.assertEqual(user1.client.user(), "")
  
          self.assertEqual(user1.client.publishers()[0].amountInstalledFonts(), 0)
          self.assertEqual(len(user1.client.publishers()[0].subscriptions()), 1)
  
          # Delete subscription from user-less app, so that it can be re-added
          # during upcoming user linking
          user1.client.publishers()[0].subscriptions()[0].delete()
          self.assertEqual(len(user1.client.publishers()), 0)
  
          user1.client.testScenario = "simulateCentralServerNotReachable"
          self.assertEqual(user1.client.linkUser(*user1.credentials)[0], False)
          user1.client.testScenario = "simulateCentralServerProgrammingError"
          self.assertEqual(user1.client.linkUser(*user1.credentials)[0], False)
          user1.client.testScenario = "simulateCentralServerErrorInResponse"
          self.assertEqual(user1.client.linkUser(*user1.credentials)[0], False)
          user1.client.testScenario = "simulateNotOnline"
          self.assertEqual(user1.client.linkUser(*user1.credentials)[0], False)
          user1.client.testScenario = None
          success, message = user1.client.linkUser(*user1.credentials)
          print("linkUser:", success, message)
          self.assertEqual(success, True)
  
          # Unlink
          self.assertEqual(user1.client.unlinkUser()[0], True)
          # Log in
          self.assertEqual(user1.client.logInUserAccount(*testUser1)[0], True)
  
          self.assertEqual(len(user1.client.publishers()[0].subscriptions()), 1)
          self.assertEqual(user1.client.userEmail(), "test1@type.world")
  
          # Install again
          user1.client.publishers()[0].subscriptions()[-1].set("acceptedTermsOfService", True)
          user1.client.publishers()[0].subscriptions()[-1].set("revealIdentity", True)
          self.assertEqual(
              user1.client.publishers()[0]
              .subscriptions()[-1]
              .installFonts([[user1.testFont().uniqueID, user1.testFont().getVersions()[-1].number]]),
              (True, None),
          )
          self.assertEqual(user1.client.publishers()[0].amountInstalledFonts(), 1)
  
          # Sync subscription
          user1.client.testScenario = "simulateCentralServerNotReachable"
          self.assertEqual(user1.client.syncSubscriptions()[0], False)
          user1.client.testScenario = "simulateCentralServerProgrammingError"
          self.assertEqual(user1.client.syncSubscriptions()[0], False)
          user1.client.testScenario = "simulateCentralServerErrorInResponse"
          self.assertEqual(user1.client.syncSubscriptions()[0], False)
          user1.client.testScenario = "simulateNotOnline"
          self.assertEqual(user1.client.syncSubscriptions()[0], False)
          user1.client.testScenario = None
          self.assertEqual(user1.client.syncSubscriptions()[0], True)
  
          # #
  
          # Scenario 4:
          # Protected subscription, installation on second machine
  
          user2.client.testScenario = "simulateWrongMimeType"
          success, message, publisher, subscription = user2.client.addSubscription(protectedSubscription)
          self.assertEqual(success, False)
          self.assertEqual(
              message,
              "Resource headers returned wrong MIME type: 'text/html'. Expected is '['application/json']'.",
          )
          user2.client.testScenario = "simulateImpersonatedCanonicalURL"
          success, message, publisher, subscription = user2.client.addSubscription(protectedSubscription)
          self.assertEqual(success, False)
          self.assertEqual(message, "'url' must begin with 'canonicalURL'")
  
          user2.client.testScenario = "simulateNotHTTP200"
          success, message, publisher, subscription = user2.client.addSubscription(protectedSubscription)
          self.assertEqual(success, False)
  
          user2.client.testScenario = "simulateProgrammingError"
          success, message, publisher, subscription = user2.client.addSubscription(protectedSubscription)
          self.assertEqual(success, False)
  
          user2.client.testScenario = "simulateInvalidAPIJSONResponse"
          success, message, publisher, subscription = user2.client.addSubscription(protectedSubscription)
          self.assertEqual(success, False)
  
          user2.client.testScenario = "simulateFaultyAPIJSONResponse"
          success, message, publisher, subscription = user2.client.addSubscription(protectedSubscription)
          self.assertEqual(success, False)
  
          user2.client.testScenario = "simulateCentralServerNotReachable"
          success, message, publisher, subscription = user2.client.addSubscription(protectedSubscription)
          self.assertEqual(success, False)
  
          user2.client.testScenario = "simulateCentralServerProgrammingError"
          success, message, publisher, subscription = user2.client.addSubscription(protectedSubscription)
          self.assertEqual(success, False)
          user2.client.testScenario = "simulateCentralServerErrorInResponse"
          success, message, publisher, subscription = user2.client.addSubscription(protectedSubscription)
          self.assertEqual(success, False)
          user2.client.testScenario = "simulateNotOnline"
          success, message, publisher, subscription = user2.client.addSubscription(protectedSubscription)
          self.assertEqual(success, False)
  
          user2.client.publishers()[0].delete()
          user2.client.testScenario = "simulateNoCommercialAppsAllowed"
          success, message, publisher, subscription = user2.client.addSubscription(protectedSubscription)
          print(message)
          self.assertEqual(success, False)
          self.assertEqual(
              message,
              [
                  "#(response.commercialAppNotAllowed)",
                  "#(response.commercialAppNotAllowed.headline)",
              ],
          )
  
          user2.client.testScenario = None
          success, message, publisher, subscription = user2.client.addSubscription(protectedSubscription)
          self.assertEqual(success, True)
  
          # Two versions available
          self.assertEqual(
              len(
                  user2.client.publishers()[0]
                  .subscriptions()[-1]
                  .installFonts([[user2.testFont().uniqueID, user2.testFont().getVersions()]])
              ),
              2,
          )
  
          # Supposed to reject because seats are limited to 1
          user2.client.publishers()[0].subscriptions()[-1].set("acceptedTermsOfService", True)
          user2.client.publishers()[0].subscriptions()[-1].set("revealIdentity", True)
          self.assertEqual(
              user2.client.publishers()[0]
              .subscriptions()[-1]
              .installFonts([[user2.testFont().uniqueID, user2.testFont().getVersions()[-1].number]]),
              (
                  False,
                  [
                      "#(response.seatAllowanceReached)",
                      "#(response.seatAllowanceReached.headline)",
                  ],
              ),
          )
  
          # Uninstall font for user1
          user1.client.testScenario = "simulatePermissionError"
          success, message = user1.client.publishers()[0].subscriptions()[-1].removeFonts([user1.testFont().uniqueID])
          self.assertEqual(success, False)
  
          user1.client.testScenario = "simulateTemporarilyUnavailable"
          success, message = user1.client.publishers()[0].subscriptions()[-1].removeFonts([user1.testFont().uniqueID])
          self.assertEqual(success, False)
  
          user1.client.testScenario = "simulateCustomError"
          success, message = user1.client.publishers()[0].subscriptions()[-1].removeFonts([user1.testFont().uniqueID])
          print(message)  # nocoverage
          self.assertEqual(success, False)
          self.assertEqual(message.getText(), "simulateCustomError")
  
          user1.client.testScenario = "simulateNoPath"
          success, message = user1.client.publishers()[0].subscriptions()[-1].removeFonts([user1.testFont().uniqueID])
          print(message)  # nocoverage
          self.assertEqual(success, False)
  
          user1.client.testScenario = "simulateProgrammingError"
          success, message = user1.client.publishers()[0].subscriptions()[-1].removeFonts([user1.testFont().uniqueID])
          self.assertEqual(success, False)
  
          user1.client.testScenario = "simulateInsufficientPermissions"
          success, message = user1.client.publishers()[0].subscriptions()[-1].removeFonts([user1.testFont().uniqueID])
          self.assertEqual(success, False)
  
          # Supposed to succeed
          user1.client.testScenario = None
          success, message = user1.client.publishers()[0].subscriptions()[-1].removeFonts([user1.testFont().uniqueID])
          self.assertEqual(success, True)
  
          # Uninstall font for user2, must fail because deleting same font file
          # (doesn't make sense in normal setup)
          result = user2.client.publishers()[0].subscriptions()[-1].removeFonts([user2.testFont().uniqueID])
          print(result)
          self.assertEqual(result, (False, "Font path couldn’t be determined (preflight)"))
  
          # Try again for user2
          self.assertEqual(
              user2.client.publishers()[0]
              .subscriptions()[-1]
              .installFonts([[user2.testFont().uniqueID, user2.testFont().getVersions()[-1].number]]),
              (True, None),
          )
  
          # Uninstall font on for user2
          result = user2.client.publishers()[0].subscriptions()[-1].removeFonts([user2.testFont().uniqueID])
          print(result)
          self.assertEqual(result, (True, None))
  
          # Install older version on second client
          self.assertEqual(
              user2.client.publishers()[0]
              .subscriptions()[-1]
              .installFonts([[user2.testFont().uniqueID, user2.testFont().getVersions()[0].number]]),
              (True, None),
          )
  
          # Check amount
          self.assertEqual(user2.client.publishers()[0].amountInstalledFonts(), 1)
  
          # One font must be outdated
          self.assertEqual(user2.client.amountOutdatedFonts(), 1)
          self.assertEqual(user2.client.publishers()[0].amountOutdatedFonts(), 1)
          self.assertEqual(user2.client.publishers()[0].subscriptions()[0].amountOutdatedFonts(), 1)
  
          # Uninstall font for user2
          result = user2.client.publishers()[0].subscriptions()[-1].removeFonts([user2.testFont().uniqueID])
          print(result)
          self.assertEqual(result, (True, None))
  
          # Family By ID
          family = (
              user2.client.publishers()[0]
              .subscriptions()[-1]
              .protocol.installableFontsCommand()[1]
              .foundries[0]
              .families[0]
          )
          self.assertEqual(
              family,
              user2.client.publishers()[0].subscriptions()[-1].familyByID(family.uniqueID),
          )
  
          # Font By ID
          font = family.fonts[0]
          self.assertEqual(
              font,
              user2.client.publishers()[0].subscriptions()[-1].fontByID(font.uniqueID),
          )
  
          # Clear
          print("user2.clearSubscriptions()")
          user2.clearSubscriptions()
          success, message = user2.client.downloadSubscriptions()
          self.assertTrue(success)
          self.assertEqual(len(user2.client.publishers()), 0)
  
          print("STATUS: -15")
  
          # Uninstallation of fonts when they aren't present in the subscription anymore
          user0.client.addSubscription(freeSubscription)
          user0.client.publishers()[0].subscriptions()[-1].set("acceptedTermsOfService", True)
          font = (
              user0.client.publishers()[0]
              .subscriptions()[-1]
              .protocol.installableFontsCommand()[1]
              .foundries[0]
              .families[0]
              .fonts[-1]
          )
          self.assertEqual(
              user0.client.publishers()[0]
              .subscriptions()[-1]
              .installFonts([[font.uniqueID, font.getVersions()[-1].number]]),
              (True, None),
          )
          self.assertTrue(
              os.path.exists(
                  os.path.join(
                      user0.client.publishers()[0].folder(),
                      user0.client.publishers()[0].subscriptions()[-1].uniqueID()
                      + "-"
                      + font.filename(font.getVersions()[-1].number),
                  )
              )
          )
  
          user0.client.testScenario = "simulateFontNoLongerIncluded"
          self.assertEqual(
              user0.client.publishers()[0].subscriptions()[-1].update(),
              (True, None, {"removedFonts": 1, "overallChanges": True}),
          )
          self.assertFalse(
              os.path.exists(
                  os.path.join(
                      user0.client.publishers()[0].folder(),
                      font.filename(font.getVersions()[-1].number),
                  )
              )
          )
  
          user0.client.testScenario = None
          self.assertEqual(
              user0.client.publishers()[0].subscriptions()[-1].update(),
              (True, None, {"addedFonts": 1, "overallChanges": True}),
          )
          self.assertFalse(
              os.path.exists(
                  os.path.join(
                      user0.client.publishers()[0].folder(),
                      font.filename(font.getVersions()[-1].number),
                  )
              )
          )
  
          user0.client.testScenario = "simulateFontsAdded"
          self.assertEqual(
              user0.client.publishers()[0].subscriptions()[-1].update(),
              (True, None, {"addedFonts": 2, "overallChanges": True}),
          )
          self.assertFalse(
              os.path.exists(
                  os.path.join(
                      user0.client.publishers()[0].folder(),
                      font.filename(font.getVersions()[-1].number),
                  )
              )
          )
  
          user0.client.testScenario = None
          self.assertEqual(
              user0.client.publishers()[0].subscriptions()[-1].update(),
              (True, None, {"overallChanges": True, "removedFonts": 2}),
          )
          self.assertFalse(
              os.path.exists(
                  os.path.join(
                      user0.client.publishers()[0].folder(),
                      font.filename(font.getVersions()[-1].number),
                  )
              )
          )
  
          user0.client.testScenario = "simulateVersionsAdded"
          self.assertEqual(
              user0.client.publishers()[0].subscriptions()[-1].update(),
              (True, None, {"fontsWithAddedVersions": 4, "overallChanges": True}),
          )
          self.assertFalse(
              os.path.exists(
                  os.path.join(
                      user0.client.publishers()[0].folder(),
                      font.filename(font.getVersions()[-1].number),
                  )
              )
          )
  
          # #
  
          print("STATUS: -14")
  
          # Invitations
          # Invite to client without linked user account
          success, message, publisher, subscription = user0.client.addSubscription(freeSubscription)
          self.assertEqual(success, True)
          result = user0.client.publishers()[0].subscriptions()[-1].inviteUser("test12345@type.world")
          self.assertEqual(result, (False, "No source user linked."))
  
          # Invite unknown user
          result = user1.client.publishers()[0].subscriptions()[-1].inviteUser("test12345@type.world")
          self.assertEqual(
              result,
              (
                  False,
                  [
                      "#(response.unknownTargetEmail)",
                      "#(response.unknownTargetEmail.headline)",
                  ],
              ),
          )
  
          # Invite same user
          user1.client.testScenario = None
          self.assertEqual(user1.client.userEmail(), "test1@type.world")
          # self.assertEqual(user1.client.user(), '736b524a-cf24-11e9-9f62-901b0ecbcc7a')
          result = user1.client.publishers()[0].subscriptions()[-1].inviteUser("test1@type.world")
          self.assertEqual(
              result,
              (
                  False,
                  [
                      "#(response.invitationsRequireProAccount)",
                      "#(response.invitationsRequireProAccount.headline)",
                  ],
              ),
          )
  
          # Invite same user
          user1.client.testScenario = "simulateTestUser1IsPro"
          self.assertEqual(user1.client.userEmail(), "test1@type.world")
          # self.assertEqual(user1.client.user(), '736b524a-cf24-11e9-9f62-901b0ecbcc7a')
          result = user1.client.publishers()[0].subscriptions()[-1].inviteUser("test1@type.world")
          self.assertEqual(
              result,
              (
                  False,
                  [
                      "#(response.sourceAndTargetIdentical)",
                      "#(response.sourceAndTargetIdentical.headline)",
                  ],
              ),
          )
  
          print("STATUS: -13")
  
          # Invite real user
          user1.client.testScenario = "simulateCentralServerNotReachable"
          result = user1.client.publishers()[0].subscriptions()[-1].inviteUser("test2@type.world")
          self.assertEqual(result[0], False)
          user1.client.testScenario = "simulateCentralServerProgrammingError"
          result = user1.client.publishers()[0].subscriptions()[-1].inviteUser("test2@type.world")
          self.assertEqual(result[0], False)
          user1.client.testScenario = "simulateCentralServerErrorInResponse"
          result = user1.client.publishers()[0].subscriptions()[-1].inviteUser("test2@type.world")
          self.assertEqual(result[0], False)
          user1.client.testScenario = "simulateNotOnline"
          result = user1.client.publishers()[0].subscriptions()[-1].inviteUser("test2@type.world")
          self.assertEqual(result[0], False)
  
          # Update user2
          self.assertEqual(user2.client.downloadSubscriptions(), (True, None))
          self.assertEqual(len(user2.client.pendingInvitations()), 0)
  
          print("STATUS: -12")
  
          # Supposed to pass invitation
          user1.client.testScenario = "simulateTestUser1IsPro"
          result = user1.client.publishers()[0].subscriptions()[-1].inviteUser("test2@type.world")
          print(result)
          self.assertEqual(result[0], True)
  
          print("STATUS: -11.8")
  
          self.assertEqual(len(user2.client.publishers()), 0)
  
          print("STATUS: -11.7")
  
          print("pendingInvitations", user2.client.get("pendingInvitations"))
          self.assertEqual(user2.client.downloadSubscriptions(), (True, None))
  
          print("STATUS: -11.6")
  
          print("pendingInvitations", user2.client.get("pendingInvitations"))
          self.assertEqual(len(user2.client.pendingInvitations()), 1)
  
          print("STATUS: -11.5")
  
          # Decline (exists only here in test script)
          user2.clearInvitations()
          # Invite again
          result = user1.client.publishers()[0].subscriptions()[-1].inviteUser("test2@type.world")
          self.assertEqual(result, (True, None))
  
          print("STATUS: -11")
  
          # Accept invitation
          self.assertEqual(user2.client.downloadSubscriptions(), (True, None))
          self.assertEqual(len(user2.client.pendingInvitations()), 1)
  
          user2.client.testScenario = "simulateCentralServerNotReachable"
          success, message = user2.client.pendingInvitations()[0].accept()
          self.assertEqual(success, False)
          user2.client.testScenario = "simulateCentralServerProgrammingError"
          success, message = user2.client.pendingInvitations()[0].accept()
          self.assertEqual(success, False)
          user2.client.testScenario = "simulateCentralServerErrorInResponse"
          success, message = user2.client.pendingInvitations()[0].accept()
          self.assertEqual(success, False)
          user2.client.testScenario = "simulateNotOnline"
          success, message = user2.client.pendingInvitations()[0].accept()
          self.assertEqual(success, False)
          user2.client.testScenario = None
          success, message = user2.client.pendingInvitations()[0].accept()
          print(message)  # nocoverage
          self.assertEqual(success, True)
  
          print("STATUS: -10")
  
          user2.client.downloadSubscriptions()
          self.assertEqual(len(user2.client.pendingInvitations()), 0)
          self.assertEqual(len(user2.client.publishers()), 1)
  
          # Invite yet another user
          user2.client.testScenario = None
          self.assertEqual(len(user3.client.pendingInvitations()), 0)
          self.assertEqual(len(user3.client.publishers()), 0)
          success, message = user2.client.publishers()[0].subscriptions()[-1].inviteUser("test3@type.world")
          self.assertEqual(success, False)
  
          # Invite yet another user
          user2.client.testScenario = "simulateTestUser2IsPro"
          self.assertEqual(len(user3.client.pendingInvitations()), 0)
          self.assertEqual(len(user3.client.publishers()), 0)
          result = user2.client.publishers()[0].subscriptions()[-1].inviteUser("test3@type.world")
          self.assertEqual(result, (True, None))
          success, message = user2.client.downloadSubscriptions()
          self.assertEqual(success, True)
          self.assertEqual(len(user2.client.sentInvitations()), 1)
  
          print("STATUS: -9")
  
          # Decline invitation
          user3.client.downloadSubscriptions()
          self.assertEqual(len(user3.client.pendingInvitations()), 1)
  
          user3.client.testScenario = "simulateCentralServerNotReachable"
          success, message = user3.client.pendingInvitations()[0].decline()
          self.assertEqual(success, False)
          user3.client.testScenario = "simulateCentralServerProgrammingError"
          success, message = user3.client.pendingInvitations()[0].decline()
          self.assertEqual(success, False)
          user3.client.testScenario = "simulateCentralServerErrorInResponse"
          success, message = user3.client.pendingInvitations()[0].decline()
          self.assertEqual(success, False)
          user3.client.testScenario = "simulateNotOnline"
          success, message = user3.client.pendingInvitations()[0].decline()
          self.assertEqual(success, False)
          user3.client.testScenario = None
          success, message = user3.client.pendingInvitations()[0].decline()
          self.assertEqual(success, True)
  
          self.assertEqual(len(user3.client.pendingInvitations()), 0)
          user2.client.downloadSubscriptions()
          self.assertEqual(len(user2.client.sentInvitations()), 0)
  
          # Invite again
          self.assertEqual(len(user3.client.pendingInvitations()), 0)
          self.assertEqual(len(user3.client.publishers()), 0)
          result = user2.client.publishers()[0].subscriptions()[-1].inviteUser("test3@type.world")
          self.assertEqual(result, (True, None))
          user2.client.downloadSubscriptions()
          self.assertEqual(len(user2.client.sentInvitations()), 1)
  
          print("STATUS: -8")
  
          # Accept invitation
          user3.client.downloadSubscriptions()
          self.assertEqual(len(user3.client.pendingInvitations()), 1)
          user3.client.pendingInvitations()[0].accept()
          self.assertEqual(len(user3.client.publishers()), 1)
  
          print("STATUS: -7")
  
          # Revoke user
          user2.client.testScenario = "simulateCentralServerNotReachable"
          success, message = user2.client.publishers()[0].subscriptions()[-1].revokeUser("test3@type.world")
          self.assertEqual(success, False)
          user2.client.testScenario = "simulateCentralServerProgrammingError"
          success, message = user2.client.publishers()[0].subscriptions()[-1].revokeUser("test3@type.world")
          self.assertEqual(success, False)
          user2.client.testScenario = "simulateCentralServerErrorInResponse"
          success, message = user2.client.publishers()[0].subscriptions()[-1].revokeUser("test3@type.world")
          self.assertEqual(success, False)
          user2.client.testScenario = "simulateNotOnline"
          success, message = user2.client.publishers()[0].subscriptions()[-1].revokeUser("test3@type.world")
          self.assertEqual(success, False)
  
          # Supposed to pass
          sentInvitations = len(user2.client.sentInvitations())
          user2.client.testScenario = None
          success, message = user2.client.publishers()[0].subscriptions()[-1].revokeUser("test3@type.world")
          if not success:
              print(message)  # nocoverage
          self.assertEqual(success, True)
          user2.client.downloadSubscriptions()
          self.assertEqual(sentInvitations - 1, len(user2.client.sentInvitations()))
  
          print("STATUS: -6")
  
          self.assertEqual(result, (True, None))
          user3.client.downloadSubscriptions()
          self.assertEqual(len(user3.client.publishers()), 0)
  
          print("STATUS: -5")
  
          # Invite again
          user2.client.testScenario = "simulateTestUser2IsPro"
          self.assertEqual(len(user3.client.pendingInvitations()), 0)
          self.assertEqual(len(user3.client.publishers()), 0)
          result = user2.client.publishers()[0].subscriptions()[-1].inviteUser("test3@type.world")
          self.assertEqual(result, (True, None))
          user2.client.downloadSubscriptions()
          self.assertEqual(len(user2.client.sentInvitations()), 1)
  
          print("STATUS: -4")
  
          # Accept invitation
          user3.client.downloadSubscriptions()
          self.assertEqual(len(user3.client.pendingInvitations()), 1)
          self.assertEqual(len(user3.client.publishers()), 0)
          user3.client.pendingInvitations()[0].accept()
          self.assertEqual(len(user3.client.publishers()), 1)
  
          print("STATUS: -3")
  
          # Invitation accepted
          success = user3.client.publishers()[-1].subscriptions()[-1].invitationAccepted()
          self.assertEqual(success, True)
  
          print("STATUS: -2")
  
          subscription = user0.client.publishers()[0].subscriptions()[0]
          logoURL = subscription.protocol.endpointCommand()[1].logoURL
          files = user0.client.publishers()[0].subscriptions()[0].files()
          self.assertTrue(logoURL in files)
          files = user0.client.publishers()[0].files()
          self.assertTrue(logoURL in files)
          files = user0.client.files()
          self.assertTrue(logoURL in files)
  
          # # Get publisher's logo, first time
          # self.assertTrue(
          #     user0.client.publishers()[0]
          #     .subscriptions()[0]
          #     .protocol.endpointCommand()[1]
          #     .logoURL
          # )
          # success, logo, mimeType = user0.client.publishers()[0].resourceByURL(
          #     user0.client.publishers()[0]
          #     .subscriptions()[0]
          #     .protocol.endpointCommand()[1]
          #     .logoURL
          # )
          # self.assertEqual(success, True)
          # self.assertTrue(logo.startswith('<?xml version="1.0" encoding="utf-8"?>'))
          # self.assertEqual(mimeType, "image/svg+xml")
  
          # # Get publisher's logo, second time (from cache in preferences)
          # self.assertTrue(
          #     user0.client.publishers()[0]
          #     .subscriptions()[0]
          #     .protocol.endpointCommand()[1]
          #     .logoURL
          # )
          # success, logo, mimeType = user0.client.publishers()[0].resourceByURL(
          #     user0.client.publishers()[0]
          #     .subscriptions()[0]
          #     .protocol.endpointCommand()[1]
          #     .logoURL
          # )
          # self.assertEqual(success, True)
          # self.assertTrue(logo.startswith('<?xml version="1.0" encoding="utf-8"?>'))
          # self.assertEqual(mimeType, "image/svg+xml")
  
          # logoURL = (
          #     user0.client.publishers()[0]
          #     .subscriptions()[0]
          #     .protocol.installableFontsCommand()[1]
          #     .foundries[0]
          #     .styling["light"]["logoURL"]
          # )
  
          # # Get foundry's logo, first time
          # self.assertTrue(logoURL)
          # success, logo, mimeType = (
          #     user0.client.publishers()[0].subscriptions()[0].resourceByURL(logoURL)
          # )
          # self.assertEqual(success, True)
          # self.assertTrue(logo.startswith('<?xml version="1.0" encoding="utf-8"?>'))
  
          # # Get foundry's logo, second time (from cache in preferences)
          # self.assertTrue(logoURL)
          # success, logo, mimeType = (
          #     user0.client.publishers()[0].subscriptions()[0].resourceByURL(logoURL)
          # )
          # self.assertEqual(success, True)
          # self.assertTrue(logo.startswith('<?xml version="1.0" encoding="utf-8"?>'))
  
          # # Get foundry's logo, first time
          # self.assertTrue(logoURL)
          # success, logo, mimeType = (
          #     user0.client.publishers()[0]
          #     .subscriptions()[0]
          #     .resourceByURL(logoURL, binary=True)
          # )
          # self.assertEqual(success, True)
          # self.assertTrue(logo.startswith("PD94b"))
  
          # # Get foundry's logo, second time (from cache in preferences)
          # self.assertTrue(logoURL)
          # success, logo, mimeType = (
          #     user0.client.publishers()[0]
          #     .subscriptions()[0]
          #     .resourceByURL(logoURL, binary=True)
          # )
          # self.assertEqual(success, True)
          # self.assertTrue(logo.startswith("PD94b"))
  
          # print("STATUS: -1")
  
          #####
  
          # Expiring font
  
          # Install font
          user1.client.testScenario = None
          success, response = (
              user1.client.publishers()[0]
              .subscriptions()[-1]
              .installFonts(
                  [
                      [
                          user1.expiringTestFont().uniqueID,
                          user1.expiringTestFont().getVersions()[-1].number,
                      ]
                  ]
              )
          )
          if not success:
              print(response)
          self.assertEqual(success, True)
  
          self.assertEqual(len(user1.client.expiringInstalledFonts()), 1)
  
          # Traceback Test
          user1.client.tracebackTest()
          user1.client.tracebackTest2()
  
          # Delete subscription from first user. Subsequent invitation must
          # then be taken down as well.
          user1.client.publishers()[0].delete()
          user2.client.downloadSubscriptions()
          user3.client.downloadSubscriptions()
          self.assertEqual(len(user2.client.pendingInvitations()), 0)
          self.assertEqual(len(user3.client.pendingInvitations()), 0)
  
          print("test_normalSubscription() finished...")
  
      # def test_APIValidator(self):
  
      # 	print('test_APIValidator() started...')
  
      # 	# Announce subscription update
      # 	parameters = {"command": "validateAPIEndpoint",
      # 				"subscriptionURL": protectedSubscription,
      # 				}
  
      # 	success, response, responseObject = typeworld.client.request(
      #   MOTHERSHIP,
      # parameters)
      # 	self.assertEqual(success, True)
  
      # 	response = json.loads(response)
      # 	print(response)
  
      # 	self.assertEqual(response['response'], 'success')
  
      # 	print('test_APIValidator() finished...')
  
      def test_UserAccounts(self):
  
          print("test_UserAccounts() started...")
  
          print(user0, user1)
  
          # Create user account
  
          success, message = user0.client.createUserAccount("Test", "test0@type.world", "", "")
          self.assertEqual(message, "#(RequiredFieldEmpty)")
  
          success, message = user0.client.createUserAccount("Test", "test0@type.world", "abc", "def")
          self.assertEqual(message, "#(PasswordsDontMatch)")
  
          user0.client.testScenario = "simulateCentralServerNotReachable"
          success, message = user0.client.createUserAccount("Test", "test0@type.world", "abc", "abc")
          self.assertEqual(success, False)
          self.assertTrue("Failed to establish a new connection" in message)
  
          user0.client.testScenario = "simulateCentralServerProgrammingError"
          success, message = user0.client.createUserAccount("Test", "test0@type.world", "abc", "abc")
          self.assertEqual(success, False)
          print(message)
          self.assertTrue("HTTP Error 500" in message)
  
          user0.client.testScenario = "simulateCentralServerErrorInResponse"
          success, message = user0.client.createUserAccount("Test", "test0@type.world", "abc", "abc")
          self.assertEqual(success, False)
          self.assertEqual(
              message,
              [
                  "#(response.simulateCentralServerErrorInResponse)",
                  "#(response.simulateCentralServerErrorInResponse.headline)",
              ],
          )
  
          user0.client.testScenario = "simulateNotOnline"
          success, message = user0.client.createUserAccount("Test", "test0@type.world", "abc", "abc")
          self.assertEqual(success, False)
          self.assertEqual(message, ["#(response.notOnline)", "#(response.notOnline.headline)"])
  
          # Delete User Account
  
          user0.client.testScenario = None
          success, message = user0.client.deleteUserAccount("test0@type.world", "")
          self.assertEqual(message, "#(RequiredFieldEmpty)")
  
          user0.client.testScenario = "simulateCentralServerNotReachable"
          success, message = user0.client.deleteUserAccount("test0@type.world", "abc")
          self.assertEqual(success, False)
          user0.client.testScenario = "simulateCentralServerProgrammingError"
          success, message = user0.client.deleteUserAccount("test0@type.world", "abc")
          self.assertEqual(success, False)
          user0.client.testScenario = "simulateCentralServerErrorInResponse"
          success, message = user0.client.deleteUserAccount("test0@type.world", "abc")
          self.assertEqual(success, False)
          user0.client.testScenario = "simulateNotOnline"
          success, message = user0.client.deleteUserAccount("test0@type.world", "abc")
          self.assertEqual(success, False)
          self.assertEqual(message, ["#(response.notOnline)", "#(response.notOnline.headline)"])
  
          # Log In User Account
  
          success, message = user0.client.logInUserAccount("test0@type.world", "")
          self.assertEqual(message, "#(RequiredFieldEmpty)")
  
          user0.client.testScenario = "simulateCentralServerNotReachable"
          success, message = user0.client.logInUserAccount(*testUser1)
          self.assertEqual(success, False)
          self.assertTrue("Failed to establish a new connection" in message)
  
          user0.client.testScenario = "simulateCentralServerProgrammingError"
          success, message = user0.client.logInUserAccount(*testUser1)
          print(message)
          self.assertEqual(success, False)
          self.assertTrue("HTTP Error 500" in message)
  
          user0.client.testScenario = "simulateCentralServerErrorInResponse"
          success, message = user0.client.logInUserAccount(*testUser1)
          self.assertEqual(success, False)
          self.assertEqual(
              message,
              [
                  "#(response.simulateCentralServerErrorInResponse)",
                  "#(response.simulateCentralServerErrorInResponse.headline)",
              ],
          )
          user0.client.testScenario = "simulateNotOnline"
          success, message = user0.client.logInUserAccount(*testUser1)
          self.assertEqual(success, False)
          self.assertEqual(
              message,
              [
                  "#(response.notOnline)",
                  "#(response.notOnline.headline)",
              ],
          )
  
          user0.client.testScenario = None
          success, message = user0.client.logInUserAccount(*testUser1)
          print(success, message)
          self.assertEqual(success, False)
          self.assertEqual(
              message,
              [
                  "#(response.linkingMoreAppInstancesRequiresProUserAccount)",
                  "#(response.linkingMoreAppInstancesRequiresProUserAccount.headline)",
              ],
          )
  
          user0.client.testScenario = "simulateTestUser1IsPro"
          success, message = user0.client.logInUserAccount(*testUser1)
          print(message)
          self.assertEqual(success, True)
          success, message = user0.client.unlinkUser()
          self.assertEqual(success, True)
  
          print("test_UserAccounts() finished...")
  
      def run(self, result=None):
          self.currentResult = result  # remember result for use in tearDown
          unittest.TestCase.run(self, result)  # call superclass run method
  
      # from https://stackoverflow.com/questions/4414234/getting-pythons-
      # unittest-results-in-a-teardown-method
      def tearDown(self):
          result = self.defaultTestResult()  # these 2 methods have no side effects
          self._feedErrorsToResult(result, self._outcome.errors)
          errors.extend(result.errors)
          failures.extend(result.failures)
  
  
  def setUp():
  
      print("setUp() started...")
  
      global user0, user1, user2, user3, user4, tempFolder
      tempFolder = tempfile.mkdtemp()
      user0 = User()
      user1 = User(testUser1)
      user2 = User(testUser2)
      user3 = User(testUser3)
      user4 = None
  
      print("setUp() finished...")
  
  
  def tearDown():
  
      print("tearDown() started...")
  
      global user0, user1, user2, user3, user4, tempFolder
      user0.takeDown()
      user0.client.quit()
      user1.takeDown()
      user1.client.quit()
      user2.takeDown()
      user2.client.quit()
      user3.takeDown()
      user3.client.quit()
  
      # Local
      if "TRAVIS" not in os.environ:
          os.rmdir(tempFolder)  # nocoverage
  
      print("tearDown() finished...")
  
  
  if __name__ == "__main__":
  
      if ONLINE:
          setUp()
  
      result = unittest.main(argv=["first-arg-is-ignored"], exit=False, failfast=True)
  
      if ONLINE:
          tearDown()
  
      if errors:
          raise ValueError()  # nocoverage
      if failures:
          raise ValueError()  # nocoverage
  