336 lines
8.4 KiB
Go
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))
|
|
})
|
|
}
|
|
}
|