updated tests
This commit is contained in:
@@ -226,7 +226,7 @@ describe('Auth Integration Tests', () => {
|
|||||||
})
|
})
|
||||||
.expect(401);
|
.expect(401);
|
||||||
|
|
||||||
expect(response.body.error).toBe('Unable to log in. Please check your email and password, or create an account.');
|
expect(response.body.error).toBe('Please check your email and password, or create an account.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject login with non-existent email', async () => {
|
it('should reject login with non-existent email', async () => {
|
||||||
@@ -238,7 +238,7 @@ describe('Auth Integration Tests', () => {
|
|||||||
})
|
})
|
||||||
.expect(401);
|
.expect(401);
|
||||||
|
|
||||||
expect(response.body.error).toBe('Unable to log in. Please check your email and password, or create an account.');
|
expect(response.body.error).toBe('Please check your email and password, or create an account.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should increment login attempts on failed login', async () => {
|
it('should increment login attempts on failed login', async () => {
|
||||||
@@ -255,8 +255,8 @@ describe('Auth Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should lock account after too many failed attempts', async () => {
|
it('should lock account after too many failed attempts', async () => {
|
||||||
// Make 5 failed login attempts
|
// Make 10 failed login attempts (MAX_LOGIN_ATTEMPTS is 10)
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
await request(app)
|
await request(app)
|
||||||
.post('/auth/login')
|
.post('/auth/login')
|
||||||
.send({
|
.send({
|
||||||
@@ -265,7 +265,7 @@ describe('Auth Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6th attempt should return locked error
|
// 11th attempt should return locked error
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.post('/auth/login')
|
.post('/auth/login')
|
||||||
.send({
|
.send({
|
||||||
|
|||||||
@@ -411,20 +411,20 @@ describe('Rental Integration Tests', () => {
|
|||||||
|
|
||||||
expect(response.body.status).toBe('confirmed');
|
expect(response.body.status).toBe('confirmed');
|
||||||
|
|
||||||
// Step 2: Rental becomes active (typically done by system/webhook)
|
// Step 2: Rental is now "active" because status is confirmed and startDateTime has passed
|
||||||
await rental.update({ status: 'active' });
|
// Note: "active" is a computed status, not stored. The stored status remains "confirmed"
|
||||||
|
|
||||||
// Verify status
|
|
||||||
await rental.reload();
|
await rental.reload();
|
||||||
expect(rental.status).toBe('active');
|
expect(rental.status).toBe('confirmed'); // Stored status is still 'confirmed'
|
||||||
|
// isActive() returns true because status='confirmed' and startDateTime is in the past
|
||||||
|
|
||||||
// Step 3: Owner marks rental as completed
|
// Step 3: Owner marks rental as completed (via mark-return with status='returned')
|
||||||
response = await request(app)
|
response = await request(app)
|
||||||
.post(`/rentals/${rental.id}/mark-completed`)
|
.post(`/rentals/${rental.id}/mark-return`)
|
||||||
.set('Cookie', [`accessToken=${ownerToken}`])
|
.set('Cookie', [`accessToken=${ownerToken}`])
|
||||||
|
.send({ status: 'returned' })
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
expect(response.body.status).toBe('completed');
|
expect(response.body.rental.status).toBe('completed');
|
||||||
|
|
||||||
// Verify final state
|
// Verify final state
|
||||||
await rental.reload();
|
await rental.reload();
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
// Set CSRF_SECRET before requiring the middleware
|
||||||
|
process.env.CSRF_SECRET = 'test-csrf-secret-that-is-at-least-32-chars-long';
|
||||||
|
|
||||||
const mockTokensInstance = {
|
const mockTokensInstance = {
|
||||||
secretSync: jest.fn().mockReturnValue('mock-secret'),
|
secretSync: jest.fn().mockReturnValue(process.env.CSRF_SECRET),
|
||||||
create: jest.fn().mockReturnValue('mock-token-123'),
|
create: jest.fn().mockReturnValue('mock-token-123'),
|
||||||
verify: jest.fn().mockReturnValue(true)
|
verify: jest.fn().mockReturnValue(true)
|
||||||
};
|
};
|
||||||
@@ -12,6 +15,17 @@ jest.mock('cookie-parser', () => {
|
|||||||
return jest.fn().mockReturnValue((req, res, next) => next());
|
return jest.fn().mockReturnValue((req, res, next) => next());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('../../../utils/logger', () => ({
|
||||||
|
error: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
withRequestId: jest.fn(() => ({
|
||||||
|
error: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
const { csrfProtection, generateCSRFToken, getCSRFToken } = require('../../../middleware/csrf');
|
const { csrfProtection, generateCSRFToken, getCSRFToken } = require('../../../middleware/csrf');
|
||||||
|
|
||||||
describe('CSRF Middleware', () => {
|
describe('CSRF Middleware', () => {
|
||||||
@@ -77,7 +91,7 @@ describe('CSRF Middleware', () => {
|
|||||||
|
|
||||||
csrfProtection(req, res, next);
|
csrfProtection(req, res, next);
|
||||||
|
|
||||||
expect(mockTokensInstance.verify).toHaveBeenCalledWith('mock-secret', 'mock-token-123');
|
expect(mockTokensInstance.verify).toHaveBeenCalledWith(process.env.CSRF_SECRET, 'mock-token-123');
|
||||||
expect(next).toHaveBeenCalled();
|
expect(next).toHaveBeenCalled();
|
||||||
expect(res.status).not.toHaveBeenCalled();
|
expect(res.status).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@@ -87,7 +101,7 @@ describe('CSRF Middleware', () => {
|
|||||||
|
|
||||||
csrfProtection(req, res, next);
|
csrfProtection(req, res, next);
|
||||||
|
|
||||||
expect(mockTokensInstance.verify).toHaveBeenCalledWith('mock-secret', 'mock-token-123');
|
expect(mockTokensInstance.verify).toHaveBeenCalledWith(process.env.CSRF_SECRET, 'mock-token-123');
|
||||||
expect(next).toHaveBeenCalled();
|
expect(next).toHaveBeenCalled();
|
||||||
expect(res.status).not.toHaveBeenCalled();
|
expect(res.status).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@@ -97,7 +111,7 @@ describe('CSRF Middleware', () => {
|
|||||||
|
|
||||||
csrfProtection(req, res, next);
|
csrfProtection(req, res, next);
|
||||||
|
|
||||||
expect(mockTokensInstance.verify).toHaveBeenCalledWith('mock-secret', 'mock-token-123');
|
expect(mockTokensInstance.verify).toHaveBeenCalledWith(process.env.CSRF_SECRET, 'mock-token-123');
|
||||||
expect(next).toHaveBeenCalled();
|
expect(next).toHaveBeenCalled();
|
||||||
expect(res.status).not.toHaveBeenCalled();
|
expect(res.status).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@@ -108,7 +122,7 @@ describe('CSRF Middleware', () => {
|
|||||||
|
|
||||||
csrfProtection(req, res, next);
|
csrfProtection(req, res, next);
|
||||||
|
|
||||||
expect(mockTokensInstance.verify).toHaveBeenCalledWith('mock-secret', 'mock-token-123');
|
expect(mockTokensInstance.verify).toHaveBeenCalledWith(process.env.CSRF_SECRET, 'mock-token-123');
|
||||||
expect(next).toHaveBeenCalled();
|
expect(next).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -118,7 +132,7 @@ describe('CSRF Middleware', () => {
|
|||||||
|
|
||||||
csrfProtection(req, res, next);
|
csrfProtection(req, res, next);
|
||||||
|
|
||||||
expect(mockTokensInstance.verify).toHaveBeenCalledWith('mock-secret', 'mock-token-123');
|
expect(mockTokensInstance.verify).toHaveBeenCalledWith(process.env.CSRF_SECRET, 'mock-token-123');
|
||||||
expect(next).toHaveBeenCalled();
|
expect(next).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -128,7 +142,7 @@ describe('CSRF Middleware', () => {
|
|||||||
|
|
||||||
csrfProtection(req, res, next);
|
csrfProtection(req, res, next);
|
||||||
|
|
||||||
expect(mockTokensInstance.verify).toHaveBeenCalledWith('mock-secret', 'mock-token-123');
|
expect(mockTokensInstance.verify).toHaveBeenCalledWith(process.env.CSRF_SECRET, 'mock-token-123');
|
||||||
expect(next).toHaveBeenCalled();
|
expect(next).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -244,7 +258,7 @@ describe('CSRF Middleware', () => {
|
|||||||
|
|
||||||
csrfProtection(req, res, next);
|
csrfProtection(req, res, next);
|
||||||
|
|
||||||
expect(mockTokensInstance.verify).toHaveBeenCalledWith('mock-secret', 'mock-token-123');
|
expect(mockTokensInstance.verify).toHaveBeenCalledWith(process.env.CSRF_SECRET, 'mock-token-123');
|
||||||
expect(res.status).toHaveBeenCalledWith(403);
|
expect(res.status).toHaveBeenCalledWith(403);
|
||||||
expect(res.json).toHaveBeenCalledWith({
|
expect(res.json).toHaveBeenCalledWith({
|
||||||
error: 'Invalid CSRF token',
|
error: 'Invalid CSRF token',
|
||||||
@@ -258,7 +272,7 @@ describe('CSRF Middleware', () => {
|
|||||||
|
|
||||||
csrfProtection(req, res, next);
|
csrfProtection(req, res, next);
|
||||||
|
|
||||||
expect(mockTokensInstance.verify).toHaveBeenCalledWith('mock-secret', 'mock-token-123');
|
expect(mockTokensInstance.verify).toHaveBeenCalledWith(process.env.CSRF_SECRET, 'mock-token-123');
|
||||||
expect(next).toHaveBeenCalled();
|
expect(next).toHaveBeenCalled();
|
||||||
expect(res.status).not.toHaveBeenCalled();
|
expect(res.status).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@@ -272,7 +286,7 @@ describe('CSRF Middleware', () => {
|
|||||||
|
|
||||||
csrfProtection(req, res, next);
|
csrfProtection(req, res, next);
|
||||||
|
|
||||||
expect(mockTokensInstance.verify).toHaveBeenCalledWith('mock-secret', 'mock-token-123');
|
expect(mockTokensInstance.verify).toHaveBeenCalledWith(process.env.CSRF_SECRET, 'mock-token-123');
|
||||||
expect(next).toHaveBeenCalled();
|
expect(next).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -283,7 +297,7 @@ describe('CSRF Middleware', () => {
|
|||||||
|
|
||||||
csrfProtection(req, res, next);
|
csrfProtection(req, res, next);
|
||||||
|
|
||||||
expect(mockTokensInstance.verify).toHaveBeenCalledWith('mock-secret', 'mock-token-123');
|
expect(mockTokensInstance.verify).toHaveBeenCalledWith(process.env.CSRF_SECRET, 'mock-token-123');
|
||||||
expect(next).toHaveBeenCalled();
|
expect(next).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -294,7 +308,7 @@ describe('CSRF Middleware', () => {
|
|||||||
|
|
||||||
csrfProtection(req, res, next);
|
csrfProtection(req, res, next);
|
||||||
|
|
||||||
expect(mockTokensInstance.verify).toHaveBeenCalledWith('mock-secret', 'mock-token-123');
|
expect(mockTokensInstance.verify).toHaveBeenCalledWith(process.env.CSRF_SECRET, 'mock-token-123');
|
||||||
expect(next).toHaveBeenCalled();
|
expect(next).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -305,7 +319,7 @@ describe('CSRF Middleware', () => {
|
|||||||
|
|
||||||
csrfProtection(req, res, next);
|
csrfProtection(req, res, next);
|
||||||
|
|
||||||
expect(mockTokensInstance.verify).toHaveBeenCalledWith('mock-secret', 'mock-token-123');
|
expect(mockTokensInstance.verify).toHaveBeenCalledWith(process.env.CSRF_SECRET, 'mock-token-123');
|
||||||
expect(next).toHaveBeenCalled();
|
expect(next).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -317,7 +331,7 @@ describe('CSRF Middleware', () => {
|
|||||||
|
|
||||||
generateCSRFToken(req, res, next);
|
generateCSRFToken(req, res, next);
|
||||||
|
|
||||||
expect(mockTokensInstance.create).toHaveBeenCalledWith('mock-secret');
|
expect(mockTokensInstance.create).toHaveBeenCalledWith(process.env.CSRF_SECRET);
|
||||||
expect(res.cookie).toHaveBeenCalledWith('csrf-token', 'mock-token-123', {
|
expect(res.cookie).toHaveBeenCalledWith('csrf-token', 'mock-token-123', {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: true,
|
secure: true,
|
||||||
@@ -404,7 +418,7 @@ describe('CSRF Middleware', () => {
|
|||||||
|
|
||||||
getCSRFToken(req, res);
|
getCSRFToken(req, res);
|
||||||
|
|
||||||
expect(mockTokensInstance.create).toHaveBeenCalledWith('mock-secret');
|
expect(mockTokensInstance.create).toHaveBeenCalledWith(process.env.CSRF_SECRET);
|
||||||
expect(res.status).toHaveBeenCalledWith(204);
|
expect(res.status).toHaveBeenCalledWith(204);
|
||||||
expect(res.send).toHaveBeenCalled();
|
expect(res.send).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,15 @@ jest.mock('express-validator', () => ({
|
|||||||
trim: jest.fn().mockReturnThis(),
|
trim: jest.fn().mockReturnThis(),
|
||||||
optional: jest.fn().mockReturnThis(),
|
optional: jest.fn().mockReturnThis(),
|
||||||
isMobilePhone: jest.fn().mockReturnThis(),
|
isMobilePhone: jest.fn().mockReturnThis(),
|
||||||
notEmpty: jest.fn().mockReturnThis()
|
notEmpty: jest.fn().mockReturnThis(),
|
||||||
|
isFloat: jest.fn().mockReturnThis(),
|
||||||
|
toFloat: jest.fn().mockReturnThis()
|
||||||
|
})),
|
||||||
|
query: jest.fn(() => ({
|
||||||
|
optional: jest.fn().mockReturnThis(),
|
||||||
|
isFloat: jest.fn().mockReturnThis(),
|
||||||
|
withMessage: jest.fn().mockReturnThis(),
|
||||||
|
toFloat: jest.fn().mockReturnThis()
|
||||||
})),
|
})),
|
||||||
validationResult: jest.fn()
|
validationResult: jest.fn()
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ jest.mock('sequelize', () => ({
|
|||||||
lte: 'lte',
|
lte: 'lte',
|
||||||
iLike: 'iLike',
|
iLike: 'iLike',
|
||||||
or: 'or',
|
or: 'or',
|
||||||
not: 'not'
|
not: 'not',
|
||||||
|
ne: 'ne'
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -199,7 +200,9 @@ describe('Items Routes', () => {
|
|||||||
{
|
{
|
||||||
model: mockUserModel,
|
model: mockUserModel,
|
||||||
as: 'owner',
|
as: 'owner',
|
||||||
attributes: ['id', 'firstName', 'lastName', 'imageFilename']
|
attributes: ['id', 'firstName', 'lastName', 'imageFilename'],
|
||||||
|
where: { isBanned: { 'ne': true } },
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
limit: 20,
|
limit: 20,
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ jest.mock('../../../services/stripeService', () => ({
|
|||||||
chargePaymentMethod: jest.fn(),
|
chargePaymentMethod: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../services/stripeWebhookService', () => ({
|
||||||
|
reconcilePayoutStatuses: jest.fn().mockResolvedValue(),
|
||||||
|
}));
|
||||||
|
|
||||||
const { Rental, Item, User } = require('../../../models');
|
const { Rental, Item, User } = require('../../../models');
|
||||||
const FeeCalculator = require('../../../utils/feeCalculator');
|
const FeeCalculator = require('../../../utils/feeCalculator');
|
||||||
const RentalDurationCalculator = require('../../../utils/rentalDurationCalculator');
|
const RentalDurationCalculator = require('../../../utils/rentalDurationCalculator');
|
||||||
@@ -588,10 +592,13 @@ describe('Rentals Routes', () => {
|
|||||||
.put('/rentals/1/status')
|
.put('/rentals/1/status')
|
||||||
.send({ status: 'confirmed' });
|
.send({ status: 'confirmed' });
|
||||||
|
|
||||||
expect(response.status).toBe(400);
|
expect(response.status).toBe(402);
|
||||||
expect(response.body).toEqual({
|
expect(response.body).toEqual({
|
||||||
error: 'Payment failed during approval',
|
error: 'payment_failed',
|
||||||
details: 'Payment failed',
|
code: 'unknown_error',
|
||||||
|
ownerMessage: 'The payment could not be processed.',
|
||||||
|
renterMessage: 'Your payment could not be processed. Please try a different payment method.',
|
||||||
|
rentalId: 1,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -798,63 +805,6 @@ describe('Rentals Routes', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /:id/mark-completed', () => {
|
|
||||||
// Active status is computed: confirmed + startDateTime in the past
|
|
||||||
const pastDate = new Date();
|
|
||||||
pastDate.setHours(pastDate.getHours() - 1); // 1 hour ago
|
|
||||||
const mockRental = {
|
|
||||||
id: 1,
|
|
||||||
ownerId: 1,
|
|
||||||
renterId: 2,
|
|
||||||
status: 'confirmed',
|
|
||||||
startDateTime: pastDate,
|
|
||||||
update: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockRentalFindByPk.mockResolvedValue(mockRental);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow owner to mark rental as completed', async () => {
|
|
||||||
const completedRental = { ...mockRental, status: 'completed' };
|
|
||||||
mockRentalFindByPk
|
|
||||||
.mockResolvedValueOnce(mockRental)
|
|
||||||
.mockResolvedValueOnce(completedRental);
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/rentals/1/mark-completed');
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(mockRental.update).toHaveBeenCalledWith({ status: 'completed', payoutStatus: 'pending' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 403 for non-owner', async () => {
|
|
||||||
const nonOwnerRental = { ...mockRental, ownerId: 3 };
|
|
||||||
mockRentalFindByPk.mockResolvedValue(nonOwnerRental);
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/rentals/1/mark-completed');
|
|
||||||
|
|
||||||
expect(response.status).toBe(403);
|
|
||||||
expect(response.body).toEqual({
|
|
||||||
error: 'Only owners can mark rentals as completed'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 400 for invalid status', async () => {
|
|
||||||
const pendingRental = { ...mockRental, status: 'pending' };
|
|
||||||
mockRentalFindByPk.mockResolvedValue(pendingRental);
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/rentals/1/mark-completed');
|
|
||||||
|
|
||||||
expect(response.status).toBe(400);
|
|
||||||
expect(response.body).toEqual({
|
|
||||||
error: 'Can only mark active rentals as completed',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /calculate-fees', () => {
|
describe('POST /calculate-fees', () => {
|
||||||
it('should calculate fees for given amount', async () => {
|
it('should calculate fees for given amount', async () => {
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
@@ -936,6 +886,9 @@ describe('Rentals Routes', () => {
|
|||||||
'payoutStatus',
|
'payoutStatus',
|
||||||
'payoutProcessedAt',
|
'payoutProcessedAt',
|
||||||
'stripeTransferId',
|
'stripeTransferId',
|
||||||
|
'bankDepositStatus',
|
||||||
|
'bankDepositAt',
|
||||||
|
'bankDepositFailureCode',
|
||||||
],
|
],
|
||||||
include: [{ model: Item, as: 'item', attributes: ['name'] }],
|
include: [{ model: Item, as: 'item', attributes: ['name'] }],
|
||||||
order: [['createdAt', 'DESC']],
|
order: [['createdAt', 'DESC']],
|
||||||
|
|||||||
@@ -421,6 +421,11 @@ describe("Stripe Routes", () => {
|
|||||||
const mockUser = {
|
const mockUser = {
|
||||||
id: 1,
|
id: 1,
|
||||||
stripeConnectedAccountId: "acct_123456789",
|
stripeConnectedAccountId: "acct_123456789",
|
||||||
|
stripePayoutsEnabled: true,
|
||||||
|
stripeRequirementsCurrentlyDue: [],
|
||||||
|
stripeRequirementsPastDue: [],
|
||||||
|
stripeDisabledReason: null,
|
||||||
|
update: jest.fn().mockResolvedValue(true),
|
||||||
};
|
};
|
||||||
|
|
||||||
it("should get account status successfully", async () => {
|
it("should get account status successfully", async () => {
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ jest.mock("../../../middleware/auth", () => ({
|
|||||||
};
|
};
|
||||||
next();
|
next();
|
||||||
}),
|
}),
|
||||||
|
optionalAuth: jest.fn((req, res, next) => next()),
|
||||||
|
requireAdmin: jest.fn((req, res, next) => next()),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("../../../services/UserService", () => ({
|
jest.mock("../../../services/UserService", () => ({
|
||||||
@@ -365,7 +367,7 @@ describe("Users Routes", () => {
|
|||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
expect(response.body).toEqual(mockUser);
|
expect(response.body).toEqual(mockUser);
|
||||||
expect(mockUserFindByPk).toHaveBeenCalledWith("2", {
|
expect(mockUserFindByPk).toHaveBeenCalledWith("2", {
|
||||||
attributes: { exclude: ["password", "email", "phone", "address"] },
|
attributes: { exclude: ["password", "email", "phone", "address", "verificationToken", "passwordResetToken", "isBanned", "bannedAt", "bannedBy", "banReason"] },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -11,18 +11,26 @@ jest.mock('@googlemaps/google-maps-services-js', () => ({
|
|||||||
}))
|
}))
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const mockLoggerInfo = jest.fn();
|
||||||
|
const mockLoggerError = jest.fn();
|
||||||
|
jest.mock('../../../utils/logger', () => ({
|
||||||
|
info: mockLoggerInfo,
|
||||||
|
error: mockLoggerError,
|
||||||
|
warn: jest.fn(),
|
||||||
|
withRequestId: jest.fn(() => ({
|
||||||
|
info: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('GoogleMapsService', () => {
|
describe('GoogleMapsService', () => {
|
||||||
let service;
|
let service;
|
||||||
let consoleSpy, consoleErrorSpy;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Clear all mocks
|
// Clear all mocks
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
// Set up console spies
|
|
||||||
consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
||||||
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
||||||
|
|
||||||
// Reset environment
|
// Reset environment
|
||||||
delete process.env.GOOGLE_MAPS_API_KEY;
|
delete process.env.GOOGLE_MAPS_API_KEY;
|
||||||
|
|
||||||
@@ -30,18 +38,13 @@ describe('GoogleMapsService', () => {
|
|||||||
jest.resetModules();
|
jest.resetModules();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
consoleSpy.mockRestore();
|
|
||||||
consoleErrorSpy.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Constructor', () => {
|
describe('Constructor', () => {
|
||||||
it('should initialize with API key and log success', () => {
|
it('should initialize with API key and log success', () => {
|
||||||
process.env.GOOGLE_MAPS_API_KEY = 'test-api-key';
|
process.env.GOOGLE_MAPS_API_KEY = 'test-api-key';
|
||||||
|
|
||||||
service = require('../../../services/googleMapsService');
|
service = require('../../../services/googleMapsService');
|
||||||
|
|
||||||
expect(consoleSpy).toHaveBeenCalledWith('✅ Google Maps service initialized');
|
expect(mockLoggerInfo).toHaveBeenCalledWith('Google Maps service initialized');
|
||||||
expect(service.isConfigured()).toBe(true);
|
expect(service.isConfigured()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -50,7 +53,7 @@ describe('GoogleMapsService', () => {
|
|||||||
|
|
||||||
service = require('../../../services/googleMapsService');
|
service = require('../../../services/googleMapsService');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith('❌ Google Maps API key not configured in environment variables');
|
expect(mockLoggerError).toHaveBeenCalledWith('Google Maps API key not configured in environment variables');
|
||||||
expect(service.isConfigured()).toBe(false);
|
expect(service.isConfigured()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -299,10 +302,11 @@ describe('GoogleMapsService', () => {
|
|||||||
status: 'ZERO_RESULTS'
|
status: 'ZERO_RESULTS'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Places Autocomplete API error:',
|
'Places Autocomplete API error',
|
||||||
'ZERO_RESULTS',
|
expect.objectContaining({
|
||||||
'No results found'
|
status: 'ZERO_RESULTS',
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -325,7 +329,7 @@ describe('GoogleMapsService', () => {
|
|||||||
|
|
||||||
await expect(service.getPlacesAutocomplete('test input')).rejects.toThrow('Failed to fetch place predictions');
|
await expect(service.getPlacesAutocomplete('test input')).rejects.toThrow('Failed to fetch place predictions');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith('Places Autocomplete service error:', 'Network error');
|
expect(mockLoggerError).toHaveBeenCalledWith('Places Autocomplete service error', expect.objectContaining({ error: expect.any(Error) }));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -557,10 +561,11 @@ describe('GoogleMapsService', () => {
|
|||||||
|
|
||||||
await expect(service.getPlaceDetails('ChIJ123')).rejects.toThrow('The specified place was not found');
|
await expect(service.getPlaceDetails('ChIJ123')).rejects.toThrow('The specified place was not found');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Place Details API error:',
|
'Place Details API error',
|
||||||
'NOT_FOUND',
|
expect.objectContaining({
|
||||||
'Place not found'
|
status: 'NOT_FOUND',
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -582,7 +587,7 @@ describe('GoogleMapsService', () => {
|
|||||||
|
|
||||||
await expect(service.getPlaceDetails('ChIJ123')).rejects.toThrow(originalError);
|
await expect(service.getPlaceDetails('ChIJ123')).rejects.toThrow(originalError);
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith('Place Details service error:', 'Network error');
|
expect(mockLoggerError).toHaveBeenCalledWith('Place Details service error', expect.objectContaining({ error: expect.any(Error) }));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -769,10 +774,11 @@ describe('GoogleMapsService', () => {
|
|||||||
status: 'ZERO_RESULTS'
|
status: 'ZERO_RESULTS'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Geocoding API error:',
|
'Geocoding API error',
|
||||||
'ZERO_RESULTS',
|
expect.objectContaining({
|
||||||
'No results found'
|
status: 'ZERO_RESULTS',
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -796,7 +802,7 @@ describe('GoogleMapsService', () => {
|
|||||||
|
|
||||||
await expect(service.geocodeAddress('123 Test St')).rejects.toThrow('Failed to geocode address');
|
await expect(service.geocodeAddress('123 Test St')).rejects.toThrow('Failed to geocode address');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith('Geocoding service error:', 'Network error');
|
expect(mockLoggerError).toHaveBeenCalledWith('Geocoding service error', expect.objectContaining({ error: expect.any(Error) }));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,6 +18,19 @@ jest.mock('sequelize', () => ({
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const mockLoggerError = jest.fn();
|
||||||
|
const mockLoggerInfo = jest.fn();
|
||||||
|
jest.mock('../../../utils/logger', () => ({
|
||||||
|
error: mockLoggerError,
|
||||||
|
info: mockLoggerInfo,
|
||||||
|
warn: jest.fn(),
|
||||||
|
withRequestId: jest.fn(() => ({
|
||||||
|
error: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
const PayoutService = require('../../../services/payoutService');
|
const PayoutService = require('../../../services/payoutService');
|
||||||
const { Rental, User, Item } = require('../../../models');
|
const { Rental, User, Item } = require('../../../models');
|
||||||
const StripeService = require('../../../services/stripeService');
|
const StripeService = require('../../../services/stripeService');
|
||||||
@@ -30,19 +43,15 @@ const mockItemModel = Item;
|
|||||||
const mockCreateTransfer = StripeService.createTransfer;
|
const mockCreateTransfer = StripeService.createTransfer;
|
||||||
|
|
||||||
describe('PayoutService', () => {
|
describe('PayoutService', () => {
|
||||||
let consoleSpy, consoleErrorSpy;
|
let consoleSpy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
// Set up console spies
|
|
||||||
consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||||
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
consoleSpy.mockRestore();
|
consoleSpy.mockRestore();
|
||||||
consoleErrorSpy.mockRestore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getEligiblePayouts', () => {
|
describe('getEligiblePayouts', () => {
|
||||||
@@ -87,7 +96,8 @@ describe('PayoutService', () => {
|
|||||||
where: {
|
where: {
|
||||||
stripeConnectedAccountId: {
|
stripeConnectedAccountId: {
|
||||||
'not': null
|
'not': null
|
||||||
}
|
},
|
||||||
|
stripePayoutsEnabled: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -106,7 +116,7 @@ describe('PayoutService', () => {
|
|||||||
|
|
||||||
await expect(PayoutService.getEligiblePayouts()).rejects.toThrow('Database connection failed');
|
await expect(PayoutService.getEligiblePayouts()).rejects.toThrow('Database connection failed');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith('Error getting eligible payouts:', dbError);
|
expect(mockLoggerError).toHaveBeenCalledWith('Error getting eligible payouts', expect.objectContaining({ error: dbError.message }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return empty array when no eligible rentals found', async () => {
|
it('should return empty array when no eligible rentals found', async () => {
|
||||||
@@ -269,9 +279,9 @@ describe('PayoutService', () => {
|
|||||||
payoutStatus: 'failed'
|
payoutStatus: 'failed'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error processing payout for rental 1:',
|
'Error processing payout for rental',
|
||||||
stripeError
|
expect.objectContaining({ rentalId: 1 })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -287,9 +297,9 @@ describe('PayoutService', () => {
|
|||||||
await expect(PayoutService.processRentalPayout(mockRental))
|
await expect(PayoutService.processRentalPayout(mockRental))
|
||||||
.rejects.toThrow('Database update failed');
|
.rejects.toThrow('Database update failed');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error processing payout for rental 1:',
|
'Error processing payout for rental',
|
||||||
dbError
|
expect.objectContaining({ rentalId: 1 })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -306,9 +316,9 @@ describe('PayoutService', () => {
|
|||||||
.rejects.toThrow('Database completion update failed');
|
.rejects.toThrow('Database completion update failed');
|
||||||
|
|
||||||
expect(mockCreateTransfer).toHaveBeenCalled();
|
expect(mockCreateTransfer).toHaveBeenCalled();
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error processing payout for rental 1:',
|
'Error processing payout for rental',
|
||||||
dbError
|
expect.objectContaining({ rentalId: 1 })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -438,9 +448,9 @@ describe('PayoutService', () => {
|
|||||||
await expect(PayoutService.processAllEligiblePayouts())
|
await expect(PayoutService.processAllEligiblePayouts())
|
||||||
.rejects.toThrow('Database connection failed');
|
.rejects.toThrow('Database connection failed');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error processing all eligible payouts:',
|
'Error processing all eligible payouts',
|
||||||
dbError
|
expect.objectContaining({ error: dbError.message })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -520,7 +530,8 @@ describe('PayoutService', () => {
|
|||||||
where: {
|
where: {
|
||||||
stripeConnectedAccountId: {
|
stripeConnectedAccountId: {
|
||||||
'not': null
|
'not': null
|
||||||
}
|
},
|
||||||
|
stripePayoutsEnabled: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -613,9 +624,9 @@ describe('PayoutService', () => {
|
|||||||
await expect(PayoutService.retryFailedPayouts())
|
await expect(PayoutService.retryFailedPayouts())
|
||||||
.rejects.toThrow('Database query failed');
|
.rejects.toThrow('Database query failed');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error retrying failed payouts:',
|
'Error retrying failed payouts',
|
||||||
dbError
|
expect.objectContaining({ error: dbError.message })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -655,9 +666,9 @@ describe('PayoutService', () => {
|
|||||||
await expect(PayoutService.processRentalPayout(mockRental))
|
await expect(PayoutService.processRentalPayout(mockRental))
|
||||||
.rejects.toThrow('Update failed');
|
.rejects.toThrow('Update failed');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error processing payout for rental 123:',
|
'Error processing payout for rental',
|
||||||
expect.any(Error)
|
expect.objectContaining({ rentalId: 123 })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,19 @@ jest.mock('../../../services/stripeService', () => ({
|
|||||||
createRefund: mockCreateRefund
|
createRefund: mockCreateRefund
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const mockLoggerError = jest.fn();
|
||||||
|
const mockLoggerWarn = jest.fn();
|
||||||
|
jest.mock('../../../utils/logger', () => ({
|
||||||
|
error: mockLoggerError,
|
||||||
|
warn: mockLoggerWarn,
|
||||||
|
info: jest.fn(),
|
||||||
|
withRequestId: jest.fn(() => ({
|
||||||
|
error: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
const RefundService = require('../../../services/refundService');
|
const RefundService = require('../../../services/refundService');
|
||||||
|
|
||||||
describe('RefundService', () => {
|
describe('RefundService', () => {
|
||||||
@@ -540,8 +553,9 @@ describe('RefundService', () => {
|
|||||||
const result = await RefundService.processCancellation(1, 200);
|
const result = await RefundService.processCancellation(1, 200);
|
||||||
|
|
||||||
expect(mockCreateRefund).not.toHaveBeenCalled();
|
expect(mockCreateRefund).not.toHaveBeenCalled();
|
||||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
expect(mockLoggerWarn).toHaveBeenCalledWith(
|
||||||
'Refund amount calculated but no payment intent ID for rental 1'
|
'Refund amount calculated but no payment intent ID for rental',
|
||||||
|
{ rentalId: 1 }
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result.refund).toEqual({
|
expect(result.refund).toEqual({
|
||||||
@@ -605,9 +619,9 @@ describe('RefundService', () => {
|
|||||||
await expect(RefundService.processCancellation(1, 200))
|
await expect(RefundService.processCancellation(1, 200))
|
||||||
.rejects.toThrow('Failed to process refund: Refund failed');
|
.rejects.toThrow('Failed to process refund: Refund failed');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error processing Stripe refund:',
|
'Error processing Stripe refund',
|
||||||
stripeError
|
expect.objectContaining({ error: stripeError })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -41,27 +41,28 @@ jest.mock('stripe', () => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mockLoggerError = jest.fn();
|
||||||
|
jest.mock('../../../utils/logger', () => ({
|
||||||
|
error: mockLoggerError,
|
||||||
|
info: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
withRequestId: jest.fn(() => ({
|
||||||
|
error: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
const StripeService = require('../../../services/stripeService');
|
const StripeService = require('../../../services/stripeService');
|
||||||
|
|
||||||
describe('StripeService', () => {
|
describe('StripeService', () => {
|
||||||
let consoleSpy, consoleErrorSpy;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
// Set up console spies
|
|
||||||
consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
||||||
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
||||||
|
|
||||||
// Set environment variables for tests
|
// Set environment variables for tests
|
||||||
process.env.FRONTEND_URL = 'http://localhost:3000';
|
process.env.FRONTEND_URL = 'http://localhost:3000';
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
consoleSpy.mockRestore();
|
|
||||||
consoleErrorSpy.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getCheckoutSession', () => {
|
describe('getCheckoutSession', () => {
|
||||||
it('should retrieve checkout session successfully', async () => {
|
it('should retrieve checkout session successfully', async () => {
|
||||||
const mockSession = {
|
const mockSession = {
|
||||||
@@ -93,9 +94,11 @@ describe('StripeService', () => {
|
|||||||
await expect(StripeService.getCheckoutSession('invalid_session'))
|
await expect(StripeService.getCheckoutSession('invalid_session'))
|
||||||
.rejects.toThrow('Session not found');
|
.rejects.toThrow('Session not found');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error retrieving checkout session:',
|
'Error retrieving checkout session',
|
||||||
stripeError
|
expect.objectContaining({
|
||||||
|
error: stripeError.message,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -174,9 +177,11 @@ describe('StripeService', () => {
|
|||||||
email: 'invalid-email'
|
email: 'invalid-email'
|
||||||
})).rejects.toThrow('Invalid email address');
|
})).rejects.toThrow('Invalid email address');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error creating connected account:',
|
'Error creating connected account',
|
||||||
stripeError
|
expect.objectContaining({
|
||||||
|
error: stripeError.message,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -225,9 +230,11 @@ describe('StripeService', () => {
|
|||||||
'http://localhost:3000/return'
|
'http://localhost:3000/return'
|
||||||
)).rejects.toThrow('Account not found');
|
)).rejects.toThrow('Account not found');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error creating account link:',
|
'Error creating account link',
|
||||||
stripeError
|
expect.objectContaining({
|
||||||
|
error: stripeError.message,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -287,9 +294,11 @@ describe('StripeService', () => {
|
|||||||
await expect(StripeService.getAccountStatus('invalid_account'))
|
await expect(StripeService.getAccountStatus('invalid_account'))
|
||||||
.rejects.toThrow('Account not found');
|
.rejects.toThrow('Account not found');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error retrieving account status:',
|
'Error retrieving account status',
|
||||||
stripeError
|
expect.objectContaining({
|
||||||
|
error: stripeError.message,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -420,9 +429,11 @@ describe('StripeService', () => {
|
|||||||
destination: 'acct_123456789'
|
destination: 'acct_123456789'
|
||||||
})).rejects.toThrow('Insufficient funds');
|
})).rejects.toThrow('Insufficient funds');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error creating transfer:',
|
'Error creating transfer',
|
||||||
stripeError
|
expect.objectContaining({
|
||||||
|
error: stripeError.message,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -558,9 +569,11 @@ describe('StripeService', () => {
|
|||||||
amount: 50.00
|
amount: 50.00
|
||||||
})).rejects.toThrow('Payment intent not found');
|
})).rejects.toThrow('Payment intent not found');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error creating refund:',
|
'Error creating refund',
|
||||||
stripeError
|
expect.objectContaining({
|
||||||
|
error: stripeError.message,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -617,9 +630,11 @@ describe('StripeService', () => {
|
|||||||
await expect(StripeService.getRefund('re_invalid'))
|
await expect(StripeService.getRefund('re_invalid'))
|
||||||
.rejects.toThrow('Refund not found');
|
.rejects.toThrow('Refund not found');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error retrieving refund:',
|
'Error retrieving refund',
|
||||||
stripeError
|
expect.objectContaining({
|
||||||
|
error: stripeError.message,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -698,11 +713,13 @@ describe('StripeService', () => {
|
|||||||
'pm_invalid',
|
'pm_invalid',
|
||||||
50.00,
|
50.00,
|
||||||
'cus_123456789'
|
'cus_123456789'
|
||||||
)).rejects.toThrow('Payment method declined');
|
)).rejects.toThrow('The payment could not be processed.');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error charging payment method:',
|
'Payment failed',
|
||||||
stripeError
|
expect.objectContaining({
|
||||||
|
code: expect.any(String),
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -839,9 +856,11 @@ describe('StripeService', () => {
|
|||||||
email: 'invalid-email'
|
email: 'invalid-email'
|
||||||
})).rejects.toThrow('Invalid email format');
|
})).rejects.toThrow('Invalid email format');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error creating customer:',
|
'Error creating customer',
|
||||||
stripeError
|
expect.objectContaining({
|
||||||
|
error: stripeError.message,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -886,6 +905,11 @@ describe('StripeService', () => {
|
|||||||
mode: 'setup',
|
mode: 'setup',
|
||||||
ui_mode: 'embedded',
|
ui_mode: 'embedded',
|
||||||
redirect_on_completion: 'never',
|
redirect_on_completion: 'never',
|
||||||
|
payment_method_options: {
|
||||||
|
card: {
|
||||||
|
request_three_d_secure: 'any',
|
||||||
|
},
|
||||||
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
type: 'payment_method_setup',
|
type: 'payment_method_setup',
|
||||||
userId: '123'
|
userId: '123'
|
||||||
@@ -919,6 +943,11 @@ describe('StripeService', () => {
|
|||||||
mode: 'setup',
|
mode: 'setup',
|
||||||
ui_mode: 'embedded',
|
ui_mode: 'embedded',
|
||||||
redirect_on_completion: 'never',
|
redirect_on_completion: 'never',
|
||||||
|
payment_method_options: {
|
||||||
|
card: {
|
||||||
|
request_three_d_secure: 'any',
|
||||||
|
},
|
||||||
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
type: 'payment_method_setup'
|
type: 'payment_method_setup'
|
||||||
}
|
}
|
||||||
@@ -934,9 +963,11 @@ describe('StripeService', () => {
|
|||||||
customerId: 'cus_invalid'
|
customerId: 'cus_invalid'
|
||||||
})).rejects.toThrow('Customer not found');
|
})).rejects.toThrow('Customer not found');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error creating setup checkout session:',
|
'Error creating setup checkout session',
|
||||||
stripeError
|
expect.objectContaining({
|
||||||
|
error: stripeError.message,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1015,9 +1046,11 @@ describe('StripeService', () => {
|
|||||||
destination: 'acct_123456789'
|
destination: 'acct_123456789'
|
||||||
})).rejects.toThrow('Request timeout');
|
})).rejects.toThrow('Request timeout');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error creating transfer:',
|
'Error creating transfer',
|
||||||
timeoutError
|
expect.objectContaining({
|
||||||
|
error: timeoutError.message,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1030,9 +1063,11 @@ describe('StripeService', () => {
|
|||||||
email: 'test@example.com'
|
email: 'test@example.com'
|
||||||
})).rejects.toThrow('Invalid API key');
|
})).rejects.toThrow('Invalid API key');
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(mockLoggerError).toHaveBeenCalledWith(
|
||||||
'Error creating customer:',
|
'Error creating customer',
|
||||||
apiKeyError
|
expect.objectContaining({
|
||||||
|
error: apiKeyError.message,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -442,6 +442,10 @@ describe("StripeWebhookService", () => {
|
|||||||
expect(mockUser.update).toHaveBeenCalledWith({
|
expect(mockUser.update).toHaveBeenCalledWith({
|
||||||
stripeConnectedAccountId: null,
|
stripeConnectedAccountId: null,
|
||||||
stripePayoutsEnabled: false,
|
stripePayoutsEnabled: false,
|
||||||
|
stripeDisabledReason: null,
|
||||||
|
stripeRequirementsCurrentlyDue: [],
|
||||||
|
stripeRequirementsPastDue: [],
|
||||||
|
stripeRequirementsLastUpdated: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -525,12 +529,12 @@ describe("StripeWebhookService", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should use name fallback when firstName is not available", async () => {
|
it("should use lastName fallback when firstName is not available", async () => {
|
||||||
const mockUser = {
|
const mockUser = {
|
||||||
id: 1,
|
id: 1,
|
||||||
email: "owner@test.com",
|
email: "owner@test.com",
|
||||||
firstName: null,
|
firstName: null,
|
||||||
name: "Full Name",
|
lastName: "Smith",
|
||||||
update: jest.fn().mockResolvedValue(true),
|
update: jest.fn().mockResolvedValue(true),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -545,7 +549,7 @@ describe("StripeWebhookService", () => {
|
|||||||
expect(emailServices.payment.sendAccountDisconnectedEmail).toHaveBeenCalledWith(
|
expect(emailServices.payment.sendAccountDisconnectedEmail).toHaveBeenCalledWith(
|
||||||
"owner@test.com",
|
"owner@test.com",
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerName: "Full Name",
|
ownerName: "Smith",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user