mirror of
https://github.com/logos-storage/logos-storage-go.git
synced 2026-01-04 06:13:07 +00:00
Adds basic test for archive downloader with the mocking setup
This commit is contained in:
parent
3e135f3c53
commit
3db63a3aab
28
README.md
28
README.md
@ -103,4 +103,30 @@ To make sure that the test is actually run and not cached, use `count` option:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
go test -v -tags=integration ./communities -run Integration -timeout 15s -count 1
|
go test -v -tags=integration ./communities -run Integration -timeout 15s -count 1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Regenerating artifacts
|
||||||
|
|
||||||
|
Everything you need comes included in the repo. But if you decide to change things,
|
||||||
|
you will need to regenerate some artifacts. There are two:
|
||||||
|
|
||||||
|
- the protobuf
|
||||||
|
- the mocks
|
||||||
|
|
||||||
|
The easiest way is to regenerate all in one go:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go generate ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
If you just need to regenerate the mocks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go generate ./communities
|
||||||
|
```
|
||||||
|
|
||||||
|
If you just need to regenerate the protobuf:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go generate ./protobuf
|
||||||
|
```
|
||||||
|
|||||||
@ -24,7 +24,7 @@ type CodexArchiveProcessor interface {
|
|||||||
|
|
||||||
// CodexArchiveDownloader handles downloading individual archive files from Codex storage
|
// CodexArchiveDownloader handles downloading individual archive files from Codex storage
|
||||||
type CodexArchiveDownloader struct {
|
type CodexArchiveDownloader struct {
|
||||||
codexClient *CodexClient
|
codexClient CodexClientInterface
|
||||||
index *protobuf.CodexWakuMessageArchiveIndex
|
index *protobuf.CodexWakuMessageArchiveIndex
|
||||||
communityID string
|
communityID string
|
||||||
existingArchiveIDs []string
|
existingArchiveIDs []string
|
||||||
@ -40,15 +40,15 @@ type CodexArchiveDownloader struct {
|
|||||||
|
|
||||||
// Download control
|
// Download control
|
||||||
downloadComplete bool
|
downloadComplete bool
|
||||||
downloadError error
|
|
||||||
cancelled bool
|
cancelled bool
|
||||||
|
pollingInterval time.Duration // configurable polling interval for HasCid checks
|
||||||
|
|
||||||
// Callback for signaling archive download completion
|
// Callback for signaling archive download completion
|
||||||
onArchiveDownloaded func(hash string, from, to uint64)
|
onArchiveDownloaded func(hash string, from, to uint64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCodexArchiveDownloader creates a new archive downloader
|
// NewCodexArchiveDownloader creates a new archive downloader
|
||||||
func NewCodexArchiveDownloader(codexClient *CodexClient, index *protobuf.CodexWakuMessageArchiveIndex, communityID string, existingArchiveIDs []string, cancelChan chan struct{}) *CodexArchiveDownloader {
|
func NewCodexArchiveDownloader(codexClient CodexClientInterface, index *protobuf.CodexWakuMessageArchiveIndex, communityID string, existingArchiveIDs []string, cancelChan chan struct{}) *CodexArchiveDownloader {
|
||||||
return &CodexArchiveDownloader{
|
return &CodexArchiveDownloader{
|
||||||
codexClient: codexClient,
|
codexClient: codexClient,
|
||||||
index: index,
|
index: index,
|
||||||
@ -59,9 +59,17 @@ func NewCodexArchiveDownloader(codexClient *CodexClient, index *protobuf.CodexWa
|
|||||||
totalDownloadedArchivesCount: len(existingArchiveIDs),
|
totalDownloadedArchivesCount: len(existingArchiveIDs),
|
||||||
archiveDownloadProgress: make(map[string]int64),
|
archiveDownloadProgress: make(map[string]int64),
|
||||||
archiveDownloadCancel: make(map[string]chan struct{}),
|
archiveDownloadCancel: make(map[string]chan struct{}),
|
||||||
|
pollingInterval: 1 * time.Second, // Default production polling interval
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetPollingInterval sets the polling interval for HasCid checks (useful for testing)
|
||||||
|
func (d *CodexArchiveDownloader) SetPollingInterval(interval time.Duration) {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
d.pollingInterval = interval
|
||||||
|
}
|
||||||
|
|
||||||
// SetOnArchiveDownloaded sets a callback function to be called when an archive is successfully downloaded
|
// SetOnArchiveDownloaded sets a callback function to be called when an archive is successfully downloaded
|
||||||
func (d *CodexArchiveDownloader) SetOnArchiveDownloaded(callback func(hash string, from, to uint64)) {
|
func (d *CodexArchiveDownloader) SetOnArchiveDownloaded(callback func(hash string, from, to uint64)) {
|
||||||
d.onArchiveDownloaded = callback
|
d.onArchiveDownloaded = callback
|
||||||
@ -106,13 +114,6 @@ func (d *CodexArchiveDownloader) IsDownloadComplete() bool {
|
|||||||
return d.downloadComplete
|
return d.downloadComplete
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDownloadError returns any error that occurred during download
|
|
||||||
func (d *CodexArchiveDownloader) GetDownloadError() error {
|
|
||||||
d.mu.RLock()
|
|
||||||
defer d.mu.RUnlock()
|
|
||||||
return d.downloadError
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCancelled returns whether the download was cancelled
|
// IsCancelled returns whether the download was cancelled
|
||||||
func (d *CodexArchiveDownloader) IsCancelled() bool {
|
func (d *CodexArchiveDownloader) IsCancelled() bool {
|
||||||
d.mu.RLock()
|
d.mu.RLock()
|
||||||
@ -215,10 +216,10 @@ func (d *CodexArchiveDownloader) downloadAllArchives() {
|
|||||||
}
|
}
|
||||||
d.mu.Unlock()
|
d.mu.Unlock()
|
||||||
|
|
||||||
// poll every second until we confirm it's downloaded
|
// poll at configured interval until we confirm it's downloaded
|
||||||
// or timeout after 30 seconds
|
// or timeout after 30 seconds
|
||||||
timeout := time.After(30 * time.Second)
|
timeout := time.After(30 * time.Second)
|
||||||
ticker := time.NewTicker(1 * time.Second)
|
ticker := time.NewTicker(d.pollingInterval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
PollLoop:
|
PollLoop:
|
||||||
for {
|
for {
|
||||||
|
|||||||
138
communities/codex_archive_downloader_test.go
Normal file
138
communities/codex_archive_downloader_test.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
//go:build !disable_torrent
|
||||||
|
// +build !disable_torrent
|
||||||
|
|
||||||
|
package communities_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go-codex-client/communities"
|
||||||
|
"go-codex-client/protobuf"
|
||||||
|
|
||||||
|
mock_communities "go-codex-client/communities/mock"
|
||||||
|
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helper function to create a test index with a single archive
|
||||||
|
func createTestIndex() *protobuf.CodexWakuMessageArchiveIndex {
|
||||||
|
return &protobuf.CodexWakuMessageArchiveIndex{
|
||||||
|
Archives: map[string]*protobuf.CodexWakuMessageArchiveIndexMetadata{
|
||||||
|
"test-archive-hash-1": {
|
||||||
|
Cid: "test-cid-1",
|
||||||
|
Metadata: &protobuf.WakuMessageArchiveMetadata{
|
||||||
|
From: 1000,
|
||||||
|
To: 2000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodexArchiveDownloader_BasicSingleArchive(t *testing.T) {
|
||||||
|
// Create gomock controller
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
// Create test data
|
||||||
|
index := createTestIndex()
|
||||||
|
communityID := "test-community"
|
||||||
|
existingArchiveIDs := []string{} // No existing archives
|
||||||
|
cancelChan := make(chan struct{})
|
||||||
|
|
||||||
|
// Create mock client using gomock
|
||||||
|
mockClient := mock_communities.NewMockCodexClientInterface(ctrl)
|
||||||
|
|
||||||
|
// Set up expectations
|
||||||
|
mockClient.EXPECT().
|
||||||
|
TriggerDownloadWithContext(gomock.Any(), "test-cid-1").
|
||||||
|
Return(&communities.CodexManifest{CID: "test-cid-1"}, nil).
|
||||||
|
Times(1)
|
||||||
|
|
||||||
|
// First HasCid call returns false, second returns true (simulating polling)
|
||||||
|
gomock.InOrder(
|
||||||
|
mockClient.EXPECT().HasCid("test-cid-1").Return(false, nil),
|
||||||
|
mockClient.EXPECT().HasCid("test-cid-1").Return(true, nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create downloader with mock client
|
||||||
|
downloader := communities.NewCodexArchiveDownloader(mockClient, index, communityID, existingArchiveIDs, cancelChan)
|
||||||
|
|
||||||
|
// Set fast polling interval for tests (10ms instead of default 1s)
|
||||||
|
downloader.SetPollingInterval(10 * time.Millisecond)
|
||||||
|
|
||||||
|
// Set up callback to track completion
|
||||||
|
var callbackInvoked bool
|
||||||
|
var callbackHash string
|
||||||
|
var callbackFrom, callbackTo uint64
|
||||||
|
|
||||||
|
downloader.SetOnArchiveDownloaded(func(hash string, from, to uint64) {
|
||||||
|
callbackInvoked = true
|
||||||
|
callbackHash = hash
|
||||||
|
callbackFrom = from
|
||||||
|
callbackTo = to
|
||||||
|
})
|
||||||
|
|
||||||
|
// Verify initial state
|
||||||
|
if downloader.GetTotalArchivesCount() != 1 {
|
||||||
|
t.Errorf("Expected 1 total archive, got %d", downloader.GetTotalArchivesCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
if downloader.GetTotalDownloadedArchivesCount() != 0 {
|
||||||
|
t.Errorf("Expected 0 downloaded archives initially, got %d", downloader.GetTotalDownloadedArchivesCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
if downloader.IsDownloadComplete() {
|
||||||
|
t.Error("Expected download to not be complete initially")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the download
|
||||||
|
downloader.StartDownload()
|
||||||
|
|
||||||
|
// Wait for download to complete (with timeout)
|
||||||
|
timeout := time.After(5 * time.Second)
|
||||||
|
ticker := time.NewTicker(100 * time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
waitLoop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-timeout:
|
||||||
|
t.Fatal("Timeout waiting for download to complete")
|
||||||
|
case <-ticker.C:
|
||||||
|
if downloader.IsDownloadComplete() {
|
||||||
|
break waitLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Verify final state
|
||||||
|
if !downloader.IsDownloadComplete() {
|
||||||
|
t.Error("Expected download to be complete")
|
||||||
|
}
|
||||||
|
|
||||||
|
if downloader.GetTotalDownloadedArchivesCount() != 1 {
|
||||||
|
t.Errorf("Expected 1 downloaded archive, got %d", downloader.GetTotalDownloadedArchivesCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify callback was invoked
|
||||||
|
if !callbackInvoked {
|
||||||
|
t.Error("Expected callback to be invoked")
|
||||||
|
}
|
||||||
|
|
||||||
|
if callbackHash != "test-archive-hash-1" {
|
||||||
|
t.Errorf("Expected callback hash 'test-archive-hash-1', got '%s'", callbackHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
if callbackFrom != 1000 {
|
||||||
|
t.Errorf("Expected callback from 1000, got %d", callbackFrom)
|
||||||
|
}
|
||||||
|
|
||||||
|
if callbackTo != 2000 {
|
||||||
|
t.Errorf("Expected callback to 2000, got %d", callbackTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("✅ Basic single archive download test passed")
|
||||||
|
t.Logf(" - All mock expectations satisfied")
|
||||||
|
t.Logf(" - Callback invoked: %v", callbackInvoked)
|
||||||
|
}
|
||||||
20
communities/codex_client_interface.go
Normal file
20
communities/codex_client_interface.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
//go:build !disable_torrent
|
||||||
|
// +build !disable_torrent
|
||||||
|
|
||||||
|
package communities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mock generation instruction above will create a mock in package `mock_communities`
|
||||||
|
// (folder `mock/`) so tests can import it as e.g. `go-codex-client/communities/mock` or
|
||||||
|
// with an alias like `mocks` to avoid import-cycle issues.
|
||||||
|
//
|
||||||
|
// CodexClientInterface defines the interface for CodexClient operations needed by the downloader
|
||||||
|
//
|
||||||
|
//go:generate mockgen -package=mock_communities -source=codex_client_interface.go -destination=mock/codex_client_interface.go
|
||||||
|
type CodexClientInterface interface {
|
||||||
|
TriggerDownloadWithContext(ctx context.Context, cid string) (*CodexManifest, error)
|
||||||
|
HasCid(cid string) (bool, error)
|
||||||
|
}
|
||||||
72
communities/mock/codex_client_interface.go
Normal file
72
communities/mock/codex_client_interface.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: codex_client_interface.go
|
||||||
|
//
|
||||||
|
// Generated by this command:
|
||||||
|
//
|
||||||
|
// mockgen -package=mock_communities -source=codex_client_interface.go -destination=mock/codex_client_interface.go
|
||||||
|
//
|
||||||
|
|
||||||
|
// Package mock_communities is a generated GoMock package.
|
||||||
|
package mock_communities
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
communities "go-codex-client/communities"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockCodexClientInterface is a mock of CodexClientInterface interface.
|
||||||
|
type MockCodexClientInterface struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockCodexClientInterfaceMockRecorder
|
||||||
|
isgomock struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockCodexClientInterfaceMockRecorder is the mock recorder for MockCodexClientInterface.
|
||||||
|
type MockCodexClientInterfaceMockRecorder struct {
|
||||||
|
mock *MockCodexClientInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockCodexClientInterface creates a new mock instance.
|
||||||
|
func NewMockCodexClientInterface(ctrl *gomock.Controller) *MockCodexClientInterface {
|
||||||
|
mock := &MockCodexClientInterface{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockCodexClientInterfaceMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockCodexClientInterface) EXPECT() *MockCodexClientInterfaceMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasCid mocks base method.
|
||||||
|
func (m *MockCodexClientInterface) HasCid(cid string) (bool, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "HasCid", cid)
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasCid indicates an expected call of HasCid.
|
||||||
|
func (mr *MockCodexClientInterfaceMockRecorder) HasCid(cid any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasCid", reflect.TypeOf((*MockCodexClientInterface)(nil).HasCid), cid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TriggerDownloadWithContext mocks base method.
|
||||||
|
func (m *MockCodexClientInterface) TriggerDownloadWithContext(ctx context.Context, cid string) (*communities.CodexManifest, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "TriggerDownloadWithContext", ctx, cid)
|
||||||
|
ret0, _ := ret[0].(*communities.CodexManifest)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// TriggerDownloadWithContext indicates an expected call of TriggerDownloadWithContext.
|
||||||
|
func (mr *MockCodexClientInterfaceMockRecorder) TriggerDownloadWithContext(ctx, cid any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TriggerDownloadWithContext", reflect.TypeOf((*MockCodexClientInterface)(nil).TriggerDownloadWithContext), ctx, cid)
|
||||||
|
}
|
||||||
4
go.mod
4
go.mod
@ -1,5 +1,7 @@
|
|||||||
module go-codex-client
|
module go-codex-client
|
||||||
|
|
||||||
go 1.21
|
go 1.23.0
|
||||||
|
|
||||||
require google.golang.org/protobuf v1.34.1
|
require google.golang.org/protobuf v1.34.1
|
||||||
|
|
||||||
|
require go.uber.org/mock v0.6.0 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@ -1,5 +1,7 @@
|
|||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user