Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- name: Setup Biome
uses: biomejs/setup-biome@v2
with:
version: latest
version: 1.9.4
- name: Run Biome
run: biome ci --formatter-enabled=true --linter-enabled=false --organize-imports-enabled=false src tests
tests:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pinata",
"version": "2.4.8",
"version": "2.4.9",
"description": "The official Pinata SDK",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand Down
7 changes: 5 additions & 2 deletions src/core/classes/analytics/AnalyticsBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ export class AnalyticsBuilder<T extends AnalyticsQuery, R> {
throw new Error("getAnalytics method must be implemented in derived class");
}

then(onfulfilled?: ((value: R) => any) | null): Promise<any> {
return this.getAnalytics().then(onfulfilled);
then(
onfulfilled?: ((value: R) => any) | null,
onrejected?: ((reason: any) => any) | null,
): Promise<any> {
return this.getAnalytics().then(onfulfilled, onrejected);
}
}
6 changes: 5 additions & 1 deletion src/core/classes/analytics/AnalyticsFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,11 @@ export class AnalyticsFilter {

then(
onfulfilled?: ((value: TopAnalyticsResponse) => any) | null,
onrejected?: ((reason: any) => any) | null,
): Promise<any> {
return analyticsTopUsage(this.config, this.query).then(onfulfilled);
return analyticsTopUsage(this.config, this.query).then(
onfulfilled,
onrejected,
);
}
}
7 changes: 5 additions & 2 deletions src/core/classes/files/FilterFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ export class FilterFiles {
return this;
}

then(onfulfilled?: ((value: FileListResponse) => any) | null): Promise<any> {
return this.fetchPage().then(onfulfilled);
then(
onfulfilled?: ((value: FileListResponse) => any) | null,
onrejected?: ((reason: any) => any) | null,
): Promise<any> {
return this.fetchPage().then(onfulfilled, onrejected);
}

private async fetchPage(): Promise<FileListResponse> {
Expand Down
7 changes: 5 additions & 2 deletions src/core/classes/files/FilterQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,11 @@ export class FilterQueue {
return this;
}

then(onfulfilled?: ((value: PinQueueResponse) => any) | null): Promise<any> {
return queue(this.config, this.query).then(onfulfilled);
then(
onfulfilled?: ((value: PinQueueResponse) => any) | null,
onrejected?: ((reason: any) => any) | null,
): Promise<any> {
return queue(this.config, this.query).then(onfulfilled, onrejected);
}

// rate limit, hopefully temporary?
Expand Down
6 changes: 5 additions & 1 deletion src/core/classes/gateways/OptimizeImageCreateSignedUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ export class OptimizeImageCreateAccessLink {
return this;
}

then(onfulfilled?: ((value: string) => any) | null): Promise<any> {
then(
onfulfilled?: ((value: string) => any) | null,
onrejected?: ((reason: any) => any) | null,
): Promise<any> {
return createAccessLink(this.config, this.urlOpts, this.imgOpts).then(
onfulfilled,
onrejected,
);
}
}
6 changes: 5 additions & 1 deletion src/core/classes/gateways/OptimizeImageGetCid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ export class OptimizeImageGetCid {
return this;
}

then(onfulfilled?: ((value: GetCIDResponse) => any) | null): Promise<any> {
then(
onfulfilled?: ((value: GetCIDResponse) => any) | null,
onrejected?: ((reason: any) => any) | null,
): Promise<any> {
return getCid(this.config, this.cid, this.gatewayType, this.options).then(
onfulfilled,
onrejected,
);
}
}
3 changes: 2 additions & 1 deletion src/core/classes/groups/GroupsFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,14 @@ export class FilterGroups {

then(
onfulfilled?: ((value: GroupListResponse) => any) | null,
onrejected?: ((reason: any) => any) | null,
): Promise<GroupListResponse> {
return this.fetchPage()
.then((response) => {
this.nextPageToken = response.next_page_token;
return response;
})
.then(onfulfilled);
.then(onfulfilled, onrejected);
}

private async fetchPage(): Promise<GroupListResponse> {
Expand Down
7 changes: 5 additions & 2 deletions src/core/classes/keys/FilterKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ export class FilterKeys {
return this;
}

then(onfulfilled?: ((value: KeyListItem[]) => any) | null): Promise<any> {
return listKeys(this.config, this.query).then(onfulfilled);
then(
onfulfilled?: ((value: KeyListItem[]) => any) | null,
onrejected?: ((reason: any) => any) | null,
): Promise<any> {
return listKeys(this.config, this.query).then(onfulfilled, onrejected);
}

// private async rateLimit(): Promise<void> {
Expand Down
198 changes: 198 additions & 0 deletions tests/gateway/optimizeImagePromiseHandling.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { PinataSDK } from "../../src/core/pinataSDK";
import type { PinataConfig } from "../../src/core/types";
import {
PinataError,
NetworkError,
AuthenticationError,
ValidationError,
} from "../../src/utils/custom-errors";

describe("OptimizeImage Promise Handling", () => {
let originalFetch: typeof fetch;

beforeEach(() => {
originalFetch = global.fetch;
});

afterEach(() => {
global.fetch = originalFetch;
jest.clearAllMocks();
});

const mockConfig: PinataConfig = {
pinataJwt: "test_jwt",
pinataGateway: "https://test.mypinata.cloud",
};

describe("OptimizeImageGetCid", () => {
it("should properly handle promise rejections when content is not pinned", async () => {
// Mock a 404 response to simulate content not being pinned
global.fetch = jest.fn().mockResolvedValueOnce({
ok: false,
status: 404,
text: jest.fn().mockResolvedValueOnce("Not Found"),
});

const pinata = new PinataSDK(mockConfig);
const cid = "QmcUNH54shLGC8tmkzt7ounwFc2W2bg3xLqbmdq5wHHj7R";

let errorCaught = false;
let errorMessage = "";

try {
await pinata.gateways.public.get(cid);
} catch (error) {
errorCaught = true;
errorMessage = error instanceof Error ? error.message : String(error);
}

expect(errorCaught).toBe(true);
expect(errorMessage).toContain("HTTP error");
});

it("should properly handle promise rejections with .then() and .catch()", async () => {
// Mock a 404 response to simulate content not being pinned
global.fetch = jest.fn().mockResolvedValueOnce({
ok: false,
status: 404,
text: jest.fn().mockResolvedValueOnce("Not Found"),
});

const pinata = new PinataSDK(mockConfig);
const cid = "QmcUNH54shLGC8tmkzt7ounwFc2W2bg3xLqbmdq5wHHj7R";

let tryBlockExecuted = false;
let catchBlockExecuted = false;
let doneBlockExecuted = false;
let errorBlockExecuted = false;

try {
await pinata.gateways.public.get(cid);
tryBlockExecuted = true;
} catch {
catchBlockExecuted = true;
}

// Test the promise chain behavior
await pinata.gateways.public
.get(cid)
.then(() => {
doneBlockExecuted = true;
})
.catch(() => {
errorBlockExecuted = true;
});

expect(tryBlockExecuted).toBe(false);
expect(catchBlockExecuted).toBe(true);
expect(doneBlockExecuted).toBe(false);
expect(errorBlockExecuted).toBe(true);
});

it("should properly handle promise rejections with image optimization", async () => {
// Mock a 404 response to simulate content not being pinned
global.fetch = jest.fn().mockResolvedValueOnce({
ok: false,
status: 404,
text: jest.fn().mockResolvedValueOnce("Not Found"),
});

const pinata = new PinataSDK(mockConfig);
const cid = "QmcUNH54shLGC8tmkzt7ounwFc2W2bg3xLqbmdq5wHHj7R";

let errorCaught = false;

try {
await pinata.gateways.public
.get(cid)
.optimizeImage({ width: 100, height: 100 });
} catch (error) {
errorCaught = true;
}

expect(errorCaught).toBe(true);
});

it("should properly handle authentication errors", async () => {
// Mock a 401 response to simulate authentication error
global.fetch = jest.fn().mockResolvedValueOnce({
ok: false,
status: 401,
text: jest.fn().mockResolvedValueOnce("Unauthorized"),
});

const pinata = new PinataSDK(mockConfig);
const cid = "QmcUNH54shLGC8tmkzt7ounwFc2W2bg3xLqbmdq5wHHj7R";

let errorCaught = false;
let isAuthenticationError = false;

try {
await pinata.gateways.public.get(cid);
} catch (error) {
errorCaught = true;
isAuthenticationError = error instanceof AuthenticationError;
}

expect(errorCaught).toBe(true);
expect(isAuthenticationError).toBe(true);
});
});

describe("OptimizeImageCreateAccessLink", () => {
it("should properly handle promise rejections", async () => {
// Mock a 401 response to simulate authentication error
global.fetch = jest.fn().mockResolvedValueOnce({
ok: false,
status: 401,
text: jest.fn().mockResolvedValueOnce("Unauthorized"),
});

const pinata = new PinataSDK(mockConfig);
const options = {
cid: "QmcUNH54shLGC8tmkzt7ounwFc2W2bg3xLqbmdq5wHHj7R",
expires: 3600,
};

let errorCaught = false;
let isAuthenticationError = false;

try {
await pinata.gateways.private.createAccessLink(options);
} catch (error) {
errorCaught = true;
isAuthenticationError = error instanceof AuthenticationError;
}

expect(errorCaught).toBe(true);
expect(isAuthenticationError).toBe(true);
});

it("should properly handle promise rejections with image optimization", async () => {
// Mock a 401 response to simulate authentication error
global.fetch = jest.fn().mockResolvedValueOnce({
ok: false,
status: 401,
text: jest.fn().mockResolvedValueOnce("Unauthorized"),
});

const pinata = new PinataSDK(mockConfig);
const options = {
cid: "QmcUNH54shLGC8tmkzt7ounwFc2W2bg3xLqbmdq5wHHj7R",
expires: 3600,
};

let errorCaught = false;

try {
await pinata.gateways.private
.createAccessLink(options)
.optimizeImage({ width: 100, height: 100 });
} catch (error) {
errorCaught = true;
}

expect(errorCaught).toBe(true);
});
});
});