diff --git a/clients/ipfs.go b/clients/ipfs.go index 166bd34..b204026 100644 --- a/clients/ipfs.go +++ b/clients/ipfs.go @@ -4,8 +4,10 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" + "net/http" "strings" "time" @@ -101,10 +103,19 @@ func (p *pinataClient) PinContent(ctx context.Context, filename, fileContentType } func (p *pinataClient) Unpin(ctx context.Context, cid string) error { - return p.DoRequest(ctx, Request{ + err := p.DoRequest(ctx, Request{ Method: "DELETE", URL: "/pinning/unpin/" + cid, }, nil) + if err != nil { + var httpErr *HTTPStatusError + if errors.As(err, &httpErr) && httpErr.Status == http.StatusBadRequest && + strings.Contains(httpErr.Body, "CURRENT_USER_HAS_NOT_PINNED_CID") { + glog.Warningf("IPFS CID %s not pinned by current account, skipping unpin", cid) + return nil + } + } + return err } func (p *pinataClient) List(ctx context.Context, pageSize, pageOffset int) (pl *PinList, next int, err error) { diff --git a/clients/ipfs_test.go b/clients/ipfs_test.go new file mode 100644 index 0000000..2ba73ca --- /dev/null +++ b/clients/ipfs_test.go @@ -0,0 +1,73 @@ +package clients + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPinataUnpin_NotPinnedByCurrentUser(t *testing.T) { + // Simulate Pinata returning CURRENT_USER_HAS_NOT_PINNED_CID on DELETE + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"error":{"reason":"CURRENT_USER_HAS_NOT_PINNED_CID","details":"The current user has not pinned the cid: bafkrei123","message":"The current user has not pinned the cid: bafkrei123"}}`)) + })) + defer srv.Close() + + client := &pinataClient{ + BaseClient: BaseClient{ + BaseUrl: srv.URL, + BaseHeaders: map[string]string{ + "Authorization": "Bearer test-jwt", + }, + }, + } + + err := client.Unpin(context.Background(), "bafkrei123") + require.NoError(t, err, "Unpin should not return an error when CID is not pinned by current user") +} + +func TestPinataUnpin_OtherError(t *testing.T) { + // Simulate a different 400 error (not CURRENT_USER_HAS_NOT_PINNED_CID) + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"error":{"reason":"SOME_OTHER_ERROR","message":"Something else went wrong"}}`)) + })) + defer srv.Close() + + client := &pinataClient{ + BaseClient: BaseClient{ + BaseUrl: srv.URL, + BaseHeaders: map[string]string{ + "Authorization": "Bearer test-jwt", + }, + }, + } + + err := client.Unpin(context.Background(), "bafkrei123") + require.Error(t, err, "Unpin should return an error for other HTTP 400 errors") +} + +func TestPinataUnpin_Success(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })) + defer srv.Close() + + client := &pinataClient{ + BaseClient: BaseClient{ + BaseUrl: srv.URL, + BaseHeaders: map[string]string{ + "Authorization": "Bearer test-jwt", + }, + }, + } + + err := client.Unpin(context.Background(), "bafkrei123") + require.NoError(t, err, "Unpin should succeed on 200") +}