large mock refector hopefully done + no errors?
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 1h19m21s

This commit is contained in:
2025-12-21 12:38:53 -08:00
parent 9d5fea19b2
commit 0cf4ca02b7
25 changed files with 156 additions and 120 deletions

View File

@@ -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.`);

View File

@@ -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

View File

@@ -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:');