mirror of
https://github.com/logos-storage/logos-storage-go.git
synced 2026-01-02 13:23:11 +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
|
||||
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
|
||||
type CodexArchiveDownloader struct {
|
||||
codexClient *CodexClient
|
||||
codexClient CodexClientInterface
|
||||
index *protobuf.CodexWakuMessageArchiveIndex
|
||||
communityID string
|
||||
existingArchiveIDs []string
|
||||
@ -40,15 +40,15 @@ type CodexArchiveDownloader struct {
|
||||
|
||||
// Download control
|
||||
downloadComplete bool
|
||||
downloadError error
|
||||
cancelled bool
|
||||
pollingInterval time.Duration // configurable polling interval for HasCid checks
|
||||
|
||||
// Callback for signaling archive download completion
|
||||
onArchiveDownloaded func(hash string, from, to uint64)
|
||||
}
|
||||
|
||||
// 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{
|
||||
codexClient: codexClient,
|
||||
index: index,
|
||||
@ -59,9 +59,17 @@ func NewCodexArchiveDownloader(codexClient *CodexClient, index *protobuf.CodexWa
|
||||
totalDownloadedArchivesCount: len(existingArchiveIDs),
|
||||
archiveDownloadProgress: make(map[string]int64),
|
||||
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
|
||||
func (d *CodexArchiveDownloader) SetOnArchiveDownloaded(callback func(hash string, from, to uint64)) {
|
||||
d.onArchiveDownloaded = callback
|
||||
@ -106,13 +114,6 @@ func (d *CodexArchiveDownloader) IsDownloadComplete() bool {
|
||||
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
|
||||
func (d *CodexArchiveDownloader) IsCancelled() bool {
|
||||
d.mu.RLock()
|
||||
@ -215,10 +216,10 @@ func (d *CodexArchiveDownloader) downloadAllArchives() {
|
||||
}
|
||||
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
|
||||
timeout := time.After(30 * time.Second)
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
ticker := time.NewTicker(d.pollingInterval)
|
||||
defer ticker.Stop()
|
||||
PollLoop:
|
||||
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
|
||||
|
||||
go 1.21
|
||||
go 1.23.0
|
||||
|
||||
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/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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user