summaryrefslogtreecommitdiff
path: root/spec/javascripts
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2018-06-28 15:25:49 +0100
committerMayra Cabrera <mcabrera@gitlab.com>2018-07-06 08:55:24 -0500
commit0e7aa236c8c8143770b6602fa99cb4197c65fe70 (patch)
treed87011605bb07678f94b8abd9f27aecc2cb8f2c8 /spec/javascripts
parent26998c68c936f183ead1a84e404a61160fc646f7 (diff)
downloadgitlab-ce-0e7aa236c8c8143770b6602fa99cb4197c65fe70.tar.gz
Escapes job name used in tooltips in vue components
Use sanitize to strip src attributes Changes sidebar back to use sanitize
Diffstat (limited to 'spec/javascripts')
-rw-r--r--spec/javascripts/fixtures/graph.html.haml1
-rw-r--r--spec/javascripts/pipelines/graph/dropdown_job_component_spec.js93
-rw-r--r--spec/javascripts/pipelines/graph/graph_component_spec.js43
-rw-r--r--spec/javascripts/pipelines/graph/job_component_spec.js26
-rw-r--r--spec/javascripts/pipelines/graph/mock_data.js2
-rw-r--r--spec/javascripts/pipelines/graph/stage_column_component_spec.js37
6 files changed, 170 insertions, 32 deletions
diff --git a/spec/javascripts/fixtures/graph.html.haml b/spec/javascripts/fixtures/graph.html.haml
deleted file mode 100644
index 4fedb0f1ded..00000000000
--- a/spec/javascripts/fixtures/graph.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-#js-pipeline-graph-vue{ data: { endpoint: "foo" } }
diff --git a/spec/javascripts/pipelines/graph/dropdown_job_component_spec.js b/spec/javascripts/pipelines/graph/dropdown_job_component_spec.js
new file mode 100644
index 00000000000..608a0d4be67
--- /dev/null
+++ b/spec/javascripts/pipelines/graph/dropdown_job_component_spec.js
@@ -0,0 +1,93 @@
+import Vue from 'vue';
+import component from '~/pipelines/components/graph/dropdown_job_component.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('dropdown job component', () => {
+ const Component = Vue.extend(component);
+ let vm;
+
+ const mock = {
+ jobs: [
+ {
+ id: 4256,
+ name: '<img src=x onerror=alert(document.domain)>',
+ status: {
+ icon: 'icon_status_success',
+ text: 'passed',
+ label: 'passed',
+ tooltip: 'passed',
+ group: 'success',
+ details_path: '/root/ci-mock/builds/4256',
+ has_details: true,
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4256/retry',
+ method: 'post',
+ },
+ },
+ },
+ {
+ id: 4299,
+ name: 'test',
+ status: {
+ icon: 'icon_status_success',
+ text: 'passed',
+ label: 'passed',
+ tooltip: 'passed',
+ group: 'success',
+ details_path: '/root/ci-mock/builds/4299',
+ has_details: true,
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4299/retry',
+ method: 'post',
+ },
+ },
+ },
+ ],
+ name: 'rspec:linux',
+ size: 2,
+ status: {
+ icon: 'icon_status_success',
+ text: 'passed',
+ label: 'passed',
+ tooltip: 'passed',
+ group: 'success',
+ details_path: '/root/ci-mock/builds/4256',
+ has_details: true,
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4256/retry',
+ method: 'post',
+ },
+ },
+ };
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ beforeEach(() => {
+ vm = mountComponent(Component, { job: mock });
+ });
+
+ it('renders button with job name and size', () => {
+ expect(vm.$el.querySelector('button').textContent).toContain(mock.name);
+ expect(vm.$el.querySelector('button').textContent).toContain(mock.size);
+ });
+
+ it('renders dropdown with jobs', () => {
+ expect(vm.$el.querySelectorAll('.scrollable-menu>ul>li').length).toEqual(mock.jobs.length);
+ });
+
+ it('escapes tooltip title', () => {
+ expect(
+ vm.$el.querySelector('.js-pipeline-graph-job-link').getAttribute('data-original-title'),
+ ).toEqual(
+ '&lt;img src=x onerror=alert(document.domain)&gt; - passed',
+ );
+ });
+});
diff --git a/spec/javascripts/pipelines/graph/graph_component_spec.js b/spec/javascripts/pipelines/graph/graph_component_spec.js
index 713baa65a17..b6fa4272c8b 100644
--- a/spec/javascripts/pipelines/graph/graph_component_spec.js
+++ b/spec/javascripts/pipelines/graph/graph_component_spec.js
@@ -1,37 +1,33 @@
import Vue from 'vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
import graphComponent from '~/pipelines/components/graph/graph_component.vue';
import graphJSON from './mock_data';
describe('graph component', () => {
- preloadFixtures('static/graph.html.raw');
+ const GraphComponent = Vue.extend(graphComponent);
+ let component;
- let GraphComponent;
-
- beforeEach(() => {
- loadFixtures('static/graph.html.raw');
- GraphComponent = Vue.extend(graphComponent);
+ afterEach(() => {
+ component.$destroy();
});
describe('while is loading', () => {
it('should render a loading icon', () => {
- const component = new GraphComponent({
- propsData: {
- isLoading: true,
- pipeline: {},
- },
- }).$mount('#js-pipeline-graph-vue');
+ component = mountComponent(GraphComponent, {
+ isLoading: true,
+ pipeline: {},
+ });
+
expect(component.$el.querySelector('.loading-icon')).toBeDefined();
});
});
describe('with data', () => {
it('should render the graph', () => {
- const component = new GraphComponent({
- propsData: {
- isLoading: false,
- pipeline: graphJSON,
- },
- }).$mount('#js-pipeline-graph-vue');
+ component = mountComponent(GraphComponent, {
+ isLoading: false,
+ pipeline: graphJSON,
+ });
expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true);
@@ -52,4 +48,15 @@ describe('graph component', () => {
expect(component.$el.querySelector('.stage-column-list')).toBeDefined();
});
});
+
+ describe('capitalizeStageName', () => {
+ it('capitalizes and escapes stage name', () => {
+ component = mountComponent(GraphComponent, {
+ isLoading: false,
+ pipeline: graphJSON,
+ });
+
+ expect(component.$el.querySelector('.stage-column:nth-child(2) .stage-name').textContent.trim()).toEqual('Deploy &lt;img src=x onerror=alert(document.domain)&gt;');
+ });
+ });
});
diff --git a/spec/javascripts/pipelines/graph/job_component_spec.js b/spec/javascripts/pipelines/graph/job_component_spec.js
index 9c55a19ebc7..e71da9946ee 100644
--- a/spec/javascripts/pipelines/graph/job_component_spec.js
+++ b/spec/javascripts/pipelines/graph/job_component_spec.js
@@ -3,7 +3,7 @@ import jobComponent from '~/pipelines/components/graph/job_component.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('pipeline graph job component', () => {
- let JobComponent;
+ const JobComponent = Vue.extend(jobComponent);
let component;
const mockJob = {
@@ -26,10 +26,6 @@ describe('pipeline graph job component', () => {
},
};
- beforeEach(() => {
- JobComponent = Vue.extend(jobComponent);
- });
-
afterEach(() => {
component.$destroy();
});
@@ -165,4 +161,24 @@ describe('pipeline graph job component', () => {
expect(component.$el.querySelector(tooltipBoundary)).toBeNull();
});
});
+
+ describe('tooltipText', () => {
+ it('escapes job name', () => {
+ component = mountComponent(JobComponent, {
+ job: {
+ id: 4259,
+ name: '<img src=x onerror=alert(document.domain)>',
+ status: {
+ icon: 'icon_status_success',
+ label: 'success',
+ tooltip: 'failed',
+ },
+ },
+ });
+
+ expect(
+ component.$el.querySelector('.js-job-component-tooltip').getAttribute('data-original-title'),
+ ).toEqual('&lt;img src=x onerror=alert(document.domain)&gt; - failed');
+ });
+ });
});
diff --git a/spec/javascripts/pipelines/graph/mock_data.js b/spec/javascripts/pipelines/graph/mock_data.js
index 9e25a4b3fed..5b7a09a682d 100644
--- a/spec/javascripts/pipelines/graph/mock_data.js
+++ b/spec/javascripts/pipelines/graph/mock_data.js
@@ -91,7 +91,7 @@ export default {
dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=test',
},
{
- name: 'deploy',
+ name: 'deploy <img src=x onerror=alert(document.domain)>',
title: 'deploy: passed',
groups: [
{
diff --git a/spec/javascripts/pipelines/graph/stage_column_component_spec.js b/spec/javascripts/pipelines/graph/stage_column_component_spec.js
index f744f1af5e6..6b417bc0133 100644
--- a/spec/javascripts/pipelines/graph/stage_column_component_spec.js
+++ b/spec/javascripts/pipelines/graph/stage_column_component_spec.js
@@ -1,8 +1,11 @@
import Vue from 'vue';
import stageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('stage column component', () => {
let component;
+ const StageColumnComponent = Vue.extend(stageColumnComponent);
+
const mockJob = {
id: 4250,
name: 'test',
@@ -22,7 +25,6 @@ describe('stage column component', () => {
};
beforeEach(() => {
- const StageColumnComponent = Vue.extend(stageColumnComponent);
const mockJobs = [];
for (let i = 0; i < 3; i += 1) {
@@ -31,12 +33,10 @@ describe('stage column component', () => {
mockJobs.push(mockedJob);
}
- component = new StageColumnComponent({
- propsData: {
- title: 'foo',
- jobs: mockJobs,
- },
- }).$mount();
+ component = mountComponent(StageColumnComponent, {
+ title: 'foo',
+ jobs: mockJobs,
+ });
});
it('should render provided title', () => {
@@ -46,4 +46,27 @@ describe('stage column component', () => {
it('should render the provided jobs', () => {
expect(component.$el.querySelectorAll('.builds-container > ul > li').length).toEqual(3);
});
+
+ describe('jobId', () => {
+ it('escapes job name', () => {
+ component = mountComponent(StageColumnComponent, {
+ jobs: [
+ {
+ id: 4259,
+ name: '<img src=x onerror=alert(document.domain)>',
+ status: {
+ icon: 'icon_status_success',
+ label: 'success',
+ tooltip: '<img src=x onerror=alert(document.domain)>',
+ },
+ },
+ ],
+ title: 'test',
+ });
+
+ expect(
+ component.$el.querySelector('.builds-container li').getAttribute('id'),
+ ).toEqual('ci-badge-&lt;img src=x onerror=alert(document.domain)&gt;');
+ });
+ });
});