]> source.dussan.org Git - sonarqube.git/blob
794a438a47f6f40946a4bc4812949c852373d212
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2021 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.server.platform.db.migration.version.v86;
21
22 import java.sql.SQLException;
23 import java.util.Map;
24 import javax.annotation.Nullable;
25 import org.junit.Rule;
26 import org.junit.Test;
27 import org.sonar.api.impl.utils.TestSystem2;
28 import org.sonar.api.utils.System2;
29 import org.sonar.core.util.UuidFactory;
30 import org.sonar.core.util.UuidFactoryFast;
31 import org.sonar.db.CoreDbTester;
32 import org.sonar.server.platform.db.migration.step.MigrationStep;
33 import org.xmlunit.builder.DiffBuilder;
34 import org.xmlunit.builder.Input;
35 import org.xmlunit.diff.Diff;
36
37 import static org.assertj.core.api.Assertions.assertThat;
38 import static org.assertj.core.api.Assertions.assertThatThrownBy;
39 import static org.assertj.core.api.Assertions.tuple;
40 import static org.sonar.server.platform.db.migration.version.v86.MigrateApplicationDefinitionsFromXmlToDb.TEXT_VALUE_MAX_LENGTH;
41
42 public class MigrateApplicationDefinitionsFromXmlToDbTest {
43   private static final String QUALIFIER_PROJECT = "TRK";
44   private static final String QUALIFIER_APP = "APP";
45   private static final long NOW = 100_000_000_000L;
46
47   private static final String PROJECT_1_UUID = "proj1-uuid";
48   private static final String PROJECT_1_MASTER_BRANCH_UUID = "proj1-master-uuid";
49   private static final String PROJECT_1_BRANCH_1_UUID = "proj1-branch1-uuid";
50   private static final String PROJECT_1_BRANCH_2_UUID = "proj1-branch2-uuid";
51   private static final String PROJECT_2_UUID = "proj2-uuid";
52   private static final String PROJECT_2_MASTER_BRANCH_UUID = "proj2-master-uuid";
53   private static final String PROJECT_2_BRANCH_1_UUID = "proj2-branch1-uuid";
54   private static final String APP_1_UUID = "app1-uuid";
55   private static final String APP_1_MASTER_BRANCH_UUID = "app1-master-uuid";
56   private static final String APP_1_BRANCH_1_UUID = "app1-branch1-uuid";
57   private static final String APP_1_BRANCH_2_UUID = "app1-branch2-uuid";
58   private static final String APP_2_UUID = "app2-uuid";
59   private static final String APP_2_MASTER_BRANCH_UUID = "app2-master-uuid";
60   private static final String APP_2_BRANCH_1_UUID = "app2-branch1-uuid";
61   private static final String EMPTY_XML = "<views></views>";
62
63   private static final String EMPTY_APP_XML = "<views>\n" +
64     "    <vw key=\"app1-key\" def=\"false\">\n" +
65     "        <name><![CDATA[app1]]></name>\n" +
66     "        <desc><![CDATA[]]></desc>\n" +
67     "        <qualifier><![CDATA[APP]]></qualifier>\n" +
68     "    </vw>\n" +
69     "</views>";
70
71   private static final String APP_WITH_NO_BRANCHES_XML = "<views>\n" +
72     "    <vw key=\"app1-key\" def=\"false\">\n" +
73     "        <name><![CDATA[app1-key]]></name>\n" +
74     "        <desc><![CDATA[]]></desc>\n" +
75     "        <qualifier><![CDATA[APP]]></qualifier>\n" +
76     "        <p>proj1-key</p>\n" +
77     "        <p>proj2-key</p>\n" +
78     "    </vw>\n" +
79     "</views>";
80
81   private static final String COMPLEX_XML_BEFORE = "<views>\n" +
82     "    <vw key=\"app1-key\" def=\"false\">\n" +
83     "        <name><![CDATA[app1-key]]></name>\n" +
84     "        <desc><![CDATA[]]></desc>\n" +
85     "        <qualifier><![CDATA[APP]]></qualifier>\n" +
86     "        <p>proj1-key</p>\n" +
87     "        <p>proj2-key</p>\n" +
88     "        <branch key=\"app1-branch1\">\n" +
89     "            <p branch=\"proj1-branch-1\">proj1-key</p>\n" +
90     "            <p branch=\"m1\">proj2-key</p>\n" +
91     "        </branch>\n" +
92     "        <branch key=\"app1-branch2\">\n" +
93     "            <p branch=\"proj1-branch-2\">proj1-key</p>\n" +
94     "            <p branch=\"m1\">proj2-key</p>\n" +
95     "        </branch>\n" +
96     "    </vw>\n" +
97     "    <vw key=\"app2-key\" def=\"false\">\n" +
98     "        <name><![CDATA[app2-key]]></name>\n" +
99     "        <desc><![CDATA[]]></desc>\n" +
100     "        <qualifier><![CDATA[APP]]></qualifier>\n" +
101     "        <p>proj1-key</p>\n" +
102     "        <p>proj2-key</p>\n" +
103     "        <branch key=\"m1\">\n" +
104     "            <p branch=\"proj1-branch-1\">proj1-key</p>\n" +
105     "            <p branch=\"m1\">proj2-key</p>\n" +
106     "        </branch>\n" +
107     "    </vw>\n" +
108     "    <vw key=\"port1\" def=\"true\">\n" +
109     "        <name><![CDATA[port1]]></name>\n" +
110     "        <desc><![CDATA[]]></desc>\n" +
111     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
112     "    </vw>\n" +
113     "    <vw key=\"port2\" def=\"false\">\n" +
114     "        <name><![CDATA[port2]]></name>\n" +
115     "        <desc><![CDATA[]]></desc>\n" +
116     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
117     "        <vw-ref><![CDATA[app1-key]]></vw-ref>\n" +
118     "        <vw-ref><![CDATA[port1]]></vw-ref>\n" +
119     "    </vw>\n" +
120     "    <vw key=\"port3\" def=\"false\">\n" +
121     "        <name><![CDATA[port3]]></name>\n" +
122     "        <desc><![CDATA[]]></desc>\n" +
123     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
124     "        <p>proj1-key</p>\n" +
125     "    </vw>\n" +
126     "    <vw key=\"port4\" def=\"false\">\n" +
127     "        <name><![CDATA[port4]]></name>\n" +
128     "        <desc><![CDATA[]]></desc>\n" +
129     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
130     "    </vw>\n" +
131     "    <vw key=\"portx\" def=\"false\" root=\"port2\" parent=\"port2\">\n" +
132     "        <name><![CDATA[portx]]></name>\n" +
133     "    </vw>\n" +
134     "    <vw key=\"port5\" def=\"false\">\n" +
135     "        <name><![CDATA[port5]]></name>\n" +
136     "        <desc><![CDATA[]]></desc>\n" +
137     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
138     "        <tagsAssociation>\n" +
139     "            <tag>tag1</tag>\n" +
140     "        </tagsAssociation>\n" +
141     "    </vw>\n" +
142     "    <vw key=\"port6\" def=\"false\">\n" +
143     "        <name><![CDATA[port6]]></name>\n" +
144     "        <desc><![CDATA[]]></desc>\n" +
145     "        <regexp><![CDATA[.*oj.*]]></regexp>\n" +
146     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
147     "    </vw>\n" +
148     "    <vw key=\"port7\" def=\"false\">\n" +
149     "        <name><![CDATA[port7]]></name>\n" +
150     "        <desc><![CDATA[]]></desc>\n" +
151     "        <tag_key><![CDATA[business_value]]></tag_key>\n" +
152     "        <tag_value><![CDATA[12]]></tag_value>\n" +
153     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
154     "    </vw>\n" +
155     "</views>";
156
157   private static final String COMPLEX_XML_AFTER = "<views>\n" +
158     "    <vw key=\"port1\" def=\"true\">\n" +
159     "        <name><![CDATA[port1]]></name>\n" +
160     "        <desc><![CDATA[]]></desc>\n" +
161     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
162     "    </vw>\n" +
163     "    <vw key=\"port2\" def=\"false\">\n" +
164     "        <name><![CDATA[port2]]></name>\n" +
165     "        <desc><![CDATA[]]></desc>\n" +
166     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
167     "        <vw-ref><![CDATA[app1-key]]></vw-ref>\n" +
168     "        <vw-ref><![CDATA[port1]]></vw-ref>\n" +
169     "    </vw>\n" +
170     "    <vw key=\"port3\" def=\"false\">\n" +
171     "        <name><![CDATA[port3]]></name>\n" +
172     "        <desc><![CDATA[]]></desc>\n" +
173     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
174     "        <p>proj1-key</p>\n" +
175     "    </vw>\n" +
176     "    <vw key=\"port4\" def=\"false\">\n" +
177     "        <name><![CDATA[port4]]></name>\n" +
178     "        <desc><![CDATA[]]></desc>\n" +
179     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
180     "    </vw>\n" +
181     "    <vw key=\"portx\" def=\"false\" root=\"port2\" parent=\"port2\">\n" +
182     "        <name><![CDATA[portx]]></name>\n" +
183     "    </vw>\n" +
184     "    <vw key=\"port5\" def=\"false\">\n" +
185     "        <name><![CDATA[port5]]></name>\n" +
186     "        <desc><![CDATA[]]></desc>\n" +
187     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
188     "        <tagsAssociation>\n" +
189     "            <tag>tag1</tag>\n" +
190     "        </tagsAssociation>\n" +
191     "    </vw>\n" +
192     "    <vw key=\"port6\" def=\"false\">\n" +
193     "        <name><![CDATA[port6]]></name>\n" +
194     "        <desc><![CDATA[]]></desc>\n" +
195     "        <regexp><![CDATA[.*oj.*]]></regexp>\n" +
196     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
197     "    </vw>\n" +
198     "    <vw key=\"port7\" def=\"false\">\n" +
199     "        <name><![CDATA[port7]]></name>\n" +
200     "        <desc><![CDATA[]]></desc>\n" +
201     "        <tag_key><![CDATA[business_value]]></tag_key>\n" +
202     "        <tag_value><![CDATA[12]]></tag_value>\n" +
203     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
204     "    </vw>\n" +
205     "</views>";
206
207   private static final String LARGE_XML_BEFORE_AND_AFTER = "<views>\n" +
208     "    <vw key=\"port1\" def=\"true\">\n" +
209     "        <name><![CDATA[port1]]></name>\n" +
210     "        <desc><![CDATA[]]></desc>\n" +
211     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
212     "    </vw>\n" +
213     "    <vw key=\"port2\" def=\"false\">\n" +
214     "        <name><![CDATA[port2]]></name>\n" +
215     "        <desc><![CDATA[]]></desc>\n" +
216     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
217     "        <vw-ref><![CDATA[app1-key]]></vw-ref>\n" +
218     "        <vw-ref><![CDATA[port1]]></vw-ref>\n" +
219     "    </vw>\n" +
220     "    <vw key=\"port3\" def=\"false\">\n" +
221     "        <name><![CDATA[port3]]></name>\n" +
222     "        <desc><![CDATA[]]></desc>\n" +
223     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
224     "        <p>proj1-key</p>\n" +
225     "    </vw>\n" +
226     "    <vw key=\"port4\" def=\"false\">\n" +
227     "        <name><![CDATA[port4]]></name>\n" +
228     "        <desc><![CDATA[]]></desc>\n" +
229     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
230     "    </vw>\n" +
231     "    <vw key=\"portx\" def=\"false\" root=\"port2\" parent=\"port2\">\n" +
232     "        <name><![CDATA[portx]]></name>\n" +
233     "    </vw>\n" +
234     "    <vw key=\"port5\" def=\"false\">\n" +
235     "        <name><![CDATA[port5]]></name>\n" +
236     "        <desc><![CDATA[]]></desc>\n" +
237     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
238     "        <tagsAssociation>\n" +
239     "            <tag>tag1</tag>\n" +
240     "        </tagsAssociation>\n" +
241     "    </vw>\n" +
242     "    <vw key=\"port6\" def=\"false\">\n" +
243     "        <name><![CDATA[port6]]></name>\n" +
244     "        <desc><![CDATA[]]></desc>\n" +
245     "        <regexp><![CDATA[.*oj.*]]></regexp>\n" +
246     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
247     "    </vw>\n" +
248     "    <vw key=\"port7\" def=\"false\">\n" +
249     "        <name><![CDATA[port7]]></name>\n" +
250     "        <desc><![CDATA[]]></desc>\n" +
251     "        <tag_key><![CDATA[business_value]]></tag_key>\n" +
252     "        <tag_value><![CDATA[12]]></tag_value>\n" +
253     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
254     "    </vw>\n" +
255     "    <vw key=\"port8\" def=\"false\">\n" +
256     "        <name><![CDATA[port7]]></name>\n" +
257     "        <desc><![CDATA[]]></desc>\n" +
258     "        <tag_key><![CDATA[business_value]]></tag_key>\n" +
259     "        <tag_value><![CDATA[12]]></tag_value>\n" +
260     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
261     "    </vw>\n" +
262     "    <vw key=\"port9\" def=\"false\">\n" +
263     "        <name><![CDATA[port7]]></name>\n" +
264     "        <desc><![CDATA[]]></desc>\n" +
265     "        <tag_key><![CDATA[business_value]]></tag_key>\n" +
266     "        <tag_value><![CDATA[12]]></tag_value>\n" +
267     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
268     "    </vw>\n" +
269     "    <vw key=\"port10\" def=\"false\">\n" +
270     "        <name><![CDATA[port7]]></name>\n" +
271     "        <desc><![CDATA[]]></desc>\n" +
272     "        <tag_key><![CDATA[business_value]]></tag_key>\n" +
273     "        <tag_value><![CDATA[12]]></tag_value>\n" +
274     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
275     "    </vw>\n" +
276     "    <vw key=\"port11\" def=\"false\">\n" +
277     "        <name><![CDATA[port7]]></name>\n" +
278     "        <desc><![CDATA[]]></desc>\n" +
279     "        <tag_key><![CDATA[business_value]]></tag_key>\n" +
280     "        <tag_value><![CDATA[12]]></tag_value>\n" +
281     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
282     "    </vw>\n" +
283     "    <vw key=\"port12\" def=\"false\">\n" +
284     "        <name><![CDATA[port7]]></name>\n" +
285     "        <desc><![CDATA[]]></desc>\n" +
286     "        <tag_key><![CDATA[business_value]]></tag_key>\n" +
287     "        <tag_value><![CDATA[12]]></tag_value>\n" +
288     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
289     "    </vw>\n" +
290     "    <vw key=\"port13\" def=\"false\">\n" +
291     "        <name><![CDATA[port7]]></name>\n" +
292     "        <desc><![CDATA[]]></desc>\n" +
293     "        <tag_key><![CDATA[business_value]]></tag_key>\n" +
294     "        <tag_value><![CDATA[12]]></tag_value>\n" +
295     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
296     "    </vw>\n" +
297     "    <vw key=\"port14\" def=\"false\">\n" +
298     "        <name><![CDATA[port7]]></name>\n" +
299     "        <desc><![CDATA[]]></desc>\n" +
300     "        <tag_key><![CDATA[business_value]]></tag_key>\n" +
301     "        <tag_value><![CDATA[12]]></tag_value>\n" +
302     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
303     "    </vw>\n" +
304     "    <vw key=\"port15\" def=\"false\">\n" +
305     "        <name><![CDATA[port7]]></name>\n" +
306     "        <desc><![CDATA[]]></desc>\n" +
307     "        <tag_key><![CDATA[business_value]]></tag_key>\n" +
308     "        <tag_value><![CDATA[12]]></tag_value>\n" +
309     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
310     "    </vw>\n" +
311     "    <vw key=\"port16\" def=\"false\">\n" +
312     "        <name><![CDATA[port7]]></name>\n" +
313     "        <desc><![CDATA[]]></desc>\n" +
314     "        <tag_key><![CDATA[business_value]]></tag_key>\n" +
315     "        <tag_value><![CDATA[12]]></tag_value>\n" +
316     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
317     "    </vw>\n" +
318     "    <vw key=\"port17\" def=\"false\">\n" +
319     "        <name><![CDATA[port7]]></name>\n" +
320     "        <desc><![CDATA[]]></desc>\n" +
321     "        <tag_key><![CDATA[business_value]]></tag_key>\n" +
322     "        <tag_value><![CDATA[12]]></tag_value>\n" +
323     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
324     "    </vw>\n" +
325     "    <vw key=\"port18\" def=\"false\">\n" +
326     "        <name><![CDATA[port7]]></name>\n" +
327     "        <desc><![CDATA[]]></desc>\n" +
328     "        <tag_key><![CDATA[business_value]]></tag_key>\n" +
329     "        <tag_value><![CDATA[12]]></tag_value>\n" +
330     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
331     "    </vw>\n" +
332     "    <vw key=\"port19\" def=\"false\">\n" +
333     "        <name><![CDATA[port7]]></name>\n" +
334     "        <desc><![CDATA[]]></desc>\n" +
335     "        <tag_key><![CDATA[business_value]]></tag_key>\n" +
336     "        <tag_value><![CDATA[12]]></tag_value>\n" +
337     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
338     "    </vw>\n" +
339     "    <vw key=\"port20\" def=\"false\">\n" +
340     "        <name><![CDATA[port7]]></name>\n" +
341     "        <desc><![CDATA[]]></desc>\n" +
342     "        <tag_key><![CDATA[business_value]]></tag_key>\n" +
343     "        <tag_value><![CDATA[12]]></tag_value>\n" +
344     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
345     "    </vw>\n" +
346     "    <vw key=\"port21\" def=\"false\">\n" +
347     "        <name><![CDATA[port7]]></name>\n" +
348     "        <desc><![CDATA[]]></desc>\n" +
349     "        <tag_key><![CDATA[business_value]]></tag_key>\n" +
350     "        <tag_value><![CDATA[12]]></tag_value>\n" +
351     "        <qualifier><![CDATA[VW]]></qualifier>\n" +
352     "    </vw>\n" +
353     "</views>";
354
355   @Rule
356   public CoreDbTester db = CoreDbTester.createForSchema(MigrateApplicationDefinitionsFromXmlToDbTest.class, "schema.sql");
357
358   private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();
359   private final System2 system2 = new TestSystem2().setNow(NOW);
360   private final MigrationStep underTest = new MigrateApplicationDefinitionsFromXmlToDb(db.database(), uuidFactory, system2);
361
362   @Test
363   public void does_nothing_when_no_views_def_property() throws SQLException {
364     setupProjectsAndApps();
365
366     underTest.execute();
367
368     assertThat(db.countSql("select count(*) from app_projects")).isZero();
369     assertThat(db.countSql("select count(*) from app_branch_project_branch")).isZero();
370     assertThat(db.countSql("select count(*) from internal_properties where kee='views.def'")).isZero();
371   }
372
373   @Test
374   public void does_nothing_when_views_def_property_empty_string() throws SQLException {
375     setupProjectsAndApps();
376     insertViewsDefInternalProperty("");
377
378     underTest.execute();
379
380     assertThat(db.countSql("select count(*) from app_projects")).isZero();
381     assertThat(db.countSql("select count(*) from app_branch_project_branch")).isZero();
382   }
383
384   @Test
385   public void does_nothing_when_views_def_property_empty_views_content() throws SQLException {
386     setupProjectsAndApps();
387     insertViewsDefInternalProperty(EMPTY_XML);
388
389     underTest.execute();
390
391     assertThat(db.countSql("select count(*) from app_projects")).isZero();
392     assertThat(db.countSql("select count(*) from app_branch_project_branch")).isZero();
393   }
394
395   @Test
396   public void throws_ISE_when_views_def_property_does_not_pass_validation() {
397     setupProjectsAndApps();
398     insertViewsDefInternalProperty("abcdefghi");
399
400     assertThatThrownBy(underTest::execute)
401       .isInstanceOf(IllegalStateException.class)
402       .hasMessage("Failed to migrate views definitions property.");
403
404     assertThat(db.countSql("select count(*) from app_projects")).isZero();
405     assertThat(db.countSql("select count(*) from app_branch_project_branch")).isZero();
406   }
407
408   @Test
409   public void migrates_applications_to_new_tables() throws SQLException {
410     setupProjectsAndApps();
411     insertViewsDefInternalProperty(COMPLEX_XML_BEFORE);
412
413     underTest.execute();
414
415     assertThat(db.select("select uuid from projects"))
416       .extracting(r -> r.get("UUID"))
417       .containsExactlyInAnyOrder(PROJECT_1_UUID, PROJECT_2_UUID, APP_1_UUID, APP_2_UUID);
418     assertThat(db.select("select application_uuid, project_uuid from app_projects"))
419       .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"))
420       .containsExactlyInAnyOrder(
421         tuple(APP_1_UUID, PROJECT_1_UUID),
422         tuple(APP_1_UUID, PROJECT_2_UUID),
423         tuple(APP_2_UUID, PROJECT_1_UUID),
424         tuple(APP_2_UUID, PROJECT_2_UUID));
425     assertThat(db.select("select application_uuid, project_uuid, application_branch_uuid, project_branch_uuid from app_branch_project_branch"))
426       .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"), r -> r.get("APPLICATION_BRANCH_UUID"), r -> r.get("PROJECT_BRANCH_UUID"))
427       .containsExactlyInAnyOrder(
428         tuple(APP_1_UUID, PROJECT_1_UUID, APP_1_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID),
429         tuple(APP_1_UUID, PROJECT_2_UUID, APP_1_BRANCH_1_UUID, PROJECT_2_BRANCH_1_UUID),
430         tuple(APP_1_UUID, PROJECT_1_UUID, APP_1_BRANCH_2_UUID, PROJECT_1_BRANCH_2_UUID),
431         tuple(APP_1_UUID, PROJECT_2_UUID, APP_1_BRANCH_2_UUID, PROJECT_2_BRANCH_1_UUID),
432         tuple(APP_2_UUID, PROJECT_1_UUID, APP_2_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID),
433         tuple(APP_2_UUID, PROJECT_2_UUID, APP_2_BRANCH_1_UUID, PROJECT_2_BRANCH_1_UUID));
434   }
435
436   @Test
437   public void migration_is_resilient() throws SQLException {
438     setupProjectsAndApps();
439     insertViewsDefInternalProperty(COMPLEX_XML_BEFORE);
440
441     // first attempt
442     underTest.execute();
443
444     // xml stays the same (stopped during migration)
445     updateViewsDefInternalProperty(COMPLEX_XML_BEFORE);
446
447     // second attempt should not fail
448     underTest.execute();
449
450     assertThat(db.select("select uuid from projects"))
451       .extracting(r -> r.get("UUID"))
452       .containsExactlyInAnyOrder(PROJECT_1_UUID, PROJECT_2_UUID, APP_1_UUID, APP_2_UUID);
453     assertThat(db.select("select application_uuid, project_uuid from app_projects"))
454       .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"))
455       .containsExactlyInAnyOrder(
456         tuple(APP_1_UUID, PROJECT_1_UUID),
457         tuple(APP_1_UUID, PROJECT_2_UUID),
458         tuple(APP_2_UUID, PROJECT_1_UUID),
459         tuple(APP_2_UUID, PROJECT_2_UUID));
460     assertThat(db.select("select application_uuid, project_uuid, application_branch_uuid, project_branch_uuid from app_branch_project_branch"))
461       .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"), r -> r.get("APPLICATION_BRANCH_UUID"), r -> r.get("PROJECT_BRANCH_UUID"))
462       .containsExactlyInAnyOrder(
463         tuple(APP_1_UUID, PROJECT_1_UUID, APP_1_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID),
464         tuple(APP_1_UUID, PROJECT_2_UUID, APP_1_BRANCH_1_UUID, PROJECT_2_BRANCH_1_UUID),
465         tuple(APP_1_UUID, PROJECT_1_UUID, APP_1_BRANCH_2_UUID, PROJECT_1_BRANCH_2_UUID),
466         tuple(APP_1_UUID, PROJECT_2_UUID, APP_1_BRANCH_2_UUID, PROJECT_2_BRANCH_1_UUID),
467         tuple(APP_2_UUID, PROJECT_1_UUID, APP_2_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID),
468         tuple(APP_2_UUID, PROJECT_2_UUID, APP_2_BRANCH_1_UUID, PROJECT_2_BRANCH_1_UUID));
469   }
470
471   @Test
472   public void migrates_applications_without_application_branches_to_new_tables() throws SQLException {
473     setupFullProject1();
474     setupProject2();
475     setupApp1WithNoBranches();
476     insertViewsDefInternalProperty(APP_WITH_NO_BRANCHES_XML);
477
478     underTest.execute();
479
480     assertThat(db.select("select uuid from projects"))
481       .extracting(r -> r.get("UUID"))
482       .containsExactlyInAnyOrder(PROJECT_1_UUID, PROJECT_2_UUID, APP_1_UUID);
483     assertThat(db.select("select application_uuid, project_uuid from app_projects"))
484       .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"))
485       .containsExactlyInAnyOrder(
486         tuple(APP_1_UUID, PROJECT_1_UUID),
487         tuple(APP_1_UUID, PROJECT_2_UUID));
488     assertThat(db.countSql("select count(*) from app_branch_project_branch")).isZero();
489   }
490
491   @Test
492   public void skips_apps_that_exist_in_the_definition_but_does_not_exist_in_db() throws SQLException {
493     setupFullProject1();
494     insertViewsDefInternalProperty(EMPTY_APP_XML);
495
496     underTest.execute();
497
498     assertThat(db.select("select uuid from projects"))
499       .extracting(r -> r.get("UUID"))
500       .containsExactlyInAnyOrder(PROJECT_1_UUID);
501     assertThat(db.countSql("select count(*) from app_projects")).isZero();
502     assertThat(db.countSql("select count(*) from app_branch_project_branch")).isZero();
503   }
504
505   @Test
506   public void migrates_app_with_0_projects_in_views_definition() throws SQLException {
507     setupFullProject1();
508     setupProject2();
509     setupApp1WithNoBranches();
510     insertViewsDefInternalProperty(EMPTY_APP_XML);
511
512     underTest.execute();
513
514     assertThat(db.select("select uuid from projects"))
515       .extracting(r -> r.get("UUID"))
516       .containsExactlyInAnyOrder(PROJECT_1_UUID, PROJECT_2_UUID, APP_1_UUID);
517     assertThat(db.countSql("select count(*) from app_projects")).isZero();
518     assertThat(db.countSql("select count(*) from app_branch_project_branch")).isZero();
519   }
520
521   @Test
522   public void skips_apps_that_are_present_in_views_definition_but_not_in_db() throws SQLException {
523     setupFullProject1();
524     setupProject2();
525     setupApp1WithTwoBranches();
526     insertViewsDefInternalProperty(COMPLEX_XML_BEFORE);
527
528     underTest.execute();
529
530     assertThat(db.select("select uuid from projects"))
531       .extracting(r -> r.get("UUID"))
532       .containsExactlyInAnyOrder(PROJECT_1_UUID, PROJECT_2_UUID, APP_1_UUID);
533     assertThat(db.select("select application_uuid, project_uuid from app_projects"))
534       .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"))
535       .containsExactlyInAnyOrder(
536         tuple(APP_1_UUID, PROJECT_1_UUID),
537         tuple(APP_1_UUID, PROJECT_2_UUID));
538     assertThat(db.select("select application_uuid, project_uuid, application_branch_uuid, project_branch_uuid from app_branch_project_branch"))
539       .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"), r -> r.get("APPLICATION_BRANCH_UUID"), r -> r.get("PROJECT_BRANCH_UUID"))
540       .containsExactlyInAnyOrder(
541         tuple(APP_1_UUID, PROJECT_1_UUID, APP_1_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID),
542         tuple(APP_1_UUID, PROJECT_2_UUID, APP_1_BRANCH_1_UUID, PROJECT_2_BRANCH_1_UUID),
543         tuple(APP_1_UUID, PROJECT_1_UUID, APP_1_BRANCH_2_UUID, PROJECT_1_BRANCH_2_UUID),
544         tuple(APP_1_UUID, PROJECT_2_UUID, APP_1_BRANCH_2_UUID, PROJECT_2_BRANCH_1_UUID));
545   }
546
547   @Test
548   public void skips_app_branches_that_are_present_in_views_definition_but_not_in_db() throws SQLException {
549     setupFullProject1();
550     setupProject2();
551     setupApp1WithNoBranches();
552     setupApp2();
553     insertViewsDefInternalProperty(COMPLEX_XML_BEFORE);
554
555     underTest.execute();
556
557     assertThat(db.select("select uuid from projects"))
558       .extracting(r -> r.get("UUID"))
559       .containsExactlyInAnyOrder(PROJECT_1_UUID, PROJECT_2_UUID, APP_1_UUID, APP_2_UUID);
560     assertThat(db.select("select application_uuid, project_uuid from app_projects"))
561       .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"))
562       .containsExactlyInAnyOrder(
563         tuple(APP_1_UUID, PROJECT_1_UUID),
564         tuple(APP_1_UUID, PROJECT_2_UUID),
565         tuple(APP_2_UUID, PROJECT_1_UUID),
566         tuple(APP_2_UUID, PROJECT_2_UUID));
567     assertThat(db.select("select application_uuid, project_uuid, application_branch_uuid, project_branch_uuid from app_branch_project_branch"))
568       .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"), r -> r.get("APPLICATION_BRANCH_UUID"), r -> r.get("PROJECT_BRANCH_UUID"))
569       .containsExactlyInAnyOrder(
570         tuple(APP_2_UUID, PROJECT_1_UUID, APP_2_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID),
571         tuple(APP_2_UUID, PROJECT_2_UUID, APP_2_BRANCH_1_UUID, PROJECT_2_BRANCH_1_UUID));
572   }
573
574   @Test
575   public void skips_projects_that_are_present_in_apps_views_definitions_but_not_in_db() throws SQLException {
576     setupPartialProject1();
577     setupApp1WithTwoBranches();
578     setupApp2();
579     insertViewsDefInternalProperty(COMPLEX_XML_BEFORE);
580
581     underTest.execute();
582
583     assertThat(db.select("select uuid from projects"))
584       .extracting(r -> r.get("UUID"))
585       .containsExactlyInAnyOrder(PROJECT_1_UUID, APP_1_UUID, APP_2_UUID);
586     assertThat(db.select("select application_uuid, project_uuid from app_projects"))
587       .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"))
588       .containsExactlyInAnyOrder(
589         tuple(APP_1_UUID, PROJECT_1_UUID),
590         tuple(APP_2_UUID, PROJECT_1_UUID));
591     assertThat(db.select("select application_uuid, project_uuid, application_branch_uuid, project_branch_uuid from app_branch_project_branch"))
592       .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"), r -> r.get("APPLICATION_BRANCH_UUID"), r -> r.get("PROJECT_BRANCH_UUID"))
593       .containsExactlyInAnyOrder(
594         tuple(APP_1_UUID, PROJECT_1_UUID, APP_1_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID),
595         tuple(APP_2_UUID, PROJECT_1_UUID, APP_2_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID));
596   }
597
598   @Test
599   public void skips_projects_branches_that_are_present_in_apps_views_definitions_but_not_in_db() throws SQLException {
600     setupPartialProject1();
601     setupProject2();
602     setupApp1WithTwoBranches();
603     setupApp2();
604     insertViewsDefInternalProperty(COMPLEX_XML_BEFORE);
605
606     underTest.execute();
607
608     assertThat(db.select("select uuid from projects"))
609       .extracting(r -> r.get("UUID"))
610       .containsExactlyInAnyOrder(PROJECT_1_UUID, PROJECT_2_UUID, APP_1_UUID, APP_2_UUID);
611     assertThat(db.select("select application_uuid, project_uuid from app_projects"))
612       .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"))
613       .containsExactlyInAnyOrder(
614         tuple(APP_1_UUID, PROJECT_1_UUID),
615         tuple(APP_1_UUID, PROJECT_2_UUID),
616         tuple(APP_2_UUID, PROJECT_1_UUID),
617         tuple(APP_2_UUID, PROJECT_2_UUID));
618     assertThat(db.select("select application_uuid, project_uuid, application_branch_uuid, project_branch_uuid from app_branch_project_branch"))
619       .extracting(r -> r.get("APPLICATION_UUID"), r -> r.get("PROJECT_UUID"), r -> r.get("APPLICATION_BRANCH_UUID"), r -> r.get("PROJECT_BRANCH_UUID"))
620       .containsExactlyInAnyOrder(
621         tuple(APP_1_UUID, PROJECT_1_UUID, APP_1_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID),
622         tuple(APP_1_UUID, PROJECT_2_UUID, APP_1_BRANCH_1_UUID, PROJECT_2_BRANCH_1_UUID),
623         tuple(APP_1_UUID, PROJECT_2_UUID, APP_1_BRANCH_2_UUID, PROJECT_2_BRANCH_1_UUID),
624         tuple(APP_2_UUID, PROJECT_1_UUID, APP_2_BRANCH_1_UUID, PROJECT_1_BRANCH_1_UUID),
625         tuple(APP_2_UUID, PROJECT_2_UUID, APP_2_BRANCH_1_UUID, PROJECT_2_BRANCH_1_UUID));
626   }
627
628   @Test
629   public void removes_application_definitions_from_xml() throws SQLException {
630     setupProjectsAndApps();
631     insertViewsDefInternalProperty(COMPLEX_XML_BEFORE);
632
633     underTest.execute();
634
635     assertThat(db.countSql("select count(*) from internal_properties where kee='views.def'")).isOne();
636     assertViewsXmlDefinitionSimilar(COMPLEX_XML_AFTER, false);
637   }
638
639   @Test
640   public void removes_application_definitions_from_large_xmls() throws SQLException {
641     setupProjectsAndApps();
642     insertViewsDefInternalProperty(LARGE_XML_BEFORE_AND_AFTER);
643
644     underTest.execute();
645
646     assertThat(db.countSql("select count(*) from internal_properties where kee='views.def'")).isOne();
647     assertViewsXmlDefinitionSimilar(LARGE_XML_BEFORE_AND_AFTER, true);
648   }
649
650   @Test
651   public void does_not_change_the_xml_if_there_are_no_application_definitions() throws SQLException {
652     setupProjectsAndApps();
653     insertViewsDefInternalProperty(EMPTY_XML);
654
655     underTest.execute();
656
657     assertThat(db.countSql("select count(*) from internal_properties where kee='views.def'")).isOne();
658     assertViewsXmlDefinitionSimilar(EMPTY_XML, false);
659   }
660
661   private void setupProjectsAndApps() {
662     setupFullProject1();
663     setupProject2();
664     setupApp1WithTwoBranches();
665     setupApp2();
666   }
667
668   private void setupFullProject1() {
669     setupPartialProject1();
670     insertBranch(PROJECT_1_BRANCH_2_UUID, PROJECT_1_UUID, "proj1-branch-2");
671   }
672
673   private void setupPartialProject1() {
674     insertProject(PROJECT_1_UUID, "proj1-key", QUALIFIER_PROJECT);
675     insertBranch(PROJECT_1_MASTER_BRANCH_UUID, PROJECT_1_UUID, "master");
676     insertBranch(PROJECT_1_BRANCH_1_UUID, PROJECT_1_UUID, "proj1-branch-1");
677   }
678
679   private void setupProject2() {
680     insertProject(PROJECT_2_UUID, "proj2-key", QUALIFIER_PROJECT);
681     insertBranch(PROJECT_2_MASTER_BRANCH_UUID, PROJECT_2_UUID, "master");
682     insertBranch(PROJECT_2_BRANCH_1_UUID, PROJECT_2_UUID, "m1");
683   }
684
685   private void setupApp1WithNoBranches() {
686     insertProject(APP_1_UUID, "app1-key", QUALIFIER_APP);
687     insertBranch(APP_1_MASTER_BRANCH_UUID, APP_1_UUID, "master");
688   }
689
690   private void setupApp1WithOneBranch() {
691     setupApp1WithNoBranches();
692     insertBranch(APP_1_BRANCH_1_UUID, APP_1_UUID, "app1-branch1");
693   }
694
695   private void setupApp1WithTwoBranches() {
696     setupApp1WithOneBranch();
697     insertBranch(APP_1_BRANCH_2_UUID, APP_1_UUID, "app1-branch2");
698   }
699
700   private void setupApp2() {
701     insertProject(APP_2_UUID, "app2-key", QUALIFIER_APP);
702     insertBranch(APP_2_MASTER_BRANCH_UUID, APP_2_UUID, "master");
703     insertBranch(APP_2_BRANCH_1_UUID, APP_2_UUID, "m1");
704   }
705
706   private void insertViewsDefInternalProperty(@Nullable String xml) {
707     String valueColumn = "text_value";
708     if (xml != null && xml.length() > TEXT_VALUE_MAX_LENGTH) {
709       valueColumn = "clob_value";
710     }
711
712     db.executeInsert("internal_properties",
713       "kee", "views.def",
714       "is_empty", "false",
715       valueColumn, xml,
716       "created_at", system2.now());
717   }
718
719   private void updateViewsDefInternalProperty(@Nullable String xml) {
720     db.executeUpdateSql("update internal_properties set text_value = ? where kee = 'views.def'",
721       xml);
722   }
723
724   private void insertProject(String uuid, String key, String qualifier) {
725     db.executeInsert("PROJECTS",
726       "UUID", uuid,
727       "KEE", key,
728       "QUALIFIER", qualifier,
729       "ORGANIZATION_UUID", uuid + "-key",
730       "TAGS", "tag1",
731       "PRIVATE", Boolean.toString(false),
732       "UPDATED_AT", System2.INSTANCE.now());
733   }
734
735   private void insertBranch(String uuid, String projectUuid, String key) {
736     db.executeInsert(
737       "PROJECT_BRANCHES",
738       "UUID", uuid,
739       "PROJECT_UUID", projectUuid,
740       "KEE", key,
741       "BRANCH_TYPE", "BRANCH",
742       "MERGE_BRANCH_UUID", null,
743       "CREATED_AT", System2.INSTANCE.now(),
744       "UPDATED_AT", System2.INSTANCE.now(),
745       "NEED_ISSUE_SYNC", Boolean.toString(false));
746   }
747
748   private void assertViewsXmlDefinitionSimilar(final String expectedValue, final boolean expectClob) {
749     Map<String, Object> result = db.selectFirst("select text_value, clob_value from internal_properties where kee='views.def'");
750     String textValue = (String) result.get("TEXT_VALUE");
751     String clobValue = (String) result.get("CLOB_VALUE");
752
753     String existingValue;
754     if (expectClob) {
755       existingValue = clobValue;
756       assertThat(textValue).isNull();
757     } else {
758       existingValue = textValue;
759       assertThat(clobValue).isNull();
760     }
761
762     Diff diff = DiffBuilder
763       .compare(Input.fromString(expectedValue))
764       .withTest(Input.fromString(existingValue))
765       .ignoreWhitespace()
766       .ignoreComments()
767       .checkForSimilar()
768       .build();
769     assertThat(diff.getDifferences())
770       .as(expectedValue)
771       .isEmpty();
772   }
773 }