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