Files
zenfeed/pkg/storage/feed/block/index/inverted/inverted_test.go
2025-06-05 23:29:37 +08:00

336 lines
8.4 KiB
Go

package inverted
import (
"bytes"
"context"
"testing"
. "github.com/onsi/gomega"
"github.com/glidea/zenfeed/pkg/model"
"github.com/glidea/zenfeed/pkg/test"
)
func TestAdd(t *testing.T) {
RegisterTestingT(t)
type givenDetail struct {
existingLabels map[uint64]model.Labels
}
type whenDetail struct {
id uint64
labels model.Labels
}
type thenExpected struct {
indexState map[string]map[string]map[uint64]struct{}
}
tests := []test.Case[givenDetail, whenDetail, thenExpected]{
{
Scenario: "Add Single Label",
Given: "An empty index",
When: "Adding an item with a single label",
Then: "Should index the item correctly",
GivenDetail: givenDetail{
existingLabels: map[uint64]model.Labels{},
},
WhenDetail: whenDetail{
id: 1,
labels: model.Labels{
{Key: "category", Value: "tech"},
},
},
ThenExpected: thenExpected{
indexState: map[string]map[string]map[uint64]struct{}{
"category": {
"tech": {1: struct{}{}},
},
},
},
},
{
Scenario: "Add Multiple Labels",
Given: "An empty index",
When: "Adding an item with multiple labels",
Then: "Should index all labels correctly",
GivenDetail: givenDetail{
existingLabels: map[uint64]model.Labels{
1: {model.Label{Key: "category", Value: "tech"}},
3: {model.Label{Key: "category", Value: "news"}},
},
},
WhenDetail: whenDetail{
id: 2,
labels: model.Labels{
{Key: "category", Value: "tech"},
{Key: "status", Value: "new"},
{Key: "author", Value: "john"},
},
},
ThenExpected: thenExpected{
indexState: map[string]map[string]map[uint64]struct{}{
"category": {
"tech": {1: struct{}{}, 2: struct{}{}},
"news": {3: struct{}{}},
},
"status": {
"new": {2: struct{}{}},
},
"author": {
"john": {2: struct{}{}},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.Scenario, func(t *testing.T) {
// Given.
idx0, err := NewFactory().New("test", &Config{}, Dependencies{})
Expect(err).NotTo(HaveOccurred())
for id, labels := range tt.GivenDetail.existingLabels {
idx0.Add(context.Background(), id, labels)
}
// When.
idx0.Add(context.Background(), tt.WhenDetail.id, tt.WhenDetail.labels)
// Then.
invIdx := idx0.(*idx)
for label, values := range tt.ThenExpected.indexState {
Expect(invIdx.m).To(HaveKey(label))
for value, ids := range values {
Expect(invIdx.m[label]).To(HaveKey(value))
for id := range ids {
Expect(invIdx.m[label][value]).To(HaveKey(id))
}
}
}
})
}
}
func TestSearch(t *testing.T) {
RegisterTestingT(t)
type givenDetail struct {
setupLabels map[uint64]model.Labels
}
type whenDetail struct {
matcher model.LabelFilter
}
type thenExpected struct {
want []uint64
}
tests := []test.Case[givenDetail, whenDetail, thenExpected]{
{
Scenario: "Search Existing Label-Value",
Given: "An index with feeds",
When: "Searching for existing label and value",
Then: "Should return matching item IDs",
GivenDetail: givenDetail{
setupLabels: map[uint64]model.Labels{
1: {model.Label{Key: "category", Value: "tech"}},
2: {model.Label{Key: "category", Value: "tech"}},
3: {model.Label{Key: "category", Value: "news"}},
},
},
WhenDetail: whenDetail{
matcher: model.LabelFilter{
Label: "category",
Value: "tech",
Equal: true,
},
},
ThenExpected: thenExpected{
want: []uint64{1, 2},
},
},
{
Scenario: "Search Non-Existing Label",
Given: "An index with feeds",
When: "Searching for non-existing label",
Then: "Should return empty result",
GivenDetail: givenDetail{
setupLabels: map[uint64]model.Labels{
1: {model.Label{Key: "category", Value: "tech"}},
},
},
WhenDetail: whenDetail{
matcher: model.LabelFilter{
Label: "invalid",
Value: "value",
Equal: true,
},
},
ThenExpected: thenExpected{
want: nil,
},
},
{
Scenario: "Search Non-Existing Value",
Given: "An index with feeds",
When: "Searching for existing label but non-existing value",
Then: "Should return empty result",
GivenDetail: givenDetail{
setupLabels: map[uint64]model.Labels{
1: {model.Label{Key: "category", Value: "tech"}},
},
},
WhenDetail: whenDetail{
matcher: model.LabelFilter{
Label: "category",
Value: "invalid",
Equal: true,
},
},
ThenExpected: thenExpected{
want: nil,
},
},
// Not equal tests.
{
Scenario: "Search Not Matching Label-Value",
Given: "An index with multiple feeds",
When: "Searching for feeds not matching a label-value pair",
Then: "Should return all feeds except those matching the pair",
GivenDetail: givenDetail{
setupLabels: map[uint64]model.Labels{
1: {model.Label{Key: "category", Value: "tech"}, model.Label{Key: "status", Value: "new"}},
2: {model.Label{Key: "category", Value: "news"}, model.Label{Key: "status", Value: "old"}},
3: {model.Label{Key: "category", Value: "tech"}, model.Label{Key: "status", Value: "old"}},
},
},
WhenDetail: whenDetail{
matcher: model.LabelFilter{
Label: "category",
Value: "tech",
Equal: false,
},
},
ThenExpected: thenExpected{
want: []uint64{2},
},
},
{
Scenario: "Search Not Matching Non-Existing Label",
Given: "An index with feeds",
When: "Searching for feeds not matching a non-existing label",
Then: "Should return all feeds",
GivenDetail: givenDetail{
setupLabels: map[uint64]model.Labels{
1: {model.Label{Key: "category", Value: "tech"}},
2: {model.Label{Key: "category", Value: "news"}},
},
},
WhenDetail: whenDetail{
matcher: model.LabelFilter{
Label: "invalid",
Value: "value",
Equal: false,
},
},
ThenExpected: thenExpected{
want: []uint64{1, 2},
},
},
}
for _, tt := range tests {
t.Run(tt.Scenario, func(t *testing.T) {
// Given.
idx, err := NewFactory().New("test", &Config{}, Dependencies{})
Expect(err).NotTo(HaveOccurred())
for id, labels := range tt.GivenDetail.setupLabels {
idx.Add(context.Background(), id, labels)
}
// When.
result := idx.Search(context.Background(), tt.WhenDetail.matcher)
// Then.
if tt.ThenExpected.want == nil {
Expect(result).To(BeEmpty())
} else {
resultIDs := make([]uint64, 0, len(result))
for id := range result {
resultIDs = append(resultIDs, id)
}
Expect(resultIDs).To(ConsistOf(tt.ThenExpected.want))
}
})
}
}
func TestEncodeDecode(t *testing.T) {
RegisterTestingT(t)
type givenDetail struct {
setupLabels map[uint64]model.Labels
}
type whenDetail struct{}
type thenExpected struct {
success bool
}
tests := []test.Case[givenDetail, whenDetail, thenExpected]{
{
Scenario: "Encode and Decode Empty Index",
Given: "An empty index",
When: "Encoding and decoding",
Then: "Should restore empty index correctly",
GivenDetail: givenDetail{
setupLabels: map[uint64]model.Labels{},
},
WhenDetail: whenDetail{},
ThenExpected: thenExpected{
success: true,
},
},
{
Scenario: "Encode and Decode Index with Data",
Given: "An index with feeds",
When: "Encoding and decoding",
Then: "Should restore all data correctly",
GivenDetail: givenDetail{
setupLabels: map[uint64]model.Labels{
1: {model.Label{Key: "category", Value: "tech"}, model.Label{Key: "status", Value: "new"}},
2: {model.Label{Key: "category", Value: "news"}, model.Label{Key: "author", Value: "john"}},
},
},
WhenDetail: whenDetail{},
ThenExpected: thenExpected{
success: true,
},
},
}
for _, tt := range tests {
t.Run(tt.Scenario, func(t *testing.T) {
// Given.
original, err := NewFactory().New("test", &Config{}, Dependencies{})
Expect(err).NotTo(HaveOccurred())
for id, labels := range tt.GivenDetail.setupLabels {
original.Add(context.Background(), id, labels)
}
// When.
var buf bytes.Buffer
err = original.EncodeTo(context.Background(), &buf)
Expect(err).NotTo(HaveOccurred())
decoded, err := NewFactory().New("test", &Config{}, Dependencies{})
Expect(err).NotTo(HaveOccurred())
err = decoded.DecodeFrom(context.Background(), &buf)
Expect(err).NotTo(HaveOccurred())
// Then.
origIdx := original.(*idx)
decodedIdx := decoded.(*idx)
Expect(decodedIdx.m).To(Equal(origIdx.m))
})
}
}