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 { searchLabel string eq bool searchValue string } 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{ searchLabel: "category", searchValue: "tech", eq: 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{ searchLabel: "invalid", searchValue: "value", eq: 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{ searchLabel: "category", searchValue: "invalid", eq: 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{ searchLabel: "category", searchValue: "tech", eq: 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{ searchLabel: "invalid", searchValue: "value", eq: 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.searchLabel, tt.WhenDetail.eq, tt.WhenDetail.searchValue) // 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)) }) } }