mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 22:06:57 +00:00
716 lines
20 KiB
Go
716 lines
20 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/DeBrosOfficial/network/pkg/gateway/ctxkeys"
|
|
"github.com/DeBrosOfficial/network/pkg/ipfs"
|
|
"github.com/DeBrosOfficial/network/pkg/logging"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Mocks
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// mockIPFSClient implements the IPFSClient interface for testing.
|
|
type mockIPFSClient struct {
|
|
addResp *ipfs.AddResponse
|
|
addErr error
|
|
pinResp *ipfs.PinResponse
|
|
pinErr error
|
|
pinStatus *ipfs.PinStatus
|
|
pinStatErr error
|
|
getReader io.ReadCloser
|
|
getErr error
|
|
unpinErr error
|
|
}
|
|
|
|
func (m *mockIPFSClient) Add(_ context.Context, _ io.Reader, _ string) (*ipfs.AddResponse, error) {
|
|
return m.addResp, m.addErr
|
|
}
|
|
|
|
func (m *mockIPFSClient) Pin(_ context.Context, _ string, _ string, _ int) (*ipfs.PinResponse, error) {
|
|
return m.pinResp, m.pinErr
|
|
}
|
|
|
|
func (m *mockIPFSClient) PinStatus(_ context.Context, _ string) (*ipfs.PinStatus, error) {
|
|
return m.pinStatus, m.pinStatErr
|
|
}
|
|
|
|
func (m *mockIPFSClient) Get(_ context.Context, _ string, _ string) (io.ReadCloser, error) {
|
|
return m.getReader, m.getErr
|
|
}
|
|
|
|
func (m *mockIPFSClient) Unpin(_ context.Context, _ string) error {
|
|
return m.unpinErr
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func newTestLogger() *logging.ColoredLogger {
|
|
logger, _ := logging.NewColoredLogger(logging.ComponentStorage, false)
|
|
return logger
|
|
}
|
|
|
|
func newTestHandlers(client IPFSClient) *Handlers {
|
|
return New(client, newTestLogger(), Config{
|
|
IPFSReplicationFactor: 3,
|
|
IPFSAPIURL: "http://localhost:5001",
|
|
}, nil) // db=nil -> ownership checks bypassed
|
|
}
|
|
|
|
// withNamespace returns a request with the namespace context key set.
|
|
func withNamespace(r *http.Request, ns string) *http.Request {
|
|
ctx := context.WithValue(r.Context(), ctxkeys.NamespaceOverride, ns)
|
|
return r.WithContext(ctx)
|
|
}
|
|
|
|
// decodeBody decodes a JSON response body into a map.
|
|
func decodeBody(t *testing.T, rec *httptest.ResponseRecorder) map[string]interface{} {
|
|
t.Helper()
|
|
var body map[string]interface{}
|
|
if err := json.NewDecoder(rec.Body).Decode(&body); err != nil {
|
|
t.Fatalf("failed to decode response body: %v", err)
|
|
}
|
|
return body
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests: getNamespaceFromContext
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestGetNamespaceFromContext_Present(t *testing.T) {
|
|
h := newTestHandlers(nil)
|
|
ctx := context.WithValue(context.Background(), ctxkeys.NamespaceOverride, "my-ns")
|
|
|
|
got := h.getNamespaceFromContext(ctx)
|
|
if got != "my-ns" {
|
|
t.Errorf("expected 'my-ns', got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestGetNamespaceFromContext_Missing(t *testing.T) {
|
|
h := newTestHandlers(nil)
|
|
|
|
got := h.getNamespaceFromContext(context.Background())
|
|
if got != "" {
|
|
t.Errorf("expected empty string, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestGetNamespaceFromContext_WrongType(t *testing.T) {
|
|
h := newTestHandlers(nil)
|
|
ctx := context.WithValue(context.Background(), ctxkeys.NamespaceOverride, 12345)
|
|
|
|
got := h.getNamespaceFromContext(ctx)
|
|
if got != "" {
|
|
t.Errorf("expected empty string for wrong type, got %q", got)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests: UploadHandler
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestUploadHandler_NilIPFS(t *testing.T) {
|
|
h := newTestHandlers(nil) // nil IPFS client
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/storage/upload", nil)
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.UploadHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("expected 503, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestUploadHandler_InvalidMethod(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/storage/upload", nil)
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.UploadHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusMethodNotAllowed {
|
|
t.Errorf("expected 405, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestUploadHandler_MissingNamespace(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
// No namespace in context
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/storage/upload", strings.NewReader(`{"data":"dGVzdA=="}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.UploadHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected 401, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestUploadHandler_InvalidJSON(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/storage/upload", strings.NewReader("not json"))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.UploadHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestUploadHandler_MissingData(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/storage/upload", strings.NewReader(`{"name":"test.txt"}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.UploadHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400, got %d", rec.Code)
|
|
}
|
|
body := decodeBody(t, rec)
|
|
errMsg, _ := body["error"].(string)
|
|
if !strings.Contains(errMsg, "data field required") {
|
|
t.Errorf("expected 'data field required' error, got %q", errMsg)
|
|
}
|
|
}
|
|
|
|
func TestUploadHandler_InvalidBase64(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/storage/upload", strings.NewReader(`{"data":"!!!invalid!!!"}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.UploadHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400, got %d", rec.Code)
|
|
}
|
|
body := decodeBody(t, rec)
|
|
errMsg, _ := body["error"].(string)
|
|
if !strings.Contains(errMsg, "base64") {
|
|
t.Errorf("expected base64 decode error, got %q", errMsg)
|
|
}
|
|
}
|
|
|
|
func TestUploadHandler_PUTNotAllowed(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodPut, "/v1/storage/upload", nil)
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.UploadHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusMethodNotAllowed {
|
|
t.Errorf("expected 405, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestUploadHandler_Success(t *testing.T) {
|
|
mock := &mockIPFSClient{
|
|
addResp: &ipfs.AddResponse{
|
|
Cid: "QmTestCID1234567890123456789012345678901234",
|
|
Name: "test.txt",
|
|
Size: 4,
|
|
},
|
|
pinResp: &ipfs.PinResponse{
|
|
Cid: "QmTestCID1234567890123456789012345678901234",
|
|
Name: "test.txt",
|
|
},
|
|
}
|
|
h := newTestHandlers(mock)
|
|
|
|
// "dGVzdA==" is base64("test")
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/storage/upload", strings.NewReader(`{"data":"dGVzdA==","name":"test.txt"}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.UploadHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("expected 200, got %d; body: %s", rec.Code, rec.Body.String())
|
|
}
|
|
|
|
body := decodeBody(t, rec)
|
|
if body["cid"] != "QmTestCID1234567890123456789012345678901234" {
|
|
t.Errorf("unexpected cid: %v", body["cid"])
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests: DownloadHandler
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestDownloadHandler_NilIPFS(t *testing.T) {
|
|
h := newTestHandlers(nil)
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/storage/get/QmSomeCID", nil)
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.DownloadHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("expected 503, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestDownloadHandler_InvalidMethod(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/storage/get/QmSomeCID", nil)
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.DownloadHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusMethodNotAllowed {
|
|
t.Errorf("expected 405, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestDownloadHandler_MissingCID(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/storage/get/", nil)
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.DownloadHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400, got %d", rec.Code)
|
|
}
|
|
body := decodeBody(t, rec)
|
|
errMsg, _ := body["error"].(string)
|
|
if !strings.Contains(errMsg, "cid required") {
|
|
t.Errorf("expected 'cid required' error, got %q", errMsg)
|
|
}
|
|
}
|
|
|
|
func TestDownloadHandler_MissingNamespace(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
// No namespace in context
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/storage/get/QmSomeCID", nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.DownloadHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected 401, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestDownloadHandler_Success(t *testing.T) {
|
|
mock := &mockIPFSClient{
|
|
getReader: io.NopCloser(strings.NewReader("file contents")),
|
|
}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/storage/get/QmTestCID", nil)
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.DownloadHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("expected 200, got %d; body: %s", rec.Code, rec.Body.String())
|
|
}
|
|
if ct := rec.Header().Get("Content-Type"); ct != "application/octet-stream" {
|
|
t.Errorf("expected application/octet-stream, got %q", ct)
|
|
}
|
|
if rec.Body.String() != "file contents" {
|
|
t.Errorf("expected 'file contents', got %q", rec.Body.String())
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests: StatusHandler
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestStatusHandler_NilIPFS(t *testing.T) {
|
|
h := newTestHandlers(nil)
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/storage/status/QmSomeCID", nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.StatusHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("expected 503, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestStatusHandler_InvalidMethod(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/storage/status/QmSomeCID", nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.StatusHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusMethodNotAllowed {
|
|
t.Errorf("expected 405, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestStatusHandler_MissingCID(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/storage/status/", nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.StatusHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400, got %d", rec.Code)
|
|
}
|
|
body := decodeBody(t, rec)
|
|
errMsg, _ := body["error"].(string)
|
|
if !strings.Contains(errMsg, "cid required") {
|
|
t.Errorf("expected 'cid required' error, got %q", errMsg)
|
|
}
|
|
}
|
|
|
|
func TestStatusHandler_Success(t *testing.T) {
|
|
mock := &mockIPFSClient{
|
|
pinStatus: &ipfs.PinStatus{
|
|
Cid: "QmTestCID",
|
|
Name: "test.txt",
|
|
Status: "pinned",
|
|
Peers: []string{"peer1", "peer2"},
|
|
},
|
|
}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/storage/status/QmTestCID", nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.StatusHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("expected 200, got %d", rec.Code)
|
|
}
|
|
body := decodeBody(t, rec)
|
|
if body["cid"] != "QmTestCID" {
|
|
t.Errorf("expected cid='QmTestCID', got %v", body["cid"])
|
|
}
|
|
if body["status"] != "pinned" {
|
|
t.Errorf("expected status='pinned', got %v", body["status"])
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests: PinHandler
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestPinHandler_NilIPFS(t *testing.T) {
|
|
h := newTestHandlers(nil)
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/storage/pin", strings.NewReader(`{"cid":"QmTest"}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.PinHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("expected 503, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestPinHandler_InvalidMethod(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/storage/pin", nil)
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.PinHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusMethodNotAllowed {
|
|
t.Errorf("expected 405, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestPinHandler_InvalidJSON(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/storage/pin", strings.NewReader("bad json"))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.PinHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestPinHandler_MissingCID(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/storage/pin", strings.NewReader(`{"name":"test"}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.PinHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400, got %d", rec.Code)
|
|
}
|
|
body := decodeBody(t, rec)
|
|
errMsg, _ := body["error"].(string)
|
|
if !strings.Contains(errMsg, "cid required") {
|
|
t.Errorf("expected 'cid required' error, got %q", errMsg)
|
|
}
|
|
}
|
|
|
|
func TestPinHandler_MissingNamespace(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
// No namespace in context
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/storage/pin", strings.NewReader(`{"cid":"QmTest"}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.PinHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected 401, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestPinHandler_Success(t *testing.T) {
|
|
mock := &mockIPFSClient{
|
|
pinResp: &ipfs.PinResponse{
|
|
Cid: "QmTestCID",
|
|
Name: "test.txt",
|
|
},
|
|
}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/storage/pin", strings.NewReader(`{"cid":"QmTestCID","name":"test.txt"}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.PinHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("expected 200, got %d; body: %s", rec.Code, rec.Body.String())
|
|
}
|
|
body := decodeBody(t, rec)
|
|
if body["cid"] != "QmTestCID" {
|
|
t.Errorf("expected cid='QmTestCID', got %v", body["cid"])
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests: UnpinHandler
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestUnpinHandler_NilIPFS(t *testing.T) {
|
|
h := newTestHandlers(nil)
|
|
req := httptest.NewRequest(http.MethodDelete, "/v1/storage/unpin/QmTest", nil)
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.UnpinHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("expected 503, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestUnpinHandler_InvalidMethod(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/storage/unpin/QmTest", nil)
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.UnpinHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusMethodNotAllowed {
|
|
t.Errorf("expected 405, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestUnpinHandler_MissingCID(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/v1/storage/unpin/", nil)
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.UnpinHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400, got %d", rec.Code)
|
|
}
|
|
body := decodeBody(t, rec)
|
|
errMsg, _ := body["error"].(string)
|
|
if !strings.Contains(errMsg, "cid required") {
|
|
t.Errorf("expected 'cid required' error, got %q", errMsg)
|
|
}
|
|
}
|
|
|
|
func TestUnpinHandler_MissingNamespace(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
// No namespace in context
|
|
req := httptest.NewRequest(http.MethodDelete, "/v1/storage/unpin/QmTest", nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.UnpinHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected 401, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestUnpinHandler_POSTNotAllowed(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/storage/unpin/QmTest", nil)
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.UnpinHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusMethodNotAllowed {
|
|
t.Errorf("expected 405, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestUnpinHandler_Success(t *testing.T) {
|
|
mock := &mockIPFSClient{}
|
|
h := newTestHandlers(mock)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/v1/storage/unpin/QmTestCID", nil)
|
|
req = withNamespace(req, "test-ns")
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.UnpinHandler(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("expected 200, got %d; body: %s", rec.Code, rec.Body.String())
|
|
}
|
|
body := decodeBody(t, rec)
|
|
if body["status"] != "ok" {
|
|
t.Errorf("expected status='ok', got %v", body["status"])
|
|
}
|
|
if body["cid"] != "QmTestCID" {
|
|
t.Errorf("expected cid='QmTestCID', got %v", body["cid"])
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests: base64Decode helper
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestBase64Decode_Valid(t *testing.T) {
|
|
// "dGVzdA==" is base64("test")
|
|
data, err := base64Decode("dGVzdA==")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if string(data) != "test" {
|
|
t.Errorf("expected 'test', got %q", string(data))
|
|
}
|
|
}
|
|
|
|
func TestBase64Decode_Invalid(t *testing.T) {
|
|
_, err := base64Decode("!!!not-valid-base64!!!")
|
|
if err == nil {
|
|
t.Error("expected error for invalid base64, got nil")
|
|
}
|
|
}
|
|
|
|
func TestBase64Decode_Empty(t *testing.T) {
|
|
data, err := base64Decode("")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(data) != 0 {
|
|
t.Errorf("expected empty slice, got %d bytes", len(data))
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests: recordCIDOwnership / checkCIDOwnership / updatePinStatus with nil DB
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestRecordCIDOwnership_NilDB(t *testing.T) {
|
|
h := newTestHandlers(&mockIPFSClient{})
|
|
err := h.recordCIDOwnership(context.Background(), "cid", "ns", "name", "uploader", 100)
|
|
if err != nil {
|
|
t.Errorf("expected nil error with nil db, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCheckCIDOwnership_NilDB(t *testing.T) {
|
|
h := newTestHandlers(&mockIPFSClient{})
|
|
hasAccess, err := h.checkCIDOwnership(context.Background(), "cid", "ns")
|
|
if err != nil {
|
|
t.Errorf("expected nil error with nil db, got %v", err)
|
|
}
|
|
if !hasAccess {
|
|
t.Error("expected true (allow access) when db is nil")
|
|
}
|
|
}
|
|
|
|
func TestUpdatePinStatus_NilDB(t *testing.T) {
|
|
h := newTestHandlers(&mockIPFSClient{})
|
|
err := h.updatePinStatus(context.Background(), "cid", "ns", true)
|
|
if err != nil {
|
|
t.Errorf("expected nil error with nil db, got %v", err)
|
|
}
|
|
}
|