This commit is contained in:
Duhan BALCI
2026-01-01 18:30:21 +03:00
commit c65195d26d
44 changed files with 8715 additions and 0 deletions

View 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);
});
});
});