package local import ( "bytes" "context" "io" "os" "path/filepath" "strings" "testing" "git.marketally.com/tutus-one/tutus-chain/pkg/storage" ) func TestNew(t *testing.T) { tmpDir := t.TempDir() adapter, err := New(Config{Path: tmpDir}) if err != nil { t.Fatalf("New() error = %v", err) } defer adapter.Close() // Check subdirectories were created for _, sub := range []string{"objects", "meta", "blocks", "states"} { path := filepath.Join(tmpDir, sub) if _, err := os.Stat(path); os.IsNotExist(err) { t.Errorf("subdirectory %s not created", sub) } } } func TestNew_DefaultPath(t *testing.T) { // Clean up default path after test defer os.RemoveAll("./storage") adapter, err := New(Config{}) if err != nil { t.Fatalf("New() error = %v", err) } defer adapter.Close() if _, err := os.Stat("./storage"); os.IsNotExist(err) { t.Error("default storage directory not created") } } func TestAdapter_Name(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() if got := adapter.Name(); got != "local" { t.Errorf("Name() = %v, want %v", got, "local") } } func TestAdapter_PutGet(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() data := []byte("test data") // Put id, err := adapter.Put(ctx, bytes.NewReader(data), storage.PutOptions{ ContentType: "text/plain", Attributes: map[string]string{"key": "value"}, }) if err != nil { t.Fatalf("Put() error = %v", err) } if id.Provider != "local" { t.Errorf("Put() Provider = %v, want %v", id.Provider, "local") } // Get reader, err := adapter.Get(ctx, id) if err != nil { t.Fatalf("Get() error = %v", err) } defer reader.Close() got, err := io.ReadAll(reader) if err != nil { t.Fatalf("ReadAll() error = %v", err) } if !bytes.Equal(got, data) { t.Errorf("Get() = %v, want %v", got, data) } } func TestAdapter_Get_NotFound(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() _, err := adapter.Get(ctx, storage.ObjectID{ID: "nonexistent"}) if err != storage.ErrNotFound { t.Errorf("Get() error = %v, want %v", err, storage.ErrNotFound) } } func TestAdapter_Exists(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() data := []byte("test data") // Put id, _ := adapter.Put(ctx, bytes.NewReader(data), storage.PutOptions{}) // Exists should return true exists, err := adapter.Exists(ctx, id) if err != nil { t.Fatalf("Exists() error = %v", err) } if !exists { t.Error("Exists() = false, want true") } // Non-existent should return false exists, err = adapter.Exists(ctx, storage.ObjectID{ID: "nonexistent"}) if err != nil { t.Fatalf("Exists() error = %v", err) } if exists { t.Error("Exists() = true, want false") } } func TestAdapter_Delete(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() data := []byte("test data") // Put id, _ := adapter.Put(ctx, bytes.NewReader(data), storage.PutOptions{}) // Delete err := adapter.Delete(ctx, id) if err != nil { t.Fatalf("Delete() error = %v", err) } // Should not exist anymore exists, _ := adapter.Exists(ctx, id) if exists { t.Error("object still exists after Delete()") } } func TestAdapter_Delete_NotFound(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() err := adapter.Delete(ctx, storage.ObjectID{ID: "nonexistent"}) if err != storage.ErrNotFound { t.Errorf("Delete() error = %v, want %v", err, storage.ErrNotFound) } } func TestAdapter_Head(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() data := []byte("test data") // Put id, _ := adapter.Put(ctx, bytes.NewReader(data), storage.PutOptions{ ContentType: "text/plain", Attributes: map[string]string{"key": "value"}, }) // Head info, err := adapter.Head(ctx, id) if err != nil { t.Fatalf("Head() error = %v", err) } if info.Size != int64(len(data)) { t.Errorf("Head() Size = %v, want %v", info.Size, len(data)) } if info.ContentType != "text/plain" { t.Errorf("Head() ContentType = %v, want %v", info.ContentType, "text/plain") } if info.Attributes["key"] != "value" { t.Errorf("Head() Attributes[key] = %v, want %v", info.Attributes["key"], "value") } } func TestAdapter_Head_NotFound(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() _, err := adapter.Head(ctx, storage.ObjectID{ID: "nonexistent"}) if err != storage.ErrNotFound { t.Errorf("Head() error = %v, want %v", err, storage.ErrNotFound) } } func TestAdapter_List(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() // Put some objects ids := make([]storage.ObjectID, 3) for i := 0; i < 3; i++ { data := []byte(strings.Repeat("x", i+1)) // Different content = different ID id, _ := adapter.Put(ctx, bytes.NewReader(data), storage.PutOptions{}) ids[i] = id } // List all results, err := adapter.List(ctx, "", storage.ListOptions{}) if err != nil { t.Fatalf("List() error = %v", err) } if len(results) != 3 { t.Errorf("List() returned %d items, want 3", len(results)) } } func TestAdapter_List_MaxResults(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() // Put some objects for i := 0; i < 5; i++ { data := []byte(strings.Repeat("x", i+1)) adapter.Put(ctx, bytes.NewReader(data), storage.PutOptions{}) } // List with limit results, err := adapter.List(ctx, "", storage.ListOptions{MaxResults: 2}) if err != nil { t.Fatalf("List() error = %v", err) } if len(results) != 2 { t.Errorf("List() returned %d items, want 2", len(results)) } } func TestAdapter_QuotaExceeded(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir(), MaxSize: 100}) defer adapter.Close() ctx := context.Background() // First put should succeed data1 := []byte(strings.Repeat("x", 50)) _, err := adapter.Put(ctx, bytes.NewReader(data1), storage.PutOptions{}) if err != nil { t.Fatalf("Put() error = %v", err) } // Second put should exceed quota data2 := []byte(strings.Repeat("y", 60)) _, err = adapter.Put(ctx, bytes.NewReader(data2), storage.PutOptions{}) if err != storage.ErrQuotaExceeded { t.Errorf("Put() error = %v, want %v", err, storage.ErrQuotaExceeded) } } // ============================================================================= // BlockStorage Tests // ============================================================================= func TestAdapter_PutGetBlock(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() blockData := []byte("block 0 data") // PutBlock id, err := adapter.PutBlock(ctx, 0, blockData) if err != nil { t.Fatalf("PutBlock() error = %v", err) } if id.Container != "blocks" { t.Errorf("PutBlock() Container = %v, want %v", id.Container, "blocks") } // GetBlock got, err := adapter.GetBlock(ctx, 0) if err != nil { t.Fatalf("GetBlock() error = %v", err) } if !bytes.Equal(got, blockData) { t.Errorf("GetBlock() = %v, want %v", got, blockData) } } func TestAdapter_GetBlock_NotFound(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() _, err := adapter.GetBlock(ctx, 999) if err != storage.ErrNotFound { t.Errorf("GetBlock() error = %v, want %v", err, storage.ErrNotFound) } } func TestAdapter_GetBlockRange(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() // Put several blocks for i := uint32(0); i < 5; i++ { data := []byte(strings.Repeat(string(rune('a'+i)), 10)) _, err := adapter.PutBlock(ctx, i, data) if err != nil { t.Fatalf("PutBlock(%d) error = %v", i, err) } } // Get range blocks, err := adapter.GetBlockRange(ctx, 1, 3) if err != nil { t.Fatalf("GetBlockRange() error = %v", err) } if len(blocks) != 3 { t.Errorf("GetBlockRange() returned %d blocks, want 3", len(blocks)) } // Verify content for i, block := range blocks { expected := []byte(strings.Repeat(string(rune('b'+i)), 10)) if !bytes.Equal(block, expected) { t.Errorf("GetBlockRange() block %d = %v, want %v", i+1, block, expected) } } } func TestAdapter_GetBlockRange_InvalidRange(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() _, err := adapter.GetBlockRange(ctx, 10, 5) if err == nil || !strings.Contains(err.Error(), "invalid block range") { t.Errorf("GetBlockRange() error = %v, want invalid block range error", err) } } func TestAdapter_GetBlockRange_NotFound(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() // Put only block 0 adapter.PutBlock(ctx, 0, []byte("block 0")) // Try to get range that includes missing block _, err := adapter.GetBlockRange(ctx, 0, 2) if err != storage.ErrNotFound { t.Errorf("GetBlockRange() error = %v, want %v", err, storage.ErrNotFound) } } func TestAdapter_GetLatestBlockIndex(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() // No blocks yet _, err := adapter.GetLatestBlockIndex(ctx) if err != storage.ErrNotFound { t.Errorf("GetLatestBlockIndex() error = %v, want %v", err, storage.ErrNotFound) } // Put some blocks for i := uint32(0); i < 10; i++ { adapter.PutBlock(ctx, i, []byte("block")) } // Get latest latest, err := adapter.GetLatestBlockIndex(ctx) if err != nil { t.Fatalf("GetLatestBlockIndex() error = %v", err) } if latest != 9 { t.Errorf("GetLatestBlockIndex() = %v, want 9", latest) } } func TestAdapter_PutBlock_QuotaExceeded(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir(), MaxSize: 100}) defer adapter.Close() ctx := context.Background() // First block should succeed _, err := adapter.PutBlock(ctx, 0, []byte(strings.Repeat("x", 50))) if err != nil { t.Fatalf("PutBlock() error = %v", err) } // Second block should exceed quota _, err = adapter.PutBlock(ctx, 1, []byte(strings.Repeat("y", 60))) if err != storage.ErrQuotaExceeded { t.Errorf("PutBlock() error = %v, want %v", err, storage.ErrQuotaExceeded) } } // ============================================================================= // StateStorage Tests // ============================================================================= func TestAdapter_PutGetState(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() stateData := []byte("state at height 0") // PutState id, err := adapter.PutState(ctx, 0, bytes.NewReader(stateData)) if err != nil { t.Fatalf("PutState() error = %v", err) } if id.Container != "states" { t.Errorf("PutState() Container = %v, want %v", id.Container, "states") } // GetState reader, err := adapter.GetState(ctx, 0) if err != nil { t.Fatalf("GetState() error = %v", err) } defer reader.Close() got, err := io.ReadAll(reader) if err != nil { t.Fatalf("ReadAll() error = %v", err) } if !bytes.Equal(got, stateData) { t.Errorf("GetState() = %v, want %v", got, stateData) } } func TestAdapter_GetState_NotFound(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() _, err := adapter.GetState(ctx, 999) if err != storage.ErrNotFound { t.Errorf("GetState() error = %v, want %v", err, storage.ErrNotFound) } } func TestAdapter_GetLatestState(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir()}) defer adapter.Close() ctx := context.Background() // No states yet _, _, err := adapter.GetLatestState(ctx) if err != storage.ErrNotFound { t.Errorf("GetLatestState() error = %v, want %v", err, storage.ErrNotFound) } // Put some states for i := uint32(0); i < 5; i++ { data := []byte(strings.Repeat(string(rune('a'+i)), 10)) _, err := adapter.PutState(ctx, i, bytes.NewReader(data)) if err != nil { t.Fatalf("PutState(%d) error = %v", i, err) } } // Get latest height, reader, err := adapter.GetLatestState(ctx) if err != nil { t.Fatalf("GetLatestState() error = %v", err) } defer reader.Close() if height != 4 { t.Errorf("GetLatestState() height = %v, want 4", height) } got, _ := io.ReadAll(reader) expected := []byte(strings.Repeat("e", 10)) if !bytes.Equal(got, expected) { t.Errorf("GetLatestState() data = %v, want %v", got, expected) } } func TestAdapter_PutState_QuotaExceeded(t *testing.T) { adapter, _ := New(Config{Path: t.TempDir(), MaxSize: 100}) defer adapter.Close() ctx := context.Background() // First state should succeed _, err := adapter.PutState(ctx, 0, bytes.NewReader([]byte(strings.Repeat("x", 50)))) if err != nil { t.Fatalf("PutState() error = %v", err) } // Second state should exceed quota _, err = adapter.PutState(ctx, 1, bytes.NewReader([]byte(strings.Repeat("y", 60)))) if err != storage.ErrQuotaExceeded { t.Errorf("PutState() error = %v, want %v", err, storage.ErrQuotaExceeded) } } // ============================================================================= // Path Helper Tests // ============================================================================= func TestAdapter_blockPath(t *testing.T) { adapter := &Adapter{basePath: "/storage"} tests := []struct { index uint32 expected string }{ {0, filepath.Join("/storage", "blocks", "0000", "0.block")}, {9999, filepath.Join("/storage", "blocks", "0000", "9999.block")}, {10000, filepath.Join("/storage", "blocks", "0001", "10000.block")}, {12345, filepath.Join("/storage", "blocks", "0001", "12345.block")}, {99999, filepath.Join("/storage", "blocks", "0009", "99999.block")}, } for _, tt := range tests { got := adapter.blockPath(tt.index) if got != tt.expected { t.Errorf("blockPath(%d) = %v, want %v", tt.index, got, tt.expected) } } } func TestAdapter_statePath(t *testing.T) { adapter := &Adapter{basePath: "/storage"} tests := []struct { height uint32 expected string }{ {0, filepath.Join("/storage", "states", "0000", "0.state")}, {9999, filepath.Join("/storage", "states", "0000", "9999.state")}, {10000, filepath.Join("/storage", "states", "0001", "10000.state")}, {12345, filepath.Join("/storage", "states", "0001", "12345.state")}, } for _, tt := range tests { got := adapter.statePath(tt.height) if got != tt.expected { t.Errorf("statePath(%d) = %v, want %v", tt.height, got, tt.expected) } } } // ============================================================================= // Interface Compliance Tests // ============================================================================= func TestAdapter_ImplementsProvider(t *testing.T) { var _ storage.Provider = (*Adapter)(nil) } func TestAdapter_ImplementsBlockStorage(t *testing.T) { var _ storage.BlockStorage = (*Adapter)(nil) } func TestAdapter_ImplementsStateStorage(t *testing.T) { var _ storage.StateStorage = (*Adapter)(nil) }