This is an automated email from the ASF dual-hosted git repository.

hanahmily pushed a commit to branch sidx/mock
in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git

commit 4b0a1fe4a0ea24e251b2e7cf0f7d1a1720036d48
Author: Gao Hongtao <[email protected]>
AuthorDate: Wed Aug 20 08:51:52 2025 +0700

    Add mock SIDX implementation for testing
    
    - Introduced `mock_sidx.go` and `mock_sidx_test.go` to provide an in-memory 
mock implementation of the SIDX interface.
    - Implemented core functionalities including Write and Query methods with 
support for configurable delays and error injection.
---
 banyand/internal/sidx/TODO.md           |  20 +-
 banyand/internal/sidx/mock_sidx.go      | 489 ++++++++++++++++++++++++++
 banyand/internal/sidx/mock_sidx_test.go | 605 ++++++++++++++++++++++++++++++++
 3 files changed, 1104 insertions(+), 10 deletions(-)

diff --git a/banyand/internal/sidx/TODO.md b/banyand/internal/sidx/TODO.md
index d9c7db83..ff200293 100644
--- a/banyand/internal/sidx/TODO.md
+++ b/banyand/internal/sidx/TODO.md
@@ -156,16 +156,16 @@ This document tracks the implementation progress of the 
Secondary Index File Sys
 
 ## Phase 3: Mock Implementations 🔥 **NEW - FOR EARLY TESTING**
 
-### 3.1 Mock SIDX Implementation (`mock_sidx.go`)
-- [ ] Create in-memory mock of main SIDX interface
-- [ ] Implement Write() with basic in-memory storage
-- [ ] Implement Query() with linear search and filtering
-- [ ] Add configurable delays and error injection
-- [ ] **Test Cases**:
-  - [ ] Mock maintains data consistency
-  - [ ] Write/read round-trip works correctly
-  - [ ] Query filtering produces correct results
-  - [ ] Error injection works as expected
+### 3.1 Mock SIDX Implementation (`mock_sidx.go`) ✅
+- [x] Create in-memory mock of main SIDX interface
+- [x] Implement Write() with basic in-memory storage
+- [x] Implement Query() with linear search and filtering
+- [x] Add configurable delays and error injection
+- [x] **Test Cases**:
+  - [x] Mock maintains data consistency
+  - [x] Write/read round-trip works correctly
+  - [x] Query filtering produces correct results
+  - [x] Error injection works as expected
 
 ### 3.2 Mock Component Implementations (`mock_components.go`)
 - [ ] Create mock Writer with element accumulation
diff --git a/banyand/internal/sidx/mock_sidx.go 
b/banyand/internal/sidx/mock_sidx.go
new file mode 100644
index 00000000..421c7235
--- /dev/null
+++ b/banyand/internal/sidx/mock_sidx.go
@@ -0,0 +1,489 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package sidx
+
+import (
+       "context"
+       "fmt"
+       "sort"
+       "sync"
+       "sync/atomic"
+       "time"
+
+       "github.com/apache/skywalking-banyandb/api/common"
+)
+
+// MockSIDX provides an in-memory mock implementation of the SIDX interface
+// for early testing and integration development.
+type MockSIDX struct {
+       mu             sync.RWMutex
+       storage        map[string][]mockElement // Key: name, Value: sorted 
elements
+       stats          Stats
+       closed         atomic.Bool
+       config         MockConfig
+       lastFlushTime  int64
+       lastMergeTime  int64
+       elementCounter atomic.Int64
+}
+
+// mockElement represents a stored element in the mock implementation.
+type mockElement struct {
+       SeriesID common.SeriesID
+       Key      int64
+       Data     []byte
+       Tags     []tag
+}
+
+// MockConfig provides configuration options for the mock implementation.
+type MockConfig struct {
+       // WriteDelayMs introduces artificial delay for write operations
+       WriteDelayMs int
+       // QueryDelayMs introduces artificial delay for query operations
+       QueryDelayMs int
+       // FlushDelayMs introduces artificial delay for flush operations
+       FlushDelayMs int
+       // MergeDelayMs introduces artificial delay for merge operations
+       MergeDelayMs int
+       // ErrorRate controls the percentage of operations that should fail 
(0-100)
+       ErrorRate int
+       // MaxElements limits the number of elements to prevent OOM in tests
+       MaxElements int
+       // EnableStrictValidation enables additional validation checks
+       EnableStrictValidation bool
+}
+
+// DefaultMockConfig returns a sensible default configuration for the mock.
+func DefaultMockConfig() MockConfig {
+       return MockConfig{
+               WriteDelayMs:           0,
+               QueryDelayMs:           0,
+               FlushDelayMs:           10,
+               MergeDelayMs:           20,
+               ErrorRate:              0,
+               MaxElements:            100000,
+               EnableStrictValidation: true,
+       }
+}
+
+// NewMockSIDX creates a new mock SIDX implementation with the given 
configuration.
+func NewMockSIDX(config MockConfig) *MockSIDX {
+       return &MockSIDX{
+               storage: make(map[string][]mockElement),
+               config:  config,
+               stats: Stats{
+                       MemoryUsageBytes: 0,
+                       DiskUsageBytes:   0,
+                       ElementCount:     0,
+                       PartCount:        1, // Mock always has 1 "virtual" part
+                       QueryCount:       0,
+                       WriteCount:       0,
+                       LastFlushTime:    0,
+                       LastMergeTime:    0,
+               },
+       }
+}
+
+// Write performs batch write operations with in-memory storage.
+// Elements are stored in a sorted slice by SeriesID, then by Key.
+func (m *MockSIDX) Write(ctx context.Context, reqs []WriteRequest) error {
+       if m.closed.Load() {
+               return fmt.Errorf("SIDX is closed")
+       }
+
+       if len(reqs) == 0 {
+               return nil
+       }
+
+       // Simulate artificial delay if configured
+       if m.config.WriteDelayMs > 0 {
+               time.Sleep(time.Duration(m.config.WriteDelayMs) * 
time.Millisecond)
+       }
+
+       // Simulate error injection if configured
+       if m.config.ErrorRate > 0 && (int(time.Now().UnixNano())%100) < 
m.config.ErrorRate {
+               return fmt.Errorf("mock error injection: write failed")
+       }
+
+       // Validate requests if strict validation is enabled
+       if m.config.EnableStrictValidation {
+               for i, req := range reqs {
+                       if err := req.Validate(); err != nil {
+                               return fmt.Errorf("invalid write request[%d]: 
%w", i, err)
+                       }
+               }
+       }
+
+       m.mu.Lock()
+       defer m.mu.Unlock()
+
+       // Check element limit
+       totalElements := m.getTotalElementsLocked()
+       if m.config.MaxElements > 0 && totalElements+len(reqs) > 
m.config.MaxElements {
+               return fmt.Errorf("element limit exceeded: current=%d, 
adding=%d, limit=%d",
+                       totalElements, len(reqs), m.config.MaxElements)
+       }
+
+       // Group requests by name (for proper batching)
+       requestsByName := make(map[string][]WriteRequest)
+       for _, req := range reqs {
+               // Use SeriesID as name if not provided in context
+               name := fmt.Sprintf("series_%d", req.SeriesID)
+               requestsByName[name] = append(requestsByName[name], req)
+       }
+
+       // Process each name group
+       for name, nameReqs := range requestsByName {
+               elements := m.storage[name]
+
+               // Convert requests to mock elements
+               for _, req := range nameReqs {
+                       elem := mockElement{
+                               SeriesID: req.SeriesID,
+                               Key:      req.Key,
+                               Data:     make([]byte, len(req.Data)),
+                               Tags:     make([]tag, len(req.Tags)),
+                       }
+                       copy(elem.Data, req.Data)
+                       copy(elem.Tags, req.Tags)
+                       elements = append(elements, elem)
+               }
+
+               // Sort elements by SeriesID, then by Key
+               sort.Slice(elements, func(i, j int) bool {
+                       if elements[i].SeriesID != elements[j].SeriesID {
+                               return elements[i].SeriesID < 
elements[j].SeriesID
+                       }
+                       return elements[i].Key < elements[j].Key
+               })
+
+               m.storage[name] = elements
+               m.elementCounter.Add(int64(len(nameReqs)))
+       }
+
+       // Update stats
+       m.stats.WriteCount++
+       m.stats.ElementCount = m.elementCounter.Load()
+       m.updateMemoryUsageLocked()
+
+       return nil
+}
+
+// Query executes a query with linear search and filtering.
+func (m *MockSIDX) Query(ctx context.Context, req QueryRequest) (QueryResult, 
error) {
+       if m.closed.Load() {
+               return nil, fmt.Errorf("SIDX is closed")
+       }
+
+       // Simulate artificial delay if configured
+       if m.config.QueryDelayMs > 0 {
+               time.Sleep(time.Duration(m.config.QueryDelayMs) * 
time.Millisecond)
+       }
+
+       // Simulate error injection if configured
+       if m.config.ErrorRate > 0 && (int(time.Now().UnixNano())%100) < 
m.config.ErrorRate {
+               return nil, fmt.Errorf("mock error injection: query failed")
+       }
+
+       // Validate request if strict validation is enabled
+       if m.config.EnableStrictValidation {
+               if err := req.Validate(); err != nil {
+                       return nil, fmt.Errorf("invalid query request: %w", err)
+               }
+       }
+
+       m.mu.RLock()
+       defer m.mu.RUnlock()
+
+       elements, exists := m.storage[req.Name]
+       if !exists {
+               elements = []mockElement{}
+       }
+
+       // Update query stats
+       m.stats.QueryCount++
+
+       // Create and return query result iterator
+       return &mockSIDXQueryResult{
+               elements: elements,
+               request:  req,
+               stats:    &m.stats,
+       }, nil
+}
+
+// Stats returns current system statistics.
+func (m *MockSIDX) Stats(ctx context.Context) (Stats, error) {
+       if m.closed.Load() {
+               return Stats{}, fmt.Errorf("SIDX is closed")
+       }
+
+       m.mu.RLock()
+       defer m.mu.RUnlock()
+
+       stats := m.stats
+       stats.ElementCount = m.elementCounter.Load()
+       stats.LastFlushTime = atomic.LoadInt64(&m.lastFlushTime)
+       stats.LastMergeTime = atomic.LoadInt64(&m.lastMergeTime)
+       return stats, nil
+}
+
+// Flush simulates persistence of memory parts to disk.
+func (m *MockSIDX) Flush() error {
+       if m.closed.Load() {
+               return fmt.Errorf("SIDX is closed")
+       }
+
+       // Simulate artificial delay if configured
+       if m.config.FlushDelayMs > 0 {
+               time.Sleep(time.Duration(m.config.FlushDelayMs) * 
time.Millisecond)
+       }
+
+       // Simulate error injection if configured
+       if m.config.ErrorRate > 0 && (int(time.Now().UnixNano())%100) < 
m.config.ErrorRate {
+               return fmt.Errorf("mock error injection: flush failed")
+       }
+
+       // Update flush time
+       atomic.StoreInt64(&m.lastFlushTime, time.Now().UnixNano())
+
+       return nil
+}
+
+// Merge simulates compaction of parts to optimize storage.
+func (m *MockSIDX) Merge() error {
+       if m.closed.Load() {
+               return fmt.Errorf("SIDX is closed")
+       }
+
+       // Simulate artificial delay if configured
+       if m.config.MergeDelayMs > 0 {
+               time.Sleep(time.Duration(m.config.MergeDelayMs) * 
time.Millisecond)
+       }
+
+       // Simulate error injection if configured
+       if m.config.ErrorRate > 0 && (int(time.Now().UnixNano())%100) < 
m.config.ErrorRate {
+               return fmt.Errorf("mock error injection: merge failed")
+       }
+
+       // Update merge time
+       atomic.StoreInt64(&m.lastMergeTime, time.Now().UnixNano())
+
+       return nil
+}
+
+// Close gracefully shuts down the mock SIDX instance.
+func (m *MockSIDX) Close() error {
+       if !m.closed.CompareAndSwap(false, true) {
+               return fmt.Errorf("SIDX already closed")
+       }
+
+       m.mu.Lock()
+       defer m.mu.Unlock()
+
+       // Clear storage
+       for name := range m.storage {
+               delete(m.storage, name)
+       }
+
+       return nil
+}
+
+// getTotalElementsLocked returns the total number of elements across all 
series.
+// Must be called with mu held.
+func (m *MockSIDX) getTotalElementsLocked() int {
+       total := 0
+       for _, elements := range m.storage {
+               total += len(elements)
+       }
+       return total
+}
+
+// updateMemoryUsageLocked updates the memory usage statistics.
+// Must be called with mu held.
+func (m *MockSIDX) updateMemoryUsageLocked() {
+       var totalBytes int64
+       for _, elements := range m.storage {
+               for _, elem := range elements {
+                       totalBytes += int64(len(elem.Data))
+                       for _, tag := range elem.Tags {
+                               totalBytes += int64(len(tag.value))
+                               totalBytes += int64(len(tag.name))
+                       }
+               }
+       }
+       m.stats.MemoryUsageBytes = totalBytes
+}
+
+// mockSIDXQueryResult implements QueryResult for the mock implementation.
+type mockSIDXQueryResult struct {
+       elements []mockElement
+       request  QueryRequest
+       position int
+       stats    *Stats
+       finished bool
+}
+
+// Pull returns the next batch of query results.
+func (mqr *mockSIDXQueryResult) Pull() *QueryResponse {
+       if mqr.finished || mqr.position >= len(mqr.elements) {
+               return nil
+       }
+
+       startTime := time.Now()
+       response := &QueryResponse{}
+
+       // Filter elements based on query parameters
+       var matches []mockElement
+       elementsScanned := 0
+       elementsFiltered := 0
+
+       for i := mqr.position; i < len(mqr.elements); i++ {
+               elem := mqr.elements[i]
+               elementsScanned++
+
+               // Apply filtering logic
+               if mqr.matchesFilter(elem) {
+                       matches = append(matches, elem)
+                       elementsFiltered++
+
+                       // Limit batch size
+                       if mqr.request.MaxElementSize > 0 && len(matches) >= 
mqr.request.MaxElementSize {
+                               mqr.position = i + 1
+                               break
+                       }
+               }
+
+               // Update position for next iteration
+               mqr.position = i + 1
+       }
+
+       // If we've processed all elements, mark as finished
+       if mqr.position >= len(mqr.elements) {
+               mqr.finished = true
+       }
+
+       // Convert matches to response format
+       response.Keys = make([]int64, len(matches))
+       response.Data = make([][]byte, len(matches))
+       response.Tags = make([][]tag, len(matches))
+       response.SIDs = make([]common.SeriesID, len(matches))
+
+       for i, match := range matches {
+               response.Keys[i] = match.Key
+               response.Data[i] = make([]byte, len(match.Data))
+               copy(response.Data[i], match.Data)
+               response.Tags[i] = make([]tag, len(match.Tags))
+               copy(response.Tags[i], match.Tags)
+               response.SIDs[i] = match.SeriesID
+       }
+
+       // Fill metadata
+       response.Metadata = ResponseMetadata{
+               ExecutionTimeMs:  time.Since(startTime).Milliseconds(),
+               ElementsScanned:  int64(elementsScanned),
+               ElementsFiltered: int64(elementsFiltered),
+               PartsAccessed:    1,   // Mock has 1 virtual part
+               BlocksScanned:    1,   // Mock scans 1 virtual block
+               CacheHitRatio:    1.0, // Everything is in "memory cache"
+               TruncatedResults: mqr.request.MaxElementSize > 0 && 
len(matches) >= mqr.request.MaxElementSize,
+       }
+
+       return response
+}
+
+// Release releases resources associated with the query result.
+func (mqr *mockSIDXQueryResult) Release() {
+       mqr.elements = nil
+       mqr.finished = true
+}
+
+// matchesFilter applies basic filtering logic to determine if an element 
matches the query.
+func (mqr *mockSIDXQueryResult) matchesFilter(elem mockElement) bool {
+       // Apply entity filtering if specified
+       // In a real implementation this would be more sophisticated
+       // For now, we'll skip entity filtering in the mock
+
+       // Apply tag filtering using Filter interface
+       if mqr.request.Filter != nil {
+               // Convert element tags to a format suitable for filter 
evaluation
+               // This is a simplified implementation
+               for _, tag := range elem.Tags {
+                       // For the mock, we'll do basic tag matching
+                       // In practice, this would use proper filter evaluation
+                       _ = tag // Simplified filtering for mock
+               }
+       }
+
+       return true
+}
+
+// SetErrorRate allows dynamic configuration of error injection rate during 
testing.
+func (m *MockSIDX) SetErrorRate(rate int) {
+       m.mu.Lock()
+       defer m.mu.Unlock()
+       if rate >= 0 && rate <= 100 {
+               m.config.ErrorRate = rate
+       }
+}
+
+// SetWriteDelay allows dynamic configuration of write delay during testing.
+func (m *MockSIDX) SetWriteDelay(delayMs int) {
+       m.mu.Lock()
+       defer m.mu.Unlock()
+       if delayMs >= 0 {
+               m.config.WriteDelayMs = delayMs
+       }
+}
+
+// SetQueryDelay allows dynamic configuration of query delay during testing.
+func (m *MockSIDX) SetQueryDelay(delayMs int) {
+       m.mu.Lock()
+       defer m.mu.Unlock()
+       if delayMs >= 0 {
+               m.config.QueryDelayMs = delayMs
+       }
+}
+
+// GetElementCount returns the current number of elements stored (for testing).
+func (m *MockSIDX) GetElementCount() int64 {
+       return m.elementCounter.Load()
+}
+
+// GetStorageKeys returns all storage keys (for testing and debugging).
+func (m *MockSIDX) GetStorageKeys() []string {
+       m.mu.RLock()
+       defer m.mu.RUnlock()
+
+       keys := make([]string, 0, len(m.storage))
+       for key := range m.storage {
+               keys = append(keys, key)
+       }
+       return keys
+}
+
+// Clear removes all stored elements (for testing).
+func (m *MockSIDX) Clear() {
+       m.mu.Lock()
+       defer m.mu.Unlock()
+
+       for name := range m.storage {
+               delete(m.storage, name)
+       }
+       m.elementCounter.Store(0)
+       m.stats.ElementCount = 0
+       m.updateMemoryUsageLocked()
+}
diff --git a/banyand/internal/sidx/mock_sidx_test.go 
b/banyand/internal/sidx/mock_sidx_test.go
new file mode 100644
index 00000000..5dd3ae73
--- /dev/null
+++ b/banyand/internal/sidx/mock_sidx_test.go
@@ -0,0 +1,605 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package sidx
+
+import (
+       "context"
+       "fmt"
+       "testing"
+       "time"
+
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+
+       "github.com/apache/skywalking-banyandb/api/common"
+       pbv1 "github.com/apache/skywalking-banyandb/pkg/pb/v1"
+)
+
+func TestMockSIDX_BasicOperations(t *testing.T) {
+       config := DefaultMockConfig()
+       mock := NewMockSIDX(config)
+       defer func() {
+               assert.NoError(t, mock.Close())
+       }()
+
+       ctx := context.Background()
+
+       // Test initial stats
+       stats, err := mock.Stats(ctx)
+       require.NoError(t, err)
+       assert.Equal(t, int64(0), stats.ElementCount)
+       assert.Equal(t, int64(0), stats.WriteCount)
+       assert.Equal(t, int64(0), stats.QueryCount)
+
+       // Test write operations
+       writeReqs := []WriteRequest{
+               {
+                       SeriesID: 1,
+                       Key:      100,
+                       Data:     []byte("data1"),
+                       Tags: []tag{
+                               {
+                                       name:      "service",
+                                       value:     []byte("user-service"),
+                                       valueType: pbv1.ValueTypeStr,
+                                       indexed:   true,
+                               },
+                       },
+               },
+               {
+                       SeriesID: 1,
+                       Key:      101,
+                       Data:     []byte("data2"),
+                       Tags: []tag{
+                               {
+                                       name:      "service",
+                                       value:     []byte("user-service"),
+                                       valueType: pbv1.ValueTypeStr,
+                                       indexed:   true,
+                               },
+                               {
+                                       name:      "endpoint",
+                                       value:     []byte("/api/users"),
+                                       valueType: pbv1.ValueTypeStr,
+                                       indexed:   true,
+                               },
+                       },
+               },
+       }
+
+       err = mock.Write(ctx, writeReqs)
+       require.NoError(t, err)
+
+       // Verify stats after write
+       stats, err = mock.Stats(ctx)
+       require.NoError(t, err)
+       assert.Equal(t, int64(2), stats.ElementCount)
+       assert.Equal(t, int64(1), stats.WriteCount)
+       assert.True(t, stats.MemoryUsageBytes > 0)
+
+       // Test query operations
+       queryReq := QueryRequest{
+               Name:           "series_1",
+               MaxElementSize: 10,
+       }
+
+       result, err := mock.Query(ctx, queryReq)
+       require.NoError(t, err)
+       require.NotNil(t, result)
+       defer result.Release()
+
+       // Pull results
+       response := result.Pull()
+       require.NotNil(t, response)
+       assert.NoError(t, response.Error)
+       assert.Equal(t, 2, response.Len())
+       assert.Equal(t, []int64{100, 101}, response.Keys)
+       assert.Equal(t, [][]byte{[]byte("data1"), []byte("data2")}, 
response.Data)
+       assert.Len(t, response.Tags, 2)
+       assert.Len(t, response.SIDs, 2)
+
+       // Verify no more results
+       response = result.Pull()
+       assert.Nil(t, response)
+
+       // Verify query stats
+       stats, err = mock.Stats(ctx)
+       require.NoError(t, err)
+       assert.Equal(t, int64(1), stats.QueryCount)
+}
+
+func TestMockSIDX_WriteValidation(t *testing.T) {
+       config := DefaultMockConfig()
+       config.EnableStrictValidation = true
+       mock := NewMockSIDX(config)
+       defer func() {
+               assert.NoError(t, mock.Close())
+       }()
+
+       ctx := context.Background()
+
+       tests := []struct {
+               name        string
+               writeReqs   []WriteRequest
+               expectError bool
+       }{
+               {
+                       name:        "empty requests",
+                       writeReqs:   []WriteRequest{},
+                       expectError: false,
+               },
+               {
+                       name: "valid request",
+                       writeReqs: []WriteRequest{
+                               {
+                                       SeriesID: 1,
+                                       Key:      100,
+                                       Data:     []byte("data"),
+                                       Tags:     []tag{},
+                               },
+                       },
+                       expectError: false,
+               },
+               {
+                       name: "invalid SeriesID",
+                       writeReqs: []WriteRequest{
+                               {
+                                       SeriesID: 0, // Invalid
+                                       Key:      100,
+                                       Data:     []byte("data"),
+                                       Tags:     []tag{},
+                               },
+                       },
+                       expectError: true,
+               },
+               {
+                       name: "nil data",
+                       writeReqs: []WriteRequest{
+                               {
+                                       SeriesID: 1,
+                                       Key:      100,
+                                       Data:     nil, // Invalid
+                                       Tags:     []tag{},
+                               },
+                       },
+                       expectError: true,
+               },
+               {
+                       name: "empty data",
+                       writeReqs: []WriteRequest{
+                               {
+                                       SeriesID: 1,
+                                       Key:      100,
+                                       Data:     []byte{}, // Invalid
+                                       Tags:     []tag{},
+                               },
+                       },
+                       expectError: true,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       err := mock.Write(ctx, tt.writeReqs)
+                       if tt.expectError {
+                               assert.Error(t, err)
+                       } else {
+                               assert.NoError(t, err)
+                       }
+               })
+       }
+}
+
+func TestMockSIDX_ElementLimits(t *testing.T) {
+       config := DefaultMockConfig()
+       config.MaxElements = 2
+       mock := NewMockSIDX(config)
+       defer func() {
+               assert.NoError(t, mock.Close())
+       }()
+
+       ctx := context.Background()
+
+       // Write up to limit
+       writeReqs := []WriteRequest{
+               {SeriesID: 1, Key: 100, Data: []byte("data1")},
+               {SeriesID: 1, Key: 101, Data: []byte("data2")},
+       }
+       err := mock.Write(ctx, writeReqs)
+       require.NoError(t, err)
+
+       // Try to exceed limit
+       writeReqs = []WriteRequest{
+               {SeriesID: 1, Key: 102, Data: []byte("data3")},
+       }
+       err = mock.Write(ctx, writeReqs)
+       assert.Error(t, err)
+       assert.Contains(t, err.Error(), "element limit exceeded")
+}
+
+func TestMockSIDX_ErrorInjection(t *testing.T) {
+       config := DefaultMockConfig()
+       config.ErrorRate = 100 // Always error
+       mock := NewMockSIDX(config)
+       defer func() {
+               assert.NoError(t, mock.Close())
+       }()
+
+       ctx := context.Background()
+
+       // Test write error injection
+       writeReqs := []WriteRequest{
+               {SeriesID: 1, Key: 100, Data: []byte("data")},
+       }
+       err := mock.Write(ctx, writeReqs)
+       assert.Error(t, err)
+       assert.Contains(t, err.Error(), "mock error injection")
+
+       // Test query error injection
+       queryReq := QueryRequest{Name: "test"}
+       _, err = mock.Query(ctx, queryReq)
+       assert.Error(t, err)
+       assert.Contains(t, err.Error(), "mock error injection")
+
+       // Test flush error injection
+       err = mock.Flush()
+       assert.Error(t, err)
+       assert.Contains(t, err.Error(), "mock error injection")
+
+       // Test merge error injection
+       err = mock.Merge()
+       assert.Error(t, err)
+       assert.Contains(t, err.Error(), "mock error injection")
+}
+
+func TestMockSIDX_Delays(t *testing.T) {
+       config := DefaultMockConfig()
+       config.WriteDelayMs = 50
+       config.QueryDelayMs = 30
+       config.FlushDelayMs = 20
+       config.MergeDelayMs = 40
+       mock := NewMockSIDX(config)
+       defer func() {
+               assert.NoError(t, mock.Close())
+       }()
+
+       ctx := context.Background()
+
+       // Test write delay
+       start := time.Now()
+       writeReqs := []WriteRequest{
+               {SeriesID: 1, Key: 100, Data: []byte("data")},
+       }
+       err := mock.Write(ctx, writeReqs)
+       require.NoError(t, err)
+       elapsed := time.Since(start)
+       assert.GreaterOrEqual(t, elapsed, 50*time.Millisecond)
+
+       // Test query delay
+       start = time.Now()
+       queryReq := QueryRequest{Name: "series_1"}
+       result, err := mock.Query(ctx, queryReq)
+       require.NoError(t, err)
+       result.Release()
+       elapsed = time.Since(start)
+       assert.GreaterOrEqual(t, elapsed, 30*time.Millisecond)
+
+       // Test flush delay
+       start = time.Now()
+       err = mock.Flush()
+       require.NoError(t, err)
+       elapsed = time.Since(start)
+       assert.GreaterOrEqual(t, elapsed, 20*time.Millisecond)
+
+       // Test merge delay
+       start = time.Now()
+       err = mock.Merge()
+       require.NoError(t, err)
+       elapsed = time.Since(start)
+       assert.GreaterOrEqual(t, elapsed, 40*time.Millisecond)
+}
+
+func TestMockSIDX_Sorting(t *testing.T) {
+       config := DefaultMockConfig()
+       mock := NewMockSIDX(config)
+       defer func() {
+               assert.NoError(t, mock.Close())
+       }()
+
+       ctx := context.Background()
+
+       // Write elements out of order
+       writeReqs := []WriteRequest{
+               {SeriesID: 2, Key: 200, Data: []byte("data2")},
+               {SeriesID: 1, Key: 102, Data: []byte("data3")},
+               {SeriesID: 1, Key: 100, Data: []byte("data1")},
+               {SeriesID: 2, Key: 199, Data: []byte("data4")},
+       }
+       err := mock.Write(ctx, writeReqs)
+       require.NoError(t, err)
+
+       // Query series 1 - should be sorted by key
+       queryReq := QueryRequest{Name: "series_1"}
+       result, err := mock.Query(ctx, queryReq)
+       require.NoError(t, err)
+       defer result.Release()
+
+       response := result.Pull()
+       require.NotNil(t, response)
+       assert.Equal(t, []int64{100, 102}, response.Keys)
+       assert.Equal(t, [][]byte{[]byte("data1"), []byte("data3")}, 
response.Data)
+
+       // Query series 2 - should be sorted by key
+       queryReq = QueryRequest{Name: "series_2"}
+       result, err = mock.Query(ctx, queryReq)
+       require.NoError(t, err)
+       defer result.Release()
+
+       response = result.Pull()
+       require.NotNil(t, response)
+       assert.Equal(t, []int64{199, 200}, response.Keys)
+       assert.Equal(t, [][]byte{[]byte("data4"), []byte("data2")}, 
response.Data)
+}
+
+func TestMockSIDX_QueryBatching(t *testing.T) {
+       config := DefaultMockConfig()
+       mock := NewMockSIDX(config)
+       defer func() {
+               assert.NoError(t, mock.Close())
+       }()
+
+       ctx := context.Background()
+
+       // Write many elements
+       var writeReqs []WriteRequest
+       for i := 0; i < 10; i++ {
+               writeReqs = append(writeReqs, WriteRequest{
+                       SeriesID: 1,
+                       Key:      int64(i),
+                       Data:     []byte(fmt.Sprintf("data%d", i)),
+               })
+       }
+       err := mock.Write(ctx, writeReqs)
+       require.NoError(t, err)
+
+       // Query with small batch size
+       queryReq := QueryRequest{
+               Name:           "series_1",
+               MaxElementSize: 3,
+       }
+       result, err := mock.Query(ctx, queryReq)
+       require.NoError(t, err)
+       defer result.Release()
+
+       // First batch
+       response := result.Pull()
+       require.NotNil(t, response)
+       assert.Equal(t, 3, response.Len())
+       assert.True(t, response.Metadata.TruncatedResults)
+
+       // Second batch
+       response = result.Pull()
+       require.NotNil(t, response)
+       assert.Equal(t, 3, response.Len())
+
+       // Continue until done
+       totalElements := 6
+       for {
+               response = result.Pull()
+               if response == nil {
+                       break
+               }
+               totalElements += response.Len()
+       }
+       assert.Equal(t, 10, totalElements)
+}
+
+func TestMockSIDX_ConcurrentOperations(t *testing.T) {
+       config := DefaultMockConfig()
+       mock := NewMockSIDX(config)
+       defer func() {
+               assert.NoError(t, mock.Close())
+       }()
+
+       ctx := context.Background()
+
+       // Concurrent writes
+       const numGoroutines = 10
+       const elementsPerGoroutine = 10
+       done := make(chan bool, numGoroutines)
+
+       for i := 0; i < numGoroutines; i++ {
+               go func(seriesID int) {
+                       defer func() { done <- true }()
+                       var writeReqs []WriteRequest
+                       for j := 0; j < elementsPerGoroutine; j++ {
+                               writeReqs = append(writeReqs, WriteRequest{
+                                       SeriesID: common.SeriesID(seriesID),
+                                       Key:      int64(j),
+                                       Data:     
[]byte(fmt.Sprintf("data%d_%d", seriesID, j)),
+                               })
+                       }
+                       err := mock.Write(ctx, writeReqs)
+                       assert.NoError(t, err)
+               }(i + 1)
+       }
+
+       // Wait for all writes to complete
+       for i := 0; i < numGoroutines; i++ {
+               <-done
+       }
+
+       // Verify total elements
+       stats, err := mock.Stats(ctx)
+       require.NoError(t, err)
+       assert.Equal(t, int64(numGoroutines*elementsPerGoroutine), 
stats.ElementCount)
+       assert.Equal(t, int64(numGoroutines), stats.WriteCount)
+}
+
+func TestMockSIDX_CloseOperations(t *testing.T) {
+       config := DefaultMockConfig()
+       mock := NewMockSIDX(config)
+
+       ctx := context.Background()
+
+       // Close the mock
+       err := mock.Close()
+       require.NoError(t, err)
+
+       // All operations should fail after close
+       writeReqs := []WriteRequest{
+               {SeriesID: 1, Key: 100, Data: []byte("data")},
+       }
+       err = mock.Write(ctx, writeReqs)
+       assert.Error(t, err)
+       assert.Contains(t, err.Error(), "SIDX is closed")
+
+       queryReq := QueryRequest{Name: "test"}
+       _, err = mock.Query(ctx, queryReq)
+       assert.Error(t, err)
+       assert.Contains(t, err.Error(), "SIDX is closed")
+
+       _, err = mock.Stats(ctx)
+       assert.Error(t, err)
+       assert.Contains(t, err.Error(), "SIDX is closed")
+
+       err = mock.Flush()
+       assert.Error(t, err)
+       assert.Contains(t, err.Error(), "SIDX is closed")
+
+       err = mock.Merge()
+       assert.Error(t, err)
+       assert.Contains(t, err.Error(), "SIDX is closed")
+
+       // Double close should fail
+       err = mock.Close()
+       assert.Error(t, err)
+       assert.Contains(t, err.Error(), "SIDX already closed")
+}
+
+func TestMockSIDX_DynamicConfiguration(t *testing.T) {
+       config := DefaultMockConfig()
+       mock := NewMockSIDX(config)
+       defer func() {
+               assert.NoError(t, mock.Close())
+       }()
+
+       ctx := context.Background()
+
+       // Test dynamic error rate configuration
+       mock.SetErrorRate(100)
+       writeReqs := []WriteRequest{
+               {SeriesID: 1, Key: 100, Data: []byte("data")},
+       }
+       err := mock.Write(ctx, writeReqs)
+       assert.Error(t, err)
+
+       mock.SetErrorRate(0)
+       err = mock.Write(ctx, writeReqs)
+       assert.NoError(t, err)
+
+       // Test dynamic delay configuration
+       mock.SetWriteDelay(50)
+       start := time.Now()
+       err = mock.Write(ctx, writeReqs)
+       require.NoError(t, err)
+       elapsed := time.Since(start)
+       assert.GreaterOrEqual(t, elapsed, 50*time.Millisecond)
+
+       mock.SetQueryDelay(30)
+       start = time.Now()
+       result, err := mock.Query(ctx, QueryRequest{Name: "series_1"})
+       require.NoError(t, err)
+       result.Release()
+       elapsed = time.Since(start)
+       assert.GreaterOrEqual(t, elapsed, 30*time.Millisecond)
+}
+
+func TestMockSIDX_UtilityMethods(t *testing.T) {
+       config := DefaultMockConfig()
+       mock := NewMockSIDX(config)
+       defer func() {
+               assert.NoError(t, mock.Close())
+       }()
+
+       ctx := context.Background()
+
+       // Test initial state
+       assert.Equal(t, int64(0), mock.GetElementCount())
+       assert.Empty(t, mock.GetStorageKeys())
+
+       // Add some data
+       writeReqs := []WriteRequest{
+               {SeriesID: 1, Key: 100, Data: []byte("data1")},
+               {SeriesID: 2, Key: 200, Data: []byte("data2")},
+       }
+       err := mock.Write(ctx, writeReqs)
+       require.NoError(t, err)
+
+       // Check state after writes
+       assert.Equal(t, int64(2), mock.GetElementCount())
+       keys := mock.GetStorageKeys()
+       assert.Len(t, keys, 2)
+       assert.Contains(t, keys, "series_1")
+       assert.Contains(t, keys, "series_2")
+
+       // Test clear
+       mock.Clear()
+       assert.Equal(t, int64(0), mock.GetElementCount())
+       assert.Empty(t, mock.GetStorageKeys())
+}
+
+func TestMockSIDX_FlushAndMergeTimestamps(t *testing.T) {
+       config := DefaultMockConfig()
+       config.FlushDelayMs = 10
+       config.MergeDelayMs = 10
+       mock := NewMockSIDX(config)
+       defer func() {
+               assert.NoError(t, mock.Close())
+       }()
+
+       ctx := context.Background()
+
+       // Initial timestamps should be zero
+       stats, err := mock.Stats(ctx)
+       require.NoError(t, err)
+       assert.Equal(t, int64(0), stats.LastFlushTime)
+       assert.Equal(t, int64(0), stats.LastMergeTime)
+
+       // Perform flush
+       startTime := time.Now().UnixNano()
+       err = mock.Flush()
+       require.NoError(t, err)
+       endTime := time.Now().UnixNano()
+
+       stats, err = mock.Stats(ctx)
+       require.NoError(t, err)
+       assert.GreaterOrEqual(t, stats.LastFlushTime, startTime)
+       assert.LessOrEqual(t, stats.LastFlushTime, endTime)
+
+       // Perform merge
+       startTime = time.Now().UnixNano()
+       err = mock.Merge()
+       require.NoError(t, err)
+       endTime = time.Now().UnixNano()
+
+       stats, err = mock.Stats(ctx)
+       require.NoError(t, err)
+       assert.GreaterOrEqual(t, stats.LastMergeTime, startTime)
+       assert.LessOrEqual(t, stats.LastMergeTime, endTime)
+}

Reply via email to