lootsa tests fixes
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 4m31s

This commit is contained in:
2025-12-05 21:21:58 -08:00
parent b456546feb
commit 6cf9829537
5 changed files with 46 additions and 81 deletions

View File

@@ -375,6 +375,11 @@ describe('ProfileManager Authenticated User Features', () => {
// --- Profile Tab ---
it('should allow updating the user profile', async () => {
// Explicitly mock a successful response for this test using the standard Response API
(mockedApiClient.updateUserProfile as Mock).mockResolvedValue(
new Response(JSON.stringify({ ...authenticatedProfile, full_name: 'Updated Name' }), { status: 200, statusText: 'OK' })
);
render(<ProfileManager {...authenticatedProps} />);
const nameInput = screen.getByLabelText(/full name/i);
@@ -392,7 +397,7 @@ describe('ProfileManager Authenticated User Features', () => {
avatar_url: 'http://example.com/avatar.png',
});
expect(mockOnProfileUpdate).toHaveBeenCalledWith(expect.objectContaining({ full_name: 'Updated Name' }));
expect(notifySuccess).toHaveBeenCalledWith('Profile updated successfully!');
expect(notifySuccess).toHaveBeenCalledWith('Profile and address updated successfully!');
});
});

View File

@@ -81,19 +81,29 @@ vi.mock('./passport.routes', () => ({
}));
// Setup Express App
const app = express();
// Rely on the route-specific middleware which sets strict: false.
app.use(express.json({ strict: false }));
app.use('/api/users', userRouter);
const createApp = (authenticatedUser?: any) => {
const app = express();
app.use(express.json({ strict: false }));
if (authenticatedUser) {
app.use((req, res, next) => {
req.user = authenticatedUser;
next();
});
}
app.use('/api/users', userRouter);
return app;
};
describe('User Routes (/api/users)', () => {
beforeEach(() => {
vi.clearAllMocks();
// It's good practice to reset the app's middleware stack if tests modify it.
});
describe('when user is not authenticated', () => {
it('GET /profile should return 401 because the mockAuth middleware is not active for this test block', async () => {
const app = createApp(); // No user injected
const response = await supertest(app).get('/api/users/watched-items');
expect(response.status).toBe(401);
});
@@ -101,39 +111,23 @@ describe('User Routes (/api/users)', () => {
describe('when user is authenticated', () => {
const mockUserProfile = createMockUserProfile({ user_id: 'user-123' });
let app: express.Express;
beforeEach(() => {
// Create a new router instance for this describe block to attach middleware to,
// preventing middleware from stacking up across tests.
const userRouterWithAuth = express.Router();
userRouterWithAuth.use((req, res, next) => {
req.user = mockUserProfile; next();
}, userRouter);
app.use('/api/users', userRouterWithAuth);
app = createApp(mockUserProfile);
});
describe('GET /profile', () => {
it('should return the full user profile', async () => {
// Arrange
vi.mocked(userDb.findUserProfileById).mockResolvedValue(mockUserProfile);
// Act
// Manually attach the user for this specific test case
const response = await supertest(app).get('/api/users/profile');
// Assert
expect(response.status).toBe(200);
expect(response.body).toEqual(mockUserProfile);
expect(userDb.findUserProfileById).toHaveBeenCalledWith(mockUserProfile.user_id);
});
it('should return 404 if profile is not found in DB', async () => {
// Arrange
vi.mocked(userDb.findUserProfileById).mockResolvedValue(undefined);
// Act
const response = await supertest(app).get('/api/users/profile');
// Assert
expect(response.status).toBe(404);
expect(response.body.message).toBe('Profile not found for this user.');
});
@@ -141,14 +135,9 @@ describe('User Routes (/api/users)', () => {
describe('GET /watched-items', () => {
it('should return a list of watched items', async () => {
// Arrange
const mockItems = [createMockMasterGroceryItem({ master_grocery_item_id: 1, name: 'Milk' })];
vi.mocked(personalizationDb.getWatchedItems).mockResolvedValue(mockItems);
// Act
const response = await supertest(app).get('/api/users/watched-items');
// Assert
expect(response.status).toBe(200);
expect(response.body).toEqual(mockItems);
});
@@ -156,17 +145,12 @@ describe('User Routes (/api/users)', () => {
describe('POST /watched-items', () => {
it('should add an item to the watchlist and return the new item', async () => {
// Arrange
const newItem = { itemName: 'Organic Bananas', category: 'Produce' };
const mockAddedItem = createMockMasterGroceryItem({ master_grocery_item_id: 99, name: 'Organic Bananas', category_name: 'Produce' });
vi.mocked(personalizationDb.addWatchedItem).mockResolvedValue(mockAddedItem);
// Act
const response = await supertest(app)
.post('/api/users/watched-items')
.send(newItem);
// Assert
expect(response.status).toBe(201);
expect(response.body).toEqual(mockAddedItem);
});
@@ -174,13 +158,8 @@ describe('User Routes (/api/users)', () => {
describe('DELETE /watched-items/:masterItemId', () => {
it('should remove an item from the watchlist', async () => {
// Arrange
vi.mocked(personalizationDb.removeWatchedItem).mockResolvedValue(undefined);
// Act
const response = await supertest(app).delete(`/api/users/watched-items/99`);
// Assert
expect(response.status).toBe(204);
expect(personalizationDb.removeWatchedItem).toHaveBeenCalledWith(mockUserProfile.user_id, 99);
});
@@ -190,9 +169,7 @@ describe('User Routes (/api/users)', () => {
it('GET /shopping-lists should return all shopping lists for the user', async () => {
const mockLists = [createMockShoppingList({ shopping_list_id: 1, user_id: mockUserProfile.user_id })];
vi.mocked(shoppingDb.getShoppingLists).mockResolvedValue(mockLists);
const response = await supertest(app).get('/api/users/shopping-lists');
expect(response.status).toBe(200);
expect(response.body).toEqual(mockLists);
});
@@ -200,7 +177,6 @@ describe('User Routes (/api/users)', () => {
it('POST /shopping-lists should create a new list', async () => {
const mockNewList = createMockShoppingList({ shopping_list_id: 2, user_id: mockUserProfile.user_id, name: 'Party Supplies' });
vi.mocked(shoppingDb.createShoppingList).mockResolvedValue(mockNewList);
const response = await supertest(app)
.post('/api/users/shopping-lists')
.send({ name: 'Party Supplies' });
@@ -222,7 +198,6 @@ describe('User Routes (/api/users)', () => {
const itemData = { customItemName: 'Paper Towels' };
const mockAddedItem = createMockShoppingListItem({ shopping_list_item_id: 101, shopping_list_id: listId, ...itemData });
vi.mocked(shoppingDb.addShoppingListItem).mockResolvedValue(mockAddedItem);
const response = await supertest(app)
.post(`/api/users/shopping-lists/${listId}/items`)
.send(itemData);
@@ -236,7 +211,6 @@ describe('User Routes (/api/users)', () => {
const updates = { is_purchased: true, quantity: 2 };
const mockUpdatedItem = createMockShoppingListItem({ shopping_list_item_id: itemId, shopping_list_id: 1, ...updates });
vi.mocked(shoppingDb.updateShoppingListItem).mockResolvedValue(mockUpdatedItem);
const response = await supertest(app)
.put(`/api/users/shopping-lists/items/${itemId}`)
.send(updates);
@@ -257,7 +231,6 @@ describe('User Routes (/api/users)', () => {
const profileUpdates = { full_name: 'New Name' };
const updatedProfile = { ...mockUserProfile, ...profileUpdates };
vi.mocked(userDb.updateUserProfile).mockResolvedValue(updatedProfile);
const response = await supertest(app)
.put('/api/users/profile')
.send(profileUpdates);
@@ -270,7 +243,6 @@ describe('User Routes (/api/users)', () => {
const response = await supertest(app)
.put('/api/users/profile')
.send({});
expect(response.status).toBe(400);
expect(response.body.message).toBe('At least one field to update must be provided.');
});
@@ -280,7 +252,6 @@ describe('User Routes (/api/users)', () => {
it('should update the password successfully with a strong password', async () => {
vi.mocked(bcrypt.hash).mockResolvedValue('hashed-password' as never);
vi.mocked(userDb.updateUserPassword).mockResolvedValue(undefined);
const response = await supertest(app)
.put('/api/users/profile/password')
.send({ newPassword: 'a-Very-Strong-Password-456!' });
@@ -301,7 +272,6 @@ describe('User Routes (/api/users)', () => {
describe('DELETE /account', () => {
it('should delete the account with the correct password', async () => {
// Arrange
const userWithHash = {
...mockUserProfile.user,
password_hash: 'hashed-password',
@@ -311,13 +281,9 @@ describe('User Routes (/api/users)', () => {
vi.mocked(userDb.findUserWithPasswordHashById).mockResolvedValue(userWithHash);
vi.mocked(userDb.deleteUserById).mockResolvedValue(undefined);
vi.mocked(bcrypt.compare).mockResolvedValue(true as never);
// Act
const response = await supertest(app)
.delete('/api/users/account')
.send({ password: 'correct-password' });
// Assert
expect(response.status).toBe(200);
expect(response.body.message).toBe('Account deleted successfully.');
});
@@ -326,12 +292,11 @@ describe('User Routes (/api/users)', () => {
const userWithHash = {
...mockUserProfile.user,
password_hash: 'hashed-password',
failed_login_attempts: 0, // Add missing properties
failed_login_attempts: 0,
last_failed_login: null
};
vi.mocked(userDb.findUserWithPasswordHashById).mockResolvedValue(userWithHash);
vi.mocked(bcrypt.compare).mockResolvedValue(false as never);
const response = await supertest(app)
.delete('/api/users/account')
.send({ password: 'wrong-password' });
@@ -350,11 +315,9 @@ describe('User Routes (/api/users)', () => {
preferences: { ...mockUserProfile.preferences, ...preferencesUpdate }
};
vi.mocked(userDb.updateUserPreferences).mockResolvedValue(updatedProfile);
const response = await supertest(app)
.put('/api/users/profile/preferences')
.send(preferencesUpdate);
expect(response.status).toBe(200);
expect(response.body).toEqual(updatedProfile);
});
@@ -364,7 +327,6 @@ describe('User Routes (/api/users)', () => {
.put('/api/users/profile/preferences')
.set('Content-Type', 'application/json')
.send('"not-an-object"');
expect(response.status).toBe(400);
expect(response.body.message).toBe('Invalid preferences format. Body must be a JSON object.');
});
@@ -374,9 +336,7 @@ describe('User Routes (/api/users)', () => {
it('GET should return a list of restriction IDs', async () => {
const mockRestrictions = [{ dietary_restriction_id: 1, name: 'Gluten-Free', type: 'diet' as const }];
vi.mocked(personalizationDb.getUserDietaryRestrictions).mockResolvedValue(mockRestrictions);
const response = await supertest(app).get('/api/users/me/dietary-restrictions');
expect(response.status).toBe(200);
expect(response.body).toEqual(mockRestrictions);
});
@@ -384,11 +344,9 @@ describe('User Routes (/api/users)', () => {
it('PUT should successfully set the restrictions', async () => {
vi.mocked(personalizationDb.setUserDietaryRestrictions).mockResolvedValue(undefined);
const restrictionIds = [1, 3, 5];
const response = await supertest(app)
.put('/api/users/me/dietary-restrictions')
.send({ restrictionIds });
expect(response.status).toBe(204);
});
});
@@ -397,15 +355,12 @@ describe('User Routes (/api/users)', () => {
it('GET should return a list of appliance IDs', async () => {
const mockAppliances: Appliance[] = [{ appliance_id: 2, name: 'Air Fryer' }];
vi.mocked(personalizationDb.getUserAppliances).mockResolvedValue(mockAppliances);
const response = await supertest(app).get('/api/users/me/appliances');
expect(response.status).toBe(200);
expect(response.body).toEqual(mockAppliances);
});
it('PUT should successfully set the appliances', async () => {
// Pass an empty array to match the expected return type UserAppliance[]
vi.mocked(personalizationDb.setUserAppliances).mockResolvedValue([]);
const applianceIds = [2, 4, 6];
const response = await supertest(app).put('/api/users/me/appliances').send({ applianceIds });

View File

@@ -19,7 +19,7 @@ const { mockGenerateContent, mockReadFile, mockToBuffer, mockExtract, mockSharp
};
});
// 2. Mock @google/genai AND @google/generative-ai to cover both SDK versions
// 2. Mock the @google/genai SDK
const MockGoogleGenAIImplementation = class {
constructor(public config: { apiKey: string }) {}
get models() { return { generateContent: mockGenerateContent }; }
@@ -30,10 +30,6 @@ vi.mock('@google/genai', () => ({
GoogleGenAI: MockGoogleGenAIImplementation,
}));
vi.mock('@google/generative-ai', () => ({
GoogleGenerativeAI: MockGoogleGenAIImplementation,
}));
// 3. Mock fs/promises
vi.mock('fs/promises', () => ({
default: {

View File

@@ -1,8 +1,19 @@
import { describe, it, expect, vi, beforeEach, beforeAll } from 'vitest';
// FIX: Mock the local re-export, not the library directly.
// This is more stable and ensures the service under test gets the mock.
vi.mock('../lib/toast');
// FIX: Mock the local re-export with explicit spies
vi.mock('../lib/toast', () => {
const success = vi.fn();
const error = vi.fn();
return {
default: {
success,
error,
},
// Also export named versions if used
success,
error
};
});
describe('Notification Service', () => {
beforeAll(() => {

View File

@@ -108,24 +108,22 @@ vi.mock('pg', () => {
/**
* Mocks the Google Generative AI package.
*/
vi.mock('@google/generative-ai', () => {
vi.mock('@google/genai', () => {
const mockGenerativeModel = {
generateContent: vi.fn().mockResolvedValue({
response: {
text: () => 'Mocked AI response',
candidates: [{ content: { parts: [{ text: 'Mocked AI response' }] } }],
},
// The new SDK structure is slightly different
text: 'Mocked AI response',
candidates: [{ content: { parts: [{ text: 'Mocked AI response' }] } }],
}),
};
return {
// FIX: Use a standard function so it can be called with 'new'
GoogleGenerativeAI: vi.fn(function() {
// Mock the GoogleGenAI class constructor
GoogleGenAI: vi.fn(function() {
return {
getGenerativeModel: () => mockGenerativeModel,
models: mockGenerativeModel, // Also mock the `models` getter
};
}),
HarmCategory: {},
HarmBlockThreshold: {},
};
});