19315: Add new test for process details
[arvados-workbench2.git] / cypress / integration / process.spec.js
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 describe('Process tests', function() {
6     let activeUser;
7     let adminUser;
8
9     before(function() {
10         // Only set up common users once. These aren't set up as aliases because
11         // aliases are cleaned up after every test. Also it doesn't make sense
12         // to set the same users on beforeEach() over and over again, so we
13         // separate a little from Cypress' 'Best Practices' here.
14         cy.getUser('admin', 'Admin', 'User', true, true)
15             .as('adminUser').then(function() {
16                 adminUser = this.adminUser;
17             }
18         );
19         cy.getUser('user', 'Active', 'User', false, true)
20             .as('activeUser').then(function() {
21                 activeUser = this.activeUser;
22             }
23         );
24     });
25
26     beforeEach(function() {
27         cy.clearCookies();
28         cy.clearLocalStorage();
29     });
30
31     function setupDockerImage(image_name) {
32         // Create a collection that will be used as a docker image for the tests.
33         cy.createCollection(adminUser.token, {
34             name: 'docker_image',
35             manifest_text: ". d21353cfe035e3e384563ee55eadbb2f+67108864 5c77a43e329b9838cbec18ff42790e57+55605760 0:122714624:sha256:d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678.tar\n"
36         }).as('dockerImage').then(function(dockerImage) {
37             // Give read permissions to the active user on the docker image.
38             cy.createLink(adminUser.token, {
39                 link_class: 'permission',
40                 name: 'can_read',
41                 tail_uuid: activeUser.user.uuid,
42                 head_uuid: dockerImage.uuid
43             }).as('dockerImagePermission').then(function() {
44                 // Set-up docker image collection tags
45                 cy.createLink(activeUser.token, {
46                     link_class: 'docker_image_repo+tag',
47                     name: image_name,
48                     head_uuid: dockerImage.uuid,
49                 }).as('dockerImageRepoTag');
50                 cy.createLink(activeUser.token, {
51                     link_class: 'docker_image_hash',
52                     name: 'sha256:d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678',
53                     head_uuid: dockerImage.uuid,
54                 }).as('dockerImageHash');
55             })
56         });
57         return cy.getAll('@dockerImage', '@dockerImageRepoTag', '@dockerImageHash',
58             '@dockerImagePermission').then(function([dockerImage]) {
59                 return dockerImage;
60             });
61     }
62
63     function createContainerRequest(user, name, docker_image, command, reuse = false, state = 'Uncommitted') {
64         return setupDockerImage(docker_image).then(function(dockerImage) {
65             return cy.createContainerRequest(user.token, {
66                 name: name,
67                 command: command,
68                 container_image: dockerImage.portable_data_hash, // for some reason, docker_image doesn't work here
69                 output_path: 'stdout.txt',
70                 priority: 1,
71                 runtime_constraints: {
72                     vcpus: 1,
73                     ram: 1,
74                 },
75                 use_existing: reuse,
76                 state: state,
77                 mounts: {
78                     foo: {
79                         kind: 'tmp',
80                         path: '/tmp/foo',
81                     }
82                 }
83             });
84         });
85     }
86
87     it('shows process logs', function() {
88         const crName = 'test_container_request';
89         createContainerRequest(
90             activeUser,
91             crName,
92             'arvados/jobs',
93             ['echo', 'hello world'],
94             false, 'Committed')
95         .then(function(containerRequest) {
96             cy.loginAs(activeUser);
97             cy.goToPath(`/processes/${containerRequest.uuid}`);
98             cy.get('[data-cy=process-details]').should('contain', crName);
99             cy.get('[data-cy=process-logs]')
100                 .should('contain', 'No logs yet')
101                 .and('not.contain', 'hello world');
102             cy.createLog(activeUser.token, {
103                 object_uuid: containerRequest.container_uuid,
104                 properties: {
105                     text: 'hello world'
106                 },
107                 event_type: 'stdout'
108             }).then(function(log) {
109                 cy.get('[data-cy=process-logs]')
110                     .should('not.contain', 'No logs yet')
111                     .and('contain', 'hello world');
112             })
113         });
114     });
115
116     it('shows process details', function() {
117         const crName = 'test_container_request';
118         createContainerRequest(
119             activeUser,
120             crName,
121             'arvados/jobs',
122             ['echo', 'hello world'],
123             false, 'Committed')
124         .then(function(containerRequest) {
125             cy.loginAs(activeUser);
126             cy.goToPath(`/processes/${containerRequest.uuid}`);
127             cy.get('[data-cy=process-details]').should('contain', crName);
128             cy.get('[data-cy=process-details-attributes-runtime-user]').contains(`Active User (${activeUser.user.uuid})`);
129         });
130     });
131
132     it('filters process logs by event type', function() {
133         const nodeInfoLogs = [
134             'Host Information',
135             'Linux compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p 5.4.0-1059-azure #62~18.04.1-Ubuntu SMP Tue Sep 14 17:53:18 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux',
136             'CPU Information',
137             'processor  : 0',
138             'vendor_id  : GenuineIntel',
139             'cpu family : 6',
140             'model      : 79',
141             'model name : Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz'
142         ];
143         const crunchRunLogs = [
144             '2022-03-22T13:56:22.542417997Z using local keepstore process (pid 3733) at http://localhost:46837, writing logs to keepstore.txt in log collection',
145             '2022-03-22T13:56:26.237571754Z crunch-run 2.4.0~dev20220321141729 (go1.17.1) started',
146             '2022-03-22T13:56:26.244704134Z crunch-run process has uid=0(root) gid=0(root) groups=0(root)',
147             '2022-03-22T13:56:26.244862836Z Executing container \'zzzzz-dz642-1wokwvcct9s9du3\' using docker runtime',
148             '2022-03-22T13:56:26.245037738Z Executing on host \'compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p\'',
149         ];
150         const stdoutLogs = [
151             'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec dui nisi, hendrerit porta sapien a, pretium dignissim purus.',
152             'Integer viverra, mauris finibus aliquet ultricies, dui mauris cursus justo, ut venenatis nibh ex eget neque.',
153             'In hac habitasse platea dictumst.',
154             'Fusce fringilla turpis id accumsan faucibus. Donec congue congue ex non posuere. In semper mi quis tristique rhoncus.',
155             'Interdum et malesuada fames ac ante ipsum primis in faucibus.',
156             'Quisque fermentum tortor ex, ut suscipit velit feugiat faucibus.',
157             'Donec vitae porta risus, at luctus nulla. Mauris gravida iaculis ipsum, id sagittis tortor egestas ac.',
158             'Maecenas condimentum volutpat nulla. Integer lacinia maximus risus eu posuere.',
159             'Donec vitae leo id augue gravida bibendum.',
160             'Nam libero libero, pretium ac faucibus elementum, mattis nec ex.',
161             'Nullam id laoreet nibh. Vivamus tellus metus, pretium quis justo ut, bibendum varius metus. Pellentesque vitae accumsan lorem, quis tincidunt augue.',
162             'Aliquam viverra nisi nulla, et efficitur dolor mattis in.',
163             'Sed at enim sit amet nulla tincidunt mattis. Aenean eget aliquet ex, non ultrices ex. Nulla ex tortor, vestibulum aliquam tempor ac, aliquam vel est.',
164             'Fusce auctor faucibus libero id venenatis. Etiam sodales, odio eu cursus efficitur, quam sem blandit ex, quis porttitor enim dui quis lectus. In id tincidunt felis.',
165             'Phasellus non ex quis arcu tempus faucibus molestie in sapien.',
166             'Duis tristique semper dolor, vitae pulvinar risus.',
167             'Aliquam tortor elit, luctus nec tortor eget, porta tristique nulla.',
168             'Nulla eget mollis ipsum.',
169         ];
170
171         createContainerRequest(
172             activeUser,
173             'test_container_request',
174             'arvados/jobs',
175             ['echo', 'hello world'],
176             false, 'Committed')
177         .then(function(containerRequest) {
178             cy.logsForContainer(activeUser.token, containerRequest.container_uuid,
179                 'node-info', nodeInfoLogs).as('nodeInfoLogs');
180             cy.logsForContainer(activeUser.token, containerRequest.container_uuid,
181                 'crunch-run', crunchRunLogs).as('crunchRunLogs');
182             cy.logsForContainer(activeUser.token, containerRequest.container_uuid,
183                 'stdout', stdoutLogs).as('stdoutLogs');
184             cy.getAll('@stdoutLogs', '@nodeInfoLogs', '@crunchRunLogs').then(function() {
185                 cy.loginAs(activeUser);
186                 cy.goToPath(`/processes/${containerRequest.uuid}`);
187                 // Should show main logs by default
188                 cy.get('[data-cy=process-logs-filter]').should('contain', 'Main logs');
189                 cy.get('[data-cy=process-logs]')
190                     .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
191                     .and('not.contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
192                     .and('contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
193                 // Select 'All logs'
194                 cy.get('[data-cy=process-logs-filter]').click();
195                 cy.get('body').contains('li', 'All logs').click();
196                 cy.get('[data-cy=process-logs]')
197                     .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
198                     .and('contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
199                     .and('contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
200                 // Select 'node-info' logs
201                 cy.get('[data-cy=process-logs-filter]').click();
202                 cy.get('body').contains('li', 'node-info').click();
203                 cy.get('[data-cy=process-logs]')
204                     .should('not.contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
205                     .and('contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
206                     .and('not.contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
207                 // Select 'stdout' logs
208                 cy.get('[data-cy=process-logs-filter]').click();
209                 cy.get('body').contains('li', 'stdout').click();
210                 cy.get('[data-cy=process-logs]')
211                     .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
212                     .and('not.contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
213                     .and('not.contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
214             });
215         });
216     });
217
218     it('should show runtime status indicators', function() {
219         // Setup running container with runtime_status error & warning messages
220         createContainerRequest(
221             activeUser,
222             'test_container_request',
223             'arvados/jobs',
224             ['echo', 'hello world'],
225             false, 'Committed')
226         .as('containerRequest')
227         .then(function(containerRequest) {
228             expect(containerRequest.state).to.equal('Committed');
229             expect(containerRequest.container_uuid).not.to.be.equal('');
230
231             cy.getContainer(activeUser.token, containerRequest.container_uuid)
232             .then(function(queuedContainer) {
233                 expect(queuedContainer.state).to.be.equal('Queued');
234             });
235             cy.updateContainer(adminUser.token, containerRequest.container_uuid, {
236                 state: 'Locked'
237             }).then(function(lockedContainer) {
238                 expect(lockedContainer.state).to.be.equal('Locked');
239
240                 cy.updateContainer(adminUser.token, lockedContainer.uuid, {
241                     state: 'Running',
242                     runtime_status: {
243                         error: 'Something went wrong',
244                         errorDetail: 'Process exited with status 1',
245                         warning: 'Free disk space is low',
246                     }
247                 })
248                 .as('runningContainer')
249                 .then(function(runningContainer) {
250                     expect(runningContainer.state).to.be.equal('Running');
251                     expect(runningContainer.runtime_status).to.be.deep.equal({
252                         'error': 'Something went wrong',
253                         'errorDetail': 'Process exited with status 1',
254                         'warning': 'Free disk space is low',
255                     });
256                 });
257             })
258         });
259         // Test that the UI shows the error and warning messages
260         cy.getAll('@containerRequest', '@runningContainer').then(function([containerRequest]) {
261             cy.loginAs(activeUser);
262             cy.goToPath(`/processes/${containerRequest.uuid}`);
263             cy.get('[data-cy=process-runtime-status-error]')
264                 .should('contain', 'Something went wrong')
265                 .and('contain', 'Process exited with status 1');
266             cy.get('[data-cy=process-runtime-status-warning]')
267                 .should('contain', 'Free disk space is low')
268                 .and('contain', 'No additional warning details available');
269         });
270
271
272         // Force container_count for testing
273         let containerCount = 2;
274         cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
275             req.reply((res) => {
276                 res.body.container_count = containerCount;
277             });
278         });
279
280         cy.getAll('@containerRequest').then(function([containerRequest]) {
281             cy.goToPath(`/processes/${containerRequest.uuid}`);
282             cy.get('[data-cy=process-runtime-status-retry-warning]')
283                 .should('contain', 'Process retried 1 time');
284         });
285
286         cy.getAll('@containerRequest').then(function([containerRequest]) {
287             containerCount = 3;
288             cy.goToPath(`/processes/${containerRequest.uuid}`);
289             cy.get('[data-cy=process-runtime-status-retry-warning]')
290                 .should('contain', 'Process retried 2 times');
291         });
292     });
293
294
295     const testInputs = [
296         {
297             definition: {
298                 "id": "#main/input_file",
299                 "label": "Label Description",
300                 "type": "File"
301             },
302             input: {
303                 "input_file": {
304                     "basename": "input1.tar",
305                     "class": "File",
306                     "location": "keep:00000000000000000000000000000000+01/input1.tar",
307                     "secondaryFiles": [
308                         {
309                             "basename": "input1-2.txt",
310                             "class": "File",
311                             "location": "keep:00000000000000000000000000000000+01/input1-2.txt"
312                         },
313                         {
314                             "basename": "input1-3.txt",
315                             "class": "File",
316                             "location": "keep:00000000000000000000000000000000+01/input1-3.txt"
317                         },
318                         {
319                             "basename": "input1-4.txt",
320                             "class": "File",
321                             "location": "keep:00000000000000000000000000000000+01/input1-4.txt"
322                         }
323                     ]
324                 }
325             }
326         },
327         {
328             definition: {
329                 "id": "#main/input_dir",
330                 "doc": "Doc Description",
331                 "type": "Directory"
332             },
333             input: {
334                 "input_dir": {
335                     "basename": "11111111111111111111111111111111+01",
336                     "class": "Directory",
337                     "location": "keep:11111111111111111111111111111111+01"
338                 }
339             }
340         },
341         {
342             definition: {
343                 "id": "#main/input_bool",
344                 "doc": ["Doc desc 1", "Doc desc 2"],
345                 "type": "boolean"
346             },
347             input: {
348                 "input_bool": true,
349             }
350         },
351         {
352             definition: {
353                 "id": "#main/input_int",
354                 "type": "int"
355             },
356             input: {
357                 "input_int": 1,
358             }
359         },
360         {
361             definition: {
362                 "id": "#main/input_long",
363                 "type": "long"
364             },
365             input: {
366                 "input_long" : 1,
367             }
368         },
369         {
370             definition: {
371                 "id": "#main/input_float",
372                 "type": "float"
373             },
374             input: {
375                 "input_float": 1.5,
376             }
377         },
378         {
379             definition: {
380                 "id": "#main/input_double",
381                 "type": "double"
382             },
383             input: {
384                 "input_double": 1.3,
385             }
386         },
387         {
388             definition: {
389                 "id": "#main/input_string",
390                 "type": "string"
391             },
392             input: {
393                 "input_string": "Hello World",
394             }
395         },
396         {
397             definition: {
398                 "id": "#main/input_file_array",
399                 "type": {
400                   "items": "File",
401                   "type": "array"
402                 }
403             },
404             input: {
405                 "input_file_array": [
406                     {
407                         "basename": "input2.tar",
408                         "class": "File",
409                         "location": "keep:00000000000000000000000000000000+02/input2.tar"
410                     },
411                     {
412                         "basename": "input3.tar",
413                         "class": "File",
414                         "location": "keep:00000000000000000000000000000000+03/input3.tar",
415                         "secondaryFiles": [
416                             {
417                                 "basename": "input3-2.txt",
418                                 "class": "File",
419                                 "location": "keep:00000000000000000000000000000000+03/input3-2.txt"
420                             }
421                         ]
422                     }
423                 ]
424             }
425         },
426         {
427             definition: {
428                 "id": "#main/input_dir_array",
429                 "type": {
430                   "items": "Directory",
431                   "type": "array"
432                 }
433             },
434             input: {
435                 "input_dir_array": [
436                     {
437                         "basename": "11111111111111111111111111111111+02",
438                         "class": "Directory",
439                         "location": "keep:11111111111111111111111111111111+02"
440                     },
441                     {
442                         "basename": "11111111111111111111111111111111+03",
443                         "class": "Directory",
444                         "location": "keep:11111111111111111111111111111111+03"
445                     }
446                 ]
447             }
448         },
449         {
450             definition: {
451                 "id": "#main/input_int_array",
452                 "type": {
453                   "items": "int",
454                   "type": "array"
455                 }
456             },
457             input: {
458                 "input_int_array": [
459                     1,
460                     3,
461                     5
462                 ]
463             }
464         },
465         {
466             definition: {
467                 "id": "#main/input_long_array",
468                 "type": {
469                   "items": "long",
470                   "type": "array"
471                 }
472             },
473             input: {
474                 "input_long_array": [
475                     10,
476                     20
477                 ]
478             }
479         },
480         {
481             definition: {
482                 "id": "#main/input_float_array",
483                 "type": {
484                   "items": "float",
485                   "type": "array"
486                 }
487             },
488             input: {
489                 "input_float_array": [
490                     10.2,
491                     10.4,
492                     10.6
493                 ]
494             }
495         },
496         {
497             definition: {
498                 "id": "#main/input_double_array",
499                 "type": {
500                   "items": "double",
501                   "type": "array"
502                 }
503             },
504             input: {
505                 "input_double_array": [
506                     20.1,
507                     20.2,
508                     20.3
509                 ]
510             }
511         },
512         {
513             definition: {
514                 "id": "#main/input_string_array",
515                 "type": {
516                   "items": "string",
517                   "type": "array"
518                 }
519             },
520             input: {
521                 "input_string_array": [
522                     "Hello",
523                     "World",
524                     "!"
525                 ]
526             }
527         }
528     ];
529
530     const testOutputs = [
531         {
532             definition: {
533                 "id": "#main/output_file",
534                 "label": "Label Description",
535                 "type": "File"
536             },
537             output: {
538                 "output_file": {
539                     "basename": "cat.png",
540                     "class": "File",
541                     "location": "cat.png"
542                 }
543             }
544         },
545         {
546             definition: {
547                 "id": "#main/output_file_with_secondary",
548                 "doc": "Doc Description",
549                 "type": "File"
550             },
551             output: {
552                 "output_file_with_secondary": {
553                     "basename": "main.dat",
554                     "class": "File",
555                     "location": "main.dat",
556                     "secondaryFiles": [
557                         {
558                             "basename": "secondary.dat",
559                             "class": "File",
560                             "location": "secondary.dat"
561                         },
562                         {
563                             "basename": "secondary2.dat",
564                             "class": "File",
565                             "location": "secondary2.dat"
566                         }
567                     ]
568                 }
569             }
570         },
571         {
572             definition: {
573                 "id": "#main/output_dir",
574                 "doc": ["Doc desc 1", "Doc desc 2"],
575                 "type": "Directory"
576             },
577             output: {
578                 "output_dir": {
579                     "basename": "outdir1",
580                     "class": "Directory",
581                     "location": "outdir1"
582                 }
583             }
584         },
585         {
586             definition: {
587                 "id": "#main/output_bool",
588                 "type": "boolean"
589             },
590             output: {
591                 "output_bool": true
592             }
593         },
594         {
595             definition: {
596                 "id": "#main/output_int",
597                 "type": "int"
598             },
599             output: {
600                 "output_int": 1
601             }
602         },
603         {
604             definition: {
605                 "id": "#main/output_long",
606                 "type": "long"
607             },
608             output: {
609                 "output_long": 1
610             }
611         },
612         {
613             definition: {
614                 "id": "#main/output_float",
615                 "type": "float"
616             },
617             output: {
618                 "output_float": 100.5
619             }
620         },
621         {
622             definition: {
623                 "id": "#main/output_double",
624                 "type": "double"
625             },
626             output: {
627                 "output_double": 100.3
628             }
629         },
630         {
631             definition: {
632                 "id": "#main/output_string",
633                 "type": "string"
634             },
635             output: {
636                 "output_string": "Hello output"
637             }
638         },
639         {
640             definition: {
641                 "id": "#main/output_file_array",
642                 "type": {
643                     "items": "File",
644                     "type": "array"
645                 }
646             },
647             output: {
648                 "output_file_array": [
649                     {
650                         "basename": "output2.tar",
651                         "class": "File",
652                         "location": "output2.tar"
653                     },
654                     {
655                         "basename": "output3.tar",
656                         "class": "File",
657                         "location": "output3.tar"
658                     }
659                 ]
660             }
661         },
662         {
663             definition: {
664                 "id": "#main/output_dir_array",
665                 "type": {
666                     "items": "Directory",
667                     "type": "array"
668                 }
669             },
670             output: {
671                 "output_dir_array": [
672                     {
673                         "basename": "outdir2",
674                         "class": "Directory",
675                         "location": "outdir2"
676                     },
677                     {
678                         "basename": "outdir3",
679                         "class": "Directory",
680                         "location": "outdir3"
681                     }
682                 ]
683             }
684         },
685         {
686             definition: {
687                 "id": "#main/output_int_array",
688                 "type": {
689                     "items": "int",
690                     "type": "array"
691                 }
692             },
693             output: {
694                 "output_int_array": [
695                     10,
696                     11,
697                     12
698                 ]
699             }
700         },
701         {
702             definition: {
703                 "id": "#main/output_long_array",
704                 "type": {
705                     "items": "long",
706                     "type": "array"
707                 }
708             },
709             output: {
710                 "output_long_array": [
711                     51,
712                     52
713                 ]
714             }
715         },
716         {
717             definition: {
718                 "id": "#main/output_float_array",
719                 "type": {
720                     "items": "float",
721                     "type": "array"
722                 }
723             },
724             output: {
725                 "output_float_array": [
726                     100.2,
727                     100.4,
728                     100.6
729                 ]
730             }
731         },
732         {
733             definition: {
734                 "id": "#main/output_double_array",
735                 "type": {
736                     "items": "double",
737                     "type": "array"
738                 }
739             },
740             output: {
741                 "output_double_array": [
742                     100.1,
743                     100.2,
744                     100.3
745                 ]
746             }
747         },
748         {
749             definition: {
750                 "id": "#main/output_string_array",
751                 "type": {
752                     "items": "string",
753                     "type": "array"
754                 }
755             },
756             output: {
757                 "output_string_array": [
758                     "Hello",
759                     "Output",
760                     "!"
761                 ]
762             }
763         }
764     ];
765
766     const verifyIOParameter = (name, label, doc, val, collection, multipleRows) => {
767         cy.get('table tr').contains(name).parents('tr').within(($mainRow) => {
768             label && cy.contains(label);
769
770             if (multipleRows) {
771                 cy.get($mainRow).nextUntil('[data-cy="process-io-param"]').as('secondaryRows');
772                 if (val) {
773                     if (Array.isArray(val)) {
774                         val.forEach(v => cy.get('@secondaryRows').contains(v));
775                     } else {
776                         cy.get('@secondaryRows').contains(val);
777                     }
778                 }
779                 if (collection) {
780                     cy.get('@secondaryRows').contains(collection);
781                 }
782             } else {
783                 if (val) {
784                     if (Array.isArray(val)) {
785                         val.forEach(v => cy.contains(v));
786                     } else {
787                         cy.contains(val);
788                     }
789                 }
790                 if (collection) {
791                     cy.contains(collection);
792                 }
793             }
794
795
796         });
797     };
798
799     const verifyIOParameterImage = (name, url) => {
800         cy.get('table tr').contains(name).parents('tr').within(() => {
801             cy.get('[alt="Inline Preview"]')
802                 .should('be.visible')
803                 .and(($img) => {
804                     expect($img[0].naturalWidth).to.be.greaterThan(0);
805                     expect($img[0].src).contains(url);
806                 })
807         });
808     };
809
810     it('displays IO parameters with keep links and previews', function() {
811         // Create output collection for real files
812         cy.createCollection(adminUser.token, {
813             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
814             owner_uuid: activeUser.user.uuid,
815         }).then((testOutputCollection) => {
816                     cy.loginAs(activeUser);
817
818                     cy.goToPath(`/collections/${testOutputCollection.uuid}`);
819
820                     cy.get('[data-cy=upload-button]').click();
821
822                     cy.fixture('files/cat.png', 'base64').then(content => {
823                         cy.get('[data-cy=drag-and-drop]').upload(content, 'cat.png');
824                         cy.get('[data-cy=form-submit-btn]').click();
825                         cy.waitForDom().get('[data-cy=form-submit-btn]').should('not.exist');
826                         // Confirm final collection state.
827                         cy.get('[data-cy=collection-files-panel]')
828                             .contains('cat.png').should('exist');
829                     });
830
831                     cy.getCollection(activeUser.token, testOutputCollection.uuid).as('testOutputCollection');
832                 });
833
834         // Get updated collection pdh
835         cy.getAll('@testOutputCollection').then(([testOutputCollection]) => {
836             // Add output uuid and inputs to container request
837             cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
838                 req.reply((res) => {
839                     res.body.output_uuid = testOutputCollection.uuid;
840                     res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
841                         content: testInputs.map((param) => (param.input)).reduce((acc, val) => (Object.assign(acc, val)), {})
842                     };
843                     res.body.mounts["/var/lib/cwl/workflow.json"] = {
844                         content: {
845                             $graph: [{
846                                 id: "#main",
847                                 inputs: testInputs.map((input) => (input.definition)),
848                                 outputs: testOutputs.map((output) => (output.definition))
849                             }]
850                         }
851                     };
852                 });
853             });
854
855             // Stub fake output collection
856             cy.intercept({method: 'GET', url: `**/arvados/v1/collections/${testOutputCollection.uuid}*`}, {
857                 statusCode: 200,
858                 body: {
859                     uuid: testOutputCollection.uuid,
860                     portable_data_hash: testOutputCollection.portable_data_hash,
861                 }
862             });
863
864             // Stub fake output json
865             cy.intercept({method: 'GET', url: '**/c%3Dzzzzz-4zz18-zzzzzzzzzzzzzzz/cwl.output.json'}, {
866                 statusCode: 200,
867                 body: testOutputs.map((param) => (param.output)).reduce((acc, val) => (Object.assign(acc, val)), {})
868             });
869
870             // Stub webdav response, points to output json
871             cy.intercept({method: 'PROPFIND', url: '*'}, {
872                 fixture: 'webdav-propfind-outputs.xml',
873             });
874         });
875
876         createContainerRequest(
877             activeUser,
878             'test_container_request',
879             'arvados/jobs',
880             ['echo', 'hello world'],
881             false, 'Committed')
882         .as('containerRequest');
883
884         cy.getAll('@containerRequest', '@testOutputCollection').then(function([containerRequest, testOutputCollection]) {
885             cy.goToPath(`/processes/${containerRequest.uuid}`);
886             cy.get('[data-cy=process-io-card] h6').contains('Inputs')
887                 .parents('[data-cy=process-io-card]').within(() => {
888                     verifyIOParameter('input_file', null, "Label Description", 'input1.tar', '00000000000000000000000000000000+01');
889                     verifyIOParameter('input_file', null, "Label Description", 'input1-2.txt', undefined, true);
890                     verifyIOParameter('input_file', null, "Label Description", 'input1-3.txt', undefined, true);
891                     verifyIOParameter('input_file', null, "Label Description", 'input1-4.txt', undefined, true);
892                     verifyIOParameter('input_dir', null, "Doc Description", '/', '11111111111111111111111111111111+01');
893                     verifyIOParameter('input_bool', null, "Doc desc 1, Doc desc 2", 'true');
894                     verifyIOParameter('input_int', null, null, '1');
895                     verifyIOParameter('input_long', null, null, '1');
896                     verifyIOParameter('input_float', null, null, '1.5');
897                     verifyIOParameter('input_double', null, null, '1.3');
898                     verifyIOParameter('input_string', null, null, 'Hello World');
899                     verifyIOParameter('input_file_array', null, null, 'input2.tar', '00000000000000000000000000000000+02');
900                     verifyIOParameter('input_file_array', null, null, 'input3.tar', undefined, true);
901                     verifyIOParameter('input_file_array', null, null, 'input3-2.txt', undefined, true);
902                     verifyIOParameter('input_dir_array', null, null, '/', '11111111111111111111111111111111+02');
903                     verifyIOParameter('input_dir_array', null, null, '/', '11111111111111111111111111111111+03', true);
904                     verifyIOParameter('input_int_array', null, null, ["1", "3", "5"]);
905                     verifyIOParameter('input_long_array', null, null, ["10", "20"]);
906                     verifyIOParameter('input_float_array', null, null, ["10.2", "10.4", "10.6"]);
907                     verifyIOParameter('input_double_array', null, null, ["20.1", "20.2", "20.3"]);
908                     verifyIOParameter('input_string_array', null, null, ["Hello", "World", "!"]);
909                 });
910             cy.get('[data-cy=process-io-card] h6').contains('Outputs')
911                 .parents('[data-cy=process-io-card]').within((ctx) => {
912                     cy.get(ctx).scrollIntoView();
913                     cy.get('[data-cy="io-preview-image-toggle"]').click();
914                     const outPdh = testOutputCollection.portable_data_hash;
915
916                     verifyIOParameter('output_file', null, "Label Description", 'cat.png', `${outPdh}`);
917                     verifyIOParameterImage('output_file', `/c=${outPdh}/cat.png`);
918                     verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'main.dat', `${outPdh}`);
919                     verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'secondary.dat', undefined, true);
920                     verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'secondary2.dat', undefined, true);
921                     verifyIOParameter('output_dir', null, "Doc desc 1, Doc desc 2", 'outdir1', `${outPdh}`);
922                     verifyIOParameter('output_bool', null, null, 'true');
923                     verifyIOParameter('output_int', null, null, '1');
924                     verifyIOParameter('output_long', null, null, '1');
925                     verifyIOParameter('output_float', null, null, '100.5');
926                     verifyIOParameter('output_double', null, null, '100.3');
927                     verifyIOParameter('output_string', null, null, 'Hello output');
928                     verifyIOParameter('output_file_array', null, null, 'output2.tar', `${outPdh}`);
929                     verifyIOParameter('output_file_array', null, null, 'output3.tar', undefined, true);
930                     verifyIOParameter('output_dir_array', null, null, 'outdir2', `${outPdh}`);
931                     verifyIOParameter('output_dir_array', null, null, 'outdir3', undefined, true);
932                     verifyIOParameter('output_int_array', null, null, ["10", "11", "12"]);
933                     verifyIOParameter('output_long_array', null, null, ["51", "52"]);
934                     verifyIOParameter('output_float_array', null, null, ["100.2", "100.4", "100.6"]);
935                     verifyIOParameter('output_double_array', null, null, ["100.1", "100.2", "100.3"]);
936                     verifyIOParameter('output_string_array', null, null, ["Hello", "Output", "!"]);
937                 });
938         });
939     });
940
941     it('displays IO parameters with no value', function() {
942
943         const fakeOutputUUID = 'zzzzz-4zz18-abcdefghijklmno';
944         const fakeOutputPDH = '11111111111111111111111111111111+99/';
945
946         cy.loginAs(activeUser);
947
948         // Add output uuid and inputs to container request
949         cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
950             req.reply((res) => {
951                 res.body.output_uuid = fakeOutputUUID;
952                 res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
953                     content: {}
954                 };
955                 res.body.mounts["/var/lib/cwl/workflow.json"] = {
956                     content: {
957                         $graph: [{
958                             id: "#main",
959                             inputs: testInputs.map((input) => (input.definition)),
960                             outputs: testOutputs.map((output) => (output.definition))
961                         }]
962                     }
963                 };
964             });
965         });
966
967         // Stub fake output collection
968         cy.intercept({method: 'GET', url: `**/arvados/v1/collections/${fakeOutputUUID}*`}, {
969             statusCode: 200,
970             body: {
971                 uuid: fakeOutputUUID,
972                 portable_data_hash: fakeOutputPDH,
973             }
974         });
975
976         // Stub fake output json
977         cy.intercept({method: 'GET', url: `**/c%3D${fakeOutputUUID}/cwl.output.json`}, {
978             statusCode: 200,
979             body: {}
980         });
981
982         cy.readFile('cypress/fixtures/webdav-propfind-outputs.xml').then((data) => {
983             // Stub webdav response, points to output json
984             cy.intercept({method: 'PROPFIND', url: '*'}, {
985                 statusCode: 200,
986                 body: data.replace(/zzzzz-4zz18-zzzzzzzzzzzzzzz/g, fakeOutputUUID)
987             });
988         });
989
990         createContainerRequest(
991             activeUser,
992             'test_container_request',
993             'arvados/jobs',
994             ['echo', 'hello world'],
995             false, 'Committed')
996         .as('containerRequest');
997
998         cy.getAll('@containerRequest').then(function([containerRequest]) {
999             cy.goToPath(`/processes/${containerRequest.uuid}`);
1000             cy.get('[data-cy=process-io-card] h6').contains('Inputs')
1001                 .parents('[data-cy=process-io-card]').within(() => {
1002                     cy.wait(2000);
1003                     cy.waitForDom();
1004                     cy.get('tbody tr').each((item) => {
1005                         cy.wrap(item).contains('No value');
1006                     });
1007                 });
1008             cy.get('[data-cy=process-io-card] h6').contains('Outputs')
1009                 .parents('[data-cy=process-io-card]').within(() => {
1010                     cy.get('tbody tr').each((item) => {
1011                         cy.wrap(item).contains('No value');
1012                     });
1013                 });
1014         });
1015     });
1016
1017 });