Skip to content

Commit 1d864e1

Browse files
committed
Fix MCP module/entity creation race conditions in tests
1 parent c13a59d commit 1d864e1

File tree

2 files changed

+54
-21
lines changed

2 files changed

+54
-21
lines changed

src/main/kotlin/com/magento/idea/magento2plugin/mcp/MagentoEntityCrudCommands.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package com.magento.idea.magento2plugin.mcp
77

88
import com.intellij.openapi.application.ReadAction
9+
import com.intellij.openapi.project.DumbService
910
import com.intellij.openapi.project.Project
1011
import com.intellij.psi.PsiFile
1112
import com.jetbrains.php.lang.psi.PhpFile
@@ -37,7 +38,6 @@ import com.magento.idea.magento2plugin.magento.packages.PropertiesTypes
3738
import com.magento.idea.magento2plugin.magento.packages.uicomponent.FormElementType
3839
import com.magento.idea.magento2plugin.util.CamelCaseToSnakeCase
3940
import com.magento.idea.magento2plugin.util.GetFirstClassOfFile
40-
import com.magento.idea.magento2plugin.util.GetPhpClassByFQN
4141
import com.magento.idea.magento2plugin.util.RegExUtil
4242
import com.magento.idea.magento2plugin.util.php.PhpTypeMetadataParserUtil
4343
import java.util.Locale
@@ -67,6 +67,10 @@ internal object MagentoEntityCrudCommands {
6767
createDataInterface: Boolean,
6868
createWebApi: Boolean
6969
): String {
70+
if (DumbService.getInstance(project).isDumb) {
71+
DumbService.getInstance(project).waitForSmartMode()
72+
}
73+
7074
val request = try {
7175
MagentoMcpCreateSupport.runReadAction {
7276
resolveRequest(
@@ -263,7 +267,8 @@ internal object MagentoEntityCrudCommands {
263267
createWebApi = createWebApi
264268
)
265269
val duplicatePhpFile = expectedPhpFiles.firstOrNull { file ->
266-
GetPhpClassByFQN.getInstance(project).execute(file.classFqn) != null
270+
val relativePath = MagentoMcpCreateSupport.phpFileRelativePath(file)
271+
moduleContext.moduleDirectory.virtualFile.findFileByRelativePath(relativePath) != null
267272
}
268273
if (duplicatePhpFile != null) {
269274
throw EntityCrudValidationException("PHP class \"${duplicatePhpFile.classFqn}\" already exists.")

src/main/kotlin/com/magento/idea/magento2plugin/mcp/MagentoModuleCommands.kt

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.magento.idea.magento2plugin.magento.packages.Package
2727
import com.magento.idea.magento2plugin.project.Settings
2828
import com.magento.idea.magento2plugin.util.CamelCaseToHyphen
2929
import java.nio.file.Paths
30+
import java.nio.file.InvalidPathException
3031
import java.util.concurrent.atomic.AtomicReference
3132

3233
internal object MagentoModuleCommands {
@@ -73,9 +74,6 @@ internal object MagentoModuleCommands {
7374
?: return "Configured Magento root path \"$configuredRoot\" could not be resolved."
7475

7576
val moduleFullName = "${normalizedPackage}_${normalizedModule}"
76-
if (ModuleIndex(project).getModuleDirectoryByModuleName(moduleFullName) != null) {
77-
return "Magento module \"$moduleFullName\" already exists."
78-
}
7977

8078
val existingModuleDirectory = ReadAction.compute<PsiDirectory?, RuntimeException> {
8179
magentoRootDirectory.findSubdirectory("app")
@@ -268,18 +266,52 @@ internal object MagentoModuleCommands {
268266
}
269267

270268
private fun refreshCreatedModuleTree(vararg directories: PsiDirectory) {
271-
val virtualFiles = directories.map { it.virtualFile }.toTypedArray()
269+
val virtualFiles = directories.map { it.virtualFile }
270+
.filter { it.isValid }
271+
.toTypedArray()
272272
VfsUtil.markDirtyAndRefresh(false, true, true, *virtualFiles)
273+
virtualFiles.forEach { it.refresh(false, true) }
273274
}
274275

275276
private fun resolveMagentoRootDirectory(project: Project, configuredRoot: String): PsiDirectory? {
276277
return ReadAction.compute<PsiDirectory?, RuntimeException> {
277278
val fileSystem = LocalFileSystem.getInstance()
278-
val candidates = linkedSetOf(configuredRoot)
279+
val normalizedConfiguredRoot = FileUtil.toSystemIndependentName(configuredRoot.trim())
280+
281+
if (isAbsolutePath(normalizedConfiguredRoot)) {
282+
val absoluteVirtualFile = fileSystem.refreshAndFindFileByPath(normalizedConfiguredRoot)
283+
?: fileSystem.findFileByPath(normalizedConfiguredRoot)
284+
if (absoluteVirtualFile != null && absoluteVirtualFile.isDirectory) {
285+
return@compute PsiManager.getInstance(project).findDirectory(absoluteVirtualFile)
286+
}
287+
}
288+
289+
val moduleIndex = ModuleIndex(project)
290+
val configuredRootTrimmed = normalizedConfiguredRoot.trim('/').replace('\\', '/')
291+
val configuredPrefix = normalizedConfiguredRoot.trimEnd('/') + "/" + Package.packagesRoot + "/"
292+
for (moduleName in moduleIndex.moduleNames) {
293+
val moduleDirectory = moduleIndex.getModuleDirectoryByModuleName(moduleName) ?: continue
294+
val modulePath = moduleDirectory.virtualFile.path.replace('\\', '/')
295+
296+
val resolvedRoot = moduleDirectory.parentDirectory(levels = 4) ?: continue
297+
val resolvedRootPath = resolvedRoot.virtualFile.path.replace('\\', '/')
298+
if (modulePath.startsWith(configuredPrefix)) {
299+
return@compute resolvedRoot
300+
}
301+
302+
if (!isAbsolutePath(normalizedConfiguredRoot) &&
303+
configuredRootTrimmed.isNotEmpty() &&
304+
resolvedRootPath.endsWith("/$configuredRootTrimmed")
305+
) {
306+
return@compute resolvedRoot
307+
}
308+
}
309+
310+
val candidates = linkedSetOf(normalizedConfiguredRoot)
279311
val basePath = project.basePath
280312
if (basePath != null) {
281-
candidates += Paths.get(basePath, configuredRoot.removePrefix("/")).normalize().toString()
282-
candidates += Paths.get(basePath, configuredRoot).normalize().toString()
313+
candidates += Paths.get(basePath, normalizedConfiguredRoot.removePrefix("/")).normalize().toString()
314+
candidates += Paths.get(basePath, normalizedConfiguredRoot).normalize().toString()
283315
}
284316

285317
val virtualFile = candidates.asSequence()
@@ -290,22 +322,18 @@ internal object MagentoModuleCommands {
290322
return@compute PsiManager.getInstance(project).findDirectory(virtualFile)
291323
}
292324

293-
val configuredPrefix = configuredRoot.trimEnd('/') + "/" + Package.packagesRoot + "/"
294-
val moduleIndex = ModuleIndex(project)
295-
for (moduleName in moduleIndex.moduleNames) {
296-
val moduleDirectory = moduleIndex.getModuleDirectoryByModuleName(moduleName) ?: continue
297-
val modulePath = moduleDirectory.virtualFile.path.replace('\\', '/')
298-
if (!modulePath.startsWith(configuredPrefix)) {
299-
continue
300-
}
301-
302-
return@compute moduleDirectory.parentDirectory(levels = 4)
303-
}
304-
305325
null
306326
}
307327
}
308328

329+
private fun isAbsolutePath(path: String): Boolean {
330+
return try {
331+
Paths.get(path).isAbsolute
332+
} catch (_: InvalidPathException) {
333+
false
334+
}
335+
}
336+
309337
private fun PsiDirectory.parentDirectory(levels: Int): PsiDirectory? {
310338
var current: PsiDirectory? = this
311339
repeat(levels) {

0 commit comments

Comments
 (0)