@@ -27,6 +27,7 @@ import io.kotest.matchers.collections.containExactlyInAnyOrder
2727import io.kotest.matchers.collections.haveSize
2828import io.kotest.matchers.collections.shouldContain
2929import io.kotest.matchers.should
30+ import io.kotest.matchers.shouldBe
3031
3132import java.time.Instant
3233
@@ -38,7 +39,9 @@ import org.ossreviewtoolkit.model.SnippetFinding
3839import org.ossreviewtoolkit.model.TextLocation
3940import org.ossreviewtoolkit.model.VcsInfo
4041import org.ossreviewtoolkit.model.VcsType
42+ import org.ossreviewtoolkit.utils.spdx.SpdxConstants
4143import org.ossreviewtoolkit.utils.spdx.SpdxExpression
44+ import org.ossreviewtoolkit.utils.spdx.toSpdx
4245import org.ossreviewtoolkit.utils.test.readResource
4346
4447class ScanOssResultParserTest : WordSpec ({
@@ -131,5 +134,54 @@ class ScanOssResultParserTest : WordSpec({
131134 )
132135 )
133136 }
137+
138+ " combine the same license from different sources into a single expression" {
139+ // When the same license appears in multiple sources (like scancode and file_header),
140+ // combine them into a single expression rather than duplicating.
141+ val results = readResource("/scanoss-snippet-same-license-multiple-sources.json").let {
142+ JsonUtils .toScanFileResultsFromObject(JsonUtils .toJsonObject(it))
143+ }
144+
145+ val time = Instant .now()
146+ val summary = generateSummary(time, time, results)
147+
148+ // Verify the snippet finding.
149+ summary.snippetFindings should haveSize(1)
150+ val snippet = summary.snippetFindings.first().snippets.first()
151+
152+ // Consolidate the license into a single expression
153+ // even though it came from both "scancode" and "file_header" sources.
154+ snippet.license shouldBe " LGPL-2.1-or-later" .toSpdx()
155+
156+ // Preserve other snippet details correctly.
157+ with(summary.snippetFindings.first()) {
158+ sourceLocation.path shouldBe " src/check_error.c"
159+ sourceLocation.startLine shouldBe 16
160+ sourceLocation.endLine shouldBe 24
161+ }
162+ }
163+
164+ " handle empty license array with NOASSERTION" {
165+ val results = readResource("/scanoss-snippet-no-license-data.json").let {
166+ JsonUtils .toScanFileResultsFromObject(JsonUtils .toJsonObject(it))
167+ }
168+
169+ val time = Instant .now()
170+ val summary = generateSummary(time, time, results)
171+
172+ // Verify the snippet finding.
173+ summary.snippetFindings should haveSize(1)
174+ val snippet = summary.snippetFindings.first().snippets.first()
175+
176+ // Use NOASSERTION when no licenses are provided.
177+ snippet.license shouldBe SpdxConstants .NOASSERTION .toSpdx()
178+
179+ // Preserve other snippet details correctly.
180+ with(summary.snippetFindings.first()) {
181+ sourceLocation.path shouldBe " fake_file.c"
182+ sourceLocation.startLine shouldBe 16
183+ sourceLocation.endLine shouldBe 24
184+ }
185+ }
134186 }
135187})
0 commit comments