Project

General

Profile

« Previous | Next » 

Revision 9bee4afa

Added by koszko over 1 year ago

support schema v2 and dependencies on mappings

View differences:

.gitmodules
1 1
[submodule "schemas"]
2
	path = schemas
2
	path = schemas/1.x
3
	url = ../hydrilla-json-schemas/
4
[submodule "hydrilla-json-schemas-2.x"]
5
	path = schemas/2.x
3 6
	url = ../hydrilla-json-schemas/
common/jsonschema.js
67 67
#EXPORT validate
68 68

  
69 69
const haketilo_schemas = [
70
#INCLUDE schemas/api_query_result-1.0.1.schema.json
70
    /* 1.x Hydrilla JSON schema series */
71
#INCLUDE schemas/1.x/api_query_result-1.0.1.schema.json
71 72
    ,
72
#INCLUDE schemas/api_mapping_description-1.0.1.schema.json
73
#INCLUDE schemas/1.x/api_mapping_description-1.0.1.schema.json
73 74
    ,
74
#INCLUDE schemas/api_resource_description-1.0.1.schema.json
75
#INCLUDE schemas/1.x/api_resource_description-1.0.1.schema.json
75 76
    ,
76
#INCLUDE schemas/common_definitions-1.0.1.schema.json
77
#INCLUDE schemas/1.x/common_definitions-1.0.1.schema.json
78
    ,
79
    /* 2.x Hydrilla JSON schema series */
80
#INCLUDE schemas/2.x/api_query_result-2.schema.json
81
    ,
82
#INCLUDE schemas/2.x/api_mapping_description-2.schema.json
83
    ,
84
#INCLUDE schemas/2.x/api_resource_description-2.schema.json
85
    ,
86
#INCLUDE schemas/2.x/common_definitions-2.schema.json
77 87
].reduce((ac, s) => Object.assign(ac, {[s.$id]: s}), {});
88

  
89
const name_base_re    = "(?<name_base>[^/]*)";
90
const major_number_re = "(?<major>[1-9][0-9]*)";
91
const minor_number_re = "(?:[1-9][0-9]*|0)";
92
const numbers_rest_re = `(?:\\.${minor_number_re})*`;
93
const version_re      = `(?<ver>${major_number_re}${numbers_rest_re})`;
94
const schema_name_re  = `${name_base_re}-${version_re}\\.schema\\.json`;
95

  
96
const haketilo_schema_name_regex = new RegExp(schema_name_re);
97

  
98
for (const [$id, schema] of [...Object.entries(haketilo_schemas)]) {
99
    const match = haketilo_schema_name_regex.exec($id);
100
    const schema_name =
101
	  `${match.groups.name_base}-${match.groups.major}.schema.json`;
102
    haketilo_schemas[schema_name] = schema;
103
}
104

  
78 105
#EXPORT haketilo_schemas
106
#EXPORT haketilo_schema_name_regex
79 107

  
80 108
const haketilo_validator = new Validator();
81 109
Object.values(haketilo_schemas)
html/install.js
49 49
#FROM html/DOM_helpers.js  IMPORT clone_template, Showable
50 50
#FROM common/entities.js   IMPORT item_id_string, version_string, get_files
51 51
#FROM common/misc.js       IMPORT sha256_async AS compute_sha256
52
#FROM common/jsonschema.js IMPORT haketilo_validator, haketilo_schemas
52
#FROM common/jsonschema.js IMPORT haketilo_validator, haketilo_schemas, \
53
                                  haketilo_schema_name_regex
53 54

  
54 55
#FROM html/repo_query_cacher_client.js IMPORT indirect_fetch
55 56

  
......
203 204

  
204 205
	const captype = item_type[0].toUpperCase() + item_type.substring(1);
205 206

  
206
	const $id =
207
	      `https://hydrilla.koszko.org/schemas/api_${item_type}_description-1.0.1.schema.json`;
208
	const schema = haketilo_schemas[$id];
209
	const result = haketilo_validator.validate(json, schema);
210
	if (result.errors.length > 0) {
211
	    const reg = new RegExp(schema.allOf[2].properties.$schema.pattern);
212
	    if (json.$schema && !reg.test(json.$schema)) {
207
	const nonconforming_format_error_msg =
208
	      `${captype} ${item_id_string(id, ver)} was served using a nonconforming response format.`;
209

  
210
	try {
211
	    const match = haketilo_schema_name_regex.exec(json.$schema);
212
	    var major_schema_version = match.groups.major;
213

  
214
	    if (!["1", "2"].includes(major_schema_version)) {
213 215
		const msg = `${captype} ${item_id_string(id, ver)} was served using unsupported Hydrilla API version. You might need to update Haketilo.`;
214
		return work.err(result.errors, msg);
216
		return work.err(null, msg);
215 217
	    }
216

  
217
	    const msg = `${captype} ${item_id_string(id, ver)} was served using a nonconforming response format.`;
218
	    return work.err(result.errors, msg);
218
	} catch(e) {
219
	    return work.err(e, nonconforming_format_error_msg);
219 220
	}
220 221

  
222
	const schema_name = `api_${item_type}_description-${major_schema_version}.schema.json`;
223

  
224
	const schema = haketilo_schemas[schema_name];
225
	const result = haketilo_validator.validate(json, schema);
226
	if (result.errors.length > 0)
227
	    return work.err(result.errors, nonconforming_format_error_msg);
228

  
221 229
	const scripts = item_type === "resource" && json.scripts;
222 230
	const files = json.source_copyright.concat(scripts || []);
223 231

  
......
229 237
		process_item(work, "resource", res_ref.identifier);
230 238
	}
231 239

  
240
	if (major_schema_version >= 2) {
241
	    for (const map_ref of (json.required_mappings || []))
242
		process_item(work, "mapping", map_ref.identifier);
243
	}
244

  
232 245
	/*
233 246
	 * At this point we already have JSON definition of the item and we
234 247
	 * triggered processing of its dependencies. We now have to verify if
schemas
1
Subproject commit 09634f3446866f712a022327683b1149d8f46bf0
schemas/1.x
1
Subproject commit 09634f3446866f712a022327683b1149d8f46bf0
schemas/2.x
1
Subproject commit 7206db45f277c10c34d1b7ed9bd35343ac742d30
test/haketilo_test/unit/test_install.py
57 57

  
58 58
@pytest.mark.ext_data(install_ext_data)
59 59
@pytest.mark.usefixtures('webextension')
60
@pytest.mark.parametrize('complex_variant', [False, True])
61
def test_install_normal_usage(driver, execute_in_page, complex_variant):
60
@pytest.mark.parametrize('variant', [{
61
    # The resource/mapping others depend on.
62
    'root_resource_id': f'resource-abcd-defg-ghij',
63
    'root_mapping_id':  f'mapping-abcd-defg-ghij',
64
    # Those ids are used to check the alphabetical ordering.
65
    'item_ids': [f'resource-{letters}' for letters in (
66
        'a', 'abcd', 'abcd-defg-ghij', 'b', 'c',
67
        'd', 'defg', 'e', 'f',
68
        'g', 'ghij', 'h', 'i', 'j'
69
    )],
70
    'files_count': 9
71
}, {
72
    'root_resource_id': 'resource-a',
73
    'root_mapping_id':  'mapping-a',
74
    'item_ids':         ['resource-a'],
75
    'files_count': 0
76
}, {
77
    'root_resource_id': 'resource-a-w-required-mapping-v1',
78
    'root_mapping_id':  'mapping-a-w-required-mapping-v1',
79
    'item_ids':         ['resource-a-w-required-mapping-v1'],
80
    'files_count':      1
81
}, {
82
    'root_resource_id': 'resource-a-w-required-mapping-v2',
83
    'root_mapping_id':  'mapping-a-w-required-mapping-v2',
84
    'item_ids':         [
85
        'mapping-a',
86
        'resource-a',
87
        'resource-a-w-required-mapping-v2'
88
    ],
89
    'files_count':      1
90
}])
91
def test_install_normal_usage(driver, execute_in_page, variant):
62 92
    """
63 93
    Test of the normal package installation procedure with one mapping and,
64 94
    depending on parameter, one or many resources.
......
67 97

  
68 98
    assert execute_in_page('returnval(shw());') == [[], False]
69 99

  
70
    if complex_variant:
71
        # The resource/mapping others depend on.
72
        root_id = 'abcd-defg-ghij'
73
        root_resource_id = f'resource-{root_id}'
74
        root_mapping_id = f'mapping-{root_id}'
75
        # Those ids are used to check the alphabetical ordering.
76
        resource_ids = [f'resource-{letters}' for letters in (
77
            'a', 'abcd', root_id, 'b', 'c',
78
            'd', 'defg', 'e', 'f',
79
            'g', 'ghij', 'h', 'i', 'j'
80
        )]
81
        files_count = 9
82
    else:
83
        root_resource_id = f'resource-a'
84
        root_mapping_id = f'mapping-a'
85
        resource_ids = [root_resource_id]
86
        files_count = 0
87

  
88 100
    # Preview the installation of a resource, show resource's details, close
89 101
    # the details and cancel installation.
90 102
    execute_in_page('returnval(install_view.show(...arguments));',
91
                    'https://hydril.la/', 'resource', root_resource_id)
103
                    'https://hydril.la/', 'resource',
104
                    variant['root_resource_id'])
92 105

  
93 106
    assert execute_in_page('returnval(shw());') == [['show'], True]
94
    assert f'{root_resource_id}-2021.11.11-1'\
107
    assert f'{variant["root_resource_id"]}-2021.11.11-1'\
95 108
        in containers['install_preview'].text
96 109
    assert_container_displayed('install_preview')
97 110

  
98 111
    entries = execute_in_page('returnval(ets().map(e => e.main_li.innerText));')
99
    assert len(entries) == len(resource_ids)
112
    assert len(entries) == len(variant['item_ids'])
113
    resource_idx = variant['item_ids'].index(variant['root_resource_id'])
100 114
    # Verify alphabetical ordering.
101
    assert all([id in text for id, text in zip(resource_ids, entries)])
115
    assert all([id in text for id, text in
116
                zip(variant['item_ids'], entries)])
102 117

  
103
    assert not execute_in_page('returnval(ets()[0].old_ver);').is_displayed()
104
    execute_in_page('returnval(ets()[0].details_but);').click()
118
    assert not execute_in_page(f'returnval(ets()[{resource_idx}].old_ver);')\
119
        .is_displayed()
120
    execute_in_page(f'returnval(ets()[{resource_idx}].details_but);').click()
105 121
    assert 'resource-a' in containers['resource_preview_container'].text
106 122
    assert_container_displayed('resource_preview_container')
107 123

  
......
116 132
    # details, close the details and commit the installation.
117 133
    execute_in_page('returnval(install_view.show(...arguments));',
118 134
                    'https://hydril.la/', 'mapping',
119
                    root_mapping_id, [2022, 5, 10])
135
                    variant['root_mapping_id'], [2022, 5, 10])
120 136

  
121 137
    assert execute_in_page('returnval(shw(2));') == [['show'], True]
122 138
    assert_container_displayed('install_preview')
123 139

  
124 140
    entries = execute_in_page('returnval(ets().map(e => e.main_li.innerText));')
125
    assert len(entries) == len(resource_ids) + 1
126
    assert f'{root_mapping_id}-2022.5.10' in entries[0]
141
    assert len(entries) == len(variant['item_ids']) + 1
142

  
143
    all_item_ids = sorted([*variant['item_ids'], variant['root_mapping_id']])
144
    mapping_idx = all_item_ids.index(variant["root_mapping_id"])
127 145
    # Verify alphabetical ordering.
128
    assert all([id in text for id, text in zip(resource_ids, entries[1:])])
146
    assert all([id in text for id, text in zip(all_item_ids, entries)])
129 147

  
130
    assert not execute_in_page('returnval(ets()[0].old_ver);').is_displayed()
131
    execute_in_page('returnval(ets()[0].details_but);').click()
132
    assert root_mapping_id in containers['mapping_preview_container'].text
148
    assert not execute_in_page(f'returnval(ets()[{mapping_idx}].old_ver);')\
149
        .is_displayed()
150
    execute_in_page(f'returnval(ets()[{mapping_idx}].details_but);').click()
151
    assert variant['root_mapping_id'] in \
152
        containers['mapping_preview_container'].text
133 153
    assert_container_displayed('mapping_preview_container')
134 154

  
135 155
    execute_in_page('returnval(install_view.mapping_back_but);').click()
......
145 165

  
146 166
    # Verify the install
147 167
    db_contents = get_db_contents(execute_in_page)
148
    for item_type, ids in \
149
        [('mapping', {root_mapping_id}), ('resource', set(resource_ids))]:
168
    all_map_ids = {id for id in all_item_ids if id.startswith('mapping')}
169
    all_res_ids = {id for id in all_item_ids if id.startswith('resource')}
170
    for item_type, ids in [
171
            ('mapping', all_map_ids),
172
            ('resource', all_res_ids)
173
    ]:
150 174
        assert set([it['identifier'] for it in db_contents[item_type]]) == ids
151 175

  
152
    assert all([len(db_contents[store]) == files_count
176
    assert all([len(db_contents[store]) == variant['files_count']
153 177
                for store in ('file', 'file_uses')])
154 178

  
155 179
    # Update the installed mapping to a newer version.
156 180
    execute_in_page('returnval(install_view.show(...arguments));',
157
                    'https://hydril.la/', 'mapping', root_mapping_id)
181
                    'https://hydril.la/', 'mapping', variant['root_mapping_id'])
158 182
    assert execute_in_page('returnval(shw(4));') == [['show'], True]
159 183
    # resources are already in the newest versions, hence they should not appear
160 184
    # in the install preview list.
......
171 195

  
172 196
    # Verify the newer version install.
173 197
    old_db_contents, db_contents = db_contents, get_db_contents(execute_in_page)
174
    old_db_contents['mapping'][0]['version'][-1] += 1
175
    assert db_contents['mapping'] == old_db_contents['mapping']
198

  
199
    old_root_mapping = [m for m in old_db_contents['mapping']
200
                        if m['identifier'] == variant['root_mapping_id']][0]
201
    old_root_mapping['version'][-1] += 1
202

  
203
    new_root_mapping = [m for m in db_contents['mapping']
204
                        if m['identifier'] == variant['root_mapping_id']][0]
205

  
206
    assert old_root_mapping == new_root_mapping
176 207

  
177 208
    # All items are up to date - verify dialog is instead shown in this case.
178 209
    execute_in_page('install_view.show(...arguments);',
179
                    'https://hydril.la/', 'mapping', root_mapping_id)
210
                    'https://hydril.la/', 'mapping', variant['root_mapping_id'])
180 211

  
181 212
    fetched = lambda d: 'Fetching ' not in containers['dialog_container'].text
182 213
    WebDriverWait(driver, 10).until(fetched)
test/haketilo_test/world_wide_library.py
33 33
from threading import Lock
34 34
from uuid import uuid4
35 35
import json
36
import functools as ft
37
import operator as op
36 38

  
37 39
from .misc_constants import here
38 40
from .unit.utils import * # sample repo data
......
114 116
                   for i in range(9)]
115 117
sample_hashes = [sha256(c.encode()).digest().hex() for c in sample_contents]
116 118

  
117
file_url = lambda hashed: f'https://hydril.la/file/sha256/{hashed}'
119
file_url = ft.partial(op.concat, 'https://hydril.la/file/sha256/')
118 120

  
119 121
sample_files_catalog = dict([(file_url(h), make_handler(c))
120 122
                             for h, c in zip(sample_hashes, sample_contents)])
......
144 146
        'dependencies': []
145 147
    })
146 148

  
149
# The one below will generate items with schema still at version 1, so required
150
# mappings will be ignored.
151
sample_resource_templates.append({
152
    'id_suffix':         'a-w-required-mapping-v1',
153
    'files_count':       1,
154
    'dependencies':      [],
155
    'required_mappings': [{'identifier': 'mapping-a'}]
156
})
157

  
158
sample_resource_templates.append({
159
    'id_suffix':         'a-w-required-mapping-v2',
160
    'files_count':       1,
161
    'dependencies':      [],
162
    'required_mappings': [{'identifier': 'mapping-a'}],
163
    'schema_ver':        '2'
164
})
165

  
147 166
sample_resources_catalog = {}
148 167
sample_mappings_catalog = {}
149 168
sample_queries = {}
150 169

  
151 170
for srt in sample_resource_templates:
152 171
    resource = make_sample_resource()
153
    resource['identifier']          = f'resource-{srt["id_suffix"]}'
154
    resource['long_name']           = resource['identifier'].upper()
155
    resource['uuid']                = str(uuid4())
156
    resource['dependencies']        = srt['dependencies']
157
    resource['source_copyright']    = []
158
    resource['scripts']             = []
172
    resource['identifier']       = f'resource-{srt["id_suffix"]}'
173
    resource['long_name']        = resource['identifier'].upper()
174
    resource['uuid']             = str(uuid4())
175
    resource['dependencies']     = srt['dependencies']
176
    resource['source_copyright'] = []
177
    resource['scripts']          = []
159 178
    for i in range(srt['files_count']):
160 179
        file_ref = {'file': f'file_{i}', 'sha256': sample_hashes[i]}
161 180
        resource[('source_copyright', 'scripts')[i & 1]].append(file_ref)
......
191 210

  
192 211
    mapping['payloads'] = payloads
193 212

  
213
    for item in resource, mapping:
214
        if 'required_mappings' in srt:
215
            item['required_mappings'] = srt['required_mappings']
216
        if 'schema_ver' in srt:
217
            item['$schema'] = item['$schema'].replace('1', srt['schema_ver'])
218

  
194 219
    for item, versions, catalog in [
195 220
        (resource, resource_versions, sample_resources_catalog),
196 221
        (mapping,  mapping_versions,  sample_mappings_catalog)

Also available in: Unified diff