1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.photopicker.lint.test
18 
19 import com.android.photopicker.lint.LazyInjectionDetector
20 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
21 import com.android.tools.lint.checks.infrastructure.TestFile
22 import com.android.tools.lint.checks.infrastructure.TestLintTask
23 import com.android.tools.lint.detector.api.Detector
24 import com.android.tools.lint.detector.api.Issue
25 import org.junit.Test
26 import org.junit.runner.RunWith
27 import org.junit.runners.JUnit4
28 
29 @Suppress("UnstableApiUsage")
30 @RunWith(JUnit4::class)
31 class LazyInjectionDetectorTest : LintDetectorTest() {
getDetectornull32     override fun getDetector(): Detector = LazyInjectionDetector()
33 
34     override fun getIssues(): List<Issue> = listOf(LazyInjectionDetector.ISSUE)
35 
36     override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
37 
38     private val entryPointStub: TestFile =
39         java(
40             """
41                 package dagger.hilt;
42                 import static java.lang.annotation.RetentionPolicy.CLASS;
43                 import java.lang.annotation.ElementType;
44                 import java.lang.annotation.Retention;
45                 import java.lang.annotation.Target;
46 
47                 @Retention(CLASS) public @interface EntryPoint {}
48                 """
49         )
50 
51     private val inject: TestFile =
52         kotlin(
53                 """
54                 package javax.inject.Inject
55 
56                 @Retention(AnnotationRetention.RUNTIME) annotation class Inject
57                 """
58             )
59             .indented()
60 
61     private val configurationManager: TestFile =
62         kotlin(
63                 """
64             package com.android.photopicker.core.configuration
65 
66             class ConfigurationManager {}
67             """
68             )
69             .indented()
70 
71     private val dataService: TestFile =
72         kotlin(
73                 """
74             package com.android.photopicker.data.DataService
75 
76             class DataService {}
77             """
78             )
79             .indented()
80 
81     private val stubs = arrayOf(configurationManager, inject, dataService, entryPointStub)
82 
83     @Test
84     fun testInjectConfigurationManager() {
85         lint()
86             .files(
87                 kotlin(
88                         """
89                 package com.android.photopicker
90 
91                 import com.android.photopicker.core.configuration.ConfigurationManager
92                 import dagger.Lazy
93                 import javax.inject.Inject
94 
95                 class MainActivity {
96 
97                     @Inject lateinit var configurationManager: ConfigurationManager
98 
99                 }
100                 """
101                     )
102                     .indented(),
103                 *stubs
104             )
105             .run()
106             .expectClean()
107     }
108 
109     @Test
testInjectDataServiceLazilynull110     fun testInjectDataServiceLazily() {
111         lint()
112             .files(
113                 kotlin(
114                         """
115                 package com.android.photopicker
116 
117                 import com.android.photopicker.core.configuration.ConfigurationManager
118                 import com.android.photopicker.data.DataService
119                 import dagger.Lazy
120                 import javax.inject.Inject
121 
122                 class MainActivity {
123 
124                     @Inject lateinit var configurationManager: ConfigurationManager
125                     @Inject lateinit var dataService: Lazy<DataService>
126 
127                 }
128                 """
129                     )
130                     .indented(),
131                 *stubs
132             )
133             .run()
134             .expectClean()
135     }
136 
137     @Test
testInjectDataServiceNotLazynull138     fun testInjectDataServiceNotLazy() {
139         lint()
140             .files(
141                 kotlin(
142                         """
143                 package com.android.photopicker
144 
145                 import com.android.photopicker.core.configuration.ConfigurationManager
146                 import com.android.photopicker.data.DataService
147                 import dagger.Lazy
148                 import javax.inject.Inject
149 
150                 class MainActivity {
151 
152                     @Inject lateinit var configurationManager: ConfigurationManager
153                     @Inject lateinit var dataService: DataService
154 
155                 }
156                 """
157                     )
158                     .indented(),
159                 *stubs
160             )
161             .run()
162             .expectContains(LazyInjectionDetector.INVALID_INJECTION_FIELD_ERROR)
163     }
164 
165     @Test
testInjectDataServiceNotLazyNotEnforcedClassnull166     fun testInjectDataServiceNotLazyNotEnforcedClass() {
167         lint()
168             .files(
169                 kotlin(
170                         """
171                 package com.android.photopicker
172 
173                 import com.android.photopicker.core.configuration.ConfigurationManager
174                 import com.android.photopicker.data.DataService
175                 import dagger.Lazy
176                 import javax.inject.Inject
177 
178                 class SomeFeatureViewModel {
179 
180                     @Inject lateinit var configurationManager: ConfigurationManager
181                     @Inject lateinit var dataService: DataService
182 
183                 }
184                 """
185                     )
186                     .indented(),
187                 *stubs
188             )
189             .run()
190             .expectClean()
191     }
192 
193     @Test
testInjectDataServiceNotLazyEntryPointnull194     fun testInjectDataServiceNotLazyEntryPoint() {
195         lint()
196             .files(
197                 kotlin(
198                         """
199                 package com.android.photopicker.core.embedded
200 
201                 import com.android.photopicker.core.configuration.ConfigurationManager
202                 import com.android.photopicker.data.DataService
203                 import dagger.Lazy
204                 import dagger.hilt.EntryPoint
205                 import javax.inject.Inject
206 
207                 class Session {
208 
209                     @EntryPoint
210                     @InstallIn(EmbeddedServiceComponent::class)
211                     interface EmbeddedEntryPoint {
212                             fun configurationManager(): Lazy<ConfigurationManager>
213                             fun dataService(): DataService
214                     }
215 
216                 }
217                 """
218                     )
219                     .indented(),
220                 *stubs
221             )
222             .run()
223             .expectContains(LazyInjectionDetector.INVALID_INJECTION_FIELD_ERROR)
224     }
225 
226     @Test
testInjectDataServiceNotLazyNotEntryPointnull227     fun testInjectDataServiceNotLazyNotEntryPoint() {
228         lint()
229             .files(
230                 kotlin(
231                         """
232                 package com.android.photopicker.core.embedded
233 
234                 import com.android.photopicker.core.configuration.ConfigurationManager
235                 import com.android.photopicker.data.DataService
236                 import javax.inject.Inject
237 
238                 class Session {
239 
240                     @InstallIn(EmbeddedServiceComponent::class)
241                     interface EmbeddedEntryPoint {
242                             fun configurationManager(): ConfigurationManager
243                             fun dataService(): DataService
244                     }
245 
246                 }
247                 """
248                     )
249                     .indented(),
250                 *stubs
251             )
252             .run()
253             .expectClean()
254     }
255 
256     @Test
testInjectDataServiceLazyEntryPointnull257     fun testInjectDataServiceLazyEntryPoint() {
258         lint()
259             .files(
260                 kotlin(
261                         """
262                 package com.android.photopicker.core.embedded
263 
264                 import com.android.photopicker.core.configuration.ConfigurationManager
265                 import com.android.photopicker.data.DataService
266                 import dagger.Lazy
267                 import dagger.hilt.EntryPoint
268                 import javax.inject.Inject
269 
270                 class Session {
271 
272                     @EntryPoint
273                     @InstallIn(EmbeddedServiceComponent::class)
274                     interface EmbeddedEntryPoint {
275                             fun configurationManager(): ConfigurationManager
276                             fun dataService(): Lazy<DataService>
277                     }
278 
279                 }
280                 """
281                     )
282                     .indented(),
283                 *stubs
284             )
285             .run()
286             .expectClean()
287     }
288 }
289