mirror of
https://github.com/duhanbalci/iyzico.git
synced 2026-03-03 20:29:18 +00:00
init
This commit is contained in:
49
tests/fixtures/card.ts
vendored
Normal file
49
tests/fixtures/card.ts
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Card storage test fixtures with randomized personal data
|
||||
*/
|
||||
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type { Card, CardCreateRequest, CardCreateWithUserKeyRequest } from '../../src/types';
|
||||
|
||||
/**
|
||||
* Generates a test card with randomized holder name
|
||||
* Card number is fixed to iyzico sandbox test card
|
||||
*/
|
||||
export function createTestCard(): Card {
|
||||
return {
|
||||
cardAlias: faker.helpers.arrayElement(['My Card', 'Personal Card', 'Main Card', 'Default Card']),
|
||||
cardNumber: '5528790000000008', // iyzico sandbox test card
|
||||
expireYear: '2030',
|
||||
expireMonth: '12',
|
||||
cardHolderName: faker.person.fullName(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a test card create request with randomized data
|
||||
*/
|
||||
export function createTestCardCreateRequest(): CardCreateRequest {
|
||||
return {
|
||||
locale: 'tr',
|
||||
conversationId: faker.string.uuid(),
|
||||
email: faker.internet.email().toLowerCase(),
|
||||
externalId: `external-${faker.string.alphanumeric(10)}`,
|
||||
card: createTestCard(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a test card create request with user key
|
||||
*/
|
||||
export function createCardCreateWithUserKeyRequest(cardUserKey: string): CardCreateWithUserKeyRequest {
|
||||
return {
|
||||
locale: 'tr',
|
||||
conversationId: faker.string.uuid(),
|
||||
cardUserKey,
|
||||
card: createTestCard(),
|
||||
};
|
||||
}
|
||||
|
||||
// Legacy exports for backward compatibility (static versions that generate once)
|
||||
export const testCard = createTestCard();
|
||||
export const testCardCreateRequest = createTestCardCreateRequest();
|
||||
206
tests/fixtures/payment.ts
vendored
Normal file
206
tests/fixtures/payment.ts
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
/**
|
||||
* Payment test fixtures with randomized personal data
|
||||
*/
|
||||
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type {
|
||||
ThreeDSInitializeRequest,
|
||||
Non3DPaymentRequest,
|
||||
PaymentCard,
|
||||
Buyer,
|
||||
BillingAddress,
|
||||
ShippingAddress,
|
||||
BasketItem,
|
||||
} from '../../src/types';
|
||||
|
||||
/**
|
||||
* Generates a valid Turkish identity number (TC Kimlik No)
|
||||
* Uses a simple algorithm that produces valid-looking numbers
|
||||
*/
|
||||
function generateTurkishIdentityNumber(): string {
|
||||
const digits: number[] = [];
|
||||
|
||||
// First digit cannot be 0
|
||||
digits.push(faker.number.int({ min: 1, max: 9 }));
|
||||
|
||||
// Generate next 8 random digits
|
||||
for (let i = 1; i < 9; i++) {
|
||||
digits.push(faker.number.int({ min: 0, max: 9 }));
|
||||
}
|
||||
|
||||
// Calculate 10th digit
|
||||
const oddSum = digits[0] + digits[2] + digits[4] + digits[6] + digits[8];
|
||||
const evenSum = digits[1] + digits[3] + digits[5] + digits[7];
|
||||
const digit10 = (oddSum * 7 - evenSum) % 10;
|
||||
digits.push(digit10 < 0 ? digit10 + 10 : digit10);
|
||||
|
||||
// Calculate 11th digit
|
||||
const allSum = digits.reduce((sum, d) => sum + d, 0);
|
||||
digits.push(allSum % 10);
|
||||
|
||||
return digits.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a Turkish phone number in the format +905XXXXXXXXX
|
||||
*/
|
||||
function generateTurkishPhoneNumber(): string {
|
||||
const areaCode = faker.helpers.arrayElement(['530', '531', '532', '533', '534', '535', '536', '537', '538', '539', '540', '541', '542', '543', '544', '545', '546', '547', '548', '549', '550', '551', '552', '553', '554', '555', '556', '557', '558', '559']);
|
||||
const number = faker.string.numeric(7);
|
||||
return `+90${areaCode}${number}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random IP address
|
||||
*/
|
||||
function generateIpAddress(): string {
|
||||
return faker.internet.ipv4();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a test payment card with randomized holder name
|
||||
* Card number is fixed to iyzico sandbox test card
|
||||
*/
|
||||
export function createTestCard(): PaymentCard {
|
||||
return {
|
||||
cardHolderName: faker.person.fullName(),
|
||||
cardNumber: '5528790000000008', // iyzico sandbox test card
|
||||
expireMonth: '12',
|
||||
expireYear: '2030',
|
||||
cvc: '123',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a test buyer with randomized personal data
|
||||
*/
|
||||
export function createTestBuyer(): Buyer {
|
||||
const firstName = faker.person.firstName();
|
||||
const lastName = faker.person.lastName();
|
||||
|
||||
return {
|
||||
id: `BY${faker.string.alphanumeric(6).toUpperCase()}`,
|
||||
name: firstName,
|
||||
surname: lastName,
|
||||
identityNumber: generateTurkishIdentityNumber(),
|
||||
email: faker.internet.email({ firstName, lastName }).toLowerCase(),
|
||||
gsmNumber: generateTurkishPhoneNumber(),
|
||||
registrationAddress: `${faker.location.streetAddress()}, ${faker.location.city()}`,
|
||||
city: faker.location.city(),
|
||||
country: 'Turkey',
|
||||
ip: generateIpAddress(),
|
||||
zipCode: faker.location.zipCode('#####'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a test billing address with randomized data
|
||||
*/
|
||||
export function createTestBillingAddress(): BillingAddress {
|
||||
return {
|
||||
contactName: faker.person.fullName(),
|
||||
city: faker.location.city(),
|
||||
country: 'Turkey',
|
||||
address: `${faker.location.streetAddress()}, ${faker.location.city()}`,
|
||||
zipCode: faker.location.zipCode('#####'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a test shipping address with randomized data
|
||||
*/
|
||||
export function createTestShippingAddress(): ShippingAddress {
|
||||
return {
|
||||
contactName: faker.person.fullName(),
|
||||
city: faker.location.city(),
|
||||
country: 'Turkey',
|
||||
address: `${faker.location.streetAddress()}, ${faker.location.city()}`,
|
||||
zipCode: faker.location.zipCode('#####'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates test basket items
|
||||
*/
|
||||
export function createTestBasketItems(): BasketItem[] {
|
||||
return [
|
||||
{
|
||||
id: `BI${faker.string.alphanumeric(3).toUpperCase()}`,
|
||||
name: faker.commerce.productName(),
|
||||
category1: faker.commerce.department(),
|
||||
category2: faker.commerce.productAdjective(),
|
||||
itemType: 'PHYSICAL',
|
||||
price: 0.3,
|
||||
},
|
||||
{
|
||||
id: `BI${faker.string.alphanumeric(3).toUpperCase()}`,
|
||||
name: faker.commerce.productName(),
|
||||
category1: 'Game',
|
||||
category2: 'Online Game Items',
|
||||
itemType: 'VIRTUAL',
|
||||
price: 0.5,
|
||||
},
|
||||
{
|
||||
id: `BI${faker.string.alphanumeric(3).toUpperCase()}`,
|
||||
name: faker.commerce.productName(),
|
||||
category1: 'Electronics',
|
||||
category2: faker.commerce.productAdjective(),
|
||||
itemType: 'PHYSICAL',
|
||||
price: 0.2,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a test 3DS initialize request with randomized data
|
||||
*/
|
||||
export function createTest3DSInitializeRequest(): ThreeDSInitializeRequest {
|
||||
return {
|
||||
locale: 'tr',
|
||||
conversationId: faker.string.uuid(),
|
||||
price: 1.0,
|
||||
paidPrice: 1.1,
|
||||
currency: 'TRY',
|
||||
installment: 1,
|
||||
paymentChannel: 'WEB',
|
||||
basketId: `B${faker.string.alphanumeric(5).toUpperCase()}`,
|
||||
paymentGroup: 'PRODUCT',
|
||||
callbackUrl: 'https://www.merchant.com/callback',
|
||||
paymentCard: createTestCard(),
|
||||
buyer: createTestBuyer(),
|
||||
billingAddress: createTestBillingAddress(),
|
||||
shippingAddress: createTestShippingAddress(),
|
||||
basketItems: createTestBasketItems(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a test non-3DS payment request with randomized data
|
||||
*/
|
||||
export function createTestNon3DPaymentRequest(): Non3DPaymentRequest {
|
||||
return {
|
||||
locale: 'tr',
|
||||
conversationId: faker.string.uuid(),
|
||||
price: 1.0,
|
||||
paidPrice: 1.1,
|
||||
currency: 'TRY',
|
||||
installment: 1,
|
||||
paymentChannel: 'WEB',
|
||||
basketId: `B${faker.string.alphanumeric(5).toUpperCase()}`,
|
||||
paymentGroup: 'PRODUCT',
|
||||
paymentCard: createTestCard(),
|
||||
buyer: createTestBuyer(),
|
||||
billingAddress: createTestBillingAddress(),
|
||||
shippingAddress: createTestShippingAddress(),
|
||||
basketItems: createTestBasketItems(),
|
||||
};
|
||||
}
|
||||
|
||||
// Legacy exports for backward compatibility (static versions that generate once)
|
||||
export const testCard = createTestCard();
|
||||
export const testBuyer = createTestBuyer();
|
||||
export const testBillingAddress = createTestBillingAddress();
|
||||
export const testShippingAddress = createTestShippingAddress();
|
||||
export const testBasketItems = createTestBasketItems();
|
||||
export const test3DSInitializeRequest = createTest3DSInitializeRequest();
|
||||
export const testNon3DPaymentRequest = createTestNon3DPaymentRequest();
|
||||
193
tests/fixtures/subscription.ts
vendored
Normal file
193
tests/fixtures/subscription.ts
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* Subscription test fixtures with randomized personal data
|
||||
*/
|
||||
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type {
|
||||
ProductCreateRequest,
|
||||
PricingPlanCreateRequest,
|
||||
SubscriptionCustomer,
|
||||
SubscriptionPaymentCard,
|
||||
SubscriptionAddress,
|
||||
SubscriptionInitializeRequest,
|
||||
SubscriptionCheckoutFormInitializeRequest,
|
||||
} from '../../src/types';
|
||||
|
||||
/**
|
||||
* Generates a valid Turkish identity number (TC Kimlik No)
|
||||
*/
|
||||
function generateTurkishIdentityNumber(): string {
|
||||
const digits: number[] = [];
|
||||
|
||||
// First digit cannot be 0
|
||||
digits.push(faker.number.int({ min: 1, max: 9 }));
|
||||
|
||||
// Generate next 8 random digits
|
||||
for (let i = 1; i < 9; i++) {
|
||||
digits.push(faker.number.int({ min: 0, max: 9 }));
|
||||
}
|
||||
|
||||
// Calculate 10th digit
|
||||
const oddSum = digits[0] + digits[2] + digits[4] + digits[6] + digits[8];
|
||||
const evenSum = digits[1] + digits[3] + digits[5] + digits[7];
|
||||
const digit10 = (oddSum * 7 - evenSum) % 10;
|
||||
digits.push(digit10 < 0 ? digit10 + 10 : digit10);
|
||||
|
||||
// Calculate 11th digit
|
||||
const allSum = digits.reduce((sum, d) => sum + d, 0);
|
||||
digits.push(allSum % 10);
|
||||
|
||||
return digits.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a Turkish phone number in the format +905XXXXXXXXX
|
||||
*/
|
||||
function generateTurkishPhoneNumber(): string {
|
||||
const areaCode = faker.helpers.arrayElement(['530', '531', '532', '533', '534', '535', '536', '537', '538', '539', '540', '541', '542', '543', '544', '545', '546', '547', '548', '549', '550', '551', '552', '553', '554', '555', '556', '557', '558', '559']);
|
||||
const number = faker.string.numeric(7);
|
||||
return `+90${areaCode}${number}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a subscription billing address with randomized data
|
||||
*/
|
||||
export function createTestSubscriptionBillingAddress(): SubscriptionAddress {
|
||||
return {
|
||||
address: `${faker.location.streetAddress()}, ${faker.location.city()}`,
|
||||
zipCode: faker.location.zipCode('#####'),
|
||||
contactName: faker.person.fullName(),
|
||||
city: faker.location.city(),
|
||||
country: 'Turkey',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a subscription shipping address with randomized data
|
||||
*/
|
||||
export function createTestSubscriptionShippingAddress(): SubscriptionAddress {
|
||||
return {
|
||||
address: `${faker.location.streetAddress()}, ${faker.location.city()}`,
|
||||
zipCode: faker.location.zipCode('#####'),
|
||||
contactName: faker.person.fullName(),
|
||||
city: faker.location.city(),
|
||||
country: 'Turkey',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a subscription customer with randomized personal data
|
||||
*/
|
||||
export function createTestSubscriptionCustomer(): SubscriptionCustomer {
|
||||
const firstName = faker.person.firstName();
|
||||
const lastName = faker.person.lastName();
|
||||
|
||||
return {
|
||||
name: firstName,
|
||||
surname: lastName,
|
||||
email: faker.internet.email({ firstName, lastName }).toLowerCase(),
|
||||
gsmNumber: generateTurkishPhoneNumber(),
|
||||
identityNumber: generateTurkishIdentityNumber(),
|
||||
billingAddress: createTestSubscriptionBillingAddress(),
|
||||
shippingAddress: createTestSubscriptionShippingAddress(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a subscription payment card with randomized holder name
|
||||
* Card number is fixed to iyzico sandbox test card
|
||||
*/
|
||||
export function createTestSubscriptionPaymentCard(): SubscriptionPaymentCard {
|
||||
return {
|
||||
cardHolderName: faker.person.fullName(),
|
||||
cardNumber: '5528790000000008', // iyzico sandbox test card
|
||||
expireMonth: '12',
|
||||
expireYear: '2030',
|
||||
cvc: '123',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a product create request with unique name
|
||||
*/
|
||||
export function createTestProductCreateRequest(): ProductCreateRequest {
|
||||
return {
|
||||
locale: 'tr',
|
||||
name: `Test ${faker.string.alphanumeric(6)}`,
|
||||
description: faker.commerce.productDescription().substring(0,20),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a pricing plan create request
|
||||
*/
|
||||
export function createTestPricingPlanCreateRequest(): PricingPlanCreateRequest {
|
||||
return {
|
||||
locale: 'tr',
|
||||
name: `Monthly Test Plan ${faker.string.alphanumeric(6)}`,
|
||||
price: 99.99,
|
||||
currencyCode: 'TRY',
|
||||
paymentInterval: 'MONTHLY',
|
||||
paymentIntervalCount: 1,
|
||||
trialPeriodDays: 7,
|
||||
planPaymentType: 'RECURRING',
|
||||
recurrenceCount: 12,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a subscription initialize request with randomized data
|
||||
*/
|
||||
export function createTestSubscriptionInitializeRequest(pricingPlanReferenceCode: string = ''): SubscriptionInitializeRequest {
|
||||
return {
|
||||
locale: 'tr',
|
||||
pricingPlanReferenceCode,
|
||||
subscriptionInitialStatus: 'ACTIVE',
|
||||
customer: createTestSubscriptionCustomer(),
|
||||
paymentCard: createTestSubscriptionPaymentCard(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a subscription checkout form request with randomized data
|
||||
*/
|
||||
export function createTestSubscriptionCheckoutFormRequest(pricingPlanReferenceCode: string = ''): SubscriptionCheckoutFormInitializeRequest {
|
||||
return {
|
||||
locale: 'tr',
|
||||
callbackUrl: 'https://www.merchant.com/callback',
|
||||
pricingPlanReferenceCode,
|
||||
subscriptionInitialStatus: 'ACTIVE',
|
||||
customer: createTestSubscriptionCustomer(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique product name for testing
|
||||
*/
|
||||
export function generateTestProductName(): string {
|
||||
return `Test Product ${Date.now()}-${faker.string.alphanumeric(4)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique pricing plan name for testing
|
||||
*/
|
||||
export function generateTestPlanName(): string {
|
||||
return `Test Plan ${Date.now()}-${faker.string.alphanumeric(4)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique email for testing
|
||||
*/
|
||||
export function generateTestEmail(): string {
|
||||
return faker.internet.email().toLowerCase();
|
||||
}
|
||||
|
||||
// Legacy exports for backward compatibility (static versions that generate once)
|
||||
export const testSubscriptionBillingAddress = createTestSubscriptionBillingAddress();
|
||||
export const testSubscriptionShippingAddress = createTestSubscriptionShippingAddress();
|
||||
export const testSubscriptionCustomer = createTestSubscriptionCustomer();
|
||||
export const testSubscriptionPaymentCard = createTestSubscriptionPaymentCard();
|
||||
export const testProductCreateRequest = createTestProductCreateRequest();
|
||||
export const testPricingPlanCreateRequest = createTestPricingPlanCreateRequest();
|
||||
export const testSubscriptionInitializeRequest = createTestSubscriptionInitializeRequest();
|
||||
export const testSubscriptionCheckoutFormRequest = createTestSubscriptionCheckoutFormRequest();
|
||||
224
tests/integration/bin-check.test.ts
Normal file
224
tests/integration/bin-check.test.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* BIN Check integration tests
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createTestClient, assertSuccessResponse } from '../setup';
|
||||
import type { ErrorResponse } from '../../src/types';
|
||||
|
||||
describe('BIN Check Integration Tests', () => {
|
||||
const client = createTestClient();
|
||||
|
||||
it('should check BIN for a valid card number', async () => {
|
||||
const response = await client.binCheck.check({
|
||||
locale: 'tr',
|
||||
binNumber: '535805',
|
||||
conversationId: 'test-bin-check-1',
|
||||
});
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.binNumber).toBe('535805');
|
||||
expect(response.cardType).toBeTruthy();
|
||||
expect(response.cardAssociation).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should check BIN with price for installment details', async () => {
|
||||
const response = await client.binCheck.check({
|
||||
locale: 'tr',
|
||||
binNumber: '535805',
|
||||
price: 100.0,
|
||||
conversationId: 'test-bin-check-2',
|
||||
});
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.binNumber).toBe('535805');
|
||||
if (response.installmentDetails && response.installmentDetails.length > 0) {
|
||||
expect(response.installmentDetails[0].installmentPrices).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle invalid BIN number', async () => {
|
||||
try {
|
||||
const response = await client.binCheck.check({
|
||||
locale: 'tr',
|
||||
binNumber: '000000',
|
||||
conversationId: 'test-bin-check-invalid',
|
||||
});
|
||||
// If API doesn't reject invalid BINs, verify response structure
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
// If it's a success response, verify it has expected structure
|
||||
if (response.status === 'success') {
|
||||
expect(response.binNumber).toBe('000000');
|
||||
}
|
||||
} catch (error: any) {
|
||||
// API might return an error for invalid BIN - verify it's an Iyzico error
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
// Verify error has proper structure
|
||||
if (error.code) {
|
||||
expect(typeof error.code).toBe('string');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('Edge Cases - Invalid Fields', () => {
|
||||
it('should handle missing binNumber', async () => {
|
||||
try {
|
||||
const response = await client.binCheck.check({
|
||||
locale: 'tr',
|
||||
binNumber: undefined as any,
|
||||
conversationId: 'test-missing-bin',
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle empty binNumber', async () => {
|
||||
try {
|
||||
const response = await client.binCheck.check({
|
||||
locale: 'tr',
|
||||
binNumber: '',
|
||||
conversationId: 'test-empty-bin',
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle binNumber that is too short', async () => {
|
||||
try {
|
||||
const response = await client.binCheck.check({
|
||||
locale: 'tr',
|
||||
binNumber: '12345', // 5 digits, should be 6
|
||||
conversationId: 'test-short-bin',
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle binNumber that is too long', async () => {
|
||||
try {
|
||||
const response = await client.binCheck.check({
|
||||
locale: 'tr',
|
||||
binNumber: '1234567', // 7 digits, should be 6
|
||||
conversationId: 'test-long-bin',
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle binNumber with non-numeric characters', async () => {
|
||||
try {
|
||||
const response = await client.binCheck.check({
|
||||
locale: 'tr',
|
||||
binNumber: 'ABC123',
|
||||
conversationId: 'test-nonnumeric-bin',
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle negative price', async () => {
|
||||
try {
|
||||
const response = await client.binCheck.check({
|
||||
locale: 'tr',
|
||||
binNumber: '535805',
|
||||
price: -100.0,
|
||||
conversationId: 'test-negative-price',
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle zero price', async () => {
|
||||
try {
|
||||
const response = await client.binCheck.check({
|
||||
locale: 'tr',
|
||||
binNumber: '535805',
|
||||
price: 0,
|
||||
conversationId: 'test-zero-price',
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle invalid locale', async () => {
|
||||
try {
|
||||
const response = await client.binCheck.check({
|
||||
locale: 'invalid' as any,
|
||||
binNumber: '535805',
|
||||
conversationId: 'test-invalid-locale',
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle missing locale', async () => {
|
||||
try {
|
||||
const response = await client.binCheck.check({
|
||||
locale: undefined as any,
|
||||
binNumber: '535805',
|
||||
conversationId: 'test-missing-locale',
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
367
tests/integration/cancel-refund.test.ts
Normal file
367
tests/integration/cancel-refund.test.ts
Normal file
@@ -0,0 +1,367 @@
|
||||
/**
|
||||
* Cancel & Refund integration tests
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createTestClient, assertSuccessResponse } from '../setup';
|
||||
import { testNon3DPaymentRequest } from '../fixtures/payment';
|
||||
import type { ErrorResponse } from '../../src/types';
|
||||
|
||||
describe('Cancel & Refund Integration Tests', () => {
|
||||
const client = createTestClient();
|
||||
|
||||
it('should cancel a payment', async () => {
|
||||
// First create a payment
|
||||
const paymentRequest = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-cancel-${Date.now()}`,
|
||||
};
|
||||
|
||||
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||
assertSuccessResponse(paymentResponse);
|
||||
|
||||
if (paymentResponse.paymentId) {
|
||||
// Then cancel it
|
||||
const cancelResponse = await client.cancelRefund.cancel({
|
||||
paymentId: paymentResponse.paymentId,
|
||||
locale: 'tr',
|
||||
conversationId: `test-cancel-${Date.now()}`,
|
||||
});
|
||||
|
||||
assertSuccessResponse(cancelResponse);
|
||||
expect(cancelResponse.paymentId).toBe(paymentResponse.paymentId);
|
||||
}
|
||||
});
|
||||
|
||||
it('should refund a payment using v1 endpoint', async () => {
|
||||
// First create a payment
|
||||
const paymentRequest = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-refund-v1-${Date.now()}`,
|
||||
};
|
||||
|
||||
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||
assertSuccessResponse(paymentResponse);
|
||||
|
||||
if (paymentResponse.paymentId && paymentResponse.itemTransactions) {
|
||||
// Get the first payment transaction ID
|
||||
const firstTransaction = paymentResponse.itemTransactions[0];
|
||||
if (firstTransaction && firstTransaction.paymentTransactionId) {
|
||||
// Then refund it using v1
|
||||
const refundResponse = await client.cancelRefund.refund({
|
||||
paymentTransactionId: firstTransaction.paymentTransactionId,
|
||||
price: firstTransaction.price,
|
||||
locale: 'tr',
|
||||
conversationId: `test-refund-v1-${Date.now()}`,
|
||||
});
|
||||
|
||||
assertSuccessResponse(refundResponse);
|
||||
expect(refundResponse.paymentTransactionId).toBe(
|
||||
firstTransaction.paymentTransactionId
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should refund a payment using v2 endpoint', async () => {
|
||||
// First create a payment
|
||||
const paymentRequest = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-refund-v2-${Date.now()}`,
|
||||
};
|
||||
|
||||
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||
assertSuccessResponse(paymentResponse);
|
||||
|
||||
if (paymentResponse.paymentId && paymentResponse.paidPrice) {
|
||||
// Then refund it using v2
|
||||
const refundResponse = await client.cancelRefund.refundV2({
|
||||
paymentId: paymentResponse.paymentId,
|
||||
price: paymentResponse.paidPrice,
|
||||
locale: 'tr',
|
||||
conversationId: `test-refund-v2-${Date.now()}`,
|
||||
});
|
||||
|
||||
assertSuccessResponse(refundResponse);
|
||||
expect(refundResponse.paymentId).toBe(paymentResponse.paymentId);
|
||||
}
|
||||
});
|
||||
|
||||
describe('Edge Cases - Invalid Fields', () => {
|
||||
it('should handle invalid paymentId for cancel', async () => {
|
||||
try {
|
||||
const response = await client.cancelRefund.cancel({
|
||||
paymentId: 'invalid-payment-id-99999',
|
||||
locale: 'tr',
|
||||
conversationId: `test-invalid-paymentid-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle empty paymentId for cancel', async () => {
|
||||
try {
|
||||
const response = await client.cancelRefund.cancel({
|
||||
paymentId: '',
|
||||
locale: 'tr',
|
||||
conversationId: `test-empty-paymentid-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle missing paymentId for cancel', async () => {
|
||||
try {
|
||||
const response = await client.cancelRefund.cancel({
|
||||
paymentId: undefined as any,
|
||||
locale: 'tr',
|
||||
conversationId: `test-missing-paymentid-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle invalid paymentTransactionId for refund', async () => {
|
||||
try {
|
||||
const response = await client.cancelRefund.refund({
|
||||
paymentTransactionId: 'invalid-transaction-id-99999',
|
||||
price: 1.0,
|
||||
locale: 'tr',
|
||||
conversationId: `test-invalid-transactionid-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle empty paymentTransactionId for refund', async () => {
|
||||
try {
|
||||
const response = await client.cancelRefund.refund({
|
||||
paymentTransactionId: '',
|
||||
price: 1.0,
|
||||
locale: 'tr',
|
||||
conversationId: `test-empty-transactionid-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle missing paymentTransactionId for refund', async () => {
|
||||
try {
|
||||
const response = await client.cancelRefund.refund({
|
||||
paymentTransactionId: undefined as any,
|
||||
price: 1.0,
|
||||
locale: 'tr',
|
||||
conversationId: `test-missing-transactionid-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle negative price for refund', async () => {
|
||||
// First create a payment to get a transaction ID
|
||||
const paymentRequest = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-refund-negative-${Date.now()}`,
|
||||
};
|
||||
|
||||
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||
assertSuccessResponse(paymentResponse);
|
||||
|
||||
if (paymentResponse.itemTransactions && paymentResponse.itemTransactions[0]) {
|
||||
const firstTransaction = paymentResponse.itemTransactions[0];
|
||||
if (firstTransaction.paymentTransactionId) {
|
||||
try {
|
||||
const response = await client.cancelRefund.refund({
|
||||
paymentTransactionId: firstTransaction.paymentTransactionId,
|
||||
price: -100.0,
|
||||
locale: 'tr',
|
||||
conversationId: `test-refund-negative-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle zero price for refund', async () => {
|
||||
// First create a payment to get a transaction ID
|
||||
const paymentRequest = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-refund-zero-${Date.now()}`,
|
||||
};
|
||||
|
||||
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||
assertSuccessResponse(paymentResponse);
|
||||
|
||||
if (paymentResponse.itemTransactions && paymentResponse.itemTransactions[0]) {
|
||||
const firstTransaction = paymentResponse.itemTransactions[0];
|
||||
if (firstTransaction.paymentTransactionId) {
|
||||
try {
|
||||
const response = await client.cancelRefund.refund({
|
||||
paymentTransactionId: firstTransaction.paymentTransactionId,
|
||||
price: 0,
|
||||
locale: 'tr',
|
||||
conversationId: `test-refund-zero-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle refund amount greater than original payment', async () => {
|
||||
// First create a payment
|
||||
const paymentRequest = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-refund-excess-${Date.now()}`,
|
||||
};
|
||||
|
||||
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||
assertSuccessResponse(paymentResponse);
|
||||
|
||||
if (paymentResponse.itemTransactions && paymentResponse.itemTransactions[0]) {
|
||||
const firstTransaction = paymentResponse.itemTransactions[0];
|
||||
if (firstTransaction.paymentTransactionId) {
|
||||
try {
|
||||
// Try to refund more than the original amount
|
||||
const excessAmount = (firstTransaction.price || 0) * 2;
|
||||
const response = await client.cancelRefund.refund({
|
||||
paymentTransactionId: firstTransaction.paymentTransactionId,
|
||||
price: excessAmount,
|
||||
locale: 'tr',
|
||||
conversationId: `test-refund-excess-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle invalid paymentId for refundV2', async () => {
|
||||
try {
|
||||
const response = await client.cancelRefund.refundV2({
|
||||
paymentId: 'invalid-payment-id-99999',
|
||||
price: 1.0,
|
||||
locale: 'tr',
|
||||
conversationId: `test-invalid-paymentid-v2-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle missing price for refund', async () => {
|
||||
// First create a payment to get a transaction ID
|
||||
const paymentRequest = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-refund-missing-price-${Date.now()}`,
|
||||
};
|
||||
|
||||
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||
assertSuccessResponse(paymentResponse);
|
||||
|
||||
if (paymentResponse.itemTransactions && paymentResponse.itemTransactions[0]) {
|
||||
const firstTransaction = paymentResponse.itemTransactions[0];
|
||||
if (firstTransaction.paymentTransactionId) {
|
||||
try {
|
||||
const response = await client.cancelRefund.refund({
|
||||
paymentTransactionId: firstTransaction.paymentTransactionId,
|
||||
price: undefined as any,
|
||||
locale: 'tr',
|
||||
conversationId: `test-refund-missing-price-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
391
tests/integration/card-storage.test.ts
Normal file
391
tests/integration/card-storage.test.ts
Normal file
@@ -0,0 +1,391 @@
|
||||
/**
|
||||
* Card Storage integration tests
|
||||
*
|
||||
* NOTE: These tests require the card storage feature to be enabled in the iyzico merchant panel.
|
||||
* If the feature is not enabled, tests will be skipped automatically.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createTestClient, assertSuccessResponse } from '../setup';
|
||||
import { testCardCreateRequest } from '../fixtures/card';
|
||||
import { IyzicoResponseError } from '../../src/errors';
|
||||
import type { ErrorResponse } from '../../src/types';
|
||||
|
||||
describe('Card Storage Integration Tests', () => {
|
||||
const client = createTestClient();
|
||||
|
||||
// Flag to track if card storage feature is available
|
||||
let cardStorageFeatureAvailable = true;
|
||||
|
||||
let cardUserKey: string | undefined;
|
||||
let cardToken: string | undefined;
|
||||
|
||||
// Helper to skip test if card storage feature is not available
|
||||
const skipIfUnavailable = () => {
|
||||
if (!cardStorageFeatureAvailable) {
|
||||
console.log('Skipping: Card storage feature not enabled in merchant panel');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
it('should create a card for a new user', async () => {
|
||||
const request = {
|
||||
...testCardCreateRequest,
|
||||
conversationId: `test-card-create-${Date.now()}`,
|
||||
externalId: `external-${Date.now()}`,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await client.cardStorage.createCard(request);
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.cardUserKey).toBeTruthy();
|
||||
expect(response.cardToken).toBeTruthy();
|
||||
expect(response.lastFourDigits).toBeTruthy();
|
||||
|
||||
cardUserKey = response.cardUserKey;
|
||||
cardToken = response.cardToken;
|
||||
} catch (error) {
|
||||
// Check if card storage feature is not enabled
|
||||
if (error instanceof IyzicoResponseError) {
|
||||
const errorCode = error.errorResponse?.errorCode;
|
||||
const errorMessage = error.errorResponse?.errorMessage || '';
|
||||
// Error code 100001 = system error, or specific message for card storage not enabled
|
||||
if (errorCode === '100001' || errorMessage.includes('Kart saklama özelliği tanımlı değil')) {
|
||||
cardStorageFeatureAvailable = false;
|
||||
console.log('Card storage feature not enabled in merchant panel. Skipping card storage tests.');
|
||||
return; // Skip this test gracefully
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
it('should list cards for a user', async () => {
|
||||
if (skipIfUnavailable()) return;
|
||||
|
||||
if (!cardUserKey) {
|
||||
// Create a card first if we don't have one
|
||||
const createResponse = await client.cardStorage.createCard({
|
||||
...testCardCreateRequest,
|
||||
conversationId: `test-list-${Date.now()}`,
|
||||
externalId: `external-list-${Date.now()}`,
|
||||
});
|
||||
cardUserKey = createResponse.cardUserKey;
|
||||
}
|
||||
|
||||
const response = await client.cardStorage.listCards({
|
||||
cardUserKey: cardUserKey!,
|
||||
locale: 'tr',
|
||||
conversationId: `test-list-${Date.now()}`,
|
||||
});
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.cardUserKey).toBe(cardUserKey);
|
||||
expect(response.cardDetails).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should delete a card', async () => {
|
||||
if (skipIfUnavailable()) return;
|
||||
|
||||
if (!cardUserKey || !cardToken) {
|
||||
// Create a card first if we don't have one
|
||||
const createResponse = await client.cardStorage.createCard({
|
||||
...testCardCreateRequest,
|
||||
conversationId: `test-delete-${Date.now()}`,
|
||||
externalId: `external-delete-${Date.now()}`,
|
||||
});
|
||||
cardUserKey = createResponse.cardUserKey;
|
||||
cardToken = createResponse.cardToken;
|
||||
}
|
||||
|
||||
const response = await client.cardStorage.deleteCard({
|
||||
cardUserKey: cardUserKey!,
|
||||
cardToken: cardToken!,
|
||||
locale: 'tr',
|
||||
conversationId: `test-delete-${Date.now()}`,
|
||||
});
|
||||
|
||||
assertSuccessResponse(response);
|
||||
});
|
||||
|
||||
describe('Edge Cases - Invalid Fields', () => {
|
||||
it('should handle missing card data', async () => {
|
||||
if (skipIfUnavailable()) return;
|
||||
|
||||
try {
|
||||
const response = await client.cardStorage.createCard({
|
||||
locale: 'tr',
|
||||
conversationId: `test-missing-card-${Date.now()}`,
|
||||
email: 'email@email.com',
|
||||
externalId: `external-${Date.now()}`,
|
||||
card: undefined as any,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle missing email', async () => {
|
||||
if (skipIfUnavailable()) return;
|
||||
|
||||
try {
|
||||
const response = await client.cardStorage.createCard({
|
||||
locale: 'tr',
|
||||
conversationId: `test-missing-email-${Date.now()}`,
|
||||
email: undefined as any,
|
||||
externalId: `external-${Date.now()}`,
|
||||
card: testCardCreateRequest.card,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle invalid email format', async () => {
|
||||
if (skipIfUnavailable()) return;
|
||||
|
||||
try {
|
||||
const response = await client.cardStorage.createCard({
|
||||
locale: 'tr',
|
||||
conversationId: `test-invalid-email-${Date.now()}`,
|
||||
email: 'invalid-email-format',
|
||||
externalId: `external-${Date.now()}`,
|
||||
card: testCardCreateRequest.card,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle missing externalId', async () => {
|
||||
if (skipIfUnavailable()) return;
|
||||
|
||||
try {
|
||||
const response = await client.cardStorage.createCard({
|
||||
locale: 'tr',
|
||||
conversationId: `test-missing-externalid-${Date.now()}`,
|
||||
email: 'email@email.com',
|
||||
externalId: undefined as any,
|
||||
card: testCardCreateRequest.card,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle invalid card number', async () => {
|
||||
if (skipIfUnavailable()) return;
|
||||
|
||||
try {
|
||||
const response = await client.cardStorage.createCard({
|
||||
locale: 'tr',
|
||||
conversationId: `test-invalid-card-number-${Date.now()}`,
|
||||
email: 'email@email.com',
|
||||
externalId: `external-${Date.now()}`,
|
||||
card: {
|
||||
...testCardCreateRequest.card,
|
||||
cardNumber: '1234',
|
||||
},
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle invalid expiry month', async () => {
|
||||
if (skipIfUnavailable()) return;
|
||||
|
||||
try {
|
||||
const response = await client.cardStorage.createCard({
|
||||
locale: 'tr',
|
||||
conversationId: `test-invalid-expmonth-${Date.now()}`,
|
||||
email: 'email@email.com',
|
||||
externalId: `external-${Date.now()}`,
|
||||
card: {
|
||||
...testCardCreateRequest.card,
|
||||
expireMonth: '13',
|
||||
},
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle invalid expiry year (past year)', async () => {
|
||||
if (skipIfUnavailable()) return;
|
||||
|
||||
const pastYear = new Date().getFullYear() - 1;
|
||||
try {
|
||||
const response = await client.cardStorage.createCard({
|
||||
locale: 'tr',
|
||||
conversationId: `test-invalid-expyear-${Date.now()}`,
|
||||
email: 'email@email.com',
|
||||
externalId: `external-${Date.now()}`,
|
||||
card: {
|
||||
...testCardCreateRequest.card,
|
||||
expireYear: String(pastYear),
|
||||
},
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle invalid cardUserKey for listCards', async () => {
|
||||
if (skipIfUnavailable()) return;
|
||||
|
||||
try {
|
||||
const response = await client.cardStorage.listCards({
|
||||
cardUserKey: 'invalid-user-key-99999',
|
||||
locale: 'tr',
|
||||
conversationId: `test-invalid-userkey-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle empty cardUserKey for listCards', async () => {
|
||||
if (skipIfUnavailable()) return;
|
||||
|
||||
try {
|
||||
const response = await client.cardStorage.listCards({
|
||||
cardUserKey: '',
|
||||
locale: 'tr',
|
||||
conversationId: `test-empty-userkey-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle invalid cardToken for deleteCard', async () => {
|
||||
if (skipIfUnavailable()) return;
|
||||
|
||||
// First create a card to get a valid cardUserKey
|
||||
const createResponse = await client.cardStorage.createCard({
|
||||
...testCardCreateRequest,
|
||||
conversationId: `test-delete-invalid-token-${Date.now()}`,
|
||||
externalId: `external-${Date.now()}`,
|
||||
});
|
||||
assertSuccessResponse(createResponse);
|
||||
|
||||
if (createResponse.cardUserKey) {
|
||||
try {
|
||||
const response = await client.cardStorage.deleteCard({
|
||||
cardUserKey: createResponse.cardUserKey,
|
||||
cardToken: 'invalid-card-token-99999',
|
||||
locale: 'tr',
|
||||
conversationId: `test-delete-invalid-token-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle empty cardToken for deleteCard', async () => {
|
||||
if (skipIfUnavailable()) return;
|
||||
|
||||
// First create a card to get a valid cardUserKey
|
||||
const createResponse = await client.cardStorage.createCard({
|
||||
...testCardCreateRequest,
|
||||
conversationId: `test-delete-empty-token-${Date.now()}`,
|
||||
externalId: `external-${Date.now()}`,
|
||||
});
|
||||
assertSuccessResponse(createResponse);
|
||||
|
||||
if (createResponse.cardUserKey) {
|
||||
try {
|
||||
const response = await client.cardStorage.deleteCard({
|
||||
cardUserKey: createResponse.cardUserKey,
|
||||
cardToken: '',
|
||||
locale: 'tr',
|
||||
conversationId: `test-delete-empty-token-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
373
tests/integration/payment.test.ts
Normal file
373
tests/integration/payment.test.ts
Normal file
@@ -0,0 +1,373 @@
|
||||
/**
|
||||
* Payment integration tests
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createTestClient, assertSuccessResponse } from '../setup';
|
||||
import { testNon3DPaymentRequest, test3DSInitializeRequest } from '../fixtures/payment';
|
||||
import { IyzicoResponseError } from '../../src/errors';
|
||||
|
||||
describe('Payment Integration Tests', () => {
|
||||
const client = createTestClient();
|
||||
|
||||
it('should create a non-3DS payment', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-non3ds-${Date.now()}`,
|
||||
};
|
||||
|
||||
const response = await client.payment.createNon3DS(request);
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.paymentId).toBeTruthy();
|
||||
expect(response.price).toBe(request.price);
|
||||
});
|
||||
|
||||
it('should get payment detail by paymentId', async () => {
|
||||
// First create a payment
|
||||
const paymentRequest = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-detail-${Date.now()}`,
|
||||
};
|
||||
|
||||
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||
assertSuccessResponse(paymentResponse);
|
||||
|
||||
if (!paymentResponse.paymentId) {
|
||||
throw new Error('Payment ID is required for detail test');
|
||||
}
|
||||
|
||||
// Then get payment detail
|
||||
const detailResponse = await client.payment.getDetail({
|
||||
paymentId: paymentResponse.paymentId,
|
||||
locale: 'tr',
|
||||
});
|
||||
|
||||
assertSuccessResponse(detailResponse);
|
||||
expect(detailResponse.paymentId).toBe(paymentResponse.paymentId);
|
||||
});
|
||||
|
||||
it('should initialize 3DS payment', async () => {
|
||||
const request = {
|
||||
...test3DSInitializeRequest,
|
||||
conversationId: `test-3ds-init-${Date.now()}`,
|
||||
};
|
||||
|
||||
const response = await client.payment.initialize3DS(request);
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.paymentId).toBeTruthy();
|
||||
expect(response.htmlContent || response.threeDSHtmlContent).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should complete 3DS v1 payment', async () => {
|
||||
// First initialize a 3DS payment
|
||||
const initRequest = {
|
||||
...test3DSInitializeRequest,
|
||||
conversationId: `test-3ds-v1-${Date.now()}`,
|
||||
};
|
||||
|
||||
const initResponse = await client.payment.initialize3DS(initRequest);
|
||||
assertSuccessResponse(initResponse);
|
||||
|
||||
if (!initResponse.paymentId) {
|
||||
throw new Error('Payment ID is required for 3DS completion test');
|
||||
}
|
||||
|
||||
// In sandbox, 3DS completion will fail without actual 3DS flow
|
||||
// We expect an IyzicoResponseError to be thrown
|
||||
await expect(
|
||||
client.payment.complete3DS({
|
||||
paymentId: initResponse.paymentId,
|
||||
locale: 'tr',
|
||||
conversationId: `test-3ds-v1-complete-${Date.now()}`,
|
||||
})
|
||||
).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should complete 3DS v2 payment', async () => {
|
||||
// First initialize a 3DS payment
|
||||
const initRequest = {
|
||||
...test3DSInitializeRequest,
|
||||
conversationId: `test-3ds-v2-${Date.now()}`,
|
||||
};
|
||||
|
||||
const initResponse = await client.payment.initialize3DS(initRequest);
|
||||
assertSuccessResponse(initResponse);
|
||||
|
||||
if (!initResponse.paymentId) {
|
||||
throw new Error('Payment ID is required for 3DS completion test');
|
||||
}
|
||||
|
||||
// In sandbox, 3DS completion will fail without actual 3DS flow
|
||||
// We expect an IyzicoResponseError to be thrown
|
||||
await expect(
|
||||
client.payment.complete3DSV2({
|
||||
paymentId: initResponse.paymentId,
|
||||
locale: 'tr',
|
||||
conversationId: `test-3ds-v2-complete-${Date.now()}`,
|
||||
paidPrice: initRequest.paidPrice,
|
||||
basketId: initRequest.basketId!,
|
||||
currency: initRequest.currency || 'TRY',
|
||||
})
|
||||
).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
describe('Edge Cases - Invalid Fields', () => {
|
||||
it('should handle missing required fields (price)', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-missing-price-${Date.now()}`,
|
||||
price: undefined as any,
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle missing required fields (paidPrice)', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-missing-paidprice-${Date.now()}`,
|
||||
paidPrice: undefined as any,
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle missing required fields (paymentCard)', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-missing-card-${Date.now()}`,
|
||||
paymentCard: undefined as any,
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle missing required fields (buyer)', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-missing-buyer-${Date.now()}`,
|
||||
buyer: undefined as any,
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle missing required fields (billingAddress)', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-missing-billing-${Date.now()}`,
|
||||
billingAddress: undefined as any,
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle missing required fields (basketItems)', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-missing-basket-${Date.now()}`,
|
||||
basketItems: undefined as any,
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle empty basketItems array', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-empty-basket-${Date.now()}`,
|
||||
basketItems: [],
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle invalid card number (too short)', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-invalid-card-short-${Date.now()}`,
|
||||
paymentCard: {
|
||||
...testNon3DPaymentRequest.paymentCard,
|
||||
cardNumber: '1234',
|
||||
},
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle invalid card number (non-numeric)', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-invalid-card-nonnumeric-${Date.now()}`,
|
||||
paymentCard: {
|
||||
...testNon3DPaymentRequest.paymentCard,
|
||||
cardNumber: 'ABCDEFGHIJKLMNOP',
|
||||
},
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle invalid expiry month (invalid format)', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-invalid-expmonth-${Date.now()}`,
|
||||
paymentCard: {
|
||||
...testNon3DPaymentRequest.paymentCard,
|
||||
expireMonth: '13',
|
||||
},
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle invalid expiry year (invalid format)', async () => {
|
||||
// Note: Sandbox doesn't validate past years, so we test invalid format instead
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-invalid-expyear-format-${Date.now()}`,
|
||||
paymentCard: {
|
||||
...testNon3DPaymentRequest.paymentCard,
|
||||
expireYear: 'AB', // Invalid format - non-numeric
|
||||
},
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle invalid CVV (too short)', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-invalid-cvv-short-${Date.now()}`,
|
||||
paymentCard: {
|
||||
...testNon3DPaymentRequest.paymentCard,
|
||||
cvc: '12',
|
||||
},
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle invalid CVV (non-numeric)', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-invalid-cvv-nonnumeric-${Date.now()}`,
|
||||
paymentCard: {
|
||||
...testNon3DPaymentRequest.paymentCard,
|
||||
cvc: 'ABC',
|
||||
},
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle negative price', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-negative-price-${Date.now()}`,
|
||||
price: -100.0,
|
||||
paidPrice: -100.0,
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle zero price', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-zero-price-${Date.now()}`,
|
||||
price: 0,
|
||||
paidPrice: 0,
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle invalid currency', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-invalid-currency-${Date.now()}`,
|
||||
currency: 'INVALID' as any,
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle invalid installment value', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-invalid-installment-${Date.now()}`,
|
||||
installment: 99 as any,
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle invalid email format', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-invalid-email-${Date.now()}`,
|
||||
buyer: {
|
||||
...testNon3DPaymentRequest.buyer,
|
||||
email: 'invalid-email-format',
|
||||
},
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle basket item with negative price', async () => {
|
||||
const request = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-negative-basket-price-${Date.now()}`,
|
||||
basketItems: [
|
||||
{
|
||||
...testNon3DPaymentRequest.basketItems[0],
|
||||
price: -10.0,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
await expect(client.payment.createNon3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle missing callbackUrl for 3DS initialize', async () => {
|
||||
const request = {
|
||||
...test3DSInitializeRequest,
|
||||
conversationId: `test-missing-callback-${Date.now()}`,
|
||||
callbackUrl: undefined as any,
|
||||
};
|
||||
|
||||
await expect(client.payment.initialize3DS(request)).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle invalid paymentId for getDetail', async () => {
|
||||
await expect(
|
||||
client.payment.getDetail({
|
||||
paymentId: 'invalid-payment-id-12345',
|
||||
locale: 'tr',
|
||||
})
|
||||
).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle empty paymentId for getDetail', async () => {
|
||||
await expect(
|
||||
client.payment.getDetail({
|
||||
paymentId: '',
|
||||
locale: 'tr',
|
||||
})
|
||||
).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle missing paymentId and paymentConversationId for getDetail', async () => {
|
||||
await expect(
|
||||
client.payment.getDetail({
|
||||
locale: 'tr',
|
||||
})
|
||||
).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
338
tests/integration/reporting.test.ts
Normal file
338
tests/integration/reporting.test.ts
Normal file
@@ -0,0 +1,338 @@
|
||||
/**
|
||||
* Reporting integration tests
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createTestClient, assertSuccessResponse } from '../setup';
|
||||
import type { ErrorResponse } from '../../src/types';
|
||||
|
||||
describe('Reporting Integration Tests', () => {
|
||||
const client = createTestClient();
|
||||
|
||||
it('should get payment transactions for a date', async () => {
|
||||
// Use today's date
|
||||
const today = new Date();
|
||||
const dateString = today.toISOString().split('T')[0]; // YYYY-MM-DD format
|
||||
|
||||
const response = await client.reporting.getPaymentTransactions({
|
||||
page: 1,
|
||||
transactionDate: dateString,
|
||||
locale: 'tr',
|
||||
conversationId: `test-transactions-${Date.now()}`,
|
||||
});
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.currentPage).toBe(1);
|
||||
// Transactions might be empty if no transactions exist for the date
|
||||
expect(response.transactions).toBeDefined();
|
||||
});
|
||||
|
||||
it('should get payment details by paymentId', async () => {
|
||||
// First create a payment to get a paymentId
|
||||
const { testNon3DPaymentRequest } = await import('../fixtures/payment');
|
||||
const paymentRequest = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId: `test-reporting-${Date.now()}`,
|
||||
};
|
||||
|
||||
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||
assertSuccessResponse(paymentResponse);
|
||||
|
||||
if (paymentResponse.paymentId) {
|
||||
// Then get payment details
|
||||
const detailsResponse = await client.reporting.getPaymentDetails({
|
||||
paymentId: paymentResponse.paymentId,
|
||||
locale: 'tr',
|
||||
conversationId: `test-details-${Date.now()}`,
|
||||
});
|
||||
|
||||
assertSuccessResponse(detailsResponse);
|
||||
expect(detailsResponse.payments).toBeTruthy();
|
||||
if (detailsResponse.payments && detailsResponse.payments.length > 0) {
|
||||
// Convert to string for comparison as API may return number or string
|
||||
const returnedPaymentId = String(detailsResponse.payments[0].paymentId);
|
||||
expect(returnedPaymentId).toBe(String(paymentResponse.paymentId));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should get payment details by paymentConversationId', async () => {
|
||||
// First create a payment to get a conversationId
|
||||
const { testNon3DPaymentRequest } = await import('../fixtures/payment');
|
||||
const conversationId = `test-reporting-conv-${Date.now()}`;
|
||||
const paymentRequest = {
|
||||
...testNon3DPaymentRequest,
|
||||
conversationId,
|
||||
};
|
||||
|
||||
const paymentResponse = await client.payment.createNon3DS(paymentRequest);
|
||||
assertSuccessResponse(paymentResponse);
|
||||
|
||||
if (paymentResponse.conversationId) {
|
||||
// Then get payment details using paymentConversationId
|
||||
const detailsResponse = await client.reporting.getPaymentDetails({
|
||||
paymentConversationId: paymentResponse.conversationId,
|
||||
locale: 'tr',
|
||||
conversationId: `test-details-conv-${Date.now()}`,
|
||||
});
|
||||
|
||||
assertSuccessResponse(detailsResponse);
|
||||
expect(detailsResponse.payments).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
describe('Edge Cases - Invalid Fields', () => {
|
||||
it('should handle invalid date format for getPaymentTransactions', async () => {
|
||||
try {
|
||||
const response = await client.reporting.getPaymentTransactions({
|
||||
page: 1,
|
||||
transactionDate: 'invalid-date-format',
|
||||
locale: 'tr',
|
||||
conversationId: `test-invalid-date-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle future date for getPaymentTransactions', async () => {
|
||||
const futureDate = new Date();
|
||||
futureDate.setFullYear(futureDate.getFullYear() + 1);
|
||||
const dateString = futureDate.toISOString().split('T')[0];
|
||||
|
||||
try {
|
||||
const response = await client.reporting.getPaymentTransactions({
|
||||
page: 1,
|
||||
transactionDate: dateString,
|
||||
locale: 'tr',
|
||||
conversationId: `test-future-date-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
// Future dates might return empty results or error
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle negative page number', async () => {
|
||||
const today = new Date();
|
||||
const dateString = today.toISOString().split('T')[0];
|
||||
|
||||
try {
|
||||
const response = await client.reporting.getPaymentTransactions({
|
||||
page: -1,
|
||||
transactionDate: dateString,
|
||||
locale: 'tr',
|
||||
conversationId: `test-negative-page-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle zero page number', async () => {
|
||||
const today = new Date();
|
||||
const dateString = today.toISOString().split('T')[0];
|
||||
|
||||
try {
|
||||
const response = await client.reporting.getPaymentTransactions({
|
||||
page: 0,
|
||||
transactionDate: dateString,
|
||||
locale: 'tr',
|
||||
conversationId: `test-zero-page-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle very large page number', async () => {
|
||||
const today = new Date();
|
||||
const dateString = today.toISOString().split('T')[0];
|
||||
|
||||
try {
|
||||
const response = await client.reporting.getPaymentTransactions({
|
||||
page: 999999,
|
||||
transactionDate: dateString,
|
||||
locale: 'tr',
|
||||
conversationId: `test-large-page-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
// Large page numbers might return empty results or error
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle missing transactionDate', async () => {
|
||||
try {
|
||||
const response = await client.reporting.getPaymentTransactions({
|
||||
page: 1,
|
||||
transactionDate: undefined as any,
|
||||
locale: 'tr',
|
||||
conversationId: `test-missing-date-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle invalid paymentId for getPaymentDetails', async () => {
|
||||
try {
|
||||
const response = await client.reporting.getPaymentDetails({
|
||||
paymentId: 'invalid-payment-id-99999',
|
||||
locale: 'tr',
|
||||
conversationId: `test-invalid-paymentid-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle empty paymentId for getPaymentDetails', async () => {
|
||||
try {
|
||||
const response = await client.reporting.getPaymentDetails({
|
||||
paymentId: '',
|
||||
locale: 'tr',
|
||||
conversationId: `test-empty-paymentid-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle invalid paymentConversationId for getPaymentDetails', async () => {
|
||||
try {
|
||||
const response = await client.reporting.getPaymentDetails({
|
||||
paymentConversationId: 'invalid-conversation-id-99999',
|
||||
locale: 'tr',
|
||||
conversationId: `test-invalid-convid-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle empty paymentConversationId for getPaymentDetails', async () => {
|
||||
try {
|
||||
const response = await client.reporting.getPaymentDetails({
|
||||
paymentConversationId: '',
|
||||
locale: 'tr',
|
||||
conversationId: `test-empty-convid-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle missing both paymentId and paymentConversationId', async () => {
|
||||
try {
|
||||
const response = await client.reporting.getPaymentDetails({
|
||||
locale: 'tr',
|
||||
conversationId: `test-missing-ids-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle invalid locale', async () => {
|
||||
const today = new Date();
|
||||
const dateString = today.toISOString().split('T')[0];
|
||||
|
||||
try {
|
||||
const response = await client.reporting.getPaymentTransactions({
|
||||
page: 1,
|
||||
transactionDate: dateString,
|
||||
locale: 'invalid' as any,
|
||||
conversationId: `test-invalid-locale-${Date.now()}`,
|
||||
});
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toBeDefined();
|
||||
if (response.status === 'failure') {
|
||||
const errorResponse = response as unknown as ErrorResponse;
|
||||
expect(errorResponse.errorCode || errorResponse.errorMessage).toBeTruthy();
|
||||
}
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
556
tests/integration/subscription.test.ts
Normal file
556
tests/integration/subscription.test.ts
Normal file
@@ -0,0 +1,556 @@
|
||||
/**
|
||||
* Subscription integration tests
|
||||
*
|
||||
* NOTE: These tests require the subscription feature to be enabled in the iyzico merchant panel.
|
||||
* If the feature is not enabled, tests will be skipped automatically.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import { createTestClient, assertSuccessResponse } from '../setup';
|
||||
import {
|
||||
testProductCreateRequest,
|
||||
testPricingPlanCreateRequest,
|
||||
testSubscriptionCustomer,
|
||||
testSubscriptionPaymentCard,
|
||||
generateTestProductName,
|
||||
generateTestPlanName,
|
||||
generateTestEmail,
|
||||
} from '../fixtures/subscription';
|
||||
import { IyzicoResponseError } from '../../src/errors';
|
||||
|
||||
describe('Subscription Integration Tests', () => {
|
||||
const client = createTestClient();
|
||||
|
||||
// Flag to track if subscription feature is available
|
||||
let subscriptionFeatureAvailable = true;
|
||||
|
||||
// Shared references for chained tests
|
||||
let createdProductReferenceCode: string | undefined;
|
||||
let createdPricingPlanReferenceCode: string | undefined;
|
||||
let createdSubscriptionReferenceCode: string | undefined;
|
||||
let createdCustomerReferenceCode: string | undefined;
|
||||
|
||||
// Helper to skip test if subscription feature is not available
|
||||
const skipIfUnavailable = () => {
|
||||
if (!subscriptionFeatureAvailable) {
|
||||
console.log('Skipping: Subscription feature not enabled in merchant panel');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Product CRUD Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('Product Operations', () => {
|
||||
it('should create a subscription product', async () => {
|
||||
const request = {
|
||||
...testProductCreateRequest,
|
||||
// conversationId: `test-product-create-${Date.now()}`,
|
||||
// name: generateTestProductName(),
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await client.subscription.createProduct(request);
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.data).toBeDefined();
|
||||
expect(response.data?.referenceCode).toBeTruthy();
|
||||
expect(response.data?.name).toBe(request.name);
|
||||
expect(response.data?.status).toBe('ACTIVE');
|
||||
|
||||
// Save for later tests
|
||||
createdProductReferenceCode = response.data?.referenceCode;
|
||||
|
||||
} catch (error) {
|
||||
// Check if subscription feature is not enabled (error code 100001 = system error)
|
||||
if (error instanceof IyzicoResponseError && error.errorResponse?.errorCode === '100001') {
|
||||
subscriptionFeatureAvailable = false;
|
||||
console.log('Subscription feature not enabled in merchant panel. Skipping subscription tests.');
|
||||
return; // Skip this test gracefully
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
it('should get a subscription product', async () => {
|
||||
if (skipIfUnavailable() || !createdProductReferenceCode) {
|
||||
return; // Skip if feature unavailable or no product created
|
||||
}
|
||||
|
||||
const response = await client.subscription.getProduct(createdProductReferenceCode);
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.data).toBeDefined();
|
||||
expect(response.data?.referenceCode).toBe(createdProductReferenceCode);
|
||||
|
||||
});
|
||||
|
||||
it('should update a subscription product', async () => {
|
||||
if (skipIfUnavailable() || !createdProductReferenceCode) {
|
||||
return; // Skip if feature unavailable or no product created
|
||||
}
|
||||
|
||||
const updatedName = `Updated Product ${Date.now()}`;
|
||||
const response = await client.subscription.updateProduct(createdProductReferenceCode, {
|
||||
name: updatedName,
|
||||
description: 'Updated description',
|
||||
conversationId: `test-product-update-${Date.now()}`,
|
||||
});
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.data).toBeDefined();
|
||||
expect(response.data?.name).toBe(updatedName);
|
||||
|
||||
});
|
||||
|
||||
it('should list subscription products', async () => {
|
||||
if (skipIfUnavailable()) {
|
||||
return; // Skip if feature unavailable
|
||||
}
|
||||
|
||||
const response = await client.subscription.listProducts({
|
||||
page: 1,
|
||||
count: 10,
|
||||
});
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.data).toBeDefined();
|
||||
expect(response.data?.items).toBeInstanceOf(Array);
|
||||
expect(response.data?.totalCount).toBeGreaterThanOrEqual(0);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Pricing Plan CRUD Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('Pricing Plan Operations', () => {
|
||||
it('should create a pricing plan', async () => {
|
||||
if (skipIfUnavailable() || !createdProductReferenceCode) {
|
||||
return; // Skip if feature unavailable or no product created
|
||||
}
|
||||
|
||||
const request = {
|
||||
...testPricingPlanCreateRequest,
|
||||
conversationId: `test-plan-create-${Date.now()}`,
|
||||
name: generateTestPlanName(),
|
||||
};
|
||||
|
||||
const response = await client.subscription.createPricingPlan(createdProductReferenceCode, request);
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.data).toBeDefined();
|
||||
expect(response.data?.referenceCode).toBeTruthy();
|
||||
expect(response.data?.name).toBe(request.name);
|
||||
expect(response.data?.price).toBe(request.price);
|
||||
expect(response.data?.paymentInterval).toBe(request.paymentInterval);
|
||||
|
||||
// Save for later tests
|
||||
createdPricingPlanReferenceCode = response.data?.referenceCode;
|
||||
|
||||
});
|
||||
|
||||
it('should get a pricing plan', async () => {
|
||||
if (skipIfUnavailable() || !createdPricingPlanReferenceCode) {
|
||||
return; // Skip if feature unavailable or no plan created
|
||||
}
|
||||
|
||||
const response = await client.subscription.getPricingPlan(createdPricingPlanReferenceCode);
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.data).toBeDefined();
|
||||
expect(response.data?.referenceCode).toBe(createdPricingPlanReferenceCode);
|
||||
|
||||
});
|
||||
|
||||
it('should update a pricing plan', async () => {
|
||||
if (skipIfUnavailable() || !createdPricingPlanReferenceCode) {
|
||||
return; // Skip if feature unavailable or no plan created
|
||||
}
|
||||
|
||||
const updatedName = `Updated Plan ${Date.now()}`;
|
||||
const response = await client.subscription.updatePricingPlan(createdPricingPlanReferenceCode, {
|
||||
name: updatedName,
|
||||
trialPeriodDays: 14,
|
||||
conversationId: `test-plan-update-${Date.now()}`,
|
||||
});
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.data).toBeDefined();
|
||||
expect(response.data?.name).toBe(updatedName);
|
||||
|
||||
});
|
||||
|
||||
it('should list pricing plans for a product', async () => {
|
||||
if (skipIfUnavailable() || !createdProductReferenceCode) {
|
||||
return; // Skip if feature unavailable or no product created
|
||||
}
|
||||
|
||||
const response = await client.subscription.listPricingPlans(createdProductReferenceCode, {
|
||||
page: 1,
|
||||
count: 10,
|
||||
});
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.data).toBeDefined();
|
||||
expect(response.data?.items).toBeInstanceOf(Array);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Subscription Lifecycle Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('Subscription Operations', () => {
|
||||
it('should initialize a subscription', async () => {
|
||||
if (skipIfUnavailable() || !createdPricingPlanReferenceCode) {
|
||||
return; // Skip if feature unavailable or no plan created
|
||||
}
|
||||
|
||||
const uniqueEmail = generateTestEmail();
|
||||
const response = await client.subscription.initialize({
|
||||
locale: 'tr',
|
||||
conversationId: `test-sub-init-${Date.now()}`,
|
||||
pricingPlanReferenceCode: createdPricingPlanReferenceCode,
|
||||
subscriptionInitialStatus: 'ACTIVE',
|
||||
customer: {
|
||||
...testSubscriptionCustomer,
|
||||
email: uniqueEmail,
|
||||
},
|
||||
paymentCard: testSubscriptionPaymentCard,
|
||||
});
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.data).toBeDefined();
|
||||
expect(response.data?.referenceCode).toBeTruthy();
|
||||
expect(response.data?.customerReferenceCode).toBeTruthy();
|
||||
|
||||
// Save for later tests
|
||||
createdSubscriptionReferenceCode = response.data?.referenceCode;
|
||||
createdCustomerReferenceCode = response.data?.customerReferenceCode;
|
||||
|
||||
});
|
||||
|
||||
it('should get a subscription', async () => {
|
||||
if (skipIfUnavailable() || !createdSubscriptionReferenceCode) {
|
||||
return; // Skip if feature unavailable or no subscription created
|
||||
}
|
||||
|
||||
const response = await client.subscription.get(createdSubscriptionReferenceCode);
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.data).toBeDefined();
|
||||
|
||||
});
|
||||
|
||||
it('should list subscriptions', async () => {
|
||||
if (skipIfUnavailable()) {
|
||||
return; // Skip if feature unavailable
|
||||
}
|
||||
|
||||
const response = await client.subscription.list({
|
||||
page: 1,
|
||||
count: 10,
|
||||
});
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.data).toBeDefined();
|
||||
expect(response.data?.items).toBeInstanceOf(Array);
|
||||
|
||||
});
|
||||
|
||||
it('should list subscriptions with filters', async () => {
|
||||
if (skipIfUnavailable()) {
|
||||
return; // Skip if feature unavailable
|
||||
}
|
||||
|
||||
const response = await client.subscription.list({
|
||||
subscriptionStatus: 'ACTIVE',
|
||||
page: 1,
|
||||
count: 10,
|
||||
});
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.data).toBeDefined();
|
||||
|
||||
});
|
||||
|
||||
it('should initialize subscription with checkout form', async () => {
|
||||
if (skipIfUnavailable() || !createdPricingPlanReferenceCode) {
|
||||
return; // Skip if feature unavailable or no plan created
|
||||
}
|
||||
|
||||
const uniqueEmail = generateTestEmail();
|
||||
const response = await client.subscription.initializeCheckoutForm({
|
||||
locale: 'tr',
|
||||
conversationId: `test-sub-checkout-${Date.now()}`,
|
||||
callbackUrl: 'https://www.merchant.com/callback',
|
||||
pricingPlanReferenceCode: createdPricingPlanReferenceCode,
|
||||
subscriptionInitialStatus: 'ACTIVE',
|
||||
customer: {
|
||||
...testSubscriptionCustomer,
|
||||
email: uniqueEmail,
|
||||
},
|
||||
});
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.token).toBeTruthy();
|
||||
expect(response.checkoutFormContent).toBeTruthy();
|
||||
expect(response.tokenExpireTime).toBeGreaterThan(0);
|
||||
|
||||
});
|
||||
|
||||
it('should cancel a subscription', async () => {
|
||||
if (skipIfUnavailable() || !createdSubscriptionReferenceCode) {
|
||||
return; // Skip if feature unavailable or no subscription created
|
||||
}
|
||||
|
||||
const response = await client.subscription.cancel(createdSubscriptionReferenceCode);
|
||||
|
||||
assertSuccessResponse(response);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Customer Operations Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('Customer Operations', () => {
|
||||
it('should list customers', async () => {
|
||||
if (skipIfUnavailable()) {
|
||||
return; // Skip if feature unavailable
|
||||
}
|
||||
|
||||
const response = await client.subscription.listCustomers({
|
||||
page: 1,
|
||||
count: 10,
|
||||
});
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.data).toBeDefined();
|
||||
expect(response.data?.items).toBeInstanceOf(Array);
|
||||
|
||||
});
|
||||
|
||||
it('should get a customer', async () => {
|
||||
if (skipIfUnavailable() || !createdCustomerReferenceCode) {
|
||||
return; // Skip if feature unavailable or no customer created
|
||||
}
|
||||
|
||||
const response = await client.subscription.getCustomer(createdCustomerReferenceCode);
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.data).toBeDefined();
|
||||
expect(response.data?.referenceCode).toBe(createdCustomerReferenceCode);
|
||||
|
||||
});
|
||||
|
||||
it('should update a customer', async () => {
|
||||
if (skipIfUnavailable() || !createdCustomerReferenceCode) {
|
||||
return; // Skip if feature unavailable or no customer created
|
||||
}
|
||||
|
||||
const response = await client.subscription.updateCustomer(createdCustomerReferenceCode, {
|
||||
name: 'Updated John',
|
||||
surname: 'Updated Doe',
|
||||
conversationId: `test-customer-update-${Date.now()}`,
|
||||
});
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.data).toBeDefined();
|
||||
expect(response.data?.name).toBe('Updated John');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Card Update Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('Card Update Operations', () => {
|
||||
it('should initialize card update checkout form', async () => {
|
||||
if (skipIfUnavailable() || !createdCustomerReferenceCode) {
|
||||
return; // Skip if feature unavailable or no customer created
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await client.subscription.initializeCardUpdate({
|
||||
locale: 'tr',
|
||||
conversationId: `test-card-update-${Date.now()}`,
|
||||
callbackUrl: 'https://www.merchant.com/callback',
|
||||
customerReferenceCode: createdCustomerReferenceCode,
|
||||
});
|
||||
|
||||
assertSuccessResponse(response);
|
||||
expect(response.token).toBeTruthy();
|
||||
expect(response.checkoutFormContent).toBeTruthy();
|
||||
|
||||
} catch (error) {
|
||||
// Card update requires an active subscription - may fail if subscription was cancelled
|
||||
if (error instanceof IyzicoResponseError) {
|
||||
const errorMessage = error.errorResponse?.errorMessage || '';
|
||||
if (errorMessage.includes('aktif abonelik bulunamadı')) {
|
||||
console.log('Skipping: No active subscription found for card update test');
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Cleanup - Delete created resources
|
||||
// ============================================================================
|
||||
|
||||
describe('Cleanup', () => {
|
||||
it('should delete pricing plan', async () => {
|
||||
if (skipIfUnavailable() || !createdPricingPlanReferenceCode) {
|
||||
console.log('No pricing plan to delete');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await client.subscription.deletePricingPlan(createdPricingPlanReferenceCode);
|
||||
assertSuccessResponse(response);
|
||||
} catch (error) {
|
||||
// Deletion may fail if there are active subscriptions - this is expected
|
||||
if (error instanceof IyzicoResponseError) {
|
||||
console.log(`Could not delete pricing plan: ${error.errorResponse?.errorMessage || error.message}`);
|
||||
return; // Don't fail the test for cleanup issues
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
it('should delete product', async () => {
|
||||
if (skipIfUnavailable() || !createdProductReferenceCode) {
|
||||
console.log('No product to delete');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await client.subscription.deleteProduct(createdProductReferenceCode);
|
||||
assertSuccessResponse(response);
|
||||
} catch (error) {
|
||||
// Deletion may fail if there are active pricing plans/subscriptions - this is expected
|
||||
if (error instanceof IyzicoResponseError) {
|
||||
console.log(`Could not delete product: ${error.errorResponse?.errorMessage || error.message}`);
|
||||
return; // Don't fail the test for cleanup issues
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Edge Cases and Error Handling
|
||||
// ============================================================================
|
||||
|
||||
describe('Edge Cases - Invalid Inputs', () => {
|
||||
it('should handle non-existent product reference code', async () => {
|
||||
if (skipIfUnavailable()) {
|
||||
return; // Skip if feature unavailable
|
||||
}
|
||||
|
||||
await expect(
|
||||
client.subscription.getProduct('invalid-product-ref-12345')
|
||||
).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle non-existent pricing plan reference code', async () => {
|
||||
if (skipIfUnavailable()) {
|
||||
return; // Skip if feature unavailable
|
||||
}
|
||||
|
||||
await expect(
|
||||
client.subscription.getPricingPlan('invalid-plan-ref-12345')
|
||||
).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle non-existent subscription reference code', async () => {
|
||||
if (skipIfUnavailable()) {
|
||||
return; // Skip if feature unavailable
|
||||
}
|
||||
|
||||
await expect(
|
||||
client.subscription.get('invalid-sub-ref-12345')
|
||||
).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle non-existent customer reference code', async () => {
|
||||
if (skipIfUnavailable()) {
|
||||
return; // Skip if feature unavailable
|
||||
}
|
||||
|
||||
await expect(
|
||||
client.subscription.getCustomer('invalid-customer-ref-12345')
|
||||
).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle missing required fields in product creation', async () => {
|
||||
if (skipIfUnavailable()) {
|
||||
return; // Skip if feature unavailable
|
||||
}
|
||||
|
||||
await expect(
|
||||
client.subscription.createProduct({
|
||||
name: '',
|
||||
conversationId: `test-empty-name-${Date.now()}`,
|
||||
})
|
||||
).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle invalid pricing plan creation without product', async () => {
|
||||
if (skipIfUnavailable()) {
|
||||
return; // Skip if feature unavailable
|
||||
}
|
||||
|
||||
await expect(
|
||||
client.subscription.createPricingPlan('invalid-product-ref', {
|
||||
...testPricingPlanCreateRequest,
|
||||
conversationId: `test-invalid-product-${Date.now()}`,
|
||||
})
|
||||
).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle subscription initialization with invalid plan', async () => {
|
||||
if (skipIfUnavailable()) {
|
||||
return; // Skip if feature unavailable
|
||||
}
|
||||
|
||||
const uniqueEmail = generateTestEmail();
|
||||
await expect(
|
||||
client.subscription.initialize({
|
||||
locale: 'tr',
|
||||
conversationId: `test-invalid-plan-${Date.now()}`,
|
||||
pricingPlanReferenceCode: 'invalid-plan-ref',
|
||||
subscriptionInitialStatus: 'ACTIVE',
|
||||
customer: {
|
||||
...testSubscriptionCustomer,
|
||||
email: uniqueEmail,
|
||||
},
|
||||
paymentCard: testSubscriptionPaymentCard,
|
||||
})
|
||||
).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle retry payment with invalid reference', async () => {
|
||||
if (skipIfUnavailable()) {
|
||||
return; // Skip if feature unavailable
|
||||
}
|
||||
|
||||
await expect(
|
||||
client.subscription.retryPayment({
|
||||
referenceCode: 'invalid-order-ref-12345',
|
||||
conversationId: `test-invalid-retry-${Date.now()}`,
|
||||
})
|
||||
).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
});
|
||||
});
|
||||
85
tests/setup.ts
Normal file
85
tests/setup.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Test setup and utilities
|
||||
*/
|
||||
|
||||
import { config } from 'dotenv';
|
||||
import { resolve, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { IyzicoClient } from '../src/client';
|
||||
|
||||
// Get the directory of this file (for ESM compatibility)
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Load environment variables from .env file
|
||||
// Resolve path relative to project root (two levels up from tests/setup.ts)
|
||||
const envPath = resolve(__dirname, '..', '.env');
|
||||
config({ path: envPath });
|
||||
|
||||
/**
|
||||
* Creates an İyzico client for testing using environment variables
|
||||
* @returns Configured IyzicoClient instance
|
||||
*/
|
||||
export function createTestClient(): IyzicoClient {
|
||||
const apiKey = process.env.IYZICO_API_KEY;
|
||||
const secretKey = process.env.IYZICO_API_SECRET;
|
||||
const baseUrl = process.env.IYZICO_BASE_URL || 'https://sandbox-api.iyzipay.com';
|
||||
|
||||
if (!apiKey) {
|
||||
throw new Error('IYZICO_API_KEY environment variable is required for tests');
|
||||
}
|
||||
if (!secretKey) {
|
||||
throw new Error('IYZICO_API_SECRET environment variable is required for tests');
|
||||
}
|
||||
|
||||
// Enable retry on rate limit for tests
|
||||
const retryOnRateLimit = process.env.IYZICO_RETRY_ON_RATE_LIMIT !== 'false';
|
||||
const maxRetries = Number.parseInt(process.env.IYZICO_MAX_RETRIES || '3', 10);
|
||||
const retryDelay = Number.parseInt(process.env.IYZICO_RETRY_DELAY_MS || '10000', 10);
|
||||
|
||||
return new IyzicoClient({
|
||||
apiKey,
|
||||
secretKey,
|
||||
baseUrl,
|
||||
locale: 'tr',
|
||||
retryOnRateLimit,
|
||||
maxRetries,
|
||||
retryDelay,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a response has a success status
|
||||
*/
|
||||
export function assertSuccessResponse(response: { status: string }): void {
|
||||
if (response.status !== 'success') {
|
||||
throw new Error(`Expected success status, got: ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a response has a failure status
|
||||
*/
|
||||
export function assertFailureResponse(response: { status: string }): void {
|
||||
if (response.status !== 'failure') {
|
||||
throw new Error(`Expected failure status, got: ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delay utility to prevent rate limiting
|
||||
* Adds a small delay between test requests
|
||||
*/
|
||||
export function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Default delay between API calls to avoid rate limiting
|
||||
* Can be overridden via IYZICO_TEST_DELAY_MS environment variable
|
||||
*/
|
||||
export const DEFAULT_TEST_DELAY_MS = Number.parseInt(
|
||||
process.env.IYZICO_TEST_DELAY_MS || '200',
|
||||
10
|
||||
);
|
||||
|
||||
62
tests/unit/auth.test.ts
Normal file
62
tests/unit/auth.test.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Authentication unit tests
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { generateAuthorization } from '../../src/auth';
|
||||
|
||||
describe('generateAuthorization', () => {
|
||||
const apiKey = 'sandbox-api-key';
|
||||
const secretKey = 'sandbox-secret-key';
|
||||
const uriPath = '/payment/bin/check';
|
||||
|
||||
it('should generate authorization header with request body', () => {
|
||||
const requestBody = { binNumber: '589004', locale: 'tr' };
|
||||
const result = generateAuthorization(apiKey, secretKey, uriPath, requestBody);
|
||||
|
||||
expect(result.authorization).toMatch(/^IYZWSv2 /);
|
||||
expect(result.randomKey).toBeTruthy();
|
||||
expect(result.randomKey.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should generate authorization header without request body', () => {
|
||||
const result = generateAuthorization(apiKey, secretKey, uriPath);
|
||||
|
||||
expect(result.authorization).toMatch(/^IYZWSv2 /);
|
||||
expect(result.randomKey).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should generate different random keys for each call', () => {
|
||||
const result1 = generateAuthorization(apiKey, secretKey, uriPath);
|
||||
const result2 = generateAuthorization(apiKey, secretKey, uriPath);
|
||||
|
||||
expect(result1.randomKey).not.toBe(result2.randomKey);
|
||||
});
|
||||
|
||||
it('should use provided random key when given', () => {
|
||||
const customRandomKey = '123456789';
|
||||
const result = generateAuthorization(apiKey, secretKey, uriPath, undefined, customRandomKey);
|
||||
|
||||
expect(result.randomKey).toBe(customRandomKey);
|
||||
});
|
||||
|
||||
it('should generate valid base64 encoded authorization', () => {
|
||||
const result = generateAuthorization(apiKey, secretKey, uriPath);
|
||||
const base64Part = result.authorization.replace('IYZWSv2 ', '');
|
||||
|
||||
// Base64 should be valid (only contains A-Z, a-z, 0-9, +, /, =)
|
||||
expect(base64Part).toMatch(/^[A-Za-z0-9+/=]+$/);
|
||||
});
|
||||
|
||||
it('should include apiKey, randomKey, and signature in authorization string', () => {
|
||||
const customRandomKey = '123456789';
|
||||
const result = generateAuthorization(apiKey, secretKey, uriPath, undefined, customRandomKey);
|
||||
const base64Part = result.authorization.replace('IYZWSv2 ', '');
|
||||
const decoded = Buffer.from(base64Part, 'base64').toString('utf-8');
|
||||
|
||||
expect(decoded).toContain(`apiKey:${apiKey}`);
|
||||
expect(decoded).toContain(`randomKey:${customRandomKey}`);
|
||||
expect(decoded).toContain('signature:');
|
||||
});
|
||||
});
|
||||
|
||||
97
tests/unit/client.test.ts
Normal file
97
tests/unit/client.test.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Client unit tests
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { IyzicoClient } from '../../src/client';
|
||||
|
||||
describe('IyzicoClient', () => {
|
||||
const apiKey = 'test-api-key';
|
||||
const secretKey = 'test-secret-key';
|
||||
|
||||
it('should create a client with required config', () => {
|
||||
const client = new IyzicoClient({
|
||||
apiKey,
|
||||
secretKey,
|
||||
});
|
||||
|
||||
expect(client.apiKey).toBe(apiKey);
|
||||
expect(client.secretKey).toBe(secretKey);
|
||||
expect(client.baseUrl).toBe('https://sandbox-api.iyzipay.com');
|
||||
expect(client.locale).toBe('tr');
|
||||
});
|
||||
|
||||
it('should throw error when apiKey is missing', () => {
|
||||
expect(() => {
|
||||
new IyzicoClient({
|
||||
apiKey: '',
|
||||
secretKey,
|
||||
} as any);
|
||||
}).toThrow('API key is required');
|
||||
});
|
||||
|
||||
it('should throw error when secretKey is missing', () => {
|
||||
expect(() => {
|
||||
new IyzicoClient({
|
||||
apiKey,
|
||||
secretKey: '',
|
||||
} as any);
|
||||
}).toThrow('Secret key is required');
|
||||
});
|
||||
|
||||
it('should use custom baseUrl when provided', () => {
|
||||
const customUrl = 'https://api.custom.com';
|
||||
const client = new IyzicoClient({
|
||||
apiKey,
|
||||
secretKey,
|
||||
baseUrl: customUrl,
|
||||
});
|
||||
|
||||
expect(client.baseUrl).toBe(customUrl);
|
||||
});
|
||||
|
||||
it('should use custom locale when provided', () => {
|
||||
const client = new IyzicoClient({
|
||||
apiKey,
|
||||
secretKey,
|
||||
locale: 'en',
|
||||
});
|
||||
|
||||
expect(client.locale).toBe('en');
|
||||
});
|
||||
|
||||
it('should initialize all services', () => {
|
||||
const client = new IyzicoClient({
|
||||
apiKey,
|
||||
secretKey,
|
||||
});
|
||||
|
||||
expect(client.payment).toBeDefined();
|
||||
expect(client.binCheck).toBeDefined();
|
||||
expect(client.cancelRefund).toBeDefined();
|
||||
expect(client.cardStorage).toBeDefined();
|
||||
expect(client.reporting).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return locale via getLocale()', () => {
|
||||
const client = new IyzicoClient({
|
||||
apiKey,
|
||||
secretKey,
|
||||
locale: 'en',
|
||||
});
|
||||
|
||||
expect(client.getLocale()).toBe('en');
|
||||
});
|
||||
|
||||
it('should return baseUrl via getBaseUrl()', () => {
|
||||
const customUrl = 'https://api.custom.com';
|
||||
const client = new IyzicoClient({
|
||||
apiKey,
|
||||
secretKey,
|
||||
baseUrl: customUrl,
|
||||
});
|
||||
|
||||
expect(client.getBaseUrl()).toBe(customUrl);
|
||||
});
|
||||
});
|
||||
|
||||
76
tests/unit/errors.test.ts
Normal file
76
tests/unit/errors.test.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Error classes unit tests
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
IyzicoError,
|
||||
IyzicoRequestError,
|
||||
IyzicoResponseError,
|
||||
} from '../../src/errors';
|
||||
import type { ErrorResponse } from '../../src/types';
|
||||
|
||||
describe('IyzicoError', () => {
|
||||
it('should create error with message', () => {
|
||||
const error = new IyzicoError('Test error');
|
||||
expect(error.message).toBe('Test error');
|
||||
expect(error.name).toBe('IyzicoError');
|
||||
expect(error.code).toBeUndefined();
|
||||
expect(error.originalError).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should create error with code and originalError', () => {
|
||||
const originalError = new Error('Original error');
|
||||
const error = new IyzicoError('Test error', 'TEST_CODE', originalError);
|
||||
expect(error.message).toBe('Test error');
|
||||
expect(error.code).toBe('TEST_CODE');
|
||||
expect(error.originalError).toBe(originalError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('IyzicoRequestError', () => {
|
||||
it('should create request error', () => {
|
||||
const error = new IyzicoRequestError('Request failed');
|
||||
expect(error.message).toBe('Request failed');
|
||||
expect(error.name).toBe('IyzicoRequestError');
|
||||
expect(error.code).toBe('REQUEST_ERROR');
|
||||
});
|
||||
|
||||
it('should create request error with originalError', () => {
|
||||
const originalError = new Error('Validation error');
|
||||
const error = new IyzicoRequestError('Request failed', originalError);
|
||||
expect(error.message).toBe('Request failed');
|
||||
expect(error.code).toBe('REQUEST_ERROR');
|
||||
expect(error.originalError).toBe(originalError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('IyzicoResponseError', () => {
|
||||
it('should create response error', () => {
|
||||
const errorResponse: ErrorResponse = {
|
||||
status: 'failure',
|
||||
errorCode: 'PAYMENT_FAILED',
|
||||
errorMessage: 'Payment failed',
|
||||
};
|
||||
const error = new IyzicoResponseError('Payment failed', errorResponse);
|
||||
expect(error.message).toBe('Payment failed');
|
||||
expect(error.name).toBe('IyzicoResponseError');
|
||||
expect(error.code).toBe('PAYMENT_FAILED');
|
||||
expect(error.errorResponse).toEqual(errorResponse);
|
||||
});
|
||||
|
||||
it('should create response error with originalError', () => {
|
||||
const errorResponse: ErrorResponse = {
|
||||
status: 'failure',
|
||||
errorCode: 'PAYMENT_FAILED',
|
||||
errorMessage: 'Payment failed',
|
||||
};
|
||||
const originalError = new Error('Network error');
|
||||
const error = new IyzicoResponseError('Payment failed', errorResponse, originalError);
|
||||
expect(error.message).toBe('Payment failed');
|
||||
expect(error.code).toBe('PAYMENT_FAILED');
|
||||
expect(error.errorResponse).toEqual(errorResponse);
|
||||
expect(error.originalError).toBe(originalError);
|
||||
});
|
||||
});
|
||||
|
||||
310
tests/unit/http.test.ts
Normal file
310
tests/unit/http.test.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
/**
|
||||
* HTTP client unit tests
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { makeRequest } from '../../src/http';
|
||||
import {
|
||||
IyzicoResponseError,
|
||||
IyzicoRequestError,
|
||||
IyzicoError,
|
||||
} from '../../src/errors';
|
||||
|
||||
describe('makeRequest', () => {
|
||||
const apiKey = 'test-api-key';
|
||||
const secretKey = 'test-secret-key';
|
||||
const baseUrl = 'https://api.test.com';
|
||||
|
||||
// Store original fetch to restore it later
|
||||
const originalFetch = globalThis.fetch;
|
||||
const mockFetch = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock fetch before each test
|
||||
globalThis.fetch = mockFetch as any;
|
||||
mockFetch.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Restore original fetch after each test
|
||||
globalThis.fetch = originalFetch;
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should make a successful POST request', async () => {
|
||||
const mockResponse = {
|
||||
status: 'success',
|
||||
data: 'test data',
|
||||
};
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse,
|
||||
});
|
||||
|
||||
const result = await makeRequest({
|
||||
method: 'POST',
|
||||
path: '/test',
|
||||
body: { test: 'value' },
|
||||
apiKey,
|
||||
secretKey,
|
||||
baseUrl,
|
||||
});
|
||||
|
||||
expect(result).toEqual(mockResponse);
|
||||
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||
const callArgs = mockFetch.mock.calls[0];
|
||||
expect(callArgs[0]).toBe('https://api.test.com/test');
|
||||
expect(callArgs[1].method).toBe('POST');
|
||||
expect(callArgs[1].headers).toHaveProperty('Authorization');
|
||||
expect(callArgs[1].headers).toHaveProperty('Content-Type', 'application/json');
|
||||
expect(callArgs[1].headers).toHaveProperty('x-iyzi-rnd');
|
||||
});
|
||||
|
||||
it('should make a successful GET request without body', async () => {
|
||||
const mockResponse = {
|
||||
status: 'success',
|
||||
data: 'test data',
|
||||
};
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse,
|
||||
});
|
||||
|
||||
const result = await makeRequest({
|
||||
method: 'GET',
|
||||
path: '/test',
|
||||
apiKey,
|
||||
secretKey,
|
||||
baseUrl,
|
||||
});
|
||||
|
||||
expect(result).toEqual(mockResponse);
|
||||
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||
const callArgs = mockFetch.mock.calls[0];
|
||||
expect(callArgs[1].body).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should throw IyzicoResponseError for failure responses', async () => {
|
||||
const errorResponse = {
|
||||
status: 'failure',
|
||||
errorCode: 'TEST_ERROR',
|
||||
errorMessage: 'Test error message',
|
||||
};
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
json: async () => errorResponse,
|
||||
});
|
||||
|
||||
await expect(
|
||||
makeRequest({
|
||||
method: 'POST',
|
||||
path: '/test',
|
||||
body: { test: 'value' },
|
||||
apiKey,
|
||||
secretKey,
|
||||
baseUrl,
|
||||
}),
|
||||
).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should throw IyzicoResponseError for failure status in response', async () => {
|
||||
const errorResponse = {
|
||||
status: 'failure',
|
||||
errorCode: 'TEST_ERROR',
|
||||
errorMessage: 'Test error message',
|
||||
};
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => errorResponse,
|
||||
});
|
||||
|
||||
await expect(
|
||||
makeRequest({
|
||||
method: 'POST',
|
||||
path: '/test',
|
||||
body: { test: 'value' },
|
||||
apiKey,
|
||||
secretKey,
|
||||
baseUrl,
|
||||
}),
|
||||
).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should use fallback message when errorMessage is missing', async () => {
|
||||
const errorResponse = {
|
||||
status: 'failure',
|
||||
errorCode: 'TEST_ERROR',
|
||||
// errorMessage is missing
|
||||
};
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => errorResponse,
|
||||
});
|
||||
|
||||
await expect(
|
||||
makeRequest({
|
||||
method: 'POST',
|
||||
path: '/test',
|
||||
body: { test: 'value' },
|
||||
apiKey,
|
||||
secretKey,
|
||||
baseUrl,
|
||||
}),
|
||||
).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should handle network errors with fetch in message', async () => {
|
||||
const networkError = new TypeError('Failed to fetch');
|
||||
mockFetch.mockRejectedValueOnce(networkError);
|
||||
|
||||
await expect(
|
||||
makeRequest({
|
||||
method: 'POST',
|
||||
path: '/test',
|
||||
body: { test: 'value' },
|
||||
apiKey,
|
||||
secretKey,
|
||||
baseUrl,
|
||||
}),
|
||||
).rejects.toThrow(IyzicoRequestError);
|
||||
});
|
||||
|
||||
it('should rethrow IyzicoResponseError', async () => {
|
||||
const errorResponse = {
|
||||
status: 'failure',
|
||||
errorCode: 'TEST_ERROR',
|
||||
errorMessage: 'Test error',
|
||||
} as const;
|
||||
const responseError = new IyzicoResponseError('Test error', errorResponse);
|
||||
mockFetch.mockRejectedValueOnce(responseError);
|
||||
|
||||
await expect(
|
||||
makeRequest({
|
||||
method: 'POST',
|
||||
path: '/test',
|
||||
body: { test: 'value' },
|
||||
apiKey,
|
||||
secretKey,
|
||||
baseUrl,
|
||||
}),
|
||||
).rejects.toThrow(IyzicoResponseError);
|
||||
});
|
||||
|
||||
it('should rethrow IyzicoRequestError', async () => {
|
||||
const requestError = new IyzicoRequestError('Request failed');
|
||||
mockFetch.mockRejectedValueOnce(requestError);
|
||||
|
||||
await expect(
|
||||
makeRequest({
|
||||
method: 'POST',
|
||||
path: '/test',
|
||||
body: { test: 'value' },
|
||||
apiKey,
|
||||
secretKey,
|
||||
baseUrl,
|
||||
}),
|
||||
).rejects.toThrow(IyzicoRequestError);
|
||||
});
|
||||
|
||||
it('should rethrow IyzicoError', async () => {
|
||||
const iyzicoError = new IyzicoError('Iyzico error');
|
||||
mockFetch.mockRejectedValueOnce(iyzicoError);
|
||||
|
||||
await expect(
|
||||
makeRequest({
|
||||
method: 'POST',
|
||||
path: '/test',
|
||||
body: { test: 'value' },
|
||||
apiKey,
|
||||
secretKey,
|
||||
baseUrl,
|
||||
}),
|
||||
).rejects.toThrow(IyzicoError);
|
||||
});
|
||||
|
||||
it('should wrap unknown errors in IyzicoError', async () => {
|
||||
const unknownError = new Error('Unknown error');
|
||||
mockFetch.mockRejectedValueOnce(unknownError);
|
||||
|
||||
await expect(
|
||||
makeRequest({
|
||||
method: 'POST',
|
||||
path: '/test',
|
||||
body: { test: 'value' },
|
||||
apiKey,
|
||||
secretKey,
|
||||
baseUrl,
|
||||
}),
|
||||
).rejects.toThrow(IyzicoError);
|
||||
});
|
||||
|
||||
it('should wrap non-Error objects in IyzicoError', async () => {
|
||||
mockFetch.mockRejectedValueOnce('String error');
|
||||
|
||||
await expect(
|
||||
makeRequest({
|
||||
method: 'POST',
|
||||
path: '/test',
|
||||
body: { test: 'value' },
|
||||
apiKey,
|
||||
secretKey,
|
||||
baseUrl,
|
||||
}),
|
||||
).rejects.toThrow(IyzicoError);
|
||||
});
|
||||
|
||||
it('should handle DELETE request with body', async () => {
|
||||
const mockResponse = {
|
||||
status: 'success',
|
||||
data: 'deleted',
|
||||
};
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse,
|
||||
});
|
||||
|
||||
const result = await makeRequest({
|
||||
method: 'DELETE',
|
||||
path: '/test',
|
||||
body: { id: '123' },
|
||||
apiKey,
|
||||
secretKey,
|
||||
baseUrl,
|
||||
});
|
||||
|
||||
expect(result).toEqual(mockResponse);
|
||||
const callArgs = mockFetch.mock.calls[0];
|
||||
expect(callArgs[1].method).toBe('DELETE');
|
||||
expect(callArgs[1].body).toBe(JSON.stringify({ id: '123' }));
|
||||
});
|
||||
|
||||
it('should handle GET request with query string', async () => {
|
||||
const mockResponse = {
|
||||
status: 'success',
|
||||
data: 'test data',
|
||||
};
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse,
|
||||
});
|
||||
|
||||
const result = await makeRequest({
|
||||
method: 'GET',
|
||||
path: '/test?param=value',
|
||||
apiKey,
|
||||
secretKey,
|
||||
baseUrl,
|
||||
});
|
||||
|
||||
expect(result).toEqual(mockResponse);
|
||||
const callArgs = mockFetch.mock.calls[0];
|
||||
expect(callArgs[0]).toBe('https://api.test.com/test?param=value');
|
||||
});
|
||||
});
|
||||
40
tests/unit/index.test.ts
Normal file
40
tests/unit/index.test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Index exports test
|
||||
* This test ensures all exports from index.ts are accessible
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('Index exports', () => {
|
||||
it('should export IyzicoClient', async () => {
|
||||
const module = await import('../../src/index');
|
||||
expect(module.IyzicoClient).toBeDefined();
|
||||
expect(typeof module.IyzicoClient).toBe('function');
|
||||
});
|
||||
|
||||
it('should export all error classes', async () => {
|
||||
const module = await import('../../src/index');
|
||||
expect(module.IyzicoError).toBeDefined();
|
||||
expect(module.IyzicoRequestError).toBeDefined();
|
||||
expect(module.IyzicoResponseError).toBeDefined();
|
||||
expect(typeof module.IyzicoError).toBe('function');
|
||||
expect(typeof module.IyzicoRequestError).toBe('function');
|
||||
expect(typeof module.IyzicoResponseError).toBe('function');
|
||||
});
|
||||
|
||||
it('should export utility functions', async () => {
|
||||
const module = await import('../../src/index');
|
||||
expect(module.generateRandomNumber).toBeDefined();
|
||||
expect(module.generateRandomKey).toBeDefined();
|
||||
expect(typeof module.generateRandomNumber).toBe('function');
|
||||
expect(typeof module.generateRandomKey).toBe('function');
|
||||
});
|
||||
|
||||
it('should be importable', async () => {
|
||||
// Just verify the module can be imported (this covers index.ts line 1)
|
||||
const module = await import('../../src/index');
|
||||
expect(module).toBeDefined();
|
||||
expect(Object.keys(module).length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
53
tests/unit/utils.test.ts
Normal file
53
tests/unit/utils.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Utility functions unit tests
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { generateRandomNumber, generateRandomKey } from '../../src/utils';
|
||||
|
||||
describe('generateRandomNumber', () => {
|
||||
it('should generate a number within the specified range', () => {
|
||||
const result = generateRandomNumber(1, 10);
|
||||
expect(result).toBeGreaterThanOrEqual(1);
|
||||
expect(result).toBeLessThanOrEqual(10);
|
||||
});
|
||||
|
||||
it('should use default values when no arguments provided', () => {
|
||||
const result = generateRandomNumber();
|
||||
expect(result).toBeGreaterThanOrEqual(0);
|
||||
expect(result).toBeLessThanOrEqual(100);
|
||||
});
|
||||
|
||||
it('should throw error when min is greater than max', () => {
|
||||
expect(() => generateRandomNumber(10, 5)).toThrow('Min value cannot be greater than max value');
|
||||
});
|
||||
|
||||
it('should handle equal min and max', () => {
|
||||
const result = generateRandomNumber(5, 5);
|
||||
expect(result).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateRandomKey', () => {
|
||||
it('should generate a non-empty string', () => {
|
||||
const result = generateRandomKey();
|
||||
expect(result).toBeTruthy();
|
||||
expect(typeof result).toBe('string');
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should generate different keys on each call', () => {
|
||||
const key1 = generateRandomKey();
|
||||
const key2 = generateRandomKey();
|
||||
expect(key1).not.toBe(key2);
|
||||
});
|
||||
|
||||
it('should generate keys that start with timestamp', () => {
|
||||
const result = generateRandomKey();
|
||||
const timestamp = Date.now();
|
||||
const keyTimestamp = parseInt(result.substring(0, 13));
|
||||
// Should be within 1 second of current timestamp
|
||||
expect(Math.abs(timestamp - keyTimestamp)).toBeLessThan(1000);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user