graphdb-workbench-tests 4.0.0-TR13 → 4.0.0-TR15

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.
@@ -5,12 +5,15 @@ import {MainMenuSteps} from '../../steps/main-menu-steps.js';
5
5
  import {YasqeSteps} from '../../steps/yasgui/yasqe-steps.js';
6
6
  import {YasrSteps} from '../../steps/yasgui/yasr-steps.js';
7
7
  import {ToasterSteps} from '../../steps/toaster-steps.js';
8
+ import {GraphNavigatorStubs} from '../../stubs/graph-navigator-stubs.js';
8
9
 
9
10
  const FILE_TO_IMPORT = 'resource-test-data.ttl';
10
11
  const SEED_RESOURCE_ENCODED = 'http:%2F%2Fexample.com%2Fontology%23CustomerLoyalty';
11
12
  const SEED_RESOURCE_LABEL = 'CustomerLoyalty';
12
13
  const SETTINGS_FILE_NAME = 'graph-navigator-settings.ttl';
13
14
  const SETTINGS_FILE = `fixtures/graph-navigator/${SETTINGS_FILE_NAME}`;
15
+ const INVALID_SETTINGS_FILE_NAME = 'graph-navigator-settings-invalid.ttl';
16
+ const INVALID_SETTINGS_FILE = `fixtures/graph-navigator/${INVALID_SETTINGS_FILE_NAME}`;
14
17
 
15
18
  const CONSTRUCT_QUERY = 'CONSTRUCT { <http://example.com/ontology#CustomerLoyalty> ?p ?o } WHERE { <http://example.com/ontology#CustomerLoyalty> ?p ?o }';
16
19
  const CONSTRUCT_TARGET_LABEL = 'Metric';
@@ -64,6 +67,18 @@ describe('Graph Navigator', () => {
64
67
  GraphNavigatorSteps.getElement(SEED_RESOURCE_LABEL).should('exist');
65
68
  });
66
69
 
70
+ it('should show an error toast, when a graph request fails', () => {
71
+ // Given the repository's SPARQL requests fail.
72
+ cy.intercept('POST', `/repositories/${repositoryId}`, {statusCode: 500}).as('graphRequest');
73
+
74
+ // When I open the Graph Navigator view with a start resource.
75
+ GraphNavigatorSteps.visit(SEED_RESOURCE_ENCODED);
76
+ cy.wait('@graphRequest');
77
+
78
+ // Then I expect an error toast about the graph data.
79
+ ToasterSteps.getToasterMessage().should('contain', 'Could not load the graph data.');
80
+ });
81
+
67
82
  it('should seed the canvas with the graph computed from a CONSTRUCT query', () => {
68
83
  // Given I open the Graph Navigator view with a CONSTRUCT query, as sent from the SPARQL editor.
69
84
  GraphNavigatorSteps.visitWithQuery(CONSTRUCT_QUERY);
@@ -207,6 +222,43 @@ describe('Graph Navigator', () => {
207
222
  GraphNavigatorSteps.getWorkspace().should('exist');
208
223
  });
209
224
 
225
+ it('should show the validation errors of a rejected file on its row', () => {
226
+ // Given I have selected a settings file with invalid values and an unknown property.
227
+ GraphNavigatorSteps.visit();
228
+ GraphNavigatorSteps.openSettings();
229
+ GraphNavigatorSteps.selectSettingsFile(INVALID_SETTINGS_FILE);
230
+
231
+ // When I upload it.
232
+ GraphNavigatorSteps.clickUpload();
233
+ GraphNavigatorSteps.confirmDialog();
234
+
235
+ // Then I expect the errors in a table on the file row.
236
+ ToasterSteps.verifyNewToasterError('Could not upload the settings');
237
+ GraphNavigatorSteps.getSelectedSettingsFile(INVALID_SETTINGS_FILE_NAME).should('exist');
238
+ GraphNavigatorSteps.getUploadErrors().should('be.visible')
239
+ .and('contain', '3 validation errors')
240
+ .and('contain', 'Fix these properties in the file and upload it again.');
241
+ GraphNavigatorSteps.getUploadError('gn:acceptBlankNodes').should('contain', 'Must be of type boolean');
242
+ GraphNavigatorSteps.getUploadError('gn:openWorldProperties').should('contain', 'Must be of type boolean');
243
+ GraphNavigatorSteps.getUploadError('gn:unknownSetting')
244
+ .should('contain', 'Not a known property');
245
+
246
+ // When I close and reopen the settings.
247
+ GraphNavigatorSteps.closeSettings();
248
+ GraphNavigatorSteps.openSettings();
249
+
250
+ // Then I expect the file and its errors to be kept.
251
+ GraphNavigatorSteps.getSelectedSettingsFile(INVALID_SETTINGS_FILE_NAME).should('exist');
252
+ GraphNavigatorSteps.getUploadErrors().should('be.visible')
253
+ .and('contain', '3 validation errors');
254
+
255
+ // When I remove the file.
256
+ GraphNavigatorSteps.clickCancelFile();
257
+
258
+ // Then I expect the errors to be cleared.
259
+ GraphNavigatorSteps.getUploadErrors().should('not.exist');
260
+ });
261
+
210
262
  it('should ask for confirmation before resetting', () => {
211
263
  // Given the repository has uploaded settings.
212
264
  GraphNavigatorSteps.visit();
@@ -252,6 +304,48 @@ describe('Graph Navigator', () => {
252
304
  });
253
305
  });
254
306
 
307
+ describe('Autocomplete warning', () => {
308
+ beforeEach(() => {
309
+ GraphNavigatorStubs.stubSettingsWithAutocomplete();
310
+ });
311
+
312
+ it('should warn once that autocomplete is off', () => {
313
+ // Given the settings use autocomplete, but autocomplete is off.
314
+ GraphNavigatorStubs.spyEntityLookup(SEED_RESOURCE_LABEL);
315
+ GraphNavigatorSteps.visit();
316
+ cy.wait('@graph-navigator-settings');
317
+
318
+ // When I search for entities.
319
+ GraphNavigatorSteps.searchEntities(SEED_RESOURCE_LABEL);
320
+ cy.wait('@entity-lookup');
321
+
322
+ // Then I expect a warning that autocomplete is off.
323
+ ToasterSteps.getWarningToast().should('be.visible').and('contain', 'Autocomplete is OFF');
324
+
325
+ // When the warning is gone and I search for entities again.
326
+ ToasterSteps.getToast().should('not.exist');
327
+ GraphNavigatorSteps.searchEntities(SEED_RESOURCE_LABEL);
328
+ cy.wait('@entity-lookup');
329
+
330
+ // Then I expect no new warning.
331
+ ToasterSteps.getToast().should('not.exist');
332
+ });
333
+
334
+ it('should redirect to the autocomplete view when the warning is clicked', () => {
335
+ // Given the settings use autocomplete, but autocomplete is off.
336
+ GraphNavigatorSteps.visit();
337
+ cy.wait('@graph-navigator-settings');
338
+
339
+ // When I search for entities and click the warning.
340
+ GraphNavigatorSteps.searchEntities(SEED_RESOURCE_LABEL);
341
+ ToasterSteps.getWarningToast().should('be.visible').and('contain', 'Autocomplete is OFF');
342
+ ToasterSteps.getWarningToast().click();
343
+
344
+ // Then I expect to be redirected to the autocomplete view.
345
+ cy.url().should('include', '/autocomplete');
346
+ });
347
+ });
348
+
255
349
  describe('Repository switch', () => {
256
350
  let secondRepositoryId;
257
351
 
@@ -302,5 +396,38 @@ describe('Graph Navigator', () => {
302
396
  GraphNavigatorSteps.getElements().should('not.exist');
303
397
  GraphNavigatorSteps.verifyOnlyRepositoryParam(secondRepositoryId);
304
398
  });
399
+
400
+ it('should warn that autocomplete is off once per repository selection', () => {
401
+ // Given the settings use autocomplete, but autocomplete is off.
402
+ GraphNavigatorStubs.stubSettingsWithAutocomplete();
403
+ GraphNavigatorSteps.visit();
404
+ cy.wait('@graph-navigator-settings');
405
+
406
+ // When I search for entities.
407
+ GraphNavigatorSteps.searchEntities(SEED_RESOURCE_LABEL);
408
+
409
+ // Then I expect a warning that autocomplete is off.
410
+ ToasterSteps.getWarningToast().should('be.visible').and('contain', 'Autocomplete is OFF');
411
+
412
+ // When the warning is gone and I switch to another repository and search for entities.
413
+ ToasterSteps.getToast().should('not.exist');
414
+ RepositorySelectorSteps.selectRepository(secondRepositoryId);
415
+ GraphNavigatorSteps.confirmDialog();
416
+ cy.wait('@graph-navigator-settings');
417
+ GraphNavigatorSteps.searchEntities(SEED_RESOURCE_LABEL);
418
+
419
+ // Then I expect a new warning, for the new repository.
420
+ ToasterSteps.getWarningToast().should('be.visible').and('contain', 'Autocomplete is OFF');
421
+
422
+ // When the warning is gone and I switch back to the first repository and search for entities.
423
+ ToasterSteps.getToast().should('not.exist');
424
+ RepositorySelectorSteps.selectRepository(repositoryId);
425
+ GraphNavigatorSteps.confirmDialog();
426
+ cy.wait('@graph-navigator-settings');
427
+ GraphNavigatorSteps.searchEntities(SEED_RESOURCE_LABEL);
428
+
429
+ // Then I expect a new warning, because the repository was selected again.
430
+ ToasterSteps.getWarningToast().should('be.visible').and('contain', 'Autocomplete is OFF');
431
+ });
305
432
  });
306
433
  });
@@ -33,7 +33,9 @@ describe('Home page: without repositories', () => {
33
33
  HomeSteps.getRepositoryErrorsWidget().should('be.visible');
34
34
  RepositoryErrorsWidgetSteps.getInfoMessage().should('contain', 'Some functionalities are not available because you are not connected to any repository.');
35
35
  RepositoryErrorsWidgetSteps.getCreateRepositoryBtn().should('be.visible');
36
- RepositoryErrorsWidgetSteps.getShowRemoteLocationsBtn().should('be.visible');
36
+ // There are no repositories to filter.
37
+ RepositoryErrorsWidgetSteps.getRepositoryFilter().should('not.exist');
38
+ RepositoryErrorsWidgetSteps.getLocalOnlyCheckbox().should('not.exist');
37
39
  });
38
40
 
39
41
  it('should render license widget', () => {
@@ -29,7 +29,8 @@ describe('Home page: without selected repository', () => {
29
29
  HomeSteps.getRepositoryErrorsWidget().should('be.visible');
30
30
  RepositoryErrorsWidgetSteps.getInfoMessage().should('contain', 'Some functionalities are not available because you are not connected to any repository.');
31
31
  RepositoryErrorsWidgetSteps.getCreateRepositoryBtn().should('be.visible');
32
- RepositoryErrorsWidgetSteps.getShowRemoteLocationsBtn().should('be.visible');
32
+ RepositoryErrorsWidgetSteps.getRepositoryFilter().should('be.visible');
33
+ RepositoryErrorsWidgetSteps.getLocalOnlyCheckbox().should('be.visible').and('be.checked');
33
34
  });
34
35
 
35
36
  // This test is unreliable with the current implementation of the license widget.
@@ -287,7 +287,7 @@ describe('Ontop repositories', () => {
287
287
 
288
288
  // WHEN: I log in as a user with maintenance permission
289
289
  cy.loginAs(REPOSITORY_MAINTAINER_USERNAME, PASSWORD);
290
- RepositorySteps.visit(false);
290
+ RepositorySteps.visit();
291
291
 
292
292
  // THEN: The repository is visible because the user can maintain it
293
293
  RepositorySteps.getRepositoryFromList(repositoryId).should('exist');
@@ -312,7 +312,7 @@ describe('Ontop repositories', () => {
312
312
  ModalDialogSteps.clickOnConfirmButton();
313
313
 
314
314
  // THEN: The changes are persisted
315
- RepositorySteps.visit(false);
315
+ RepositorySteps.visit();
316
316
  RepositorySteps.getRepositoryFromList(repositoryId).should('exist');
317
317
  RepositorySteps.editRepository(repositoryId);
318
318
  cy.wait('@getJDBCProperties');
@@ -348,7 +348,7 @@ describe('Ontop repositories', () => {
348
348
  ModalDialogSteps.clickOnConfirmButton();
349
349
 
350
350
  // THEN: The OBDA file changes are persisted
351
- RepositorySteps.visit(false);
351
+ RepositorySteps.visit();
352
352
  RepositorySteps.getRepositoryFromList(repositoryId).should('exist');
353
353
  RepositorySteps.editRepository(repositoryId);
354
354
  cy.wait('@getJDBCProperties');
@@ -2,7 +2,7 @@ import {ConnectorsSteps} from "../../../steps/setup/connectors-steps";
2
2
  import HomeSteps from "../../../steps/home-steps";
3
3
  import {MainMenuSteps} from "../../../steps/main-menu-steps";
4
4
 
5
- const expectedButtons = ['elasticsearch', 'opensearch', 'solr', 'lucene', 'kafka', 'chatgpt-retrieval'];
5
+ const expectedButtons = ['elasticsearch', 'opensearch', 'lucene', 'kafka', 'chatgpt-retrieval'];
6
6
 
7
7
  function verifyConnectorsPage() {
8
8
  expectedButtons.forEach(buttonId => {
@@ -22,7 +22,7 @@ describe('Plugins without selected repository', () => {
22
22
  const verifyInitialStateWithoutSelectedRepository = () => {
23
23
  ErrorSteps.verifyNoConnectedRepoMessage();
24
24
  PluginsSteps.getPluginsView().should('be.visible');
25
- PluginsSteps.getSearchPluginBar().should('not.be.visible');
25
+ PluginsSteps.getSearchPluginBar().should('not.exist');
26
26
  PluginsSteps.getPluginsList().should('not.exist');
27
27
  };
28
28
  })
@@ -18,7 +18,7 @@ describe('User and Access', () => {
18
18
  const DEFAULT_ADMIN_PASSWORD = 'root';
19
19
 
20
20
  context('User and access management', () => {
21
- const user = 'user';
21
+ const user = 'user' + Date.now();
22
22
  let repoName;
23
23
 
24
24
  before(() => {
@@ -428,7 +428,7 @@ describe('User and Access', () => {
428
428
  let repositoryId1;
429
429
  let repositoryId2;
430
430
  let repositoryId3;
431
- const graphqlUser = 'graphqlUser';
431
+ const graphqlUser = 'graphqlUser' + Date.now();
432
432
 
433
433
  beforeEach(() => {
434
434
  RepositoriesStubs.spyGetRepositories();
@@ -649,7 +649,7 @@ describe('User and Access', () => {
649
649
  let writeRepositoryId;
650
650
  let graphQLOnlyRepositoryId;
651
651
  let maintainedRepositoryId;
652
- const maintainerUser = 'maintainerUser';
652
+ const maintainerUser = 'maintainerUser' + Date.now();
653
653
 
654
654
  beforeEach(() => {
655
655
  RepositoriesStubs.spyGetRepositories();
@@ -705,7 +705,7 @@ describe('User and Access', () => {
705
705
  // Wait for the login to complete, otherwise visiting another page aborts it.
706
706
  cy.url().should('not.include', '/login');
707
707
  // And navigate to the repository list view.
708
- RepositorySteps.visit(false);
708
+ RepositorySteps.visit();
709
709
 
710
710
  // Then I should see all repositories for which the user has at least read permission.
711
711
  RepositorySteps.getRepositories().should('have.length', 4);
@@ -766,7 +766,7 @@ describe('User and Access', () => {
766
766
  LoginSteps.loginWithUser(maintainerUser, PASSWORD);
767
767
  // Wait for the login to complete, otherwise visiting another page aborts it.
768
768
  cy.url().should('not.include', '/login');
769
- RepositorySteps.visit(false);
769
+ RepositorySteps.visit();
770
770
 
771
771
  // When the user opens the edit page of the repository they can maintain.
772
772
  RepositorySteps.getEditRepositoryButton(maintainedRepositoryId).should('be.visible');
@@ -230,7 +230,7 @@ describe('TTYG chat list', () => {
230
230
  HomeSteps.visit();
231
231
  cy.wait('@get-chat');
232
232
  // and came back to the ttyg page
233
- TTYGViewSteps.visit(false);
233
+ TTYGViewSteps.visit();
234
234
 
235
235
  // Then I expect to last used chat be selected.
236
236
  TTYGViewSteps.getChatFromGroup(0, 2).should('have.class', 'selected');
@@ -61,7 +61,7 @@ describe('TTYG create chat', () => {
61
61
  // and returns to the TTYG page
62
62
  TTYGStubs.stubChatsListGet("/ttyg/chats/create/get-chats-after-create.json");
63
63
  TTYGStubs.stubAgentGet();
64
- TTYGViewSteps.visit(false);
64
+ TTYGViewSteps.visit();
65
65
  cy.wait('@get-chat-list');
66
66
  // Then I expect newly created chat be selected.
67
67
  TTYGViewSteps.getChatFromGroup(0, 0).should('contain', 'New chat of Han Solo is a character');
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "Elasticsearch": "http://www.ontotext.com/connectors/elasticsearch#",
3
3
  "OpenSearch": "http://www.ontotext.com/connectors/opensearch#",
4
- "Solr": "http://www.ontotext.com/connectors/solr#",
5
4
  "Lucene": "http://www.ontotext.com/connectors/lucene#",
6
5
  "Kafka": "http://www.ontotext.com/connectors/kafka#",
7
6
  "ChatGPT Retrieval": "http://www.ontotext.com/connectors/retrieval#"
@@ -0,0 +1,149 @@
1
+ @prefix gn: <http://www.ontotext.com/graph-navigator#> .
2
+
3
+ gn:settings gn:defaultPrefix """PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
4
+ PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
5
+ PREFIX owl: <http://www.w3.org/2002/07/owl#>
6
+ """;
7
+ gn:schemaLabelProperty "rdfs:label";
8
+ gn:dataLabelProperty "rdfs:label";
9
+ gn:classTreeQuery """
10
+ SELECT ?class ?label ?parent
11
+ WHERE {
12
+ {
13
+ ?class a rdfs:Class
14
+ } UNION {
15
+ ?class a owl:Class
16
+ }
17
+ FILTER ISIRI(?class)
18
+ OPTIONAL {
19
+ ?class rdfs:label ?label
20
+ ${labelLanguageFilter}
21
+ }
22
+ OPTIONAL {?class rdfs:subClassOf ?parent. FILTER ISIRI(?parent)}
23
+ }
24
+ """;
25
+ gn:classInfoQuery """SELECT ?class ?label ?instcount WHERE {
26
+ VALUES(?class) {${ids}}
27
+ OPTIONAL {
28
+ ?class ${schemaLabelProperty} ?label
29
+ ${labelLanguageFilter}
30
+ }
31
+ BIND(\"\" as ?instcount)
32
+ }""";
33
+ gn:linkTypesQuery """SELECT DISTINCT ?link ?instcount ?label WHERE {
34
+ ${linkTypesPattern}
35
+ OPTIONAL {
36
+ ?link ${schemaLabelProperty} ?label
37
+ ${labelLanguageFilter}
38
+ }
39
+ }""";
40
+ gn:linkTypesPattern """
41
+ { ?link a rdf:Property }
42
+ UNION
43
+ { ?link a owl:ObjectProperty }
44
+ BIND('' as ?instcount)
45
+ """;
46
+ gn:linkTypesInfoQuery """SELECT ?link ?label WHERE {
47
+ VALUES(?link) {${ids}}
48
+ OPTIONAL {
49
+ ?link ${schemaLabelProperty} ?label
50
+ ${labelLanguageFilter}
51
+ }
52
+ }""";
53
+ gn:propertyInfoQuery """SELECT ?property ?label WHERE {
54
+ VALUES(?property) {${ids}}
55
+ OPTIONAL {
56
+ ?property ${schemaLabelProperty} ?label
57
+ ${labelLanguageFilter}
58
+ }
59
+ }""";
60
+ gn:elementInfoQuery """
61
+ CONSTRUCT {
62
+ ?inst <urn:reactodia:sparql:type> ?class .
63
+ ?inst <urn:reactodia:sparql:label> ?label .
64
+ ?inst ?propType ?propValue.
65
+ } WHERE {
66
+ VALUES (?inst) {${ids}}
67
+ OPTIONAL { ?inst a ?class }
68
+ OPTIONAL {
69
+ ?inst ${dataLabelProperty} ?label
70
+ ${labelLanguageFilter}
71
+ }
72
+ OPTIONAL {
73
+ ${propertyConfigurations}
74
+ FILTER (isLiteral(?propValue))
75
+ ${valueLanguageFilter}
76
+ }
77
+ }
78
+ """;
79
+ gn:linksInfoQuery """SELECT ?source ?type ?target
80
+ WHERE {
81
+ ${linkConfigurations}
82
+ VALUES (?source) {${sourceIris}}
83
+ VALUES (?target) {${targetIris}}
84
+ }""";
85
+ gn:imageQueryPattern "{ ?inst ?linkType ?image } UNION { [] ?linkType ?inst. BIND(?inst as ?image) }";
86
+ gn:linkTypesOfQuery """
87
+ SELECT DISTINCT ?link ?direction
88
+ WHERE {
89
+ ${linkConfigurations}
90
+ }
91
+ """;
92
+ gn:linkTypesStatisticsQuery """
93
+ SELECT ?link ?outCount ?inCount
94
+ WHERE {
95
+ {
96
+ SELECT (${linkId} as ?link) (count(?outObject) as ?outCount) WHERE {
97
+ ${linkConfigurationOut}
98
+ ${navigateElementFilterOut}
99
+ } LIMIT 101
100
+ } {
101
+ SELECT (${linkId} as ?link) (count(?inObject) as ?inCount) WHERE {
102
+ ${linkConfigurationIn}
103
+ ${navigateElementFilterIn}
104
+ } LIMIT 101
105
+ }
106
+ }
107
+ """;
108
+ gn:lookupQuery """SELECT ${outerProjection} WHERE {
109
+ ${filterInnerPrelude}
110
+ {
111
+ SELECT DISTINCT ${innerProjection} WHERE {
112
+ ${filterByType}
113
+ ${filterByRefElementLink}
114
+ ${filterByText}
115
+ ${filterAdditionalRestriction}
116
+ } ${orderBy} ${limit}
117
+ }
118
+ ${queryTypes}
119
+ ${queryElementInfo}
120
+ } ${orderBy}""";
121
+ gn:filterInnerPrelude "";
122
+ gn:filterRefElementLinkPattern "";
123
+ gn:filterTypePattern "?inst a ?instType. ?instType rdfs:subClassOf* ?class";
124
+ gn:filterElementInfoPattern """
125
+ OPTIONAL {?inst rdf:type ?foundClass}
126
+ BIND (coalesce(?foundClass, owl:Thing) as ?class)
127
+ OPTIONAL {
128
+ ?inst ${dataLabelProperty} ?label
129
+ ${labelLanguageFilter}
130
+ }
131
+ """;
132
+ gn:filterAdditionalRestriction "";
133
+ gn:openWorldLinks false;
134
+ gn:openWorldProperties "no";
135
+ gn:acceptBlankNodes "no";
136
+ gn:unknownSetting "x";
137
+ gn:blankNodeStatementLimit "20"^^<http://www.w3.org/2001/XMLSchema#int>;
138
+ gn:filterOnlyLanguages [ a <http://www.w3.org/1999/02/22-rdf-syntax-ns#List>;
139
+ <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "en";
140
+ <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil>
141
+ ];
142
+ gn:fullTextSearch [
143
+ gn:prefix "";
144
+ gn:queryPattern """?inst ${dataLabelProperty} ?search1
145
+ FILTER regex(COALESCE(str(?search1)), \"${text}\", \"i\")
146
+ BIND(0 as ?score)
147
+ """;
148
+ gn:extractLabel true
149
+ ] .
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "graphdb-workbench-tests",
3
- "version": "4.0.0-TR13",
3
+ "version": "4.0.0-TR15",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "graphdb-workbench-tests",
9
- "version": "4.0.0-TR13",
9
+ "version": "4.0.0-TR15",
10
10
  "license": "Apache-2.0",
11
11
  "devDependencies": {
12
12
  "@bahmutov/cypress-code-coverage": "^2.7.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphdb-workbench-tests",
3
- "version": "4.0.0-TR13",
3
+ "version": "4.0.0-TR15",
4
4
  "description": "Cypress tests for GraphDB workbench",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,7 +1,7 @@
1
1
  export class ErrorSteps {
2
2
 
3
3
  static getRepositoryErrors() {
4
- return cy.get('.card.repository-errors')
4
+ return cy.get('.repository-errors')
5
5
  }
6
6
 
7
7
  static verifyError(errorMessage) {
@@ -44,6 +44,20 @@ export class GraphNavigatorSteps extends BaseSteps {
44
44
  return GraphNavigatorSteps.getCanvas().find(`[data-element-id]`).contains(text).first();
45
45
  }
46
46
 
47
+ static getSearchInput() {
48
+ return GraphNavigatorSteps.getComponent().find('.reactodia-unified-search__search-input');
49
+ }
50
+
51
+ static getSearchSectionTab(name) {
52
+ return GraphNavigatorSteps.getComponent().find('.reactodia-unified-search__section-tab').contains(name);
53
+ }
54
+
55
+ static searchEntities(text) {
56
+ GraphNavigatorSteps.getSearchInput().click();
57
+ GraphNavigatorSteps.getSearchSectionTab('Entities').click();
58
+ GraphNavigatorSteps.getSearchInput().clear().type(`${text}{enter}`);
59
+ }
60
+
47
61
  static getSettingsButton() {
48
62
  return cy.get('[data-test="graph-navigator-settings-btn"]');
49
63
  }
@@ -73,6 +87,14 @@ export class GraphNavigatorSteps extends BaseSteps {
73
87
  return GraphNavigatorSteps.getSettingsPopover().contains(fileName);
74
88
  }
75
89
 
90
+ static getUploadErrors() {
91
+ return this.getByTestId('graph-navigator-settings-upload-errors');
92
+ }
93
+
94
+ static getUploadError(property) {
95
+ return GraphNavigatorSteps.getUploadErrors().contains('[data-test="graph-navigator-settings-upload-error"]', property);
96
+ }
97
+
76
98
  static getUploadButton() {
77
99
  return GraphNavigatorSteps.getSettingsPopover().find('.p-fileupload-upload-button');
78
100
  }
@@ -3,17 +3,14 @@ import {BaseSteps} from "./base-steps.js";
3
3
 
4
4
  export class RepositorySteps extends BaseSteps {
5
5
 
6
- static visit(waitPageLoaded = true) {
6
+ static visit() {
7
7
  cy.intercept('/rest/locations').as('getLocations');
8
8
  cy.intercept(REPOSITORIES_URL + '/all').as('getRepositories');
9
9
  cy.visit('/repository');
10
- if (waitPageLoaded) {
11
- RepositorySteps.waitLoader();
12
- }
10
+ RepositorySteps.waitUntilRepositoriesPageIsLoaded();
11
+ // Consumes the initial locations request, so a later cy.wait('@getLocations') in a test matches the next one.
12
+ // It must stay after the page-loaded check, which has a longer timeout than cy.wait for the request to start.
13
13
  cy.wait('@getLocations');
14
- if (waitPageLoaded) {
15
- RepositorySteps.waitUntilRepositoriesPageIsLoaded();
16
- }
17
14
  }
18
15
 
19
16
  static visitEditPage(repositoryId) {
@@ -36,9 +33,12 @@ export class RepositorySteps extends BaseSteps {
36
33
  return cy.get('#wb-repositories-addRepositoryLink');
37
34
  }
38
35
 
36
+ // The page loader is hidden once the locations are loaded, regardless of the user's role.
39
37
  static waitUntilRepositoriesPageIsLoaded() {
40
- RepositorySteps.getRepositoriesDropdown().should('not.be.disabled');
41
- RepositorySteps.getCreateRepositoryButton().should('be.visible').and('not.be.disabled');
38
+ RepositorySteps.getRepositoriesPage()
39
+ .should('be.visible')
40
+ .find('.ot-loader-new-content')
41
+ .should('not.be.visible');
42
42
  }
43
43
 
44
44
  static getRepositoriesDropdown() {
@@ -190,7 +190,7 @@ export class AclManagementSteps {
190
190
  }
191
191
 
192
192
  static getWarningIcon(index) {
193
- return this.getSavedRoleField(index).get('.ri-alert-line');
193
+ return this.getSavedRoleField(index).find('.ri-alert-line');
194
194
  }
195
195
 
196
196
  static getWarningIconTooltipText(index) {
@@ -3,6 +3,10 @@ export class ToasterSteps {
3
3
  return cy.get('.toast');
4
4
  }
5
5
 
6
+ static getWarningToast() {
7
+ return ToasterSteps.getToast().filter('.warning');
8
+ }
9
+
6
10
  static verifyTitle(title) {
7
11
  return ToasterSteps.getToast()
8
12
  .find('.toast-title')
@@ -1,21 +1,8 @@
1
1
  import {BaseSteps} from "../base-steps";
2
- import {DeprecationSteps} from '../deprecation-banner/deprecation-banner-steps.js';
3
2
 
4
3
  export class TTYGViewSteps extends BaseSteps {
5
- /**
6
- * Visits the TTYG page and closes the Solr deprecation banner by default.
7
- *
8
- * @param {boolean} closeSolrBanner Whether to close the banner. Set to `false`
9
- * when revisiting the page after the banner has already been closed.
10
- */
11
- static visit(closeSolrBanner = true) {
4
+ static visit() {
12
5
  cy.visit('/ttyg');
13
- // Temporary workaround until the Solr deprecation banner is removed.
14
- // The banner changes the page layout, causing tests to fail randomly.
15
- // This quick fix prevents GraphDB build failures and should be deleted together with the banner.
16
- if (closeSolrBanner) {
17
- DeprecationSteps.closeBanner();
18
- }
19
6
  }
20
7
 
21
8
  static getTtygView() {
@@ -7,8 +7,12 @@ export class RepositoryErrorsWidgetSteps {
7
7
  return RepositoryErrorsWidgetSteps.getWidget().find('.create-repository-btn');
8
8
  }
9
9
 
10
- static getShowRemoteLocationsBtn() {
11
- return RepositoryErrorsWidgetSteps.getWidget().find('.show-remote-locations-btn');
10
+ static getRepositoryFilter() {
11
+ return RepositoryErrorsWidgetSteps.getWidget().getByTestId('core-errors-repository-filter');
12
+ }
13
+
14
+ static getLocalOnlyCheckbox() {
15
+ return RepositoryErrorsWidgetSteps.getWidget().getByTestId('core-errors-local-only-checkbox');
12
16
  }
13
17
 
14
18
  static getInfoMessage() {
@@ -0,0 +1,27 @@
1
+ import {Stubs} from './stubs';
2
+
3
+ export class GraphNavigatorStubs extends Stubs {
4
+ /**
5
+ * Makes the full-text search of the Graph Navigator settings use the GraphDB autocomplete index.
6
+ */
7
+ static stubSettingsWithAutocomplete() {
8
+ cy.intercept('GET', '/rest/repositories/*/graph-navigator/settings', (req) => {
9
+ req.continue((res) => {
10
+ res.body.fullTextSearch.prefix = 'PREFIX auto: <http://www.ontotext.com/plugins/autocomplete#>';
11
+ });
12
+ }).as('graph-navigator-settings');
13
+ }
14
+
15
+ /**
16
+ * Aliases the SPARQL requests that look up entities by the given text as `entity-lookup`.
17
+ */
18
+ static spyEntityLookup(text) {
19
+ cy.intercept('POST', '/repositories/*', (req) => {
20
+ // The body is form-encoded (`query=...`), so decode it to match the SPARQL text.
21
+ const body = new URLSearchParams(String(req.body)).get('query') ?? '';
22
+ if (body.includes('SELECT ?inst ?class ?label WHERE') && body.includes(text)) {
23
+ req.alias = 'entity-lookup';
24
+ }
25
+ });
26
+ }
27
+ }
@@ -50,7 +50,6 @@ export class LicenseStubs {
50
50
  "productType": "enterprise",
51
51
  "licenseCapabilities": [
52
52
  "Lucene connector",
53
- "Solr connector",
54
53
  "Elasticsearch connector",
55
54
  "OpenSearch connector",
56
55
  "Kafka connector",
@@ -114,7 +113,6 @@ export class LicenseStubs {
114
113
  "OpenSearch connector",
115
114
  "Lucene connector",
116
115
  "Kafka connector",
117
- "Solr connector",
118
116
  "Cluster",
119
117
  "Elasticsearch connector"
120
118
  ],
@@ -140,7 +138,6 @@ export class LicenseStubs {
140
138
  "OpenSearch connector",
141
139
  "Lucene connector",
142
140
  "Kafka connector",
143
- "Solr connector",
144
141
  "Cluster",
145
142
  "Elasticsearch connector"
146
143
  ],
@@ -1,137 +0,0 @@
1
- import HomeSteps from '../../../steps/home-steps.js';
2
- import {DeprecationSteps} from '../../../steps/deprecation-steps.js';
3
- import {LoginSteps} from '../../../steps/login-steps.js';
4
- import {GuidesStubs} from '../../../stubs/guides/guides-stubs.js';
5
- import {GuideSteps} from '../../../steps/guides/guide-steps.js';
6
- import {GuideDialogSteps} from '../../../steps/guides/guide-dialog-steps.js';
7
-
8
- describe('Solr deprecation banner', () => {
9
- context('Security disabled', () => {
10
- it('should keep the Solr deprecation banner hidden after the anonymous user dismisses it', () => {
11
- // GIVEN: I have opened the application with security disabled.
12
- HomeSteps.visit();
13
-
14
- // THEN: I expect the Solr deprecation banner to be visible.
15
- DeprecationSteps.getDeprecationBanner()
16
- .should('be.visible')
17
- .should('contain.text', 'The Solr connector is deprecated and will be removed in GraphDB 12');
18
-
19
- // WHEN: I dismiss the Solr deprecation banner.
20
- DeprecationSteps.closeBanner();
21
- // THEN: I expect the Solr deprecation banner to be hidden.
22
- DeprecationSteps.getDeprecationBanner().should('not.exist');
23
-
24
- // WHEN: I reload the application.
25
- HomeSteps.visit();
26
- // THEN: I expect the Solr deprecation banner to remain hidden.
27
- DeprecationSteps.getDeprecationBanner().should('not.exist');
28
- });
29
- });
30
-
31
- context('Security enabled', () => {
32
- const PASSWORD = 'root';
33
- const USER_USERNAME = 'username';
34
-
35
- beforeEach(() => {
36
- cy.createUser({
37
- username: USER_USERNAME,
38
- password: PASSWORD
39
- });
40
-
41
- cy.switchOnSecurity()
42
- .then(() => cy.loginAsAdmin())
43
- .then(() => cy.switchOnFreeAccess(true));
44
- });
45
-
46
- afterEach(() => {
47
- cy.loginAsAdmin()
48
- .then(() => {
49
- cy.deleteUser(USER_USERNAME, true);
50
- cy.switchOffFreeAccess(true);
51
- cy.switchOffSecurity(true);
52
- });
53
- });
54
-
55
- it('should persist separate Solr banner states for anonymous and logged-in users', () => {
56
- // GIVEN: Security and free access are enabled, and no user is logged in.
57
- HomeSteps.visit();
58
-
59
- // THEN: I expect the Solr deprecation banner to be visible for the anonymous user.
60
- DeprecationSteps.getDeprecationBanner()
61
- .should('be.visible')
62
- .should('contain.text', 'The Solr connector is deprecated and will be removed in GraphDB 12');
63
-
64
- // WHEN: The anonymous user dismisses the banner.
65
- DeprecationSteps.closeBanner();
66
- // THEN: I expect the banner to be hidden for the anonymous user.
67
- DeprecationSteps.getDeprecationBanner().should('not.exist');
68
-
69
- // WHEN: I log in as a user who has not dismissed the banner.
70
- cy.loginAs(USER_USERNAME, PASSWORD);
71
- HomeSteps.visit();
72
-
73
- // THEN: I expect the banner to be visible for the logged-in user.
74
- DeprecationSteps.getDeprecationBanner()
75
- .should('be.visible')
76
- .should('contain.text', 'The Solr connector is deprecated and will be removed in GraphDB 12');
77
-
78
- // WHEN: The logged-in user dismisses the banner.
79
- DeprecationSteps.closeBanner();
80
- // THEN: I expect the banner to be hidden for the logged-in user.
81
- DeprecationSteps.getDeprecationBanner().should('not.exist');
82
-
83
- // WHEN: The user logs out.
84
- LoginSteps.logout();
85
- // THEN: I expect the banner to remain hidden because the anonymous user has already dismissed it.
86
- DeprecationSteps.getDeprecationBanner().should('not.exist');
87
-
88
- // WHEN: I log in as the administrator, who has not dismissed the banner.
89
- cy.loginAs('admin', 'root');
90
- HomeSteps.visit();
91
- // THEN: I expect the banner to be visible for the administrator.
92
- DeprecationSteps.getDeprecationBanner()
93
- .should('be.visible')
94
- .should('contain.text', 'The Solr connector is deprecated and will be removed in GraphDB 12');
95
-
96
- // WHEN: The administrator logs out.
97
- LoginSteps.logout();
98
- // THEN: I expect the banner to remain hidden because the anonymous user has already dismissed it.
99
- DeprecationSteps.getDeprecationBanner().should('not.exist');
100
-
101
- // WHEN: I log in again as the user who previously dismissed the banner.
102
- cy.loginAs(USER_USERNAME, PASSWORD);
103
- HomeSteps.visit();
104
- // THEN: I expect the banner to remain hidden for the logged-in user.
105
- DeprecationSteps.getDeprecationBanner().should('not.exist');
106
- });
107
- });
108
-
109
- context('User guides', () => {
110
- it('should hide the Solr deprecation banner while a guide is running', () => {
111
- // GIVEN: The guides are loaded and ready to be started.
112
- GuidesStubs.stubMainMenuGuide();
113
- GuideSteps.visit();
114
- GuideSteps.verifyGuidesListExists();
115
- cy.wait('@getGuides');
116
-
117
- // WHEN: I am on an application page, and the Solr deprecation banner has not been dismissed by the current user.
118
- // THEN: I expect the Solr deprecation banner to be visible because it has not been dismissed by the current user.
119
- DeprecationSteps.getDeprecationBanner()
120
- .should('be.visible')
121
- .should('contain.text', 'The Solr connector is deprecated and will be removed in GraphDB 12');
122
-
123
- // WHEN: I start a guide.
124
- GuideSteps.runFirstGuide();
125
- // THEN: I expect the Solr deprecation banner to be hidden while the guide is running.
126
- DeprecationSteps.getDeprecationBanner().should('not.exist');
127
-
128
- // WHEN: I cancel the guide.
129
- GuideDialogSteps.clickOnCancelButton();
130
- GuideDialogSteps.clickConfirmCancelDialogExitButton();
131
- // THEN: I expect the Solr deprecation banner to be visible again.
132
- DeprecationSteps.getDeprecationBanner()
133
- .should('be.visible')
134
- .should('contain.text', 'The Solr connector is deprecated and will be removed in GraphDB 12');
135
- });
136
- });
137
- });
@@ -1,13 +0,0 @@
1
- export class DeprecationSteps {
2
- static getDeprecationBanner() {
3
- return cy.get('onto-deprecation-banner');
4
- }
5
-
6
- static getCloseButton() {
7
- return DeprecationSteps.getDeprecationBanner().find('.close-button');
8
- }
9
-
10
- static closeBanner() {
11
- DeprecationSteps.getCloseButton().click();
12
- }
13
- }
@@ -1,13 +0,0 @@
1
- export class DeprecationSteps {
2
- static getDeprecationBanner() {
3
- return cy.get('onto-deprecation-banner');
4
- }
5
-
6
- static getCloseButton() {
7
- return DeprecationSteps.getDeprecationBanner().find('.close-button');
8
- }
9
-
10
- static closeBanner() {
11
- DeprecationSteps.getCloseButton().click();
12
- }
13
- }