typeworld / typeworld
1
# -*- coding: utf-8 -*-
2
# Usage: python test.py [TestTypeWorld.test_name] [mothershipURL]
3

4 3
import sys
5 3
import os
6 3
import copy
7 3
import time
8 3
import urllib.request
9 3
import urllib.parse
10 3
import ssl
11 3
import certifi
12 3
import json
13 3
import unittest
14 3
import tempfile
15

16
# Use local code for local testing, and rely on system-installed module for CI-testing
17 3
CI = os.getenv("CI", "false").lower() != "false"
18 3
if not CI:
19 0
    path = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
20 0
    if path not in sys.path:
21 0
        sys.path.append(path)
22

23 3
import typeworld.client  # noqa: E402
24

25 3
from typeworld.client import (  # noqa: E402
26
    APIClient,
27
    JSON,
28
    AppKitNSUserDefaults,
29
)
30

31 3
if CI:
32 3
    SECRETKEY = os.getenv("REVOKEAPPINSTANCEAUTHKEY")
33
else:
34 0
    import keyring
35

36 0
    SECRETKEY = keyring.get_password("http://127.0.0.1:8080/api", "revokeAppInstance")
37

38 3
assert SECRETKEY
39

40

41
# Data Types
42 3
from typeworld.api import (  # noqa: E402
43
    HexColorDataType,
44
    FontEncodingDataType,
45
    EmailDataType,
46
    BooleanDataType,
47
    WebURLDataType,
48
    IntegerDataType,
49
    FloatDataType,
50
    DateDataType,
51
    VersionDataType,
52
    MultiLanguageText,
53
    MultiLanguageLongText,
54
)
55

56
# Classes
57 3
from typeworld.api import (  # noqa: E402
58
    InstallFontAsset,
59
    UninstallFontAsset,
60
    FontListProxy,
61
    RootResponse,
62
    EndpointResponse,
63
    Designer,
64
    LicenseDefinition,
65
    Version,
66
    LicenseUsage,
67
    Font,
68
    Family,
69
    Foundry,
70
    InstallableFontsResponse,
71
    InstallFontsResponse,
72
    UninstallFontsResponse,
73
    FontPackage,
74
)
75

76 3
from typeworld.api import (  # noqa: E402
77
    LanguageSupportDataType,
78
    OpenTypeFeatureDataType,
79
    OpenSourceLicenseIdentifierDataType,
80
    SupportedAPICommandsDataType,
81
    FontPurposeDataType,
82
    FontMimeType,
83
    FontStatusDataType,
84
    InstallableFontsResponseType,
85
    InstallFontAssetResponseType,
86
    InstallFontResponseType,
87
    UninstallFontAssedResponseType,
88
    UninstallFontResponseType,
89
)
90

91
# Constants
92 3
from typeworld.api import COMMANDS, MAC, PUBLISHERTYPES  # noqa: E402
93

94
# Methods
95 3
from typeworld.api import makeSemVer  # noqa: E402
96

97 3
from inspect import currentframe, getframeinfo  # noqa: E402
98

99

100 3
ONLINE = True
101

102 3
print("Started...")
103

104 3
sslcontext = ssl.create_default_context(cafile=certifi.where())
105

106

107
# Inspection/Interactive shell
108
# import code; code.interact(local=locals())
109

110

111 3
freeSubscription = (
112
    "typeworld://json+https//typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
113
)
114 3
flatFreeSubscription = (
115
    "typeworld://json+https//typeworldserver.com/flatapi/q8JZfYn9olyUvcCOiqHq/"
116
)
117 3
protectedSubscription = (
118
    "typeworld://json+https//s9lWvayTEOaB9eIIMA67:OxObIWDJjW95SkeL3BNr:"
119
    "qncMnRXZLvHfLLwteTsX@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
120
)
121 3
protectedSubscriptionWithoutAccessToken = (
122
    "typeworld://json+https//s9lWvayTEOaB9eIIMA67:OxObIWDJjW95SkeL3BNr@"
123
    "typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
124
)
125 3
freeNamedSubscription = (
126
    "typeworld://json+https//s9lWvayTEOaB9eIIMA67@"
127
    "typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
128
)
129 3
testUser1 = ("test1@type.world", "12345678")
130 3
testUser2 = ("test2@type.world", "01234567")
131 3
testUser3 = ("test3@type.world", "23456789")
132

133

134
# Travis CI
135 3
if "TRAVIS" in os.environ:
136 3
    MOTHERSHIP = "https://api.type.world/v1"
137

138
# Local testing
139
else:
140
    MOTHERSHIP = "https://api.type.world/v1"  # nocoverage (for local testing only)
141

142
    # Testing specific version
143
    for arg in sys.argv:  # nocoverage (for local testing only)
144
        if arg.startswith("http"):  # nocoverage (for local testing only)
145
            MOTHERSHIP = arg  # nocoverage (for local testing only)
146
            if not MOTHERSHIP.endswith("/v1"):  # nocoverage (for local testing only)
147
                MOTHERSHIP += "/v1"  # nocoverage (for local testing only)
148

149

150 3
print("Testing on %s" % MOTHERSHIP)
151

152
global errors, failures
153 3
errors = []
154 3
failures = []
155

156 3
print("setting up objects started...")
157

158

159
# EndpointResponse
160 3
root = EndpointResponse()
161 3
root.name.en = "Font Publisher"
162 3
root.canonicalURL = "http://fontpublisher.com/api/"
163 3
root.adminEmail = "admin@fontpublisher.com"
164 3
root.supportedCommands = [
165
    x["keyword"] for x in COMMANDS
166
]  # this API supports all commands
167 3
root.publisherTypes = PUBLISHERTYPES
168 3
root.backgroundColor = "AABBCC"
169 3
root.licenseIdentifier = "CC-BY-NC-ND-4.0"
170 3
root.logoURL = (
171
    "https://typeworldserver.com/?page=outputDataBaseFile&"
172
    "className=TWFS_Foundry&ID=8&field=logo"
173
)
174 3
root.privacyPolicyURL = "https://type.world/legal/privacy.html"
175 3
root.termsOfServiceURL = "https://type.world/legal/terms.html"
176 3
root.public = False
177 3
root.version = "0.1.7-alpha"
178 3
root.websiteURL = "https://typeworldserver.com"
179

180
# InstallableFontsResponse
181 3
designer = Designer()
182 3
designer.description.en = "Yanone is a type designer based in Germany."
183 3
designer.keyword = "yanone"
184 3
designer.name.en = "Yanone"
185 3
designer.websiteURL = "https://yanone.de"
186

187 3
designer2 = Designer()
188 3
designer2.description.en = "Yanone is a type designer based in Germany."
189 3
designer2.keyword = "yanone2"
190 3
designer2.name.en = "Yanone"
191 3
designer2.websiteURL = "https://yanone.de"
192

193
# License
194 3
license = LicenseDefinition()
195 3
license.URL = "https://yanone.de/eula.html"
196 3
license.keyword = "yanoneEULA"
197 3
license.name.en = "Yanone EULA"
198

199
# Font-specific Version
200 3
fontVersion = Version()
201 3
fontVersion.description.en = "Initial release"
202 3
fontVersion.description.de = "Erstveröffentlichung"
203 3
fontVersion.number = 1.0
204 3
fontVersion.releaseDate = "2004-10-10"
205

206
# FontPackage
207 3
desktopFontsPackage = FontPackage()
208 3
desktopFontsPackage.keyword = "desktop"
209 3
desktopFontsPackage.name.en = "Desktop Fonts"
210 3
desktopFontsPackage.name.de = "Desktop-Schriften"
211

212
# LicenseUsage
213 3
usedLicense = LicenseUsage()
214 3
usedLicense.allowanceDescription.en = "N/A"
215 3
usedLicense.dateAddedForUser = "2019-10-01"
216 3
usedLicense.keyword = "yanoneEULA"
217 3
usedLicense.seatsAllowed = 5
218 3
usedLicense.seatsInstalled = 1
219 3
usedLicense.upgradeURL = "https://yanone.de/buy/kaffeesatz/upgrade?customerID=123456"
220

221
# Font
222 3
font = Font()
223 3
font.dateFirstPublished = "2004-10-10"
224 3
font.designerKeywords.append("yanone")
225 3
font.designerKeywords = ["yanone", "yanone2"]
226 3
assert len(font.designerKeywords) == 2
227 3
font.format = "otf"
228 3
font.free = True
229 3
font.name.en = "Regular"
230 3
font.name.de = "Normale"
231 3
font.pdfURL = "https://yanone.de/fonts/kaffeesatz.pdf"
232 3
font.postScriptName = "YanoneKaffeesatz-Regular"
233 3
font.protected = False
234 3
font.purpose = "desktop"
235 3
font.packageKeywords.append("desktop")
236 3
font.status = "stable"
237 3
font.uniqueID = "yanone-kaffeesatz-regular"
238 3
font.usedLicenses.append(usedLicense)
239 3
font.usedLicenses = [usedLicense]
240 3
assert len(font.usedLicenses) == 1
241 3
font.variableFont = False
242 3
font.versions.append(fontVersion)
243 3
font.versions = [fontVersion]
244 3
assert len(font.versions) == 1
245 3
font.languageSupport = {"latn": ["DEU"]}
246 3
font.features = ["aalt", "liga"]
247

248
# Font 2
249 3
font2 = Font()
250 3
font2.billboardURLs = [
251
    (
252
        "https://typeworldserver.com/?page=outputDataBaseFile&"
253
        "className=TWFS_FamilyBillboards&ID=2&field=image"
254
    )
255
]
256 3
font2.billboardURLs.append(
257
    (
258
        "https://typeworldserver.com/?page=outputDataBaseFile&"
259
        "className=TWFS_FamilyBillboards&ID=6&field=image"
260
    )
261
)
262 3
font2.dateFirstPublished = "2004-10-10"
263 3
font2.designerKeywords.append("yanone")
264 3
font2.designerKeywords = ["yanone"]
265 3
assert len(font2.designerKeywords) == 1
266 3
font2.format = "otf"
267 3
font2.free = True
268 3
font2.name.en = "Bold"
269 3
font2.name.de = "Fette"
270 3
font2.pdfURL = "https://yanone.de/fonts/kaffeesatz.pdf"
271 3
font2.postScriptName = "YanoneKaffeesatz-Bold"
272 3
font2.protected = False
273 3
font2.purpose = "desktop"
274 3
font.packageKeywords.append("desktop")
275 3
font2.status = "stable"
276 3
font2.uniqueID = "yanone-kaffeesatz-bold"
277 3
font2.usedLicenses.append(usedLicense)
278 3
font2.usedLicenses = [usedLicense]
279 3
assert len(font2.usedLicenses) == 1
280 3
font2.variableFont = False
281 3
font2.versions.append(fontVersion)
282 3
font2.versions = [fontVersion]
283 3
assert len(font2.versions) == 1
284

285
# Family-specific Version
286 3
familyVersion = Version()
287 3
familyVersion.description.en = "Initial release"
288 3
familyVersion.description.de = "Erstveröffentlichung"
289 3
familyVersion.number = 1.0
290 3
familyVersion.releaseDate = "2004-10-10"
291

292
# Family
293 3
family = Family()
294 3
family.billboardURLs = [
295
    (
296
        "https://typeworldserver.com/?page=outputDataBaseFile&"
297
        "className=TWFS_FamilyBillboards&ID=2&field=image"
298
    )
299
]
300 3
family.billboardURLs.append(
301
    (
302
        "https://typeworldserver.com/?page=outputDataBaseFile&"
303
        "className=TWFS_FamilyBillboards&ID=6&field=image"
304
    )
305
)
306 3
family.dateFirstPublished = "2019-10-01"
307 3
family.description.en = "Kaffeesatz is a free font classic"
308 3
family.designerKeywords.append("yanone")
309 3
family.designerKeywords = ["yanone"]  # same as above
310 3
assert len(family.designerKeywords) == 1
311 3
family.galleryURL = "https://fontsinuse.com/kaffeesatz"
312 3
family.issueTrackerURL = "https://github.com/yanone/kaffeesatzfont/issues"
313 3
family.name.en = "Yanone Kaffeesatz"
314 3
family.pdfURL = "https://yanone.de/fonts/kaffeesatz.pdf"
315 3
family.sourceURL = "https://github.com/yanone/kaffeesatzfont"
316 3
family.uniqueID = "yanone-yanonekaffeesatz"
317 3
family.versions.append(familyVersion)
318 3
family.versions = [familyVersion]
319 3
assert len(family.versions) == 1
320 3
family.fonts.append(font)
321 3
family.fonts = [font]
322 3
assert len(family.fonts) == 1
323 3
family.fonts.append(font2)
324 3
assert len(family.fonts) == 2
325 3
family.fonts = [font, font2]
326 3
assert len(family.fonts) == 2
327 3
family.packages.append(desktopFontsPackage)
328 3
assert len(family.packages) == 1
329

330 3
print(family.billboardURLs)
331 3
print(font2.billboardURLs)
332 3
print(font2.getBillboardURLs())
333 3
assert font2.getBillboardURLs() == font2.billboardURLs
334
# str because objects are not identical, only content:
335 3
assert str(font2.getBillboardURLs()) == str(family.billboardURLs)
336

337
# Foundry
338 3
foundry = Foundry()
339 3
foundry.backgroundColor = "AABBCC"
340 3
foundry.description.en = "Yanone is a foundry lead by German type designer Yanone."
341 3
foundry.email = "font@yanone.de"
342 3
foundry.licenses.append(license)
343 3
foundry.name.en = "Awesome Fonts"
344 3
foundry.name.de = "Tolle Schriften"
345

346 3
foundry.socialURLs.append("https://facebook.com/pages/YanoneYanone")
347 3
foundry.socialURLs.append("https://twitter.com/yanone")
348

349 3
foundry.supportEmail = "support@yanone.de"
350 3
foundry.supportTelephone = "+49123456789"
351 3
foundry.supportURL = "https://yanone.de/support/"
352 3
foundry.telephone = "+49123456789"
353 3
foundry.twitter = "yanone"
354 3
foundry.uniqueID = "yanone"
355 3
foundry.websiteURL = "https://yanone.de"
356 3
foundry.families.append(family)
357 3
foundry.families = [family]
358 3
assert len(foundry.families) == 1
359 3
foundry.styling = json.loads(
360
    """{"light": {
361
            "headerColor": "F20D5E",
362
            "headerTextColor": "000000",
363
            "headerLinkColor": "E5F20D",
364

365
            "backgroundColor": "E5F20D",
366
            "textColor": "000000",
367
            "linkColor": "F7AD22",
368

369
            "selectionColor": "0D79F2",
370
            "selectionTextColor": "E5F20D",
371

372
            "buttonColor": "197AA3",
373
            "buttonTextColor": "FFFFFF",
374

375
            "informationViewBackgroundColor": "469BF5",
376
            "informationViewTextColor": "000000",
377
            "informationViewLinkColor": "E5F20D",
378

379
            "informationViewButtonColor": "E5F20D",
380
            "informationViewButtonTextColor": "000000"
381

382
        }, "dark": {
383
            "headerColor": "B10947",
384
            "headerTextColor": "000000",
385
            "headerLinkColor": "E5F20D",
386

387
            "backgroundColor": "1A1A1A",
388
            "textColor": "E5F20D",
389
            "linkColor": "C07F07",
390

391
            "selectionColor": "B10947",
392
            "selectionTextColor": "E5F20D",
393

394
            "buttonColor": "22A4DC",
395
            "buttonTextColor": "000000",
396

397
            "informationViewBackgroundColor": "000000",
398
            "informationViewTextColor": "999999",
399
            "informationViewLinkColor": "E5F20D",
400

401
            "informationViewButtonColor": "1E90C1",
402
            "informationViewButtonTextColor": "000000"
403

404
        } }"""
405
)
406

407
# InstallableFontsResponse
408 3
installableFonts = InstallableFontsResponse()
409 3
installableFonts.designers.append(designer)
410 3
installableFonts.designers.append(designer2)
411 3
installableFonts.designers.remove(designer2)
412 3
installableFonts.designers.extend([designer2])
413 3
installableFonts.name.en = "Commercial Fonts"
414 3
installableFonts.name.de = "Kommerzielle Schriften"
415 3
installableFonts.prefersRevealedUserIdentity = True
416 3
installableFonts.response = "success"
417 3
installableFonts.userEmail = "post@yanone.de"
418 3
installableFonts.userName.en = "Yanone"
419 3
installableFonts.version = "0.1.7-alpha"
420 3
installableFonts.foundries.append(foundry)
421
# print(installableFonts.designers)
422

423

424 3
class User(object):
425 3
    def __init__(
426
        self,
427
        login=None,
428
        online=True,
429
        delegate=None,
430
        createUserAccount=True,
431
        testScenario=None,
432
    ):
433 3
        self.login = login
434 3
        self.prefFile = os.path.join(tempFolder, str(id(self)) + ".json")
435 3
        self.online = online
436 3
        self.credentials = ()
437 3
        self.delegate = delegate
438 3
        self.createUserAccount = createUserAccount
439

440 3
        self.loadClient(testScenario=testScenario)
441

442 3
        if self.login:
443

444 3
            if self.createUserAccount:
445 3
                success, message = self.client.deleteUserAccount(*self.login)
446 3
                if not success and message != [
447
                    "#(response.userUnknown)",
448
                    "#(response.userUnknown.headline)",
449
                ]:
450 0
                    raise ValueError(message)
451
                # print('Creating user account for %s' % self.login[0])
452 3
                success, message = self.client.createUserAccount(
453
                    "Test User", self.login[0], self.login[1], self.login[1]
454
                )
455 3
                if not success:
456 0
                    raise ValueError(message)
457
                else:
458 3
                    print(
459
                        "Successfully created user account for",
460
                        self.login[0],
461
                        "/",
462
                        self.client.user(),
463
                    )
464
            else:
465 3
                success, message = self.client.logInUserAccount(
466
                    self.login[0], self.login[1]
467
                )
468 3
                assert success
469

470 3
            self.credentials = (self.client.user(), self.client.secretKey())
471

472 3
            self.clearInvitations()
473 3
            self.clearSubscriptions()
474

475
    # def linkUser(self):
476
    # 	self.unlinkUser()
477
    # 	if self.login:
478
    # 		return self.client.linkUser(*self.credentials)
479

480 3
    def expiringTestFont(self):
481 3
        return (
482
            self.client.publishers()[0]
483
            .subscriptions()[-1]
484
            .protocol.installableFontsCommand()[1]
485
            .foundries[-1]
486
            .families[0]
487
            .fonts[-1]
488
        )
489

490 3
    def testFont(self):
491 3
        publisher = self.client.publishers()[0]
492 3
        subscription = publisher.subscriptions()[-1]
493 3
        installableFontsCommand = subscription.protocol.installableFontsCommand()[1]
494 3
        foundry = installableFontsCommand.foundries[-1]
495 3
        family = foundry.families[-1]
496 3
        font = family.fonts[-1]
497 3
        return font
498

499 3
    def testFonts(self):
500 3
        publisher = self.client.publishers()[0]
501 3
        subscription = publisher.subscriptions()[-1]
502 3
        installableFontsCommand = subscription.protocol.installableFontsCommand()[1]
503 3
        foundry = installableFontsCommand.foundries[-1]
504 3
        family = foundry.families[-1]
505 3
        return family.fonts
506

507 3
    def clearSubscriptions(self):
508 3
        self.client.testScenario = None
509 3
        for publisher in self.client.publishers():
510 3
            success, message = publisher.delete()
511 3
            assert success
512

513 3
    def clearInvitations(self):
514 3
        self.client.testScenario = None
515 3
        for invitation in self.client.pendingInvitations():
516 3
            invitation.decline()
517

518 3
    def takeDown(self):
519 3
        self.client.testScenario = None
520 3
        self.clearInvitations()
521 3
        self.clearSubscriptions()
522 3
        if self.login and self.createUserAccount:
523 3
            self.client.deleteUserAccount(*self.login)
524

525
    # def unlinkUser(self):
526
    # 	self.client.testScenario = None
527
    # 	if self.login:
528
    # 		if self.client.user():
529
    # 			self.client.unlinkUser()
530

531 3
    def loadClient(self, testScenario=None):
532 3
        self.client = APIClient(
533
            preferences=AppKitNSUserDefaults("world.type.test%s" % id(self))
534
            if MAC
535
            else JSON(self.prefFile),
536
            mothership=MOTHERSHIP,
537
            zmqSubscriptions=True,
538
            online=self.online,
539
            testing=True,
540
            delegate=self.delegate,
541
            secretServerAuthKey=SECRETKEY,
542
            commercial=True,
543
            appID="world.type.app",
544
        )
545 3
        self.client.testScenario = testScenario
546

547

548 3
print("setting up objects finished...")
549

550

551 3
class TestTypeWorld(unittest.TestCase):
552

553 3
    currentResult = None
554 3
    maxDiff = None
555

556 3
    def test_updateNotifications(self):
557

558 3
        print("test_updateNotifications() started...")
559

560 3
        class TestDelegate(typeworld.client.TypeWorldClientDelegate):
561 3
            def initialize(self):
562 3
                self._subscriptionsUpdated = []
563 3
                self._accountUpdateCheck = False
564

565 3
            def reset(self):
566 3
                self._subscriptionsUpdated = []
567 3
                self._accountUpdateCheck = False
568

569 3
            def subscriptionUpdateNotificationHasBeenReceived(self, subscription):
570 3
                assert type(subscription) == typeworld.client.APISubscription
571 3
                self._subscriptionsUpdated.append(subscription)
572 3
                print("subscriptionUpdateNotificationHasBeenReceived", subscription)
573

574 3
            def userAccountUpdateNotificationHasBeenReceived(self):
575 3
                print("userAccountUpdateNotificationHasBeenReceived")
576 3
                self._accountUpdateCheck = True
577

578 3
        print("\nLine %s" % getframeinfo(currentframe()).lineno)
579

580 3
        user1.client.delegate = TestDelegate()
581 3
        user1.clearInvitations()
582 3
        user1.clearSubscriptions()
583

584 3
        user4 = User(
585
            testUser1, createUserAccount=False, testScenario="simulateTestUser1IsPro"
586
        )
587 3
        self.assertEqual(user1.client.user(), user4.client.user())
588 3
        user4.client.delegate = TestDelegate()
589 3
        self.assertTrue(user4.client.requiresMessageQueueConnection())
590 3
        self.assertTrue(user4.client._zmqRunning)
591

592 3
        print("\nLine %s" % getframeinfo(currentframe()).lineno)
593

594
        # Reset
595 3
        user1.client.delegate.reset()
596

597
        # Add protected subscription
598 3
        result = user1.client.addSubscription(protectedSubscription)
599 3
        success, message, publisher, subscription = result
600 3
        if not success:
601 0
            print(message)
602 3
        self.assertEqual(success, True)
603

604 3
        print("\nLine %s" % getframeinfo(currentframe()).lineno)
605

606 3
        loop = 0
607 3
        while user4.client.delegate._accountUpdateCheck is False and loop < 60:  # wait
608 0
            print(
609
                f"Waiting for user account to be updated... {loop}s",
610
                user1.client.delegate._accountUpdateCheck,
611
            )
612 0
            time.sleep(1)
613 0
            loop += 1
614

615 3
        print("\nLine %s" % getframeinfo(currentframe()).lineno)
616

617
        # Send Update Subscription Notification
618 3
        parameters = {
619
            "subscriptionURL": protectedSubscriptionWithoutAccessToken,
620
            "APIKey": "I3ZYbDwYgG3S7lpOGI6LjEylQWt6tPS7MJtN1d3T",
621
            "testing": "true",
622
            "testScenario": "simulateUpdateSubscriptionQuotaReached",
623
        }
624 3
        success, response, responseObject = typeworld.client.request(
625
            MOTHERSHIP + "/updateSubscription", parameters
626
        )
627 3
        if not success:
628 0
            print(response)
629 3
        self.assertEqual(success, True)
630 3
        response = json.loads(response.decode())
631 3
        self.assertEqual(response["response"], "paidSubscriptionRequired")
632

633
        # Send Update Subscription Notification
634 3
        parameters = {
635
            "subscriptionURL": protectedSubscriptionWithoutAccessToken,
636
            "APIKey": "I3ZYbDwYgG3S7lpOGI6LjEylQWt6tPS7MJtN1d3T",
637
            "testing": "true",
638
            "testScenario": "simulateAddedFontVersion",
639
        }
640 3
        success, response, responseObject = typeworld.client.request(
641
            MOTHERSHIP + "/updateSubscription", parameters
642
        )
643 3
        self.assertEqual(success, True)
644

645 3
        parameters = {
646
            "subscriptionURL": protectedSubscriptionWithoutAccessToken,
647
            "APIKey": "I3ZYbDwYgG3S7lpOGI6LjEylQWt6tPS7MJtN1d3T",
648
            "testing": "true",
649
            "testScenario": "simulateAddedFont",
650
        }
651 3
        success, response, responseObject = typeworld.client.request(
652
            MOTHERSHIP + "/updateSubscription", parameters
653
        )
654 3
        self.assertEqual(success, True)
655

656
        # Send Update Subscription Notification
657 3
        parameters = {
658
            "subscriptionURL": protectedSubscriptionWithoutAccessToken,
659
            "APIKey": "I3ZYbDwYgG3S7lpOGI6LjEylQWt6tPS7MJtN1d3T",
660
            "testing": "true",
661
        }
662 3
        success, response, responseObject = typeworld.client.request(
663
            MOTHERSHIP + "/updateSubscription", parameters
664
        )
665 3
        if not success:
666 0
            print(response)
667 3
        self.assertEqual(success, True)
668 3
        response = json.loads(response.decode())
669 3
        self.assertEqual(response["response"], "success")
670

671 3
        print("\nLine %s" % getframeinfo(currentframe()).lineno)
672

673 3
        loop = 0
674 3
        while (
675
            subscription not in user1.client.delegate._subscriptionsUpdated
676
            and loop < 60  # wait
677
        ):
678 0
            print(f"Waiting for subscription to be updated... {loop}s")
679 0
            time.sleep(1)
680 0
            loop += 1
681

682 3
        print("\nLine %s" % getframeinfo(currentframe()).lineno)
683

684
        # User1 hasn't been notified to pull user account updates
685
        # Because it's the origin user account of the subbscription addition
686 3
        self.assertFalse(user1.client.delegate._accountUpdateCheck)
687

688 3
        print("\nLine %s" % getframeinfo(currentframe()).lineno)
689

690
        # User4 has been notified to pull user account updates
691
        # because it's the same user account but on another machine
692 3
        self.assertTrue(user4.client.delegate._accountUpdateCheck)
693

694 3
        print("\nLine %s" % getframeinfo(currentframe()).lineno)
695

696
        # This subscription has received an update notification
697 3
        self.assertIn(subscription, user1.client.delegate._subscriptionsUpdated)
698

699 3
        print("\nLine %s" % getframeinfo(currentframe()).lineno)
700

701 3
        user1.clearSubscriptions()
702

703 3
        print("\nLine %s" % getframeinfo(currentframe()).lineno)
704

705 3
        user2.clearSubscriptions()
706

707 3
        print("\nLine %s" % getframeinfo(currentframe()).lineno)
708

709 3
        user4.takeDown()
710 3
        user4.client.quit()
711

712 3
    def test_emptyValues(self):
713

714 3
        print("test_emptyValues() started...")
715

716 3
        self.assertTrue(HexColorDataType().valid())
717 3
        self.assertTrue(FontEncodingDataType().valid())
718 3
        self.assertTrue(EmailDataType().valid())
719 3
        self.assertTrue(BooleanDataType().valid())
720 3
        self.assertTrue(WebURLDataType().valid())
721 3
        self.assertTrue(IntegerDataType().valid())
722 3
        self.assertTrue(FloatDataType().valid())
723 3
        self.assertTrue(DateDataType().valid())
724 3
        self.assertTrue(VersionDataType().valid())
725

726 3
        self.assertTrue(LanguageSupportDataType().valid())
727 3
        self.assertTrue(OpenTypeFeatureDataType().valid())
728 3
        self.assertTrue(OpenSourceLicenseIdentifierDataType().valid())
729 3
        self.assertTrue(SupportedAPICommandsDataType().valid())
730 3
        self.assertTrue(FontPurposeDataType().valid())
731 3
        self.assertTrue(FontMimeType().valid())
732 3
        self.assertTrue(FontStatusDataType().valid())
733

734 3
        self.assertTrue(InstallableFontsResponseType().valid())
735 3
        self.assertTrue(InstallFontAssetResponseType().valid())
736 3
        self.assertTrue(InstallFontResponseType().valid())
737 3
        self.assertTrue(UninstallFontAssedResponseType().valid())
738 3
        self.assertTrue(UninstallFontResponseType().valid())
739

740 3
    def test_EndpointResponse(self):
741

742 3
        print("test_EndpointResponse() started...")
743

744 3
        print(root)
745
        # Dump and reload
746 3
        json = root.dumpJSON()
747 3
        root2 = EndpointResponse()
748 3
        root2.loadJSON(json)
749 3
        self.assertTrue(root.sameContent(root2))
750

751
        # name
752 3
        r2 = copy.deepcopy(root)
753 3
        r2.name.en = ""
754 3
        self.assertEqual(
755
            r2.validate()[2],
756
            ["<EndpointResponse>.name is a required attribute, but empty"],
757
        )
758

759
        # canonicalURL
760 3
        r2 = copy.deepcopy(root)
761 3
        try:
762 3
            r2.canonicalURL = (
763
                "typeworldserver.com/?page=outputDataBaseFile&"
764
                "className=TWFS_FamilyBillboards&ID=2&field=image"
765
            )
766 3
        except ValueError as e:
767 3
            self.assertEqual(str(e), "Needs to start with http:// or https://")
768

769
        # adminEmail
770 3
        r2 = copy.deepcopy(root)
771 3
        try:
772 3
            r2.adminEmail = "post_at_yanone.de"
773 3
        except ValueError as e:
774 3
            self.assertEqual(str(e), "Not a valid email format: post_at_yanone.de")
775

776
        # supportedCommands
777 3
        r2 = copy.deepcopy(root)
778 3
        try:
779 3
            r2.supportedCommands = ["unsupportedCommand"]
780 3
        except ValueError as e:
781 3
            self.assertEqual(
782
                str(e),
783
                (
784
                    "Unknown API command: 'unsupportedCommand'. "
785
                    "Possible: ['endpoint', 'installableFonts', 'installFonts', "
786
                    "'uninstallFonts']"
787
                ),
788
            )
789

790
        # publisherTypes
791 3
        r2 = copy.deepcopy(root)
792 3
        try:
793 3
            r2.publisherTypes = ["unsupportedCommand"]
794 3
        except ValueError as e:
795 3
            self.assertEqual(
796
                str(e),
797
                (
798
                    "Unknown publisher type: 'unsupportedCommand'. "
799
                    "Possible: ['free', 'retail', 'custom', 'undefined']"
800
                ),
801
            )
802

803
        # publisherTypes
804 3
        r2 = copy.deepcopy(root)
805 3
        try:
806 3
            r2.publisherTypes = ["undefined"]
807 3
            r2.public = True
808 3
            validate = r2.validate()
809 3
            print(validate[2])
810 3
            self.assertEqual(
811
                validate[2],
812
                [
813
                    (
814
                        "<EndpointResponse> --> When EndpointResponse.public is set "
815
                        "to True, then only a restricted set of types is allowed for "
816
                        "EndpointResponse.publisherTypes: ['free', 'retail', 'custom']."
817
                        " You have 'undefined'"
818
                    )
819
                ],
820
            )
821

822 0
        except ValueError as e:
823 0
            self.assertEqual(
824
                str(e),
825
                (
826
                    "Unknown publisher type: 'unsupportedCommand'. "
827
                    "Possible: ['free', 'retail', 'custom', 'undefined']"
828
                ),
829
            )
830

831
        # backgroundColor
832 3
        r2 = copy.deepcopy(root)
833 3
        try:
834 3
            r2.backgroundColor = "CDEFGH"
835 3
        except ValueError as e:
836 3
            self.assertEqual(
837
                str(e),
838
                "Not a valid hex color of format RRGGBB (like FF0000 for red): CDEFGH",
839
            )
840

841
        # licenseIdentifier
842 3
        r2 = copy.deepcopy(root)
843 3
        try:
844 3
            r2.licenseIdentifier = "unsupportedLicense"
845 3
        except ValueError as e:
846 3
            self.assertEqual(
847
                str(e),
848
                (
849
                    "Unknown license identifier: 'unsupportedLicense'. "
850
                    "See https://spdx.org/licenses/"
851
                ),
852
            )
853

854
        # logo
855 3
        r2 = copy.deepcopy(root)
856 3
        try:
857 3
            r2.logoURL = (
858
                "typeworldserver.com/?page=outputDataBaseFile&"
859
                "className=TWFS_FamilyBillboards&ID=2&field=image"
860
            )
861 3
        except ValueError as e:
862 3
            self.assertEqual(str(e), "Needs to start with http:// or https://")
863

864
        # privacyPolicyURL
865 3
        r2 = copy.deepcopy(root)
866 3
        try:
867 3
            r2.privacyPolicyURL = (
868
                "typeworldserver.com/?page=outputDataBaseFile&"
869
                "className=TWFS_FamilyBillboards&ID=2&field=image"
870
            )
871 3
        except ValueError as e:
872 3
            self.assertEqual(str(e), "Needs to start with http:// or https://")
873

874
        # termsOfServiceURL
875 3
        r2 = copy.deepcopy(root)
876 3
        try:
877 3
            r2.termsOfServiceURL = (
878
                "typeworldserver.com/?page=outputDataBaseFile&"
879
                "className=TWFS_FamilyBillboards&ID=2&field=image"
880
            )
881 3
        except ValueError as e:
882 3
            self.assertEqual(str(e), "Needs to start with http:// or https://")
883

884
        # public
885 3
        r2 = copy.deepcopy(root)
886 3
        try:
887 3
            r2.public = "True"
888 3
        except ValueError as e:
889 3
            self.assertEqual(
890
                str(e), "Wrong data type. Is <class 'str'>, should be: <class 'bool'>."
891
            )
892

893
        # website
894 3
        r2 = copy.deepcopy(root)
895 3
        try:
896 3
            r2.websiteURL = (
897
                "typeworldserver.com/?page=outputDataBaseFile&"
898
                "className=TWFS_FamilyBillboards&ID=2&field=image"
899
            )
900 3
        except ValueError as e:
901 3
            self.assertEqual(str(e), "Needs to start with http:// or https://")
902

903 3
        print("test_EndpointResponse() finished...")
904

905 3
    def test_copy(self):
906

907 3
        print("test_copy() started...")
908

909 3
        i2 = copy.copy(installableFonts)
910 3
        self.assertTrue(installableFonts.sameContent(i2))
911

912 3
        i3 = copy.deepcopy(installableFonts)
913 3
        self.assertTrue(installableFonts.sameContent(i3))
914

915 3
        print("test_copy() finished...")
916

917 3
    def test_InstallableFontsResponse(self):
918

919 3
        print("test_InstallableFontsResponse()")
920

921 3
        print(installableFonts)
922
        # Dump and reload
923 3
        json = installableFonts.dumpJSON()
924 3
        installableFonts2 = InstallableFontsResponse()
925 3
        installableFonts2.loadJSON(json)
926 3
        self.assertTrue(installableFonts.sameContent(installableFonts2))
927

928
        # designers
929 3
        i2 = copy.deepcopy(installableFonts)
930 3
        try:
931 3
            i2.designers = "yanone"
932 3
        except ValueError as e:
933 3
            self.assertEqual(
934
                str(e), "Wrong data type. Is <class 'str'>, should be: <class 'list'>."
935
            )
936

937
        # error
938 3
        i2 = copy.deepcopy(installableFonts)
939 3
        i2.response = "error"
940 3
        validate = i2.validate()
941 3
        print(validate[2])
942 3
        self.assertEqual(
943
            validate[2],
944
            [
945
                (
946
                    "<InstallableFontsResponse> --> .response is 'error', "
947
                    "but .errorMessage is missing."
948
                )
949
            ],
950
        )
951

952
        # error
953 3
        i2 = copy.deepcopy(installableFonts)
954 3
        i2.response = "error"
955 3
        try:
956 3
            data = i2.dumpDict()  # noqa: F841
957 3
        except ValueError as e:
958 3
            self.assertEqual(
959
                str(e),
960
                (
961
                    "<InstallableFontsResponse> --> .response is 'error', "
962
                    "but .errorMessage is missing."
963
                ),
964
            )
965

966
        # name
967
        # allowed to be emtpy
968 3
        i2 = copy.deepcopy(installableFonts)
969 3
        i2.name.en = ""
970 3
        validate = i2.validate()
971 3
        print(validate[2])
972 3
        self.assertEqual(validate[2], [])
973

974
        # prefersRevealedUserIdentity
975 3
        i2 = copy.deepcopy(installableFonts)
976 3
        try:
977 3
            i2.prefersRevealedUserIdentity = "True"
978 3
        except ValueError as e:
979 3
            self.assertEqual(
980
                str(e), "Wrong data type. Is <class 'str'>, should be: <class 'bool'>."
981
            )
982

983
        # type
984 3
        i2 = copy.deepcopy(installableFonts)
985 3
        try:
986 3
            i2.response = "abc"
987 3
        except ValueError as e:
988 3
            self.assertEqual(
989
                str(e),
990
                (
991
                    "Unknown response type: 'abc'. Possible: ['success', 'error', "
992
                    "'noFontsAvailable', 'insufficientPermission', "
993
                    "'temporarilyUnavailable', 'validTypeWorldUserAccountRequired']"
994
                ),
995
            )
996

997
        # userEmail
998 3
        i2 = copy.deepcopy(installableFonts)
999 3
        try:
1000 3
            i2.userEmail = "post_at_yanone.de"
1001 3
        except ValueError as e:
1002 3
            self.assertEqual(str(e), "Not a valid email format: post_at_yanone.de")
1003

1004
        # userName
1005 3
        i2 = copy.deepcopy(installableFonts)
1006 3
        i2.userName.en = ""
1007 3
        validate = i2.validate()
1008 3
        print(validate[2])
1009 3
        self.assertEqual(validate[2], [])
1010

1011
        # foundries
1012 3
        i2 = copy.deepcopy(installableFonts)
1013 3
        try:
1014 3
            i2.foundries = "yanone"
1015 3
        except ValueError as e:
1016 3
            self.assertEqual(
1017
                str(e), "Wrong data type. Is <class 'str'>, should be: <class 'list'>."
1018
            )
1019

1020 3
        i2 = copy.deepcopy(installableFonts)
1021 3
        i2.name.en = ""
1022 3
        i2.name.de = ""
1023 3
        validate = i2.validate()
1024 3
        print(validate[2])
1025
        # 		print(validate)
1026 3
        self.assertEqual(
1027
            validate[1],
1028
            [
1029
                (
1030
                    "<InstallableFontsResponse> --> The response has no .name value. "
1031
                    "It is not required, but highly recommended, to describe the "
1032
                    "purpose of this subscription to the user (such as 'Commercial "
1033
                    "Fonts', 'Free Fonts', etc. This is especially useful if you offer "
1034
                    "several different subscriptions to the same user."
1035
                )
1036
            ],
1037
        )
1038

1039 3
        i2 = copy.deepcopy(installableFonts)
1040 3
        i2.response = "error"
1041 3
        validate = i2.validate()
1042 3
        print(validate[2])
1043 3
        self.assertEqual(
1044
            validate[2],
1045
            [
1046
                (
1047
                    "<InstallableFontsResponse> --> .response is 'error', "
1048
                    "but .errorMessage is missing."
1049
                )
1050
            ],
1051
        )
1052

1053 3
    def test_Designer(self):
1054

1055 3
        print("test_Designer()")
1056

1057
        # name
1058 3
        i2 = copy.deepcopy(installableFonts)
1059 3
        i2.designers[0].name.en = ""
1060 3
        validate = i2.validate()
1061 3
        print(validate[2])
1062 3
        self.assertEqual(
1063
            validate[2],
1064
            [
1065
                (
1066
                    "<InstallableFontsResponse>.designers --> <Designer 'None'>.name "
1067
                    "is a required attribute, but empty"
1068
                )
1069
            ],
1070
        )
1071

1072
        # description
1073
        # is optional, so this will pass
1074 3
        i2 = copy.deepcopy(installableFonts)
1075 3
        i2.designers[0].description.en = ""
1076 3
        self.assertEqual(i2.validate()[2], [])
1077

1078
        # keyword
1079 3
        i2 = copy.deepcopy(installableFonts)
1080 3
        i2.designers[0].keyword = ""
1081 3
        validate = i2.validate()
1082 3
        print(validate[2])
1083 3
        self.assertEqual(
1084
            validate[2],
1085
            [
1086
                (
1087
                    "<InstallableFontsResponse>.designers --> <Designer 'Yanone'>."
1088
                    "keyword is a required attribute, but empty"
1089
                ),
1090
                (
1091
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1092
                    ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
1093
                    "'YanoneKaffeesatz-Regular'> --> Has designer 'yanone', but "
1094
                    "<InstallableFontsResponse>.designers has no matching designer."
1095
                ),
1096
                (
1097
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1098
                    ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
1099
                    "'YanoneKaffeesatz-Bold'> --> Has designer 'yanone', but "
1100
                    "<InstallableFontsResponse>.designers has no matching designer."
1101
                ),
1102
                (
1103
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1104
                    ".families --> <Family 'Yanone Kaffeesatz'> --> Has designer "
1105
                    "'yanone', but <InstallableFontsResponse>.designers has no "
1106
                    "matching designer."
1107
                ),
1108
            ],
1109
        )
1110

1111
        # name
1112 3
        i2 = copy.deepcopy(installableFonts)
1113 3
        i2.designers[0].name.en = ""
1114 3
        validate = i2.validate()
1115 3
        print(validate[2])
1116 3
        self.assertEqual(
1117
            validate[2],
1118
            [
1119
                (
1120
                    "<InstallableFontsResponse>.designers --> <Designer 'None'>.name"
1121
                    " is a required attribute, but empty"
1122
                )
1123
            ],
1124
        )
1125

1126
        # website
1127 3
        i2 = copy.deepcopy(installableFonts)
1128 3
        try:
1129 3
            i2.designers[0].websiteURL = (
1130
                "typeworldserver.com/?page=outputDataBaseFile&"
1131
                "className=TWFS_FamilyBillboards&ID=2&field=image"
1132
            )
1133 3
        except ValueError as e:
1134 3
            self.assertEqual(str(e), "Needs to start with http:// or https://")
1135

1136 3
    def test_LicenseDefinition(self):
1137

1138 3
        print("test_LicenseDefinition()")
1139

1140
        # name
1141 3
        i2 = copy.deepcopy(installableFonts)
1142 3
        i2.foundries[0].licenses[0].name.en = ""
1143 3
        validate = i2.validate()
1144 3
        print(validate[2])
1145 3
        self.assertEqual(
1146
            validate[2],
1147
            [
1148
                (
1149
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1150
                    ".licenses --> <LicenseDefinition 'None'>.name is a required "
1151
                    "attribute, but empty"
1152
                )
1153
            ],
1154
        )
1155

1156
        # URL
1157 3
        i2 = copy.deepcopy(installableFonts)
1158 3
        try:
1159 3
            i2.foundries[0].licenses[0].URL = (
1160
                "typeworldserver.com/?page=outputDataBaseFile&"
1161
                "className=TWFS_FamilyBillboards&ID=2&field=image"
1162
            )
1163 3
        except ValueError as e:
1164 3
            self.assertEqual(str(e), "Needs to start with http:// or https://")
1165

1166
        # keyword
1167 3
        i2 = copy.deepcopy(installableFonts)
1168 3
        i2.foundries[0].licenses[0].keyword = ""
1169 3
        validate = i2.validate()
1170 3
        print(validate[2])
1171 3
        self.assertEqual(
1172
            validate[2],
1173
            [
1174
                (
1175
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1176
                    ".licenses --> <LicenseDefinition 'Yanone EULA'>.keyword is a "
1177
                    "required attribute, but empty"
1178
                ),
1179
                (
1180
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1181
                    ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> "
1182
                    "<Font 'YanoneKaffeesatz-Regular'>.usedLicenses --> <LicenseUsage"
1183
                    " 'yanoneEULA'> --> Has license 'yanoneEULA', but <Foundry "
1184
                    "'Awesome Fonts'> has no matching license."
1185
                ),
1186
                (
1187
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1188
                    ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
1189
                    "'YanoneKaffeesatz-Bold'>.usedLicenses --> <LicenseUsage "
1190
                    "'yanoneEULA'> --> Has license 'yanoneEULA', but <Foundry "
1191
                    "'Awesome Fonts'> has no matching license."
1192
                ),
1193
            ],
1194
        )
1195

1196 3
        i2 = copy.deepcopy(installableFonts)
1197 3
        print(i2.foundries[0].licenses[0])
1198 3
        assert i2.foundries[0].licenses[0].parent == i2.foundries[0]
1199

1200 3
    def test_Version(self):
1201

1202 3
        print("test_Version()")
1203

1204
        # description
1205
        # is optional, so this will pass
1206 3
        i2 = copy.deepcopy(installableFonts)
1207 3
        i2.foundries[0].families[0].versions[0].description.en = ""
1208 3
        i2.foundries[0].families[0].versions[0].description.de = ""
1209 3
        self.assertEqual(i2.validate()[2], [])
1210

1211
        # number
1212 3
        i2 = copy.deepcopy(installableFonts)
1213 3
        try:
1214 3
            i2.foundries[0].families[0].versions[0].number = "1.1.2.3"
1215 3
        except ValueError as e:
1216 3
            self.assertEqual(str(e), "1.1.2.3 is not valid SemVer string")
1217

1218
        # number
1219 3
        i2 = copy.deepcopy(installableFonts)
1220 3
        try:
1221 3
            i2.foundries[0].families[0].versions[0].number = "a"
1222 3
        except ValueError as e:
1223 3
            self.assertEqual(str(e), "False")
1224

1225
        # releaseDate
1226 3
        i2 = copy.deepcopy(installableFonts)
1227 3
        try:
1228 3
            i2.foundries[0].families[0].versions[0].releaseDate = "2010-20-21"
1229 3
        except ValueError as e:
1230 3
            self.assertEqual(
1231
                str(e),
1232
                "ValueError: time data '2010-20-21' does not match format '%Y-%m-%d'",
1233
            )
1234

1235 3
        i2 = copy.deepcopy(installableFonts)
1236 3
        print(i2.foundries[0].families[0].versions[0])
1237 3
        assert i2.foundries[0].families[0].versions[0].isFontSpecific() is False
1238 3
        assert (
1239
            i2.foundries[0].families[0].versions[0].parent
1240
            == i2.foundries[0].families[0]
1241
        )
1242 3
        assert i2.foundries[0].families[0].fonts[0].versions[0].isFontSpecific() is True
1243 3
        assert (
1244
            i2.foundries[0].families[0].fonts[0].versions[0].parent
1245
            == i2.foundries[0].families[0].fonts[0]
1246
        )
1247

1248 3
        i2 = copy.deepcopy(installableFonts)
1249 3
        try:
1250 3
            i2.foundries[0].families[0].fonts[0].filename(
1251
                i2.foundries[0].families[0].fonts[0].getVersions()[-1]
1252
            )
1253 3
        except ValueError as e:
1254 3
            self.assertEqual(str(e), "Supplied version must be str or int or float")
1255

1256 3
    def test_LicenseUsage(self):
1257

1258 3
        print("test_LicenseUsage()")
1259

1260
        # allowanceDescription
1261
        # is optional, so this will pass
1262 3
        i2 = copy.deepcopy(installableFonts)
1263 3
        i2.foundries[0].families[0].fonts[0].usedLicenses[
1264
            0
1265
        ].allowanceDescription.en = None
1266 3
        self.assertEqual(i2.validate()[2], [])
1267

1268
        # dateAddedForUser
1269 3
        i2 = copy.deepcopy(installableFonts)
1270 3
        try:
1271 3
            i2.foundries[0].families[0].fonts[0].usedLicenses[
1272
                0
1273
            ].dateAddedForUser = "2010-20-21"
1274 3
        except ValueError as e:
1275 3
            self.assertEqual(
1276
                str(e),
1277
                "ValueError: time data '2010-20-21' does not match format '%Y-%m-%d'",
1278
            )
1279

1280
        # keyword
1281 3
        i2 = copy.deepcopy(installableFonts)
1282 3
        i2.foundries[0].families[0].fonts[0].usedLicenses[0].keyword = ""
1283 3
        validate = i2.validate()
1284 3
        print(validate[2])
1285 3
        self.assertEqual(
1286
            validate[2],
1287
            [
1288
                (
1289
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1290
                    ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
1291
                    "'YanoneKaffeesatz-Regular'>.usedLicenses --> <LicenseUsage ''>."
1292
                    "keyword is a required attribute, but empty"
1293
                )
1294
            ],
1295
        )
1296

1297
        # seatsAllowed
1298 3
        i2 = copy.deepcopy(installableFonts)
1299 3
        try:
1300 3
            i2.foundries[0].families[0].fonts[0].usedLicenses[0].seatsAllowed = "5,0"
1301 3
        except ValueError as e:
1302 3
            self.assertEqual(str(e), "invalid literal for int() with base 10: '5,0'")
1303
        # try the same, but modify the json string, the re-import
1304 3
        json = installableFonts.dumpJSON()
1305 3
        json = json.replace('"seatsAllowed": 5', '"seatsAllowed": "5,0"')
1306 3
        try:
1307 3
            i2.loadJSON(json)
1308 3
        except ValueError as e:
1309 3
            self.assertEqual(str(e), "invalid literal for int() with base 10: '5,0'")
1310

1311
        # seatsInstalled
1312 3
        i2 = copy.deepcopy(installableFonts)
1313 3
        try:
1314 3
            i2.foundries[0].families[0].fonts[0].usedLicenses[0].seatsInstalled = "1,0"
1315 3
        except ValueError as e:
1316 3
            self.assertEqual(str(e), "invalid literal for int() with base 10: '1,0'")
1317

1318
        # URL
1319 3
        i2 = copy.deepcopy(installableFonts)
1320 3
        try:
1321 3
            i2.foundries[0].families[0].fonts[0].usedLicenses[0].upgradeURL = (
1322
                "typeworldserver.com/?page=outputDataBaseFile&"
1323
                "className=TWFS_FamilyBillboards&ID=2&field=image"
1324
            )
1325 3
        except ValueError as e:
1326 3
            self.assertEqual(str(e), "Needs to start with http:// or https://")
1327

1328 3
        i2 = copy.deepcopy(installableFonts)
1329 3
        print(i2.foundries[0].families[0].fonts[0].usedLicenses[0])
1330

1331 3
    def test_FontPackage(self):
1332

1333 3
        print("test_FontPackage()")
1334

1335 3
        i2 = copy.deepcopy(installableFonts)
1336 3
        print(i2.foundries[0].families[0].packages[0])
1337

1338 3
    def test_Font(self):
1339

1340 3
        print("test_Font()")
1341

1342
        # billboardURLs
1343 3
        i2 = copy.deepcopy(installableFonts)
1344 3
        try:
1345 3
            i2.foundries[0].families[0].fonts[1].billboardURLs[0] = (
1346
                "typeworldserver.com/?page=outputDataBaseFile&"
1347
                "className=TWFS_FamilyBillboards&ID=2&field=image"
1348
            )
1349 3
        except ValueError as e:
1350 3
            self.assertEqual(str(e), "Needs to start with http:// or https://")
1351

1352
        # dateAddedForUser
1353 3
        i2 = copy.deepcopy(installableFonts)
1354 3
        try:
1355 3
            i2.foundries[0].families[0].fonts[0].dateFirstPublished = "2010-20-21"
1356 3
        except ValueError as e:
1357 3
            self.assertEqual(
1358
                str(e),
1359
                "ValueError: time data '2010-20-21' does not match format '%Y-%m-%d'",
1360
            )
1361

1362
        # designers
1363 3
        i2 = copy.deepcopy(installableFonts)
1364 3
        i2.foundries[0].families[0].fonts[0].designerKeywords = ["gfknlergerg"]
1365 3
        validate = i2.validate()
1366 3
        print(validate[2])
1367 3
        self.assertEqual(
1368
            validate[2],
1369
            [
1370
                (
1371
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1372
                    ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
1373
                    "'YanoneKaffeesatz-Regular'> --> Has designer 'gfknlergerg', but "
1374
                    "<InstallableFontsResponse>.designers has no matching designer."
1375
                )
1376
            ],
1377
        )
1378

1379
        # format
1380 3
        i2 = copy.deepcopy(installableFonts)
1381 3
        try:
1382 3
            i2.foundries[0].families[0].fonts[0].format = "abc"
1383 3
        except ValueError as e:
1384 3
            self.assertTrue("Unknown font extension: 'abc'." in str(e))
1385

1386
        # free
1387 3
        i2 = copy.deepcopy(installableFonts)
1388 3
        try:
1389 3
            i2.foundries[0].families[0].fonts[0].free = "True"
1390 3
        except ValueError as e:
1391 3
            self.assertEqual(
1392
                str(e), "Wrong data type. Is <class 'str'>, should be: <class 'bool'>."
1393
            )
1394

1395
        # name
1396 3
        i2 = copy.deepcopy(installableFonts)
1397 3
        i2.foundries[0].families[0].fonts[0].name.en = ""
1398 3
        i2.foundries[0].families[0].fonts[0].name.de = ""
1399 3
        validate = i2.validate()
1400 3
        print(validate[2])
1401 3
        self.assertEqual(
1402
            validate[2],
1403
            [
1404
                (
1405
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1406
                    ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
1407
                    "'YanoneKaffeesatz-Regular'>.name is a required attribute, "
1408
                    "but empty"
1409
                )
1410
            ],
1411
        )
1412

1413
        # pdf
1414 3
        i2 = copy.deepcopy(installableFonts)
1415 3
        try:
1416 3
            i2.foundries[0].families[0].fonts[0].pdfURL = (
1417
                "typeworldserver.com/?page=outputDataBaseFile&"
1418
                "className=TWFS_FamilyBillboards&ID=2&field=image"
1419
            )
1420 3
        except ValueError as e:
1421 3
            self.assertEqual(str(e), "Needs to start with http:// or https://")
1422

1423
        # postScriptName
1424
        # TODO: write check for postScriptName
1425 3
        i2 = copy.deepcopy(installableFonts)
1426 3
        i2.foundries[0].families[0].fonts[0].postScriptName = "YanoneKaffeesatzRegular"
1427 3
        validate = i2.validate()
1428 3
        print(validate[2])
1429 3
        self.assertEqual(validate[2], [])
1430

1431
        # protected
1432 3
        i2 = copy.deepcopy(installableFonts)
1433 3
        try:
1434 3
            i2.foundries[0].families[0].fonts[0].protected = "True"
1435 3
        except ValueError as e:
1436 3
            self.assertEqual(
1437
                str(e), "Wrong data type. Is <class 'str'>, should be: <class 'bool'>."
1438
            )
1439

1440
        # purpose
1441 3
        i2 = copy.deepcopy(installableFonts)
1442 3
        try:
1443 3
            i2.foundries[0].families[0].fonts[0].purpose = "anything"
1444 3
        except ValueError as e:
1445 3
            self.assertEqual(
1446
                str(e),
1447
                "Unknown font type: 'anything'. Possible: ['desktop', 'web', 'app']",
1448
            )
1449

1450
        # status
1451 3
        i2 = copy.deepcopy(installableFonts)
1452 3
        try:
1453 3
            i2.foundries[0].families[0].fonts[0].status = "instable"
1454 3
        except ValueError as e:
1455 3
            self.assertEqual(
1456
                str(e),
1457
                (
1458
                    "Unknown Font Status: 'instable'. Possible: ['prerelease', "
1459
                    "'trial', 'stable']"
1460
                ),
1461
            )
1462

1463
        # uniqueID
1464 3
        i2 = copy.deepcopy(installableFonts)
1465 3
        i2.foundries[0].families[0].fonts[0].uniqueID = ""
1466 3
        validate = i2.validate()
1467 3
        print(validate[2])
1468 3
        self.assertEqual(
1469
            validate[2],
1470
            [
1471
                (
1472
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1473
                    ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
1474
                    "'YanoneKaffeesatz-Regular'>.uniqueID is a required attribute, "
1475
                    "but empty"
1476
                )
1477
            ],
1478
        )
1479 3
        i2 = copy.deepcopy(installableFonts)
1480 3
        i2.foundries[0].families[0].fonts[0].uniqueID = "a" * 255
1481 3
        validate = i2.validate()
1482 3
        print(validate[2])
1483 3
        self.assertEqual(
1484
            validate[2],
1485
            [
1486
                (
1487
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1488
                    ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
1489
                    "'YanoneKaffeesatz-Regular'> --> The suggested file name is longer "
1490
                    "than 220 characters: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1491
                    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1492
                    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1493
                    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1494
                    "aaaaaaaaaaaa_1.0.otf"
1495
                )
1496
            ],
1497
        )
1498 3
        i2 = copy.deepcopy(installableFonts)
1499 3
        i2.foundries[0].families[0].fonts[0].uniqueID = "abc:def"
1500 3
        validate = i2.validate()
1501 3
        print(validate[2])
1502 3
        self.assertEqual(
1503
            validate[2],
1504
            [
1505
                (
1506
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1507
                    ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
1508
                    "'YanoneKaffeesatz-Regular'> --> .uniqueID must not contain the "
1509
                    "character ':' because it will be used for the font’s file name "
1510
                    "on disk."
1511
                )
1512
            ],
1513
        )
1514

1515
        # usedLicenses
1516 3
        i2 = copy.deepcopy(installableFonts)
1517 3
        i2.foundries[0].families[0].fonts[0].usedLicenses = []
1518 3
        validate = i2.validate()
1519 3
        print(validate[2])
1520 3
        self.assertEqual(
1521
            validate[2],
1522
            [
1523
                (
1524
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1525
                    ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
1526
                    "'YanoneKaffeesatz-Regular'>.usedLicenses is a required "
1527
                    "attribute, but empty"
1528
                )
1529
            ],
1530
        )
1531 3
        try:
1532 3
            i2.foundries[0].families[0].fonts[0].usedLicenses = ["hergerg"]
1533 3
        except ValueError as e:
1534 3
            self.assertEqual(
1535
                str(e),
1536
                (
1537
                    "Wrong data type. Is <class 'str'>, should be: "
1538
                    "<class 'typeworld.api.LicenseUsage'>."
1539
                ),
1540
            )
1541

1542
        # variableFont
1543 3
        i2 = copy.deepcopy(installableFonts)
1544 3
        try:
1545 3
            i2.foundries[0].families[0].fonts[0].variableFont = "True"
1546 3
        except ValueError as e:
1547 3
            self.assertEqual(
1548
                str(e), "Wrong data type. Is <class 'str'>, should be: <class 'bool'>."
1549
            )
1550

1551
        # versions
1552 3
        i2 = copy.deepcopy(installableFonts)
1553 3
        i2.foundries[0].families[0].versions = []
1554 3
        i2.foundries[0].families[0].fonts[0].versions = []
1555 3
        try:
1556 3
            validate = i2.validate()
1557 3
        except ValueError as e:
1558 3
            self.assertEqual(
1559
                str(e),
1560
                (
1561
                    "<Font 'YanoneKaffeesatz-Regular'> has no version information, and "
1562
                    "neither has its family <Family 'Yanone Kaffeesatz'>. Either one "
1563
                    "needs to carry version information."
1564
                ),
1565
            )
1566

1567
        # other
1568 3
        i2 = copy.deepcopy(installableFonts)
1569 3
        print(i2.foundries[0].families[0].fonts[0])
1570 3
        assert (
1571
            i2.foundries[0].families[0].fonts[0].parent == i2.foundries[0].families[0]
1572
        )
1573 3
        assert type(i2.foundries[0].families[0].fonts[0].getDesigners()) == list
1574

1575
        # filename and purpose
1576 3
        i2 = copy.deepcopy(installableFonts)
1577 3
        self.assertEqual(
1578
            i2.foundries[0]
1579
            .families[0]
1580
            .fonts[0]
1581
            .filename(i2.foundries[0].families[0].fonts[0].getVersions()[-1].number),
1582
            "yanone-kaffeesatz-regular_1.0.otf",
1583
        )
1584 3
        i2.foundries[0].families[0].fonts[0].format = ""
1585 3
        self.assertEqual(
1586
            i2.foundries[0]
1587
            .families[0]
1588
            .fonts[0]
1589
            .filename(i2.foundries[0].families[0].fonts[0].getVersions()[-1].number),
1590
            "yanone-kaffeesatz-regular_1.0",
1591
        )
1592 3
        validate = i2.validate()
1593 3
        print(validate[2])
1594 3
        self.assertEqual(
1595
            validate[2],
1596
            [
1597
                (
1598
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1599
                    ".families --> <Family 'Yanone Kaffeesatz'>.fonts --> <Font "
1600
                    "'YanoneKaffeesatz-Regular'> --> Is a desktop font (see .purpose), "
1601
                    "but has no .format value."
1602
                )
1603
            ],
1604
        )
1605

1606
        # language support
1607 3
        i2 = copy.deepcopy(installableFonts)
1608 3
        try:
1609 3
            i2.foundries[0].families[0].fonts[0].languageSupport = {"LATN": ["DEU"]}
1610 3
        except ValueError as e:
1611 3
            self.assertEqual(
1612
                str(e), "Script tag 'LATN' needs to be a four-letter lowercase tag."
1613
            )
1614 3
        i2 = copy.deepcopy(installableFonts)
1615 3
        try:
1616 3
            i2.foundries[0].families[0].fonts[0].languageSupport = {"latn": ["de"]}
1617 3
        except ValueError as e:
1618 3
            self.assertEqual(
1619
                str(e), "Language tag 'de' needs to be a three-letter uppercase tag."
1620
            )
1621

1622
        # features
1623 3
        i2 = copy.deepcopy(installableFonts)
1624 3
        try:
1625 3
            i2.foundries[0].families[0].fonts[0].features = ["aal", "liga"]
1626 3
        except ValueError as e:
1627 3
            self.assertEqual(
1628
                str(e),
1629
                "OpenType feature tag 'aal' needs to be a four-letter lowercase tag.",
1630
            )
1631

1632 3
    def test_Family(self):
1633

1634 3
        print("test_Family()")
1635

1636
        # billboardURLs
1637 3
        i2 = copy.deepcopy(installableFonts)
1638 3
        try:
1639 3
            i2.foundries[0].families[0].billboardURLs[0] = (
1640
                "typeworldserver.com/?page=outputDataBaseFile&"
1641
                "className=TWFS_FamilyBillboards&ID=2&field=image"
1642
            )
1643 3
        except ValueError as e:
1644 3
            self.assertEqual(str(e), "Needs to start with http:// or https://")
1645

1646
        # dateFirstPublished
1647 3
        i2 = copy.deepcopy(installableFonts)
1648 3
        try:
1649 3
            i2.foundries[0].families[0].dateFirstPublished = "2010-20-21"
1650 3
        except ValueError as e:
1651 3
            self.assertEqual(
1652
                str(e),
1653
                "ValueError: time data '2010-20-21' does not match format '%Y-%m-%d'",
1654
            )
1655

1656
        # description
1657
        # allowed to be empty
1658 3
        i2 = copy.deepcopy(installableFonts)
1659 3
        i2.foundries[0].families[0].description.en = ""
1660 3
        validate = i2.validate()
1661 3
        print(validate[2])
1662 3
        self.assertEqual(validate[2], [])
1663

1664
        # designers
1665 3
        i2 = copy.deepcopy(installableFonts)
1666 3
        try:
1667 3
            i2.foundries[0].families[0].designerKeywords = "yanone"
1668 3
        except ValueError as e:
1669 3
            self.assertEqual(
1670
                str(e), "Wrong data type. Is <class 'str'>, should be: <class 'list'>."
1671
            )
1672 3
        i2.foundries[0].families[0].designerKeywords = ["awieberg"]
1673 3
        validate = i2.validate()
1674 3
        print(validate[2])
1675 3
        self.assertEqual(
1676
            validate[2],
1677
            [
1678
                (
1679
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1680
                    ".families --> <Family 'Yanone Kaffeesatz'> --> Has designer "
1681
                    "'awieberg', but <InstallableFontsResponse>.designers has no "
1682
                    "matching designer."
1683
                )
1684
            ],
1685
        )
1686

1687
        # galleryURL
1688 3
        i2 = copy.deepcopy(installableFonts)
1689 3
        try:
1690 3
            i2.foundries[0].families[0].galleryURL = (
1691
                "typeworldserver.com/?page=outputDataBaseFile&"
1692
                "className=TWFS_FamilyBillboards&ID=2&field=image"
1693
            )
1694 3
        except ValueError as e:
1695 3
            self.assertEqual(str(e), "Needs to start with http:// or https://")
1696

1697
        # issueTrackerURL
1698 3
        i2 = copy.deepcopy(installableFonts)
1699 3
        try:
1700 3
            i2.foundries[0].families[0].issueTrackerURL = (
1701
                "typeworldserver.com/?page=outputDataBaseFile&"
1702
                "className=TWFS_FamilyBillboards&ID=2&field=image"
1703
            )
1704 3
        except ValueError as e:
1705 3
            self.assertEqual(str(e), "Needs to start with http:// or https://")
1706

1707
        # name
1708 3
        i2.foundries[0].families[0].name.en = ""
1709 3
        validate = i2.validate()
1710 3
        print(validate[2])
1711 3
        self.assertEqual(
1712
            validate[2],
1713
            [
1714
                (
1715
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1716
                    ".families --> <Family 'None'>.name is a required attribute, "
1717
                    "but empty"
1718
                )
1719
            ],
1720
        )
1721

1722
        # pdf
1723 3
        i2 = copy.deepcopy(installableFonts)
1724 3
        try:
1725 3
            i2.foundries[0].families[0].pdfURL = (
1726
                "typeworldserver.com/?page=outputDataBaseFile&"
1727
                "className=TWFS_FamilyBillboards&ID=2&field=image"
1728
            )
1729 3
        except ValueError as e:
1730 3
            self.assertEqual(str(e), "Needs to start with http:// or https://")
1731

1732
        # sourceURL
1733 3
        i2 = copy.deepcopy(installableFonts)
1734 3
        try:
1735 3
            i2.foundries[0].families[0].sourceURL = (
1736
                "typeworldserver.com/?page=outputDataBaseFile&"
1737
                "className=TWFS_FamilyBillboards&ID=2&field=image"
1738
            )
1739 3
        except ValueError as e:
1740 3
            self.assertEqual(str(e), "Needs to start with http:// or https://")
1741

1742
        # uniqueID
1743
        # TODO
1744

1745
        # versions
1746
        # already check at font level
1747

1748 3
        i2 = copy.deepcopy(installableFonts)
1749 3
        print(i2.foundries[0].families[0])
1750 3
        assert i2.foundries[0].families[0].parent == i2.foundries[0]
1751 3
        assert type(i2.foundries[0].families[0].getDesigners()) == list
1752 3
        assert type(i2.foundries[0].families[0].getAllDesigners()) == list
1753

1754
        # Package Names
1755 3
        i2 = copy.deepcopy(installableFonts)
1756

1757 3
        self.assertEqual(
1758
            i2.foundries[0].families[0].getPackages()[-1].name.de, "Desktop-Schriften"
1759
        )
1760 3
        self.assertEqual(
1761
            i2.foundries[0].families[0].getPackages()[-1].name.en, "Desktop Fonts"
1762
        )
1763 3
        self.assertEqual(
1764
            i2.foundries[0].families[0].getPackages()[-1].getFormats(), ["otf"]
1765
        )
1766

1767 3
        self.assertEqual(
1768
            i2.foundries[0].families[0].fonts[0].getPackageKeywords(), ["desktop"]
1769
        )
1770 3
        self.assertEqual(
1771
            i2.foundries[0].families[0].fonts[1].getPackageKeywords(),
1772
            [typeworld.api.DEFAULT],
1773
        )
1774

1775 3
    def test_Foundry(self):
1776

1777 3
        print("test_Foundry()")
1778

1779
        # description
1780
        # allowed to be empty
1781 3
        i2 = copy.deepcopy(installableFonts)
1782 3
        i2.foundries[0].description.en = ""
1783 3
        validate = i2.validate()
1784 3
        print(validate[2])
1785 3
        self.assertEqual(validate[2], [])
1786

1787
        # styling
1788 3
        i2 = copy.deepcopy(installableFonts)
1789 3
        i2.foundries[0].styling = json.loads(
1790
            """{"whatevs": {
1791
            "headerColor": "F20D5E",
1792
            "headerTextColor": "000000",
1793
            "headerLinkColor": "E5F20D",
1794

1795
            "backgroundColor": "E5F20D",
1796
            "textColor": "000000",
1797
            "linkColor": "F7AD22",
1798

1799
            "selectionColor": "0D79F2",
1800
            "selectionTextColor": "E5F20D",
1801

1802
            "buttonColor": "197AA3",
1803
            "buttonTextColor": "FFFFFF",
1804

1805
            "informationViewBackgroundColor": "469BF5",
1806
            "informationViewTextColor": "000000",
1807
            "informationViewLinkColor": "E5F20D",
1808

1809
            "informationViewButtonColor": "E5F20D",
1810
            "informationViewButtonTextColor": "000000"
1811

1812
        }, "dark": {
1813
            "headerColor": "B10947",
1814
            "headerTextColor": "000000",
1815
            "headerLinkColor": "E5F20D",
1816

1817
            "backgroundColor": "1A1A1A",
1818
            "textColor": "E5F20D",
1819
            "linkColor": "C07F07",
1820

1821
            "selectionColor": "B10947",
1822
            "selectionTextColor": "E5F20D",
1823

1824
            "buttonColor": "22A4DC",
1825
            "buttonTextColor": "000000",
1826

1827
            "informationViewBackgroundColor": "000000",
1828
            "informationViewTextColor": "999999",
1829
            "informationViewLinkColor": "E5F20D",
1830

1831
            "informationViewButtonColor": "1E90C1",
1832
            "informationViewButtonTextColor": "000000"
1833

1834
        } }"""
1835
        )
1836 3
        validate = i2.validate()
1837 3
        print(validate[2])
1838 3
        self.assertEqual(
1839
            validate[2],
1840
            [
1841
                (
1842
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1843
                    " --> Styling keyword 'whatevs' is unknown. Known are "
1844
                    "['light', 'dark']."
1845
                )
1846
            ],
1847
        )
1848

1849
        # styling
1850 3
        i2 = copy.deepcopy(installableFonts)
1851 3
        i2.foundries[0].styling = json.loads(
1852
            """{"light": {
1853
            "headerColor": "F20D5E",
1854
            "headerTextColor": "000000",
1855
            "headerLinkColor": "E5F20D",
1856

1857
            "backgroundColor": "E5F20D",
1858
            "textColor": "000000",
1859
            "linkColor": "F7AD22",
1860

1861
            "selectionColor": "0D79F2",
1862
            "selectionTextColor": "E5F20D",
1863

1864
            "buttonColor": "197AA3",
1865
            "buttonTextColor": "FFFFFF",
1866

1867
            "informationViewBackgroundColor": "469BF5",
1868
            "informationViewTextColor": "000000",
1869
            "informationViewLinkColor": "E5F20D",
1870

1871
            "informationViewButtonColor": "E5F20D",
1872
            "informationViewButtonTextColor": "000000",
1873

1874
            "logoURL": "awesomefonts.com/logo.svg"
1875

1876
        }, "dark": {
1877
            "headerColor": "B10947",
1878
            "headerTextColor": "000000",
1879
            "headerLinkColor": "E5F20D",
1880

1881
            "backgroundColor": "1A1A1A",
1882
            "textColor": "E5F20D",
1883
            "linkColor": "C07F07",
1884

1885
            "selectionColor": "B10947",
1886
            "selectionTextColor": "E5F20D",
1887

1888
            "buttonColor": "22A4DC",
1889
            "buttonTextColor": "000000",
1890

1891
            "informationViewBackgroundColor": "000000",
1892
            "informationViewTextColor": "999999",
1893
            "informationViewLinkColor": "E5F20D",
1894

1895
            "informationViewButtonColor": "1E90C1",
1896
            "informationViewButtonTextColor": "000000"
1897

1898
        } }"""
1899
        )
1900 3
        validate = i2.validate()
1901 3
        print(validate[2])
1902 3
        self.assertEqual(
1903
            validate[2],
1904
            [
1905
                (
1906
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1907
                    " --> .styling 'logoURL' attribute: Needs to start with http:// or "
1908
                    "https://"
1909
                )
1910
            ],
1911
        )
1912

1913
        # styling
1914 3
        i2 = copy.deepcopy(installableFonts)
1915 3
        i2.foundries[0].styling = json.loads(
1916
            """{"light": {
1917
            "headerColor": "F20D5I",
1918
            "headerTextColor": "000000",
1919
            "headerLinkColor": "E5F20D",
1920

1921
            "backgroundColor": "E5F20D",
1922
            "textColor": "000000",
1923
            "linkColor": "F7AD22",
1924

1925
            "selectionColor": "0D79F2",
1926
            "selectionTextColor": "E5F20D",
1927

1928
            "buttonColor": "197AA3",
1929
            "buttonTextColor": "FFFFFF",
1930

1931
            "informationViewBackgroundColor": "469BF5",
1932
            "informationViewTextColor": "000000",
1933
            "informationViewLinkColor": "E5F20D",
1934

1935
            "informationViewButtonColor": "E5F20D",
1936
            "informationViewButtonTextColor": "000000"
1937

1938
        }, "dark": {
1939
            "headerColor": "B10947",
1940
            "headerTextColor": "000000",
1941
            "headerLinkColor": "E5F20D",
1942

1943
            "backgroundColor": "1A1A1A",
1944
            "textColor": "E5F20D",
1945
            "linkColor": "C07F07",
1946

1947
            "selectionColor": "B10947",
1948
            "selectionTextColor": "E5F20D",
1949

1950
            "buttonColor": "22A4DC",
1951
            "buttonTextColor": "000000",
1952

1953
            "informationViewBackgroundColor": "000000",
1954
            "informationViewTextColor": "999999",
1955
            "informationViewLinkColor": "E5F20D",
1956

1957
            "informationViewButtonColor": "1E90C1",
1958
            "informationViewButtonTextColor": "000000"
1959

1960
        } }"""
1961
        )
1962 3
        validate = i2.validate()
1963 3
        print(validate[2])
1964 3
        self.assertEqual(
1965
            validate[2],
1966
            [
1967
                (
1968
                    "<InstallableFontsResponse>.foundries --> <Foundry 'Awesome Fonts'>"
1969
                    " --> .styling color attribute 'headerColor': Not a valid hex color"
1970
                    " of format RRGGBB (like FF0000 for red): F20D5I"
1971
                )
1972
            ],
1973
        )
1974

1975
        # email
1976 3
        i2 = copy.deepcopy(installableFonts)
1977 3
        try:
1978 3
            i2.foundries[0].email = "post_at_yanone.de"
1979 3
        except ValueError as e:
1980 3
            self.assertEqual(str(e), "Not a valid email format: post_at_yanone.de")
1981

1982
        # licenses
1983 3
        i2 = copy.deepcopy(installableFonts)
1984 3
        try:
1985 3
            i2.foundries[0].licenses = "yanoneEULA"
1986 3
        except ValueError as e:
1987 3
            self.assertEqual(
1988
                str(e), "Wrong data type. Is <class 'str'>, should be: <class 'list'>."
1989
            )
1990

1991
        # name
1992 3
        i2 = copy.deepcopy(installableFonts)
1993 3
        i2.foundries[0].name.en = ""
1994 3
        i2.foundries[0].name.de = ""
1995 3
        validate = i2.validate()
1996 3
        print(validate[2])
1997 3
        self.assertEqual(
1998
            validate[2],
1999
            [
2000
                (
2001
                    "<InstallableFontsResponse>.foundries --> <Foundry 'None'>.name"
2002
                    " is a required attribute, but empty"
2003
                )
2004
            ],
2005
        )
2006

2007
        # supportEmail
2008 3
        i2 = copy.deepcopy(installableFonts)
2009 3
        try:
2010 3
            i2.foundries[0].supportEmail = "post_at_yanone.de"
2011 3
        except ValueError as e:
2012 3
            self.assertEqual(str(e), "Not a valid email format: post_at_yanone.de")
2013

2014
        # telephone
2015 3
        i2 = copy.deepcopy(installableFonts)
2016 3
        try:
2017 3
            i2.foundries[0].telephone = "+49176123456a456"
2018 3
        except ValueError as e:
2019 3
            self.assertEqual(
2020
                str(e), "Needs to start with + and contain only numbers 0-9"
2021
            )
2022 3
        i2 = copy.deepcopy(installableFonts)
2023 3
        try:
2024 3
            i2.foundries[0].telephone = "0049176123456456"
2025 3
        except ValueError as e:
2026 3
            self.assertEqual(
2027
                str(e), "Needs to start with + and contain only numbers 0-9"
2028
            )
2029 3
        try:
2030 3
            i2.foundries[0].telephone = "a"
2031 3
        except ValueError as e:
2032 3
            self.assertEqual(
2033
                str(e), "Needs to start with + and contain only numbers 0-9"
2034
            )
2035

2036
        # socialURLs
2037 3
        self.assertEqual(
2038
            str(foundry.socialURLs),
2039
            "['https://facebook.com/pages/YanoneYanone', 'https://twitter.com/yanone']",
2040
        )
2041

2042
        # website
2043 3
        i2 = copy.deepcopy(installableFonts)
2044 3
        try:
2045 3
            i2.foundries[0].websiteURL = 1
2046 3
        except ValueError as e:
2047 3
            self.assertEqual(str(e), "Needs to start with http:// or https://")
2048

2049 3
        i2 = copy.deepcopy(installableFonts)
2050 3
        print(i2.foundries[0])
2051 3
        assert i2.foundries[0].name.getTextAndLocale("en") == ("Awesome Fonts", "en")
2052 3
        assert i2.foundries[0].name.getTextAndLocale(["en"]) == ("Awesome Fonts", "en")
2053 3
        assert i2.foundries[0].name.getTextAndLocale("de") == ("Tolle Schriften", "de")
2054 3
        assert i2.foundries[0].name.getTextAndLocale(["de"]) == (
2055
            "Tolle Schriften",
2056
            "de",
2057
        )
2058 3
        assert i2.foundries[0].name.getTextAndLocale(["ar"]) == ("Awesome Fonts", "en")
2059

2060 3
        i2 = copy.deepcopy(installableFonts)
2061 3
        i2.foundries[0].name.en = "abc" * 1000
2062 3
        validate = i2.validate()
2063 3
        print(validate[2])
2064 3
        self.assertEqual(
2065
            validate[2],
2066
            [
2067
                (
2068
                    "<InstallableFontsResponse>.foundries --> <Foundry 'abcabcabcabcabc"
2069
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2070
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2071
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2072
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2073
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2074
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2075
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2076
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2077
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2078
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2079
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2080
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2081
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2082
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2083
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2084
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2085
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2086
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2087
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2088
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2089
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2090
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2091
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2092
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2093
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2094
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2095
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2096
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2097
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2098
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2099
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2100
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2101
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2102
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2103
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2104
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2105
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2106
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2107
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2108
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2109
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2110
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2111
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2112
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2113
                    "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc"
2114
                    "abcabcabcabcabc'>.name --> <MultiLanguageText> --> Language entry "
2115
                    "'en' is too long. Allowed are 100 characters."
2116
                )
2117
            ],
2118
        )
2119

2120 3
        i2 = copy.deepcopy(installableFonts)
2121 3
        foundry2 = copy.deepcopy(i2.foundries[0])
2122 3
        i2.foundries.append(foundry2)
2123 3
        validate = i2.validate()
2124 3
        print(validate[2])
2125 3
        self.assertEqual(
2126
            validate[2],
2127
            [
2128
                (
2129
                    "<InstallableFontsResponse> --> "
2130
                    "Duplicate unique foundry IDs: ['yanone']"
2131
                ),
2132
                (
2133
                    "<InstallableFontsResponse> --> "
2134
                    "Duplicate unique family IDs: ['yanone-yanonekaffeesatz']"
2135
                ),
2136
                (
2137
                    "<InstallableFontsResponse> --> Duplicate unique family IDs: "
2138
                    "['yanone-kaffeesatz-regular', 'yanone-kaffeesatz-bold']"
2139
                ),
2140
            ],
2141
        )
2142

2143 3
    def test_otherStuff(self):
2144

2145 3
        print("test_otherStuff()")
2146

2147
        # __repr__
2148 3
        s = typeworld.api.StringDataType()
2149 3
        s.value = "a"
2150 3
        self.assertEqual(str(s), "<StringDataType 'a'>")
2151

2152 3
        assert type(root.supportedCommands.index("installableFonts")) == int
2153 3
        assert installableFonts.designers[0].parent == installableFonts
2154

2155 3
        success, message = user1.client.endpointCommand(protectedSubscription)
2156 3
        self.assertTrue(success)
2157

2158 3
        success, message = user1.client.endpointCommand(protectedSubscription[1:])
2159 3
        self.assertFalse(success)
2160 3
        self.assertEqual(message, "Unknown custom protocol, known are: ['typeworld']")
2161

2162 3
        user1.client.testScenario = "simulateInvalidAPIJSONResponse"
2163 3
        success, message = user1.client.endpointCommand(protectedSubscription)
2164 3
        self.assertFalse(success)
2165 3
        self.assertEqual(
2166
            message,
2167
            "Unknown license identifier: 'mefowefbhrf'. See https://spdx.org/licenses/",
2168
        )
2169

2170 3
        user1.client.testScenario = "simulateFaultyAPIJSONResponse"
2171 3
        success, message = user1.client.endpointCommand(protectedSubscription)
2172 3
        self.assertFalse(success)
2173
        print(message)  # nocoverage
2174 3
        self.assertTrue(
2175
            "Invalid control character at: line 9 column 47 (char 300)" in message
2176
        )
2177

2178
        # TODO: Invite user to subscription with API endpoint as source
2179

2180 3
        self.assertEqual(
2181
            typeworld.client.helpers.addAttributeToURL(
2182
                "https://type.world?hello=world#xyz", "hello=type&world=type"
2183
            ),
2184
            "https://type.world?hello=type&world=type#xyz",
2185
        )
2186

2187
        # load json/dict
2188 3
        d = {"en": "Hello World", "de": "Hallo Welt"}
2189 3
        j = json.dumps(d)
2190 3
        d1 = typeworld.api.MultiLanguageText(json=j).dumpDict()
2191 3
        d2 = typeworld.api.MultiLanguageText(dict=d).dumpDict()
2192 3
        from deepdiff import DeepDiff
2193

2194 3
        self.assertEqual(DeepDiff(d1, d2, ignore_order=True), {})
2195

2196
        # URL parsing and constructing
2197 3
        success, protocol = typeworld.client.getProtocol(freeNamedSubscription)
2198 3
        self.assertEqual(protocol.secretURL(), freeNamedSubscription)
2199 3
        self.assertEqual(protocol.unsecretURL(), freeNamedSubscription)
2200

2201 3
        self.assertEqual(
2202
            protocol.shortUnsecretURL(),
2203
            (
2204
                "typeworld://json+https//s9lWvayTEOaB9eIIMA67@"
2205
                "typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
2206
            ),
2207
        )
2208

2209 3
        self.assertEqual(
2210
            typeworld.client.URL(freeNamedSubscription).HTTPURL(),
2211
            "https://typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/",
2212
        )
2213

2214 3
        typeworld.client.helpers.Garbage(
2215
            20, uppercase=True, lowercase=True, numbers=True, punctuation=True
2216
        )
2217

2218 3
    def test_InstallFontsResponse(self):
2219

2220 3
        print("test_InstallFontsResponse()")
2221

2222 3
        installFonts = InstallFontsResponse()
2223 3
        asset = InstallFontAsset()
2224 3
        installFonts.assets.append(asset)
2225 3
        asset.uniqueID = "abc"
2226 3
        asset.response = "success"
2227 3
        asset.mimeType = "font/otf"
2228 3
        asset.version = "1.0"
2229
        # 		asset.encoding = 'base64' # missing
2230 3
        asset.data = b"ABC"
2231 3
        validate = asset.validate()
2232 3
        print(validate[2])
2233 3
        self.assertEqual(
2234
            validate[2],
2235
            ["<InstallFontAsset> --> .data is set, but .encoding is missing"],
2236
        )
2237

2238 3
        installFonts = InstallFontsResponse()
2239 3
        asset = InstallFontAsset()
2240 3
        installFonts.assets.append(asset)
2241 3
        asset.uniqueID = "abc"
2242 3
        asset.response = "success"
2243 3
        asset.encoding = "base64"
2244 3
        asset.version = "1.0"
2245
        # 		asset.mimeType = 'font/otf' # missing
2246 3
        asset.data = b"ABC"
2247 3
        validate = asset.validate()
2248 3
        print(validate[2])
2249 3
        self.assertEqual(
2250
            validate[2],
2251
            ["<InstallFontAsset> --> .data is set, but .mimeType is missing"],
2252
        )
2253

2254 3
        installFonts = InstallFontsResponse()
2255 3
        asset = InstallFontAsset()
2256 3
        installFonts.assets.append(asset)
2257 3
        asset.uniqueID = "abc"
2258 3
        asset.response = "success"
2259 3
        asset.encoding = "base64"
2260 3
        asset.version = "1.0"
2261
        # 		asset.mimeType = 'font/otf' # missing
2262 3
        asset.dataURL = "https://awesomefonts.com/font.otf"
2263 3
        validate = asset.validate()
2264 3
        print(validate[2])
2265 3
        self.assertEqual(
2266
            validate[2],
2267
            ["<InstallFontAsset> --> .dataURL is set, but .mimeType is missing"],
2268
        )
2269

2270 3
        installFonts = InstallFontsResponse()
2271 3
        asset = InstallFontAsset()
2272 3
        installFonts.assets.append(asset)
2273 3
        asset.uniqueID = "abc"
2274 3
        asset.response = "success"
2275 3
        asset.encoding = "base64"
2276 3
        asset.version = "1.0"
2277 3
        asset.mimeType = "font/otf"  # missing
2278 3
        asset.dataURL = "https://awesomefonts.com/font.otf"
2279 3
        asset.data = b"ABC"
2280 3
        validate = asset.validate()
2281 3
        print(validate[2])
2282 3
        self.assertEqual(
2283
            validate[2],
2284
            [
2285
                (
2286
                    "<InstallFontAsset> --> "
2287
                    "Either .dataURL or .data can be defined, not both"
2288
                )
2289
            ],
2290
        )
2291

2292 3
        installFonts = InstallFontsResponse()
2293 3
        asset = InstallFontAsset()
2294 3
        installFonts.assets.append(asset)
2295 3
        asset.uniqueID = "abc"
2296 3
        asset.response = "success"
2297 3
        asset.mimeType = "font/otf"
2298 3
        asset.encoding = "base64"
2299 3
        asset.version = "1.0"
2300
        # 		asset.data = b'ABC' # missing
2301 3
        validate = asset.validate()
2302 3
        print(validate[2])
2303 3
        self.assertEqual(
2304
            validate[2],
2305
            [
2306
                (
2307
                    "<InstallFontAsset> --> .response is set to success, but neither "
2308
                    ".data nor .dataURL are set."
2309
                )
2310
            ],
2311
        )
2312

2313 3
        installFonts = InstallFontsResponse()
2314 3
        asset = InstallFontAsset()
2315 3
        installFonts.assets.append(asset)
2316 3
        asset.uniqueID = "abc"
2317 3
        asset.mimeType = "font/otf"
2318 3
        asset.response = "error"
2319 3
        asset.version = "1.0"
2320 3
        validate = asset.validate()
2321 3
        print(validate[2])
2322 3
        self.assertEqual(
2323
            validate[2],
2324
            [
2325
                (
2326
                    "<InstallFontAsset> --> .response is 'error', "
2327
                    "but .errorMessage is missing."
2328
                )
2329
            ],
2330
        )
2331

2332 3
        installFonts = InstallFontsResponse()
2333 3
        asset = InstallFontAsset()
2334 3
        try:
2335 3
            asset.mimeType = "font/whatevs"
2336 3
        except ValueError as e:
2337 3
            self.assertEqual(
2338
                str(e),
2339
                (
2340
                    "Unknown font MIME Type: 'font/whatevs'. Possible: "
2341
                    "['font/collection', 'font/otf', 'font/sfnt', 'font/ttf']"
2342
                ),
2343
            )
2344

2345 3
        asset = InstallFontAsset()
2346 3
        try:
2347 3
            asset.response = "a"
2348 3
        except ValueError as e:
2349 3
            self.assertEqual(
2350
                str(e),
2351
                (
2352
                    "Unknown response type: 'a'. Possible: ['success', 'error', "
2353
                    "'unknownFont', 'insufficientPermission', "
2354
                    "'temporarilyUnavailable', 'validTypeWorldUserAccountRequired', "
2355
                    "'loginRequired', 'revealedUserIdentityRequired', "
2356
                    "'seatAllowanceReached']"
2357
                ),
2358
            )
2359

2360 3
    def test_UninstallFontsResponse(self):
2361

2362 3
        print("test_UninstallFontsResponse()")
2363

2364 3
        asset = UninstallFontAsset()
2365 3
        try:
2366 3
            asset.response = "a"
2367 3
        except ValueError as e:
2368 3
            self.assertEqual(
2369
                str(e),
2370
                (
2371
                    "Unknown response type: 'a'. Possible: ['success', 'error', "
2372
                    "'unknownFont', 'insufficientPermission', "
2373
                    "'temporarilyUnavailable', 'validTypeWorldUserAccountRequired', "
2374
                    "'loginRequired', 'unknownInstallation']"
2375
                ),
2376
            )
2377

2378 3
    def test_InstallableFontsResponse_Old(self):
2379

2380
        # DOCU
2381 3
        RootResponse().docu()
2382 3
        EndpointResponse().docu()
2383 3
        InstallableFontsResponse().docu()
2384 3
        InstallFontsResponse().docu()
2385 3
        UninstallFontsResponse().docu()
2386

2387
        # Data types
2388 3
        try:
2389 3
            BooleanDataType().put("abc")
2390 3
        except ValueError:
2391 3
            pass
2392

2393 3
        try:
2394 3
            IntegerDataType().put("abc")
2395 3
        except ValueError:
2396 3
            pass
2397

2398 3
        try:
2399 3
            FloatDataType().put("abc")
2400 3
        except ValueError:
2401 3
            pass
2402

2403 3
        FontEncodingDataType().valid()
2404 3
        try:
2405 3
            FontEncodingDataType().put("abc")
2406 3
        except ValueError:
2407 3
            pass
2408

2409 3
        try:
2410 3
            VersionDataType().put("0.1.2.3")
2411 3
        except ValueError:
2412 3
            pass
2413

2414 3
        try:
2415 3
            WebURLDataType().put("yanone.de")
2416 3
        except ValueError:
2417 3
            pass
2418

2419 3
        try:
2420 3
            EmailDataType().put("post@yanone")
2421 3
        except ValueError:
2422 3
            pass
2423

2424 3
        try:
2425 3
            HexColorDataType().put("ABCDEF")  # pass
2426 3
            HexColorDataType().put("ABCDEX")  # error
2427 3
        except ValueError:
2428 3
            pass
2429

2430 3
        try:
2431 3
            HexColorDataType().put("012345678")
2432 3
        except ValueError:
2433 3
            pass
2434

2435 3
        try:
2436 3
            DateDataType().put("2018-02-28")  # pass
2437 3
            DateDataType().put("2018-02-30")  # error
2438 3
        except ValueError:
2439 3
            pass
2440

2441 3
        text = MultiLanguageText()
2442 3
        self.assertEqual(
2443
            text.customValidation()[2], ["Needs to contain at least one language field"]
2444
        )
2445 3
        self.assertEqual(bool(text), False)
2446 3
        text.en = "Hello"
2447 3
        self.assertEqual(bool(text), True)
2448

2449 3
        text.en = "Hello"
2450 3
        self.assertEqual(text.customValidation()[2], [])
2451

2452
        # HTML in Text
2453 3
        text.en = "Hello, <b>world</b>"
2454 3
        self.assertNotEqual(text.customValidation()[2], [])
2455

2456
        # Markdown in Text
2457 3
        text.en = "Hello, _world_"
2458 3
        self.assertNotEqual(text.customValidation()[2], [])
2459

2460 3
        description = MultiLanguageLongText()
2461 3
        self.assertEqual(bool(description), False)
2462 3
        description.en = "Hello"
2463 3
        self.assertEqual(bool(description), True)
2464

2465 3
        description.en = "Hello"
2466 3
        self.assertEqual(description.customValidation()[2], [])
2467

2468
        # HTML in Text
2469 3
        description.en = "Hello, <b>world</b>"
2470 3
        self.assertNotEqual(description.customValidation()[2], [])
2471

2472
        # Markdown in Text
2473 3
        description.en = "Hello, _world_"
2474 3
        self.assertEqual(description.customValidation()[2], [])
2475

2476 3
        i2 = copy.deepcopy(installableFonts)
2477 3
        font = i2.foundries[0].families[0].fonts[0]
2478

2479
        # __repr__
2480 3
        print(font.uniqueID)
2481

2482 3
        _list = FontListProxy()
2483 3
        print(_list)
2484 3
        _list.append(Font())
2485 3
        _list[0] = Font()
2486

2487 3
        _list = font.nonListProxyBasedKeys()
2488

2489 3
        font.doesntHaveThisAttribute = "a"
2490 3
        print(font.doesntHaveThisAttribute)
2491 3
        try:
2492 3
            print(font.doesntHaveThatAttribute)
2493 3
        except AttributeError:
2494 3
            pass
2495

2496 3
        font.postScriptName = ""
2497

2498 3
        font.name = MultiLanguageText()
2499 3
        font.name.en = None
2500 3
        print(font.name.parent)
2501
        # try:
2502 3
        print(installableFonts.validate())
2503
        # except:
2504
        # 	pass
2505

2506 3
        usedLicense = LicenseUsage()
2507 3
        usedLicense.keyword = "awesomeFontsEULAAAAA"
2508 3
        font.usedLicenses.append(usedLicense)
2509
        # try:
2510 3
        print(installableFonts.validate())
2511
        # except:
2512
        # 	pass
2513

2514 3
        font.versions = []
2515 3
        font.parent.versions = []
2516
        # try:
2517 3
        print(installableFonts.validate())
2518
        # except:
2519
        # 	pass
2520

2521 3
        font.designerKeywords.append("maxx")
2522
        # try:
2523 3
        print(installableFonts.validate())
2524
        # except:
2525
        # 	pass
2526

2527 3
    def test_helpers(self):
2528

2529 3
        print("test_helpers()")
2530

2531
        # makeSemVer(a.number)
2532 3
        self.assertEqual(makeSemVer(2.1), "2.1.0")
2533

2534
        # urlIsValid()
2535 3
        self.assertEqual(
2536
            typeworld.client.urlIsValid(
2537
                (
2538
                    "typeworld://json+https//s9lWvayTEOaB9eIIMA67:bN0QnnNsaE4LfHlOMGkm"
2539
                    "@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
2540
                )
2541
            )[0],
2542
            True,
2543
        )
2544 3
        self.assertEqual(
2545
            typeworld.client.urlIsValid(
2546
                (
2547
                    "https//s9lWvayTEOaB9eIIMA67:bN0QnnNsaE4LfHlOMGkm"
2548
                    "@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
2549
                )
2550
            )[0],
2551
            False,
2552
        )
2553 3
        self.assertEqual(
2554
            typeworld.client.urlIsValid(
2555
                (
2556
                    "typeworldjson://json+https//s9lWvayTEOaB9eIIMA67:"
2557
                    "bN0QnnNsaE4LfHlOMGkm@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
2558
                )
2559
            )[0],
2560
            False,
2561
        )
2562

2563
        # splitJSONURL()
2564 3
        self.assertEqual(
2565
            typeworld.client.splitJSONURL(
2566
                (
2567
                    "typeworld://json+https//s9lWvayTEOaB9eIIMA67:bN0QnnNsaE4LfHlOMGkm"
2568
                    ":accessToken@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
2569
                )
2570
            ),
2571
            (
2572
                "typeworld://",
2573
                "json",
2574
                "https://",
2575
                "s9lWvayTEOaB9eIIMA67",
2576
                "bN0QnnNsaE4LfHlOMGkm",
2577
                "accessToken",
2578
                "typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/",
2579
            ),
2580
        )
2581 3
        self.assertEqual(
2582
            typeworld.client.splitJSONURL(
2583
                (
2584
                    "typeworld://json+https//s9lWvayTEOaB9eIIMA67@"
2585
                    "typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
2586
                )
2587
            ),
2588
            (
2589
                "typeworld://",
2590
                "json",
2591
                "https://",
2592
                "s9lWvayTEOaB9eIIMA67",
2593
                "",
2594
                "",
2595
                "typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/",
2596
            ),
2597
        )
2598 3
        self.assertEqual(
2599
            typeworld.client.splitJSONURL(
2600
                "typeworld://json+https//typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
2601
            ),
2602
            (
2603
                "typeworld://",
2604
                "json",
2605
                "https://",
2606
                "",
2607
                "",
2608
                "",
2609
                "typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/",
2610
            ),
2611
        )
2612 3
        self.assertEqual(
2613
            typeworld.client.splitJSONURL(
2614
                "typeworld://json+http//typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
2615
            ),
2616
            (
2617
                "typeworld://",
2618
                "json",
2619
                "http://",
2620
                "",
2621
                "",
2622
                "",
2623
                "typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/",
2624
            ),
2625
        )
2626

2627 3
        if ONLINE:
2628
            # Locale
2629 3
            self.assertTrue("en" in user0.client.locale())
2630 3
            user0.client.set("localizationType", "systemLocale")
2631 3
            self.assertTrue("en" in user0.client.locale())
2632 3
            user0.client.set("localizationType", "customLocale")
2633 3
            self.assertTrue("en" in user0.client.locale())
2634 3
            user0.client.set("customLocaleChoice", "de")
2635 3
            self.assertEqual(user0.client.locale(), ["de", "en"])
2636

2637 3
        from typeworld.client.helpers import addAttributeToURL
2638

2639 3
        self.assertEqual(
2640
            addAttributeToURL("https://type.world/", "hello=world"),
2641
            "https://type.world/?hello=world",
2642
        )
2643 3
        self.assertEqual(
2644
            addAttributeToURL("https://type.world/?foo=bar", "hello=world"),
2645
            "https://type.world/?foo=bar&hello=world",
2646
        )
2647

2648 3
    def test_simulateExternalScenarios(self):
2649

2650 3
        print("test_simulateExternalScenarios()")
2651

2652 3
        user0.takeDown()
2653

2654 3
        user0.client.testScenario = "simulateEndpointDoesntSupportInstallFontCommand"
2655 3
        success, message, publisher, subscription = user0.client.addSubscription(
2656
            freeSubscription
2657
        )
2658 3
        self.assertEqual(success, False)
2659

2660 3
        user0.client.testScenario = "simulateBreakingAPIVersion"
2661 3
        success, message, publisher, subscription = user0.client.addSubscription(
2662
            freeSubscription
2663
        )
2664 3
        self.assertEqual(success, False)
2665 3
        self.assertEqual(
2666
            message,
2667
            [
2668
                "#(response.appUpdateRequired)",
2669
                "#(response.appUpdateRequired.headline)",
2670
            ],
2671
        )
2672

2673
        # Supposed to pass
2674 3
        user0.client.testScenario = "simulateNonBreakingHigherAPIVersion"
2675 3
        success, message, publisher, subscription = user0.client.addSubscription(
2676
            freeSubscription
2677
        )
2678 3
        if not success:
2679 0
            print(message)
2680 3
        self.assertEqual(success, True)
2681 3
        user0.client.publishers()[0].delete()
2682

2683 3
        user0.client.testScenario = (
2684
            "simulateEndpointDoesntSupportInstallableFontsCommand"
2685
        )
2686 3
        success, message, publisher, subscription = user0.client.addSubscription(
2687
            freeSubscription
2688
        )
2689 3
        self.assertEqual(success, False)
2690

2691 3
        user0.client.testScenario = "simulateCustomError"
2692 3
        success, message, publisher, subscription = user0.client.addSubscription(
2693
            freeSubscription
2694
        )
2695 3
        self.assertEqual(success, False)
2696 3
        self.assertEqual(message.getText(), "simulateCustomError")
2697

2698 3
        user0.client.testScenario = "simulateNotOnline"
2699 3
        success, message, publisher, subscription = user0.client.addSubscription(
2700
            freeSubscription
2701
        )
2702 3
        self.assertEqual(success, False)
2703

2704 3
        user0.client.testScenario = "simulateProgrammingError"
2705 3
        success, message, publisher, subscription = user0.client.addSubscription(
2706
            freeSubscription
2707
        )
2708 3
        self.assertEqual(success, False)
2709 3
        self.assertEqual(message, "HTTP Error 500")
2710

2711 3
        success, message, publisher, subscription = user0.client.addSubscription(
2712
            (
2713
                "typeworld://unknownprotocol+"
2714
                "https//typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
2715
            )
2716
        )
2717 3
        self.assertEqual(success, False)
2718 3
        self.assertEqual(
2719
            message, "Protocol unknownprotocol doesn’t exist in this app (yet)."
2720
        )
2721

2722 3
        success, message, publisher, subscription = user0.client.addSubscription(
2723
            (
2724
                "typeworld://json+https://s9lWvayTEOaB9eIIMA67:bN0QnnNsaE4LfHlOMGkm@"
2725
                "typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/@"
2726
            )
2727
        )
2728 3
        self.assertEqual(success, False)
2729 3
        self.assertEqual(
2730
            message, "URL contains more than one @ sign, so don’t know how to parse it."
2731
        )
2732

2733 3
        success, message, publisher, subscription = user0.client.addSubscription(
2734
            (
2735
                "typeworldjson://json+https://s9lWvayTEOaB9eIIMA67:bN0QnnNsaE4LfHlOMGkm"
2736
                "@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
2737
            )
2738
        )
2739 3
        self.assertEqual(success, False)
2740 3
        self.assertEqual(message, "Unknown custom protocol, known are: ['typeworld']")
2741

2742 3
        success, message, publisher, subscription = user0.client.addSubscription(
2743
            (
2744
                "typeworldjson//json+https://s9lWvayTEOaB9eIIMA67:bN0QnnNsaE4LfHlOMGkm"
2745
                "@typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/"
2746
            )
2747
        )
2748 3
        self.assertEqual(success, False)
2749 3
        self.assertEqual(message, "Unknown custom protocol, known are: ['typeworld']")
2750

2751 3
        success, message, publisher, subscription = user0.client.addSubscription(
2752
            (
2753
                "typeworld://json+https://s9lWvayTEOaB9eIIMA67:bN0QnnNsaE4LfHlOMGkm@"
2754
                "typeworldserver.com/api/q8JZfYn9olyUvcCOiqHq/:"
2755
            )
2756
        )
2757 3
        self.assertEqual(success, False)
2758 3
        self.assertEqual(
2759
            message,
2760
            (
2761
                "URL contains more than one :// combination, "
2762
                "so don’t know how to parse it."
2763
            ),
2764
        )