ccs-digitalmarketplace-frameworks 3.12.17 → 3.12.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,10 @@ fields:
11
11
  price: physicalStudioBookingCostPrice
12
12
  price_unit: physicalStudioBookingCostUnit
13
13
 
14
+ limits:
15
+ price:
16
+ - greater_than_zero
17
+
14
18
  units:
15
19
  - label: an hour
16
20
  value: Hour
@@ -31,7 +35,7 @@ validations:
31
35
  message: Enter how much it costs to book a studio
32
36
  - name: not_money_format
33
37
  field: physicalStudioBookingCostPrice
34
- message: Price must be a number, without units, eg 99.95
38
+ message: Price must be a number greater than 0, without units, eg 99.95
35
39
  - name: answer_required
36
40
  field: physicalStudioBookingCostUnit
37
41
  message: You must select the unit
@@ -11,6 +11,10 @@ fields:
11
11
  price: virtualStudioBookingCostPrice
12
12
  price_unit: virtualStudioBookingCostUnit
13
13
 
14
+ limits:
15
+ price:
16
+ - greater_than_zero
17
+
14
18
  units:
15
19
  - label: an hour
16
20
  value: Hour
@@ -31,7 +35,7 @@ validations:
31
35
  message: Enter how much it costs to book a studio
32
36
  - name: not_money_format
33
37
  field: virtualStudioBookingCostPrice
34
- message: Price must be a number, without units, eg 99.95
38
+ message: Price must be a number greater than 0, without units, eg 99.95
35
39
  - name: answer_required
36
40
  field: virtualStudioBookingCostUnit
37
41
  message: You must select the unit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccs-digitalmarketplace-frameworks",
3
- "version": "3.12.17",
3
+ "version": "3.12.20",
4
4
  "description": "Data files for Digital Marketplace’s procurement frameworks",
5
5
  "repository": "git@github.com:Crown-Commercial-Service/ccs-digitalmarketplace-frameworks",
6
6
  "author": "enquiries@digitalmarketplace.service.gov.uk"
@@ -351,7 +351,7 @@ def drop_non_schema_questions(questions):
351
351
  questions.pop(key, None)
352
352
 
353
353
 
354
- def text_property(question):
354
+ def text_property(question, _dependent_follow_up_map):
355
355
  data = {
356
356
  "type": "string",
357
357
  "minLength": 0 if question.get('optional') else 1,
@@ -366,11 +366,11 @@ def text_property(question):
366
366
  return {question['id']: data}
367
367
 
368
368
 
369
- def date_property(question):
369
+ def date_property(question, _dependent_follow_up_map):
370
370
  return {question['id']: {"type": "string", "format": "date"}}
371
371
 
372
372
 
373
- def uri_property(question):
373
+ def uri_property(question, _dependent_follow_up_map):
374
374
  return {question['id']: {
375
375
  "type": "string",
376
376
  "format": "uri",
@@ -381,12 +381,12 @@ def has_dependent_follow_up(property_func):
381
381
  """
382
382
  Adds the additional dependent follow up schema to the property
383
383
  """
384
- def inner_func(question):
385
- schema = property_func(question)
384
+ def inner_func(question, dependent_follow_up_map):
385
+ schema = property_func(question, dependent_follow_up_map)
386
386
  schema_addition = {}
387
387
 
388
388
  if question.get('dependent_follow_up'):
389
- schema_addition = _dependent_follow_up(question)
389
+ schema_addition = _dependent_follow_up(question, dependent_follow_up_map)
390
390
 
391
391
  return schema, schema_addition
392
392
 
@@ -394,7 +394,7 @@ def has_dependent_follow_up(property_func):
394
394
 
395
395
 
396
396
  @has_dependent_follow_up
397
- def checkbox_property(question):
397
+ def checkbox_property(question, _dependent_follow_up_map):
398
398
  """
399
399
  Convert a checkbox question into JSON Schema.
400
400
  """
@@ -414,7 +414,7 @@ def checkbox_property(question):
414
414
  }
415
415
 
416
416
 
417
- def checkbox_tree_property(question):
417
+ def checkbox_tree_property(question, _dependent_follow_up_map):
418
418
  """
419
419
  Convert a checkbox tree question into JSON Schema by flattening the tree structure. Only leaf
420
420
  nodes can be selected.
@@ -447,7 +447,7 @@ def checkbox_tree_property(question):
447
447
  return schema_fragment
448
448
 
449
449
 
450
- def radios_property(question):
450
+ def radios_property(question, _dependent_follow_up_map):
451
451
  return {question['id']: {
452
452
  "enum": [
453
453
  option.get('value', option['label'])
@@ -456,14 +456,14 @@ def radios_property(question):
456
456
  }}
457
457
 
458
458
 
459
- def boolean_property(question):
459
+ def boolean_property(question, _dependent_follow_up_map):
460
460
  data = {"type": "boolean"}
461
461
  if question.get("required_value") is not None:
462
462
  data = {"enum": [question["required_value"]]}
463
463
  return {question['id']: data}
464
464
 
465
465
 
466
- def list_property(question):
466
+ def list_property(question, _dependent_follow_up_map):
467
467
  items = {
468
468
  "type": "string",
469
469
  "maxLength": 100,
@@ -480,7 +480,7 @@ def list_property(question):
480
480
  }}
481
481
 
482
482
 
483
- def boolean_list_property(question):
483
+ def boolean_list_property(question, _dependent_follow_up_map):
484
484
  return {question['id']: {
485
485
  "type": "array",
486
486
  "minItems": 0 if question.get('optional') else 1,
@@ -491,7 +491,7 @@ def boolean_list_property(question):
491
491
  }}
492
492
 
493
493
 
494
- def price_string(optional, decimal_place_restriction=False, digit_limit=None):
494
+ def price_string(optional, decimal_place_restriction=False, digit_limit=None, limits=None):
495
495
  if digit_limit is None:
496
496
  digit_limit = 15
497
497
 
@@ -504,25 +504,34 @@ def price_string(optional, decimal_place_restriction=False, digit_limit=None):
504
504
  pattern = restricted_pattern
505
505
  if optional:
506
506
  pattern = r"^$|" + pattern
507
+ if limits:
508
+ for limit in limits:
509
+ if limit == 'greater_than_zero':
510
+ pattern = '^(?!0(?:\\.0+)?$)' + pattern.split('^')[1]
507
511
  return {
508
512
  "type": "string",
509
513
  "pattern": pattern,
510
514
  }
511
515
 
512
516
 
513
- def pricing_property(question):
517
+ def pricing_property(question, _dependent_follow_up_map):
514
518
  pricing = {}
515
519
  for price_key in ['price', 'minimum_price', 'maximum_price']:
516
520
  if price_key in question.fields:
517
521
  digit_limit = None
522
+ limits = []
518
523
 
519
524
  if 'digit_limits' in question:
520
525
  digit_limit = question['digit_limits'].get(price_key)
521
526
 
527
+ if 'limits' in question:
528
+ limits = question['limits'].get(price_key)
529
+
522
530
  pricing[question.fields[price_key]] = price_string(
523
531
  price_key in question.get('optional_fields', []),
524
532
  question.decimal_place_restriction,
525
- digit_limit=digit_limit
533
+ digit_limit=digit_limit,
534
+ limits=limits,
526
535
  )
527
536
 
528
537
  if 'price_unit' in question.fields:
@@ -589,7 +598,7 @@ def pricing_property(question):
589
598
  return pricing
590
599
 
591
600
 
592
- def number_property(question):
601
+ def number_property(question, _dependent_follow_up_map):
593
602
  limits = question.get('limits', {})
594
603
  output = {question['id']: {
595
604
  "minimum": limits.get('min_value') or 0,
@@ -601,15 +610,15 @@ def number_property(question):
601
610
  return output
602
611
 
603
612
 
604
- def multiquestion(question):
613
+ def multiquestion(question, dependent_follow_up_map):
605
614
  """
606
615
  Moves subquestions of multiquestions into fully fledged questions.
607
616
  """
608
617
 
609
618
  if question._data['type'] == 'dynamic_list':
610
- return _dynamic_list(question)
619
+ return _dynamic_list(question, dependent_follow_up_map)
611
620
  else:
612
- property_schema, schema_addition = _flat_multiquestion(question)
621
+ property_schema, schema_addition = _flat_multiquestion(question, dependent_follow_up_map)
613
622
  required_fields = _flat_multiquestion_required(question)
614
623
  if required_fields:
615
624
  required_schema = {"required": _flat_multiquestion_required(question)}
@@ -618,19 +627,19 @@ def multiquestion(question):
618
627
  return property_schema, schema_addition
619
628
 
620
629
 
621
- def list_multiquestion(question):
630
+ def list_multiquestion(question, dependent_follow_up_map):
622
631
  return {
623
632
  question['id']: {
624
633
  "type": "array",
625
634
  "minItems": 1,
626
635
  "maxItems": question.number_of_items,
627
636
  "uniqueItems": True,
628
- "items": _nested_multiquestion(question)[question['id']]
637
+ "items": _nested_multiquestion(question, dependent_follow_up_map)[question['id']]
629
638
  }
630
639
  }
631
640
 
632
641
 
633
- def _dynamic_list(question):
642
+ def _dynamic_list(question, dependent_follow_up_map):
634
643
  # Some dynamic list questions use `{{ item }}` in temlate fields.
635
644
  # This call to filter ensures that `item` is in the template context
636
645
  # (yes, filter() can mutate objects, go figure).
@@ -641,7 +650,7 @@ def _dynamic_list(question):
641
650
  question['id']: {
642
651
  "type": "array",
643
652
  "minItems": 0 if question.get('optional') else 1,
644
- "items": _nested_multiquestion(question)[question['id']]
653
+ "items": _nested_multiquestion(question, dependent_follow_up_map)[question['id']]
645
654
  }
646
655
  }
647
656
 
@@ -695,49 +704,50 @@ def _followup(question, root):
695
704
  return {'allOf': schemas}
696
705
 
697
706
 
698
- def _dependent_follow_up(question):
707
+ def _dependent_follow_up(question, dependent_follow_up_map):
699
708
  schemas = []
700
709
  question_values = [
701
710
  option.get('value', option['label'])
702
711
  for option in question.options
703
712
  ]
704
713
  for dependent_follow_up_field, values in question["dependent_follow_up"].items():
705
- non_dependent_question_values = [
706
- value
707
- for value in question_values
708
- if value not in values
709
- ]
710
- should_not_have_followup = {
711
- "items": {"enum": non_dependent_question_values}
712
- }
713
- should_have_followup = {
714
- "not": {"items": {"enum": non_dependent_question_values}}
715
- }
714
+ if dependent_follow_up_field in dependent_follow_up_map.get(question.id, []):
715
+ non_dependent_question_values = [
716
+ value
717
+ for value in question_values
718
+ if value not in values
719
+ ]
720
+ should_not_have_followup = {
721
+ "items": {"enum": non_dependent_question_values}
722
+ }
723
+ should_have_followup = {
724
+ "not": {"items": {"enum": non_dependent_question_values}}
725
+ }
716
726
 
717
- schemas.append({
718
- 'oneOf': [
719
- {
720
- "properties": {
721
- question['id']: should_not_have_followup,
722
- dependent_follow_up_field: {"type": "null"}
727
+ schemas.append({
728
+ 'oneOf': [
729
+ {
730
+ "properties": {
731
+ question['id']: should_not_have_followup,
732
+ dependent_follow_up_field: {"type": "null"}
733
+ },
723
734
  },
724
- },
725
- {
726
- "properties": {
727
- question['id']: should_have_followup,
735
+ {
736
+ "properties": {
737
+ question['id']: should_have_followup,
738
+ },
739
+ "required": [dependent_follow_up_field]
728
740
  },
729
- "required": [dependent_follow_up_field]
730
- },
731
- ]
732
- })
741
+ ]
742
+ })
733
743
 
734
744
  return {'allOf': schemas}
735
745
 
736
746
 
737
- def _flat_multiquestion(question):
747
+ def _flat_multiquestion(question, dependent_follow_up_map):
738
748
  properties = {}
739
749
  for nested_question in question.questions:
740
- build_question_properties_results = build_question_properties(nested_question)
750
+ build_question_properties_results = build_question_properties(nested_question, dependent_follow_up_map)
741
751
 
742
752
  if isinstance(build_question_properties_results, dict):
743
753
  build_question_properties_results = (build_question_properties_results,)
@@ -758,8 +768,8 @@ def _flat_multiquestion(question):
758
768
  return properties, schema_addition
759
769
 
760
770
 
761
- def _nested_multiquestion(question):
762
- properties, schema_addition = _flat_multiquestion(question)
771
+ def _nested_multiquestion(question, dependent_follow_up_map):
772
+ properties, schema_addition = _flat_multiquestion(question, dependent_follow_up_map)
763
773
 
764
774
  object_schema = merge_schemas({
765
775
  "type": "object",
@@ -932,8 +942,8 @@ def add_assurance(value_schema, assurance_approach):
932
942
  }
933
943
 
934
944
 
935
- def build_question_properties(question):
936
- question_data = QUESTION_TYPES[question['type']](question)
945
+ def build_question_properties(question, dependent_follow_up_map):
946
+ question_data = QUESTION_TYPES[question['type']](question, dependent_follow_up_map)
937
947
  if question.get('assuranceApproach'):
938
948
  if isinstance(question_data, tuple):
939
949
  for key, value_schema in question_data[0].items():
@@ -953,8 +963,10 @@ def build_any_of(any_of, fields):
953
963
 
954
964
 
955
965
  def build_schema_properties(schema, questions):
966
+ dependent_follow_up_map = _generate_dependent_follow_up_map(questions)
967
+
956
968
  for question in questions.values():
957
- property_schema = build_question_properties(question)
969
+ property_schema = build_question_properties(question, dependent_follow_up_map)
958
970
  if isinstance(property_schema, tuple):
959
971
  property_schema, schema_addition = property_schema
960
972
  schema['properties'].update(property_schema)
@@ -1011,6 +1023,22 @@ def _multiquestion_dependencies(questions):
1011
1023
  return {'dependencies': dependencies} if dependencies else {}
1012
1024
 
1013
1025
 
1026
+ def _generate_dependent_follow_up_map(questions):
1027
+ dependent_follow_up_map = {}
1028
+
1029
+ for question_id, question in questions.items():
1030
+ if question.get('dependent_follow_up'):
1031
+ dependent_follow_up_map[question_id] = list(question["dependent_follow_up"].keys())
1032
+
1033
+ for dependent_question_id, dependent_question_list in dependent_follow_up_map.items():
1034
+ for question_id, question_list in dependent_follow_up_map.items():
1035
+ if dependent_question_id != question_id and dependent_question_id in question_list:
1036
+ for q_id in dependent_question_list:
1037
+ question_list.remove(q_id)
1038
+
1039
+ return dependent_follow_up_map
1040
+
1041
+
1014
1042
  def _generate_question_schema(schema_type, schema_name, framework_slug, lot_slug):
1015
1043
  questions = load_questions(schema_type, framework_slug, lot_slug)
1016
1044
  drop_non_schema_questions(questions)
@@ -303,7 +303,8 @@
303
303
  "format": {"type": "string"},
304
304
  "max_value": {"type": "number"},
305
305
  "min_value": {"type": "number"},
306
- "integer_only": {"type": "boolean"}
306
+ "integer_only": {"type": "boolean"},
307
+ "price": {"type": "array"}
307
308
  }
308
309
  },
309
310
  "empty_message": {
@@ -119,25 +119,25 @@ def test_empty_schema():
119
119
 
120
120
  @given(st.text())
121
121
  def test_text_property(id_name):
122
- result = text_property({"id": id_name})
122
+ result = text_property({"id": id_name}, {})
123
123
  assert result == {id_name: {"type": "string", "minLength": 1}}
124
124
 
125
125
 
126
126
  @given(st.text(), st.booleans())
127
127
  def test_optional_text_property(id_name, optional):
128
- result = text_property({"id": id_name, "optional": optional})
128
+ result = text_property({"id": id_name, "optional": optional}, {})
129
129
  assert result == {id_name: {"type": "string", "minLength": 0 if optional else 1}}
130
130
 
131
131
 
132
132
  @given(st.text())
133
133
  def test_text_property_format(id_name):
134
- result = text_property({"id": id_name, "limits": {"format": "email"}})
134
+ result = text_property({"id": id_name, "limits": {"format": "email"}}, {})
135
135
  assert result == {id_name: {"type": "string", "format": "email", "minLength": 1}}
136
136
 
137
137
 
138
138
  @given(st.text())
139
139
  def test_uri_property(id_name):
140
- result = uri_property({"id": id_name})
140
+ result = uri_property({"id": id_name}, {})
141
141
  assert result == {id_name: {
142
142
  "type": "string",
143
143
  "format": "uri"}}
@@ -275,12 +275,12 @@ def test_checkbox_property(id, options):
275
275
  "id": id,
276
276
  "options": options
277
277
  }
278
- result, additional_schema = checkbox_property(question)
278
+ result, additional_schema = checkbox_property(question, {})
279
279
  assert result[id]['minItems'] == 1
280
280
  assert additional_schema == {}
281
281
 
282
282
  question['optional'] = True
283
- result, additional_schema = checkbox_property(question)
283
+ result, additional_schema = checkbox_property(question, {})
284
284
  assert result[id]['minItems'] == 0
285
285
  assert additional_schema == {}
286
286
 
@@ -316,7 +316,7 @@ def test_checkbox_property_with_dependent_follow_up():
316
316
  ]
317
317
  }
318
318
  })
319
- result, additional_schema = checkbox_property(question)
319
+ result, additional_schema = checkbox_property(question, {"q1": ["a", "b", "c"]})
320
320
  assert result['q1']['minItems'] == 1
321
321
 
322
322
  enum = result['q1']['items']['enum']
@@ -374,6 +374,60 @@ def test_checkbox_property_with_dependent_follow_up():
374
374
  }
375
375
 
376
376
 
377
+ def test_checkbox_property_with_dependent_follow_up_with_map():
378
+ options = [
379
+ {"value": "1", "label": "One"},
380
+ {"label": "Two"},
381
+ {"value": "3", "label": "Three"}
382
+ ]
383
+
384
+ question = ContentQuestion({
385
+ "id": "q1",
386
+ "options": options,
387
+ "dependent_follow_up": {
388
+ "a": [
389
+ "1",
390
+ "Two"
391
+ ],
392
+ "b": [
393
+ "Two",
394
+ ],
395
+ "c": [
396
+ "1",
397
+ "3"
398
+ ]
399
+ }
400
+ })
401
+ result, additional_schema = checkbox_property(question, {"q1": ["b"]})
402
+ assert result['q1']['minItems'] == 1
403
+
404
+ enum = result['q1']['items']['enum']
405
+ assert len(enum) == len(options)
406
+ assert all(option['value'] in enum
407
+ for option in options if 'value' in option)
408
+ assert all(option['label'] in enum
409
+ for option in options if 'value' not in option)
410
+
411
+ assert additional_schema == {
412
+ "allOf": [
413
+ {
414
+ "oneOf": [
415
+ {
416
+ "properties": {
417
+ "q1": {"items": {"enum": ["1", "3"]}},
418
+ "b": {"type": "null"},
419
+ }
420
+ },
421
+ {
422
+ "properties": {"q1": {"not": {"items": {"enum": ["1", "3"]}}}},
423
+ "required": ["b"],
424
+ },
425
+ ]
426
+ },
427
+ ]
428
+ }
429
+
430
+
377
431
  @settings(suppress_health_check=[HealthCheck.too_slow])
378
432
  @given(st.text(), nested_checkboxes_list(), st.integers(min_value=1))
379
433
  def test_checkbox_tree_property(id, options, number_of_items):
@@ -384,11 +438,11 @@ def test_checkbox_tree_property(id, options, number_of_items):
384
438
  "options": options,
385
439
  "number_of_items": number_of_items
386
440
  }
387
- result = checkbox_tree_property(question)
441
+ result = checkbox_tree_property(question, {})
388
442
  assert result[id]['minItems'] == 1
389
443
 
390
444
  question['optional'] = True
391
- result = checkbox_tree_property(question)
445
+ result = checkbox_tree_property(question, {})
392
446
  assert result[id]['minItems'] == 0
393
447
  assert result[id]['maxItems'] == number_of_items
394
448
 
@@ -410,7 +464,7 @@ def test_checkbox_tree_property(id, options, number_of_items):
410
464
  @given(st.text(), st.lists(radios()))
411
465
  def test_radios_property(id, options):
412
466
  question = {"id": id, "options": options}
413
- result = radios_property(question)
467
+ result = radios_property(question, {})
414
468
  assert isinstance(result, dict)
415
469
  assert id in result.keys()
416
470
  enum = result[id]['enum']
@@ -427,7 +481,7 @@ def test_list_property(id):
427
481
  'id': id,
428
482
  'optional': True
429
483
  }
430
- result = list_property(question)
484
+ result = list_property(question, {})
431
485
  assert id in result.keys()
432
486
  assert result[id]['minItems'] == 0
433
487
  assert result[id]['items'] == {
@@ -441,7 +495,7 @@ def test_boolean_property():
441
495
  question = {
442
496
  'id': "Test",
443
497
  }
444
- result = boolean_property(question)
498
+ result = boolean_property(question, {})
445
499
  assert result["Test"]["type"] == "boolean"
446
500
  assert "enum" not in result["Test"]
447
501
 
@@ -452,7 +506,7 @@ def test_boolean_property_with_required_value(boolean):
452
506
  'id': "Test",
453
507
  'required_value': boolean
454
508
  }
455
- result = boolean_property(question)
509
+ result = boolean_property(question, {})
456
510
  assert result["Test"]["enum"] == [boolean]
457
511
  assert "type" not in result["Test"]
458
512
 
@@ -463,7 +517,7 @@ def test_boolean_list_property(id):
463
517
  'id': id,
464
518
  'optional': True
465
519
  }
466
- result = boolean_list_property(question)
520
+ result = boolean_list_property(question, {})
467
521
  assert id in result.keys()
468
522
  assert result[id]['minItems'] == 0
469
523
 
@@ -524,7 +578,7 @@ def test_pricing_property_minmax_price():
524
578
  }
525
579
  }
526
580
  cq = ContentQuestion(manifest)
527
- result = pricing_property(cq)
581
+ result = pricing_property(cq, {})
528
582
  assert not result['priceMin']['pattern'].startswith("^$|")
529
583
  assert not result['priceMax']['pattern'].startswith("^$|")
530
584
 
@@ -543,7 +597,7 @@ def test_pricing_property_minmax_price_optional():
543
597
  ]
544
598
  }
545
599
  cq = ContentQuestion(manifest)
546
- result = pricing_property(cq)
600
+ result = pricing_property(cq, {})
547
601
  assert result['priceMin']['pattern'].startswith("^$|")
548
602
  assert result['priceMax']['pattern'].startswith("^$|")
549
603
 
@@ -558,7 +612,7 @@ def test_pricing_property_price_unit_and_interval():
558
612
  }
559
613
  }
560
614
  cq = ContentQuestion(manifest)
561
- result = pricing_property(cq)
615
+ result = pricing_property(cq, {})
562
616
  assert result['priceUnit']['enum'] == [
563
617
  "Unit",
564
618
  "Person",
@@ -600,7 +654,7 @@ def test_pricing_property_price_unit_and_interval_optional():
600
654
  ]
601
655
  }
602
656
  cq = ContentQuestion(manifest)
603
- result = pricing_property(cq)
657
+ result = pricing_property(cq, {})
604
658
  assert "" in result['priceUnit']['enum']
605
659
  assert "" in result['priceInterval']['enum']
606
660
 
@@ -614,7 +668,7 @@ def test_hours_for_price():
614
668
  }
615
669
  }
616
670
  cq = ContentQuestion(manifest)
617
- result = pricing_property(cq)
671
+ result = pricing_property(cq, {})
618
672
  assert "enum" in result['pfh'].keys()
619
673
 
620
674
 
@@ -631,7 +685,7 @@ def test_pricing_property_digit_limits():
631
685
  }
632
686
  }
633
687
  cq = ContentQuestion(manifest)
634
- result = pricing_property(cq)
688
+ result = pricing_property(cq, {})
635
689
 
636
690
  assert '{1,15}' in result['priceMin']['pattern']
637
691
  assert '{1,5}' in result['priceMax']['pattern']
@@ -651,7 +705,7 @@ def test_pricing_property_digit_limits_decimal_place_restriction():
651
705
  }
652
706
  }
653
707
  cq = ContentQuestion(manifest)
654
- result = pricing_property(cq)
708
+ result = pricing_property(cq, {})
655
709
 
656
710
  assert '{1,4}' in result['priceMin']['pattern']
657
711
  assert '{1,14}' in result['priceMax']['pattern']
@@ -666,7 +720,7 @@ def test_pricing_property_price():
666
720
  }
667
721
  }
668
722
  cq = ContentQuestion(manifest)
669
- result = pricing_property(cq)
723
+ result = pricing_property(cq, {})
670
724
  assert not result['price']['pattern'].startswith("^$|")
671
725
 
672
726
 
@@ -693,7 +747,7 @@ def test_pricing_property_custom_units():
693
747
  ]
694
748
  }
695
749
  cq = ContentQuestion(manifest)
696
- result = pricing_property(cq)
750
+ result = pricing_property(cq, {})
697
751
  assert result['priceUnit']['enum'] == [
698
752
  "Option 1",
699
753
  "option-2",
@@ -723,7 +777,7 @@ def test_pricing_property_custom_intervals():
723
777
  ]
724
778
  }
725
779
  cq = ContentQuestion(manifest)
726
- result = pricing_property(cq)
780
+ result = pricing_property(cq, {})
727
781
  assert result['priceInterval']['enum'] == [
728
782
  "option-1",
729
783
  "Option 2",
@@ -731,9 +785,29 @@ def test_pricing_property_custom_intervals():
731
785
  ]
732
786
 
733
787
 
788
+ def test_pricing_property_limits_greater_than_zero():
789
+ manifest = {
790
+ "id": "Test",
791
+ "type": "pricing",
792
+ "fields": {
793
+ "price": "price",
794
+ },
795
+ "limits": {
796
+ "price": [
797
+ "greater_than_zero"
798
+ ]
799
+ }
800
+ }
801
+
802
+ cq = ContentQuestion(manifest)
803
+ result = pricing_property(cq, {})
804
+
805
+ assert '(?!0(?:\\.0+)?$)' in result['price']['pattern']
806
+
807
+
734
808
  @given(st.text())
735
809
  def test_number_property(id):
736
- actual = number_property({'id': id, 'type': 'number'})
810
+ actual = number_property({'id': id, 'type': 'number'}, {})
737
811
  expected = {id: {
738
812
  "exclusiveMaximum": 100,
739
813
  "minimum": 0,
@@ -744,9 +818,16 @@ def test_number_property(id):
744
818
 
745
819
  @given(st.integers(), st.integers(), st.booleans())
746
820
  def test_number_property_limits(max_value, min_value, integer_only):
747
- actual = number_property({'id': 'number-question', 'type': 'number', 'limits': {
748
- 'max_value': max_value, 'min_value': min_value, 'integer_only': integer_only
749
- }})
821
+ actual = number_property(
822
+ {
823
+ 'id': 'number-question',
824
+ 'type': 'number',
825
+ 'limits': {
826
+ 'max_value': max_value, 'min_value': min_value, 'integer_only': integer_only
827
+ }
828
+ },
829
+ {}
830
+ )
750
831
  expected = {"number-question": {
751
832
  "minimum": min_value,
752
833
  "type": "integer" if integer_only else "number"
@@ -774,7 +855,7 @@ def test_multiquestion():
774
855
  ]
775
856
  })
776
857
 
777
- result, schema_addition = multiquestion(question)
858
+ result, schema_addition = multiquestion(question, {})
778
859
  assert 'subquestion1' in result.keys()
779
860
  assert 'subquestion2' in result.keys()
780
861
 
@@ -801,7 +882,7 @@ def test_dynamic_list():
801
882
  ]
802
883
  })
803
884
 
804
- result = multiquestion(question)
885
+ result = multiquestion(question, {})
805
886
 
806
887
  assert set(result["q1"]["items"]["properties"].keys()) \
807
888
  == {"q1a", "q1b"}
@@ -833,7 +914,7 @@ def test_dynamic_list_with_dynamic_content():
833
914
  ]
834
915
  })
835
916
 
836
- result = multiquestion(question)
917
+ result = multiquestion(question, {})
837
918
 
838
919
  assert set(result["q"]["items"]["properties"].keys()) \
839
920
  == {"q1"}
@@ -868,7 +949,7 @@ def test_list_multiquestion():
868
949
  ]
869
950
  })
870
951
 
871
- result = list_multiquestion(question)
952
+ result = list_multiquestion(question, {})
872
953
 
873
954
  assert set(result["example"]["items"]["properties"].keys()) == {"example2", "example3"}
874
955
 
@@ -903,7 +984,7 @@ def test_followup():
903
984
  ]
904
985
  })
905
986
 
906
- result, schema_addition = multiquestion(question)
987
+ result, schema_addition = multiquestion(question, {})
907
988
  assert 'subquestion1' in result.keys()
908
989
  assert 'subquestion2' in result.keys()
909
990
 
@@ -949,7 +1030,7 @@ def test_checkboxes_followup():
949
1030
  ]
950
1031
  })
951
1032
 
952
- result, schema_addition = multiquestion(question)
1033
+ result, schema_addition = multiquestion(question, {})
953
1034
  assert 'subquestion1' in result.keys()
954
1035
  assert 'subquestion2' in result.keys()
955
1036