mirror of
https://github.com/logos-storage/logos-storage-go.git
synced 2026-01-04 06:13:07 +00:00
FetchManifestWithContext extracted from index downloader and added to CodexClient
This commit is contained in:
parent
cad584303e
commit
626ef22d49
@ -23,6 +23,18 @@ type CodexClient struct {
|
|||||||
Client *http.Client
|
Client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CodexManifest struct {
|
||||||
|
CID string `json:"cid"`
|
||||||
|
Manifest struct {
|
||||||
|
TreeCid string `json:"treeCid"`
|
||||||
|
DatasetSize int64 `json:"datasetSize"`
|
||||||
|
BlockSize int `json:"blockSize"`
|
||||||
|
Protected bool `json:"protected"`
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Mimetype string `json:"mimetype"`
|
||||||
|
} `json:"manifest"`
|
||||||
|
}
|
||||||
|
|
||||||
// NewCodexClient creates a new Codex client
|
// NewCodexClient creates a new Codex client
|
||||||
func NewCodexClient(host string, port string) *CodexClient {
|
func NewCodexClient(host string, port string) *CodexClient {
|
||||||
return &CodexClient{
|
return &CodexClient{
|
||||||
@ -179,17 +191,32 @@ func (c *CodexClient) LocalDownloadWithContext(ctx context.Context, cid string,
|
|||||||
return c.copyWithContext(ctx, output, resp.Body)
|
return c.copyWithContext(ctx, output, resp.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CodexManifest represents the manifest returned by async download
|
func (c *CodexClient) FetchManifestWithContext(ctx context.Context, cid string) (*CodexManifest, error) {
|
||||||
type CodexManifest struct {
|
url := fmt.Sprintf("%s/api/codex/v1/data/%s/network/manifest", c.BaseURL, cid)
|
||||||
CID string `json:"cid"`
|
|
||||||
Manifest struct {
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
TreeCid string `json:"treeCid"`
|
if err != nil {
|
||||||
DatasetSize int64 `json:"datasetSize"`
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
BlockSize int `json:"blockSize"`
|
}
|
||||||
Protected bool `json:"protected"`
|
|
||||||
Filename string `json:"filename"`
|
resp, err := c.Client.Do(req)
|
||||||
Mimetype string `json:"mimetype"`
|
if err != nil {
|
||||||
} `json:"manifest"`
|
return nil, fmt.Errorf("failed to fetch manifest from codex: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return nil, fmt.Errorf("codex fetch manifest failed with status %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse JSON response containing manifest
|
||||||
|
var manifest CodexManifest
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&manifest); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse manifest: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &manifest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CodexClient) TriggerDownloadWithContext(ctx context.Context, cid string) (*CodexManifest, error) {
|
func (c *CodexClient) TriggerDownloadWithContext(ctx context.Context, cid string) (*CodexManifest, error) {
|
||||||
|
|||||||
@ -213,6 +213,98 @@ func TestIntegration_TriggerDownload(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIntegration_FetchManifest(t *testing.T) {
|
||||||
|
host := getenv("CODEX_HOST", "localhost")
|
||||||
|
port := getenv("CODEX_API_PORT", "8080")
|
||||||
|
client := communities.NewCodexClient(host, port)
|
||||||
|
|
||||||
|
// Optional request timeout override
|
||||||
|
if ms := os.Getenv("CODEX_TIMEOUT_MS"); ms != "" {
|
||||||
|
if d, err := time.ParseDuration(ms + "ms"); err == nil {
|
||||||
|
client.SetRequestTimeout(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate random payload to ensure proper round-trip verification
|
||||||
|
payload := make([]byte, 1024)
|
||||||
|
if _, err := rand.Read(payload); err != nil {
|
||||||
|
t.Fatalf("failed to generate random payload: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("Generated payload (first 32 bytes hex): %s", hex.EncodeToString(payload[:32]))
|
||||||
|
|
||||||
|
cid, err := client.Upload(bytes.NewReader(payload), "fetch-manifest-test.bin")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("upload failed: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("Upload successful, CID: %s", cid)
|
||||||
|
|
||||||
|
// Clean up after test
|
||||||
|
defer func() {
|
||||||
|
if err := client.RemoveCid(cid); err != nil {
|
||||||
|
t.Logf("Warning: Failed to remove CID %s: %v", cid, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Verify existence via HasCid first
|
||||||
|
exists, err := client.HasCid(cid)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("HasCid failed: %v", err)
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
t.Fatalf("HasCid returned false for uploaded CID %s", cid)
|
||||||
|
}
|
||||||
|
t.Logf("HasCid confirmed existence of CID: %s", cid)
|
||||||
|
|
||||||
|
// Fetch manifest with context timeout
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
manifest, err := client.FetchManifestWithContext(ctx, cid)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("FetchManifestWithContext failed: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("FetchManifest successful, manifest CID: %s", manifest.CID)
|
||||||
|
|
||||||
|
// Verify manifest properties
|
||||||
|
if manifest.CID != cid {
|
||||||
|
t.Errorf("Manifest CID mismatch: expected %s, got %s", cid, manifest.CID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify manifest has expected fields
|
||||||
|
if manifest.Manifest.TreeCid == "" {
|
||||||
|
t.Error("Expected TreeCid to be non-empty")
|
||||||
|
}
|
||||||
|
t.Logf("Manifest TreeCid: %s", manifest.Manifest.TreeCid)
|
||||||
|
|
||||||
|
if manifest.Manifest.DatasetSize <= 0 {
|
||||||
|
t.Errorf("Expected DatasetSize > 0, got %d", manifest.Manifest.DatasetSize)
|
||||||
|
}
|
||||||
|
t.Logf("Manifest DatasetSize: %d", manifest.Manifest.DatasetSize)
|
||||||
|
|
||||||
|
if manifest.Manifest.BlockSize <= 0 {
|
||||||
|
t.Errorf("Expected BlockSize > 0, got %d", manifest.Manifest.BlockSize)
|
||||||
|
}
|
||||||
|
t.Logf("Manifest BlockSize: %d", manifest.Manifest.BlockSize)
|
||||||
|
|
||||||
|
if manifest.Manifest.Filename != "fetch-manifest-test.bin" {
|
||||||
|
t.Errorf("Expected Filename 'fetch-manifest-test.bin', got '%s'", manifest.Manifest.Filename)
|
||||||
|
}
|
||||||
|
t.Logf("Manifest Filename: %s", manifest.Manifest.Filename)
|
||||||
|
|
||||||
|
// Log manifest details for verification
|
||||||
|
t.Logf("Manifest Protected: %v", manifest.Manifest.Protected)
|
||||||
|
t.Logf("Manifest Mimetype: %s", manifest.Manifest.Mimetype)
|
||||||
|
|
||||||
|
// Test fetching manifest for non-existent CID (should fail gracefully)
|
||||||
|
nonExistentCID := "zDvZRwzmNonExistentCID123456789"
|
||||||
|
_, err = client.FetchManifestWithContext(ctx, nonExistentCID)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error when fetching manifest for non-existent CID, got nil")
|
||||||
|
} else {
|
||||||
|
t.Logf("Expected error for non-existent CID: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getenv(k, def string) string {
|
func getenv(k, def string) string {
|
||||||
if v := os.Getenv(k); v != "" {
|
if v := os.Getenv(k); v != "" {
|
||||||
return v
|
return v
|
||||||
|
|||||||
@ -31,6 +31,9 @@ type CodexClientInterface interface {
|
|||||||
TriggerDownload(cid string) (*CodexManifest, error)
|
TriggerDownload(cid string) (*CodexManifest, error)
|
||||||
TriggerDownloadWithContext(ctx context.Context, cid string) (*CodexManifest, error)
|
TriggerDownloadWithContext(ctx context.Context, cid string) (*CodexManifest, error)
|
||||||
|
|
||||||
|
// Manifest methods
|
||||||
|
FetchManifestWithContext(ctx context.Context, cid string) (*CodexManifest, error)
|
||||||
|
|
||||||
// CID management methods
|
// CID management methods
|
||||||
HasCid(cid string) (bool, error)
|
HasCid(cid string) (bool, error)
|
||||||
RemoveCid(cid string) error
|
RemoveCid(cid string) error
|
||||||
|
|||||||
@ -583,3 +583,183 @@ func TestLocalDownloadWithContext_Cancellation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFetchManifestWithContext_Success(t *testing.T) {
|
||||||
|
testCid := "zDvZRwzmTestCID"
|
||||||
|
expectedManifest := `{
|
||||||
|
"cid": "zDvZRwzmTestCID",
|
||||||
|
"manifest": {
|
||||||
|
"treeCid": "zDvZRwzmTreeCID123",
|
||||||
|
"datasetSize": 1024,
|
||||||
|
"blockSize": 256,
|
||||||
|
"protected": true,
|
||||||
|
"filename": "test-file.bin",
|
||||||
|
"mimetype": "application/octet-stream"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expectedPath := fmt.Sprintf("/api/codex/v1/data/%s/network/manifest", testCid)
|
||||||
|
if r.URL.Path != expectedPath {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(expectedManifest))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := communities.NewCodexClient("localhost", "8080")
|
||||||
|
client.BaseURL = server.URL
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
manifest, err := client.FetchManifestWithContext(ctx, testCid)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if manifest == nil {
|
||||||
|
t.Fatal("Expected manifest, got nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if manifest.CID != testCid {
|
||||||
|
t.Errorf("Expected CID %s, got %s", testCid, manifest.CID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if manifest.Manifest.TreeCid != "zDvZRwzmTreeCID123" {
|
||||||
|
t.Errorf("Expected TreeCid %s, got %s", "zDvZRwzmTreeCID123", manifest.Manifest.TreeCid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if manifest.Manifest.DatasetSize != 1024 {
|
||||||
|
t.Errorf("Expected DatasetSize %d, got %d", 1024, manifest.Manifest.DatasetSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if manifest.Manifest.BlockSize != 256 {
|
||||||
|
t.Errorf("Expected BlockSize %d, got %d", 256, manifest.Manifest.BlockSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !manifest.Manifest.Protected {
|
||||||
|
t.Error("Expected Protected to be true, got false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if manifest.Manifest.Filename != "test-file.bin" {
|
||||||
|
t.Errorf("Expected Filename %s, got %s", "test-file.bin", manifest.Manifest.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
if manifest.Manifest.Mimetype != "application/octet-stream" {
|
||||||
|
t.Errorf("Expected Mimetype %s, got %s", "application/octet-stream", manifest.Manifest.Mimetype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchManifestWithContext_RequestError(t *testing.T) {
|
||||||
|
client := communities.NewCodexClient("invalid-host", "8080")
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
manifest, err := client.FetchManifestWithContext(ctx, "test-cid")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected error for invalid host, got nil")
|
||||||
|
}
|
||||||
|
if manifest != nil {
|
||||||
|
t.Fatal("Expected nil manifest on error, got non-nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(err.Error(), "failed to fetch manifest from codex") {
|
||||||
|
t.Errorf("Expected 'failed to fetch manifest from codex' in error message, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchManifestWithContext_HTTPError(t *testing.T) {
|
||||||
|
testCid := "zDvZRwzmTestCID"
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte("Manifest not found"))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := communities.NewCodexClient("localhost", "8080")
|
||||||
|
client.BaseURL = server.URL
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
manifest, err := client.FetchManifestWithContext(ctx, testCid)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected error for HTTP 404, got nil")
|
||||||
|
}
|
||||||
|
if manifest != nil {
|
||||||
|
t.Fatal("Expected nil manifest on error, got non-nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(err.Error(), "404") {
|
||||||
|
t.Errorf("Expected '404' in error message, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchManifestWithContext_JSONParseError(t *testing.T) {
|
||||||
|
testCid := "zDvZRwzmTestCID"
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte("invalid json {"))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := communities.NewCodexClient("localhost", "8080")
|
||||||
|
client.BaseURL = server.URL
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
manifest, err := client.FetchManifestWithContext(ctx, testCid)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected error for invalid JSON, got nil")
|
||||||
|
}
|
||||||
|
if manifest != nil {
|
||||||
|
t.Fatal("Expected nil manifest on JSON parse error, got non-nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(err.Error(), "failed to parse manifest") {
|
||||||
|
t.Errorf("Expected 'failed to parse manifest' in error message, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchManifestWithContext_Cancellation(t *testing.T) {
|
||||||
|
testCid := "zDvZRwzmTestCID"
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Simulate a slow response
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`{"cid": "test"}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := communities.NewCodexClient("localhost", "8080")
|
||||||
|
client.BaseURL = server.URL
|
||||||
|
|
||||||
|
// Create a context with a very short timeout
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
manifest, err := client.FetchManifestWithContext(ctx, testCid)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected context cancellation error, got nil")
|
||||||
|
}
|
||||||
|
if manifest != nil {
|
||||||
|
t.Fatal("Expected nil manifest on cancellation, got non-nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept either canceled or deadline exceeded depending on timing
|
||||||
|
if !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
// net/http may wrap the context error; check error string as a fallback
|
||||||
|
es := err.Error()
|
||||||
|
if !(es == context.Canceled.Error() || es == context.DeadlineExceeded.Error()) {
|
||||||
|
t.Fatalf("expected context cancellation, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -2,10 +2,7 @@ package communities
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -73,41 +70,20 @@ func (d *CodexIndexDownloader) GotManifest() <-chan struct{} {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Fetch manifest from Codex
|
// Fetch manifest from Codex
|
||||||
url := fmt.Sprintf("%s/api/codex/v1/data/%s/network/manifest", d.codexClient.BaseURL, d.indexCid)
|
manifest, err := d.codexClient.FetchManifestWithContext(ctx, d.indexCid)
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := d.codexClient.Client.Do(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Don't close channel on error - let timeout handle it
|
// Don't close channel on error - let timeout handle it
|
||||||
return
|
// This is to fit better in the original status-go app
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// Check if request was successful
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
// Don't close channel on error - let timeout handle it
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the JSON response
|
|
||||||
var manifestResp ManifestResponse
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&manifestResp); err != nil {
|
|
||||||
// Don't close channel on error - let timeout handle it
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that the CID matches our configured indexCid
|
// Verify that the CID matches our configured indexCid
|
||||||
if manifestResp.CID != d.indexCid {
|
if manifest.CID != d.indexCid {
|
||||||
// Don't close channel on error - let timeout handle it
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the dataset size for later use - this indicates success
|
// Store the dataset size for later use - this indicates success
|
||||||
d.datasetSize = manifestResp.Manifest.DatasetSize
|
d.datasetSize = manifest.Manifest.DatasetSize
|
||||||
|
|
||||||
// Success! Close the channel to signal completion
|
// Success! Close the channel to signal completion
|
||||||
close(ch)
|
close(ch)
|
||||||
|
|||||||
@ -71,6 +71,21 @@ func (mr *MockCodexClientInterfaceMockRecorder) DownloadWithContext(ctx, cid, ou
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadWithContext", reflect.TypeOf((*MockCodexClientInterface)(nil).DownloadWithContext), ctx, cid, output)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadWithContext", reflect.TypeOf((*MockCodexClientInterface)(nil).DownloadWithContext), ctx, cid, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FetchManifestWithContext mocks base method.
|
||||||
|
func (m *MockCodexClientInterface) FetchManifestWithContext(ctx context.Context, cid string) (*communities.CodexManifest, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "FetchManifestWithContext", ctx, cid)
|
||||||
|
ret0, _ := ret[0].(*communities.CodexManifest)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchManifestWithContext indicates an expected call of FetchManifestWithContext.
|
||||||
|
func (mr *MockCodexClientInterfaceMockRecorder) FetchManifestWithContext(ctx, cid any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchManifestWithContext", reflect.TypeOf((*MockCodexClientInterface)(nil).FetchManifestWithContext), ctx, cid)
|
||||||
|
}
|
||||||
|
|
||||||
// HasCid mocks base method.
|
// HasCid mocks base method.
|
||||||
func (m *MockCodexClientInterface) HasCid(cid string) (bool, error) {
|
func (m *MockCodexClientInterface) HasCid(cid string) (bool, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user