diff --git a/src/registry_schemas/example_data/__init__.py b/src/registry_schemas/example_data/__init__.py index 8d0686e..72c0877 100644 --- a/src/registry_schemas/example_data/__init__.py +++ b/src/registry_schemas/example_data/__init__.py @@ -43,6 +43,8 @@ INCORPORATION_FILING_TEMPLATE, SPECIAL_RESOLUTION, STUB_FILING, + TRANSITION, + TRANSITION_FILING_TEMPLATE, VOLUNTARY_DISSOLUTION, ) @@ -73,5 +75,7 @@ 'ALTERATION', 'ALTERATION_FILING_TEMPLATE', 'CONVERSION', - 'CONVERSION_FILING_TEMPLATE' + 'CONVERSION_FILING_TEMPLATE', + 'TRANSITION', + 'TRANSITION_FILING_TEMPLATE' ] diff --git a/src/registry_schemas/example_data/schema_data.py b/src/registry_schemas/example_data/schema_data.py index fdfc616..84b802c 100644 --- a/src/registry_schemas/example_data/schema_data.py +++ b/src/registry_schemas/example_data/schema_data.py @@ -1314,6 +1314,154 @@ } } +TRANSITION = { + 'nameTranslations': {'new': ['ABC Ltd.', 'Financière de l’Odet', 'Société Générale']}, + 'hasProvisions': False, + 'offices': { + 'registeredOffice': { + 'deliveryAddress': { + 'streetAddress': 'delivery_address - address line one', + 'addressCity': 'delivery_address city', + 'addressCountry': 'CA', + 'postalCode': 'H0H0H0', + 'addressRegion': 'BC' + }, + 'mailingAddress': { + 'streetAddress': 'mailing_address - address line one', + 'addressCity': 'mailing_address city', + 'addressCountry': 'CA', + 'postalCode': 'H0H0H0', + 'addressRegion': 'BC', + } + }, + 'recordsOffice': { + 'deliveryAddress': { + 'streetAddress': 'delivery_address - address line one', + 'addressCity': 'delivery_address city', + 'addressCountry': 'CA', + 'postalCode': 'H0H0H0', + 'addressRegion': 'BC' + }, + 'mailingAddress': { + 'streetAddress': 'mailing_address - address line one', + 'addressCity': 'mailing_address city', + 'addressCountry': 'CA', + 'postalCode': 'H0H0H0', + 'addressRegion': 'BC', + } + } + }, + 'parties': [ + { + 'officer': { + 'id': 1, + 'firstName': 'Joe', + 'lastName': 'Swanson', + 'middleName': 'P', + 'email': 'joe@email.com', + 'orgName': '', + 'partyType': 'person' + }, + 'mailingAddress': { + 'streetAddress': 'mailing_address - address line one', + 'streetAddressAdditional': '', + 'addressCity': 'mailing_address city', + 'addressCountry': 'CA', + 'postalCode': 'H0H0H0', + 'addressRegion': 'BC' + }, + 'deliveryAddress': { + 'streetAddress': 'delivery_address - address line one', + 'streetAddressAdditional': '', + 'addressCity': 'delivery_address city', + 'addressCountry': 'CA', + 'postalCode': 'H0H0H0', + 'addressRegion': 'BC' + }, + 'roles': [ + { + 'roleType': 'Director', + 'appointmentDate': '2018-01-01' + + } + ] + }, + { + 'officer': { + 'id': 2, + 'firstName': '', + 'lastName': '', + 'middleName': '', + 'orgName': 'Xyz Inc.', + 'partyType': 'org' + }, + 'mailingAddress': { + 'streetAddress': 'mailing_address - address line one', + 'streetAddressAdditional': '', + 'addressCity': 'mailing_address city', + 'addressCountry': 'CA', + 'postalCode': 'H0H0H0', + 'addressRegion': 'BC' + }, + 'roles': [ + { + 'roleType': 'Director', + 'appointmentDate': '2018-01-01' + } + ] + } + ], + 'shareStructure': { + 'resolutionDates': ['2020-05-23', '2020-06-01'], + 'shareClasses': [ + { + 'id': 1, + 'name': 'Share Class 1', + 'priority': 1, + 'hasMaximumShares': True, + 'maxNumberOfShares': 100, + 'hasParValue': True, + 'parValue': 10, + 'currency': 'CAD', + 'hasRightsOrRestrictions': False, + 'series': [ + { + 'id': 1, + 'name': 'Share Series 1', + 'priority': 1, + 'hasMaximumShares': True, + 'maxNumberOfShares': 50, + 'hasRightsOrRestrictions': False, + }, + { + 'id': 2, + 'name': 'Share Series 2', + 'priority': 2, + 'hasMaximumShares': True, + 'maxNumberOfShares': 100, + 'hasRightsOrRestrictions': False, + } + ] + }, + { + 'id': 2, + 'name': 'Share Class 2', + 'priority': 1, + 'hasMaximumShares': False, + 'maxNumberOfShares': None, + 'hasParValue': False, + 'parValue': None, + 'currency': None, + 'hasRightsOrRestrictions': True, + 'series': [] + }, + ] + }, + 'contactPoint': { + 'email': 'no_one@never.get', + 'phone': '123-456-7890' + } +} FILING_TEMPLATE = { 'filing': { @@ -1394,6 +1542,25 @@ } } +TRANSITION_FILING_TEMPLATE = { + 'filing': { + 'header': { + 'name': 'transition', + 'date': '2020-10-19', + 'certifiedBy': 'full name', + 'email': 'no_one@never.get', + 'filingId': 1 + }, + 'business': { + 'foundingDate': '2018-01-01T00:00:00+00:00', + 'identifier': 'BC1234567', + 'lastLedgerTimestamp': '2019-04-15T20:05:49.068272+00:00', + 'legalName': 'legal name - BC1234567', + 'legalType': 'BC' + }, + 'transition': TRANSITION + } +} STUB_FILING = { } @@ -1419,7 +1586,8 @@ ('continuedOut', STUB_FILING), ('changeOfDirectors', CHANGE_OF_DIRECTORS_MAILING), # bcorp-specific version of filing ('alteration', ALTERATION), - ('conversion', CONVERSION) + ('conversion', CONVERSION), + ('transition', TRANSITION) ] diff --git a/src/registry_schemas/schemas/filing.json b/src/registry_schemas/schemas/filing.json index 2007c60..ca8e5a3 100644 --- a/src/registry_schemas/schemas/filing.json +++ b/src/registry_schemas/schemas/filing.json @@ -63,7 +63,8 @@ "continuedOut", "correction", "alteration", - "conversion" + "conversion", + "transition" ] }, "availableOnPaperOnly": { @@ -232,6 +233,9 @@ }, { "$ref": "https://bcrs.gov.bc.ca/.well_known/schemas/alteration" + }, + { + "$ref": "https://bcrs.gov.bc.ca/.well_known/schemas/transition" } ] } diff --git a/src/registry_schemas/schemas/transition.json b/src/registry_schemas/schemas/transition.json new file mode 100644 index 0000000..2130b95 --- /dev/null +++ b/src/registry_schemas/schemas/transition.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://bcrs.gov.bc.ca/.well_known/schemas/transition", + "required": [ + "nameTranslations", + "offices", + "parties", + "shareStructure", + "hasProvisions" + ], + "type": "object", + "title": "Transition Filing", + "properties": { + "nameTranslations": { + "$ref": "https://bcrs.gov.bc.ca/.well_known/schemas/name_translations" + }, + "offices": { + "registeredOffice": { + "$ref": "https://bcrs.gov.bc.ca/.well_known/schemas/office" + }, + "recordsOffice": { + "$ref": "https://bcrs.gov.bc.ca/.well_known/schemas/office" + }, + "required": ["registeredOffice", "recordsOffice"] + }, + "parties": { + "type": "array", + "items": { + "$ref": "https://bcrs.gov.bc.ca/.well_known/schemas/parties#/definitions/party" + } + }, + "shareStructure": { + "$ref": "https://bcrs.gov.bc.ca/.well_known/schemas/share_structure" + }, + "hasProvisions": { + "type": "boolean", + "title": "Has Pre-existing company provisions?" + }, + "contactPoint": { + "$ref": "https://bcrs.gov.bc.ca/.well_known/schemas/contactPoint" + } + } +} diff --git a/src/registry_schemas/version.py b/src/registry_schemas/version.py index 339b62c..c4ac083 100644 --- a/src/registry_schemas/version.py +++ b/src/registry_schemas/version.py @@ -22,4 +22,4 @@ Development release segment: .devN """ -__version__ = '2.8.0' # pylint: disable=invalid-name +__version__ = '2.8.1' # pylint: disable=invalid-name diff --git a/tests/unit/schema_data.py b/tests/unit/schema_data.py index 312488c..afa3d42 100644 --- a/tests/unit/schema_data.py +++ b/tests/unit/schema_data.py @@ -42,5 +42,6 @@ ('alteration.json'), ('name_translations.json'), ('conversion.json'), + ('transition.json'), ('diff.json') ] diff --git a/tests/unit/test_transition.py b/tests/unit/test_transition.py new file mode 100644 index 0000000..7407c9b --- /dev/null +++ b/tests/unit/test_transition.py @@ -0,0 +1,198 @@ +# Copyright © 2020 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Test Suite to ensure transition schemas are valid.""" + +import copy + +from registry_schemas import validate +from registry_schemas.example_data import TRANSITION + + +def test_transition_schema(): + """Assert that the JSONSchema validator is working.""" + is_valid, errors = validate(TRANSITION, 'transition') + + if errors: + for err in errors: + print(err.message) + print(errors) + + assert is_valid + + +def test_validate_no_offices(): + """Assert not valid if the required offices are not present.""" + transition_json = copy.deepcopy(TRANSITION) + del transition_json['offices']['registeredOffice'] + + is_valid, errors = validate(transition_json, 'transition') + + if errors: + for err in errors: + print(err.message) + print(errors) + + assert not is_valid + + +def test_validate_optional_contact(): + """Assert valid if the required contact info is not present.""" + transition_json = copy.deepcopy(TRANSITION) + del transition_json['contactPoint'] + + is_valid, errors = validate(transition_json, 'transition') + + if errors: + for err in errors: + print(err.message) + print(errors) + + assert is_valid + + +def test_validate_no_parties(): + """Assert not valid if parties are omitted.""" + transition_json = copy.deepcopy(TRANSITION) + del transition_json['parties'] + + is_valid, errors = validate(transition_json, 'transition') + + if errors: + for err in errors: + print(err.message) + print(errors) + + assert not is_valid + + +def test_validate_party_type(): + """Assert party types are required.""" + transition_json = copy.deepcopy(TRANSITION) + + transition_json['parties'][0]['officer']['partyType'] = 'Invalid' + + is_valid, errors = validate(transition_json, 'transition') + + if errors: + for err in errors: + print(err.message) + + print(errors) + + assert not is_valid + + +def test_validate_no_share_classes(): + """Assert not valid if share classes are not present.""" + transition_json = copy.deepcopy(TRANSITION) + del transition_json['shareStructure'] + + is_valid, errors = validate(transition_json, 'transition') + + if errors: + for err in errors: + print(err.message) + + assert not is_valid + + +def test_validate_valid_share_classes(): + """Assert valid if share classes are have all required fields.""" + transition_json = copy.deepcopy(TRANSITION) + + is_valid, errors = validate(transition_json, 'transition') + + if errors: + for err in errors: + print(err.message) + + assert is_valid + + +def test_validate_share_classes_no_name(): + """Assert not valid if mandatory fields are not present.""" + transition_json = copy.deepcopy(TRANSITION) + del transition_json['shareStructure']['shareClasses'][0]['name'] + + is_valid, errors = validate(transition_json, 'transition') + + if errors: + for err in errors: + print(err.message) + print(errors) + + assert not is_valid + + +def test_validate_share_classes_no_resolution_dates(): + """Assert valid if optional resolution dates is not present.""" + transition_json = copy.deepcopy(TRANSITION) + del transition_json['shareStructure']['shareClasses'][0]['name'] + + is_valid, errors = validate(transition_json, 'transition') + + if errors: + for err in errors: + print(err.message) + print(errors) + + assert not is_valid + + +def test_validate_invalid_name_translations(): + """Assert not valid if name translations contains numbers.""" + transition_json = copy.deepcopy(TRANSITION) + transition_json['nameTranslations'] = {'new': ['Abc 123 Ltd']} + + is_valid, errors = validate(transition_json, 'transition') + + if errors: + for err in errors: + print(err.message) + print(errors) + + assert not is_valid + + +def test_validate_invalid_name_translations_long_name(): + """Assert not valid if name translations has more than 150 characters.""" + transition_json = copy.deepcopy(TRANSITION) + transition_json['nameTranslations'] =\ + {'new': ['AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'] + } + + is_valid, errors = validate(transition_json, 'transition') + + if errors: + for err in errors: + print(err.message) + print(errors) + + assert not is_valid + + +def test_validate_existing_company_provisions(): + """Assert not valid if pre-existing company provisions is not present.""" + transition_json = copy.deepcopy(TRANSITION) + del transition_json['hasProvisions'] + + is_valid, errors = validate(transition_json, 'transition') + + if errors: + for err in errors: + print(err.message) + print(errors) + + assert not is_valid