lootsa tests fixes
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 4m31s
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 4m31s
This commit is contained in:
@@ -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!');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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: {},
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user