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.
- package/frameworks/digital-outcomes-and-specialists-7/questions/services/physicalStudioBookingCost.yml +5 -1
- package/frameworks/digital-outcomes-and-specialists-7/questions/services/virtualStudioBookingCost.yml +5 -1
- package/package.json +1 -1
- package/schema_generator/validation.py +84 -56
- package/schemas/questions.json +2 -1
- package/tests/test_generate_validation_schema.py +115 -34
|
@@ -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.
|
|
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
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
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
|
-
|
|
727
|
-
|
|
735
|
+
{
|
|
736
|
+
"properties": {
|
|
737
|
+
question['id']: should_have_followup,
|
|
738
|
+
},
|
|
739
|
+
"required": [dependent_follow_up_field]
|
|
728
740
|
},
|
|
729
|
-
|
|
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)
|
package/schemas/questions.json
CHANGED
|
@@ -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(
|
|
748
|
-
|
|
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
|
|