@@ -1214,3 +1214,194 @@ Describe "Get-BuildAllProjects" {
12141214 Remove-Item $baseFolder - Force - Recurse
12151215 }
12161216}
1217+
1218+ Describe " ConvertTo-RepoRelativePath" {
1219+ BeforeAll {
1220+ Import-Module (Join-Path $PSScriptRoot " ../Actions/DetermineProjectsToBuild/DetermineProjectsToBuild.psm1" - Resolve) - DisableNameChecking - Force
1221+ }
1222+
1223+ BeforeEach {
1224+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute (' PSUseDeclaredVarsMoreThanAssignments' , ' baseFolder' , Justification = ' False positive.' )]
1225+ $baseFolder = (New-Item - ItemType Directory - Path (Join-Path $ ([System.IO.Path ]::GetTempPath()) $ ([System.IO.Path ]::GetRandomFileName()))).FullName
1226+ }
1227+
1228+ It ' resolves a standard .\ prefixed folder to a repo-relative path' {
1229+ New-Item - Path " $baseFolder /app" - ItemType Directory - Force | Out-Null
1230+ $result = ConvertTo-RepoRelativePath - folder ' .\app' - projectPath $baseFolder - baseFolder $baseFolder
1231+ $result | Should - Be ' app'
1232+ }
1233+
1234+ It ' resolves a ../ relative folder to a repo-relative path' {
1235+ # Simulate: baseFolder/src/Apps/MyApp/App exists, project is at baseFolder/build/projects/Proj
1236+ New-Item - Path " $baseFolder /src/Apps/MyApp/App" - ItemType Directory - Force | Out-Null
1237+ $projectPath = (New-Item - Path " $baseFolder /build/projects/Proj" - ItemType Directory - Force).FullName
1238+
1239+ $result = ConvertTo-RepoRelativePath - folder ' ..\..\..\src\Apps\MyApp\App' - projectPath $projectPath - baseFolder $baseFolder
1240+ $sep = [System.IO.Path ]::DirectorySeparatorChar
1241+ $result | Should - Be " src${sep} Apps${sep} MyApp${sep} App"
1242+ }
1243+
1244+ It ' returns $null for a folder that does not exist on disk' {
1245+ $result = ConvertTo-RepoRelativePath - folder ' .\nonexistent' - projectPath $baseFolder - baseFolder $baseFolder
1246+ $result | Should - BeNullOrEmpty
1247+ }
1248+
1249+ It ' returns $null for a folder that resolves outside the base folder' {
1250+ # Create a directory above baseFolder and try to reference it
1251+ $parentDir = Split-Path $baseFolder - Parent
1252+ $outsideDir = New-Item - Path (Join-Path $parentDir ' outside-repo' ) - ItemType Directory - Force
1253+ try {
1254+ $result = ConvertTo-RepoRelativePath - folder ' ..\outside-repo' - projectPath $baseFolder - baseFolder $baseFolder
1255+ $result | Should - BeNullOrEmpty
1256+ }
1257+ finally {
1258+ Remove-Item $outsideDir - Force - Recurse
1259+ }
1260+ }
1261+
1262+ It ' handles baseFolder with trailing separator' {
1263+ New-Item - Path " $baseFolder /app" - ItemType Directory - Force | Out-Null
1264+ $baseFolderWithTrailing = $baseFolder + [System.IO.Path ]::DirectorySeparatorChar
1265+ $result = ConvertTo-RepoRelativePath - folder ' .\app' - projectPath $baseFolder - baseFolder $baseFolderWithTrailing
1266+ $result | Should - Be ' app'
1267+ }
1268+
1269+ It ' handles deeply nested project with multiple ../ segments' {
1270+ New-Item - Path " $baseFolder /src/Common/Shared" - ItemType Directory - Force | Out-Null
1271+ $projectPath = (New-Item - Path " $baseFolder /a/b/c/d/project" - ItemType Directory - Force).FullName
1272+
1273+ $result = ConvertTo-RepoRelativePath - folder ' ..\..\..\..\..\src\Common\Shared' - projectPath $projectPath - baseFolder $baseFolder
1274+ $sep = [System.IO.Path ]::DirectorySeparatorChar
1275+ $result | Should - Be " src${sep} Common${sep} Shared"
1276+ }
1277+
1278+ AfterEach {
1279+ Remove-Item $baseFolder - Force - Recurse
1280+ }
1281+ }
1282+
1283+ Describe " Get-UnmodifiedAppsFromBaselineWorkflowRun" {
1284+ BeforeAll {
1285+ . (Join-Path - Path $PSScriptRoot - ChildPath " ../Actions/AL-Go-Helper.ps1" - Resolve)
1286+ DownloadAndImportBcContainerHelper - baseFolder $ ([System.IO.Path ]::GetTempPath())
1287+
1288+ Import-Module (Join-Path $PSScriptRoot " ../Actions/DetermineProjectsToBuild/DetermineProjectsToBuild.psm1" - Resolve) - DisableNameChecking - Force
1289+
1290+ # Helper to extract the download entries from captured Write-Host output for a given section
1291+ function script :Get-DownloadEntries {
1292+ param ([string ] $section , [System.Collections.ArrayList ] $output )
1293+ $inSection = $false
1294+ $entries = @ ()
1295+ foreach ($line in $output ) {
1296+ if ($line -eq " Download ${section} :" ) {
1297+ $inSection = $true
1298+ continue
1299+ }
1300+ if ($inSection ) {
1301+ if ($line -match ' ^Download ' ) { break }
1302+ $entries += $line
1303+ }
1304+ }
1305+ return $entries
1306+ }
1307+ }
1308+
1309+ BeforeEach {
1310+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute (' PSUseDeclaredVarsMoreThanAssignments' , ' baseFolder' , Justification = ' False positive.' )]
1311+ $baseFolder = (New-Item - ItemType Directory - Path (Join-Path $ ([System.IO.Path ]::GetTempPath()) $ ([System.IO.Path ]::GetRandomFileName()))).FullName
1312+
1313+ if (-not (Get-Command ' Trace-Information' - ErrorAction SilentlyContinue)) {
1314+ function global :Trace-Information {
1315+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute (' PSReviewUnusedParameter' , ' Message' , Justification = ' Stub function for testing.' )]
1316+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute (' PSReviewUnusedParameter' , ' AdditionalData' , Justification = ' Stub function for testing.' )]
1317+ param ([string ]$Message , $AdditionalData )
1318+ }
1319+ }
1320+ $env: GITHUB_API_URL = ' https://api.github.com'
1321+ $env: GITHUB_REPOSITORY = ' test/repo'
1322+
1323+ Mock InvokeWebRequest {
1324+ $uri = $args [0 ]
1325+ if (-not $uri ) { $uri = $Uri }
1326+ $content = if ($uri -like ' */actions/runs/*/artifacts*' ) {
1327+ ' {"artifacts":[]}'
1328+ } else {
1329+ ' {"head_branch":"main"}'
1330+ }
1331+ return [PSCustomObject ]@ { Content = $content }
1332+ } - ModuleName ' Github-Helper'
1333+
1334+ $script :capturedOutput = [System.Collections.ArrayList ]::new()
1335+ Mock Write-Host { $null = $script :capturedOutput.Add ($Object ) } - ModuleName ' DetermineProjectsToBuild'
1336+ }
1337+
1338+ It ' identifies unmodified apps when appFolders reference paths above the project via ../' {
1339+ $project = ' build/projects/MyProject'
1340+ $projectPath = Join-Path $baseFolder $project
1341+
1342+ $repoSettings = @ { fullBuildPatterns = @ (); projects = @ ($project ); powerPlatformSolutionFolder = ' ' ; useProjectDependencies = $false ; incrementalBuilds = @ { onPull_Request = $true ; mode = ' modifiedApps' } }
1343+ New-Item - Path " $baseFolder /.github/AL-Go-Settings.json" - Value (ConvertTo-Json $repoSettings - Depth 10 ) - type File - Force | Out-Null
1344+ New-Item - Path " $projectPath /.AL-Go/settings.json" - Value (ConvertTo-Json @ { appFolders = @ (' ../../../src/Apps/*/App' ); testFolders = @ (); bcptTestFolders = @ () } - Depth 10 ) - type File - Force | Out-Null
1345+
1346+ @ (' AppA' , ' AppB' , ' AppC' ) | ForEach-Object {
1347+ $app = @ { id = [guid ]::NewGuid().ToString(); name = $_ ; publisher = ' Test' ; version = ' 1.0.0.0' ; dependencies = @ () }
1348+ New-Item - Path " $baseFolder /src/Apps/$_ /App/app.json" - Value (ConvertTo-Json $app - Depth 10 ) - type File - Force | Out-Null
1349+ }
1350+
1351+ $env: Settings = ConvertTo-Json $repoSettings - Depth 99 - Compress
1352+
1353+ Push-Location $projectPath
1354+ $resolvedAppFolders = @ (Resolve-Path ' ../../../src/Apps/*/App' - Relative - ErrorAction SilentlyContinue | Where-Object { Test-Path (Join-Path $_ ' app.json' ) })
1355+ Pop-Location
1356+
1357+ $buildArtifactFolder = Join-Path $projectPath ' .buildartifacts'
1358+ New-Item - Path $buildArtifactFolder - ItemType Directory - Force | Out-Null
1359+
1360+ $sep = [System.IO.Path ]::DirectorySeparatorChar
1361+ Get-UnmodifiedAppsFromBaselineWorkflowRun `
1362+ - token ' fake-token' `
1363+ - settings @ { appFolders = $resolvedAppFolders ; testFolders = @ (); bcptTestFolders = @ () } `
1364+ - baseFolder $baseFolder - project $project - baselineWorkflowRunId ' 12345' `
1365+ - modifiedFiles @ (" src${sep} Apps${sep} AppA${sep} App${sep} MyCodeunit.al" ) `
1366+ - buildArtifactFolder $buildArtifactFolder - buildMode ' Default' - projectPath $projectPath
1367+
1368+ $entries = Get-DownloadEntries - section ' appFolders' - output $script :capturedOutput
1369+ $entries | Should -Not - Contain ' - None' - Because ' unmodified apps should be identified for download'
1370+ ($entries | Where-Object { $_ -like ' *AppB*' }) | Should -Not - BeNullOrEmpty - Because ' AppB is unmodified'
1371+ ($entries | Where-Object { $_ -like ' *AppC*' }) | Should -Not - BeNullOrEmpty - Because ' AppC is unmodified'
1372+ ($entries | Where-Object { $_ -like ' *AppA*' }) | Should - BeNullOrEmpty - Because ' AppA is modified'
1373+ }
1374+
1375+ It ' identifies unmodified apps in a standard layout with .\ prefixed folders' {
1376+ $repoSettings = @ { fullBuildPatterns = @ (); projects = @ (); powerPlatformSolutionFolder = ' ' ; useProjectDependencies = $false ; incrementalBuilds = @ { onPull_Request = $true ; mode = ' modifiedApps' } }
1377+ New-Item - Path " $baseFolder /.github/AL-Go-Settings.json" - Value (ConvertTo-Json $repoSettings - Depth 10 ) - type File - Force | Out-Null
1378+ New-Item - Path " $baseFolder /.AL-Go/settings.json" - Value (ConvertTo-Json @ {} - Depth 10 ) - type File - Force | Out-Null
1379+
1380+ @ (' app1' , ' app2' ) | ForEach-Object {
1381+ $app = @ { id = [guid ]::NewGuid().ToString(); name = $_ ; publisher = ' Test' ; version = ' 1.0.0.0' ; dependencies = @ () }
1382+ New-Item - Path " $baseFolder /$_ /app.json" - Value (ConvertTo-Json $app - Depth 10 ) - type File - Force | Out-Null
1383+ }
1384+
1385+ $env: Settings = ConvertTo-Json $repoSettings - Depth 99 - Compress
1386+
1387+ $buildArtifactFolder = Join-Path $baseFolder ' .buildartifacts'
1388+ New-Item - Path $buildArtifactFolder - ItemType Directory - Force | Out-Null
1389+
1390+ $sep = [System.IO.Path ]::DirectorySeparatorChar
1391+ Get-UnmodifiedAppsFromBaselineWorkflowRun `
1392+ - token ' fake-token' `
1393+ - settings @ { appFolders = @ (' .\app1' , ' .\app2' ); testFolders = @ (); bcptTestFolders = @ () } `
1394+ - baseFolder $baseFolder - project ' ' - baselineWorkflowRunId ' 12345' `
1395+ - modifiedFiles @ (" app1${sep} MyCodeunit.al" ) `
1396+ - buildArtifactFolder $buildArtifactFolder - buildMode ' Default' - projectPath $baseFolder
1397+
1398+ $entries = Get-DownloadEntries - section ' appFolders' - output $script :capturedOutput
1399+ $entries | Should -Not - Contain ' - None' - Because ' unmodified app2 should be identified for download'
1400+ ($entries | Where-Object { $_ -like ' *app2*' }) | Should -Not - BeNullOrEmpty - Because ' app2 is unmodified'
1401+ ($entries | Where-Object { $_ -like ' *app1*' }) | Should - BeNullOrEmpty - Because ' app1 is modified'
1402+ }
1403+
1404+ AfterEach {
1405+ Remove-Item $baseFolder - Force - Recurse
1406+ }
1407+ }
0 commit comments