Skip to content

Commit a92afd5

Browse files
committed
add structural extracts and it's tests
1 parent 27c4d5e commit a92afd5

File tree

2 files changed

+315
-0
lines changed

2 files changed

+315
-0
lines changed

pkg/scorer/v2/extractors/structural.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,119 @@
1313
// limitations under the License.
1414

1515
package extractors
16+
17+
import (
18+
"fmt"
19+
"strings"
20+
21+
"github.com/interlynk-io/sbomqs/pkg/sbom"
22+
"github.com/interlynk-io/sbomqs/pkg/scorer/v2/config"
23+
"github.com/interlynk-io/sbomqs/pkg/scorer/v2/formulae"
24+
)
25+
26+
// SBOMWithSpec check for SBOM spec
27+
func SBOMWithSpec(doc sbom.Document) config.FeatureScore {
28+
spec := strings.TrimSpace(strings.ToLower(doc.Spec().GetSpecType()))
29+
30+
if spec == "" {
31+
return config.FeatureScore{
32+
Score: formulae.BooleanScore(false),
33+
Desc: formulae.MissingField("spec"),
34+
Ignore: false,
35+
}
36+
}
37+
38+
for _, s := range sbom.SupportedSBOMSpecs() {
39+
if spec == strings.ToLower(strings.TrimSpace(s)) {
40+
return config.FeatureScore{
41+
Score: formulae.BooleanScore(true),
42+
Desc: spec,
43+
Ignore: false,
44+
}
45+
}
46+
}
47+
48+
return config.FeatureScore{
49+
Score: formulae.BooleanScore(false),
50+
Desc: fmt.Sprintf("unsupported spec: %s", spec),
51+
Ignore: false,
52+
}
53+
}
54+
55+
// SBOMSpecVersion: version supported for this spec?
56+
func SBOMSpecVersion(doc sbom.Document) config.FeatureScore {
57+
spec := strings.TrimSpace(strings.ToLower(doc.Spec().GetSpecType()))
58+
ver := strings.TrimSpace(doc.Spec().GetVersion())
59+
60+
if spec == "" || ver == "" {
61+
return config.FeatureScore{
62+
Score: formulae.BooleanScore(false),
63+
Desc: formulae.MissingField("spec/version"),
64+
Ignore: false,
65+
}
66+
}
67+
68+
supported := sbom.SupportedSBOMSpecVersions(spec)
69+
for _, v := range supported {
70+
if ver == v {
71+
return config.FeatureScore{
72+
Score: formulae.BooleanScore(true),
73+
Desc: ver,
74+
Ignore: false,
75+
}
76+
}
77+
}
78+
79+
return config.FeatureScore{
80+
Score: formulae.BooleanScore(false),
81+
Desc: fmt.Sprintf("unsupported version: %s (spec %s)", ver, spec),
82+
Ignore: false,
83+
}
84+
}
85+
86+
// SBOMFileFormat: file format supported for this spec?
87+
func SBOMFileFormat(doc sbom.Document) config.FeatureScore {
88+
spec := strings.TrimSpace(strings.ToLower(doc.Spec().GetSpecType()))
89+
format := strings.TrimSpace(strings.ToLower(doc.Spec().FileFormat()))
90+
91+
if spec == "" || format == "" {
92+
return config.FeatureScore{
93+
Score: formulae.BooleanScore(false),
94+
Desc: formulae.MissingField("file format"),
95+
Ignore: false,
96+
}
97+
}
98+
99+
supported := sbom.SupportedSBOMFileFormats(spec)
100+
for _, f := range supported {
101+
if format == strings.ToLower(strings.TrimSpace(f)) {
102+
return config.FeatureScore{
103+
Score: formulae.BooleanScore(true),
104+
Desc: format,
105+
Ignore: false,
106+
}
107+
}
108+
}
109+
110+
return config.FeatureScore{
111+
Score: formulae.BooleanScore(false),
112+
Desc: fmt.Sprintf("unsupported format: %s (spec %s)", format, spec),
113+
Ignore: false,
114+
}
115+
}
116+
117+
// SBOMSchemaValid: validate document against official schema for its spec/version.
118+
func SBOMSchemaValid(doc sbom.Document) config.FeatureScore {
119+
if doc.SchemaValidation() {
120+
return config.FeatureScore{
121+
Score: formulae.BooleanScore(true),
122+
Desc: "schema valid",
123+
Ignore: false,
124+
}
125+
}
126+
return config.FeatureScore{
127+
Score: formulae.BooleanScore(false),
128+
Desc: "schema invalid",
129+
Ignore: false,
130+
}
131+
}
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// Copyright 2025 Interlynk.io
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package extractors
16+
17+
import (
18+
"testing"
19+
20+
"github.com/interlynk-io/sbomqs/pkg/sbom"
21+
"github.com/stretchr/testify/assert"
22+
)
23+
24+
func spdxSpecForStructural(ver, fileFmt, spec string) *sbom.Specs {
25+
s := sbom.NewSpec()
26+
s.Version = ver
27+
s.SpecType = spec
28+
s.Format = fileFmt
29+
s.Spdxid = "DOCUMENT"
30+
s.Namespace = "https://example.com/ns"
31+
return s
32+
}
33+
34+
func cdxSpecForStructural(ver, fileFmt, spec string) *sbom.Specs {
35+
s := sbom.NewSpec()
36+
s.Version = ver
37+
s.SpecType = spec
38+
s.Format = fileFmt
39+
s.URI = "urn:uuid:11111111-2222-3333-4444-555555555555"
40+
return s
41+
}
42+
43+
func Test_SBOMSpec(t *testing.T) {
44+
t.Run("SupportedSPDX", func(t *testing.T) {
45+
doc := sbom.SpdxDoc{SpdxSpec: spdxSpecForStructural("SPDX-2.3", "json", "spdx")}
46+
47+
got := SBOMWithSpec(doc)
48+
49+
assert.Equal(t, 10.0, got.Score)
50+
assert.Equal(t, "spdx", got.Desc)
51+
assert.False(t, got.Ignore)
52+
})
53+
54+
t.Run("SupportedSPDXUpperCase", func(t *testing.T) {
55+
doc := sbom.SpdxDoc{SpdxSpec: spdxSpecForStructural("SPDX-2.3", "json", "SPDX")}
56+
57+
got := SBOMWithSpec(doc)
58+
59+
assert.Equal(t, 10.0, got.Score)
60+
assert.Equal(t, "spdx", got.Desc)
61+
assert.False(t, got.Ignore)
62+
})
63+
64+
t.Run("UnsupportedSpec", func(t *testing.T) {
65+
doc := sbom.SpdxDoc{SpdxSpec: spdxSpecForStructural("SPDX-2.3", "json", "something-else")}
66+
67+
got := SBOMWithSpec(doc)
68+
assert.Equal(t, 0.0, got.Score)
69+
assert.Contains(t, got.Desc, "unsupported spec")
70+
})
71+
}
72+
73+
func Test_SBOMSpecVersion(t *testing.T) {
74+
t.Run("SupportedSPDXVersion", func(t *testing.T) {
75+
doc := sbom.SpdxDoc{SpdxSpec: spdxSpecForStructural("SPDX-2.3", "json", "spdx")}
76+
77+
got := SBOMSpecVersion(doc)
78+
79+
assert.Equal(t, 10.0, got.Score)
80+
assert.Equal(t, "SPDX-2.3", got.Desc)
81+
})
82+
83+
t.Run("UnSupportedSPDXVersion", func(t *testing.T) {
84+
doc := sbom.SpdxDoc{SpdxSpec: spdxSpecForStructural("SPDX-100.3", "json", "spdx")}
85+
86+
got := SBOMSpecVersion(doc)
87+
88+
assert.Equal(t, 0.0, got.Score)
89+
assert.Equal(t, "unsupported version: SPDX-100.3 (spec spdx)", got.Desc)
90+
})
91+
92+
t.Run("UnsupportedCDXVersion", func(t *testing.T) {
93+
doc := sbom.CdxDoc{CdxSpec: cdxSpecForStructural("9.9", "json", "cyclonedx")}
94+
95+
got := SBOMSpecVersion(doc)
96+
97+
assert.Equal(t, 0.0, got.Score)
98+
assert.Equal(t, "unsupported version: 9.9 (spec cyclonedx)", got.Desc)
99+
})
100+
}
101+
102+
func Test_SBOMFileFormat(t *testing.T) {
103+
t.Run("SupportedFormatForSPDX", func(t *testing.T) {
104+
doc := sbom.SpdxDoc{SpdxSpec: spdxSpecForStructural("SPDX-2.3", "json", "spdx")}
105+
106+
got := SBOMFileFormat(doc)
107+
108+
assert.Equal(t, 10.0, got.Score)
109+
assert.Equal(t, "json", got.Desc)
110+
})
111+
112+
t.Run("UnsupportedFormatForSPDX", func(t *testing.T) {
113+
doc := sbom.SpdxDoc{SpdxSpec: spdxSpecForStructural("SPDX-2.3", "pdf", "spdx")}
114+
115+
got := SBOMFileFormat(doc)
116+
117+
assert.Equal(t, 0.0, got.Score)
118+
assert.Equal(t, "unsupported format: pdf (spec spdx)", got.Desc)
119+
})
120+
121+
t.Run("UnsupportedFormat", func(t *testing.T) {
122+
doc := sbom.CdxDoc{CdxSpec: cdxSpecForStructural("1.4", "ppl", "cyclonedx")}
123+
124+
got := SBOMFileFormat(doc)
125+
126+
assert.Equal(t, 0.0, got.Score)
127+
assert.Equal(t, "unsupported format: ppl (spec cyclonedx)", got.Desc)
128+
})
129+
}
130+
131+
// Test-only wrappers: embed real docs (to satisfy the full interface)
132+
// and override only SchemaValidation().
133+
type spdxSchemaDoc struct {
134+
sbom.SpdxDoc
135+
valid bool
136+
}
137+
138+
func (d spdxSchemaDoc) SchemaValidation() bool { return d.valid }
139+
140+
type cdxSchemaDoc struct {
141+
sbom.CdxDoc
142+
valid bool
143+
}
144+
145+
func (d cdxSchemaDoc) SchemaValidation() bool { return d.valid }
146+
147+
func Test_SBOMSchemaValid(t *testing.T) {
148+
t.Run("ValidSchema_SPDX", func(t *testing.T) {
149+
doc := spdxSchemaDoc{
150+
SpdxDoc: sbom.SpdxDoc{SpdxSpec: spdxSpecForStructural("SPDX-2.3", "json", "spdx")},
151+
valid: true,
152+
}
153+
154+
got := SBOMSchemaValid(doc)
155+
156+
assert.Equal(t, 10.0, got.Score)
157+
assert.Equal(t, "schema valid", got.Desc)
158+
assert.False(t, got.Ignore)
159+
})
160+
161+
t.Run("ValidSchema_CDX", func(t *testing.T) {
162+
doc := cdxSchemaDoc{
163+
CdxDoc: sbom.CdxDoc{CdxSpec: cdxSpecForStructural("1.6", "json", "cyclonedx")},
164+
valid: true,
165+
}
166+
167+
got := SBOMSchemaValid(doc)
168+
169+
assert.Equal(t, 10.0, got.Score)
170+
assert.Equal(t, "schema valid", got.Desc)
171+
assert.False(t, got.Ignore)
172+
})
173+
174+
t.Run("InvalidSchema_SPDX", func(t *testing.T) {
175+
doc := spdxSchemaDoc{
176+
SpdxDoc: sbom.SpdxDoc{SpdxSpec: spdxSpecForStructural("SPDX-2.3", "json", "spdx")},
177+
valid: false,
178+
}
179+
180+
got := SBOMSchemaValid(doc)
181+
182+
assert.Equal(t, 0.0, got.Score)
183+
assert.Equal(t, "schema invalid", got.Desc)
184+
assert.False(t, got.Ignore)
185+
})
186+
187+
t.Run("InvalidSchema_CDX", func(t *testing.T) {
188+
doc := cdxSchemaDoc{
189+
CdxDoc: sbom.CdxDoc{CdxSpec: cdxSpecForStructural("1.4", "json", "cyclonedx")},
190+
valid: false,
191+
}
192+
193+
got := SBOMSchemaValid(doc)
194+
195+
assert.Equal(t, 0.0, got.Score)
196+
assert.Equal(t, "schema invalid", got.Desc)
197+
assert.False(t, got.Ignore)
198+
})
199+
}

0 commit comments

Comments
 (0)