Skip to content

Commit e639717

Browse files
authored
(catalogd) add unit tests for indexing algo for query endpoint (#1702)
Closes #1697 Signed-off-by: Anik Bhattacharjee <[email protected]>
1 parent 38b4795 commit e639717

File tree

2 files changed

+285
-14
lines changed

2 files changed

+285
-14
lines changed

Diff for: catalogd/internal/storage/index.go

-14
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,6 @@ func (s *section) UnmarshalJSON(b []byte) error {
5050
return nil
5151
}
5252

53-
func (i index) Size() int64 {
54-
size := 0
55-
for k, v := range i.BySchema {
56-
size += len(k) + len(v)*16
57-
}
58-
for k, v := range i.ByPackage {
59-
size += len(k) + len(v)*16
60-
}
61-
for k, v := range i.ByName {
62-
size += len(k) + len(v)*16
63-
}
64-
return int64(size)
65-
}
66-
6753
func (i index) Get(r io.ReaderAt, schema, packageName, name string) io.Reader {
6854
sectionSet := i.getSectionSet(schema, packageName, name)
6955

Diff for: catalogd/internal/storage/index_test.go

+285
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
package storage
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"io"
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/operator-framework/operator-registry/alpha/declcfg"
12+
)
13+
14+
func TestIndexCreation(t *testing.T) {
15+
// Create test Meta objects
16+
metas := []*declcfg.Meta{
17+
{
18+
Schema: "olm.package",
19+
Package: "test",
20+
Name: "test-package",
21+
Blob: []byte(`{"test": "data1"}`),
22+
},
23+
{
24+
Schema: "olm.bundle",
25+
Package: "test",
26+
Name: "test-bundle",
27+
Blob: []byte(`{"test": "data2"}`),
28+
},
29+
}
30+
31+
// Create channel and feed Metas
32+
metasChan := make(chan *declcfg.Meta, len(metas))
33+
for _, meta := range metas {
34+
metasChan <- meta
35+
}
36+
close(metasChan)
37+
38+
// Create index
39+
idx := newIndex(metasChan)
40+
41+
// Verify schema index
42+
require.Len(t, idx.BySchema, 2, "Expected 2 schema entries, got %d", len(idx.BySchema))
43+
require.Len(t, idx.BySchema["olm.package"], 1, "Expected 1 olm.package entry, got %d", len(idx.BySchema["olm.package"]))
44+
require.Len(t, idx.BySchema["olm.bundle"], 1, "Expected 1 olm.bundle entry, got %d", len(idx.BySchema["olm.bundle"]))
45+
46+
// Verify package index
47+
require.Len(t, idx.ByPackage["test"], 2, "Expected 2 package entries, got %d", len(idx.ByPackage))
48+
49+
// Verify name index
50+
require.Len(t, idx.ByName["test-package"], 1, "Expected 1 entry for name 'test-package', got %d", len(idx.ByName["test-package"]))
51+
require.Len(t, idx.ByName["test-bundle"], 1, "Expected 1 entry for name 'test-bundle', got %d", len(idx.ByName["test-bundle"]))
52+
}
53+
54+
func TestIndexGet(t *testing.T) {
55+
// Test data structure that represents a catalog
56+
metas := []*declcfg.Meta{
57+
{
58+
// Package definition
59+
Schema: "olm.package",
60+
Name: "test-package",
61+
Blob: createBlob(t, map[string]interface{}{
62+
"schema": "olm.package",
63+
"name": "test-package",
64+
"defaultChannel": "stable-v6.x",
65+
}),
66+
},
67+
{
68+
// First channel (stable-5.x)
69+
Schema: "olm.channel",
70+
Package: "test-package",
71+
Name: "stable-5.x",
72+
Blob: createBlob(t, map[string]interface{}{
73+
"schema": "olm.channel",
74+
"name": "stable-5.x",
75+
"package": "test-package",
76+
"entries": []map[string]interface{}{
77+
{"name": "test-bunble.v5.0.3"},
78+
{"name": "test-bundle.v5.0.4", "replaces": "test-bundle.v5.0.3"},
79+
},
80+
}),
81+
},
82+
{
83+
// Second channel (stable-v6.x)
84+
Schema: "olm.channel",
85+
Package: "test-package",
86+
Name: "stable-v6.x",
87+
Blob: createBlob(t, map[string]interface{}{
88+
"schema": "olm.channel",
89+
"name": "stable-v6.x",
90+
"package": "test-package",
91+
"entries": []map[string]interface{}{
92+
{"name": "test-bundle.v6.0.0", "skipRange": "<6.0.0"},
93+
},
94+
}),
95+
},
96+
{
97+
// Bundle v5.0.3
98+
Schema: "olm.bundle",
99+
Package: "test-package",
100+
Name: "test-bundle.v5.0.3",
101+
Blob: createBlob(t, map[string]interface{}{
102+
"schema": "olm.bundle",
103+
"name": "test-bundle.v5.0.3",
104+
"package": "test-package",
105+
"image": "test-image@sha256:a5d4f",
106+
"properties": []map[string]interface{}{
107+
{
108+
"type": "olm.package",
109+
"value": map[string]interface{}{
110+
"packageName": "test-package",
111+
"version": "5.0.3",
112+
},
113+
},
114+
},
115+
}),
116+
},
117+
{
118+
// Bundle v5.0.4
119+
Schema: "olm.bundle",
120+
Package: "test-package",
121+
Name: "test-bundle.v5.0.4",
122+
Blob: createBlob(t, map[string]interface{}{
123+
"schema": "olm.bundle",
124+
"name": "test-bundle.v5.0.4",
125+
"package": "test-package",
126+
"image": "test-image@sha256:f4233",
127+
"properties": []map[string]interface{}{
128+
{
129+
"type": "olm.package",
130+
"value": map[string]interface{}{
131+
"packageName": "test-package",
132+
"version": "5.0.4",
133+
},
134+
},
135+
},
136+
}),
137+
},
138+
{
139+
// Bundle v6.0.0
140+
Schema: "olm.bundle",
141+
Package: "test-package",
142+
Name: "test-bundle.v6.0.0",
143+
Blob: createBlob(t, map[string]interface{}{
144+
"schema": "olm.bundle",
145+
"name": "test-bundle.v6.0.0",
146+
"package": "test-package",
147+
"image": "test-image@sha256:d3016b",
148+
"properties": []map[string]interface{}{
149+
{
150+
"type": "olm.package",
151+
"value": map[string]interface{}{
152+
"packageName": "test-package",
153+
"version": "6.0.0",
154+
},
155+
},
156+
},
157+
}),
158+
},
159+
}
160+
161+
// Create and populate the index
162+
metasChan := make(chan *declcfg.Meta, len(metas))
163+
for _, meta := range metas {
164+
metasChan <- meta
165+
}
166+
close(metasChan)
167+
168+
idx := newIndex(metasChan)
169+
170+
// Create a reader from the metas
171+
var combinedBlob bytes.Buffer
172+
for _, meta := range metas {
173+
combinedBlob.Write(meta.Blob)
174+
}
175+
fullData := bytes.NewReader(combinedBlob.Bytes())
176+
177+
tests := []struct {
178+
name string
179+
schema string
180+
packageName string
181+
blobName string
182+
wantCount int
183+
validate func(t *testing.T, entry map[string]interface{})
184+
}{
185+
{
186+
name: "filter by schema - olm.package",
187+
schema: "olm.package",
188+
wantCount: 1,
189+
validate: func(t *testing.T, entry map[string]interface{}) {
190+
if entry["schema"] != "olm.package" {
191+
t.Errorf("Expected olm.package schema blob got %v", entry["schema"])
192+
}
193+
},
194+
},
195+
{
196+
name: "filter by schema - olm.channel",
197+
schema: "olm.channel",
198+
wantCount: 2,
199+
validate: func(t *testing.T, entry map[string]interface{}) {
200+
if entry["schema"] != "olm.channel" {
201+
t.Errorf("Expected olm.channel schema blob got %v", entry["schema"])
202+
}
203+
},
204+
},
205+
{
206+
name: "filter by schema - olm.bundle",
207+
schema: "olm.bundle",
208+
wantCount: 3,
209+
validate: func(t *testing.T, entry map[string]interface{}) {
210+
if entry["schema"] != "olm.bundle" {
211+
t.Errorf("Expected olm.bundle schema blob got %v", entry["schema"])
212+
}
213+
},
214+
},
215+
{
216+
name: "filter by package",
217+
packageName: "test-package",
218+
wantCount: 5,
219+
validate: func(t *testing.T, entry map[string]interface{}) {
220+
if entry["package"] != "test-package" {
221+
t.Errorf("Expected blobs with package name test-package, got blob with package name %v", entry["package"])
222+
}
223+
},
224+
},
225+
{
226+
name: "filter by specific bundle name",
227+
blobName: "test-bundle.v5.0.3",
228+
wantCount: 1,
229+
validate: func(t *testing.T, entry map[string]interface{}) {
230+
if entry["schema"] != "olm.bundle" && entry["name"] != "test-bundle.v5.0.3" {
231+
t.Errorf("Expected blob with schema=olm.bundle and name=test-bundle.v5.0.3, got %v", entry)
232+
}
233+
},
234+
},
235+
{
236+
name: "filter by schema and package",
237+
schema: "olm.bundle",
238+
packageName: "test-package",
239+
wantCount: 3,
240+
validate: func(t *testing.T, entry map[string]interface{}) {
241+
if entry["schema"] != "olm.bundle" && entry["package"] != "test-package" {
242+
t.Errorf("Expected blob with schema=olm.bundle and package=test-package, got %v", entry)
243+
}
244+
},
245+
},
246+
{
247+
name: "no matches",
248+
schema: "non.existent",
249+
packageName: "not-found",
250+
wantCount: 0,
251+
},
252+
}
253+
254+
for _, tt := range tests {
255+
t.Run(tt.name, func(t *testing.T) {
256+
reader := idx.Get(fullData, tt.schema, tt.packageName, tt.blobName)
257+
content, err := io.ReadAll(reader)
258+
require.NoError(t, err, "Failed to read content: %v", err)
259+
260+
var count int
261+
decoder := json.NewDecoder(bytes.NewReader(content))
262+
for decoder.More() {
263+
var entry map[string]interface{}
264+
err := decoder.Decode(&entry)
265+
require.NoError(t, err, "Failed to decode result: %v", err)
266+
count++
267+
268+
if tt.validate != nil {
269+
tt.validate(t, entry)
270+
}
271+
}
272+
273+
require.Equal(t, tt.wantCount, count, "Got %d entries, want %d", count, tt.wantCount)
274+
})
275+
}
276+
}
277+
278+
// createBlob is a helper function that creates a JSON blob with a trailing newline
279+
func createBlob(t *testing.T, data map[string]interface{}) []byte {
280+
blob, err := json.Marshal(data)
281+
if err != nil {
282+
t.Fatalf("Failed to create blob: %v", err)
283+
}
284+
return append(blob, '\n')
285+
}

0 commit comments

Comments
 (0)