From cb89d6abf58b0b554fd9cfe0f62605a28b0d6e8e Mon Sep 17 00:00:00 2001 From: Johanna Lamppu Date: Fri, 24 Jan 2025 17:13:10 +0200 Subject: [PATCH] feat: Enhance packages endpoint with shortest dependency path data Add information about the shortest dependency path data to the response of the packages endpoint. This will later be added to be shown in the UI in order to get a sense of the importance to address issues found in the packages. Signed-off-by: Johanna Lamppu --- .../mapping/src/commonMain/kotlin/Mappings.kt | 27 +++++ api/v1/model/src/commonMain/kotlin/Package.kt | 3 +- .../kotlin/ShortestDependencyPath.kt | 35 ++++++ core/src/main/kotlin/api/RunsRoute.kt | 8 +- core/src/main/kotlin/apiDocs/RunsDocs.kt | 9 ++ .../kotlin/api/RunsRouteIntegrationTest.kt | 55 ++++++--- dao/src/testFixtures/kotlin/Fixtures.kt | 26 ++++- .../runs/PackageWithShortestDependencyPath.kt | 32 ++++++ .../src/main/kotlin/PackageService.kt | 21 +++- .../src/test/kotlin/PackageServiceTest.kt | 104 +++++++++++------- 10 files changed, 257 insertions(+), 63 deletions(-) create mode 100644 api/v1/model/src/commonMain/kotlin/ShortestDependencyPath.kt create mode 100644 model/src/commonMain/kotlin/runs/PackageWithShortestDependencyPath.kt diff --git a/api/v1/mapping/src/commonMain/kotlin/Mappings.kt b/api/v1/mapping/src/commonMain/kotlin/Mappings.kt index 853162f552..9df21681dc 100644 --- a/api/v1/mapping/src/commonMain/kotlin/Mappings.kt +++ b/api/v1/mapping/src/commonMain/kotlin/Mappings.kt @@ -75,6 +75,7 @@ import org.eclipse.apoapsis.ortserver.api.v1.model.ScannerJob as ApiScannerJob import org.eclipse.apoapsis.ortserver.api.v1.model.ScannerJobConfiguration as ApiScannerJobConfiguration import org.eclipse.apoapsis.ortserver.api.v1.model.Secret as ApiSecret import org.eclipse.apoapsis.ortserver.api.v1.model.Severity as ApiSeverity +import org.eclipse.apoapsis.ortserver.api.v1.model.ShortestDependencyPath as ApiShortestDependencyPath import org.eclipse.apoapsis.ortserver.api.v1.model.SortDirection as ApiSortDirection import org.eclipse.apoapsis.ortserver.api.v1.model.SortProperty as ApiSortProperty import org.eclipse.apoapsis.ortserver.api.v1.model.SourceCodeOrigin as ApiSourceCodeOrigin @@ -133,8 +134,10 @@ import org.eclipse.apoapsis.ortserver.model.runs.Issue import org.eclipse.apoapsis.ortserver.model.runs.OrtRuleViolation import org.eclipse.apoapsis.ortserver.model.runs.Package import org.eclipse.apoapsis.ortserver.model.runs.PackageManagerConfiguration +import org.eclipse.apoapsis.ortserver.model.runs.PackageWithShortestDependencyPath import org.eclipse.apoapsis.ortserver.model.runs.ProcessedDeclaredLicense import org.eclipse.apoapsis.ortserver.model.runs.RemoteArtifact +import org.eclipse.apoapsis.ortserver.model.runs.ShortestDependencyPath import org.eclipse.apoapsis.ortserver.model.runs.VcsInfo import org.eclipse.apoapsis.ortserver.model.runs.advisor.Vulnerability import org.eclipse.apoapsis.ortserver.model.runs.advisor.VulnerabilityReference @@ -791,3 +794,27 @@ fun ApiSubmoduleFetchStrategy.mapToModel() = when (this) { ApiSubmoduleFetchStrategy.TOP_LEVEL_ONLY -> SubmoduleFetchStrategy.TOP_LEVEL_ONLY ApiSubmoduleFetchStrategy.FULLY_RECURSIVE -> SubmoduleFetchStrategy.FULLY_RECURSIVE } + +fun ShortestDependencyPath.mapToApi() = ApiShortestDependencyPath( + scope = scope, + projectIdentifier = project.identifier.mapToApi(), + path = path.map { it.mapToApi() } +) + +fun PackageWithShortestDependencyPath.mapToApi() = ApiPackage( + pkg.identifier.mapToApi(), + pkg.purl, + pkg.cpe, + pkg.authors, + pkg.declaredLicenses, + pkg.processedDeclaredLicense.mapToApi(), + pkg.description, + pkg.homepageUrl, + pkg.binaryArtifact.mapToApi(), + pkg.sourceArtifact.mapToApi(), + pkg.vcs.mapToApi(), + pkg.vcsProcessed.mapToApi(), + pkg.isMetadataOnly, + pkg.isModified, + shortestDependencyPath?.mapToApi() +) diff --git a/api/v1/model/src/commonMain/kotlin/Package.kt b/api/v1/model/src/commonMain/kotlin/Package.kt index 80d6119c35..ddef22eb26 100644 --- a/api/v1/model/src/commonMain/kotlin/Package.kt +++ b/api/v1/model/src/commonMain/kotlin/Package.kt @@ -36,5 +36,6 @@ data class Package( val vcs: VcsInfo, val vcsProcessed: VcsInfo, val isMetadataOnly: Boolean = false, - val isModified: Boolean = false + val isModified: Boolean = false, + val shortestDependencyPath: ShortestDependencyPath? = null ) diff --git a/api/v1/model/src/commonMain/kotlin/ShortestDependencyPath.kt b/api/v1/model/src/commonMain/kotlin/ShortestDependencyPath.kt new file mode 100644 index 0000000000..9bfd0e481b --- /dev/null +++ b/api/v1/model/src/commonMain/kotlin/ShortestDependencyPath.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2025 The ORT Server Authors (See ) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ + +package org.eclipse.apoapsis.ortserver.api.v1.model + +import kotlinx.serialization.Serializable + +/** The shortest dependency path for a Package. */ +@Serializable +data class ShortestDependencyPath( + /** The identifier of the root project of this path. */ + val projectIdentifier: Identifier, + + /** The scope in which this shortest path of the dependency is found in. */ + val scope: String, + + /** Path of dependency identifiers to the dependency. */ + val path: List +) diff --git a/core/src/main/kotlin/api/RunsRoute.kt b/core/src/main/kotlin/api/RunsRoute.kt index d6808e3b29..27e535359a 100644 --- a/core/src/main/kotlin/api/RunsRoute.kt +++ b/core/src/main/kotlin/api/RunsRoute.kt @@ -26,7 +26,6 @@ import io.ktor.http.ContentDisposition import io.ktor.http.HttpHeaders import io.ktor.http.HttpStatusCode import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.call import io.ktor.server.response.header import io.ktor.server.response.respond import io.ktor.server.response.respondFile @@ -73,7 +72,7 @@ import org.eclipse.apoapsis.ortserver.model.authorization.RepositoryPermission import org.eclipse.apoapsis.ortserver.model.repositories.OrtRunRepository import org.eclipse.apoapsis.ortserver.model.runs.Issue import org.eclipse.apoapsis.ortserver.model.runs.OrtRuleViolation -import org.eclipse.apoapsis.ortserver.model.runs.Package +import org.eclipse.apoapsis.ortserver.model.runs.PackageWithShortestDependencyPath import org.eclipse.apoapsis.ortserver.services.IssueService import org.eclipse.apoapsis.ortserver.services.OrtRunService import org.eclipse.apoapsis.ortserver.services.PackageService @@ -233,9 +232,10 @@ fun Route.runs() = route("runs") { val pagingOptions = call.pagingOptions(SortProperty("purl", SortDirection.ASCENDING)) - val packagesForOrtRun = packageService.listForOrtRunId(ortRun.id, pagingOptions.mapToModel()) + val packagesForOrtRun = packageService + .listForOrtRunId(ortRun.id, pagingOptions.mapToModel()) - val pagedResponse = packagesForOrtRun.mapToApi(Package::mapToApi) + val pagedResponse = packagesForOrtRun.mapToApi(PackageWithShortestDependencyPath::mapToApi) call.respond(HttpStatusCode.OK, pagedResponse) } diff --git a/core/src/main/kotlin/apiDocs/RunsDocs.kt b/core/src/main/kotlin/apiDocs/RunsDocs.kt index bdb805475d..960e991f52 100644 --- a/core/src/main/kotlin/apiDocs/RunsDocs.kt +++ b/core/src/main/kotlin/apiDocs/RunsDocs.kt @@ -47,6 +47,7 @@ import org.eclipse.apoapsis.ortserver.api.v1.model.RemoteArtifact import org.eclipse.apoapsis.ortserver.api.v1.model.RepositoryType import org.eclipse.apoapsis.ortserver.api.v1.model.RuleViolation import org.eclipse.apoapsis.ortserver.api.v1.model.Severity +import org.eclipse.apoapsis.ortserver.api.v1.model.ShortestDependencyPath import org.eclipse.apoapsis.ortserver.api.v1.model.SortDirection import org.eclipse.apoapsis.ortserver.api.v1.model.SortProperty import org.eclipse.apoapsis.ortserver.api.v1.model.VcsInfo @@ -396,6 +397,14 @@ val getPackagesByRunId: OpenApiRoute.() -> Unit = { vcsProcessed = VcsInfo(RepositoryType.GIT.name, "url", "revision", "path"), isMetadataOnly = false, isModified = false, + shortestDependencyPath = ShortestDependencyPath( + scope = "productionRuntimeClasspath", + projectIdentifier = Identifier("Gradle", "", "project-name", "1.0"), + path = listOf( + Identifier("Maven", "org.namespace", "some", "1.0"), + Identifier("Maven", "org.namespace", "other", "1.0") + ) + ) ) ), PagingData( diff --git a/core/src/test/kotlin/api/RunsRouteIntegrationTest.kt b/core/src/test/kotlin/api/RunsRouteIntegrationTest.kt index 26d2d04790..ef3c0e5ef2 100644 --- a/core/src/test/kotlin/api/RunsRouteIntegrationTest.kt +++ b/core/src/test/kotlin/api/RunsRouteIntegrationTest.kt @@ -112,6 +112,7 @@ import org.eclipse.apoapsis.ortserver.model.runs.OrtRuleViolation import org.eclipse.apoapsis.ortserver.model.runs.Package import org.eclipse.apoapsis.ortserver.model.runs.ProcessedDeclaredLicense import org.eclipse.apoapsis.ortserver.model.runs.RemoteArtifact +import org.eclipse.apoapsis.ortserver.model.runs.ShortestDependencyPath import org.eclipse.apoapsis.ortserver.model.runs.VcsInfo import org.eclipse.apoapsis.ortserver.model.runs.advisor.AdvisorConfiguration import org.eclipse.apoapsis.ortserver.model.runs.advisor.AdvisorResult @@ -890,6 +891,10 @@ class RunsRouteIntegrationTest : AbstractIntegrationTest({ configuration = AnalyzerJobConfiguration() ) + val project = dbExtension.fixtures.getProject() + val identifier1 = Identifier("Maven", "com.example", "example", "1.0") + val identifier2 = Identifier("Maven", "com.example", "example2", "1.0") + dbExtension.fixtures.analyzerRunRepository.create( analyzerJobId = analyzerJob.id, startTime = Clock.System.now().toDatabasePrecision(), @@ -910,10 +915,10 @@ class RunsRouteIntegrationTest : AbstractIntegrationTest({ packageManagers = emptyMap(), skipExcluded = true ), - projects = emptySet(), + projects = setOf(project), packages = setOf( Package( - Identifier("Maven", "com.example", "example", "1.0"), + identifier1, purl = "pkg:maven/com.example/example@1.0", cpe = null, authors = setOf("Author One", "Author Two"), @@ -954,12 +959,7 @@ class RunsRouteIntegrationTest : AbstractIntegrationTest({ isModified = false ), Package( - Identifier( - type = "Maven", - namespace = "com.example", - name = "example2", - version = "1.0" - ), + identifier2, purl = "pkg:maven/com.example/example2@1.0", cpe = null, authors = emptySet(), @@ -998,7 +998,19 @@ class RunsRouteIntegrationTest : AbstractIntegrationTest({ ) ), issues = emptyList(), - dependencyGraphs = emptyMap() + dependencyGraphs = emptyMap(), + shortestDependencyPaths = mapOf( + identifier1 to ShortestDependencyPath( + project, + "compileClassPath", + emptyList() + ), + identifier2 to ShortestDependencyPath( + project, + "compileClassPath", + listOf(identifier1) + ) + ) ) val response = superuserClient.get("/api/v1/runs/${ortRun.id}/packages") @@ -1008,11 +1020,26 @@ class RunsRouteIntegrationTest : AbstractIntegrationTest({ with(packages.data) { shouldHaveSize(2) - first().identifier.name shouldBe "example" - first().authors shouldHaveSize 2 - first().declaredLicenses shouldHaveSize 3 - first().processedDeclaredLicense.mappedLicenses shouldHaveSize 2 - first().processedDeclaredLicense.unmappedLicenses shouldHaveSize 4 + + with(first()) { + identifier.name shouldBe "example" + authors shouldHaveSize 2 + declaredLicenses shouldHaveSize 3 + processedDeclaredLicense.mappedLicenses shouldHaveSize 2 + processedDeclaredLicense.unmappedLicenses shouldHaveSize 4 + + shortestDependencyPath shouldNotBeNull { + projectIdentifier shouldBe project.identifier.mapToApi() + scope shouldBe "compileClassPath" + path shouldBe emptyList() + } + } + + last().shortestDependencyPath shouldNotBeNull { + projectIdentifier shouldBe project.identifier.mapToApi() + scope shouldBe "compileClassPath" + path shouldBe listOf(identifier1.mapToApi()) + } last().identifier.name shouldBe "example2" } } diff --git a/dao/src/testFixtures/kotlin/Fixtures.kt b/dao/src/testFixtures/kotlin/Fixtures.kt index 238436d047..6f93c92abf 100644 --- a/dao/src/testFixtures/kotlin/Fixtures.kt +++ b/dao/src/testFixtures/kotlin/Fixtures.kt @@ -63,7 +63,10 @@ import org.eclipse.apoapsis.ortserver.model.runs.Identifier import org.eclipse.apoapsis.ortserver.model.runs.Issue import org.eclipse.apoapsis.ortserver.model.runs.OrtRuleViolation import org.eclipse.apoapsis.ortserver.model.runs.Package +import org.eclipse.apoapsis.ortserver.model.runs.ProcessedDeclaredLicense import org.eclipse.apoapsis.ortserver.model.runs.Project +import org.eclipse.apoapsis.ortserver.model.runs.ShortestDependencyPath +import org.eclipse.apoapsis.ortserver.model.runs.VcsInfo import org.eclipse.apoapsis.ortserver.model.runs.advisor.AdvisorConfiguration import org.eclipse.apoapsis.ortserver.model.runs.advisor.AdvisorResult @@ -221,12 +224,30 @@ class Fixtures(private val db: Database) { howToFix = "how to fix" ) + fun getProject() = Project( + identifier = Identifier("Gradle", "", "project", "1.0"), + definitionFilePath = "build.gradle.kts", + authors = emptySet(), + declaredLicenses = emptySet(), + processedDeclaredLicense = ProcessedDeclaredLicense( + null, + emptyMap(), + emptySet() + ), + vcs = VcsInfo(RepositoryType.GIT, "https://example.com/git", "revision", ""), + vcsProcessed = VcsInfo(RepositoryType.GIT, "https://example.com/git", "revision", ""), + description = "", + homepageUrl = "https://example.com", + scopeNames = setOf("compileClasspath", "runtimeClasspath") + ) + fun createAnalyzerRun( analyzerJobId: Long = analyzerJob.id, projects: Set = emptySet(), packages: Set = emptySet(), issues: List = emptyList(), - dependencyGraphs: Map = emptyMap() + dependencyGraphs: Map = emptyMap(), + shortestDependencyPaths: Map = emptyMap() ) = analyzerRunRepository.create( analyzerJobId = analyzerJobId, startTime = Clock.System.now(), @@ -250,7 +271,8 @@ class Fixtures(private val db: Database) { projects = projects, packages = packages, issues = issues, - dependencyGraphs = dependencyGraphs + dependencyGraphs = dependencyGraphs, + shortestDependencyPaths = shortestDependencyPaths ) fun createAdvisorRun( diff --git a/model/src/commonMain/kotlin/runs/PackageWithShortestDependencyPath.kt b/model/src/commonMain/kotlin/runs/PackageWithShortestDependencyPath.kt new file mode 100644 index 0000000000..a232dc3cfa --- /dev/null +++ b/model/src/commonMain/kotlin/runs/PackageWithShortestDependencyPath.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2025 The ORT Server Authors (See ) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ + +package org.eclipse.apoapsis.ortserver.model.runs + +/** + * A data class representing a package, and the shortest dependency path that the package is found in (relative to a + * project found in a run). + */ +data class PackageWithShortestDependencyPath( + /** A package. */ + val pkg: Package, + + /** The shortest dependency path for the package. */ + val shortestDependencyPath: ShortestDependencyPath? = null +) diff --git a/services/hierarchy/src/main/kotlin/PackageService.kt b/services/hierarchy/src/main/kotlin/PackageService.kt index 3fe002a3b4..cb839d0c6b 100644 --- a/services/hierarchy/src/main/kotlin/PackageService.kt +++ b/services/hierarchy/src/main/kotlin/PackageService.kt @@ -25,10 +25,12 @@ import org.eclipse.apoapsis.ortserver.dao.repositories.analyzerrun.AnalyzerRunsT import org.eclipse.apoapsis.ortserver.dao.repositories.analyzerrun.PackageDao import org.eclipse.apoapsis.ortserver.dao.repositories.analyzerrun.PackagesAnalyzerRunsTable import org.eclipse.apoapsis.ortserver.dao.repositories.analyzerrun.PackagesTable +import org.eclipse.apoapsis.ortserver.dao.repositories.analyzerrun.ShortestDependencyPathDao +import org.eclipse.apoapsis.ortserver.dao.repositories.analyzerrun.ShortestDependencyPathsTable import org.eclipse.apoapsis.ortserver.dao.tables.shared.IdentifiersTable import org.eclipse.apoapsis.ortserver.dao.utils.listCustomQuery import org.eclipse.apoapsis.ortserver.model.EcosystemStats -import org.eclipse.apoapsis.ortserver.model.runs.Package +import org.eclipse.apoapsis.ortserver.model.runs.PackageWithShortestDependencyPath import org.eclipse.apoapsis.ortserver.model.util.ListQueryParameters import org.eclipse.apoapsis.ortserver.model.util.ListQueryResult @@ -43,10 +45,11 @@ class PackageService(private val db: Database) { suspend fun listForOrtRunId( ortRunId: Long, parameters: ListQueryParameters = ListQueryParameters.DEFAULT - ): ListQueryResult = db.dbQuery { - PackageDao.listCustomQuery(parameters, ResultRow::toPackage) { + ): ListQueryResult = db.dbQuery { + PackageDao.listCustomQuery(parameters, ResultRow::toPackageWithShortestDependencyPath) { PackagesTable.joinAnalyzerTables() - .select(PackagesTable.columns) + .leftJoin(ShortestDependencyPathsTable) + .select(PackagesTable.columns + ShortestDependencyPathsTable.columns) .where { AnalyzerJobsTable.ortRunId eq ortRunId } } } @@ -78,7 +81,15 @@ class PackageService(private val db: Database) { } } -private fun ResultRow.toPackage(): Package = PackageDao.wrapRow(this).mapToModel() +private fun ResultRow.toPackageWithShortestDependencyPath(): PackageWithShortestDependencyPath = + PackageWithShortestDependencyPath( + pkg = PackageDao.wrapRow(this).mapToModel(), + shortestDependencyPath = if (getOrNull(ShortestDependencyPathsTable.id) != null) { + ShortestDependencyPathDao.wrapRow(this).mapToModel() + } else { + null + } + ) private fun PackagesTable.joinAnalyzerTables() = innerJoin(PackagesAnalyzerRunsTable) diff --git a/services/hierarchy/src/test/kotlin/PackageServiceTest.kt b/services/hierarchy/src/test/kotlin/PackageServiceTest.kt index f7d2d9a011..7fcc0ad6b2 100644 --- a/services/hierarchy/src/test/kotlin/PackageServiceTest.kt +++ b/services/hierarchy/src/test/kotlin/PackageServiceTest.kt @@ -26,20 +26,18 @@ import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.should import io.kotest.matchers.shouldBe -import kotlinx.datetime.Clock - import org.eclipse.apoapsis.ortserver.dao.test.DatabaseTestExtension import org.eclipse.apoapsis.ortserver.dao.test.Fixtures import org.eclipse.apoapsis.ortserver.model.AnalyzerJobConfiguration import org.eclipse.apoapsis.ortserver.model.JobConfigurations import org.eclipse.apoapsis.ortserver.model.OrtRun import org.eclipse.apoapsis.ortserver.model.RepositoryType -import org.eclipse.apoapsis.ortserver.model.runs.AnalyzerConfiguration -import org.eclipse.apoapsis.ortserver.model.runs.Environment import org.eclipse.apoapsis.ortserver.model.runs.Identifier import org.eclipse.apoapsis.ortserver.model.runs.Package import org.eclipse.apoapsis.ortserver.model.runs.ProcessedDeclaredLicense +import org.eclipse.apoapsis.ortserver.model.runs.Project import org.eclipse.apoapsis.ortserver.model.runs.RemoteArtifact +import org.eclipse.apoapsis.ortserver.model.runs.ShortestDependencyPath import org.eclipse.apoapsis.ortserver.model.runs.VcsInfo import org.eclipse.apoapsis.ortserver.model.util.ListQueryParameters import org.eclipse.apoapsis.ortserver.model.util.OrderDirection @@ -83,7 +81,7 @@ class PackageServiceTest : WordSpec() { val ortRunId = createAnalyzerRunWithPackages(setOf(pkg1, pkg2)).id - service.listForOrtRunId(ortRunId).data should containExactlyInAnyOrder(pkg1, pkg2) + service.listForOrtRunId(ortRunId).data.map { it.pkg } should containExactlyInAnyOrder(pkg1, pkg2) } "return non-empty maps and sets for authors, declared licenses, and mapped and unmapped licenses" { @@ -100,7 +98,7 @@ class PackageServiceTest : WordSpec() { mappedLicenses = mapOf( "License 1" to "Mapped License 1", "License 2" to "Mapped License 2", - ), + ), unmappedLicenses = setOf("License 1", "License 2", "License 3", "License 4") ), ) @@ -111,7 +109,7 @@ class PackageServiceTest : WordSpec() { results shouldHaveSize 1 - with(results.first()) { + with(results.first().pkg) { authors shouldHaveSize 3 authors shouldBe setOf("Author One", "Author Two", "Author Three") declaredLicenses shouldHaveSize 4 @@ -148,13 +146,8 @@ class PackageServiceTest : WordSpec() { results.data shouldHaveSize 2 results.totalCount shouldBe 3 - with(results.data.first()) { - identifier.name shouldBe "example3" - } - - with(results.data.last()) { - identifier.name shouldBe "example2" - } + results.data.first().pkg.identifier.name shouldBe "example3" + results.data.last().pkg.identifier.name shouldBe "example2" } "return an empty list if no packages were found in an ORT run" { @@ -166,6 +159,60 @@ class PackageServiceTest : WordSpec() { results should beEmpty() } + + "return the shortest dependency path for packages" { + val service = PackageService(db) + + val project = fixtures.getProject() + + val identifier1 = Identifier("Maven", "com.example", "example", "1.0") + val identifier2 = Identifier("Maven", "com.example", "example2", "1.0") + + val ortRunId = createAnalyzerRunWithPackages( + projects = setOf(project), + packages = setOf( + generatePackage(identifier1), + generatePackage(identifier2) + ), + shortestPaths = mapOf( + identifier1 to ShortestDependencyPath( + project, + "compileClassPath", + emptyList() + ), + identifier2 to ShortestDependencyPath( + project, + "compileClassPath", + listOf(identifier1) + ) + ) + ).id + + val results = service.listForOrtRunId( + ortRunId, + ListQueryParameters(listOf(OrderField("purl", OrderDirection.DESCENDING))) + ) + + results.data shouldHaveSize 2 + + with(results.data.first()) { + pkg.identifier shouldBe identifier2 + shortestDependencyPath shouldBe ShortestDependencyPath( + project, + "compileClassPath", + listOf(identifier1) + ) + } + + with(results.data.last()) { + pkg.identifier shouldBe identifier1 + shortestDependencyPath shouldBe ShortestDependencyPath( + project, + "compileClassPath", + emptyList() + ) + } + } } "countForOrtRunId" should { @@ -256,7 +303,9 @@ class PackageServiceTest : WordSpec() { private fun createAnalyzerRunWithPackages( packages: Set, - repositoryId: Long = fixtures.createRepository().id + repositoryId: Long = fixtures.createRepository().id, + projects: Set = emptySet(), + shortestPaths: Map = emptyMap() ): OrtRun { val ortRun = fixtures.createOrtRun( repositoryId = repositoryId, @@ -269,30 +318,11 @@ class PackageServiceTest : WordSpec() { configuration = AnalyzerJobConfiguration(), ) - fixtures.analyzerRunRepository.create( + fixtures.createAnalyzerRun( analyzerJobId = analyzerJob.id, - startTime = Clock.System.now(), - endTime = Clock.System.now(), - environment = Environment( - ortVersion = "1.0", - javaVersion = "11.0.16", - os = "Linux", - processors = 8, - maxMemory = 8321499136, - variables = emptyMap(), - toolVersions = emptyMap() - ), - config = AnalyzerConfiguration( - allowDynamicVersions = true, - enabledPackageManagers = emptyList(), - disabledPackageManagers = emptyList(), - packageManagers = emptyMap(), - skipExcluded = true - ), - projects = emptySet(), + projects = projects, packages = packages, - issues = emptyList(), - dependencyGraphs = emptyMap() + shortestDependencyPaths = shortestPaths ) return ortRun