Skip to content

Commit 333e6a8

Browse files
committed
use claude to resolve unit tests after updates
Signed-off-by: grokspawn <jordan@nimblewidget.com>
1 parent 6f4e334 commit 333e6a8

File tree

4 files changed

+244
-13
lines changed

4 files changed

+244
-13
lines changed

alpha/declcfg/declcfg.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
utilerrors "k8s.io/apimachinery/pkg/util/errors"
1111
"k8s.io/apimachinery/pkg/util/sets"
1212

13+
"github.com/blang/semver/v4"
1314
"github.com/operator-framework/operator-registry/alpha/property"
1415
prettyunmarshaler "github.com/operator-framework/operator-registry/pkg/prettyunmarshaler"
1516
)
@@ -206,3 +207,71 @@ func (destination *DeclarativeConfig) Merge(src *DeclarativeConfig) {
206207
destination.Others = append(destination.Others, src.Others...)
207208
destination.Deprecations = append(destination.Deprecations, src.Deprecations...)
208209
}
210+
211+
type CompositeVersion struct {
212+
Version semver.Version
213+
Release semver.Version
214+
}
215+
216+
func (cv *CompositeVersion) Compare(other *CompositeVersion) int {
217+
if cv.Version.NE(other.Version) {
218+
return cv.Version.Compare(other.Version)
219+
}
220+
hasrelease := len(cv.Release.Pre) > 0
221+
otherhasrelease := len(other.Release.Pre) > 0
222+
if hasrelease && !otherhasrelease {
223+
return 1
224+
}
225+
if !hasrelease && otherhasrelease {
226+
return -1
227+
}
228+
return cv.Release.Compare(other.Release)
229+
}
230+
231+
// order by version, then
232+
// release, if present
233+
func (b *Bundle) Compare(other *Bundle) int {
234+
if b.Name == other.Name {
235+
return 0
236+
}
237+
acv, err := b.CompositeVersion()
238+
if err != nil {
239+
return 0
240+
}
241+
otherCv, err := other.CompositeVersion()
242+
if err != nil {
243+
return 0
244+
}
245+
return acv.Compare(otherCv)
246+
}
247+
248+
func (b *Bundle) CompositeVersion() (*CompositeVersion, error) {
249+
props, err := property.Parse(b.Properties)
250+
if err != nil {
251+
return nil, fmt.Errorf("parse properties for bundle %q: %v", b.Name, err)
252+
}
253+
if len(props.Packages) != 1 {
254+
return nil, fmt.Errorf("bundle %q must have exactly 1 \"olm.package\" property, found %v", b.Name, len(props.Packages))
255+
}
256+
v, err := semver.Parse(props.Packages[0].Version)
257+
if err != nil {
258+
return nil, fmt.Errorf("bundle %q has invalid version %q: %v", b.Name, props.Packages[0].Version, err)
259+
}
260+
261+
var r semver.Version
262+
if props.Packages[0].Release != "" {
263+
r, err = semver.Parse(fmt.Sprintf("0.0.0-%s", props.Packages[0].Release))
264+
if err != nil {
265+
return nil, fmt.Errorf("error parsing bundle %q release version %q: %v", b.Name, props.Packages[0].Release, err)
266+
}
267+
// only need to check for build metadata since we are using explicit zero major, minor, and patch versions above
268+
if len(r.Build) != 0 {
269+
return nil, fmt.Errorf("bundle %q release version %q cannot contain build metadata", b.Name, props.Packages[0].Release)
270+
}
271+
}
272+
273+
return &CompositeVersion{
274+
Version: v,
275+
Release: r,
276+
}, nil
277+
}

alpha/model/model.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,14 @@ func (b *Bundle) Compare(other *Bundle) int {
366366
if b.Version.NE(other.Version) {
367367
return b.Version.Compare(other.Version)
368368
}
369+
bhasrelease := len(b.Release.Pre) > 0
370+
otherhasrelease := len(other.Release.Pre) > 0
371+
if bhasrelease && !otherhasrelease {
372+
return 1
373+
}
374+
if !bhasrelease && otherhasrelease {
375+
return -1
376+
}
369377
return b.Release.Compare(other.Release)
370378
}
371379

alpha/template/substitutes/substitutes.go

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ func parseSpec(reader io.Reader) (*SubstitutesTemplateData, error) {
106106
return st, nil
107107
}
108108

109-
// processSubstitution handles the complex logic for processing a single substitution
110-
func (t *template) processSubstitution(ctx context.Context, cfg *declcfg.DeclarativeConfig, substitution Substitute) error {
109+
// validateSubstitution validates the substitution references
110+
func (t *template) validateSubstitution(ctx context.Context, cfg *declcfg.DeclarativeConfig, substitution Substitute) error {
111111
// Validate substitution fields - all are required
112112
if substitution.Name == "" {
113113
return fmt.Errorf("substitution name cannot be empty")
@@ -119,6 +119,53 @@ func (t *template) processSubstitution(ctx context.Context, cfg *declcfg.Declara
119119
return fmt.Errorf("substitution name and base cannot be the same")
120120
}
121121

122+
// determine the versions of the base and substitute bundles and ensure that
123+
// the composite version of the substitute bundle is greater than the composite version of the base bundle
124+
125+
// 1. Render the pullspec represented by substitution.Name
126+
substituteCfg, err := t.renderBundle(ctx, substitution.Name)
127+
if err != nil {
128+
return fmt.Errorf("failed to render bundle image reference %q: %v", substitution.Name, err)
129+
}
130+
if substituteCfg == nil || len(substituteCfg.Bundles) == 0 {
131+
return fmt.Errorf("rendered bundle image reference %q contains no bundles", substitution.Name)
132+
}
133+
substituteBundle := &substituteCfg.Bundles[0]
134+
substituteCv, err := substituteBundle.CompositeVersion()
135+
if err != nil {
136+
return fmt.Errorf("failed to get composite version for substitute bundle %q: %v", substitution.Name, err)
137+
}
138+
139+
// 2. Examine cfg to find the bundle which has matching name to substitution.Base
140+
var baseBundle *declcfg.Bundle
141+
for i := range cfg.Bundles {
142+
if cfg.Bundles[i].Name == substitution.Base {
143+
baseBundle = &cfg.Bundles[i]
144+
break
145+
}
146+
}
147+
if baseBundle == nil {
148+
return fmt.Errorf("base bundle %q does not exist in catalog", substitution.Base)
149+
}
150+
baseCv, err := baseBundle.CompositeVersion()
151+
if err != nil {
152+
return fmt.Errorf("failed to get composite version for base bundle %q: %v", substitution.Base, err)
153+
}
154+
155+
// 3. Ensure that the base bundle composite version is less than the substitute bundle composite version
156+
if baseCv.Compare(substituteCv) >= 0 {
157+
return fmt.Errorf("base bundle %q is not less than substitute bundle %q", substitution.Base, substitution.Name)
158+
}
159+
160+
return nil
161+
}
162+
163+
// processSubstitution handles the complex logic for processing a single substitution
164+
func (t *template) processSubstitution(ctx context.Context, cfg *declcfg.DeclarativeConfig, substitution Substitute) error {
165+
if err := t.validateSubstitution(ctx, cfg, substitution); err != nil {
166+
return err
167+
}
168+
122169
substituteCfg, err := t.RenderBundle(ctx, substitution.Name)
123170
if err != nil {
124171
return fmt.Errorf("failed to render bundle image reference %q: %v", substitution.Name, err)

alpha/template/substitutes/substitutes_test.go

Lines changed: 118 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,7 +1066,19 @@ func TestBoundaryCases(t *testing.T) {
10661066
{
10671067
name: "Error/empty DeclarativeConfig",
10681068
testFunc: func(t *testing.T) {
1069-
cfg := &declcfg.DeclarativeConfig{}
1069+
cfg := &declcfg.DeclarativeConfig{
1070+
Bundles: []declcfg.Bundle{
1071+
{
1072+
Schema: "olm.bundle",
1073+
Name: "test.v0.9.0",
1074+
Package: "test",
1075+
Image: "quay.io/test/test-bundle:v0.9.0",
1076+
Properties: []property.Property{
1077+
property.MustBuildPackage("test", "0.9.0"),
1078+
},
1079+
},
1080+
},
1081+
}
10701082
substitution := Substitute{Name: "quay.io/test/test-bundle:v1.0.0-alpha", Base: "test.v0.9.0"}
10711083
template := createMockTemplate()
10721084
ctx := context.Background()
@@ -1080,6 +1092,17 @@ func TestBoundaryCases(t *testing.T) {
10801092
testFunc: func(t *testing.T) {
10811093
cfg := &declcfg.DeclarativeConfig{
10821094
Channels: []declcfg.Channel{},
1095+
Bundles: []declcfg.Bundle{
1096+
{
1097+
Schema: "olm.bundle",
1098+
Name: "test.v0.9.0",
1099+
Package: "test",
1100+
Image: "quay.io/test/test-bundle:v0.9.0",
1101+
Properties: []property.Property{
1102+
property.MustBuildPackage("test", "0.9.0"),
1103+
},
1104+
},
1105+
},
10831106
}
10841107
substitution := Substitute{Name: "quay.io/test/test-bundle:v1.0.0-alpha", Base: "test.v0.9.0"}
10851108
template := createMockTemplate()
@@ -1108,6 +1131,17 @@ func TestBoundaryCases(t *testing.T) {
11081131
Entries: []declcfg.ChannelEntry{},
11091132
},
11101133
},
1134+
Bundles: []declcfg.Bundle{
1135+
{
1136+
Schema: "olm.bundle",
1137+
Name: "test.v0.9.0",
1138+
Package: "test",
1139+
Image: "quay.io/test/test-bundle:v0.9.0",
1140+
Properties: []property.Property{
1141+
property.MustBuildPackage("test", "0.9.0"),
1142+
},
1143+
},
1144+
},
11111145
}
11121146
substitution := Substitute{Name: "quay.io/test/test-bundle:v1.0.0-alpha", Base: "test.v0.9.0"}
11131147
template := createMockTemplate()
@@ -1213,24 +1247,38 @@ func TestBoundaryCases(t *testing.T) {
12131247
{
12141248
name: "Error/substitution with invalid declarative config - missing package",
12151249
testFunc: func(t *testing.T) {
1216-
// Create a config with a bundle that references a non-existent package
1250+
// Create a config with a bundle in the catalog that is not referenced in any channels
12171251
cfg := &declcfg.DeclarativeConfig{
12181252
Packages: []declcfg.Package{
12191253
{
12201254
Schema: "olm.package",
1221-
Name: "nonexistent",
1255+
Name: "testoperator",
12221256
DefaultChannel: "stable",
12231257
},
12241258
},
1259+
Channels: []declcfg.Channel{
1260+
{
1261+
Schema: "olm.channel",
1262+
Name: "stable",
1263+
Package: "testoperator",
1264+
Entries: []declcfg.ChannelEntry{
1265+
{Name: "testoperator.v1.0.0"},
1266+
},
1267+
},
1268+
},
12251269
Bundles: []declcfg.Bundle{
12261270
{
1227-
Name: "testoperator.v1.1.0", // This is the substitution name we're testing
1228-
Package: "nonexistent", // This package exists but bundle name doesn't match
1271+
Name: "testoperator.v1.0.0", // Base bundle for substitution
1272+
Package: "testoperator",
12291273
Properties: []property.Property{
1230-
{
1231-
Type: property.TypePackage,
1232-
Value: json.RawMessage(`{"packageName":"nonexistent","version":"1.1.0"}`),
1233-
},
1274+
property.MustBuildPackage("testoperator", "1.0.0"),
1275+
},
1276+
},
1277+
{
1278+
Name: "testoperator.v1.1.0", // Extra bundle not in any channel
1279+
Package: "testoperator",
1280+
Properties: []property.Property{
1281+
property.MustBuildPackage("testoperator", "1.1.0"),
12341282
},
12351283
},
12361284
},
@@ -1246,7 +1294,7 @@ func TestBoundaryCases(t *testing.T) {
12461294
{
12471295
name: "Error/substitution with invalid declarative config - bundle missing olm.package property",
12481296
testFunc: func(t *testing.T) {
1249-
// Create a config with a bundle that has no olm.package property
1297+
// Create a config where the base bundle has no olm.package property
12501298
cfg := &declcfg.DeclarativeConfig{
12511299
Packages: []declcfg.Package{
12521300
{
@@ -1257,7 +1305,7 @@ func TestBoundaryCases(t *testing.T) {
12571305
},
12581306
Bundles: []declcfg.Bundle{
12591307
{
1260-
Name: "testoperator.v1.1.0", // This is the substitution name we're testing
1308+
Name: "testoperator.v1.0.0", // Base bundle for substitution, missing olm.package property
12611309
Package: "testoperator",
12621310
Properties: []property.Property{}, // No olm.package property
12631311
},
@@ -1271,6 +1319,65 @@ func TestBoundaryCases(t *testing.T) {
12711319
require.Contains(t, err.Error(), "must have exactly 1 \"olm.package\" property")
12721320
},
12731321
},
1322+
{
1323+
name: "Error/base bundle does not exist in catalog",
1324+
testFunc: func(t *testing.T) {
1325+
cfg := &declcfg.DeclarativeConfig{
1326+
Packages: []declcfg.Package{
1327+
{
1328+
Schema: "olm.package",
1329+
Name: "testoperator",
1330+
DefaultChannel: "stable",
1331+
},
1332+
},
1333+
Bundles: []declcfg.Bundle{
1334+
{
1335+
Name: "testoperator.v1.1.0",
1336+
Package: "testoperator",
1337+
Properties: []property.Property{
1338+
property.MustBuildPackage("testoperator", "1.1.0"),
1339+
},
1340+
},
1341+
},
1342+
}
1343+
substitution := Substitute{Name: "quay.io/test/testoperator-bundle:v1.2.0-alpha", Base: "testoperator.v1.0.0"}
1344+
template := createMockTemplate()
1345+
ctx := context.Background()
1346+
err := template.processSubstitution(ctx, cfg, substitution)
1347+
require.Error(t, err)
1348+
require.Contains(t, err.Error(), "does not exist in catalog")
1349+
},
1350+
},
1351+
{
1352+
name: "Error/substitute bundle version not greater than base bundle",
1353+
testFunc: func(t *testing.T) {
1354+
cfg := &declcfg.DeclarativeConfig{
1355+
Packages: []declcfg.Package{
1356+
{
1357+
Schema: "olm.package",
1358+
Name: "testoperator",
1359+
DefaultChannel: "stable",
1360+
},
1361+
},
1362+
Bundles: []declcfg.Bundle{
1363+
{
1364+
Name: "testoperator-v1.2.0-beta",
1365+
Package: "testoperator",
1366+
Properties: []property.Property{
1367+
property.MustBuildPackageRelease("testoperator", "1.2.0", "beta"),
1368+
},
1369+
},
1370+
},
1371+
}
1372+
// Trying to substitute with an alpha release (lower than beta)
1373+
substitution := Substitute{Name: "quay.io/test/testoperator-bundle:v1.2.0-alpha", Base: "testoperator-v1.2.0-beta"}
1374+
template := createMockTemplate()
1375+
ctx := context.Background()
1376+
err := template.processSubstitution(ctx, cfg, substitution)
1377+
require.Error(t, err)
1378+
require.Contains(t, err.Error(), "is not less than")
1379+
},
1380+
},
12741381
}
12751382

12761383
for _, tt := range tests {

0 commit comments

Comments
 (0)