Skip to content
Open
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
29 changes: 29 additions & 0 deletions src/functions/auctions.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,39 @@
import { endpoints } from "../constants/endpoints.constant";
import APIClient from "../lib/api-client";
import AppError from "../lib/app-error";
import { withValidation } from "../lib/with-validation";
import type { Auction, AuctionResult } from "../types/auctions";
import type { Config } from "../types/shared";

function validateAuctionParams(body: Auction): void {
if (!body || !body.auctions) {
throw new AppError(
400,
"Invalid auction request: body and auctions array are required.",
undefined,
);
}

for (const auction of body.auctions) {
const hasCategory = auction.category !== undefined;
const hasProducts = auction.products !== undefined;
const hasSearchQuery = auction.searchQuery !== undefined;

const paramCount = [hasCategory, hasProducts, hasSearchQuery].filter(Boolean).length;

if (paramCount > 2) {
throw new AppError(
400,
"Cannot pass all three parameters: category, products, and searchQuery. Only two at most are allowed.",
undefined,
);
}
}
}

async function handler(body: Auction, config: Config): Promise<AuctionResult> {
validateAuctionParams(body);

const url = `${config.host}/${endpoints.auctions}`;
const result = await APIClient.post(url.toString(), body, config);

Expand Down
209 changes: 201 additions & 8 deletions test/auctions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ import {
import AppError from "../src/lib/app-error";
import type { Auction } from "../src/types/auctions";

const validAuction: Auction = {
auctions: [
{
type: "listings",
slots: 1,
category: { id: "test-category" },
},
],
};

describe("createAuction", () => {
let topsortClient: TopsortClient;
beforeAll(() => mswServer.listen());
Expand All @@ -20,7 +30,7 @@ describe("createAuction", () => {

it("should handle authentication error", async () => {
returnStatus(401, `${baseURL}/${endpoints.auctions}`);
expect(topsortClient.createAuction({} as Auction)).rejects.toEqual({
expect(topsortClient.createAuction(validAuction)).rejects.toEqual({
status: 401,
retry: false,
statusText: "Unauthorized",
Expand All @@ -30,7 +40,7 @@ describe("createAuction", () => {

it("should handle retryable error", async () => {
returnStatus(429, `${baseURL}/${endpoints.auctions}`);
expect(topsortClient.createAuction({} as Auction)).rejects.toEqual({
expect(topsortClient.createAuction(validAuction)).rejects.toEqual({
status: 429,
retry: true,
statusText: "Too Many Requests",
Expand All @@ -40,7 +50,7 @@ describe("createAuction", () => {

it("should handle server error", async () => {
returnStatus(500, `${baseURL}/${endpoints.auctions}`);
expect(topsortClient.createAuction({} as Auction)).rejects.toEqual({
expect(topsortClient.createAuction(validAuction)).rejects.toEqual({
status: 500,
retry: true,
statusText: "Internal Server Error",
Expand All @@ -55,7 +65,7 @@ describe("createAuction", () => {
host: "https://demo.api.topsort.com",
});

expect(topsortClient.createAuction({} as Auction)).resolves.toEqual({
expect(topsortClient.createAuction(validAuction)).resolves.toEqual({
results: [
{
resultType: "listings",
Expand All @@ -73,13 +83,13 @@ describe("createAuction", () => {

it("should handle fetch error", async () => {
returnError(`${baseURL}/${endpoints.auctions}`);
expect(async () => topsortClient.createAuction({} as Auction)).toThrow(AppError);
expect(async () => topsortClient.createAuction(validAuction)).toThrow(AppError);
});

it("should handle invalid URL error", async () => {
const invalidHost = "invalid-url";
topsortClient = new TopsortClient({ apiKey: "apiKey", host: invalidHost });
expect(async () => topsortClient.createAuction({} as Auction)).toThrow(AppError);
expect(async () => topsortClient.createAuction(validAuction)).toThrow(AppError);
});

it("should handle success auction with timeout", async () => {
Expand All @@ -90,7 +100,7 @@ describe("createAuction", () => {
timeout: 50,
});

expect(topsortClient.createAuction({} as Auction)).resolves.toEqual({
expect(topsortClient.createAuction(validAuction)).resolves.toEqual({
results: [
{
resultType: "listings",
Expand All @@ -114,7 +124,118 @@ describe("createAuction", () => {
fetchOptions: { keepalive: false, cache: "no-cache" },
});

expect(topsortClient.createAuction({} as Auction)).resolves.toEqual({
expect(topsortClient.createAuction(validAuction)).resolves.toEqual({
results: [
{
resultType: "listings",
winners: [],
error: false,
},
{
resultType: "banners",
winners: [],
error: false,
},
],
});
});

it("should throw error when all three parameters (category, products, searchQuery) are provided", async () => {
const invalidAuction: Auction = {
auctions: [
{
type: "listings",
slots: 1,
category: { id: "cat123" },
products: { ids: ["prod1", "prod2"] },
searchQuery: "test query",
},
],
};

try {
await topsortClient.createAuction(invalidAuction);
expect(true).toBe(false); // Should not reach here
} catch (error) {
expect(error).toBeInstanceOf(AppError);
expect((error as AppError).statusText).toBe(
"Cannot pass all three parameters: category, products, and searchQuery. Only two at most are allowed.",
);
}
});

it("should allow passing category and products without searchQuery", async () => {
returnAuctionSuccess(`${baseURL}/${endpoints.auctions}`);
const validAuction: Auction = {
auctions: [
{
type: "listings",
slots: 1,
category: { id: "cat123" },
products: { ids: ["prod1", "prod2"] },
},
],
};

expect(topsortClient.createAuction(validAuction)).resolves.toEqual({
results: [
{
resultType: "listings",
winners: [],
error: false,
},
{
resultType: "banners",
winners: [],
error: false,
},
],
});
});

it("should allow passing category and searchQuery without products", async () => {
returnAuctionSuccess(`${baseURL}/${endpoints.auctions}`);
const validAuction: Auction = {
auctions: [
{
type: "listings",
slots: 1,
category: { id: "cat123" },
searchQuery: "test query",
},
],
};

expect(topsortClient.createAuction(validAuction)).resolves.toEqual({
results: [
{
resultType: "listings",
winners: [],
error: false,
},
{
resultType: "banners",
winners: [],
error: false,
},
],
});
});

it("should allow passing products and searchQuery without category", async () => {
returnAuctionSuccess(`${baseURL}/${endpoints.auctions}`);
const validAuction: Auction = {
auctions: [
{
type: "listings",
slots: 1,
products: { ids: ["prod1", "prod2"] },
searchQuery: "test query",
},
],
};

expect(topsortClient.createAuction(validAuction)).resolves.toEqual({
results: [
{
resultType: "listings",
Expand All @@ -129,4 +250,76 @@ describe("createAuction", () => {
],
});
});

it("should allow passing only one parameter", async () => {
returnAuctionSuccess(`${baseURL}/${endpoints.auctions}`);
const validAuction: Auction = {
auctions: [
{
type: "listings",
slots: 1,
category: { id: "cat123" },
},
],
};

expect(topsortClient.createAuction(validAuction)).resolves.toEqual({
results: [
{
resultType: "listings",
winners: [],
error: false,
},
{
resultType: "banners",
winners: [],
error: false,
},
],
});
});

it("should validate all auctions in the array", async () => {
const invalidAuction: Auction = {
auctions: [
{
type: "listings",
slots: 1,
category: { id: "cat123" },
},
{
type: "banners",
slots: 1,
slotId: "slot1",
category: { id: "cat456" },
products: { ids: ["prod1"] },
searchQuery: "test",
},
],
};

try {
await topsortClient.createAuction(invalidAuction);
expect(true).toBe(false); // Should not reach here
} catch (error) {
expect(error).toBeInstanceOf(AppError);
expect((error as AppError).statusText).toBe(
"Cannot pass all three parameters: category, products, and searchQuery. Only two at most are allowed.",
);
}
});

it("should throw error when auctions array is missing", async () => {
const invalidAuction = {} as Auction;

try {
await topsortClient.createAuction(invalidAuction);
expect(true).toBe(false); // Should not reach here
} catch (error) {
expect(error).toBeInstanceOf(AppError);
expect((error as AppError).statusText).toBe(
"Invalid auction request: body and auctions array are required.",
);
}
});
});