large mock refector hopefully done + no errors?
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 1h19m21s
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 1h19m21s
This commit is contained in:
@@ -295,11 +295,16 @@ router.post('/flyers/process', optionalAuth, uploadToDisk.single('flyerImage'),
|
||||
extractedData = {};
|
||||
}
|
||||
|
||||
// Ensure items is an array (DB function handles zero-items case)
|
||||
const itemsArray = Array.isArray(extractedData.items) ? extractedData.items : [];
|
||||
if (!Array.isArray(extractedData.items)) {
|
||||
logger.warn('extractedData.items is missing or not an array; proceeding with empty items array.');
|
||||
}
|
||||
// Transform the extracted items into the format required for database insertion.
|
||||
// This adds default values for fields like `view_count` and `click_count`
|
||||
// and makes this legacy endpoint consistent with the newer FlyerDataTransformer service.
|
||||
const itemsForDb = (extractedData.items ?? []).map(item => ({
|
||||
...item,
|
||||
master_item_id: item.master_item_id === null ? undefined : item.master_item_id,
|
||||
view_count: 0,
|
||||
click_count: 0,
|
||||
updated_at: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
// Ensure we have a valid store name; the DB requires a non-null store name.
|
||||
const storeName = extractedData.store_name && String(extractedData.store_name).trim().length > 0
|
||||
@@ -337,7 +342,7 @@ router.post('/flyers/process', optionalAuth, uploadToDisk.single('flyerImage'),
|
||||
};
|
||||
|
||||
// 3. Create flyer and its items in a transaction
|
||||
const { flyer: newFlyer, items: newItems } = await createFlyerAndItems(flyerData, itemsArray, req.log);
|
||||
const { flyer: newFlyer, items: newItems } = await createFlyerAndItems(flyerData, itemsForDb, req.log);
|
||||
|
||||
logger.info(`Successfully processed and saved new flyer: ${newFlyer.file_name} (ID: ${newFlyer.flyer_id}) with ${newItems.length} items.`);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/routes/passport.test.ts
|
||||
// src/routes/passport.routes.test.ts
|
||||
import { describe, it, expect, vi, beforeEach, type Mocked } from 'vitest';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
@@ -105,15 +105,21 @@ describe('Passport Configuration', () => {
|
||||
|
||||
it('should call done(null, user) on successful authentication', async () => {
|
||||
// Arrange
|
||||
const mockUser = {
|
||||
const mockAuthableProfile = {
|
||||
...createMockUserProfile({
|
||||
user_id: 'user-123',
|
||||
user: { user_id: 'user-123', email: 'test@test.com' },
|
||||
points: 0,
|
||||
role: 'user' as const,
|
||||
}),
|
||||
...createMockUserWithPasswordHash({
|
||||
user_id: 'user-123',
|
||||
email: 'test@test.com',
|
||||
password_hash: 'hashed_password',
|
||||
}),
|
||||
points: 0,
|
||||
role: 'user' as const,
|
||||
refresh_token: 'mock-refresh-token',
|
||||
};
|
||||
vi.mocked(mockedDb.userRepo.findUserWithProfileByEmail).mockResolvedValue(mockUser);
|
||||
vi.mocked(mockedDb.userRepo.findUserWithProfileByEmail).mockResolvedValue(mockAuthableProfile);
|
||||
vi.mocked(bcrypt.compare).mockResolvedValue(true as never);
|
||||
|
||||
// Act
|
||||
@@ -125,17 +131,8 @@ describe('Passport Configuration', () => {
|
||||
expect(mockedDb.userRepo.findUserWithProfileByEmail).toHaveBeenCalledWith('test@test.com', logger);
|
||||
expect(bcrypt.compare).toHaveBeenCalledWith('password', 'hashed_password');
|
||||
expect(mockedDb.adminRepo.resetFailedLoginAttempts).toHaveBeenCalledWith('user-123', '127.0.0.1', logger);
|
||||
// The strategy transforms the flat DB user into a nested UserProfile structure.
|
||||
// We need to construct the expected object based on that transformation logic.
|
||||
const { password_hash, email, ...profileData } = mockUser;
|
||||
const expectedUserProfile = {
|
||||
...profileData,
|
||||
user: {
|
||||
user_id: mockUser.user_id,
|
||||
email: mockUser.email,
|
||||
}
|
||||
};
|
||||
|
||||
// The strategy now just strips auth fields.
|
||||
const { password_hash, failed_login_attempts, last_failed_login, created_at, updated_at, last_login_ip, refresh_token, email, ...expectedUserProfile } = mockAuthableProfile;
|
||||
expect(done).toHaveBeenCalledWith(null, expectedUserProfile);
|
||||
});
|
||||
|
||||
@@ -151,13 +148,18 @@ describe('Passport Configuration', () => {
|
||||
|
||||
it('should call done(null, false) and increment failed attempts on password mismatch', async () => {
|
||||
const mockUser = {
|
||||
...createMockUserProfile({
|
||||
user_id: 'user-123',
|
||||
user: { user_id: 'user-123', email: 'test@test.com' },
|
||||
points: 0,
|
||||
role: 'user' as const,
|
||||
}),
|
||||
...createMockUserWithPasswordHash({
|
||||
user_id: 'user-123',
|
||||
email: 'test@test.com',
|
||||
failed_login_attempts: 1,
|
||||
}),
|
||||
points: 0,
|
||||
role: 'user' as const,
|
||||
refresh_token: 'mock-refresh-token',
|
||||
};
|
||||
vi.mocked(mockedDb.userRepo.findUserWithProfileByEmail).mockResolvedValue(mockUser);
|
||||
vi.mocked(bcrypt.compare).mockResolvedValue(false as never);
|
||||
@@ -178,13 +180,18 @@ describe('Passport Configuration', () => {
|
||||
|
||||
it('should return a lockout message immediately if the final attempt fails', async () => {
|
||||
const mockUser = {
|
||||
...createMockUserProfile({
|
||||
user_id: 'user-123',
|
||||
user: { user_id: 'user-123', email: 'test@test.com' },
|
||||
points: 0,
|
||||
role: 'user' as const,
|
||||
}),
|
||||
...createMockUserWithPasswordHash({
|
||||
user_id: 'user-123',
|
||||
email: 'test@test.com',
|
||||
failed_login_attempts: 4, // This is the 4th failed attempt
|
||||
}),
|
||||
points: 0,
|
||||
role: 'user' as const,
|
||||
refresh_token: 'mock-refresh-token',
|
||||
};
|
||||
vi.mocked(mockedDb.userRepo.findUserWithProfileByEmail).mockResolvedValue(mockUser);
|
||||
vi.mocked(bcrypt.compare).mockResolvedValue(false as never);
|
||||
@@ -202,13 +209,18 @@ describe('Passport Configuration', () => {
|
||||
|
||||
it('should call done(null, false) for an OAuth user (no password hash)', async () => {
|
||||
const mockUser = {
|
||||
...createMockUserProfile({
|
||||
user_id: 'oauth-user',
|
||||
user: { user_id: 'oauth-user', email: 'oauth@test.com' },
|
||||
points: 0,
|
||||
role: 'user' as const,
|
||||
}),
|
||||
...createMockUserWithPasswordHash({
|
||||
user_id: 'oauth-user',
|
||||
email: 'oauth@test.com',
|
||||
password_hash: null,
|
||||
}),
|
||||
points: 0,
|
||||
role: 'user' as const,
|
||||
refresh_token: 'mock-refresh-token',
|
||||
};
|
||||
vi.mocked(mockedDb.userRepo.findUserWithProfileByEmail).mockResolvedValue(mockUser);
|
||||
|
||||
@@ -221,14 +233,19 @@ describe('Passport Configuration', () => {
|
||||
|
||||
it('should call done(null, false) if account is locked', async () => {
|
||||
const mockUser = {
|
||||
...createMockUserProfile({
|
||||
user_id: 'locked-user',
|
||||
user: { user_id: 'locked-user', email: 'locked@test.com' },
|
||||
points: 0,
|
||||
role: 'user' as const,
|
||||
}),
|
||||
...createMockUserWithPasswordHash({
|
||||
user_id: 'locked-user',
|
||||
email: 'locked@test.com',
|
||||
failed_login_attempts: 5,
|
||||
last_failed_login: new Date().toISOString(),
|
||||
}),
|
||||
points: 0,
|
||||
role: 'user' as const,
|
||||
refresh_token: 'mock-refresh-token',
|
||||
};
|
||||
vi.mocked(mockedDb.userRepo.findUserWithProfileByEmail).mockResolvedValue(mockUser);
|
||||
|
||||
@@ -241,14 +258,19 @@ describe('Passport Configuration', () => {
|
||||
|
||||
it('should allow login if lockout period has expired', async () => {
|
||||
const mockUser = {
|
||||
...createMockUserProfile({
|
||||
user_id: 'expired-lock-user',
|
||||
user: { user_id: 'expired-lock-user', email: 'expired@test.com' },
|
||||
points: 0,
|
||||
role: 'user' as const,
|
||||
}),
|
||||
...createMockUserWithPasswordHash({
|
||||
user_id: 'expired-lock-user',
|
||||
email: 'expired@test.com',
|
||||
failed_login_attempts: 5,
|
||||
last_failed_login: new Date(Date.now() - 20 * 60 * 1000).toISOString(),
|
||||
}),
|
||||
points: 0,
|
||||
role: 'user' as const,
|
||||
refresh_token: 'mock-refresh-token',
|
||||
};
|
||||
vi.mocked(mockedDb.userRepo.findUserWithProfileByEmail).mockResolvedValue(mockUser);
|
||||
vi.mocked(bcrypt.compare).mockResolvedValue(true as never); // Correct password
|
||||
|
||||
@@ -98,18 +98,11 @@ passport.use(new LocalStrategy(
|
||||
|
||||
logger.info(`User successfully authenticated: ${email}`);
|
||||
|
||||
// The `user` object from `findUserWithProfileByEmail` is a flat combination of User and Profile.
|
||||
// We transform it into the nested `UserProfile` structure used by the rest of the application
|
||||
// to ensure a consistent object shape.
|
||||
const { password_hash, email: userEmail, ...profileData } = user;
|
||||
const userProfile: UserProfile = {
|
||||
...profileData,
|
||||
user: {
|
||||
user_id: user.user_id,
|
||||
email: userEmail,
|
||||
},
|
||||
};
|
||||
|
||||
// The `user` object from `findUserWithProfileByEmail` is now a fully formed
|
||||
// UserProfile object with additional authentication fields. We must strip these
|
||||
// sensitive fields before passing the profile to the session.
|
||||
// The `...userProfile` rest parameter will contain the clean UserProfile object.
|
||||
const { password_hash, failed_login_attempts, last_failed_login, refresh_token, email: _, ...userProfile } = user;
|
||||
return done(null, userProfile);
|
||||
} catch (err: unknown) {
|
||||
req.log.error({ error: err }, 'Error during local authentication strategy:');
|
||||
|
||||
Reference in New Issue
Block a user