1 /*
2  * Copyright (C) 2023 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 
18 package com.android.internal.systemui.lint
19 
20 import com.android.tools.lint.checks.infrastructure.TestFiles
21 import com.android.tools.lint.checks.infrastructure.TestMode
22 import com.android.tools.lint.detector.api.Detector
23 import com.android.tools.lint.detector.api.Issue
24 import org.junit.Ignore
25 import org.junit.Test
26 
27 @Suppress("UnstableApiUsage")
28 @Ignore("b/254533331")
29 class CleanArchitectureDependencyViolationDetectorTest : SystemUILintDetectorTest() {
30     override fun getDetector(): Detector {
31         return CleanArchitectureDependencyViolationDetector()
32     }
33 
34     override fun getIssues(): List<Issue> {
35         return listOf(
36             CleanArchitectureDependencyViolationDetector.ISSUE,
37         )
38     }
39 
40     @Test
41     fun noViolations() {
42         lint()
43             .files(
44                 *LEGITIMATE_FILES,
45             )
46             .issues(
47                 CleanArchitectureDependencyViolationDetector.ISSUE,
48             )
49             .run()
50             .expectWarningCount(0)
51     }
52 
53     @Test
54     fun violation_domainDependsOnUi() {
55         lint()
56             .files(
57                 *LEGITIMATE_FILES,
58                 TestFiles.kotlin(
59                     """
60                         package test.domain.interactor
61 
62                         import test.ui.viewmodel.ViewModel
63 
64                         class BadClass(
65                             private val viewModel: ViewModel,
66                         )
67                     """
68                         .trimIndent()
69                 )
70             )
71             .issues(
72                 CleanArchitectureDependencyViolationDetector.ISSUE,
73             )
74             .testModes(TestMode.DEFAULT)
75             .run()
76             .expectWarningCount(1)
77             .expect(
78                 expectedText =
79                     """
80                     src/test/domain/interactor/BadClass.kt:3: Warning: The domain layer may not depend on the ui layer. [CleanArchitectureDependencyViolation]
81                     import test.ui.viewmodel.ViewModel
82                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
83                     0 errors, 1 warnings
84                 """,
85             )
86     }
87 
88     @Test
89     fun violation_uiDependsOnData() {
90         lint()
91             .files(
92                 *LEGITIMATE_FILES,
93                 TestFiles.kotlin(
94                     """
95                         package test.ui.viewmodel
96 
97                         import test.data.repository.Repository
98 
99                         class BadClass(
100                             private val repository: Repository,
101                         )
102                     """
103                         .trimIndent()
104                 )
105             )
106             .issues(
107                 CleanArchitectureDependencyViolationDetector.ISSUE,
108             )
109             .testModes(TestMode.DEFAULT)
110             .run()
111             .expectWarningCount(1)
112             .expect(
113                 expectedText =
114                     """
115                     src/test/ui/viewmodel/BadClass.kt:3: Warning: The ui layer may not depend on the data layer. [CleanArchitectureDependencyViolation]
116                     import test.data.repository.Repository
117                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
118                     0 errors, 1 warnings
119                 """,
120             )
121     }
122 
123     @Test
124     fun violation_sharedDependsOnAllOtherLayers() {
125         lint()
126             .files(
127                 *LEGITIMATE_FILES,
128                 TestFiles.kotlin(
129                     """
130                         package test.shared.model
131 
132                         import test.data.repository.Repository
133                         import test.domain.interactor.Interactor
134                         import test.ui.viewmodel.ViewModel
135 
136                         class BadClass(
137                             private val repository: Repository,
138                             private val interactor: Interactor,
139                             private val viewmodel: ViewModel,
140                         )
141                     """
142                         .trimIndent()
143                 )
144             )
145             .issues(
146                 CleanArchitectureDependencyViolationDetector.ISSUE,
147             )
148             .testModes(TestMode.DEFAULT)
149             .run()
150             .expectWarningCount(3)
151             .expect(
152                 expectedText =
153                     """
154                     src/test/shared/model/BadClass.kt:3: Warning: The shared layer may not depend on the data layer. [CleanArchitectureDependencyViolation]
155                     import test.data.repository.Repository
156                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
157                     src/test/shared/model/BadClass.kt:4: Warning: The shared layer may not depend on the domain layer. [CleanArchitectureDependencyViolation]
158                     import test.domain.interactor.Interactor
159                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
160                     src/test/shared/model/BadClass.kt:5: Warning: The shared layer may not depend on the ui layer. [CleanArchitectureDependencyViolation]
161                     import test.ui.viewmodel.ViewModel
162                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
163                     0 errors, 3 warnings
164                 """,
165             )
166     }
167 
168     @Test
169     fun violation_dataDependsOnDomain() {
170         lint()
171             .files(
172                 *LEGITIMATE_FILES,
173                 TestFiles.kotlin(
174                     """
175                         package test.data.repository
176 
177                         import test.domain.interactor.Interactor
178 
179                         class BadClass(
180                             private val interactor: Interactor,
181                         )
182                     """
183                         .trimIndent()
184                 )
185             )
186             .issues(
187                 CleanArchitectureDependencyViolationDetector.ISSUE,
188             )
189             .testModes(TestMode.DEFAULT)
190             .run()
191             .expectWarningCount(1)
192             .expect(
193                 expectedText =
194                     """
195                     src/test/data/repository/BadClass.kt:3: Warning: The data layer may not depend on the domain layer. [CleanArchitectureDependencyViolation]
196                     import test.domain.interactor.Interactor
197                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
198                     0 errors, 1 warnings
199                 """,
200             )
201     }
202 
203     companion object {
204         private val MODEL_FILE =
205             TestFiles.kotlin(
206                 """
207                     package test.shared.model
208 
209                     import test.some.other.thing.SomeOtherThing
210 
211                     data class Model(
212                         private val name: String,
213                     )
214                 """
215                     .trimIndent()
216             )
217         private val REPOSITORY_FILE =
218             TestFiles.kotlin(
219                 """
220                     package test.data.repository
221 
222                     import test.shared.model.Model
223                     import test.some.other.thing.SomeOtherThing
224 
225                     class Repository {
226                         private val models = listOf(
227                             Model("one"),
228                             Model("two"),
229                             Model("three"),
230                         )
231 
232                         fun getModels(): List<Model> {
233                             return models
234                         }
235                     }
236                 """
237                     .trimIndent()
238             )
239         private val INTERACTOR_FILE =
240             TestFiles.kotlin(
241                 """
242                     package test.domain.interactor
243 
244                     import test.data.repository.Repository
245                     import test.shared.model.Model
246 
247                     class Interactor(
248                         private val repository: Repository,
249                     ) {
250                         fun getModels(): List<Model> {
251                             return repository.getModels()
252                         }
253                     }
254                 """
255                     .trimIndent()
256             )
257         private val VIEW_MODEL_FILE =
258             TestFiles.kotlin(
259                 """
260                     package test.ui.viewmodel
261 
262                     import test.domain.interactor.Interactor
263                     import test.some.other.thing.SomeOtherThing
264 
265                     class ViewModel(
266                         private val interactor: Interactor,
267                     ) {
268                         fun getNames(): List<String> {
269                             return interactor.getModels().map { model -> model.name }
270                         }
271                     }
272                 """
273                     .trimIndent()
274             )
275         private val NON_CLEAN_ARCHITECTURE_FILE =
276             TestFiles.kotlin(
277                 """
278                     package test.some.other.thing
279 
280                     import test.data.repository.Repository
281                     import test.domain.interactor.Interactor
282                     import test.ui.viewmodel.ViewModel
283 
284                     class SomeOtherThing {
285                         init {
286                             val viewModel = ViewModel(
287                                 interactor = Interactor(
288                                     repository = Repository(),
289                                 ),
290                             )
291                         }
292                     }
293                 """
294                     .trimIndent()
295             )
296         private val LEGITIMATE_FILES =
297             arrayOf(
298                 MODEL_FILE,
299                 REPOSITORY_FILE,
300                 INTERACTOR_FILE,
301                 VIEW_MODEL_FILE,
302                 NON_CLEAN_ARCHITECTURE_FILE,
303             )
304     }
305 }
306