]> source.dussan.org Git - sonarqube.git/blob
c62af5f23b09247bc139a0a263e3de4b0d6dd8d9
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2019 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.ce.task.projectanalysis.component;
21
22 import java.util.Arrays;
23 import java.util.EnumSet;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Objects;
28 import java.util.Optional;
29 import java.util.Random;
30 import java.util.function.Function;
31 import org.junit.Rule;
32 import org.junit.Test;
33 import org.junit.rules.ExpectedException;
34 import org.junit.rules.ExternalResource;
35 import org.sonar.ce.task.projectanalysis.analysis.Branch;
36 import org.sonar.core.component.ComponentKeys;
37 import org.sonar.scanner.protocol.output.ScannerReport;
38 import org.sonar.server.project.Project;
39
40 import static com.google.common.base.Preconditions.checkArgument;
41 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
42 import static org.assertj.core.api.Assertions.assertThat;
43 import static org.junit.Assert.fail;
44 import static org.mockito.Mockito.mock;
45 import static org.mockito.Mockito.when;
46 import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.PRE_ORDER;
47 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
48 import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto;
49 import static org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType.DIRECTORY;
50 import static org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType.FILE;
51 import static org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType.MODULE;
52 import static org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType.PROJECT;
53 import static org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType.UNRECOGNIZED;
54 import static org.sonar.scanner.protocol.output.ScannerReport.Component.newBuilder;
55
56 public class ComponentTreeBuilderTest {
57
58   private static final ComponentKeyGenerator KEY_GENERATOR = (projectKey, path) -> "generated_"
59     + ComponentKeys.createEffectiveKey(projectKey, path);
60   private static final ComponentKeyGenerator PUBLIC_KEY_GENERATOR = (projectKey, path) -> "public_"
61     + ComponentKeys.createEffectiveKey(projectKey, path);
62   private static final Function<String, String> UUID_SUPPLIER = (componentKey) -> componentKey + "_uuid";
63   private static final EnumSet<ScannerReport.Component.ComponentType> REPORT_TYPES = EnumSet.of(PROJECT, FILE);
64   private static final String NO_SCM_BASE_PATH = "";
65   // both no project as "" or null should be supported
66   private static final ProjectAttributes SOME_PROJECT_ATTRIBUTES = new ProjectAttributes(
67     randomAlphabetic(20), new Random().nextBoolean() ? null : randomAlphabetic(12));
68
69   @Rule
70   public ExpectedException expectedException = ExpectedException.none();
71
72   @Rule
73   public ScannerComponentProvider scannerComponentProvider = new ScannerComponentProvider();
74
75   private Project projectInDb = Project.from(newPrivateProjectDto(newOrganizationDto(), UUID_SUPPLIER.apply("K1")).setDbKey("K1").setDescription(null));
76
77   @Test
78   public void build_throws_IAE_for_all_types_except_PROJECT_and_FILE() {
79     Arrays.stream(ScannerReport.Component.ComponentType.values())
80       .filter((type) -> type != UNRECOGNIZED)
81       .filter((type) -> !REPORT_TYPES.contains(type))
82       .forEach(
83         (type) -> {
84           scannerComponentProvider.clear();
85           ScannerReport.Component project = newBuilder()
86             .setType(PROJECT)
87             .setKey(projectInDb.getKey())
88             .setRef(1)
89             .addChildRef(2)
90             .setProjectRelativePath("root")
91             .build();
92           scannerComponentProvider.add(newBuilder()
93             .setRef(2)
94             .setType(type)
95             .setProjectRelativePath("src")
96             .setLines(1));
97           try {
98             call(project, NO_SCM_BASE_PATH, SOME_PROJECT_ATTRIBUTES);
99             fail("Should have thrown a IllegalArgumentException");
100           } catch (IllegalArgumentException e) {
101             assertThat(e).hasMessage("Unsupported component type '" + type + "'");
102           }
103         });
104   }
105
106   @Test
107   public void build_throws_IAE_if_root_is_not_PROJECT() {
108     Arrays.stream(ScannerReport.Component.ComponentType.values())
109       .filter((type) -> type != UNRECOGNIZED)
110       .filter((type) -> !REPORT_TYPES.contains(type))
111       .forEach(
112         (type) -> {
113           ScannerReport.Component component = newBuilder().setType(type).build();
114           try {
115             call(component);
116             fail("Should have thrown a IllegalArgumentException");
117           } catch (IllegalArgumentException e) {
118             assertThat(e).hasMessage("Expected root component of type 'PROJECT'");
119           }
120         });
121   }
122
123   @Test
124   public void by_default_project_fields_are_loaded_from_report() {
125     String nameInReport = "the name";
126     String descriptionInReport = "the desc";
127     String buildString = randomAlphabetic(21);
128     Component root = call(newBuilder()
129       .setType(PROJECT)
130       .setKey(projectInDb.getKey())
131       .setRef(42)
132       .setName(nameInReport)
133       .setDescription(descriptionInReport)
134       .build(), NO_SCM_BASE_PATH, new ProjectAttributes("6.5", buildString));
135
136     assertThat(root.getUuid()).isEqualTo("generated_K1_uuid");
137     assertThat(root.getDbKey()).isEqualTo("generated_K1");
138     assertThat(root.getKey()).isEqualTo("public_K1");
139     assertThat(root.getType()).isEqualTo(Component.Type.PROJECT);
140     assertThat(root.getName()).isEqualTo(nameInReport);
141     assertThat(root.getShortName()).isEqualTo(nameInReport);
142     assertThat(root.getDescription()).isEqualTo(descriptionInReport);
143     assertThat(root.getReportAttributes().getRef()).isEqualTo(42);
144     assertThat(root.getProjectAttributes().getProjectVersion()).contains("6.5");
145     assertThat(root.getProjectAttributes().getBuildString()).isEqualTo(Optional.of(buildString));
146     assertThatFileAttributesAreNotSet(root);
147   }
148
149   @Test
150   public void project_name_is_loaded_from_db_if_absent_from_report() {
151     Component root = call(newBuilder()
152       .setType(PROJECT)
153       .build(), NO_SCM_BASE_PATH, SOME_PROJECT_ATTRIBUTES);
154
155     assertThat(root.getName()).isEqualTo(projectInDb.getName());
156   }
157
158   @Test
159   public void project_name_is_loaded_from_report_if_present_and_on_main_branch() {
160     String reportName = randomAlphabetic(5);
161     ScannerReport.Component reportProject = newBuilder()
162       .setType(PROJECT)
163       .setName(reportName)
164       .build();
165
166     Component root = newUnderTest(SOME_PROJECT_ATTRIBUTES, true).buildProject(reportProject, NO_SCM_BASE_PATH);
167
168     assertThat(root.getName()).isEqualTo(reportName);
169   }
170
171   @Test
172   public void project_name_is_loaded_from_db_if_not_on_main_branch() {
173     String reportName = randomAlphabetic(5);
174     ScannerReport.Component reportProject = newBuilder()
175       .setType(PROJECT)
176       .setName(reportName)
177       .build();
178
179     Component root = newUnderTest(SOME_PROJECT_ATTRIBUTES, false)
180       .buildProject(reportProject, NO_SCM_BASE_PATH);
181
182     assertThat(root.getName()).isEqualTo(projectInDb.getName());
183   }
184
185   @Test
186   public void project_description_is_loaded_from_db_if_absent_from_report() {
187     Component root = call(newBuilder()
188       .setType(PROJECT)
189       .build(), NO_SCM_BASE_PATH, SOME_PROJECT_ATTRIBUTES);
190
191     assertThat(root.getDescription()).isEqualTo(projectInDb.getDescription());
192   }
193
194   @Test
195   public void project_description_is_loaded_from_report_if_present_and_on_main_branch() {
196     String reportDescription = randomAlphabetic(5);
197     ScannerReport.Component reportProject = newBuilder()
198       .setType(PROJECT)
199       .setDescription(reportDescription)
200       .build();
201
202     Component root = newUnderTest(SOME_PROJECT_ATTRIBUTES, true).buildProject(reportProject, NO_SCM_BASE_PATH);
203
204     assertThat(root.getDescription()).isEqualTo(reportDescription);
205   }
206
207   @Test
208   public void project_description_is_loaded_from_db_if_not_on_main_branch() {
209     String reportDescription = randomAlphabetic(5);
210     ScannerReport.Component reportProject = newBuilder()
211       .setType(PROJECT)
212       .setDescription(reportDescription)
213       .build();
214
215     Component root = newUnderTest(SOME_PROJECT_ATTRIBUTES, false).buildProject(reportProject, NO_SCM_BASE_PATH);
216
217     assertThat(root.getDescription()).isEqualTo(projectInDb.getDescription());
218   }
219
220   @Test
221   public void project_scmPath_is_empty_if_scmBasePath_is_empty() {
222     Component root = call(newBuilder()
223       .setType(PROJECT)
224       .build(), NO_SCM_BASE_PATH, SOME_PROJECT_ATTRIBUTES);
225
226     assertThat(root.getReportAttributes().getScmPath()).isEmpty();
227   }
228
229   @Test
230   public void projectAttributes_is_constructor_argument() {
231     Component root = call(newBuilder()
232       .setType(PROJECT)
233       .build(), NO_SCM_BASE_PATH, SOME_PROJECT_ATTRIBUTES);
234
235     assertThat(root.getProjectAttributes()).isSameAs(SOME_PROJECT_ATTRIBUTES);
236   }
237
238   @Test
239   public void any_component_with_projectRelativePath_has_this_value_as_scmPath_if_scmBasePath_is_empty() {
240     ScannerReport.Component project = newBuilder()
241       .setType(PROJECT)
242       .setKey(projectInDb.getKey())
243       .setRef(1)
244       .addChildRef(2)
245       .setProjectRelativePath("root")
246       .build();
247     scannerComponentProvider.add(newBuilder()
248       .setRef(2)
249       .setType(FILE)
250       .setProjectRelativePath("src/js/Foo.js")
251       .setLines(1));
252
253     Component root = call(project, NO_SCM_BASE_PATH, SOME_PROJECT_ATTRIBUTES);
254
255     assertThat(root.getReportAttributes().getScmPath())
256       .contains("root");
257     Component directory = root.getChildren().iterator().next();
258     assertThat(directory.getReportAttributes().getScmPath())
259       .contains("src/js");
260     Component file = directory.getChildren().iterator().next();
261     assertThat(file.getReportAttributes().getScmPath())
262       .contains("src/js/Foo.js");
263   }
264
265   @Test
266   public void any_component_with_projectRelativePath_has_this_value_appended_to_scmBasePath_and_a_slash_as_scmPath_if_scmBasePath_is_not_empty() {
267     ScannerReport.Component project = createProject();
268     String scmBasePath = randomAlphabetic(10);
269
270     Component root = call(project, scmBasePath, SOME_PROJECT_ATTRIBUTES);
271     assertThat(root.getReportAttributes().getScmPath())
272       .contains(scmBasePath);
273     Component directory = root.getChildren().iterator().next();
274     assertThat(directory.getReportAttributes().getScmPath())
275       .contains(scmBasePath + "/src/js");
276     Component file = directory.getChildren().iterator().next();
277     assertThat(file.getReportAttributes().getScmPath())
278       .contains(scmBasePath + "/src/js/Foo.js");
279   }
280
281   private ScannerReport.Component createProject() {
282     ScannerReport.Component project = newBuilder()
283       .setType(PROJECT)
284       .setKey(projectInDb.getKey())
285       .setRef(1)
286       .addChildRef(2)
287       .build();
288     scannerComponentProvider.add(newBuilder()
289       .setRef(2)
290       .setType(FILE)
291       .setProjectRelativePath("src/js/Foo.js")
292       .setLines(1));
293     return project;
294   }
295
296   @Test
297   public void keys_of_directory_and_file_are_generated() {
298     ScannerReport.Component project = createProject();
299
300     Component root = call(project);
301     assertThat(root.getDbKey()).isEqualTo("generated_" + projectInDb.getKey());
302     assertThat(root.getKey()).isEqualTo("public_" + projectInDb.getKey());
303     assertThat(root.getChildren()).hasSize(1);
304
305     Component directory = root.getChildren().iterator().next();
306     assertThat(directory.getDbKey()).isEqualTo("generated_" + projectInDb.getKey() + ":src/js");
307     assertThat(directory.getKey()).isEqualTo("public_" + projectInDb.getKey() + ":src/js");
308     assertThat(directory.getChildren()).hasSize(1);
309
310     Component file = directory.getChildren().iterator().next();
311     assertThat(file.getDbKey()).isEqualTo("generated_" + projectInDb.getKey() + ":src/js/Foo.js");
312     assertThat(file.getKey()).isEqualTo("public_" + projectInDb.getKey() + ":src/js/Foo.js");
313     assertThat(file.getChildren()).isEmpty();
314   }
315
316   @Test
317   public void modules_are_not_created() {
318     ScannerReport.Component project = newBuilder()
319       .setType(PROJECT)
320       .setKey(projectInDb.getKey())
321       .setRef(1)
322       .addChildRef(2)
323       .build();
324     scannerComponentProvider.add(newBuilder()
325       .setRef(2)
326       .setType(FILE)
327       .setProjectRelativePath("src/js/Foo.js")
328       .setLines(1));
329
330     Component root = call(project);
331
332     List<Component> components = root.getChildren();
333     assertThat(components).extracting("type").containsOnly(Component.Type.DIRECTORY);
334   }
335
336   @Test
337   public void folder_hierarchy_is_created() {
338     ScannerReport.Component project = newBuilder()
339       .setType(PROJECT)
340       .setKey(projectInDb.getKey())
341       .setRef(1)
342       .addChildRef(4)
343       .addChildRef(5)
344       .addChildRef(6)
345       .build();
346     scannerComponentProvider.add(newBuilder()
347       .setRef(4)
348       .setType(FILE)
349       .setProjectRelativePath("src/main/xoo/Foo1.js")
350       .setLines(1));
351     scannerComponentProvider.add(newBuilder()
352       .setRef(5)
353       .setType(FILE)
354       .setProjectRelativePath("src/test/xoo/org/sonar/Foo2.js")
355       .setLines(1));
356     scannerComponentProvider.add(newBuilder()
357       .setRef(6)
358       .setType(FILE)
359       .setProjectRelativePath("pom.xml")
360       .setLines(1));
361
362     Component root = call(project);
363     assertThat(root.getChildren()).hasSize(2);
364
365     Component pom = root.getChildren().get(1);
366     assertThat(pom.getKey()).isEqualTo("public_K1:pom.xml");
367     assertThat(pom.getName()).isEqualTo("pom.xml");
368
369     Component directory = root.getChildren().get(0);
370     assertThat(directory.getKey()).isEqualTo("public_K1:src");
371     assertThat(directory.getName()).isEqualTo("src");
372
373     // folders are collapsed and they only contain one directory
374     Component d1 = directory.getChildren().get(0);
375     assertThat(d1.getKey()).isEqualTo("public_K1:src/main/xoo");
376     assertThat(d1.getName()).isEqualTo("src/main/xoo");
377     assertThat(d1.getShortName()).isEqualTo("main/xoo");
378
379     Component d2 = directory.getChildren().get(1);
380     assertThat(d2.getKey()).isEqualTo("public_K1:src/test/xoo/org/sonar");
381     assertThat(d2.getName()).isEqualTo("src/test/xoo/org/sonar");
382     assertThat(d2.getShortName()).isEqualTo("test/xoo/org/sonar");
383   }
384
385   @Test
386   public void collapse_directories_from_root() {
387     ScannerReport.Component project = newBuilder()
388       .setType(PROJECT)
389       .setKey(projectInDb.getKey())
390       .setRef(1)
391       .addChildRef(2)
392       .build();
393     scannerComponentProvider.add(newBuilder()
394       .setRef(2)
395       .setType(FILE)
396       .setProjectRelativePath("src/test/xoo/org/sonar/Foo2.js")
397       .setLines(1));
398
399     Component root = call(project);
400
401     // folders are collapsed and they only contain one directory
402     Component dir = root.getChildren().get(0);
403     assertThat(dir.getKey()).isEqualTo("public_K1:src/test/xoo/org/sonar");
404     assertThat(dir.getName()).isEqualTo("src/test/xoo/org/sonar");
405     assertThat(dir.getShortName()).isEqualTo("src/test/xoo/org/sonar");
406
407     Component file = dir.getChildren().get(0);
408     assertThat(file.getKey()).isEqualTo("public_K1:src/test/xoo/org/sonar/Foo2.js");
409     assertThat(file.getName()).isEqualTo("src/test/xoo/org/sonar/Foo2.js");
410     assertThat(file.getShortName()).isEqualTo("Foo2.js");
411   }
412
413   @Test
414   public void directories_are_collapsed() {
415     ScannerReport.Component project = newBuilder()
416       .setType(PROJECT)
417       .setKey(projectInDb.getKey())
418       .setRef(1)
419       .addChildRef(2)
420       .build();
421     scannerComponentProvider.add(newBuilder()
422       .setRef(2)
423       .setType(FILE)
424       .setProjectRelativePath("src/js/Foo.js")
425       .setLines(1));
426
427     Component root = call(project);
428
429     Component directory = root.getChildren().iterator().next();
430     assertThat(directory.getKey()).isEqualTo("public_K1:src/js");
431     assertThat(directory.getName()).isEqualTo("src/js");
432     assertThat(directory.getShortName()).isEqualTo("src/js");
433
434     Component file = directory.getChildren().iterator().next();
435     assertThat(file.getKey()).isEqualTo("public_K1:src/js/Foo.js");
436     assertThat(file.getName()).isEqualTo("src/js/Foo.js");
437     assertThat(file.getShortName()).isEqualTo("Foo.js");
438   }
439
440   @Test
441   public void names_of_directory_and_file_are_based_on_the_path() {
442     ScannerReport.Component project = newBuilder()
443       .setType(PROJECT)
444       .setKey(projectInDb.getKey())
445       .setRef(1)
446       .addChildRef(2)
447       .build();
448     scannerComponentProvider.add(newBuilder()
449       .setRef(2)
450       .setType(FILE)
451       .setProjectRelativePath("src/js/Foo.js")
452       .setName("")
453       .setLines(1));
454
455     Component root = call(project);
456
457     Component directory = root.getChildren().iterator().next();
458     assertThat(directory.getName()).isEqualTo("src/js");
459     assertThat(directory.getShortName()).isEqualTo("src/js");
460
461     Component file = directory.getChildren().iterator().next();
462     assertThat(file.getName()).isEqualTo("src/js/Foo.js");
463     assertThat(file.getShortName()).isEqualTo("Foo.js");
464   }
465
466   @Test
467   public void create_full_hierarchy_of_directories() {
468     ScannerReport.Component project = newBuilder()
469       .setType(PROJECT)
470       .setKey(projectInDb.getKey())
471       .setRef(1)
472       .addChildRef(2)
473       .addChildRef(3)
474       .build();
475     scannerComponentProvider.add(newBuilder()
476       .setRef(2)
477       .setType(FILE)
478       .setProjectRelativePath("src/java/Bar.java")
479       .setName("")
480       .setLines(2));
481     scannerComponentProvider.add(newBuilder()
482       .setRef(3)
483       .setType(FILE)
484       .setProjectRelativePath("src/js/Foo.js")
485       .setName("")
486       .setLines(1));
487
488     Component root = call(project);
489
490     Component directory = root.getChildren().iterator().next();
491     assertThat(directory.getKey()).isEqualTo("public_K1:src");
492     assertThat(directory.getName()).isEqualTo("src");
493     assertThat(directory.getShortName()).isEqualTo("src");
494
495     Component directoryJava = directory.getChildren().get(0);
496     assertThat(directoryJava.getKey()).isEqualTo("public_K1:src/java");
497     assertThat(directoryJava.getName()).isEqualTo("src/java");
498     assertThat(directoryJava.getShortName()).isEqualTo("java");
499
500     Component directoryJs = directory.getChildren().get(1);
501     assertThat(directoryJs.getKey()).isEqualTo("public_K1:src/js");
502     assertThat(directoryJs.getName()).isEqualTo("src/js");
503     assertThat(directoryJs.getShortName()).isEqualTo("js");
504
505     Component file = directoryJs.getChildren().iterator().next();
506     assertThat(file.getKey()).isEqualTo("public_K1:src/js/Foo.js");
507     assertThat(file.getName()).isEqualTo("src/js/Foo.js");
508     assertThat(file.getShortName()).isEqualTo("Foo.js");
509   }
510
511   private void assertThatFileAttributesAreNotSet(Component root) {
512     try {
513       root.getFileAttributes();
514       fail();
515     } catch (IllegalStateException e) {
516       assertThat(e).hasMessage("Only component of type FILE have a FileAttributes object");
517     }
518   }
519
520   @Test
521   public void keys_of_directory_and_files_includes_always_root_project() {
522     ScannerReport.Component project = newBuilder()
523       .setType(PROJECT)
524       .setKey("project 1")
525       .setRef(1)
526       .addChildRef(31).build();
527     scannerComponentProvider.add(newBuilder().setRef(31).setType(FILE).setProjectRelativePath("file in project").setLines(1));
528     Component root = call(project);
529     Map<String, Component> componentsByKey = indexComponentByKey(root);
530
531     assertThat(componentsByKey.values()).extracting("key").startsWith("public_project 1");
532     assertThat(componentsByKey.values()).extracting("dbKey").startsWith("generated_project 1");
533   }
534
535   @Test
536   public void uuids_are_provided_by_supplier() {
537     ScannerReport.Component project = newBuilder()
538       .setType(PROJECT)
539       .setKey("c1")
540       .setRef(1)
541       .addChildRef(2)
542       .build();
543     scannerComponentProvider.add(newBuilder()
544       .setRef(2)
545       .setType(FILE)
546       .setProjectRelativePath("src/js/Foo.js")
547       .setLines(1));
548
549     Component root = call(project);
550     assertThat(root.getUuid()).isEqualTo("generated_c1_uuid");
551
552     Component directory = root.getChildren().iterator().next();
553     assertThat(directory.getUuid()).isEqualTo("generated_c1:src/js_uuid");
554
555     Component file = directory.getChildren().iterator().next();
556     assertThat(file.getUuid()).isEqualTo("generated_c1:src/js/Foo.js_uuid");
557   }
558
559   @Test
560   public void issues_are_relocated_from_directories_and_modules_to_root() {
561     ScannerReport.Component project = newBuilder()
562       .setType(PROJECT)
563       .setKey("c1")
564       .setRef(1)
565       .addChildRef(2)
566       .build();
567     ScannerReport.Component.Builder file = newBuilder()
568       .setRef(2)
569       .setType(FILE)
570       .setProjectRelativePath("src/js/Foo.js")
571       .setLines(1);
572     scannerComponentProvider.add(file);
573
574     call(project);
575   }
576
577   @Test
578   public void descriptions_of_module_directory_and_file_are_null_if_absent_from_report() {
579     ScannerReport.Component project = newBuilder()
580       .setType(PROJECT)
581       .setRef(1)
582       .addChildRef(2)
583       .build();
584     scannerComponentProvider.add(newBuilder()
585       .setRef(2)
586       .setType(FILE)
587       .setProjectRelativePath("src/js/Foo.js")
588       .setLines(1));
589
590     Component root = call(project);
591
592     Component directory = root.getChildren().iterator().next();
593     assertThat(directory.getDescription()).isNull();
594
595     Component file = directory.getChildren().iterator().next();
596     assertThat(file.getDescription()).isNull();
597   }
598
599   @Test
600   public void descriptions_of_module_directory_and_file_are_null_if_empty_in_report() {
601     ScannerReport.Component project = newBuilder()
602       .setType(PROJECT)
603       .setRef(1)
604       .setDescription("")
605       .addChildRef(2)
606       .build();
607     scannerComponentProvider.add(newBuilder()
608       .setRef(2)
609       .setType(FILE)
610       .setDescription("")
611       .setProjectRelativePath("src/js/Foo.js")
612       .setLines(1));
613
614     Component root = call(project);
615
616     Component directory = root.getChildren().iterator().next();
617     assertThat(directory.getDescription()).isNull();
618
619     Component file = directory.getChildren().iterator().next();
620     assertThat(file.getDescription()).isNull();
621   }
622
623   @Test
624   public void descriptions_of_module_directory_and_file_are_set_from_report_if_present() {
625     ScannerReport.Component project = newBuilder()
626       .setType(PROJECT)
627       .setRef(1)
628       .addChildRef(2)
629       .build();
630     scannerComponentProvider.add(newBuilder()
631       .setRef(2)
632       .setType(FILE)
633       .setDescription("d")
634       .setProjectRelativePath("src/js/Foo.js")
635       .setLines(1));
636
637     Component root = call(project);
638     Component directory = root.getChildren().iterator().next();
639     assertThat(directory.getDescription()).isNull();
640
641     Component file = directory.getChildren().iterator().next();
642     assertThat(file.getDescription()).isEqualTo("d");
643   }
644
645   @Test
646   public void only_nb_of_lines_is_mandatory_on_file_attributes() {
647     ScannerReport.Component project = newBuilder()
648       .setType(PROJECT)
649       .setRef(1)
650       .addChildRef(2)
651       .build();
652     scannerComponentProvider.add(newBuilder()
653       .setRef(2)
654       .setType(FILE)
655       .setProjectRelativePath("src/js/Foo.js")
656       .setLines(1));
657
658     Component root = call(project);
659     Component dir = root.getChildren().iterator().next();
660     Component file = dir.getChildren().iterator().next();
661     assertThat(file.getFileAttributes().getLines()).isEqualTo(1);
662     assertThat(file.getFileAttributes().getLanguageKey()).isNull();
663     assertThat(file.getFileAttributes().isUnitTest()).isFalse();
664   }
665
666   @Test
667   public void language_file_attributes_is_null_if_empty_in_report() {
668     ScannerReport.Component project = newBuilder()
669       .setType(PROJECT)
670       .setRef(1)
671       .addChildRef(2)
672       .build();
673     scannerComponentProvider.add(newBuilder()
674       .setRef(2)
675       .setType(FILE)
676       .setProjectRelativePath("src/js/Foo.js")
677       .setLines(1)
678       .setLanguage(""));
679
680     Component root = call(project);
681     Component dir2 = root.getChildren().iterator().next();
682
683     Component file = dir2.getChildren().iterator().next();
684     assertThat(file.getFileAttributes().getLanguageKey()).isNull();
685   }
686
687   @Test
688   public void file_attributes_are_fully_loaded_from_report() {
689     ScannerReport.Component project = newBuilder()
690       .setType(PROJECT)
691       .setRef(1)
692       .addChildRef(2)
693       .build();
694     scannerComponentProvider.add(newBuilder()
695       .setRef(2)
696       .setType(FILE)
697       .setProjectRelativePath("src/js/Foo.js")
698       .setLines(1)
699       .setLanguage("js")
700       .setIsTest(true));
701
702     Component root = call(project);
703     Component dir = root.getChildren().iterator().next();
704     Component file = dir.getChildren().iterator().next();
705     assertThat(file.getFileAttributes().getLines()).isEqualTo(1);
706     assertThat(file.getFileAttributes().getLanguageKey()).isEqualTo("js");
707     assertThat(file.getFileAttributes().isUnitTest()).isTrue();
708   }
709
710   @Test
711   public void throw_IAE_if_lines_is_absent_from_report() {
712     ScannerReport.Component project = newBuilder()
713       .setType(PROJECT)
714       .setRef(1)
715       .addChildRef(2)
716       .build();
717     scannerComponentProvider.add(newBuilder()
718       .setRef(2)
719       .setType(FILE)
720       .setProjectRelativePath("src/js/Foo.js"));
721
722     expectedException.expect(IllegalArgumentException.class);
723     expectedException.expectMessage("File 'src/js/Foo.js' has no line");
724
725     call(project);
726   }
727
728   @Test
729   public void throw_IAE_if_lines_is_zero_in_report() {
730     ScannerReport.Component project = newBuilder()
731       .setType(PROJECT)
732       .setRef(1)
733       .addChildRef(2)
734       .build();
735     scannerComponentProvider.add(newBuilder()
736       .setRef(2)
737       .setType(FILE)
738       .setProjectRelativePath("src/js/Foo.js")
739       .setLines(0));
740
741     expectedException.expect(IllegalArgumentException.class);
742     expectedException.expectMessage("File 'src/js/Foo.js' has no line");
743
744     call(project);
745   }
746
747   @Test
748   public void throw_IAE_if_lines_is_negative_in_report() {
749     ScannerReport.Component project = newBuilder()
750       .setType(PROJECT)
751       .setRef(1)
752       .addChildRef(2)
753       .build();
754     scannerComponentProvider.add(newBuilder()
755       .setRef(2)
756       .setType(FILE)
757       .setProjectRelativePath("src/js/Foo.js")
758       .setLines(-10));
759
760     expectedException.expect(IllegalArgumentException.class);
761     expectedException.expectMessage("File 'src/js/Foo.js' has no line");
762
763     call(project);
764   }
765
766   private static class ScannerComponentProvider extends ExternalResource implements Function<Integer, ScannerReport.Component> {
767     private final Map<Integer, ScannerReport.Component> components = new HashMap<>();
768
769     @Override
770     protected void before() {
771       clear();
772     }
773
774     public void clear() {
775       components.clear();
776     }
777
778     @Override
779     public ScannerReport.Component apply(Integer componentRef) {
780       return Objects.requireNonNull(components.get(componentRef), "No Component for componentRef " + componentRef);
781     }
782
783     public ScannerReport.Component add(ScannerReport.Component.Builder builder) {
784       ScannerReport.Component component = builder.build();
785       ScannerReport.Component existing = components.put(component.getRef(), component);
786       checkArgument(existing == null, "Component %s already set for ref %s", existing, component.getRef());
787       return component;
788     }
789   }
790
791   private Component call(ScannerReport.Component project) {
792     return call(project, NO_SCM_BASE_PATH, SOME_PROJECT_ATTRIBUTES);
793   }
794
795   private Component call(ScannerReport.Component project, String scmBasePath, ProjectAttributes projectAttributes) {
796     return newUnderTest(projectAttributes, true).buildProject(project, scmBasePath);
797   }
798
799   private ComponentTreeBuilder newUnderTest(ProjectAttributes projectAttributes, boolean mainBranch) {
800     Branch branch = mock(Branch.class);
801     when(branch.isMain()).thenReturn(mainBranch);
802     return new ComponentTreeBuilder(KEY_GENERATOR, PUBLIC_KEY_GENERATOR, UUID_SUPPLIER, scannerComponentProvider,
803       projectInDb, branch, projectAttributes);
804   }
805
806   private static Map<String, Component> indexComponentByKey(Component root) {
807     Map<String, Component> componentsByKey = new HashMap<>();
808     new DepthTraversalTypeAwareCrawler(
809       new TypeAwareVisitorAdapter(CrawlerDepthLimit.FILE, PRE_ORDER) {
810         @Override
811         public void visitAny(Component any) {
812           componentsByKey.put(any.getDbKey(), any);
813         }
814       }).visit(root);
815     return componentsByKey;
816   }
817 }