graphdb-workbench-tests 3.5.1 → 4.0.0-SPARQL12-TEST-FIX
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/cypress-guides.config.js +45 -0
- package/e2e-flaky/import/import-server-files-operations.spec.js +8 -30
- package/e2e-flaky/import/import-user-data-batch-operations.spec.js +7 -7
- package/e2e-flaky/import/import-user-data-url.spec.js +1 -1
- package/e2e-legacy/explore/visual-graph/visual.graph.spec.js +35 -4
- package/e2e-legacy/guides/import-rdf-file/confirm-duplicate-rdf-file.spec.js +1 -1
- package/e2e-legacy/guides/main-menu/main-menu-guide.spec.js +6 -14
- package/e2e-legacy/guides/ttyg/configure-agent/configure-agent-guide.spec.js +27 -32
- package/e2e-legacy/guides/ttyg/edit-agent/edit-ttyg-agent-guide.spec.js +12 -20
- package/e2e-legacy/import/import-server-files-batch-operations.spec.js +5 -5
- package/e2e-legacy/import/import-server-files.spec.js +21 -23
- package/e2e-legacy/import/import-user-data-file-upload.spec.js +21 -22
- package/e2e-legacy/import/import-user-data-text-snippet.spec.js +6 -45
- package/e2e-legacy/import/import-user-data-url.spec.js +9 -6
- package/e2e-legacy/import/import-view.spec.js +2 -2
- package/e2e-legacy/repository/rename-repository-restrictions.spec.js +129 -0
- package/e2e-legacy/repository/repositories.spec.js +3 -3
- package/e2e-legacy/resource/resource.spec.js +157 -33
- package/e2e-legacy/setup/aclmanagement/create-rule.spec.js +14 -25
- package/e2e-legacy/setup/aclmanagement/delete-rule.spec.js +6 -6
- package/e2e-legacy/setup/aclmanagement/edit-rule.spec.js +15 -15
- package/e2e-legacy/setup/aclmanagement/render-rules.spec.js +1 -1
- package/e2e-legacy/setup/aclmanagement/reorder-rules.spec.js +2 -2
- package/e2e-legacy/setup/aclmanagement/revert-rules.spec.js +2 -2
- package/e2e-legacy/setup/aclmanagement/update-rules.spec.js +3 -3
- package/e2e-legacy/setup/aclmanagement/validate-uri.spec.js +152 -0
- package/e2e-legacy/setup/users-and-access/user-and-access.spec.js +19 -19
- package/e2e-legacy/sparql-editor/yasr/download-as.spec.js +4 -7
- package/e2e-legacy/ttyg/edit-agent.spec.js +8 -0
- package/e2e-legacy/ttyg/ttyg-initial-state-with-configured-api-key.spec.js +1 -1
- package/e2e-legacy/ttyg/ttyg-initial-state-with-selected-repository.spec.js +1 -1
- package/e2e-legacy/ttyg/ttyg-initial-state-without-repositories.spec.js +1 -1
- package/eslint.config.js +5 -0
- package/fixtures/graphdb-import/wine.rdf +279 -285
- package/npm-shrinkwrap.json +11 -11
- package/package.json +3 -2
- package/steps/import/import-steps.js +26 -6
- package/steps/import/import-test-constants.js +1 -1
- package/steps/main-menu-steps.js +5 -14
- package/steps/resource/resource-steps.js +64 -5
- package/steps/setup/acl-management-steps.js +3 -23
- package/stubs/repositories/repositories-stubs.js +4 -0
- package/fixtures/graphdb-import/test-trigstar.trigs +0 -32
- package/fixtures/graphdb-import/test_turtlestar.ttls +0 -6
- package/fixtures/graphdb-import/turtlestar-data.ttls +0 -77
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {AclManagementSteps} from "../../../steps/setup/acl-management-steps";
|
|
2
|
+
import {ApplicationSteps} from "../../../steps/application-steps";
|
|
3
|
+
|
|
4
|
+
describe('ACL Management: IRI validation', () => {
|
|
5
|
+
|
|
6
|
+
let repositoryId;
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
cy.deleteRepository(repositoryId);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
repositoryId = 'acl-management-' + Date.now();
|
|
14
|
+
cy.createRepository({id: repositoryId});
|
|
15
|
+
cy.presetRepository(repositoryId);
|
|
16
|
+
cy.initializeRepository(repositoryId);
|
|
17
|
+
cy.enableAutocomplete(repositoryId);
|
|
18
|
+
AclManagementSteps.visit();
|
|
19
|
+
ApplicationSteps.getLoader().should('not.exist');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Fills in all the fields required for the rule to be otherwise valid, except the subject,
|
|
24
|
+
* so that only the subject's own validity determines whether the rule can be saved.
|
|
25
|
+
* @param {number} index
|
|
26
|
+
*/
|
|
27
|
+
const fillMandatoryFieldsExceptSubject = (index) => {
|
|
28
|
+
AclManagementSteps.fillRole(index, 'ROLE1');
|
|
29
|
+
AclManagementSteps.selectPolicy(index, 'allow');
|
|
30
|
+
AclManagementSteps.selectOperation(index, 'write');
|
|
31
|
+
AclManagementSteps.fillPredicate(index, '*');
|
|
32
|
+
AclManagementSteps.fillObject(index, '*');
|
|
33
|
+
AclManagementSteps.fillContext(index, '*');
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
it('should mark the subject field as invalid and block saving when an absolute http IRI is typed without angle brackets', () => {
|
|
37
|
+
// When I create a new rule and type an absolute http IRI without wrapping it in angle brackets
|
|
38
|
+
AclManagementSteps.addRuleInBeginning();
|
|
39
|
+
fillMandatoryFieldsExceptSubject(0);
|
|
40
|
+
AclManagementSteps.fillSubject(0, 'http://example.com/John');
|
|
41
|
+
|
|
42
|
+
// Then I expect the field to be marked as invalid
|
|
43
|
+
AclManagementSteps.getSubjectField(0).should('have.class', 'invalid');
|
|
44
|
+
// And I expect saving the rule to be forbidden
|
|
45
|
+
AclManagementSteps.checkIfRuleSavingIsForbidden(0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should mark the subject field as valid and allow saving once the http IRI is wrapped in angle brackets', () => {
|
|
49
|
+
// When I create a new rule and type a properly wrapped absolute http IRI
|
|
50
|
+
AclManagementSteps.addRuleInBeginning();
|
|
51
|
+
fillMandatoryFieldsExceptSubject(0);
|
|
52
|
+
AclManagementSteps.fillSubject(0, '<http://example.com/John>');
|
|
53
|
+
|
|
54
|
+
// Then I expect the field to be marked as valid
|
|
55
|
+
AclManagementSteps.getSubjectField(0).should('not.have.class', 'invalid');
|
|
56
|
+
// And I expect saving the rule to be allowed
|
|
57
|
+
AclManagementSteps.checkIfRuleSavingIsAllowed(0);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should mark the subject field as invalid for a urn IRI without angle brackets and valid once wrapped', () => {
|
|
61
|
+
// When I create a new rule and type a urn IRI without angle brackets
|
|
62
|
+
AclManagementSteps.addRuleInBeginning();
|
|
63
|
+
fillMandatoryFieldsExceptSubject(0);
|
|
64
|
+
AclManagementSteps.fillSubject(0, 'urn:John');
|
|
65
|
+
|
|
66
|
+
// Then I expect the field to be marked as invalid
|
|
67
|
+
AclManagementSteps.getSubjectField(0).should('have.class', 'invalid');
|
|
68
|
+
AclManagementSteps.checkIfRuleSavingIsForbidden(0);
|
|
69
|
+
|
|
70
|
+
// When I wrap the same urn IRI in angle brackets
|
|
71
|
+
AclManagementSteps.fillSubject(0, '<urn:John>');
|
|
72
|
+
|
|
73
|
+
// Then I expect the field to become valid
|
|
74
|
+
AclManagementSteps.getSubjectField(0).should('not.have.class', 'invalid');
|
|
75
|
+
AclManagementSteps.checkIfRuleSavingIsAllowed(0);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should keep the wildcard (*) value valid', () => {
|
|
79
|
+
// When I create a new rule and use the wildcard value for the subject
|
|
80
|
+
AclManagementSteps.addRuleInBeginning();
|
|
81
|
+
fillMandatoryFieldsExceptSubject(0);
|
|
82
|
+
AclManagementSteps.fillSubject(0, '*');
|
|
83
|
+
|
|
84
|
+
// Then I expect the field to remain valid
|
|
85
|
+
AclManagementSteps.getSubjectField(0).should('not.have.class', 'invalid');
|
|
86
|
+
AclManagementSteps.checkIfRuleSavingIsAllowed(0);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should still query autocomplete suggestions for an absolute IRI typed without angle brackets', () => {
|
|
90
|
+
// Given I intercept the autocomplete suggestions requests
|
|
91
|
+
cy.intercept('GET', '**/rest/autocomplete/query*').as('autocompleteQuery');
|
|
92
|
+
|
|
93
|
+
// When I create a new rule and type an absolute http IRI without angle brackets
|
|
94
|
+
AclManagementSteps.addRuleInBeginning();
|
|
95
|
+
fillMandatoryFieldsExceptSubject(0);
|
|
96
|
+
AclManagementSteps.fillSubject(0, 'http://example.com/resource/John');
|
|
97
|
+
|
|
98
|
+
// Then I expect the autocomplete request to still be triggered, searching only by the
|
|
99
|
+
// local name within the detected namespace (namespace and local name joined by ";")
|
|
100
|
+
cy.wait('@autocompleteQuery');
|
|
101
|
+
cy.get('@autocompleteQuery.all').then((interceptions) => {
|
|
102
|
+
const lastCall = interceptions[interceptions.length - 1];
|
|
103
|
+
const url = new URL(lastCall.request.url);
|
|
104
|
+
expect(url.searchParams.get('q')).to.eq('http://example.com/resource/;John');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should reject a triple term value for a plain field validated only as an IRI', () => {
|
|
109
|
+
// Predicate only allows validate-uri/validate-default-value, so a triple term is not a valid value there
|
|
110
|
+
AclManagementSteps.addRuleInBeginning();
|
|
111
|
+
AclManagementSteps.fillRole(0, 'ROLE1');
|
|
112
|
+
AclManagementSteps.selectPolicy(0, 'allow');
|
|
113
|
+
AclManagementSteps.selectOperation(0, 'write');
|
|
114
|
+
AclManagementSteps.fillSubject(0, '*');
|
|
115
|
+
AclManagementSteps.fillObject(0, '*');
|
|
116
|
+
AclManagementSteps.fillContext(0, '*');
|
|
117
|
+
AclManagementSteps.fillPredicate(0, '<<(<http://example.com/a> <http://example.com/b> "c")>>');
|
|
118
|
+
|
|
119
|
+
AclManagementSteps.getPredicateField(0).should('have.class', 'invalid');
|
|
120
|
+
AclManagementSteps.checkIfRuleSavingIsForbidden(0);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should accept a triple term value for the object field, where it is allowed', () => {
|
|
124
|
+
// Only the object field is configured with validate-triple-term-value="true"
|
|
125
|
+
AclManagementSteps.addRuleInBeginning();
|
|
126
|
+
AclManagementSteps.fillRole(0, 'ROLE1');
|
|
127
|
+
AclManagementSteps.selectPolicy(0, 'allow');
|
|
128
|
+
AclManagementSteps.selectOperation(0, 'write');
|
|
129
|
+
AclManagementSteps.fillSubject(0, '*');
|
|
130
|
+
AclManagementSteps.fillPredicate(0, '*');
|
|
131
|
+
AclManagementSteps.fillContext(0, '*');
|
|
132
|
+
AclManagementSteps.fillObject(0, '<<(<http://example.com/a> <http://example.com/b> "c")>>');
|
|
133
|
+
|
|
134
|
+
AclManagementSteps.getObjectField(0).should('not.have.class', 'invalid');
|
|
135
|
+
AclManagementSteps.checkIfRuleSavingIsAllowed(0);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should reject a malformed triple term value (missing closing brackets) for the object field', () => {
|
|
139
|
+
AclManagementSteps.addRuleInBeginning();
|
|
140
|
+
AclManagementSteps.fillRole(0, 'ROLE1');
|
|
141
|
+
AclManagementSteps.selectPolicy(0, 'allow');
|
|
142
|
+
AclManagementSteps.selectOperation(0, 'write');
|
|
143
|
+
AclManagementSteps.fillSubject(0, '*');
|
|
144
|
+
AclManagementSteps.fillPredicate(0, '*');
|
|
145
|
+
AclManagementSteps.fillContext(0, '*');
|
|
146
|
+
AclManagementSteps.fillObject(0, '<<(<http://example.com/a> <http://example.com/b> "c")');
|
|
147
|
+
|
|
148
|
+
AclManagementSteps.getObjectField(0).should('have.class', 'invalid');
|
|
149
|
+
AclManagementSteps.checkIfRuleSavingIsForbidden(0);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
@@ -280,7 +280,7 @@ describe('User and Access', () => {
|
|
|
280
280
|
LoginSteps.loginWithUser('admin', DEFAULT_ADMIN_PASSWORD);
|
|
281
281
|
UserAndAccessSteps.getUsersTable().should('be.visible');
|
|
282
282
|
|
|
283
|
-
cy.visit('/login?r=%
|
|
283
|
+
cy.visit('/login?r=%2Fsparql');
|
|
284
284
|
cy.reload();
|
|
285
285
|
UserAndAccessSteps.getUrl().should('include', '/sparql');
|
|
286
286
|
});
|
|
@@ -976,14 +976,14 @@ describe('User and Access', () => {
|
|
|
976
976
|
};
|
|
977
977
|
|
|
978
978
|
const MENU_ITEMS_WITHOUT_GRAPHQL = [
|
|
979
|
-
//
|
|
979
|
+
// Import
|
|
980
980
|
{
|
|
981
981
|
path: ['Import'],
|
|
982
982
|
expectedUrl: '/import',
|
|
983
983
|
checks: noAuthChecks,
|
|
984
984
|
},
|
|
985
985
|
|
|
986
|
-
//
|
|
986
|
+
// Explore
|
|
987
987
|
{
|
|
988
988
|
path: ['Explore', 'Graphs overview'],
|
|
989
989
|
expectedUrl: '/graphs',
|
|
@@ -1011,7 +1011,7 @@ describe('User and Access', () => {
|
|
|
1011
1011
|
checks: noAuthChecks,
|
|
1012
1012
|
},
|
|
1013
1013
|
|
|
1014
|
-
//
|
|
1014
|
+
// SPARQL
|
|
1015
1015
|
{
|
|
1016
1016
|
path: ['SPARQL'],
|
|
1017
1017
|
expectedTitle: 'SPARQL Query & Update',
|
|
@@ -1019,9 +1019,16 @@ describe('User and Access', () => {
|
|
|
1019
1019
|
checks: noAuthChecks,
|
|
1020
1020
|
},
|
|
1021
1021
|
|
|
1022
|
-
//
|
|
1022
|
+
// TTYG
|
|
1023
|
+
{
|
|
1024
|
+
path: ['Talk to Your Graph'],
|
|
1025
|
+
expectedUrl: '/ttyg',
|
|
1026
|
+
checks: noAuthChecks,
|
|
1027
|
+
},
|
|
1028
|
+
|
|
1029
|
+
// GraphQL
|
|
1023
1030
|
|
|
1024
|
-
//
|
|
1031
|
+
// Monitor
|
|
1025
1032
|
{
|
|
1026
1033
|
path: ['Monitor', 'Queries and Updates'],
|
|
1027
1034
|
expectedUrl: '/monitor/queries',
|
|
@@ -1029,7 +1036,7 @@ describe('User and Access', () => {
|
|
|
1029
1036
|
checks: noAuthChecks,
|
|
1030
1037
|
},
|
|
1031
1038
|
|
|
1032
|
-
//
|
|
1039
|
+
// Setup
|
|
1033
1040
|
{
|
|
1034
1041
|
path: ['Setup', 'My Settings'],
|
|
1035
1042
|
expectedUrl: '/settings',
|
|
@@ -1081,14 +1088,7 @@ describe('User and Access', () => {
|
|
|
1081
1088
|
checks: noAuthChecks,
|
|
1082
1089
|
},
|
|
1083
1090
|
|
|
1084
|
-
//
|
|
1085
|
-
{
|
|
1086
|
-
path: ['Lab', 'Talk to Your Graph'],
|
|
1087
|
-
expectedUrl: '/ttyg',
|
|
1088
|
-
checks: noAuthChecks,
|
|
1089
|
-
},
|
|
1090
|
-
|
|
1091
|
-
// 8) Help
|
|
1091
|
+
// Help
|
|
1092
1092
|
{
|
|
1093
1093
|
path: ['Help', 'REST API'],
|
|
1094
1094
|
expectedUrl: '/webapi',
|
|
@@ -1128,14 +1128,14 @@ describe('User and Access', () => {
|
|
|
1128
1128
|
];
|
|
1129
1129
|
|
|
1130
1130
|
const FREE_ACCESS_MENU_ITEMS_WITHOUT_GRAPHQL = [
|
|
1131
|
-
//
|
|
1131
|
+
// Import
|
|
1132
1132
|
{
|
|
1133
1133
|
path: ['Import'],
|
|
1134
1134
|
expectedUrl: '/import',
|
|
1135
1135
|
checks: noAuthChecks,
|
|
1136
1136
|
},
|
|
1137
1137
|
|
|
1138
|
-
//
|
|
1138
|
+
// Explore
|
|
1139
1139
|
{
|
|
1140
1140
|
path: ['Explore', 'Graphs overview'],
|
|
1141
1141
|
expectedUrl: '/graphs',
|
|
@@ -1163,7 +1163,7 @@ describe('User and Access', () => {
|
|
|
1163
1163
|
checks: noAuthChecks,
|
|
1164
1164
|
},
|
|
1165
1165
|
|
|
1166
|
-
//
|
|
1166
|
+
// SPARQL
|
|
1167
1167
|
{
|
|
1168
1168
|
path: ['SPARQL'],
|
|
1169
1169
|
expectedTitle: 'SPARQL Query & Update',
|
|
@@ -1171,7 +1171,7 @@ describe('User and Access', () => {
|
|
|
1171
1171
|
checks: noAuthChecks,
|
|
1172
1172
|
},
|
|
1173
1173
|
|
|
1174
|
-
//
|
|
1174
|
+
// Setup
|
|
1175
1175
|
{
|
|
1176
1176
|
path: ['Setup', 'Connectors'],
|
|
1177
1177
|
expectedUrl: '/connectors',
|
|
@@ -44,13 +44,10 @@ describe('Download results', () => {
|
|
|
44
44
|
|
|
45
45
|
// Then expect follow options to be present.
|
|
46
46
|
YasrSteps.getDownloadAsOption(0).contains('JSON');
|
|
47
|
-
YasrSteps.getDownloadAsOption(1).contains( '
|
|
48
|
-
YasrSteps.getDownloadAsOption(2).contains( '
|
|
49
|
-
YasrSteps.getDownloadAsOption(3).contains( '
|
|
50
|
-
YasrSteps.getDownloadAsOption(4).contains( '
|
|
51
|
-
YasrSteps.getDownloadAsOption(5).contains( 'TSV');
|
|
52
|
-
YasrSteps.getDownloadAsOption(6).contains( 'TSV*');
|
|
53
|
-
YasrSteps.getDownloadAsOption(7).contains( 'Binary RDF Results');
|
|
47
|
+
YasrSteps.getDownloadAsOption(1).contains( 'XML');
|
|
48
|
+
YasrSteps.getDownloadAsOption(2).contains( 'CSV');
|
|
49
|
+
YasrSteps.getDownloadAsOption(3).contains( 'TSV');
|
|
50
|
+
YasrSteps.getDownloadAsOption(4).contains( 'Binary RDF Results');
|
|
54
51
|
});
|
|
55
52
|
|
|
56
53
|
it('should download as JSON-LD after a CONSTRUCT query', () => {
|
|
@@ -109,6 +109,14 @@ describe('TTYG edit an agent', () => {
|
|
|
109
109
|
|
|
110
110
|
// THEN: The Save Agent button should be disabled
|
|
111
111
|
TtygAgentSettingsModalSteps.getSaveAgentButton().should('be.disabled');
|
|
112
|
+
// AND: The panel of the FTS extraction method should be expanded, so that the reason is visible
|
|
113
|
+
TtygAgentSettingsModalSteps.getFtsDisabledHelp().should('be.visible');
|
|
114
|
+
|
|
115
|
+
// WHEN: I try to collapse the panel of the FTS extraction method
|
|
116
|
+
TtygAgentSettingsModalSteps.toggleFTSExtractionMethodPanel();
|
|
117
|
+
// THEN: The panel should remain expanded, because the precondition error is not resolved yet
|
|
118
|
+
TtygAgentSettingsModalSteps.getFtsDisabledHelp().should('be.visible');
|
|
119
|
+
TtygAgentSettingsModalSteps.getSaveAgentButton().should('be.disabled');
|
|
112
120
|
});
|
|
113
121
|
|
|
114
122
|
it('should be able to edit Autocomplete extraction method option', () => {
|
|
@@ -39,7 +39,7 @@ describe('TTYG initial state with API key', () => {
|
|
|
39
39
|
it('Should display the correct initial state when navigating via the navigation menu', () => {
|
|
40
40
|
// Given, I visit the TTYG page via the navigation menu
|
|
41
41
|
HomeSteps.visit();
|
|
42
|
-
MainMenuSteps.
|
|
42
|
+
MainMenuSteps.clickOnMenuTTYG();
|
|
43
43
|
// Then,
|
|
44
44
|
verifyStateWithApiKey();
|
|
45
45
|
});
|
|
@@ -38,7 +38,7 @@ describe.skip('TTYG initial state with selected repository', () => {
|
|
|
38
38
|
it('Should display the correct initial state when navigating via the navigation menu', () => {
|
|
39
39
|
// Given, I visit the TTYG page via the navigation menu with a repository selected
|
|
40
40
|
HomeSteps.visit();
|
|
41
|
-
MainMenuSteps.
|
|
41
|
+
MainMenuSteps.clickOnMenuTTYG();
|
|
42
42
|
// Then,
|
|
43
43
|
verifyStateWithSelectedRepository();
|
|
44
44
|
});
|
|
@@ -14,7 +14,7 @@ describe('TTYG initial state without selected repository', () => {
|
|
|
14
14
|
it('Should display the correct initial state when navigating via the navigation menu', () => {
|
|
15
15
|
// Given, I visit the TTYG page via the navigation menu without a repository selected
|
|
16
16
|
HomeSteps.visit();
|
|
17
|
-
MainMenuSteps.
|
|
17
|
+
MainMenuSteps.clickOnMenuTTYG();
|
|
18
18
|
// Then,
|
|
19
19
|
ErrorSteps.verifyNoConnectedRepoMessage();
|
|
20
20
|
});
|
package/eslint.config.js
CHANGED
|
@@ -35,6 +35,11 @@ export default [
|
|
|
35
35
|
rules: {
|
|
36
36
|
...pluginJs.configs.recommended.rules,
|
|
37
37
|
...pluginCypress.configs.recommended.rules,
|
|
38
|
+
// Exclusive tests (.only) silently skip the rest of the suite, so they must never be committed.
|
|
39
|
+
'no-restricted-syntax': ['error', {
|
|
40
|
+
selector: "MemberExpression[computed=false][property.name='only'][object.name=/^(describe|context|it|specify|test|suite)$/]",
|
|
41
|
+
message: 'Exclusive tests are not allowed: remove ".only" before committing.'
|
|
42
|
+
}],
|
|
38
43
|
},
|
|
39
44
|
},
|
|
40
45
|
];
|