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