// src/routes/user.routes.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import supertest from 'supertest';
import express from 'express';
// Use * as bcrypt to match the implementation's import style and ensure mocks align.
import * as bcrypt from 'bcrypt';
import userRouter from './user.routes';
import { createMockUserProfile, createMockMasterGroceryItem, createMockShoppingList, createMockShoppingListItem, createMockRecipe, createMockNotification, createMockDietaryRestriction, createMockAppliance, createMockUserWithPasswordHash } from '../tests/utils/mockFactories';
import { Appliance, Notification, DietaryRestriction } from '../types';
import { ForeignKeyConstraintError, NotFoundError } from '../services/db/errors.db';
import { createTestApp } from '../tests/utils/createTestApp';

// 1. Mock the Service Layer directly.
// The user.routes.ts file imports from '.../db/index.db'. We need to mock that module.
vi.mock('../services/db/index.db', () => ({
  // Repository instances
  userRepo: {
    findUserProfileById: vi.fn(),
    updateUserProfile: vi.fn(),
    updateUserPassword: vi.fn(),
    findUserWithPasswordHashById: vi.fn(),
    deleteUserById: vi.fn(),
    updateUserPreferences: vi.fn(),
  },
  personalizationRepo: {
    getWatchedItems: vi.fn(),
    removeWatchedItem: vi.fn(),
    addWatchedItem: vi.fn(),
    getUserDietaryRestrictions: vi.fn(),
    setUserDietaryRestrictions: vi.fn(),
    getUserAppliances: vi.fn(),
    setUserAppliances: vi.fn(),
  },
  shoppingRepo: {
    getShoppingLists: vi.fn(),
    createShoppingList: vi.fn(),
    deleteShoppingList: vi.fn(),
    addShoppingListItem: vi.fn(),
    updateShoppingListItem: vi.fn(),
    removeShoppingListItem: vi.fn(),
    getShoppingListById: vi.fn(), // Added missing mock
  },
  recipeRepo: {
    deleteRecipe: vi.fn(),
    updateRecipe: vi.fn(),
  },
  addressRepo: {
    getAddressById: vi.fn(),
    upsertAddress: vi.fn(),
  },
  notificationRepo: {
    getNotificationsForUser: vi.fn(),
    markAllNotificationsAsRead: vi.fn(),
    markNotificationAsRead: vi.fn(),
  },
}));

// Mock userService
vi.mock('../services/userService', () => ({
  userService: {
    upsertUserAddress: vi.fn(),
  },
}));

// 2. Mock bcrypt.
// We return an object that satisfies both default and named imports to be safe.
vi.mock('bcrypt', () => {
  const hash = vi.fn();
  const compare = vi.fn();
  return {
    default: { hash, compare },
    hash,
    compare,
  };
});

// Mock the logger
vi.mock('../services/logger.server', () => ({
  logger: {
    info: vi.fn(),
    debug: vi.fn(),
    error: vi.fn(),
    warn: vi.fn(),
  },
}));

import { userService } from '../services/userService'; // Import for checking calls

// Mock Passport middleware
vi.mock('./passport.routes', () => ({
  default: {
    authenticate: vi.fn(() => (req: express.Request, res: express.Response, next: express.NextFunction) => {
      // If we are testing the unauthenticated state (no user injected), simulate 401.
      // If a user WAS injected by our test helper, proceed.
      if (!req.user) {
        return res.status(401).json({ message: 'Unauthorized' });
      }
      next();
    }),
  },
  // We also need to provide mocks for any other named exports from passport.routes.ts
  isAdmin: vi.fn((req: express.Request, res: express.Response, next: express.NextFunction) => next()),
  optionalAuth: vi.fn((req: express.Request, res: express.Response, next: express.NextFunction) => next()),
}));

// Import the mocked db module to control its functions in tests
import * as db from '../services/db/index.db';

// Define a reusable matcher for the logger object.
// This is more specific than `expect.anything()` and ensures a logger-like object is passed.
const expectLogger = expect.objectContaining({
  info: expect.any(Function),
  error: expect.any(Function),
});
describe('User Routes (/api/users)', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });
  const basePath = '/api/users';

  describe('when user is not authenticated', () => {
    it('GET /profile should return 401', async () => {
      const app = createTestApp({ router: userRouter, basePath }); // No user injected
      const response = await supertest(app).get('/api/users/profile');
      expect(response.status).toBe(401);
    });
  });

  describe('when user is authenticated', () => {
    const mockUserProfile = createMockUserProfile({ user_id: 'user-123' });
    const app = createTestApp({ router: userRouter, basePath, authenticatedUser: mockUserProfile });

    // Add a basic error handler to capture errors passed to next(err) and return JSON.
    // This prevents unhandled error crashes in tests and ensures we get the 500 response we expect.
    app.use((err: any, req: any, res: any, next: any) => {
      res.status(err.status || 500).json({ message: err.message, errors: err.errors });
    });

    beforeEach(() => {
      // All tests in this block will use the authenticated app
    });
    describe('GET /profile', () => {
      it('should return the full user profile', async () => {
        vi.mocked(db.userRepo.findUserProfileById).mockResolvedValue(mockUserProfile);
        const response = await supertest(app).get('/api/users/profile');
        expect(response.status).toBe(200);
        expect(response.body).toEqual(mockUserProfile);
        expect(db.userRepo.findUserProfileById).toHaveBeenCalledWith(mockUserProfile.user_id, expectLogger);
      });

      it('should return 404 if profile is not found in DB', async () => {
        vi.mocked(db.userRepo.findUserProfileById).mockRejectedValue(new NotFoundError('Profile not found for this user.'));
        const response = await supertest(app).get('/api/users/profile');
        expect(response.status).toBe(404);
        expect(response.body.message).toContain('Profile not found');
      });
    });

    describe('GET /watched-items', () => {
      it('should return a list of watched items', async () => {
        const mockItems = [createMockMasterGroceryItem({ master_grocery_item_id: 1, name: 'Milk' })];
        vi.mocked(db.personalizationRepo.getWatchedItems).mockResolvedValue(mockItems);
        const response = await supertest(app).get('/api/users/watched-items');
        expect(response.status).toBe(200);
        expect(response.body).toEqual(mockItems);
      });
    });

    describe('POST /watched-items', () => {
      it('should add an item to the watchlist and return the new item', async () => {
        const newItem = { itemName: 'Organic Bananas', category: 'Produce' };
        const mockAddedItem = createMockMasterGroceryItem({ master_grocery_item_id: 99, name: 'Organic Bananas', category_name: 'Produce' });
        vi.mocked(db.personalizationRepo.addWatchedItem).mockResolvedValue(mockAddedItem);
        const response = await supertest(app)
          .post('/api/users/watched-items')
          .send(newItem);
        expect(response.status).toBe(201);
        expect(response.body).toEqual(mockAddedItem);
      });
    });

    describe('POST /watched-items (Validation)', () => {
      it('should return 400 if itemName is missing', async () => {
        const response = await supertest(app)
          .post('/api/users/watched-items')
          .send({ category: 'Produce' });
        expect(response.status).toBe(400);
        // Check the 'errors' array for the specific validation message.
        expect(response.body.errors[0].message).toBe("Field 'itemName' is required.");
      });

      it('should return 400 if category is missing', async () => {
        const response = await supertest(app)
          .post('/api/users/watched-items')
          .send({ itemName: 'Apples' });
        expect(response.status).toBe(400);
        // Check the 'errors' array for the specific validation message.
        expect(response.body.errors[0].message).toBe("Field 'category' is required.");
      });
    });

    it('should return 400 if a foreign key constraint fails', async () => {
      vi.mocked(db.personalizationRepo.addWatchedItem).mockRejectedValue(new ForeignKeyConstraintError('Category not found'));
      const response = await supertest(app)
        .post('/api/users/watched-items')
        .send({ itemName: 'Test', category: 'Invalid' });
      expect(response.status).toBe(400);
    });

    describe('DELETE /watched-items/:masterItemId', () => {
      it('should remove an item from the watchlist', async () => {
        vi.mocked(db.personalizationRepo.removeWatchedItem).mockResolvedValue(undefined);
        const response = await supertest(app).delete(`/api/users/watched-items/99`);
        expect(response.status).toBe(204);
        expect(db.personalizationRepo.removeWatchedItem).toHaveBeenCalledWith(mockUserProfile.user_id, 99, expectLogger);
      });
    });

    describe('Shopping List Routes', () => {
      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(db.shoppingRepo.getShoppingLists).mockResolvedValue(mockLists);
        const response = await supertest(app).get('/api/users/shopping-lists');
        expect(response.status).toBe(200);
        expect(response.body).toEqual(mockLists);
      });

      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(db.shoppingRepo.createShoppingList).mockResolvedValue(mockNewList);
        const response = await supertest(app)
          .post('/api/users/shopping-lists')
          .send({ name: 'Party Supplies' });

        expect(response.status).toBe(201);
        expect(response.body).toEqual(mockNewList);
      });

      it('should return 400 if name is missing', async () => {
        const response = await supertest(app).post('/api/users/shopping-lists').send({});
        expect(response.status).toBe(400);
        // Check the 'errors' array for the specific validation message.
        expect(response.body.errors[0].message).toBe("Field 'name' is required.");
      });

      it('should return 400 on foreign key constraint error', async () => {
        vi.mocked(db.shoppingRepo.createShoppingList).mockRejectedValue(new ForeignKeyConstraintError('User not found'));
        const response = await supertest(app).post('/api/users/shopping-lists').send({ name: 'Failing List' });
        expect(response.status).toBe(400);
        expect(response.body.message).toBe('User not found');
      });

      it('should return 400 for an invalid listId on DELETE', async () => {
        const response = await supertest(app).delete('/api/users/shopping-lists/abc');
        expect(response.status).toBe(400);
        // Check the 'errors' array for the specific validation message.
        expect(response.body.errors[0].message).toContain("received NaN");
      });

      describe('DELETE /shopping-lists/:listId', () => {
        it('should delete a list', async () => {
          vi.mocked(db.shoppingRepo.deleteShoppingList).mockResolvedValue(undefined);
          const response = await supertest(app).delete('/api/users/shopping-lists/1');
          expect(response.status).toBe(204);
        });

        it('should return 404 if list to delete is not found', async () => {
          vi.mocked(db.shoppingRepo.deleteShoppingList).mockRejectedValue(new Error('not found'));
          vi.mocked(db.shoppingRepo.deleteShoppingList).mockRejectedValue(new NotFoundError('not found'));
          const response = await supertest(app).delete('/api/users/shopping-lists/999');
          expect(response.status).toBe(404);
        });

      it('should return 400 for an invalid listId', async () => {
        const response = await supertest(app).delete('/api/users/shopping-lists/abc');
        expect(response.status).toBe(400);
        expect(response.body.errors[0].message).toContain("received NaN");
      });
      });
    });
    describe('Shopping List Item Routes', () => { it('POST /shopping-lists/:listId/items should add an item to a list', async () => {
        const listId = 1;
        const itemData = { customItemName: 'Paper Towels' };
        const mockAddedItem = createMockShoppingListItem({ shopping_list_item_id: 101, shopping_list_id: listId, ...itemData });
        vi.mocked(db.shoppingRepo.addShoppingListItem).mockResolvedValue(mockAddedItem);
        const response = await supertest(app)
          .post(`/api/users/shopping-lists/${listId}/items`)
          .send(itemData);

        expect(response.status).toBe(201);
        expect(response.body).toEqual(mockAddedItem);
      });

      it('should return 400 on foreign key error when adding an item', async () => {
        vi.mocked(db.shoppingRepo.addShoppingListItem).mockRejectedValue(new ForeignKeyConstraintError('List not found'));
        const response = await supertest(app).post('/api/users/shopping-lists/999/items').send({ customItemName: 'Test' });
        expect(response.status).toBe(400);
      });

      it('PUT /shopping-lists/items/:itemId should update an item', async () => {
        const itemId = 101;
        const updates = { is_purchased: true, quantity: 2 };
        const mockUpdatedItem = createMockShoppingListItem({ shopping_list_item_id: itemId, shopping_list_id: 1, ...updates });
        vi.mocked(db.shoppingRepo.updateShoppingListItem).mockResolvedValue(mockUpdatedItem);
        const response = await supertest(app)
          .put(`/api/users/shopping-lists/items/${itemId}`)
          .send(updates);

        expect(response.status).toBe(200);
        expect(response.body).toEqual(mockUpdatedItem);
      });

      it('should return 404 if item to update is not found', async () => {
        vi.mocked(db.shoppingRepo.updateShoppingListItem).mockRejectedValue(new NotFoundError('not found'));
        const response = await supertest(app).put('/api/users/shopping-lists/items/999').send({ is_purchased: true });
        expect(response.status).toBe(404);
      });

      describe('DELETE /shopping-lists/items/:itemId', () => {
        it('should delete an item', async () => {
          vi.mocked(db.shoppingRepo.removeShoppingListItem).mockResolvedValue(undefined);
          const response = await supertest(app).delete('/api/users/shopping-lists/items/101');
          expect(response.status).toBe(204);
        });

        it('should return 404 if item to delete is not found', async () => {
          vi.mocked(db.shoppingRepo.removeShoppingListItem).mockRejectedValue(new NotFoundError('not found'));
          const response = await supertest(app).delete('/api/users/shopping-lists/items/999');
          expect(response.status).toBe(404);
        });
      });
    });

    describe('PUT /profile', () => {
      it('should update the user profile successfully', async () => {
        const profileUpdates = { full_name: 'New Name' };
        const updatedProfile = { ...mockUserProfile, ...profileUpdates };
        vi.mocked(db.userRepo.updateUserProfile).mockResolvedValue(updatedProfile);
        const response = await supertest(app)
          .put('/api/users/profile')
          .send(profileUpdates);

        expect(response.status).toBe(200);
        expect(response.body).toEqual(updatedProfile);
      });

      it('should return 400 if the body is empty', async () => {
        const response = await supertest(app)
          .put('/api/users/profile')
          .send({});
        expect(response.status).toBe(400);
        expect(response.body.errors[0].message).toBe('At least one field to update must be provided.');
      });
    });

    describe('PUT /profile/password', () => {
      it('should update the password successfully with a strong password', async () => {
        vi.mocked(bcrypt.hash).mockResolvedValue('hashed-password' as never);
        vi.mocked(db.userRepo.updateUserPassword).mockResolvedValue(undefined);
        const response = await supertest(app)
          .put('/api/users/profile/password')
          .send({ newPassword: 'a-Very-Strong-Password-456!' });

        expect(response.status).toBe(200);
        expect(response.body.message).toBe('Password updated successfully.');
      });

      it('should return 400 for a weak password', async () => {
        // Use a password long enough to pass .min(8) but weak enough to fail strength check
        const response = await supertest(app)
          .put('/api/users/profile/password')
          .send({ newPassword: 'password123' });

        expect(response.status).toBe(400);
        expect(response.body.errors[0].message).toContain('Password is too weak.');
      });
    });

    describe('DELETE /account', () => {
      it('should delete the account with the correct password', async () => {
        const userWithHash = createMockUserWithPasswordHash({ ...mockUserProfile.user, password_hash: 'hashed-password' });
        vi.mocked(db.userRepo.findUserWithPasswordHashById).mockResolvedValue(userWithHash);
        vi.mocked(db.userRepo.deleteUserById).mockResolvedValue(undefined);
        vi.mocked(bcrypt.compare).mockResolvedValue(true as never);
        const response = await supertest(app)
          .delete('/api/users/account')
          .send({ password: 'correct-password' });
        expect(response.status).toBe(200);
        expect(response.body.message).toBe('Account deleted successfully.');
      });

      it('should return 403 for an incorrect password', async () => {
        const userWithHash = createMockUserWithPasswordHash({ ...mockUserProfile.user, password_hash: 'hashed-password' });
        vi.mocked(db.userRepo.findUserWithPasswordHashById).mockResolvedValue(userWithHash);
        vi.mocked(bcrypt.compare).mockResolvedValue(false as never);
        const response = await supertest(app)
          .delete('/api/users/account')
          .send({ password: 'wrong-password' });

        expect(response.status).toBe(403);
        expect(response.body.message).toBe('Incorrect password.');
      });

      it('should return 404 if the user to delete is not found', async () => {
        vi.mocked(db.userRepo.findUserWithPasswordHashById).mockRejectedValue(new NotFoundError('User not found or password not set.'));
        const response = await supertest(app)
          .delete('/api/users/account')
          .send({ password: 'any-password' });
        expect(response.status).toBe(404);
        expect(response.body.message).toBe('User not found or password not set.');
      });
    });

    describe('User Preferences and Personalization', () => {
      describe('PUT /profile/preferences', () => {
        it('should update user preferences successfully', async () => {
          const preferencesUpdate = { darkMode: true, unitSystem: 'metric' as const };
          const updatedProfile = { 
            ...mockUserProfile, 
            preferences: { ...mockUserProfile.preferences, ...preferencesUpdate } 
          };
          vi.mocked(db.userRepo.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);
        });

        it('should return 400 if the request body is not a valid object', async () => {
          const response = await supertest(app)
            .put('/api/users/profile/preferences')
            .set('Content-Type', 'application/json')
            .send('"not-an-object"');
          expect(response.status).toBe(400);
          // Zod or Body Parser error
          expect(response.body).toBeDefined(); 
        });
      });

      describe('GET and PUT /users/me/dietary-restrictions', () => {
        it('GET should return a list of restriction IDs', async () => {
          const mockRestrictions: DietaryRestriction[] = [createMockDietaryRestriction({ name: 'Gluten-Free' })];
          vi.mocked(db.personalizationRepo.getUserDietaryRestrictions).mockResolvedValue(mockRestrictions);
          const response = await supertest(app).get('/api/users/me/dietary-restrictions');
          expect(response.status).toBe(200);
          expect(response.body).toEqual(mockRestrictions);
        });

      it('should return 400 for an invalid masterItemId', async () => {
        const response = await supertest(app).delete('/api/users/watched-items/abc');
        expect(response.status).toBe(400);
        // Check the 'errors' array for the specific validation message.
        expect(response.body.errors[0].message).toContain("received NaN");
      });

        it('PUT should successfully set the restrictions', async () => {
        vi.mocked(db.personalizationRepo.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);
        });

        it('PUT should return 400 on foreign key constraint error', async () => {
          vi.mocked(db.personalizationRepo.setUserDietaryRestrictions).mockRejectedValue(new ForeignKeyConstraintError('Invalid restriction ID'));
          const response = await supertest(app)
            .put('/api/users/me/dietary-restrictions')
            .send({ restrictionIds: [999] }); // Invalid ID
          expect(response.status).toBe(400);
        });
      });

      describe('GET and PUT /users/me/appliances', () => {
        it('GET should return a list of appliance IDs', async () => {
          const mockAppliances: Appliance[] = [createMockAppliance({ name: 'Air Fryer' })];
          vi.mocked(db.personalizationRepo.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 () => {
        vi.mocked(db.personalizationRepo.setUserAppliances).mockResolvedValue([]);
          const applianceIds = [2, 4, 6];
          const response = await supertest(app).put('/api/users/me/appliances').send({ applianceIds });
          expect(response.status).toBe(204);
        });

        it('PUT should return 400 on foreign key constraint error', async () => {
          vi.mocked(db.personalizationRepo.setUserAppliances).mockRejectedValue(new ForeignKeyConstraintError('Invalid appliance ID'));
          const response = await supertest(app)
            .put('/api/users/me/appliances')
            .send({ applianceIds: [999] }); // Invalid ID
          expect(response.status).toBe(400);
          expect(response.body.message).toBe('Invalid appliance ID');
        });
      });
    });

    describe('Notification Routes', () => {
      it('GET /notifications should return notifications for the user', async () => {
        const mockNotifications: Notification[] = [createMockNotification({ user_id: 'user-123', content: 'Test' })];
        vi.mocked(db.notificationRepo.getNotificationsForUser).mockResolvedValue(mockNotifications);

        const response = await supertest(app).get('/api/users/notifications?limit=10&offset=0');

        expect(response.status).toBe(200);
        expect(response.body).toEqual(mockNotifications);
        expect(db.notificationRepo.getNotificationsForUser).toHaveBeenCalledWith('user-123', 10, 0, expectLogger);
      });

      it('POST /notifications/mark-all-read should return 204', async () => {
        vi.mocked(db.notificationRepo.markAllNotificationsAsRead).mockResolvedValue(undefined);
        const response = await supertest(app).post('/api/users/notifications/mark-all-read');
        expect(response.status).toBe(204);
        expect(db.notificationRepo.markAllNotificationsAsRead).toHaveBeenCalledWith('user-123', expectLogger);
      });

      it('POST /notifications/:notificationId/mark-read should return 204', async () => {
        // Fix: Return a mock notification object to match the function's signature.
        vi.mocked(db.notificationRepo.markNotificationAsRead).mockResolvedValue(createMockNotification({ notification_id: 1, user_id: 'user-123' }));
        const response = await supertest(app).post('/api/users/notifications/1/mark-read');
        expect(response.status).toBe(204);
        expect(db.notificationRepo.markNotificationAsRead).toHaveBeenCalledWith(1, 'user-123', expectLogger);
      });

      it('should return 400 for an invalid notificationId', async () => {
        const response = await supertest(app).post('/api/users/notifications/abc/mark-read').send({});
        expect(response.status).toBe(400);
        expect(response.body.errors[0].message).toContain("received NaN");
      });
    });

    describe('GET /addresses/:addressId', () => {
      it('should return 400 for a non-numeric address ID', async () => {
        const response = await supertest(app).get('/api/users/addresses/abc'); // This was a duplicate, fixed.
        expect(response.status).toBe(400);
      });
    });

    describe('Address Routes', () => {
      it('GET /addresses/:addressId should return 403 if address does not belong to user', async () => {
        const appWithDifferentUser = createTestApp({ router: userRouter, basePath, authenticatedUser: { ...mockUserProfile, address_id: 999 } });
        const response = await supertest(appWithDifferentUser).get('/api/users/addresses/1');
        expect(response.status).toBe(403);
      });

      it('GET /addresses/:addressId should return 404 if address not found', async () => {
        const appWithUser = createTestApp({ router: userRouter, basePath, authenticatedUser: { ...mockUserProfile, address_id: 1 } });
        vi.mocked(db.addressRepo.getAddressById).mockRejectedValue(new NotFoundError('Address not found.'));
        const response = await supertest(appWithUser).get('/api/users/addresses/1');
        expect(response.status).toBe(404);
        expect(response.body.message).toBe('Address not found.');
      });

      it('PUT /profile/address should call upsertAddress and updateUserProfile if needed', async () => {
        const appWithUser = createTestApp({ router: userRouter, basePath, authenticatedUser: { ...mockUserProfile, address_id: null } }); // User has no address yet
        const addressData = { address_line_1: '123 New St' };
        vi.mocked(db.addressRepo.upsertAddress).mockResolvedValue(5); // New address ID is 5
        vi.mocked(db.userRepo.updateUserProfile).mockResolvedValue({ ...mockUserProfile, address_id: 5 });

        const response = await supertest(appWithUser)
          .put('/api/users/profile/address')
          .send(addressData);

        expect(response.status).toBe(200);
        expect(userService.upsertUserAddress).toHaveBeenCalledWith(expect.anything(), addressData, expectLogger);
      });

    });

    describe('POST /profile/avatar', () => {
      it('should upload an avatar and update the user profile', async () => {
        const mockUpdatedProfile = { ...mockUserProfile, avatar_url: '/uploads/avatars/new-avatar.png' };
        vi.mocked(db.userRepo.updateUserProfile).mockResolvedValue(mockUpdatedProfile);

        // Create a dummy file path for supertest to attach
        const dummyImagePath = 'test-avatar.png';
        
        const response = await supertest(app)
          .post('/api/users/profile/avatar')
          .attach('avatar', Buffer.from('dummy-image-content'), dummyImagePath);

        expect(response.status).toBe(200);
        expect(response.body.avatar_url).toContain('/uploads/avatars/');
        expect(db.userRepo.updateUserProfile).toHaveBeenCalledWith(mockUserProfile.user_id, { avatar_url: expect.any(String) }, expectLogger);
      });

      it('should return 400 if a non-image file is uploaded', async () => {
        const dummyTextPath = 'document.txt';

        const response = await supertest(app)
          .post('/api/users/profile/avatar')
          .attach('avatar', Buffer.from('this is not an image'), dummyTextPath);

        expect(response.status).toBe(400);
        expect(response.body.message).toBe('Only image files are allowed!');
      });

      it('should return 400 if no file is uploaded', async () => {
        const response = await supertest(app)
          .post('/api/users/profile/avatar'); // No .attach() call

        expect(response.status).toBe(400);
        expect(response.body.message).toBe('No avatar file uploaded.');
      });

      it('should return 400 for a non-numeric address ID', async () => {
        const response = await supertest(app).get('/api/users/addresses/abc');
        expect(response.status).toBe(400);
        expect(response.body.errors[0].message).toContain("received NaN");
      });
    });

    describe('Recipe Routes', () => {
      it('DELETE /recipes/:recipeId should delete a user\'s own recipe', async () => {
        vi.mocked(db.recipeRepo.deleteRecipe).mockResolvedValue(undefined);
        const response = await supertest(app).delete('/api/users/recipes/1');
        expect(response.status).toBe(204);
        expect(db.recipeRepo.deleteRecipe).toHaveBeenCalledWith(1, mockUserProfile.user_id, false, expectLogger);
      });

      it('PUT /recipes/:recipeId should update a user\'s own recipe', async () => {
        const updates = { description: 'A new delicious description.' };
        const mockUpdatedRecipe = { ...createMockRecipe({ recipe_id: 1 }), ...updates };
        vi.mocked(db.recipeRepo.updateRecipe).mockResolvedValue(mockUpdatedRecipe);

        const response = await supertest(app)
          .put('/api/users/recipes/1')
          .send(updates);

        expect(response.status).toBe(200);
        expect(response.body).toEqual(mockUpdatedRecipe);
        expect(db.recipeRepo.updateRecipe).toHaveBeenCalledWith(1, mockUserProfile.user_id, updates, expectLogger);
      });

      it('PUT /recipes/:recipeId should return 404 if recipe not found', async () => {
        vi.mocked(db.recipeRepo.updateRecipe).mockRejectedValue(new NotFoundError('not found'));
        const response = await supertest(app).put('/api/users/recipes/999').send({ name: 'New Name' });
        expect(response.status).toBe(404);
      });

      it('GET /shopping-lists/:listId should return 404 if list is not found', async () => {
        vi.mocked(db.shoppingRepo.getShoppingListById).mockRejectedValue(new NotFoundError('Shopping list not found'));
        const response = await supertest(app).get('/api/users/shopping-lists/999');
        expect(response.status).toBe(404);
        expect(response.body.message).toBe('Shopping list not found');
      });
    }); // End of Recipe Routes
  });
});