github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/resolver/resolver_test.go (about)

     1  package resolver
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"math/rand"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/blang/semver/v4"
    12  	"github.com/sirupsen/logrus"
    13  	"github.com/sirupsen/logrus/hooks/test"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  
    18  	"github.com/operator-framework/api/pkg/constraints"
    19  	operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
    20  	"github.com/operator-framework/api/pkg/operators/v1alpha1"
    21  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
    22  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver"
    23  	"github.com/operator-framework/operator-registry/pkg/api"
    24  	opregistry "github.com/operator-framework/operator-registry/pkg/registry"
    25  )
    26  
    27  var testGVKKey = opregistry.APIKey{Group: "g", Version: "v", Kind: "k", Plural: "ks"}
    28  var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
    29  
    30  // tests can directly specify fixtures as cache entries instead of depending on this translation
    31  func csvSnapshotOrPanic(ns string, subs []*v1alpha1.Subscription, csvs ...*v1alpha1.ClusterServiceVersion) *cache.Snapshot {
    32  	var entries []*cache.Entry
    33  	for _, csv := range csvs {
    34  		entry, err := newEntryFromV1Alpha1CSV(csv)
    35  		if err != nil {
    36  			panic(err)
    37  		}
    38  		entry.SourceInfo.Catalog = cache.NewVirtualSourceKey(ns)
    39  		for _, sub := range subs {
    40  			if sub.Status.InstalledCSV == entry.Name {
    41  				entry.SourceInfo.Subscription = sub
    42  			}
    43  		}
    44  		entries = append(entries, entry)
    45  	}
    46  	return &cache.Snapshot{Entries: entries}
    47  }
    48  
    49  func TestSolveOperators(t *testing.T) {
    50  	APISet := cache.APISet{testGVKKey: struct{}{}}
    51  	Provides := APISet
    52  
    53  	const namespace = "test-namespace"
    54  	catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace}
    55  
    56  	csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil)
    57  	sub := existingSub(namespace, "test-package.v1", "test-package", "alpha", catalog)
    58  	newSub := newSub(namespace, "another-package", "alpha", catalog)
    59  	subs := []*v1alpha1.Subscription{sub, newSub}
    60  
    61  	resolver := Resolver{
    62  		cache: cache.New(cache.StaticSourceProvider{
    63  			catalog: &cache.Snapshot{
    64  				Entries: []*cache.Entry{
    65  					genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
    66  					genEntry("another-package.v1", "1.0.1", "", "another-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
    67  				},
    68  			},
    69  			cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv),
    70  		}),
    71  		log: logrus.New(),
    72  	}
    73  
    74  	operators, err := resolver.Resolve([]string{namespace}, subs)
    75  	assert.NoError(t, err)
    76  
    77  	expected := []*cache.Entry{
    78  		genEntry("another-package.v1", "1.0.1", "", "another-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
    79  	}
    80  	require.ElementsMatch(t, expected, operators)
    81  }
    82  
    83  // ConstraintProviderFunc is a simple implementation of ConstraintProvider
    84  type ConstraintProviderFunc func(e *cache.Entry) ([]solver.Constraint, error)
    85  
    86  func (c ConstraintProviderFunc) Constraints(e *cache.Entry) ([]solver.Constraint, error) {
    87  	return c(e)
    88  }
    89  
    90  func TestSolveOperators_WithSystemConstraints(t *testing.T) {
    91  	const namespace = "test-namespace"
    92  	catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace}
    93  
    94  	packageASub := newSub(namespace, "test-package", "alpha", catalog)
    95  	packageDSub := existingSub(namespace, "packageD.v1", "packageD", "alpha", catalog)
    96  
    97  	APISet := cache.APISet{opregistry.APIKey{Group: "g", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}}
    98  
    99  	// test-package requires an API that can be provided by B or C
   100  	testPackage := genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", catalog.Name, catalog.Namespace, APISet, nil, nil, "", false)
   101  	anotherPackage := genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", catalog.Name, catalog.Namespace, nil, APISet, nil, "", false)
   102  	packageC := genEntry("packageC.v1", "1.0.0", "", "packageC", "alpha", catalog.Name, catalog.Namespace, nil, APISet, nil, "", false)
   103  
   104  	// Existing operators
   105  	packageD := genEntry("packageD.v1", "1.0.0", "", "packageD", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false)
   106  	existingPackageD := existingOperator(namespace, "packageD.v1", "packageD", "alpha", "", nil, nil, nil, nil)
   107  	existingPackageD.Annotations = map[string]string{"operatorframework.io/properties": `{"properties":[{"type":"olm.package","value":{"packageName":"packageD","version":"1.0.0"}}]}`}
   108  
   109  	whiteListConstraintProvider := func(whiteList ...*cache.Entry) ConstraintProviderFunc {
   110  		return func(entry *cache.Entry) ([]solver.Constraint, error) {
   111  			for _, whiteListedEntry := range whiteList {
   112  				if whiteListedEntry.Package() == entry.Package() &&
   113  					whiteListedEntry.Name == entry.Name &&
   114  					whiteListedEntry.Version == entry.Version {
   115  					return nil, nil
   116  				}
   117  			}
   118  			return []solver.Constraint{PrettyConstraint(
   119  				solver.Prohibited(),
   120  				fmt.Sprintf("package: %s is not white listed", entry.Package()),
   121  			)}, nil
   122  		}
   123  	}
   124  
   125  	testCases := []struct {
   126  		title                     string
   127  		systemConstraintsProvider constraintProvider
   128  		expectedOperators         []*cache.Entry
   129  		csvs                      []*v1alpha1.ClusterServiceVersion
   130  		subs                      []*v1alpha1.Subscription
   131  		snapshotEntries           []*cache.Entry
   132  		err                       string
   133  	}{
   134  		{
   135  			title:                     "No runtime constraints",
   136  			snapshotEntries:           []*cache.Entry{testPackage, anotherPackage, packageC, packageD},
   137  			systemConstraintsProvider: nil,
   138  			expectedOperators:         []*cache.Entry{testPackage, anotherPackage},
   139  			csvs:                      nil,
   140  			subs:                      []*v1alpha1.Subscription{packageASub},
   141  			err:                       "",
   142  		},
   143  		{
   144  			title:                     "Runtime constraints only accept packages A and C",
   145  			snapshotEntries:           []*cache.Entry{testPackage, anotherPackage, packageC, packageD},
   146  			systemConstraintsProvider: whiteListConstraintProvider(testPackage, packageC),
   147  			expectedOperators:         []*cache.Entry{testPackage, packageC},
   148  			csvs:                      nil,
   149  			subs:                      []*v1alpha1.Subscription{packageASub},
   150  			err:                       "",
   151  		},
   152  		{
   153  			title:                     "Existing packages are ignored",
   154  			snapshotEntries:           []*cache.Entry{testPackage, anotherPackage, packageC, packageD},
   155  			systemConstraintsProvider: whiteListConstraintProvider(testPackage, packageC),
   156  			expectedOperators:         []*cache.Entry{testPackage, packageC},
   157  			csvs:                      []*v1alpha1.ClusterServiceVersion{existingPackageD},
   158  			subs:                      []*v1alpha1.Subscription{packageASub, packageDSub},
   159  			err:                       "",
   160  		},
   161  		{
   162  			title:                     "Runtime constraints don't allow A",
   163  			snapshotEntries:           []*cache.Entry{testPackage, anotherPackage, packageC, packageD},
   164  			systemConstraintsProvider: whiteListConstraintProvider(anotherPackage, packageC, packageD),
   165  			expectedOperators:         nil,
   166  			csvs:                      nil,
   167  			subs:                      []*v1alpha1.Subscription{packageASub},
   168  			err:                       "test-package is not white listed",
   169  		},
   170  	}
   171  
   172  	for _, testCase := range testCases {
   173  		resolver := Resolver{
   174  			cache: cache.New(cache.StaticSourceProvider{
   175  				catalog: &cache.Snapshot{
   176  					Entries: testCase.snapshotEntries,
   177  				},
   178  				cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, testCase.subs, testCase.csvs...),
   179  			}),
   180  			log:                       logrus.New(),
   181  			systemConstraintsProvider: testCase.systemConstraintsProvider,
   182  		}
   183  		operators, err := resolver.Resolve([]string{namespace}, testCase.subs)
   184  
   185  		if testCase.err != "" {
   186  			require.Containsf(t, err.Error(), testCase.err, "Test %s failed", testCase.title)
   187  		} else {
   188  			require.NoErrorf(t, err, "Test %s failed", testCase.title)
   189  		}
   190  		require.ElementsMatch(t, testCase.expectedOperators, operators, "Test %s failed", testCase.title)
   191  	}
   192  }
   193  
   194  func TestDisjointChannelGraph(t *testing.T) {
   195  	const namespace = "test-namespace"
   196  	catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace}
   197  
   198  	newSub := newSub(namespace, "test-package", "alpha", catalog)
   199  	subs := []*v1alpha1.Subscription{newSub}
   200  
   201  	resolver := Resolver{
   202  		cache: cache.New(cache.StaticSourceProvider{
   203  			catalog: &cache.Snapshot{
   204  				Entries: []*cache.Entry{
   205  					genEntry("test-package.side1.v1", "0.0.1", "", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
   206  					genEntry("test-package.side1.v2", "0.0.2", "test-package.side1.v1", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
   207  					genEntry("test-package.side2.v1", "1.0.0", "", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
   208  					genEntry("test-package.side2.v2", "2.0.0", "test-package.side2.v1", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
   209  				},
   210  			},
   211  		}),
   212  		log: logrus.New(),
   213  	}
   214  
   215  	_, err := resolver.Resolve([]string{namespace}, subs)
   216  	require.Error(t, err, "a unique replacement chain within a channel is required to determine the relative order between channel entries, but 2 replacement chains were found in channel \"alpha\" of package \"test-package\": test-package.side1.v2...test-package.side1.v1, test-package.side2.v2...test-package.side2.v1")
   217  }
   218  
   219  func TestSolveOperators_MultipleChannels(t *testing.T) {
   220  	APISet := cache.APISet{testGVKKey: struct{}{}}
   221  	Provides := APISet
   222  
   223  	namespace := "olm"
   224  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
   225  
   226  	csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil)
   227  	sub := existingSub(namespace, "test-package.v1", "test-package", "alpha", catalog)
   228  	newSub := newSub(namespace, "another-package", "alpha", catalog)
   229  	subs := []*v1alpha1.Subscription{sub, newSub}
   230  
   231  	resolver := Resolver{
   232  		cache: cache.New(cache.StaticSourceProvider{
   233  			catalog: &cache.Snapshot{
   234  				Entries: []*cache.Entry{
   235  					genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   236  					genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   237  					genEntry("another-package.v1", "1.0.0", "", "another-package", "beta", "community", "olm", nil, nil, nil, "", false),
   238  				},
   239  			},
   240  			cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv),
   241  		}),
   242  		log: logrus.New(),
   243  	}
   244  
   245  	operators, err := resolver.Resolve([]string{"olm"}, subs)
   246  	assert.NoError(t, err)
   247  	expected := []*cache.Entry{
   248  		genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   249  	}
   250  	assert.ElementsMatch(t, expected, operators)
   251  }
   252  
   253  func TestSolveOperators_FindLatestVersion(t *testing.T) {
   254  	APISet := cache.APISet{testGVKKey: struct{}{}}
   255  	Provides := APISet
   256  
   257  	namespace := "olm"
   258  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
   259  
   260  	csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil)
   261  	sub := existingSub(namespace, "test-package.v1", "test-package", "alpha", catalog)
   262  	newSub := newSub(namespace, "another-package", "alpha", catalog)
   263  	subs := []*v1alpha1.Subscription{sub, newSub}
   264  
   265  	resolver := Resolver{
   266  		cache: cache.New(cache.StaticSourceProvider{
   267  			cache.SourceKey{
   268  				Namespace: "olm",
   269  				Name:      "community",
   270  			}: &cache.Snapshot{
   271  				Entries: []*cache.Entry{
   272  					genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   273  					genEntry("another-package.v0.9.0", "0.9.0", "", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   274  					genEntry("another-package.v1.0.0", "1.0.0", "another-package.v0.9.0", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   275  					genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   276  				},
   277  			},
   278  			cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv),
   279  		}),
   280  		log: logrus.New(),
   281  	}
   282  
   283  	operators, err := resolver.Resolve([]string{"olm"}, subs)
   284  	assert.NoError(t, err)
   285  	assert.Equal(t, 2, len(operators))
   286  	for _, op := range operators {
   287  		assert.Equal(t, "1.0.1", op.Version.String())
   288  	}
   289  
   290  	expected := []*cache.Entry{
   291  		genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   292  		genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   293  	}
   294  	assert.ElementsMatch(t, expected, operators)
   295  }
   296  
   297  func TestSolveOperators_FindLatestVersionWithDependencies(t *testing.T) {
   298  	APISet := cache.APISet{testGVKKey: struct{}{}}
   299  	Provides := APISet
   300  
   301  	namespace := "olm"
   302  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
   303  
   304  	csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil)
   305  	sub := existingSub(namespace, "test-package.v1", "test-package", "alpha", catalog)
   306  	newSub := newSub(namespace, "another-package", "alpha", catalog)
   307  	subs := []*v1alpha1.Subscription{sub, newSub}
   308  
   309  	opToAddVersionDeps := []*api.Dependency{
   310  		{
   311  			Type:  "olm.package",
   312  			Value: `{"packageName":"packageC","version":"1.0.1"}`,
   313  		},
   314  		{
   315  			Type:  "olm.package",
   316  			Value: `{"packageName":"packageD","version":"1.0.1"}`,
   317  		},
   318  	}
   319  
   320  	resolver := Resolver{
   321  		cache: cache.New(cache.StaticSourceProvider{
   322  			catalog: &cache.Snapshot{
   323  				Entries: []*cache.Entry{
   324  					genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   325  					genEntry("another-package.v0.9.0", "0.9.0", "", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   326  					genEntry("another-package.v1.0.0", "1.0.0", "another-package.v0.9.0", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   327  					genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false),
   328  					genEntry("packageC.v1.0.0", "1.0.0", "", "packageC", "alpha", "community", "olm", nil, nil, nil, "", false),
   329  					genEntry("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "community", "olm", nil, nil, nil, "", false),
   330  					genEntry("packageD.v1.0.0", "1.0.0", "", "packageD", "alpha", "community", "olm", nil, nil, nil, "", false),
   331  					genEntry("packageD.v1.0.1", "1.0.1", "packageD.v1.0.0", "packageD", "alpha", "community", "olm", nil, nil, nil, "", false),
   332  					genEntry("packageD.v1.0.2", "1.0.2", "packageD.v1.0.1", "packageD", "alpha", "community", "olm", nil, nil, nil, "", false),
   333  				},
   334  			},
   335  			cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv),
   336  		}),
   337  		log: logrus.New(),
   338  	}
   339  
   340  	operators, err := resolver.Resolve([]string{"olm"}, subs)
   341  	assert.NoError(t, err)
   342  	assert.Equal(t, 4, len(operators))
   343  
   344  	expected := []*cache.Entry{
   345  		genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   346  		genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false),
   347  		genEntry("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "community", "olm", nil, nil, nil, "", false),
   348  		genEntry("packageD.v1.0.1", "1.0.1", "packageD.v1.0.0", "packageD", "alpha", "community", "olm", nil, nil, nil, "", false),
   349  	}
   350  	assert.ElementsMatch(t, expected, operators)
   351  }
   352  
   353  func TestSolveOperators_FindLatestVersionWithNestedDependencies(t *testing.T) {
   354  	APISet := cache.APISet{testGVKKey: struct{}{}}
   355  	Provides := APISet
   356  
   357  	namespace := "olm"
   358  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
   359  
   360  	csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil)
   361  	sub := existingSub(namespace, "test-package.v1", "test-package", "alpha", catalog)
   362  	newSub := newSub(namespace, "another-package", "alpha", catalog)
   363  	subs := []*v1alpha1.Subscription{sub, newSub}
   364  
   365  	opToAddVersionDeps := []*api.Dependency{
   366  		{
   367  			Type:  "olm.package",
   368  			Value: `{"packageName":"packageC","version":"1.0.1"}`,
   369  		},
   370  		{
   371  			Type:  "olm.package",
   372  			Value: `{"packageName":"packageD","version":"1.0.1"}`,
   373  		},
   374  	}
   375  	nestedVersionDeps := []*api.Dependency{
   376  		{
   377  			Type:  "olm.package",
   378  			Value: `{"packageName":"packageE","version":"1.0.1"}`,
   379  		},
   380  	}
   381  
   382  	resolver := Resolver{
   383  		cache: cache.New(cache.StaticSourceProvider{
   384  			catalog: &cache.Snapshot{
   385  				Entries: []*cache.Entry{
   386  					genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   387  					genEntry("another-package.v0.9.0", "0.9.0", "", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   388  					genEntry("another-package.v1.0.0", "1.0.0", "another-package.v0.9.0", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   389  					genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false),
   390  					genEntry("packageC.v1.0.0", "1.0.0", "", "packageC", "alpha", "community", "olm", nil, nil, nestedVersionDeps, "", false),
   391  					genEntry("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "community", "olm", nil, nil, nestedVersionDeps, "", false),
   392  					genEntry("packageD.v1.0.1", "1.0.1", "", "packageD", "alpha", "community", "olm", nil, nil, nil, "", false),
   393  					genEntry("packageE.v1.0.1", "1.0.1", "packageE.v1.0.0", "packageE", "alpha", "community", "olm", nil, nil, nil, "", false),
   394  					genEntry("packageE.v1.0.0", "1.0.0", "", "packageE", "alpha", "community", "olm", nil, nil, nil, "", false),
   395  				},
   396  			},
   397  			cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv),
   398  		}),
   399  		log: logrus.New(),
   400  	}
   401  
   402  	operators, err := resolver.Resolve([]string{"olm"}, subs)
   403  	assert.NoError(t, err)
   404  
   405  	expected := []*cache.Entry{
   406  		genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   407  		genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false),
   408  		genEntry("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "community", "olm", nil, nil, nestedVersionDeps, "", false),
   409  		genEntry("packageD.v1.0.1", "1.0.1", "", "packageD", "alpha", "community", "olm", nil, nil, nil, "", false),
   410  		genEntry("packageE.v1.0.1", "1.0.1", "packageE.v1.0.0", "packageE", "alpha", "community", "olm", nil, nil, nil, "", false),
   411  	}
   412  	assert.ElementsMatch(t, expected, operators)
   413  }
   414  
   415  type stubSourcePriorityProvider map[cache.SourceKey]int
   416  
   417  func (spp stubSourcePriorityProvider) Priority(k cache.SourceKey) int {
   418  	return spp[k]
   419  }
   420  
   421  func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) {
   422  	opToAddVersionDeps := []*api.Dependency{
   423  		{
   424  			Type:  "olm.package",
   425  			Value: `{"packageName":"another-package","version":"0.0.1"}`,
   426  		},
   427  	}
   428  
   429  	namespace := "olm"
   430  	customCatalog := cache.SourceKey{Name: "community", Namespace: namespace}
   431  	newSub := newSub(namespace, "test-package", "alpha", customCatalog)
   432  	subs := []*v1alpha1.Subscription{newSub}
   433  
   434  	ssp := cache.StaticSourceProvider{
   435  		cache.SourceKey{Namespace: "olm", Name: "community"}: &cache.Snapshot{
   436  			Entries: []*cache.Entry{
   437  				genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "community", namespace, nil,
   438  					nil, opToAddVersionDeps, "", false),
   439  			},
   440  		},
   441  		cache.SourceKey{Namespace: "olm", Name: "community-operator"}: &cache.Snapshot{
   442  			Entries: []*cache.Entry{
   443  				genEntry("another-package.v1", "0.0.1", "", "another-package", "alpha", "community-operator",
   444  					namespace, nil, nil, nil, "", false),
   445  			},
   446  		},
   447  		cache.SourceKey{Namespace: "olm", Name: "high-priority-operator"}: &cache.Snapshot{
   448  			Entries: []*cache.Entry{
   449  				genEntry("another-package.v1", "0.0.1", "", "another-package", "alpha", "high-priority-operator",
   450  					namespace, nil, nil, nil, "", false),
   451  			},
   452  		},
   453  	}
   454  
   455  	resolver := Resolver{
   456  		cache: cache.New(ssp, cache.WithSourcePriorityProvider(stubSourcePriorityProvider{cache.SourceKey{Namespace: "olm", Name: "high-priority-operator"}: 100})),
   457  	}
   458  
   459  	operators, err := resolver.Resolve([]string{"olm"}, subs)
   460  	assert.NoError(t, err)
   461  	expected := []*cache.Entry{
   462  		genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "community", "olm",
   463  			nil, nil, opToAddVersionDeps, "", false),
   464  		genEntry("another-package.v1", "0.0.1", "", "another-package", "alpha", "high-priority-operator", "olm",
   465  			nil, nil, nil, "", false),
   466  	}
   467  	assert.ElementsMatch(t, expected, operators)
   468  
   469  	// Catsrc with the same priority, ns, different name
   470  	ssp[cache.SourceKey{
   471  		Namespace: "olm",
   472  		Name:      "community-operator",
   473  	}] = &cache.Snapshot{
   474  		Entries: []*cache.Entry{
   475  			genEntry("another-package.v1", "0.0.1", "", "another-package", "alpha", "community-operator",
   476  				namespace, nil, nil, nil, "", false),
   477  		},
   478  	}
   479  
   480  	resolver = Resolver{
   481  		cache: cache.New(ssp, cache.WithSourcePriorityProvider(stubSourcePriorityProvider{
   482  			cache.SourceKey{Namespace: "olm", Name: "high-priority-operator"}: 100,
   483  			cache.SourceKey{Namespace: "olm", Name: "community-operator"}:     100,
   484  		})),
   485  	}
   486  
   487  	operators, err = resolver.Resolve([]string{"olm"}, subs)
   488  	assert.NoError(t, err)
   489  	expected = []*cache.Entry{
   490  		genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "community", "olm",
   491  			nil, nil, opToAddVersionDeps, "", false),
   492  		genEntry("another-package.v1", "0.0.1", "", "another-package", "alpha", "community-operator", "olm",
   493  			nil, nil, nil, "", false),
   494  	}
   495  	assert.ElementsMatch(t, expected, operators)
   496  
   497  	// operators from the same catalogs source should be prioritized.
   498  	ssp[cache.SourceKey{
   499  		Namespace: "olm",
   500  		Name:      "community",
   501  	}] = &cache.Snapshot{
   502  		Entries: []*cache.Entry{
   503  			genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "community", namespace, nil,
   504  				nil, opToAddVersionDeps, "", false),
   505  			genEntry("another-package.v1", "0.0.1", "", "another-package", "alpha", "community",
   506  				namespace, nil, nil, nil, "", false),
   507  		},
   508  	}
   509  
   510  	resolver = Resolver{
   511  		cache: cache.New(ssp),
   512  	}
   513  
   514  	operators, err = resolver.Resolve([]string{"olm"}, subs)
   515  	assert.NoError(t, err)
   516  	expected = []*cache.Entry{
   517  		genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "community", "olm",
   518  			nil, nil, opToAddVersionDeps, "", false),
   519  		genEntry("another-package.v1", "0.0.1", "", "another-package", "alpha", "community", "olm",
   520  			nil, nil, nil, "", false),
   521  	}
   522  	assert.ElementsMatch(t, expected, operators)
   523  }
   524  
   525  func TestSolveOperators_WithPackageDependencies(t *testing.T) {
   526  	APISet := cache.APISet{testGVKKey: struct{}{}}
   527  	Provides := APISet
   528  
   529  	namespace := "olm"
   530  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
   531  
   532  	csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil)
   533  	sub := existingSub(namespace, "test-package.v1", "test-package", "alpha", catalog)
   534  	newSub := newSub(namespace, "another-package", "alpha", catalog)
   535  	subs := []*v1alpha1.Subscription{sub, newSub}
   536  
   537  	opToAddVersionDeps := []*api.Dependency{
   538  		{
   539  			Type:  "olm.package",
   540  			Value: `{"packageName":"packageC","version":"0.1.0"}`,
   541  		},
   542  	}
   543  
   544  	resolver := Resolver{
   545  		cache: cache.New(cache.StaticSourceProvider{
   546  			catalog: &cache.Snapshot{
   547  				Entries: []*cache.Entry{
   548  					genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   549  					genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false),
   550  					genEntry("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, nil, nil, "", false),
   551  				},
   552  			},
   553  			cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv),
   554  		}),
   555  		log: logrus.New(),
   556  	}
   557  
   558  	operators, err := resolver.Resolve([]string{"olm"}, subs)
   559  	assert.NoError(t, err)
   560  	assert.Equal(t, 3, len(operators))
   561  
   562  	expected := []*cache.Entry{
   563  		genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   564  		genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false),
   565  		genEntry("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, nil, nil, "", false),
   566  	}
   567  	assert.ElementsMatch(t, expected, operators)
   568  }
   569  
   570  func TestSolveOperators_WithGVKDependencies(t *testing.T) {
   571  	APISet := cache.APISet{testGVKKey: struct{}{}}
   572  	Provides := APISet
   573  
   574  	namespace := "olm"
   575  	community := cache.SourceKey{Name: "community", Namespace: namespace}
   576  
   577  	subs := []*v1alpha1.Subscription{
   578  		existingSub(namespace, "test-package.v1", "test-package", "alpha", community),
   579  		newSub(namespace, "another-package", "alpha", community),
   580  	}
   581  
   582  	deps := []*api.Dependency{
   583  		{
   584  			Type:  "olm.gvk",
   585  			Value: `{"group":"g","kind":"k","version":"v"}`,
   586  		},
   587  	}
   588  
   589  	resolver := Resolver{
   590  		cache: cache.New(cache.StaticSourceProvider{
   591  			community: &cache.Snapshot{
   592  				Entries: []*cache.Entry{
   593  					genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   594  					genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", Provides, nil, deps, "", false),
   595  					genEntry("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, Provides, nil, "", false),
   596  				},
   597  			},
   598  			cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(
   599  				namespace,
   600  				subs,
   601  				existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", nil, nil, nil, nil),
   602  			),
   603  		}),
   604  		log: logrus.New(),
   605  	}
   606  
   607  	operators, err := resolver.Resolve([]string{"olm"}, subs)
   608  	assert.NoError(t, err)
   609  
   610  	expected := []*cache.Entry{
   611  		genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", Provides, nil, deps, "", false),
   612  		genEntry("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, Provides, nil, "", false),
   613  	}
   614  	assert.ElementsMatch(t, expected, operators)
   615  }
   616  
   617  func TestSolveOperators_WithLabelDependencies(t *testing.T) {
   618  	namespace := "olm"
   619  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
   620  
   621  	newSub := newSub(namespace, "test-package", "alpha", catalog)
   622  	subs := []*v1alpha1.Subscription{newSub}
   623  
   624  	deps := []*api.Dependency{
   625  		{
   626  			Type:  "olm.label",
   627  			Value: `{"label":"lts"}`,
   628  		},
   629  	}
   630  
   631  	props := []*api.Property{
   632  		{
   633  			Type:  "olm.label",
   634  			Value: `{"label":"lts"}`,
   635  		},
   636  	}
   637  
   638  	operatorBv1 := genEntry("another-package.v1", "1.0.0", "", "another-package", "beta", "community", "olm", nil, nil, nil, "", false)
   639  	operatorBv1.Properties = append(operatorBv1.Properties, props...)
   640  
   641  	resolver := Resolver{
   642  		cache: cache.New(cache.StaticSourceProvider{
   643  			catalog: &cache.Snapshot{
   644  				Entries: []*cache.Entry{
   645  					genEntry("test-package", "0.0.1", "", "test-package", "alpha", "community", "olm", nil, nil, deps, "", false),
   646  					operatorBv1,
   647  				},
   648  			},
   649  		}),
   650  	}
   651  
   652  	operators, err := resolver.Resolve([]string{"olm"}, subs)
   653  	assert.NoError(t, err)
   654  	assert.Equal(t, 2, len(operators))
   655  
   656  	expected := []*cache.Entry{
   657  		genEntry("test-package", "0.0.1", "", "test-package", "alpha", "community", "olm", nil, nil, deps, "", false),
   658  		operatorBv1,
   659  	}
   660  	assert.ElementsMatch(t, expected, operators)
   661  }
   662  
   663  func TestSolveOperators_WithUnsatisfiableLabelDependencies(t *testing.T) {
   664  	namespace := "olm"
   665  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
   666  
   667  	newSub := newSub(namespace, "test-package", "alpha", catalog)
   668  	subs := []*v1alpha1.Subscription{newSub}
   669  
   670  	deps := []*api.Dependency{
   671  		{
   672  			Type:  "olm.label",
   673  			Value: `{"label":"lts"}`,
   674  		},
   675  	}
   676  
   677  	resolver := Resolver{
   678  		cache: cache.New(cache.StaticSourceProvider{
   679  			catalog: &cache.Snapshot{
   680  				Entries: []*cache.Entry{
   681  					genEntry("test-package", "0.0.1", "", "test-package", "alpha", "community", "olm", nil, nil, deps, "", false),
   682  					genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   683  				},
   684  			},
   685  		}),
   686  	}
   687  
   688  	operators, err := resolver.Resolve([]string{"olm"}, subs)
   689  	assert.Error(t, err)
   690  	assert.Equal(t, 0, len(operators))
   691  }
   692  
   693  func TestSolveOperators_WithNestedGVKDependencies(t *testing.T) {
   694  	APISet := cache.APISet{testGVKKey: struct{}{}}
   695  	Provides := APISet
   696  
   697  	namespace := "olm"
   698  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
   699  
   700  	csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil)
   701  	sub := existingSub(namespace, "test-package.v1", "test-package", "alpha", catalog)
   702  	newSub := newSub(namespace, "another-package", "alpha", catalog)
   703  	subs := []*v1alpha1.Subscription{sub, newSub}
   704  
   705  	deps := []*api.Dependency{
   706  		{
   707  			Type:  "olm.gvk",
   708  			Value: `{"group":"g","kind":"k","version":"v"}`,
   709  		},
   710  	}
   711  
   712  	deps2 := []*api.Dependency{
   713  		{
   714  			Type:  "olm.gvk",
   715  			Value: `{"group":"g2","kind":"k2","version":"v2"}`,
   716  		},
   717  	}
   718  
   719  	resolver := Resolver{
   720  		cache: cache.New(cache.StaticSourceProvider{
   721  			cache.SourceKey{
   722  				Namespace: "olm",
   723  				Name:      "community",
   724  			}: &cache.Snapshot{
   725  				Entries: []*cache.Entry{
   726  					genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   727  					genEntry("another-package.v1.0.0", "1.0.0", "", "another-package", "alpha", "community", "olm", Provides, nil, deps, "", false),
   728  					genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", Provides, nil, deps, "", false),
   729  					genEntry("packageC.v1.0.0", "1.0.0", "", "packageC", "alpha", "community", "olm", Provides2, Provides, deps2, "", false),
   730  					genEntry("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "community", "olm", Provides2, Provides, deps2, "", false),
   731  					genEntry("packageD.v1.0.1", "1.0.1", "", "packageD", "alpha", "community", "olm", nil, Provides2, deps2, "", false),
   732  				},
   733  			},
   734  			cache.SourceKey{
   735  				Namespace: "olm",
   736  				Name:      "certified",
   737  			}: &cache.Snapshot{
   738  				Entries: []*cache.Entry{
   739  					genEntry("packageC.v1.0.0", "1.0.0", "", "packageC", "alpha", "certified", "olm", Provides2, Provides, deps2, "", false),
   740  					genEntry("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "certified", "olm", Provides2, Provides, deps2, "", false),
   741  					genEntry("packageD.v1.0.1", "1.0.1", "", "packageD", "alpha", "certified", "olm", nil, Provides2, nil, "", false),
   742  				},
   743  			},
   744  			cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv),
   745  		}),
   746  		log: logrus.New(),
   747  	}
   748  
   749  	operators, err := resolver.Resolve([]string{"olm"}, subs)
   750  	assert.NoError(t, err)
   751  	expected := []*cache.Entry{
   752  		genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false),
   753  		genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", Provides, nil, deps, "", false),
   754  		genEntry("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "community", "olm", Provides2, Provides, deps2, "", false),
   755  		genEntry("packageD.v1.0.1", "1.0.1", "", "packageD", "alpha", "community", "olm", nil, Provides2, deps2, "", false),
   756  	}
   757  	assert.ElementsMatch(t, expected, operators)
   758  }
   759  
   760  type entryGenerator struct {
   761  	name, version                string
   762  	replaces                     string
   763  	pkg, channel, defaultChannel string
   764  	catName, catNamespace        string
   765  	requiredAPIs, providedAPIs   cache.APISet
   766  	properties                   []*api.Property
   767  	deprecated                   bool
   768  }
   769  
   770  func (g entryGenerator) gen() *cache.Entry {
   771  	entry := genEntry(g.name, g.version, g.replaces, g.pkg, g.channel, g.catName, g.catNamespace, g.requiredAPIs, g.providedAPIs, nil, g.defaultChannel, g.deprecated)
   772  	entry.Properties = append(entry.Properties, g.properties...)
   773  	return entry
   774  }
   775  
   776  func genEntriesRandom(ops ...entryGenerator) []*cache.Entry {
   777  	entries := make([]*cache.Entry, len(ops))
   778  	// Randomize entry order to fuzz input operators over time.
   779  	idxs := rnd.Perm(len(ops))
   780  	for destIdx, srcIdx := range idxs {
   781  		entries[destIdx] = ops[srcIdx].gen()
   782  	}
   783  	return entries
   784  }
   785  
   786  func TestSolveOperators_OLMConstraint_CompoundAll(t *testing.T) {
   787  	namespace := "olm"
   788  	csName := "community"
   789  	catalog := cache.SourceKey{Name: csName, Namespace: namespace}
   790  
   791  	newOperatorGens := []entryGenerator{{
   792  		name: "bar.v1.0.0", version: "1.0.0",
   793  		pkg: "bar", channel: "stable",
   794  		catName: csName, catNamespace: namespace,
   795  		properties: []*api.Property{{
   796  			Type: constraints.OLMConstraintType,
   797  			Value: `{"failureMessage": "all constraint",
   798  				"all": {"constraints": [
   799  					{"package": {"packageName": "foo", "versionRange": ">=1.0.0"}},
   800  					{"gvk": {"group": "g1", "version": "v1", "kind": "k1"}},
   801  					{"gvk": {"group": "g2", "version": "v2", "kind": "k2"}}
   802  				]}
   803  			}`,
   804  		}},
   805  	}}
   806  	dependeeOperatorGens := []entryGenerator{{
   807  		name: "foo.v1.0.1", version: "1.0.1",
   808  		pkg: "foo", channel: "stable", replaces: "foo.v1.0.0",
   809  		catName: csName, catNamespace: namespace,
   810  		providedAPIs: cache.APISet{
   811  			opregistry.APIKey{Group: "g1", Version: "v1", Kind: "k1"}: {},
   812  			opregistry.APIKey{Group: "g2", Version: "v2", Kind: "k2"}: {},
   813  			opregistry.APIKey{Group: "g3", Version: "v3", Kind: "k3"}: {},
   814  		},
   815  	}}
   816  
   817  	inputs := append(dependeeOperatorGens, newOperatorGens...)
   818  
   819  	resolver := Resolver{
   820  		cache: cache.New(cache.StaticSourceProvider{
   821  			catalog: &cache.Snapshot{
   822  				Entries: genEntriesRandom(append(
   823  					inputs,
   824  					entryGenerator{
   825  						name: "foo.v0.99.0", version: "0.99.0",
   826  						pkg: "foo", channel: "stable",
   827  						catName: csName, catNamespace: namespace,
   828  						providedAPIs: cache.APISet{
   829  							opregistry.APIKey{Group: "g1", Version: "v1", Kind: "k1"}: {},
   830  							opregistry.APIKey{Group: "g2", Version: "v2", Kind: "k2"}: {},
   831  							opregistry.APIKey{Group: "g3", Version: "v3", Kind: "k3"}: {},
   832  						},
   833  					},
   834  					entryGenerator{
   835  						name: "foo.v1.0.0", version: "1.0.0",
   836  						pkg: "foo", channel: "stable", replaces: "foo.v0.99.0",
   837  						catName: csName, catNamespace: namespace,
   838  						providedAPIs: cache.APISet{
   839  							opregistry.APIKey{Group: "g2", Version: "v2", Kind: "k2"}: {},
   840  							opregistry.APIKey{Group: "g3", Version: "v3", Kind: "k3"}: {},
   841  						},
   842  					},
   843  				)...),
   844  			},
   845  		}),
   846  		log: logrus.New(),
   847  	}
   848  
   849  	newSub := newSub(namespace, "bar", "stable", catalog)
   850  	subs := []*v1alpha1.Subscription{newSub}
   851  
   852  	operators, err := resolver.Resolve([]string{namespace}, subs)
   853  	require.NoError(t, err)
   854  
   855  	expected := make([]*cache.Entry, len(inputs))
   856  	for i, gen := range inputs {
   857  		expected[i] = gen.gen()
   858  	}
   859  	assert.ElementsMatch(t, expected, operators)
   860  }
   861  
   862  func TestSolveOperators_OLMConstraint_CompoundAny(t *testing.T) {
   863  	namespace := "olm"
   864  	csName := "community"
   865  	catalog := cache.SourceKey{Name: csName, Namespace: namespace}
   866  
   867  	newOperatorGens := []entryGenerator{{
   868  		name: "bar.v1.0.0", version: "1.0.0",
   869  		pkg: "bar", channel: "stable",
   870  		catName: csName, catNamespace: namespace,
   871  		properties: []*api.Property{{
   872  			Type: constraints.OLMConstraintType,
   873  			Value: `{"failureMessage": "any constraint",
   874  				"any": {"constraints": [
   875  					{"gvk": {"group": "g1", "version": "v1", "kind": "k1"}},
   876  					{"gvk": {"group": "g2", "version": "v2", "kind": "k2"}}
   877  				]}
   878  			}`,
   879  		}},
   880  	}}
   881  	dependeeOperatorGens := []entryGenerator{{
   882  		name: "foo.v1.0.1", version: "1.0.1",
   883  		pkg: "foo", channel: "stable", replaces: "foo.v1.0.0",
   884  		catName: csName, catNamespace: namespace,
   885  		providedAPIs: cache.APISet{
   886  			opregistry.APIKey{Group: "g1", Version: "v1", Kind: "k1"}: {},
   887  			opregistry.APIKey{Group: "g2", Version: "v2", Kind: "k2"}: {},
   888  			opregistry.APIKey{Group: "g3", Version: "v3", Kind: "k3"}: {},
   889  		},
   890  	}}
   891  
   892  	inputs := append(dependeeOperatorGens, newOperatorGens...)
   893  
   894  	resolver := Resolver{
   895  		cache: cache.New(cache.StaticSourceProvider{
   896  			catalog: &cache.Snapshot{
   897  				Entries: genEntriesRandom(append(
   898  					inputs,
   899  					entryGenerator{
   900  						name: "foo.v0.99.0", version: "0.99.0",
   901  						pkg: "foo", channel: "stable",
   902  						catName: csName, catNamespace: namespace,
   903  						providedAPIs: cache.APISet{
   904  							opregistry.APIKey{Group: "g0", Version: "v0", Kind: "k0"}: {},
   905  						},
   906  					},
   907  					entryGenerator{
   908  						name: "foo.v1.0.0", version: "1.0.0",
   909  						pkg: "foo", channel: "stable", replaces: "foo.v0.99.0",
   910  						catName: csName, catNamespace: namespace,
   911  						providedAPIs: cache.APISet{
   912  							opregistry.APIKey{Group: "g1", Version: "v1", Kind: "k1"}: {},
   913  							opregistry.APIKey{Group: "g2", Version: "v2", Kind: "k2"}: {},
   914  						},
   915  					},
   916  				)...),
   917  			},
   918  		}),
   919  		log: logrus.New(),
   920  	}
   921  
   922  	newSub := newSub(namespace, "bar", "stable", catalog)
   923  	subs := []*v1alpha1.Subscription{newSub}
   924  
   925  	operators, err := resolver.Resolve([]string{namespace}, subs)
   926  	require.NoError(t, err)
   927  	assert.Equal(t, 2, len(operators))
   928  
   929  	expected := make([]*cache.Entry, len(inputs))
   930  	for i, gen := range inputs {
   931  		expected[i] = gen.gen()
   932  	}
   933  	assert.ElementsMatch(t, expected, operators)
   934  }
   935  
   936  func TestSolveOperators_OLMConstraint_CompoundNot(t *testing.T) {
   937  	namespace := "olm"
   938  	csName := "community"
   939  	catalog := cache.SourceKey{Name: csName, Namespace: namespace}
   940  
   941  	newOperatorGens := []entryGenerator{{
   942  		name: "bar.v1.0.0", version: "1.0.0",
   943  		pkg: "bar", channel: "stable",
   944  		catName: csName, catNamespace: namespace,
   945  		properties: []*api.Property{
   946  			{
   947  				Type: constraints.OLMConstraintType,
   948  				Value: `{"failureMessage": "compound not constraint",
   949  					"all": {"constraints": [
   950  						{"gvk": {"group": "g0", "version": "v0", "kind": "k0"}},
   951  						{"not": {"constraints": [
   952  							{"gvk": {"group": "g1", "version": "v1", "kind": "k1"}},
   953  							{"gvk": {"group": "g2", "version": "v2", "kind": "k2"}}
   954  						]}}
   955  					]}
   956  				}`,
   957  			},
   958  		},
   959  	}}
   960  	dependeeOperatorGens := []entryGenerator{{
   961  		name: "foo.v0.99.0", version: "0.99.0",
   962  		pkg: "foo", channel: "stable",
   963  		catName: csName, catNamespace: namespace,
   964  		providedAPIs: cache.APISet{
   965  			opregistry.APIKey{Group: "g0", Version: "v0", Kind: "k0"}: {},
   966  		},
   967  	}}
   968  
   969  	inputs := append(dependeeOperatorGens, newOperatorGens...)
   970  
   971  	resolver := Resolver{
   972  		cache: cache.New(cache.StaticSourceProvider{
   973  			catalog: &cache.Snapshot{
   974  				Entries: genEntriesRandom(append(
   975  					inputs,
   976  					entryGenerator{
   977  						name: "foo.v1.0.0", version: "1.0.0",
   978  						pkg: "foo", channel: "stable", replaces: "foo.v0.99.0",
   979  						catName: csName, catNamespace: namespace,
   980  						providedAPIs: cache.APISet{
   981  							opregistry.APIKey{Group: "g0", Version: "v0", Kind: "k0"}: {},
   982  							opregistry.APIKey{Group: "g1", Version: "v1", Kind: "k1"}: {},
   983  							opregistry.APIKey{Group: "g2", Version: "v2", Kind: "k2"}: {},
   984  						},
   985  					},
   986  					entryGenerator{
   987  						name: "foo.v1.0.1", version: "1.0.1",
   988  						pkg: "foo", channel: "stable", replaces: "foo.v1.0.0",
   989  						catName: csName, catNamespace: namespace,
   990  						providedAPIs: cache.APISet{
   991  							opregistry.APIKey{Group: "g0", Version: "v0", Kind: "k0"}: {},
   992  							opregistry.APIKey{Group: "g1", Version: "v1", Kind: "k1"}: {},
   993  							opregistry.APIKey{Group: "g2", Version: "v2", Kind: "k2"}: {},
   994  							opregistry.APIKey{Group: "g3", Version: "v3", Kind: "k3"}: {},
   995  						},
   996  					},
   997  				)...),
   998  			},
   999  		}),
  1000  		log: logrus.New(),
  1001  	}
  1002  
  1003  	newSub := newSub(namespace, "bar", "stable", catalog)
  1004  	subs := []*v1alpha1.Subscription{newSub}
  1005  
  1006  	operators, err := resolver.Resolve([]string{namespace}, subs)
  1007  	require.NoError(t, err)
  1008  	assert.Equal(t, 2, len(operators))
  1009  
  1010  	expected := make([]*cache.Entry, len(inputs))
  1011  	for i, gen := range inputs {
  1012  		expected[i] = gen.gen()
  1013  	}
  1014  	assert.ElementsMatch(t, expected, operators)
  1015  }
  1016  
  1017  func TestSolveOperators_OLMConstraint_Unknown(t *testing.T) {
  1018  	namespace := "olm"
  1019  	csName := "community"
  1020  	catalog := cache.SourceKey{Name: csName, Namespace: namespace}
  1021  
  1022  	newOperatorGens := []entryGenerator{{
  1023  		name: "bar.v1.0.0", version: "1.0.0",
  1024  		pkg: "bar", channel: "stable",
  1025  		catName: csName, catNamespace: namespace,
  1026  		properties: []*api.Property{{
  1027  			Type:  constraints.OLMConstraintType,
  1028  			Value: `{"failureMessage": "unknown constraint", "unknown": {"foo": "bar"}}`,
  1029  		}},
  1030  	}}
  1031  
  1032  	resolver := Resolver{
  1033  		cache: cache.New(cache.StaticSourceProvider{
  1034  			catalog: &cache.Snapshot{
  1035  				Entries: genEntriesRandom(newOperatorGens...),
  1036  			},
  1037  		}),
  1038  		log: logrus.New(),
  1039  	}
  1040  
  1041  	newSub := newSub(namespace, "bar", "stable", catalog)
  1042  	subs := []*v1alpha1.Subscription{newSub}
  1043  
  1044  	_, err := resolver.Resolve([]string{namespace}, subs)
  1045  	require.Error(t, err)
  1046  	require.Contains(t, err.Error(), `json: unknown field "unknown"`)
  1047  }
  1048  
  1049  func TestSolveOperators_IgnoreUnsatisfiableDependencies(t *testing.T) {
  1050  	const namespace = "olm"
  1051  
  1052  	Provides := cache.APISet{testGVKKey: struct{}{}}
  1053  	community := cache.SourceKey{Name: "community", Namespace: namespace}
  1054  
  1055  	subs := []*v1alpha1.Subscription{
  1056  		existingSub(namespace, "test-package.v1", "test-package", "alpha", community),
  1057  		newSub(namespace, "another-package", "alpha", community),
  1058  	}
  1059  
  1060  	opToAddVersionDeps := []*api.Dependency{
  1061  		{
  1062  			Type:  "olm.package",
  1063  			Value: `{"packageName":"packageC","version":"0.1.0"}`,
  1064  		},
  1065  	}
  1066  	unsatisfiableVersionDeps := []*api.Dependency{
  1067  		{
  1068  			Type:  "olm.package",
  1069  			Value: `{"packageName":"packageD","version":"0.1.0"}`,
  1070  		},
  1071  	}
  1072  
  1073  	resolver := Resolver{
  1074  		cache: cache.New(cache.StaticSourceProvider{
  1075  			community: &cache.Snapshot{
  1076  				Entries: []*cache.Entry{
  1077  					genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false),
  1078  					genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false),
  1079  					genEntry("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, nil, unsatisfiableVersionDeps, "", false),
  1080  				},
  1081  			},
  1082  			{Namespace: "olm", Name: "certified"}: &cache.Snapshot{
  1083  				Entries: []*cache.Entry{
  1084  					genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "certified", "olm", nil, nil, nil, "", false),
  1085  					genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "certified", "olm", nil, nil, opToAddVersionDeps, "", false),
  1086  					genEntry("packageC.v1", "0.1.0", "", "packageC", "alpha", "certified", "olm", nil, nil, nil, "", false),
  1087  				},
  1088  			},
  1089  			cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(
  1090  				namespace,
  1091  				subs,
  1092  				existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil),
  1093  			),
  1094  		}),
  1095  		log: logrus.New(),
  1096  	}
  1097  
  1098  	operators, err := resolver.Resolve([]string{"olm"}, subs)
  1099  	assert.NoError(t, err)
  1100  	expected := []*cache.Entry{
  1101  		genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false),
  1102  		genEntry("packageC.v1", "0.1.0", "", "packageC", "alpha", "certified", "olm", nil, nil, nil, "", false),
  1103  	}
  1104  	assert.ElementsMatch(t, expected, operators)
  1105  }
  1106  
  1107  // Behavior: The resolver should prefer catalogs in the same namespace as the subscription.
  1108  // It should also prefer the same catalog over global catalogs in terms of the operator cache.
  1109  func TestSolveOperators_PreferCatalogInSameNamespace(t *testing.T) {
  1110  	APISet := cache.APISet{testGVKKey: struct{}{}}
  1111  	Provides := APISet
  1112  
  1113  	namespace := "olm"
  1114  	altNamespace := "alt-olm"
  1115  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
  1116  	altnsCatalog := cache.SourceKey{Name: "alt-community", Namespace: altNamespace}
  1117  
  1118  	csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil)
  1119  	sub := existingSub(namespace, "test-package.v1", "test-package", "alpha", catalog)
  1120  	subs := []*v1alpha1.Subscription{sub}
  1121  
  1122  	resolver := Resolver{
  1123  		cache: cache.New(cache.StaticSourceProvider{
  1124  			catalog: &cache.Snapshot{
  1125  				Entries: []*cache.Entry{
  1126  					genEntry("test-package.v0.0.1", "0.0.1", "test-package.v1", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, "", false),
  1127  				},
  1128  			},
  1129  			altnsCatalog: &cache.Snapshot{
  1130  				Entries: []*cache.Entry{
  1131  					genEntry("test-package.v0.0.1", "0.0.1", "test-package.v1", "test-package", "alpha", altnsCatalog.Name, altnsCatalog.Namespace, nil, Provides, nil, "", false),
  1132  				},
  1133  			},
  1134  			cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv),
  1135  		}),
  1136  		log: logrus.New(),
  1137  	}
  1138  
  1139  	operators, err := resolver.Resolve([]string{namespace}, subs)
  1140  	assert.NoError(t, err)
  1141  
  1142  	expected := []*cache.Entry{
  1143  		genEntry("test-package.v0.0.1", "0.0.1", "test-package.v1", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, "", false),
  1144  	}
  1145  	require.ElementsMatch(t, expected, operators)
  1146  }
  1147  
  1148  // Behavior: The resolver should not look in catalogs not in the same namespace or the global catalog namespace when resolving the subscription.
  1149  // This test should not result in a successful resolution because the catalog fulfilling the subscription is not in the operator cache.
  1150  func TestSolveOperators_ResolveOnlyInCachedNamespaces(t *testing.T) {
  1151  	APISet := cache.APISet{testGVKKey: struct{}{}}
  1152  	Provides := APISet
  1153  
  1154  	namespace := "olm"
  1155  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
  1156  	otherCatalog := cache.SourceKey{Name: "secret", Namespace: "secret"}
  1157  
  1158  	csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil)
  1159  	newSub := newSub(namespace, "test-package", "alpha", catalog)
  1160  	subs := []*v1alpha1.Subscription{newSub}
  1161  
  1162  	resolver := Resolver{
  1163  		cache: cache.New(cache.StaticSourceProvider{
  1164  			catalog: &cache.Snapshot{
  1165  				Entries: []*cache.Entry{
  1166  					genEntry("test-package.v0.0.1", "0.0.1", "test-package.v1", "test-package", "alpha", otherCatalog.Name, otherCatalog.Namespace, nil, Provides, nil, "", false),
  1167  				},
  1168  			},
  1169  			cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv),
  1170  		}),
  1171  		log: logrus.New(),
  1172  	}
  1173  
  1174  	operators, err := resolver.Resolve([]string{namespace}, subs)
  1175  	assert.Error(t, err)
  1176  	assert.Equal(t, err.Error(), "expected exactly one operator, got 0", "did not expect to receive a resolution")
  1177  	assert.Len(t, operators, 0)
  1178  }
  1179  
  1180  // Behavior: the resolver should always prefer the default channel for the subscribed bundle (unless we implement ordering for channels)
  1181  func TestSolveOperators_PreferDefaultChannelInResolution(t *testing.T) {
  1182  	APISet := cache.APISet{testGVKKey: struct{}{}}
  1183  	Provides := APISet
  1184  
  1185  	namespace := "olm"
  1186  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
  1187  
  1188  	const defaultChannel = "stable"
  1189  	// do not specify a channel explicitly on the subscription
  1190  	newSub := newSub(namespace, "test-package", "", catalog)
  1191  	subs := []*v1alpha1.Subscription{newSub}
  1192  
  1193  	resolver := Resolver{
  1194  		cache: cache.New(cache.StaticSourceProvider{
  1195  			catalog: &cache.Snapshot{
  1196  				Entries: []*cache.Entry{
  1197  					// Default channel is stable in this case
  1198  					genEntry("test-package.v0.0.2", "0.0.2", "test-package.v1", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false),
  1199  					genEntry("test-package.v0.0.1", "0.0.1", "test-package.v1", "test-package", "stable", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false),
  1200  				},
  1201  			},
  1202  		}),
  1203  		log: logrus.New(),
  1204  	}
  1205  
  1206  	operators, err := resolver.Resolve([]string{namespace}, subs)
  1207  	assert.NoError(t, err)
  1208  
  1209  	// operator should be from the default stable channel
  1210  	expected := []*cache.Entry{
  1211  		genEntry("test-package.v0.0.1", "0.0.1", "test-package.v1", "test-package", "stable", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false),
  1212  	}
  1213  	require.ElementsMatch(t, expected, operators)
  1214  }
  1215  
  1216  // Behavior: the resolver should always prefer the default channel for bundles satisfying transitive dependencies
  1217  func TestSolveOperators_PreferDefaultChannelInResolutionForTransitiveDependencies(t *testing.T) {
  1218  	APISet := cache.APISet{testGVKKey: struct{}{}}
  1219  	Provides := APISet
  1220  
  1221  	namespace := "olm"
  1222  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
  1223  
  1224  	newSub := newSub(namespace, "test-package", "alpha", catalog)
  1225  	subs := []*v1alpha1.Subscription{newSub}
  1226  
  1227  	const defaultChannel = "stable"
  1228  
  1229  	resolver := Resolver{
  1230  		cache: cache.New(cache.StaticSourceProvider{
  1231  			catalog: &cache.Snapshot{
  1232  				Entries: []*cache.Entry{
  1233  					genEntry("test-package.v0.0.1", "0.0.1", "test-package.v1", "test-package", "alpha", catalog.Name, catalog.Namespace, Provides, nil, apiSetToDependencies(nil, Provides), defaultChannel, false),
  1234  					genEntry("another-package.v0.0.1", "0.0.1", "another-package.v1", "another-package", defaultChannel, catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false),
  1235  					genEntry("another-package.v0.0.2", "0.0.2", "another-package.v0.0.1", "another-package", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false),
  1236  				},
  1237  			},
  1238  		}),
  1239  		log: logrus.New(),
  1240  	}
  1241  
  1242  	operators, err := resolver.Resolve([]string{namespace}, subs)
  1243  	assert.NoError(t, err)
  1244  
  1245  	// operator should be from the default stable channel
  1246  	expected := []*cache.Entry{
  1247  		genEntry("test-package.v0.0.1", "0.0.1", "test-package.v1", "test-package", "alpha", catalog.Name, catalog.Namespace, Provides, nil, apiSetToDependencies(nil, Provides), defaultChannel, false),
  1248  		genEntry("another-package.v0.0.1", "0.0.1", "another-package.v1", "another-package", defaultChannel, catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false),
  1249  	}
  1250  	require.ElementsMatch(t, expected, operators)
  1251  }
  1252  
  1253  func TestSolveOperators_SubscriptionlessOperatorsSatisfyDependencies(t *testing.T) {
  1254  	APISet := cache.APISet{testGVKKey: struct{}{}}
  1255  	Provides := APISet
  1256  
  1257  	namespace := "olm"
  1258  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
  1259  
  1260  	csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil)
  1261  	newSub := newSub(namespace, "another-package", "alpha", catalog)
  1262  	subs := []*v1alpha1.Subscription{newSub}
  1263  
  1264  	deps := []*api.Dependency{
  1265  		{
  1266  			Type:  "olm.gvk",
  1267  			Value: `{"group":"g","kind":"k","version":"v"}`,
  1268  		},
  1269  	}
  1270  
  1271  	resolver := Resolver{
  1272  		cache: cache.New(cache.StaticSourceProvider{
  1273  			catalog: &cache.Snapshot{
  1274  				Entries: []*cache.Entry{
  1275  					genEntry("another-package.v1.0.0", "1.0.0", "", "another-package", "alpha", "community", "olm", Provides, nil, deps, "", false),
  1276  					genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", Provides, nil, deps, "", false),
  1277  				},
  1278  			},
  1279  			cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv),
  1280  		}),
  1281  		log: logrus.New(),
  1282  	}
  1283  
  1284  	operators, err := resolver.Resolve([]string{"olm"}, subs)
  1285  	assert.NoError(t, err)
  1286  	expected := []*cache.Entry{
  1287  		genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", catalog.Name, catalog.Namespace, Provides, nil, apiSetToDependencies(Provides, nil), "", false),
  1288  	}
  1289  	assert.ElementsMatch(t, expected, operators)
  1290  }
  1291  
  1292  func TestSolveOperators_SubscriptionlessOperatorsCanConflict(t *testing.T) {
  1293  	APISet := cache.APISet{testGVKKey: struct{}{}}
  1294  	Provides := APISet
  1295  
  1296  	namespace := "olm"
  1297  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
  1298  
  1299  	csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil)
  1300  	newSub := newSub(namespace, "another-package", "alpha", catalog)
  1301  	subs := []*v1alpha1.Subscription{newSub}
  1302  
  1303  	resolver := Resolver{
  1304  		cache: cache.New(cache.StaticSourceProvider{
  1305  			catalog: &cache.Snapshot{
  1306  				Entries: []*cache.Entry{
  1307  					genEntry("another-package.v1.0.0", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, Provides, nil, "", false),
  1308  					genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", nil, Provides, nil, "", false),
  1309  				},
  1310  			},
  1311  			cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv),
  1312  		}),
  1313  		log: logrus.New(),
  1314  	}
  1315  
  1316  	_, err := resolver.Resolve([]string{"olm"}, subs)
  1317  	assert.Error(t, err)
  1318  }
  1319  
  1320  func TestSolveOperators_PackageCannotSelfSatisfy(t *testing.T) {
  1321  	Provides1 := cache.APISet{testGVKKey: struct{}{}}
  1322  	Requires1 := cache.APISet{testGVKKey: struct{}{}}
  1323  	Provides2 := cache.APISet{opregistry.APIKey{Group: "g2", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}}
  1324  	Requires2 := cache.APISet{opregistry.APIKey{Group: "g2", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}}
  1325  	ProvidesBoth := Provides1.Union(Provides2)
  1326  	RequiresBoth := Requires1.Union(Requires2)
  1327  
  1328  	namespace := "olm"
  1329  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
  1330  	secondaryCatalog := cache.SourceKey{Namespace: "olm", Name: "secondary"}
  1331  
  1332  	newSub := newSub(namespace, "test-package", "stable", catalog)
  1333  	subs := []*v1alpha1.Subscription{newSub}
  1334  
  1335  	resolver := Resolver{
  1336  		cache: cache.New(cache.StaticSourceProvider{
  1337  			catalog: &cache.Snapshot{
  1338  				Entries: []*cache.Entry{
  1339  					genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, RequiresBoth, nil, nil, "", false),
  1340  					// Despite satisfying dependencies of opA, this is not chosen because it is in the same package
  1341  					genEntry("opABC.v1.0.0", "1.0.0", "", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, ProvidesBoth, nil, "", false),
  1342  
  1343  					genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "stable", false),
  1344  					genEntry("opD.v1.0.0", "1.0.0", "", "another-package", "alpha", catalog.Name, catalog.Namespace, nil, Provides1, nil, "stable", false),
  1345  				},
  1346  			},
  1347  			secondaryCatalog: &cache.Snapshot{
  1348  				Entries: []*cache.Entry{
  1349  					genEntry("opC.v1.0.0", "1.0.0", "", "another-package", "stable", secondaryCatalog.Name, secondaryCatalog.Namespace, nil, Provides2, nil, "stable", false),
  1350  
  1351  					genEntry("opE.v1.0.0", "1.0.0", "", "packageC", "stable", secondaryCatalog.Name, secondaryCatalog.Namespace, nil, Provides2, nil, "", false),
  1352  				},
  1353  			},
  1354  		}),
  1355  		log: logrus.New(),
  1356  	}
  1357  
  1358  	operators, err := resolver.Resolve([]string{"olm"}, subs)
  1359  	assert.NoError(t, err)
  1360  	expected := []*cache.Entry{
  1361  		genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, RequiresBoth, nil, nil, "", false),
  1362  		genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "stable", false),
  1363  		genEntry("opE.v1.0.0", "1.0.0", "", "packageC", "stable", secondaryCatalog.Name, secondaryCatalog.Namespace, nil, Provides2, nil, "", false),
  1364  	}
  1365  	assert.ElementsMatch(t, expected, operators)
  1366  }
  1367  
  1368  func TestSolveOperators_TransferApiOwnership(t *testing.T) {
  1369  	Provides1 := cache.APISet{testGVKKey: struct{}{}}
  1370  	Requires1 := cache.APISet{testGVKKey: struct{}{}}
  1371  	Provides2 := cache.APISet{opregistry.APIKey{Group: "g2", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}}
  1372  	ProvidesBoth := Provides1.Union(Provides2)
  1373  
  1374  	namespace := "olm"
  1375  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
  1376  	og := &operatorsv1.OperatorGroup{
  1377  		ObjectMeta: metav1.ObjectMeta{
  1378  			Name:      "og",
  1379  			Namespace: namespace,
  1380  		},
  1381  	}
  1382  
  1383  	phases := []struct {
  1384  		subs     []*v1alpha1.Subscription
  1385  		catalog  cache.Source
  1386  		expected []*cache.Entry
  1387  	}{
  1388  		{
  1389  			subs: []*v1alpha1.Subscription{newSub(namespace, "another-package", "stable", catalog)},
  1390  			catalog: &cache.Snapshot{
  1391  				Entries: []*cache.Entry{
  1392  					genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false),
  1393  					genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false),
  1394  				},
  1395  			},
  1396  			expected: []*cache.Entry{
  1397  				genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false),
  1398  				genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false),
  1399  			},
  1400  		},
  1401  		{
  1402  			// will have two existing subs after resolving once
  1403  			subs: []*v1alpha1.Subscription{
  1404  				existingSub(namespace, "opA.v1.0.0", "test-package", "stable", catalog),
  1405  				existingSub(namespace, "opB.v1.0.0", "another-package", "stable", catalog),
  1406  			},
  1407  			catalog: &cache.Snapshot{
  1408  				Entries: []*cache.Entry{
  1409  					genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false),
  1410  					genEntry("opA.v1.0.1", "1.0.1", "opA.v1.0.0", "test-package", "stable", catalog.Name, catalog.Namespace, Requires1, nil, nil, "", false),
  1411  					genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false),
  1412  				},
  1413  			},
  1414  			// nothing new to do here
  1415  			expected: nil,
  1416  		},
  1417  		{
  1418  			// will have two existing subs after resolving once
  1419  			subs: []*v1alpha1.Subscription{
  1420  				existingSub(namespace, "opA.v1.0.0", "test-package", "stable", catalog),
  1421  				existingSub(namespace, "opB.v1.0.0", "another-package", "stable", catalog),
  1422  			},
  1423  			catalog: &cache.Snapshot{
  1424  				Entries: []*cache.Entry{
  1425  					genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false),
  1426  					genEntry("opA.v1.0.1", "1.0.1", "opA.v1.0.0", "test-package", "stable", catalog.Name, catalog.Namespace, Requires1, nil, nil, "", false),
  1427  					genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false),
  1428  					genEntry("opB.v1.0.1", "1.0.1", "opB.v1.0.0", "another-package", "stable", catalog.Name, catalog.Namespace, nil, ProvidesBoth, nil, "stable", false),
  1429  				},
  1430  			},
  1431  			expected: []*cache.Entry{
  1432  				genEntry("opA.v1.0.1", "1.0.1", "opA.v1.0.0", "test-package", "stable", catalog.Name, catalog.Namespace, Requires1, nil, nil, "", false),
  1433  				genEntry("opB.v1.0.1", "1.0.1", "opB.v1.0.0", "another-package", "stable", catalog.Name, catalog.Namespace, nil, ProvidesBoth, nil, "stable", false),
  1434  			},
  1435  		},
  1436  	}
  1437  
  1438  	var csvs fakeCSVLister
  1439  	var operators []*cache.Entry
  1440  	for i, p := range phases {
  1441  		t.Run(fmt.Sprintf("phase %d", i+1), func(t *testing.T) {
  1442  			logger, _ := test.NewNullLogger()
  1443  			resolver := Resolver{
  1444  				cache: cache.New(cache.StaticSourceProvider{
  1445  					catalog: p.catalog,
  1446  					// todo: test depends on csvSource
  1447  					cache.NewVirtualSourceKey(namespace): &csvSource{
  1448  						key:       cache.NewVirtualSourceKey(namespace),
  1449  						csvLister: &csvs,
  1450  						subLister: fakeSubscriptionLister(p.subs),
  1451  						ogLister:  fakeOperatorGroupLister{og},
  1452  						logger:    logger,
  1453  						listSubscriptions: func(ctx context.Context) (*v1alpha1.SubscriptionList, error) {
  1454  							return &v1alpha1.SubscriptionList{
  1455  								Items: func(ptrs []*v1alpha1.Subscription) []v1alpha1.Subscription {
  1456  									var out []v1alpha1.Subscription
  1457  									for _, sub := range ptrs {
  1458  										out = append(out, *sub)
  1459  									}
  1460  									return out
  1461  								}(p.subs),
  1462  							}, nil
  1463  						},
  1464  					},
  1465  				}),
  1466  				log: logger,
  1467  			}
  1468  			csvs = csvs[:0]
  1469  			for _, o := range operators {
  1470  				var pkg, channel string
  1471  				if si := o.SourceInfo; si != nil {
  1472  					pkg = si.Package
  1473  					channel = si.Channel
  1474  				}
  1475  				csv := existingOperator(namespace, o.Name, pkg, channel, o.Replaces, o.ProvidedAPIs, o.RequiredAPIs, nil, nil)
  1476  				csvs = append(csvs, csv)
  1477  				if o.SourceInfo != nil && o.SourceInfo.Subscription != nil {
  1478  					o.SourceInfo.Subscription.Status = v1alpha1.SubscriptionStatus{
  1479  						InstalledCSV: csv.Name,
  1480  					}
  1481  				}
  1482  			}
  1483  
  1484  			var err error
  1485  			operators, err = resolver.Resolve([]string{"olm"}, p.subs)
  1486  			assert.NoError(t, err)
  1487  			assert.ElementsMatch(t, p.expected, operators)
  1488  		})
  1489  	}
  1490  }
  1491  
  1492  func genEntry(name, version, replaces, pkg, channel, catalogName, catalogNamespace string, requiredAPIs, providedAPIs cache.APISet, dependencies []*api.Dependency, defaultChannel string, deprecated bool) *cache.Entry {
  1493  	semversion, _ := semver.Make(version)
  1494  	properties := apiSetToProperties(providedAPIs, nil, deprecated)
  1495  	if len(dependencies) == 0 {
  1496  		ps, err := requiredAPIsToProperties(requiredAPIs)
  1497  		if err != nil {
  1498  			panic(err)
  1499  		}
  1500  		properties = append(properties, ps...)
  1501  	} else {
  1502  		ps, err := legacyDependenciesToProperties(dependencies)
  1503  		if err != nil {
  1504  			panic(err)
  1505  		}
  1506  		properties = append(properties, ps...)
  1507  	}
  1508  	o := &cache.Entry{
  1509  		Name:       name,
  1510  		Version:    &semversion,
  1511  		Replaces:   replaces,
  1512  		Properties: properties,
  1513  		SourceInfo: &cache.OperatorSourceInfo{
  1514  			Catalog: cache.SourceKey{
  1515  				Name:      catalogName,
  1516  				Namespace: catalogNamespace,
  1517  			},
  1518  			DefaultChannel: defaultChannel != "" && channel == defaultChannel,
  1519  			Package:        pkg,
  1520  			Channel:        channel,
  1521  		},
  1522  		ProvidedAPIs: providedAPIs,
  1523  		RequiredAPIs: requiredAPIs,
  1524  	}
  1525  	EnsurePackageProperty(o, pkg, version)
  1526  	return o
  1527  }
  1528  
  1529  func TestSolveOperators_WithoutDeprecated(t *testing.T) {
  1530  	catalog := cache.SourceKey{Name: "catalog", Namespace: "namespace"}
  1531  
  1532  	subs := []*v1alpha1.Subscription{
  1533  		newSub(catalog.Namespace, "test-package", "alpha", catalog),
  1534  	}
  1535  
  1536  	resolver := Resolver{
  1537  		cache: cache.New(cache.StaticSourceProvider{
  1538  			catalog: &cache.Snapshot{
  1539  				Entries: []*cache.Entry{
  1540  					genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", true),
  1541  				},
  1542  			},
  1543  		}),
  1544  		log: logrus.New(),
  1545  	}
  1546  
  1547  	operators, err := resolver.Resolve([]string{catalog.Namespace}, subs)
  1548  	assert.Empty(t, operators)
  1549  	assert.IsType(t, solver.NotSatisfiable{}, err)
  1550  }
  1551  
  1552  func TestSolveOperatorsWithDeprecatedInnerChannelEntry(t *testing.T) {
  1553  	catalog := cache.SourceKey{Name: "catalog", Namespace: "namespace"}
  1554  
  1555  	subs := []*v1alpha1.Subscription{
  1556  		newSub(catalog.Namespace, "a", "c", catalog),
  1557  	}
  1558  	logger, _ := test.NewNullLogger()
  1559  	resolver := Resolver{
  1560  		cache: cache.New(cache.StaticSourceProvider{
  1561  			catalog: &cache.Snapshot{
  1562  				Entries: []*cache.Entry{
  1563  					genEntry("a-1", "1.0.0", "", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
  1564  					genEntry("a-2", "2.0.0", "a-1", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", true),
  1565  					genEntry("a-3", "3.0.0", "a-2", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
  1566  				},
  1567  			},
  1568  		}),
  1569  		log: logger,
  1570  	}
  1571  
  1572  	operators, err := resolver.Resolve([]string{catalog.Namespace}, subs)
  1573  	assert.NoError(t, err)
  1574  	assert.ElementsMatch(t, []*cache.Entry{genEntry("a-3", "3.0.0", "a-2", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", false)}, operators)
  1575  }
  1576  
  1577  func TestSolveOperators_WithSkipsAndStartingCSV(t *testing.T) {
  1578  	APISet := cache.APISet{testGVKKey: struct{}{}}
  1579  	Provides := APISet
  1580  
  1581  	namespace := "olm"
  1582  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
  1583  
  1584  	newSub := newSub(namespace, "another-package", "alpha", catalog, withStartingCSV("another-package.v1"))
  1585  	subs := []*v1alpha1.Subscription{newSub}
  1586  
  1587  	opToAddVersionDeps := []*api.Dependency{
  1588  		{
  1589  			Type:  "olm.gvk",
  1590  			Value: `{"group":"g","kind":"k","version":"v"}`,
  1591  		},
  1592  	}
  1593  
  1594  	opB := genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false)
  1595  	opB2 := genEntry("another-package.v2", "2.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false)
  1596  	opB2.Skips = []string{"another-package.v1"}
  1597  	op1 := genEntry("test-package.v1", "1.0.0", "", "test-package", "alpha", "community", "olm", nil, Provides, nil, "", false)
  1598  	op2 := genEntry("test-package.v2", "2.0.0", "test-package.v1", "test-package", "alpha", "community", "olm", nil, Provides, nil, "", false)
  1599  	op3 := genEntry("test-package.v3", "3.0.0", "test-package.v2", "test-package", "alpha", "community", "olm", nil, Provides, nil, "", false)
  1600  	op4 := genEntry("test-package.v4", "4.0.0", "test-package.v3", "test-package", "alpha", "community", "olm", nil, Provides, nil, "", false)
  1601  	op4.Skips = []string{"test-package.v3"}
  1602  	op5 := genEntry("test-package.v5", "5.0.0", "test-package.v4", "test-package", "alpha", "community", "olm", nil, Provides, nil, "", false)
  1603  	op5.Skips = []string{"test-package.v2", "test-package.v3", "test-package.v4"}
  1604  	op6 := genEntry("test-package.v6", "6.0.0", "test-package.v5", "test-package", "alpha", "community", "olm", nil, Provides, nil, "", false)
  1605  
  1606  	resolver := Resolver{
  1607  		cache: cache.New(cache.StaticSourceProvider{
  1608  			catalog: &cache.Snapshot{
  1609  				Entries: []*cache.Entry{
  1610  					opB, opB2, op1, op2, op3, op4, op5, op6,
  1611  				},
  1612  			},
  1613  		}),
  1614  		log: logrus.New(),
  1615  	}
  1616  
  1617  	operators, err := resolver.Resolve([]string{"olm"}, subs)
  1618  	assert.NoError(t, err)
  1619  	opB.SourceInfo.StartingCSV = "another-package.v1"
  1620  	expected := []*cache.Entry{opB, op6}
  1621  	require.ElementsMatch(t, expected, operators)
  1622  }
  1623  
  1624  func TestSolveOperators_WithSkips(t *testing.T) {
  1625  	const namespace = "test-namespace"
  1626  	catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace}
  1627  
  1628  	newSub := newSub(namespace, "another-package", "alpha", catalog)
  1629  	subs := []*v1alpha1.Subscription{newSub}
  1630  
  1631  	opB := genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false)
  1632  	opB2 := genEntry("another-package.v2", "2.0.0", "", "another-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false)
  1633  	opB2.Skips = []string{"another-package.v1"}
  1634  
  1635  	resolver := Resolver{
  1636  		cache: cache.New(cache.StaticSourceProvider{
  1637  			catalog: &cache.Snapshot{
  1638  				Entries: []*cache.Entry{
  1639  					opB, opB2,
  1640  				},
  1641  			},
  1642  		}),
  1643  		log: logrus.New(),
  1644  	}
  1645  
  1646  	operators, err := resolver.Resolve([]string{namespace}, subs)
  1647  	assert.NoError(t, err)
  1648  	expected := []*cache.Entry{opB2}
  1649  	require.ElementsMatch(t, expected, operators)
  1650  }
  1651  
  1652  func TestSolveOperatorsWithSkipsPreventingSelection(t *testing.T) {
  1653  	const namespace = "test-namespace"
  1654  	catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace}
  1655  	gvks := cache.APISet{testGVKKey: struct{}{}}
  1656  
  1657  	// Subscription candidate a-1 requires a GVK provided
  1658  	// exclusively by b-1, but b-1 is skipped by b-3 and can't be
  1659  	// chosen.
  1660  	subs := []*v1alpha1.Subscription{newSub(namespace, "a", "channel", catalog)}
  1661  	a1 := genEntry("a-1", "1.0.0", "", "a", "channel", catalog.Name, catalog.Namespace, gvks, nil, nil, "", false)
  1662  	b3 := genEntry("b-3", "3.0.0", "b-2", "b", "channel", catalog.Name, catalog.Namespace, nil, nil, nil, "", false)
  1663  	b3.Skips = []string{"b-1"}
  1664  	b2 := genEntry("b-2", "2.0.0", "b-1", "b", "channel", catalog.Name, catalog.Namespace, nil, nil, nil, "", false)
  1665  	b1 := genEntry("b-1", "1.0.0", "", "b", "channel", catalog.Name, catalog.Namespace, nil, gvks, nil, "", false)
  1666  
  1667  	logger, _ := test.NewNullLogger()
  1668  	resolver := Resolver{
  1669  		cache: cache.New(cache.StaticSourceProvider{
  1670  			catalog: &cache.Snapshot{
  1671  				Entries: []*cache.Entry{a1, b3, b2, b1},
  1672  			},
  1673  		}),
  1674  		log: logger,
  1675  	}
  1676  
  1677  	_, err := resolver.Resolve([]string{namespace}, subs)
  1678  	assert.IsType(t, solver.NotSatisfiable{}, err)
  1679  }
  1680  
  1681  func TestSolveOperatorsWithClusterServiceVersionHavingDependency(t *testing.T) {
  1682  	const namespace = "test-namespace"
  1683  	catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace}
  1684  	virtual := cache.NewVirtualSourceKey(namespace)
  1685  
  1686  	subs := []*v1alpha1.Subscription{
  1687  		existingSub(namespace, "b-1", "b", "default", catalog),
  1688  	}
  1689  
  1690  	log, _ := test.NewNullLogger()
  1691  	r := Resolver{
  1692  		cache: cache.New(cache.StaticSourceProvider{
  1693  			catalog: &cache.Snapshot{
  1694  				Entries: []*cache.Entry{
  1695  					{
  1696  						Name:     "b-2",
  1697  						Replaces: "b-1",
  1698  						Version:  &semver.Version{},
  1699  						SourceInfo: &cache.OperatorSourceInfo{
  1700  							Package: "b",
  1701  							Channel: "default",
  1702  							Catalog: catalog,
  1703  						},
  1704  					},
  1705  				},
  1706  			},
  1707  			virtual: &cache.Snapshot{
  1708  				Entries: []*cache.Entry{
  1709  					{
  1710  						Name: "a-1",
  1711  						Properties: []*api.Property{
  1712  							{Type: "olm.package.required", Value: `{"packageName":"b","versionRange":"1.0.0"}`},
  1713  						},
  1714  						Version: &semver.Version{},
  1715  						SourceInfo: &cache.OperatorSourceInfo{
  1716  							Catalog: virtual,
  1717  						},
  1718  					},
  1719  					{
  1720  						Name: "b-1",
  1721  						Properties: []*api.Property{
  1722  							{Type: "olm.package", Value: `{"packageName":"b","version":"1.0.0"}`},
  1723  						},
  1724  						Version: &semver.Version{},
  1725  						SourceInfo: &cache.OperatorSourceInfo{
  1726  							Catalog: virtual,
  1727  						},
  1728  					},
  1729  				},
  1730  			},
  1731  		}),
  1732  		log: log,
  1733  	}
  1734  
  1735  	operators, err := r.Resolve([]string{namespace}, subs)
  1736  	assert.NoError(t, err)
  1737  	require.Empty(t, operators)
  1738  }
  1739  
  1740  func TestSortChannel(t *testing.T) {
  1741  	for _, tc := range []struct {
  1742  		Name string
  1743  		In   []*cache.Entry
  1744  		Out  []*cache.Entry
  1745  		Err  error
  1746  	}{
  1747  		{
  1748  			Name: "wrinkle-free",
  1749  			In: []*cache.Entry{
  1750  				{
  1751  					Name: "b",
  1752  					SourceInfo: &cache.OperatorSourceInfo{
  1753  						Package: "package",
  1754  						Channel: "channel",
  1755  					},
  1756  				},
  1757  				{
  1758  					Name:     "a",
  1759  					Replaces: "b",
  1760  					SourceInfo: &cache.OperatorSourceInfo{
  1761  						Package: "package",
  1762  						Channel: "channel",
  1763  					},
  1764  				},
  1765  			},
  1766  			Out: []*cache.Entry{
  1767  				{
  1768  					Name:     "a",
  1769  					Replaces: "b",
  1770  					SourceInfo: &cache.OperatorSourceInfo{
  1771  						Package: "package",
  1772  						Channel: "channel",
  1773  					},
  1774  				},
  1775  				{
  1776  					Name: "b",
  1777  					SourceInfo: &cache.OperatorSourceInfo{
  1778  						Package: "package",
  1779  						Channel: "channel",
  1780  					},
  1781  				},
  1782  			},
  1783  		},
  1784  		{
  1785  			Name: "empty",
  1786  			In:   nil,
  1787  			Out:  nil,
  1788  		},
  1789  		{
  1790  			Name: "replacement cycle",
  1791  			In: []*cache.Entry{
  1792  				{
  1793  					Name:     "a",
  1794  					Replaces: "b",
  1795  					SourceInfo: &cache.OperatorSourceInfo{
  1796  						Package: "package",
  1797  						Channel: "channel",
  1798  					},
  1799  				},
  1800  				{
  1801  					Name:     "b",
  1802  					Replaces: "a",
  1803  					SourceInfo: &cache.OperatorSourceInfo{
  1804  						Package: "package",
  1805  						Channel: "channel",
  1806  					},
  1807  				},
  1808  			},
  1809  			Err: errors.New(`no channel heads (entries not replaced by another entry) found in channel "channel" of package "package"`),
  1810  		},
  1811  		{
  1812  			Name: "replacement cycle",
  1813  			In: []*cache.Entry{
  1814  				{
  1815  					Name:     "a",
  1816  					Replaces: "b",
  1817  					SourceInfo: &cache.OperatorSourceInfo{
  1818  						Package: "package",
  1819  						Channel: "channel",
  1820  					},
  1821  				},
  1822  				{
  1823  					Name:     "b",
  1824  					Replaces: "c",
  1825  					SourceInfo: &cache.OperatorSourceInfo{
  1826  						Package: "package",
  1827  						Channel: "channel",
  1828  					},
  1829  				},
  1830  				{
  1831  					Name:     "c",
  1832  					Replaces: "b",
  1833  					SourceInfo: &cache.OperatorSourceInfo{
  1834  						Package: "package",
  1835  						Channel: "channel",
  1836  					},
  1837  				},
  1838  			},
  1839  			Err: errors.New(`a cycle exists in the chain of replacement beginning with "a" in channel "channel" of package "package"`),
  1840  		},
  1841  		{
  1842  			Name: "skipped and replaced entry omitted",
  1843  			In: []*cache.Entry{
  1844  				{
  1845  					Name:     "a",
  1846  					Replaces: "b",
  1847  					Skips:    []string{"b"},
  1848  				},
  1849  				{
  1850  					Name: "b",
  1851  				},
  1852  			},
  1853  			Out: []*cache.Entry{
  1854  				{
  1855  					Name:     "a",
  1856  					Replaces: "b",
  1857  					Skips:    []string{"b"},
  1858  				},
  1859  			},
  1860  		},
  1861  		{
  1862  			Name: "skipped entry omitted",
  1863  			In: []*cache.Entry{
  1864  				{
  1865  					Name:     "a",
  1866  					Replaces: "b",
  1867  					Skips:    []string{"c"},
  1868  				},
  1869  				{
  1870  					Name:     "b",
  1871  					Replaces: "c",
  1872  				},
  1873  				{
  1874  					Name: "c",
  1875  				},
  1876  			},
  1877  			Out: []*cache.Entry{
  1878  				{
  1879  					Name:     "a",
  1880  					Replaces: "b",
  1881  					Skips:    []string{"c"},
  1882  				},
  1883  				{
  1884  					Name:     "b",
  1885  					Replaces: "c",
  1886  				},
  1887  			},
  1888  		},
  1889  		{
  1890  			Name: "two replaces chains",
  1891  			In: []*cache.Entry{
  1892  				{
  1893  					Name: "a",
  1894  					SourceInfo: &cache.OperatorSourceInfo{
  1895  						Package: "package",
  1896  						Channel: "channel",
  1897  					},
  1898  				},
  1899  				{
  1900  					Name:     "b",
  1901  					Replaces: "c",
  1902  					SourceInfo: &cache.OperatorSourceInfo{
  1903  						Package: "package",
  1904  						Channel: "channel",
  1905  					},
  1906  				},
  1907  				{
  1908  					Name: "c",
  1909  					SourceInfo: &cache.OperatorSourceInfo{
  1910  						Package: "package",
  1911  						Channel: "channel",
  1912  					},
  1913  				},
  1914  			},
  1915  			Err: errors.New(`a unique replacement chain within a channel is required to determine the relative order between channel entries, but 2 replacement chains were found in channel "channel" of package "package": a, b...c`),
  1916  		},
  1917  	} {
  1918  		t.Run(tc.Name, func(t *testing.T) {
  1919  			assert := assert.New(t)
  1920  			actual, err := sortChannel(tc.In)
  1921  			if tc.Err == nil {
  1922  				assert.NoError(err)
  1923  			} else {
  1924  				assert.EqualError(err, tc.Err.Error())
  1925  			}
  1926  			assert.Equal(tc.Out, actual)
  1927  		})
  1928  	}
  1929  }
  1930  
  1931  func TestSolveOperators_GenericConstraint(t *testing.T) {
  1932  	Provides1 := cache.APISet{opregistry.APIKey{Group: "g", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}}
  1933  	namespace := "olm"
  1934  	catalog := cache.SourceKey{Name: "community", Namespace: namespace}
  1935  
  1936  	deps1 := []*api.Dependency{
  1937  		{
  1938  			Type: "olm.constraint",
  1939  			Value: `{"failureMessage":"gvk-constraint",
  1940  				"cel":{"rule":"properties.exists(p, p.type == 'olm.gvk' && p.value == {'group': 'g', 'version': 'v', 'kind': 'k'})"}}`,
  1941  		},
  1942  	}
  1943  	deps2 := []*api.Dependency{
  1944  		{
  1945  			Type: "olm.constraint",
  1946  			Value: `{"failureMessage":"gvk2-constraint",
  1947  				"cel":{"rule":"properties.exists(p, p.type == 'olm.gvk' && p.value == {'group': 'g2', 'version': 'v', 'kind': 'k'})"}}`,
  1948  		},
  1949  	}
  1950  	deps3 := []*api.Dependency{
  1951  		{
  1952  			Type: "olm.constraint",
  1953  			Value: `{"failureMessage":"package-constraint",
  1954  				"cel":{"rule":"properties.exists(p, p.type == 'olm.package' && p.value.packageName == 'another-package' && (semver_compare(p.value.version, '1.0.1') == 0))"}}`,
  1955  		},
  1956  	}
  1957  
  1958  	tests := []struct {
  1959  		name     string
  1960  		isErr    bool
  1961  		subs     []*v1alpha1.Subscription
  1962  		catalog  cache.Source
  1963  		expected []*cache.Entry
  1964  		message  string
  1965  	}{
  1966  		{
  1967  			// generic constraint for satisfiable gvk dependency
  1968  			name:  "Generic Constraint/Satisfiable GVK Dependency",
  1969  			isErr: false,
  1970  			subs: []*v1alpha1.Subscription{
  1971  				newSub(namespace, "test-package", "stable", catalog),
  1972  			},
  1973  			catalog: &cache.Snapshot{
  1974  				Entries: []*cache.Entry{
  1975  					genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, nil, deps1, "", false),
  1976  					genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "stable", false),
  1977  				},
  1978  			},
  1979  			expected: []*cache.Entry{
  1980  				genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, nil, deps1, "", false),
  1981  				genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "stable", false),
  1982  			},
  1983  		},
  1984  		{
  1985  			// generic constraint for NotSatisfiable gvk dependency
  1986  			name:  "Generic Constraint/NotSatisfiable GVK Dependency",
  1987  			isErr: true,
  1988  			subs: []*v1alpha1.Subscription{
  1989  				newSub(namespace, "test-package", "stable", catalog),
  1990  			},
  1991  			catalog: &cache.Snapshot{
  1992  				Entries: []*cache.Entry{
  1993  					genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, nil, deps2, "", false),
  1994  					genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false),
  1995  				},
  1996  			},
  1997  			// unable to find satisfiable gvk dependency
  1998  			// resolve into nothing
  1999  			expected: nil,
  2000  			message:  "gvk2-constraint",
  2001  		},
  2002  		{
  2003  			// generic constraint for package constraint
  2004  			name:  "Generic Constraint/Satisfiable Package Dependency",
  2005  			isErr: false,
  2006  			subs: []*v1alpha1.Subscription{
  2007  				newSub(namespace, "test-package", "stable", catalog),
  2008  			},
  2009  			catalog: &cache.Snapshot{
  2010  				Entries: []*cache.Entry{
  2011  					genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, nil, deps3, "", false),
  2012  					genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, nil, nil, nil, "", false),
  2013  					genEntry("opB.v1.0.1", "1.0.1", "opB.v1.0.0", "another-package", "stable", catalog.Name, catalog.Namespace, nil, nil, nil, "stable", false),
  2014  					genEntry("opB.v1.0.2", "1.0.2", "opB.v1.0.1", "another-package", "stable", catalog.Name, catalog.Namespace, nil, nil, nil, "stable", false),
  2015  				},
  2016  			},
  2017  			expected: []*cache.Entry{
  2018  				genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, nil, deps3, "", false),
  2019  				genEntry("opB.v1.0.1", "1.0.1", "opB.v1.0.0", "another-package", "stable", catalog.Name, catalog.Namespace, nil, nil, nil, "stable", false),
  2020  			},
  2021  		},
  2022  	}
  2023  
  2024  	for _, tt := range tests {
  2025  		t.Run(tt.name, func(t *testing.T) {
  2026  			resolver := Resolver{
  2027  				cache: cache.New(cache.StaticSourceProvider{
  2028  					catalog: tt.catalog,
  2029  				}),
  2030  				log: logrus.New(),
  2031  				pc: &predicateConverter{
  2032  					celEnv: constraints.NewCelEnvironment(),
  2033  				},
  2034  			}
  2035  
  2036  			operators, err := resolver.Resolve([]string{namespace}, tt.subs)
  2037  			if tt.isErr {
  2038  				assert.Error(t, err)
  2039  				assert.Contains(t, err.Error(), tt.message)
  2040  			} else {
  2041  				assert.NoError(t, err)
  2042  			}
  2043  			assert.ElementsMatch(t, tt.expected, operators)
  2044  		})
  2045  	}
  2046  }