practical.py (6631B)
1 """Portable demo presets and independent expected records for prepare.py.""" 2 def literal(value): 3 if isinstance(value, bool): return dict(type='bool', value=value) 4 if isinstance(value, int): return dict(type='number', value=str(value)) 5 if isinstance(value, str): return dict(type='string', value=value) 6 return dict(type='list', value=[literal(v) for v in value]) 7 8 9 def settings(): 10 defaults = { 11 'shipping': ('Shipping policies', 'v1', ['domestic', 8000, 1500], ['v1', 'v2']), 12 'events': ('Event adapters', 'vendorA', [['purchase', 'James', 2499]], ['vendorA', 'vendorB']), 13 'registration': ('Registration validator', 'main', ['James', 'james@example.org', 21, True], ['main']), 14 'migration': ('Profile migrations', 'v1', [['James', 'james@example.org']], ['v1', 'v2']), 15 } 16 result = {} 17 for name, (title, export, args, exports) in defaults.items(): 18 presets = {e: dict(arguments=[literal(v) for v in args], output='record', mode='pure') for e in exports} 19 if name == 'events': presets['vendorB']['arguments'] = [literal(['James', False, 2499])] 20 result[name] = dict(title=title, exports=exports, export=export, args=[literal(v) for v in args], 21 output='record', mode='pure', presets=presets) 22 return result 23 24 25 def cases(): 26 result = {name: [] for name in settings()} 27 def add(name, export, args, expected): 28 result[name].append((export, [literal(v) if not isinstance(v, dict) else v for v in args], expected)) 29 def error(message): return dict(status='error', message=message) 30 raw = lambda tree: dict(type='tree', value=tree) 31 for version in [1, 2]: 32 for zone, subtotal, grams in [('domestic', 8000, 1500), ('domestic', 7499, 1000), 33 ('domestic', 7500, 1001), ('domestic', 10000, 5000), ('regional', 0, 1000), ('international', 0, 1001)]: 34 free = zone == 'domestic' and subtotal >= (7500 if version == 1 else 10000) 35 base = (499 if version == 1 else 599) if zone == 'domestic' else (999 if zone == 'regional' else 1999) 36 cents = 0 if free else base + (250 if grams > 1000 else 0) 37 add('shipping', f'v{version}', [zone, subtotal, grams], dict(status='ok', policyVersion=version, destination=zone, shippingCents=cents)) 38 for args, message in [(['moon', 0, 10], 'Unknown destination.'), 39 (['domestic', 0, 0], 'Subtotal must be <=1000000 cents; grams must be 1..5000.'), 40 (['domestic', 0, 5001], 'Subtotal must be <=1000000 cents; grams must be 1..5000.'), 41 (['domestic', 1000001, 1], 'Subtotal must be <=1000000 cents; grams must be 1..5000.'), 42 (['domestic', raw('10'), 1], 'Subtotal and grams must be canonical natural numbers.'), 43 (['domestic', 0, raw('21100')], 'Subtotal and grams must be canonical natural numbers.'), 44 (['domestic', raw('200'), 1], 'Subtotal and grams must be canonical natural numbers.')]: 45 add('shipping', 'v1', args, error(message)) 46 for customer in ['James', 'λ雪🙂', '<img src=x onerror=alert(1)>']: 47 for refunded in [False, True]: 48 expected = dict(schemaVersion=1, eventType='order.refunded' if refunded else 'order.created', customer=customer, amountCents=2499) 49 add('events', 'vendorA', [[('refund' if refunded else 'purchase'), customer, 2499]], expected) 50 add('events', 'vendorB', [[customer, refunded, 2499]], expected) 51 for export, args, message in [('vendorA', [['other', 'James', 0]], 'Unknown vendor A event.'), 52 ('vendorA', [['purchase', 'James', 1000001]], 'amountCents must be a canonical natural <=1000000.'), 53 ('vendorA', [[1, 'James', 0]], 'Unknown vendor A event.'), 54 ('vendorA', [['purchase', 1, 0]], 'customer must be scalar text.'), 55 ('vendorA', [[]], 'Expected exactly three fields.'), 56 ('vendorA', [['purchase','James',0,'extra']], 'Expected exactly three fields.'), 57 ('vendorB', [['James', 2, 0]], 'refunded must be a canonical Boolean.'), 58 ('vendorB', [raw('10')], 'Expected a list.')]: 59 add('events', export, args, error(message)) 60 def report(name='', email='', age='', terms=''): 61 return dict(accepted=not any([name,email,age,terms]), nameError=name, emailError=email, ageError=age, termsError=terms) 62 for age in [18, 21, 120]: add('registration','main',['James','james@example.org',age,True], report()) 63 add('registration','main',['','bad',17,False],report('Name is required.','Email must contain @ and a dot.','Age must be from 18 to 120.','Accept the terms.')) 64 add('registration','main',['λ雪🙂','a@b.c',121,True],report(age='Age must be from 18 to 120.')) 65 add('registration','main',[1,2,raw('21100'),3],report('Name must be scalar text.','Email must be scalar text.','Age must be a canonical natural.','Accept the terms.')) 66 for version in [1,2]: 67 for name,email in [('James','james@example.org'), ('λ雪🙂',''), ('<script>','a"b@example.org')]: 68 expected = dict(schemaVersion=version,displayName=name,email=email) 69 if version==2: expected['marketingOptIn']=False 70 add('migration',f'v{version}',[[name,email]],expected) 71 for args,message in [([[]],'Expected [name email].'), ([['James']],'Expected [name email].'), 72 ([['James','email','extra']],'Expected [name email].'), ([[1,'email']],'Name and email must be scalar text.'), 73 ([raw('10')],'Expected a list.')]: add('migration',f'v{version}',args,error(message)) 74 # Malformed spines keep precise diagnostics; scalar and numeric boundaries 75 # are expectations, not outputs borrowed from the historical implementation. 76 for export in ['vendorA', 'vendorB']: 77 for tree, message in [('2010', 'Expected a list.'), ('202010', 'Expected a list.'), 78 ('20202010', 'Expected exactly three fields.')]: 79 add('events', export, [raw(tree)], error(message)) 80 for version in [1, 2]: 81 add('migration', f'v{version}', [raw('2010')], error('Expected a list.')) 82 add('migration', f'v{version}', [raw('202010')], error('Expected [name email].')) 83 add('migration', f'v{version}', [[[55296], '']], error('Name and email must be scalar text.')) 84 add('migration', f'v{version}', [[[1114112], '']], error('Name and email must be scalar text.')) 85 add('shipping', 'v1', ['moon', raw('200'), 0], error('Subtotal and grams must be canonical natural numbers.')) 86 add('events', 'vendorA', [['purchase', 'James', raw('200')]], error('amountCents must be a canonical natural <=1000000.')) 87 return result