1   /*
2    * Copyright 2020 LINE Corporation
3    *
4    * LINE Corporation licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package com.linecorp.centraldogma.testing.junit;
17  
18  import java.io.IOException;
19  import java.net.InetSocketAddress;
20  import java.nio.file.Path;
21  import java.util.concurrent.CompletableFuture;
22  import java.util.concurrent.CompletionException;
23  
24  import org.junit.jupiter.api.extension.Extension;
25  import org.junit.jupiter.api.extension.ExtensionContext;
26  
27  import com.spotify.futures.CompletableFutures;
28  
29  import com.linecorp.armeria.client.WebClient;
30  import com.linecorp.armeria.client.WebClientBuilder;
31  import com.linecorp.armeria.common.annotation.UnstableApi;
32  import com.linecorp.centraldogma.client.CentralDogma;
33  import com.linecorp.centraldogma.client.armeria.ArmeriaCentralDogmaBuilder;
34  import com.linecorp.centraldogma.client.armeria.legacy.LegacyCentralDogmaBuilder;
35  import com.linecorp.centraldogma.server.CentralDogmaBuilder;
36  import com.linecorp.centraldogma.server.MirroringService;
37  import com.linecorp.centraldogma.server.storage.project.ProjectManager;
38  import com.linecorp.centraldogma.testing.internal.CentralDogmaRuleDelegate;
39  import com.linecorp.centraldogma.testing.internal.TemporaryFolder;
40  
41  /**
42   * A JUnit {@link Extension} that starts an embedded Central Dogma server.
43   *
44   * <pre>{@code
45   * > class MyTest {
46   * >     @RegisterExtension
47   * >     static final CentralDogmaExtension extension = new CentralDogmaExtension();
48   * >
49   * >     @Test
50   * >     void test() throws Exception {
51   * >         CentralDogma dogma = extension.client();
52   * >         dogma.push(...).join();
53   * >         ...
54   * >     }
55   * > }
56   * }</pre>
57   */
58  public class CentralDogmaExtension extends AbstractAllOrEachExtension {
59  
60      private final CentralDogmaRuleDelegate delegate;
61  
62      private final TemporaryFolder dataDir = new TemporaryFolder();
63  
64      /**
65       * Creates a new instance with TLS disabled.
66       */
67      public CentralDogmaExtension() {
68          this(false);
69      }
70  
71      /**
72       * Creates a new instance.
73       */
74      public CentralDogmaExtension(boolean useTls) {
75          delegate = new CentralDogmaRuleDelegate(useTls) {
76              @Override
77              protected void configure(CentralDogmaBuilder builder) {
78                  CentralDogmaExtension.this.configure(builder);
79              }
80  
81              @Override
82              protected void configureClient(ArmeriaCentralDogmaBuilder builder) {
83                  CentralDogmaExtension.this.configureClient(builder);
84              }
85  
86              @Override
87              protected void configureClient(LegacyCentralDogmaBuilder builder) {
88                  CentralDogmaExtension.this.configureClient(builder);
89              }
90  
91              @Override
92              protected void configureHttpClient(WebClientBuilder builder) {
93                  CentralDogmaExtension.this.configureHttpClient(builder);
94              }
95  
96              @Override
97              protected void scaffold(CentralDogma client) {
98                  CentralDogmaExtension.this.scaffold(client);
99              }
100         };
101     }
102 
103     @Override
104     public void before(ExtensionContext context) throws Exception {
105         dataDir.create();
106         delegate.setUp(dataDir.getRoot().toFile());
107     }
108 
109     @Override
110     public void after(ExtensionContext context) throws Exception {
111         stopAsync().whenComplete((unused1, unused2) -> {
112             try {
113                 dataDir.delete();
114             } catch (IOException e) {
115                 throw new CompletionException(e);
116             }
117         });
118     }
119 
120     /**
121      * Returns the {@link Path} to the server's data directory.
122      *
123      * @throws IllegalStateException if the data directory is not created yet
124      */
125     @UnstableApi
126     public final Path dataDir() {
127         return dataDir.getRoot();
128     }
129 
130     /**
131      * Creates a new server, configures it with {@link #configure(CentralDogmaBuilder)} and starts the server.
132      * Note that you don't need to call this method if you did not stop the server with {@link #stop()},
133      * because the server is automatically started up by JUnit.
134      */
135     public final void start() {
136         startAsync().join();
137     }
138 
139     /**
140      * Creates a new server, configures it with {@link #configure(CentralDogmaBuilder)},
141      * and starts the server asynchronously.
142      */
143     public final CompletableFuture<Void> startAsync() {
144         // Create the root folder first if it doesn't exist.
145         if (!dataDir.exists()) {
146             try {
147                 dataDir.create();
148             } catch (IOException e) {
149                 return CompletableFutures.exceptionallyCompletedFuture(e);
150             }
151         }
152 
153         return delegate.startAsync(dataDir.getRoot().toFile());
154     }
155 
156     /**
157      * Stops the server and deletes the temporary files created by the server. Note that you don't usually need
158      * to call this method manually because the server is automatically stopped at the end by JUnit.
159      */
160     public final void stop() {
161         stopAsync().join();
162     }
163 
164     /**
165      * Stops the server and deletes the temporary files created by the server. Note that you don't usually need
166      * to call this method manually because the server is automatically stopped at the end by JUnit.
167      */
168     public final CompletableFuture<Void> stopAsync() {
169         return delegate.stopAsync();
170     }
171 
172     /**
173      * Returns whether the server is running over TLS or not.
174      */
175     public boolean useTls() {
176         return delegate.useTls();
177     }
178 
179     /**
180      * Returns the server.
181      *
182      * @throws IllegalStateException if Central Dogma did not start yet
183      */
184     public final com.linecorp.centraldogma.server.CentralDogma dogma() {
185         return delegate.dogma();
186     }
187 
188     /**
189      * Returns the {@link ProjectManager} of the server.
190      *
191      * @throws IllegalStateException if Central Dogma did not start yet
192      */
193     public ProjectManager projectManager() {
194         return delegate.projectManager();
195     }
196 
197     /**
198      * Returns the {@link MirroringService} of the server.
199      *
200      * @throws IllegalStateException if Central Dogma did not start yet
201      */
202     public final MirroringService mirroringService() {
203         return delegate.mirroringService();
204     }
205 
206     /**
207      * Returns the HTTP-based {@link CentralDogma} client.
208      *
209      * @throws IllegalStateException if Central Dogma did not start yet
210      */
211     public final CentralDogma client() {
212         return delegate.client();
213     }
214 
215     /**
216      * Returns the Thrift-based {@link CentralDogma} client.
217      *
218      * @throws IllegalStateException if Central Dogma did not start yet
219      */
220     public final CentralDogma legacyClient() {
221         return delegate.legacyClient();
222     }
223 
224     /**
225      * Returns the HTTP client.
226      *
227      * @throws IllegalStateException if Central Dogma did not start yet
228      */
229     public final WebClient httpClient() {
230         return delegate.httpClient();
231     }
232 
233     /**
234      * Returns the server address.
235      *
236      * @throws IllegalStateException if Central Dogma did not start yet
237      */
238     public final InetSocketAddress serverAddress() {
239         return delegate.serverAddress();
240     }
241 
242     /**
243      * Override this method to configure the server.
244      */
245     protected void configure(CentralDogmaBuilder builder) {}
246 
247     /**
248      * Override this method to configure the HTTP-based {@link CentralDogma} client builder.
249      */
250     protected void configureClient(ArmeriaCentralDogmaBuilder builder) {}
251 
252     /**
253      * Override this method to configure the Thrift-based {@link CentralDogma} client builder.
254      */
255     protected void configureClient(LegacyCentralDogmaBuilder builder) {}
256 
257     /**
258      * Override this method to configure the {@link WebClient} builder.
259      */
260     protected void configureHttpClient(WebClientBuilder builder) {}
261 
262     /**
263      * Override this method to perform the initial updates on the server,
264      * such as creating a repository and populating sample data.
265      */
266     protected void scaffold(CentralDogma client) {}
267 }