/
cli_reporter.js
305 lines (290 loc) · 12.5 KB
/
cli_reporter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
'use strict';
var chalk = require('chalk');
var expect = require('chai').expect;
var fs = require('fs');
var CliReporter = require('../lib/cli_reporter.js');
describe('cli_reporter', () => {
describe('#constructor(opts)', () => {
it('sets defaults with no options given', () => {
let reporter = new CliReporter();
expect(reporter.ui.width).to.equal(110);
expect(reporter.ui.wrap).to.equal(true);
expect(reporter.issueTitleWidth).to.equal(22);
expect(reporter.styles['Deprecation']).to.be.a.function;
expect(reporter.styles['Possible Bug']).to.be.a.function;
expect(reporter.styles['Clarity']).to.be.a.function;
expect(reporter.styles['Optimization']).to.be.a.function;
expect(reporter.fileReports).to.be.empty;
});
it('accepts width and wrap', () => {
let reporter = new CliReporter({ width: 200, wrap: false });
expect(reporter.ui.width).to.equal(200);
expect(reporter.ui.wrap).to.equal(false);
expect(reporter.issueTitleWidth).to.equal(40);
});
it('doesn\'t blow up when width is NaN', () => {
let reporter = new CliReporter({ width: 'hello' });
expect(reporter.ui.width).to.equal(110);
expect(reporter.ui.wrap).to.equal(true);
expect(reporter.issueTitleWidth).to.equal(22);
});
});
describe('#addFile(file, fileContent, items)', () => {
it('ignores an invalid file argument', () => {
let reporter = new CliReporter().addFile(null);
expect(reporter.fileReports).to.be.empty;
});
it('adds a good report when no items given', () => {
let file = './test/examples/Dockerfile.busybox';
let reporter = new CliReporter().addFile(file, fs.readFileSync(file, 'UTF-8'), []);
expect(Object.keys(reporter.fileReports)).to.have.length(1);
let fileReport = reporter.fileReports[file];
expect(fileReport.itemsByLine).to.be.empty;
expect(fileReport.uniqueIssues).to.equal(0);
expect(fileReport.contentArray).to.have.length(4);
});
it('modifies existing report when same file added twice', () => {
let file = './test/examples/Dockerfile.registry';
let fileContent = fs.readFileSync(file, 'UTF-8');
let item = {
title: 'Consider `--no-install-recommends`',
description: 'Consider using a `--no-install-recommends` when `apt-get` installing packages. This will result in a smaller image size. For\nmore information, see [this blog post](http://blog.replicated.com/2016/02/05/refactoring-a-dockerfile-for-image-size/)',
category: 'Optimization',
line: 5
};
let reporter = new CliReporter()
.addFile(file, fileContent, [])
.addFile(file, fileContent, [ item ]);
expect(Object.keys(reporter.fileReports)).to.have.length(1);
let fileReport = reporter.fileReports[file];
expect(fileReport.uniqueIssues).to.equal(1);
expect(fileReport.contentArray).to.have.length(16);
expect(fileReport.itemsByLine).to.deep.equal({
'5': [ item ]
});
});
it('groups multiple items by line number', () => {
let file = './test/examples/Dockerfile.misc';
let fileContent = fs.readFileSync(file, 'UTF-8');
let items = [
{
title: 'Base Image Missing Tag',
description: 'Base images should specify a tag to use.',
category: 'Clarity',
line: 5
},
{
title: 'First Command Must Be FROM',
description: 'The first instruction in a Dockerfile must specify the base image using a FROM command. Additionally, FROM cannot appear later in a Dockerfile.',
category: 'Possible Bug',
line: 6
},
{
title: 'Base Image Latest Tag',
description: 'Base images should not use the latest tag.',
category: 'Clarity',
line: 6
}
];
let reporter = new CliReporter().addFile(file, fileContent, items);
expect(Object.keys(reporter.fileReports)).to.have.length(1);
let fileReport = reporter.fileReports[file];
expect(fileReport.uniqueIssues).to.equal(3);
expect(fileReport.contentArray).to.have.length(36);
expect(fileReport.itemsByLine).to.deep.equal({
'5': [ items[0] ],
'6': items.splice(1)
});
});
it('ignores duplicate items', () => {
let file = './test/examples/Dockerfile.misc';
let fileContent = fs.readFileSync(file, 'UTF-8');
let items = [
{
title: 'First Command Must Be FROM',
description: 'The first instruction in a Dockerfile must specify the base image using a FROM command. Additionally, FROM cannot appear later in a Dockerfile.',
category: 'Possible Bug',
line: 6
},
{
title: 'First Command Must Be FROM',
description: 'The first instruction in a Dockerfile must specify the base image using a FROM command. Additionally, FROM cannot appear later in a Dockerfile.',
category: 'Possible Bug',
line: 6
}
];
let reporter = new CliReporter().addFile(file, fileContent, items);
expect(Object.keys(reporter.fileReports)).to.have.length(1);
let fileReport = reporter.fileReports[file];
expect(fileReport.uniqueIssues).to.equal(1);
expect(fileReport.contentArray).to.have.length(36);
expect(fileReport.itemsByLine).to.deep.equal({
'6': [ items[0] ]
});
});
it('allows multiple files to be added', () => {
let file1 = './test/examples/Dockerfile.registry';
let file1Content = fs.readFileSync(file1, 'UTF-8');
let file1Items = [
{
title: 'Consider `--no-install-recommends`',
description: 'Consider using a `--no-install-recommends` when `apt-get` installing packages. This will result in a smaller image size. For\nmore information, see [this blog post](http://blog.replicated.com/2016/02/05/refactoring-a-dockerfile-for-image-size/)',
category: 'Optimization',
line: 5
}
];
let file2 = './test/examples/Dockerfile.misc';
let file2Content = fs.readFileSync(file2, 'UTF-8');
let file2Items = [
{
title: 'Base Image Missing Tag',
description: 'Base images should specify a tag to use.',
category: 'Clarity',
line: 5
},
{
title: 'First Command Must Be FROM',
description: 'The first instruction in a Dockerfile must specify the base image using a FROM command. Additionally, FROM cannot appear later in a Dockerfile.',
category: 'Possible Bug',
line: 6
}
];
let reporter = new CliReporter()
.addFile(file1, file1Content, file1Items)
.addFile(file2, file2Content, file2Items);
expect(Object.keys(reporter.fileReports)).to.have.length(2);
let file1Report = reporter.fileReports[file1];
expect(file1Report.uniqueIssues).to.equal(1);
expect(file1Report.contentArray).to.have.length(16);
expect(file1Report.itemsByLine).to.deep.equal({
'5': file1Items
});
let file2Report = reporter.fileReports[file2];
expect(file2Report.uniqueIssues).to.equal(2);
expect(file2Report.contentArray).to.have.length(36);
expect(file2Report.itemsByLine).to.deep.equal({
'5': [ file2Items[0] ],
'6': [ file2Items[1] ]
});
});
});
describe('#buildReport()', () => {
it('returns blank report if no files added', () => {
let report = new CliReporter().buildReport();
expect(report.totalIssues).to.equal(0);
expect(report.toString()).to.equal('');
});
it('returns green report when one file added with no issues', () => {
let file = './test/examples/Dockerfile.busybox';
let report = new CliReporter()
.addFile(file, fs.readFileSync(file, 'UTF-8'), [])
.buildReport();
expect(report.totalIssues).to.equal(0);
expect(report.toString().split('\n')).to.deep.equal([
'',
'File: ' + file,
'Issues: \u001b[32mNone found\u001b[39m 👍',
''
]);
});
it('returns green report when multiple files added with no issues', () => {
let file1 = './test/examples/Dockerfile.busybox';
let file2 = './test/examples/Dockerfile.debian';
let report = new CliReporter()
.addFile(file1, fs.readFileSync(file1, 'UTF-8'), [])
.addFile(file2, fs.readFileSync(file2, 'UTF-8'), [])
.buildReport();
expect(report.totalIssues).to.equal(0);
expect(report.toString().split('\n')).to.deep.equal([
'',
'File: ' + file1,
'Issues: ' + chalk.green('None found') + ' 👍',
'',
'File: ' + file2,
'Issues: ' + chalk.green('None found') + ' 👍',
''
]);
});
it('returns pretty report when file added with issues', () => {
let file = './test/examples/Dockerfile.misc';
let items = [
{
title: 'Base Image Missing Tag',
description: 'Base images should specify a tag to use.',
category: 'Clarity',
line: 5
},
{
title: 'First Command Must Be FROM',
description: 'The first instruction in a Dockerfile must specify the base image using a FROM command. Additionally, FROM cannot appear later in a Dockerfile.',
category: 'Possible Bug',
line: 6
},
{
title: 'Base Image Latest Tag',
description: 'Base images should not use the latest tag.',
category: 'Clarity',
line: 6
},
{
title: 'Expose Only Container Port',
description: 'Using `EXPOSE` to specify a host port is not allowed.',
category: 'Deprecation',
line: 21
}
];
let report = new CliReporter()
.addFile(file, fs.readFileSync(file, 'UTF-8'), items)
.buildReport();
expect(report.totalIssues).to.equal(4);
expect(report.toString().split('\n')).to.deep.equal([
'',
'File: ' + file,
'Issues: 4',
'',
'Line 5: ' + chalk.magenta('FROM ubuntu'),
'Issue Category Title Description',
' ' + chalk.cyan('1') + ' ' + chalk.cyan.inverse('Clarity') + ' ' + chalk.cyan('Base Image Missing') + ' ' + chalk.gray('Base images should specify a tag to use.'),
' ' + chalk.cyan('Tag'),
'',
'Line 6: ' + chalk.magenta('FROM ubuntu:latest'),
'Issue Category Title Description',
' ' + chalk.yellow('2') + ' ' + chalk.yellow.inverse('Possible Bug') + ' ' + chalk.yellow('First Command Must') + ' ' + chalk.gray('The first instruction in a Dockerfile must specify the base image'),
' ' + chalk.yellow('Be FROM') + ' ' + chalk.gray('using a FROM command. Additionally, FROM cannot appear later in a'),
' ' + chalk.gray('Dockerfile.'),
' ' + chalk.cyan('3') + ' ' + chalk.cyan.inverse('Clarity') + ' ' + chalk.cyan('Base Image Latest') + ' ' + chalk.gray('Base images should not use the latest tag.'),
' ' + chalk.cyan('Tag'),
'',
'Line 21: ' + chalk.magenta('EXPOSE 80:80'),
'Issue Category Title Description',
' ' + chalk.red('4') + ' ' + chalk.red.inverse('Deprecation') + ' ' + chalk.red('Expose Only') + ' ' + chalk.gray('Using `EXPOSE` to specify a host port is not allowed.'),
' ' + chalk.red('Container Port'),
''
]);
});
it('uses Clarity style for unknown category', () => {
let file = './test/examples/Dockerfile.registry';
let fileContent = fs.readFileSync(file, 'UTF-8');
let item = {
title: 'Hello World!',
description: 'This is a test.',
category: 'Hello',
line: 5
};
let report = new CliReporter()
.addFile(file, fs.readFileSync(file, 'UTF-8'), [ item ])
.buildReport();
expect(report.totalIssues).to.equal(1);
expect(report.toString().split('\n')).to.deep.equal([
'',
'File: ' + file,
'Issues: 1',
'',
'Line 5: ' + chalk.magenta('RUN apt-get update && \\'),
'Issue Category Title Description',
' ' + chalk.cyan('1') + ' ' + chalk.cyan.inverse('Hello') + ' ' + chalk.cyan('Hello World!') + ' ' + chalk.gray('This is a test.'),
''
]);
});
});
});