Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0085579
Minting claim actions
prxt6529 Mar 13, 2026
0cf1ed2
WIP
prxt6529 Mar 13, 2026
a7e67ce
WIP
prxt6529 Mar 13, 2026
adf20ba
WIP
prxt6529 Mar 13, 2026
8e872ab
Merge branch 'main' into minting-claims-actions
prxt6529 Mar 13, 2026
73eb8fb
WIP
prxt6529 Mar 13, 2026
bb5c4d7
WIP
prxt6529 Mar 13, 2026
4ed4ef7
Merge branch 'main' into minting-claims-actions
prxt6529 Mar 13, 2026
0bd2114
WIP - auto select phase
prxt6529 Mar 16, 2026
2ff8757
WIP
prxt6529 Mar 16, 2026
ef718f8
Merge branch 'main' into minting-claims-actions
prxt6529 Mar 16, 2026
cb3d24f
WIP
prxt6529 Mar 16, 2026
9a4dddc
WIP
prxt6529 Mar 16, 2026
a6c7617
WIP
prxt6529 Mar 16, 2026
ebbe1ce
WIP
prxt6529 Mar 16, 2026
8da72cf
WIP
prxt6529 Mar 16, 2026
c7a10eb
WIP
prxt6529 Mar 16, 2026
5cf5f21
WIP
prxt6529 Mar 16, 2026
dde5e47
Merge branch 'main' into minting-claims-actions
prxt6529 Mar 16, 2026
465d9cf
Merge branch 'main' into minting-claims-actions
prxt6529 Mar 17, 2026
1bc006d
WIP
prxt6529 Mar 17, 2026
8f06e72
WIP
prxt6529 Mar 17, 2026
73d794a
Merge branch 'main' into minting-claims-actions
prxt6529 Mar 17, 2026
29bd6c0
WIP
prxt6529 Mar 17, 2026
5e6c4d7
WIP
prxt6529 Mar 17, 2026
55a10e3
WIP
prxt6529 Mar 17, 2026
08230d0
WIP
prxt6529 Mar 17, 2026
74a5260
Merge branch 'main' into minting-claims-actions
prxt6529 Mar 17, 2026
3deb8e0
WIP
prxt6529 Mar 17, 2026
669d810
WIP
prxt6529 Mar 17, 2026
ce401dc
Merge branch 'main' into minting-claims-actions
prxt6529 Mar 17, 2026
b39de5c
Merge branch 'main' into minting-claims-actions
prxt6529 Mar 17, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { useMemo, useState } from "react";

const mockDownload = jest.fn();
const mockUseDownloader = jest.fn();
const mockGetStagingAuth = jest.fn();
const mockGetAuthJwt = jest.fn();

jest.mock("react-use-downloader", () => ({
__esModule: true,
default: (...args: any[]) => mockUseDownloader(...args),
}));

jest.mock("@/services/auth/auth.utils", () => ({
getStagingAuth: () => mockGetStagingAuth(),
getAuthJwt: () => mockGetAuthJwt(),
}));

jest.mock(
"@/components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription",
() => ({
Expand All @@ -31,7 +46,8 @@ jest.mock(
data-testid="upload-airdrops-button"
onClick={() =>
props.onUpload("contract", "123", "0x123,5\n0x456,10")
}>
}
>
Upload
</button>
</div>
Expand All @@ -47,14 +63,23 @@ jest.mock(
<div data-testid="Confirm Token ID">
<button
data-testid="confirm-token-id-button"
onClick={() => props.onConfirm("123")}>
onClick={() => props.onConfirm("123")}
>
Confirm
</button>
</div>
) : null,
})
);

jest.mock("@/contexts/SeizeSettingsContext", () => ({
useSeizeSettings: () => ({
seizeSettings: {
distribution_admin_wallets: ["0x1"],
},
}),
}));

const {
isSubscriptionsAdmin,
} = require("@/components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription");
Expand All @@ -64,6 +89,35 @@ const authCtx = {
setToast: jest.fn(),
} as any;

beforeEach(() => {
jest.clearAllMocks();
authCtx.setToast.mockClear();
mockDownload.mockReset();
mockUseDownloader.mockReset();
mockGetStagingAuth.mockReset();
mockGetAuthJwt.mockReset();
mockDownload.mockResolvedValue(undefined);
mockGetStagingAuth.mockReturnValue(null);
mockGetAuthJwt.mockReturnValue(null);
mockUseDownloader.mockReturnValue({
download: mockDownload,
isInProgress: false,
error: null,
});
jest
.spyOn(require("@/services/api/common-api"), "commonApiFetch")
.mockResolvedValue({
photos_count: 0,
is_normalized: false,
automatic_airdrops_addresses: 0,
automatic_airdrops_count: 0,
});
});

afterEach(() => {
jest.restoreAllMocks();
});

function TestWrapper({
initialTokenId = null,
}: {
Expand Down Expand Up @@ -114,6 +168,9 @@ test("renders admin buttons after token id is confirmed", async () => {
expect(screen.getByText("Reset Subscriptions")).toBeInTheDocument();
expect(screen.getByText("Upload Distribution Photos")).toBeInTheDocument();
expect(screen.getByText("Upload Automatic Airdrops")).toBeInTheDocument();
expect(
screen.getByRole("button", { name: /download automatic airdrops csv/i })
).toBeInTheDocument();
expect(screen.getByText("Finalize Distribution")).toBeInTheDocument();
});
});
Expand Down Expand Up @@ -260,7 +317,8 @@ test("uploadAutomaticAirdrops posts CSV data and shows success toast", async ()
.mockResolvedValue({
photos_count: 0,
is_normalized: false,
automatic_airdrops: 5,
automatic_airdrops_addresses: 2,
automatic_airdrops_count: 15,
});

render(<TestWrapper initialTokenId="123" />);
Expand Down Expand Up @@ -350,3 +408,83 @@ test("uploadAutomaticAirdrops handles exceptions", async () => {
});
});
});

test("disables automatic airdrops csv download when there are no values", async () => {
(isSubscriptionsAdmin as jest.Mock).mockReturnValue(true);

render(<TestWrapper initialTokenId="123" />);

const downloadButton = await screen.findByRole("button", {
name: /download automatic airdrops csv/i,
});

expect(downloadButton).toBeDisabled();
});

test("downloads automatic airdrops csv with the response filename", async () => {
const user = userEvent.setup();
(isSubscriptionsAdmin as jest.Mock).mockReturnValue(true);
mockGetStagingAuth.mockReturnValue("staging-token");
mockGetAuthJwt.mockReturnValue("wallet-token");

jest
.spyOn(require("@/services/api/common-api"), "commonApiFetch")
.mockResolvedValue({
photos_count: 0,
is_normalized: false,
automatic_airdrops_addresses: 2,
automatic_airdrops_count: 15,
});

render(<TestWrapper initialTokenId="123" />);

const downloadButton = await screen.findByRole("button", {
name: /download automatic airdrops csv/i,
});

await waitFor(() => expect(downloadButton).toBeEnabled());
await user.click(downloadButton);

await waitFor(() => {
expect(mockUseDownloader).toHaveBeenCalledWith();
expect(mockDownload).toHaveBeenCalledWith(
"https://api.test.6529.io/api/distributions/0x33FD426905F149f8376e227d0C9D3340AaD17aF1/123/automatic_airdrops",
"automatic_airdrops_123.csv",
undefined,
{
headers: {
"x-6529-auth": "staging-token",
Authorization: "Bearer wallet-token",
},
}
);
});
});

test("shows an admin-facing error when the downloader reports one", async () => {
(isSubscriptionsAdmin as jest.Mock).mockReturnValue(true);

jest
.spyOn(require("@/services/api/common-api"), "commonApiFetch")
.mockResolvedValue({
photos_count: 0,
is_normalized: false,
automatic_airdrops_addresses: 1,
automatic_airdrops_count: 5,
});

mockUseDownloader.mockReturnValue({
download: mockDownload,
isInProgress: false,
error: { errorMessage: "wallet is not authorized" },
});

render(<TestWrapper initialTokenId="123" />);

await waitFor(() => {
expect(authCtx.setToast).toHaveBeenCalledWith({
type: "error",
message: "wallet is not authorized",
});
});
});
101 changes: 101 additions & 0 deletions __tests__/services/api/memes-minting-claims-api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { MEMES_CONTRACT } from "@/constants/constants";
import {
getMemesMintingClaimActions,
getMemesMintingClaimActionTypes,
upsertMemesMintingClaimAction,
} from "@/services/api/memes-minting-claims-api";
import { getAuthJwt, getStagingAuth } from "@/services/auth/auth.utils";

jest.mock("@/services/auth/auth.utils", () => ({
getStagingAuth: jest.fn(),
getAuthJwt: jest.fn(),
}));

const fetchMock = jest.fn() as jest.MockedFunction<typeof fetch>;
globalThis.fetch = fetchMock;

describe("memes-minting-claims-api", () => {
const encodedContract = encodeURIComponent(MEMES_CONTRACT);

beforeEach(() => {
jest.clearAllMocks();
fetchMock.mockReset();
(getStagingAuth as jest.Mock).mockReturnValue(null);
(getAuthJwt as jest.Mock).mockReturnValue(null);
});

it("fetches supported MEMES claim action types", async () => {
fetchMock.mockResolvedValue({
ok: true,
json: async () => ({
contract: MEMES_CONTRACT,
action_types: ["ARTIST_AIRDROP", "TEAM_AIRDROP"],
}),
});

const response = await getMemesMintingClaimActionTypes();

expect(fetchMock).toHaveBeenCalledWith(
`https://api.test.6529.io/api/minting-claims/actions/${encodedContract}/types`,
expect.objectContaining({
method: "GET",
})
);
expect(response.action_types).toEqual(["ARTIST_AIRDROP", "TEAM_AIRDROP"]);
});

it("fetches MEMES claim actions by claim id", async () => {
fetchMock.mockResolvedValue({
ok: true,
json: async () => ({
contract: MEMES_CONTRACT,
claim_id: 123,
actions: [{ action: "ARTIST_AIRDROP", completed: false }],
}),
});

const response = await getMemesMintingClaimActions(123);

expect(fetchMock).toHaveBeenCalledWith(
`https://api.test.6529.io/api/minting-claims/actions/${encodedContract}/123`,
expect.objectContaining({
method: "GET",
})
);
expect(response.actions).toHaveLength(1);
expect(response.actions[0].action).toBe("ARTIST_AIRDROP");
});

it("upserts a MEMES claim action", async () => {
(getAuthJwt as jest.Mock).mockReturnValue("jwt");
fetchMock.mockResolvedValue({
ok: true,
json: async () => ({
contract: MEMES_CONTRACT,
claim_id: 123,
actions: [{ action: "ARTIST_AIRDROP", completed: true }],
}),
});

const response = await upsertMemesMintingClaimAction(123, {
action: "ARTIST_AIRDROP",
completed: true,
});

expect(fetchMock).toHaveBeenCalledWith(
`https://api.test.6529.io/api/minting-claims/actions/${encodedContract}/123`,
expect.objectContaining({
method: "POST",
headers: expect.objectContaining({
Authorization: "Bearer jwt",
"Content-Type": "application/json",
}),
body: JSON.stringify({
action: "ARTIST_AIRDROP",
completed: true,
}),
})
);
expect(response.actions[0].completed).toBe(true);
});
});
Loading
Loading