logos-storage-go/codexclient/codex_client_test.go
2025-10-30 02:23:23 +01:00

285 lines
9.5 KiB
Go

//go:build codex_integration
// +build codex_integration
package codexclient_test
import (
"bytes"
"context"
"errors"
"io"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"go-codex-client/codexclient"
"go-codex-client/codextestutils"
)
func upload(client codexclient.CodexClient, t *testing.T, buf *bytes.Buffer) string {
filename := "hello.txt"
cid, err := client.Upload(buf, filename)
if err != nil {
t.Fatalf("Failed to upload file: %v", err)
}
if cid == "" {
t.Fatalf("Expected non-empty CID after upload")
}
return cid
}
// CodexClientTestSuite demonstrates testify's suite functionality for CodexClient tests
type CodexClientTestSuite struct {
suite.Suite
client *codexclient.CodexClient
}
// SetupTest runs before each test method
func (suite *CodexClientTestSuite) SetupTest() {
suite.client = codextestutils.NewCodexClientTest(suite.T())
}
// TearDownTest runs after each test method
func (suite *CodexClientTestSuite) TearDownTest() {
}
// TestCodexClientTestSuite runs the test suite
func TestCodexClientTestSuite(t *testing.T) {
suite.Run(t, new(CodexClientTestSuite))
}
func (suite *CodexClientTestSuite) TestUpload_Success() {
// Act
cid, err := suite.client.Upload(bytes.NewReader([]byte("payload")), "hello.txt")
// Assert
require.NoError(suite.T(), err)
// Codex uses CIDv1 with base58btc encoding (prefix: zDv)
assert.Equal(suite.T(), "zDvZRwzmBEaJ338xaCHbKbGAJ4X41YyccS6eyorrYBbmPnWuLxCh", cid)
}
func (suite *CodexClientTestSuite) TestDownloadWithContext_Cancel() {
// skip test
suite.T().Skip("Wait for cancellation support PR to be merged in codex-go-bindings")
len := 1024 * 1024 * 50
buf := bytes.NewBuffer(make([]byte, len))
cid := upload(*suite.client, suite.T(), buf)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Millisecond)
channelError := make(chan error, 1)
go func() {
err := suite.client.DownloadWithContext(ctx, cid, io.Discard)
channelError <- err
}()
cancel()
err := <-channelError
require.Error(suite.T(), err)
// 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()) {
suite.T().Fatalf("expected context cancellation, got: %v", err)
}
}
}
func (suite *CodexClientTestSuite) TestHasCid_Success() {
const payload = "hello from codex"
cid := upload(*suite.client, suite.T(), bytes.NewBuffer([]byte(payload)))
tests := []struct {
name string
cid string
wantBool bool
}{
{"has CID returns true", cid, true},
{"has CID returns false", "zDvZRwzmBEaJ338xaCHbKbGAJ4X41YyccS6eyorrYBbmPnWuLxCe", false},
}
for _, tt := range tests {
suite.Run(tt.name, func() {
got, err := suite.client.HasCid(tt.cid)
require.NoError(suite.T(), err)
assert.Equal(suite.T(), tt.wantBool, got, "HasCid(%q) = %v, want %v", tt.cid, got, tt.wantBool)
})
}
}
func (suite *CodexClientTestSuite) TestDownload_Success() {
const payload = "hello from codex"
cid := upload(*suite.client, suite.T(), bytes.NewBuffer([]byte(payload)))
var buf bytes.Buffer
err := suite.client.Download(cid, &buf)
require.NoError(suite.T(), err)
assert.Equal(suite.T(), payload, buf.String())
}
func (suite *CodexClientTestSuite) TestRemoveCid_Success() {
const payload = "hello from codex"
cid := upload(*suite.client, suite.T(), bytes.NewBuffer([]byte(payload)))
err := suite.client.RemoveCid(cid)
require.NoError(suite.T(), err)
}
func (suite *CodexClientTestSuite) TestTriggerDownload() {
const payload = "hello from codex"
cid := upload(*suite.client, suite.T(), bytes.NewBuffer([]byte(payload)))
ctx := context.Background()
manifest, err := suite.client.TriggerDownloadWithContext(ctx, cid)
require.NoError(suite.T(), err)
assert.Equal(suite.T(), cid, manifest.Cid)
assert.Equal(suite.T(), "zDzSvJTf7mGkC3yuiVGco7Qc6s4LA8edye9inT4w2QqHnfbuRvMr", manifest.TreeCid)
assert.Equal(suite.T(), len(payload), manifest.DatasetSize)
assert.Equal(suite.T(), "hello.txt", manifest.Filename)
}
func (suite *CodexClientTestSuite) TestTriggerDownloadWithContext_Cancellation() {
suite.T().Skip("Not sure if we are going to have cancellation in trigger download")
const testCid = "zDvZRwzmTestCID"
// Cancel after 50ms (before server responds)
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
manifest, err := suite.client.TriggerDownloadWithContext(ctx, testCid)
require.Error(suite.T(), err, "expected cancellation error")
assert.Nil(suite.T(), manifest, "expected nil manifest on cancellation")
// 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()) {
suite.T().Fatalf("expected context cancellation, got: %v", err)
}
}
}
func (suite *CodexClientTestSuite) TestLocalDownload() {
const payload = "test data for local download"
cid := upload(*suite.client, suite.T(), bytes.NewBuffer([]byte(payload)))
var buf bytes.Buffer
err := suite.client.LocalDownload(cid, &buf)
require.NoError(suite.T(), err, "LocalDownload failed")
assert.Equal(suite.T(), payload, buf.String(), "Downloaded data mismatch")
}
func (suite *CodexClientTestSuite) TestLocalDownloadWithContext_Success() {
const payload = "test data for local download with context"
cid := upload(*suite.client, suite.T(), bytes.NewBuffer([]byte(payload)))
ctx := context.Background()
var buf bytes.Buffer
err := suite.client.LocalDownloadWithContext(ctx, cid, &buf)
require.NoError(suite.T(), err, "LocalDownloadWithContext failed")
assert.Equal(suite.T(), payload, buf.String(), "Downloaded data mismatch")
}
func (suite *CodexClientTestSuite) TestLocalDownloadWithContext_Cancellation() {
suite.T().Skip("Wait for cancellation support PR to be merged in codex-go-bindings")
len := 1024 * 1024 * 50
buf := bytes.NewBuffer(make([]byte, len))
cid := upload(*suite.client, suite.T(), buf)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
channelError := make(chan error, 1)
go func() {
err := suite.client.LocalDownloadWithContext(ctx, cid, io.Discard)
channelError <- err
}()
cancel()
err := <-channelError
require.Error(suite.T(), err, "Expected context cancellation error")
// 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()) {
suite.T().Fatalf("expected context cancellation, got: %v", err)
}
}
}
func (suite *CodexClientTestSuite) TestFetchManifestWithContext_Success() {
const payload = "hello from codex"
cid := upload(*suite.client, suite.T(), bytes.NewBuffer([]byte(payload)))
ctx := context.Background()
manifest, err := suite.client.FetchManifestWithContext(ctx, cid)
require.NoError(suite.T(), err, "Expected no error")
require.NotNil(suite.T(), manifest, "Expected manifest, got nil")
assert.Equal(suite.T(), cid, manifest.Cid)
assert.Equal(suite.T(), "zDzSvJTf7mGkC3yuiVGco7Qc6s4LA8edye9inT4w2QqHnfbuRvMr", manifest.TreeCid)
assert.Equal(suite.T(), len(payload), manifest.DatasetSize)
assert.Equal(suite.T(), 65536, manifest.BlockSize)
assert.True(suite.T(), !manifest.Protected, "Expected Protected to be false")
assert.Equal(suite.T(), "hello.txt", manifest.Filename)
assert.Equal(suite.T(), "text/plain", manifest.Mimetype)
}
func (suite *CodexClientTestSuite) TestFetchManifestWithContext_Cancellation() {
suite.T().Skip("Not sure if we are going to have cancellation in fetch manifest")
testCid := "zDvZRwzmTestCID"
// Create a context with a very short timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
manifest, err := suite.client.FetchManifestWithContext(ctx, testCid)
require.Error(suite.T(), err, "Expected context cancellation error")
assert.Nil(suite.T(), manifest, "Expected nil manifest on cancellation")
// 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()) {
suite.T().Fatalf("expected context cancellation, got: %v", err)
}
}
buf := bytes.NewBuffer([]byte("Hello World!"))
if buf.Len() != manifest.DatasetSize {
suite.T().Errorf("expected size %d, got %d", buf.Len(), manifest.DatasetSize)
}
defaultBlockSize := 1024 * 64
if manifest.BlockSize != defaultBlockSize {
suite.T().Errorf("expected block size %d, got %d", defaultBlockSize, manifest.BlockSize)
}
if manifest.Filename != "test.txt" {
suite.T().Errorf("expected filename %q, got %q", "test.txt", manifest.Filename)
}
if manifest.Protected {
suite.T().Errorf("expected protected to be false, got true")
}
if manifest.Mimetype != "text/plain" {
suite.T().Errorf("expected mimetype %q, got %q", "text/plain", manifest.Mimetype)
}
if manifest.TreeCid == "" {
suite.T().Errorf("expected non-empty TreeCid")
}
}