Add user database service and unit tests
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 4m28s
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 4m28s
- Implement user database service with functions for user management (create, find, update, delete). - Add comprehensive unit tests for user database service using Vitest. - Mock database interactions to ensure isolated testing. - Create setup files for unit tests to handle database connections and global mocks. - Introduce error handling for unique constraints and foreign key violations. - Enhance logging for better traceability during database operations.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// src/services/db/address.ts
|
||||
import { getPool } from './connection';
|
||||
// src/services/db/address.db.ts
|
||||
import { getPool } from './connection.db';
|
||||
import { logger } from '../logger.server';
|
||||
import { Address } from '../../types';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/services/db/admin.test.ts
|
||||
// src/services/db/admin.db.test.ts
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import {
|
||||
getSuggestedCorrections,
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
getUnmatchedFlyerItems,
|
||||
updateRecipeStatus,
|
||||
updateReceiptStatus,
|
||||
} from './admin';
|
||||
import { getPool } from './connection';
|
||||
} from './admin.db';
|
||||
import { getPool } from './connection.db';
|
||||
import type { SuggestedCorrection } from '../../types';
|
||||
|
||||
// Define test-local mock functions. These will be used to control the mock's behavior.
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/services/db/admin.ts
|
||||
import { getPool } from './connection';
|
||||
import { ForeignKeyConstraintError } from './errors';
|
||||
// src/services/db/admin.db.ts
|
||||
import { getPool } from './connection.db';
|
||||
import { ForeignKeyConstraintError } from './errors.db';
|
||||
import { logger } from '../logger';
|
||||
import { SuggestedCorrection, MostFrequentSaleItem, Recipe, RecipeComment, UnmatchedFlyerItem, ActivityLogItem, Receipt, User } from '../../types';
|
||||
/**
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/services/db/budget.test.ts
|
||||
// src/services/db/budget.db.test.ts
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import {
|
||||
getBudgetsForUser,
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
updateBudget,
|
||||
deleteBudget,
|
||||
getSpendingByCategory,
|
||||
} from './budget';
|
||||
} from './budget.db';
|
||||
import { Pool } from 'pg';
|
||||
|
||||
const mockQuery = vi.fn();
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/services/db/budget.ts
|
||||
import { getPool } from './connection';
|
||||
import { ForeignKeyConstraintError } from './errors';
|
||||
// src/services/db/budget.db.ts
|
||||
import { getPool } from './connection.db';
|
||||
import { ForeignKeyConstraintError } from './errors.db';
|
||||
import { logger } from '../logger';
|
||||
import { Budget, SpendingByCategory } from '../../types';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/services/db/connection.test.ts
|
||||
// src/services/db/connection.db.test.ts
|
||||
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
||||
|
||||
// Mock the logger
|
||||
@@ -12,37 +12,37 @@ vi.mock('../logger', () => ({
|
||||
describe('DB Connection Service', () => {
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
// We reset modules to ensure 'connection.ts' re-evaluates and calls the (mocked) Pool constructor again
|
||||
// We reset modules to ensure 'connection.db.ts' re-evaluates and calls the (mocked) Pool constructor again
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
// --- DIAGNOSTIC TEST ---
|
||||
it('DEBUG: Verify pg import and Pool constructor', async () => {
|
||||
const pg = await import('pg');
|
||||
console.log('[DEBUG] connection.test.ts: keys(pg):', Object.keys(pg));
|
||||
console.log('[DEBUG] connection.test.ts: typeof pg.Pool:', typeof pg.Pool);
|
||||
console.log('[DEBUG] connection.db.test.ts: keys(pg):', Object.keys(pg));
|
||||
console.log('[DEBUG] connection.db.test.ts: typeof pg.Pool:', typeof pg.Pool);
|
||||
|
||||
if (typeof pg.Pool === 'function') {
|
||||
try {
|
||||
const instance = new pg.Pool();
|
||||
console.log('[DEBUG] connection.test.ts: new pg.Pool() SUCCESS. Instance:', Object.keys(instance));
|
||||
console.log('[DEBUG] connection.db.test.ts: new pg.Pool() SUCCESS. Instance:', Object.keys(instance));
|
||||
} catch (e) {
|
||||
console.error('[DEBUG] connection.test.ts: new pg.Pool() FAILED:', e);
|
||||
console.error('[DEBUG] connection.db.test.ts: new pg.Pool() FAILED:', e);
|
||||
}
|
||||
} else {
|
||||
console.error('[DEBUG] connection.test.ts: pg.Pool is NOT a function!');
|
||||
console.error('[DEBUG] connection.db.test.ts: pg.Pool is NOT a function!');
|
||||
}
|
||||
});
|
||||
|
||||
describe('getPool', () => {
|
||||
it('should create a new pool instance on the first call', async () => {
|
||||
// Re-import to ensure fresh state after resetModules
|
||||
const { getPool } = await import('./connection');
|
||||
const { getPool } = await import('./connection.db');
|
||||
const { Pool } = await import('pg'); // Get the current mock
|
||||
|
||||
const pool = getPool();
|
||||
|
||||
console.log('[DEBUG] connection.test.ts: getPool() returned:', pool ? 'Object' : pool);
|
||||
console.log('[DEBUG] connection.db.test.ts: getPool() returned:', pool ? 'Object' : pool);
|
||||
|
||||
// Verify Pool constructor was called
|
||||
expect(Pool).toHaveBeenCalledTimes(1);
|
||||
@@ -53,7 +53,7 @@ describe('DB Connection Service', () => {
|
||||
});
|
||||
|
||||
it('should return the same pool instance on subsequent calls', async () => {
|
||||
const { getPool } = await import('./connection');
|
||||
const { getPool } = await import('./connection.db');
|
||||
const { Pool } = await import('pg');
|
||||
|
||||
const pool1 = getPool();
|
||||
@@ -66,7 +66,7 @@ describe('DB Connection Service', () => {
|
||||
|
||||
describe('checkTablesExist', () => {
|
||||
it('should return an empty array if all tables exist', async () => {
|
||||
const { getPool, checkTablesExist } = await import('./connection');
|
||||
const { getPool, checkTablesExist } = await import('./connection.db');
|
||||
const pool = getPool();
|
||||
|
||||
// Mock the query response on the *instance* returned by getPool
|
||||
@@ -81,7 +81,7 @@ describe('DB Connection Service', () => {
|
||||
});
|
||||
|
||||
it('should return an array of missing tables', async () => {
|
||||
const { getPool, checkTablesExist } = await import('./connection');
|
||||
const { getPool, checkTablesExist } = await import('./connection.db');
|
||||
const pool = getPool();
|
||||
|
||||
(pool.query as Mock).mockResolvedValue({ rows: [{ table_name: 'users' }] });
|
||||
@@ -95,7 +95,7 @@ describe('DB Connection Service', () => {
|
||||
|
||||
describe('getPoolStatus', () => {
|
||||
it('should return the status counts from the pool instance', async () => {
|
||||
const { getPoolStatus } = await import('./connection');
|
||||
const { getPoolStatus } = await import('./connection.db');
|
||||
const status = getPoolStatus();
|
||||
|
||||
// These values match the default mock implementation in unit-setup/mock-db
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/services/db/connection.ts
|
||||
// src/services/db/connection.db.ts
|
||||
import { Pool, PoolConfig } from 'pg';
|
||||
import { logger } from '../logger';
|
||||
|
||||
@@ -31,8 +31,8 @@ const createPool = (): Pool => {
|
||||
};
|
||||
|
||||
// --- ADD DEBUG LOGGING HERE ---
|
||||
console.log(`[DEBUG] connection.ts: createPool called. poolId=${poolId}`);
|
||||
console.log(`[DEBUG] connection.ts: typeof Pool is "${typeof Pool}"`);
|
||||
console.log(`[DEBUG] connection.db.ts: createPool called. poolId=${poolId}`);
|
||||
console.log(`[DEBUG] connection.db.ts: typeof Pool is "${typeof Pool}"`);
|
||||
if (typeof Pool !== 'function') {
|
||||
console.error('[DEBUG] CRITICAL: Pool is NOT a function/class!', Pool);
|
||||
}
|
||||
@@ -43,12 +43,12 @@ const createPool = (): Pool => {
|
||||
newPool.poolId = poolId;
|
||||
|
||||
// --- ADDED SUCCESS LOG ---
|
||||
console.log('[DEBUG] connection.ts: Successfully instantiated Pool.');
|
||||
console.log('[DEBUG] connection.db.ts: Successfully instantiated Pool.');
|
||||
// -------------------------
|
||||
|
||||
return newPool;
|
||||
} catch (error) {
|
||||
console.error('[DEBUG] connection.ts: "new Pool()" threw error:', error);
|
||||
console.error('[DEBUG] connection.db.ts: "new Pool()" threw error:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/services/db/errors.ts
|
||||
// src/services/db/errors.db.ts
|
||||
|
||||
/**
|
||||
* Base class for custom database errors to ensure they have a status property.
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/services/db/flyer.test.ts
|
||||
// src/services/db/flyer.db.test.ts
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import {
|
||||
getFlyers,
|
||||
@@ -13,9 +13,9 @@ import {
|
||||
updateStoreLogo,
|
||||
trackFlyerItemInteraction,
|
||||
getHistoricalPriceDataForItems,
|
||||
} from './flyer';
|
||||
} from './flyer.db';
|
||||
import type { Flyer, FlyerItem } from '../../types';
|
||||
import { getPool } from './connection';
|
||||
import { getPool } from './connection.db';
|
||||
import { Pool } from 'pg';
|
||||
|
||||
const mockQuery = vi.fn();
|
||||
@@ -36,7 +36,7 @@ describe('Flyer DB Service', () => {
|
||||
beforeEach(() => {
|
||||
// FIX: Use mockImplementation with a standard function to support 'new Pool()'
|
||||
vi.mocked(Pool).mockImplementation(function() {
|
||||
console.log('[DEBUG] flyer.test.ts: Local Pool mock instantiated via "new"');
|
||||
console.log('[DEBUG] flyer.db.test.ts: Local Pool mock instantiated via "new"');
|
||||
return {
|
||||
query: mockQuery,
|
||||
connect: mockConnect,
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/services/db/flyer.ts
|
||||
import { getPool } from './connection';
|
||||
import { UniqueConstraintError } from './errors';
|
||||
// src/services/db/flyer.db.ts
|
||||
import { getPool } from './connection.db';
|
||||
import { UniqueConstraintError } from './errors.db';
|
||||
import { logger } from '../logger.server';
|
||||
import { geocodeAddress } from '../geocodingService.server';
|
||||
import { Flyer, Brand, MasterGroceryItem, FlyerItem, Address } from '../../types';
|
||||
@@ -1,13 +1,13 @@
|
||||
// src/services/db/gamification.test.ts
|
||||
// src/services/db/gamification.db.test.ts
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import {
|
||||
getAllAchievements,
|
||||
getUserAchievements,
|
||||
awardAchievement,
|
||||
getLeaderboard,
|
||||
} from './gamification';
|
||||
} from './gamification.db';
|
||||
import type { Achievement, UserAchievement, LeaderboardUser } from '../../types';
|
||||
import { getPool } from './connection';
|
||||
import { getPool } from './connection.db';
|
||||
|
||||
// Mock the getPool function to return a mocked pool object.
|
||||
const mockQuery = vi.fn();
|
||||
@@ -32,7 +32,7 @@ vi.mock('../logger', () => ({
|
||||
describe('Gamification DB Service', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
console.log('[gamification.test.ts] Mocks cleared');
|
||||
console.log('[gamification.db.test.ts] Mocks cleared');
|
||||
});
|
||||
|
||||
describe('getAllAchievements', () => {
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/services/db/gamification.ts
|
||||
import { getPool } from './connection';
|
||||
import { ForeignKeyConstraintError } from './errors';
|
||||
// src/services/db/gamification.db.ts
|
||||
import { getPool } from './connection.db';
|
||||
import { ForeignKeyConstraintError } from './errors.db';
|
||||
import { logger } from '../logger';
|
||||
import { Achievement, UserAchievement, LeaderboardUser } from '../../types';
|
||||
|
||||
13
src/services/db/index.db.ts
Normal file
13
src/services/db/index.db.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// src/services/db/index.db.ts
|
||||
export * from './connection.db';
|
||||
export * from './errors.db';
|
||||
export * from './user.db';
|
||||
export * from './flyer.db';
|
||||
export * from './shopping.db';
|
||||
export * from './personalization.db';
|
||||
export * from './recipe.db';
|
||||
export * from './admin.db';
|
||||
export * from './notification.db';
|
||||
export * from './gamification.db';
|
||||
export * from './budget.db';
|
||||
export * from './address.db'; // Add the new address service exports
|
||||
@@ -1,13 +0,0 @@
|
||||
// src/services/db/index.ts
|
||||
export * from './connection';
|
||||
export * from './errors';
|
||||
export * from './user';
|
||||
export * from './flyer';
|
||||
export * from './shopping';
|
||||
export * from './personalization';
|
||||
export * from './recipe';
|
||||
export * from './admin';
|
||||
export * from './notification';
|
||||
export * from './gamification';
|
||||
export * from './budget';
|
||||
export * from './address'; // Add the new address service exports
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/services/db/notification.test.ts
|
||||
// src/services/db/notification.db.test.ts
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import {
|
||||
getNotificationsForUser,
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
createBulkNotifications,
|
||||
markAllNotificationsAsRead,
|
||||
markNotificationAsRead,
|
||||
} from './notification';
|
||||
} from './notification.db';
|
||||
import { Pool } from 'pg';
|
||||
|
||||
const mockQuery = vi.fn();
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/services/db/notification.ts
|
||||
import { getPool } from './connection';
|
||||
import { ForeignKeyConstraintError } from './errors';
|
||||
// src/services/db/notification.db.ts
|
||||
import { getPool } from './connection.db';
|
||||
import { ForeignKeyConstraintError } from './errors.db';
|
||||
import { logger } from '../logger';
|
||||
import { Notification } from '../../types';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/services/db/personalization.test.ts
|
||||
// src/services/db/personalization.db.test.ts
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import {
|
||||
getWatchedItems,
|
||||
@@ -16,9 +16,9 @@ import {
|
||||
getUserAppliances,
|
||||
setUserAppliances,
|
||||
getRecipesForUserDiets,
|
||||
} from './personalization';
|
||||
} from './personalization.db';
|
||||
import type { MasterGroceryItem } from '../../types';
|
||||
import { getPool } from './connection';
|
||||
import { getPool } from './connection.db';
|
||||
import { Pool } from 'pg';
|
||||
|
||||
const mockQuery = vi.fn();
|
||||
@@ -39,7 +39,7 @@ describe('Personalization DB Service', () => {
|
||||
beforeEach(() => {
|
||||
mockQuery.mockReset();
|
||||
vi.mocked(Pool).mockImplementation(function() {
|
||||
console.log('[DEBUG] personalization.test.ts: Local Pool mock instantiated via "new"');
|
||||
console.log('[DEBUG] personalization.db.test.ts: Local Pool mock instantiated via "new"');
|
||||
return {
|
||||
query: mockQuery,
|
||||
connect: mockConnect,
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/services/db/personalization.ts
|
||||
import { getPool } from './connection';
|
||||
import { UniqueConstraintError, ForeignKeyConstraintError } from './errors';
|
||||
// src/services/db/personalization.db.ts
|
||||
import { getPool } from './connection.db';
|
||||
import { UniqueConstraintError, ForeignKeyConstraintError } from './errors.db';
|
||||
import { logger } from '../logger';
|
||||
import {
|
||||
MasterGroceryItem,
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/services/db/recipe.test.ts
|
||||
// src/services/db/recipe.db.test.ts
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import {
|
||||
getRecipesBySalePercentage,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
getRecipeComments,
|
||||
addRecipeComment,
|
||||
forkRecipe,
|
||||
} from './recipe';
|
||||
} from './recipe.db';
|
||||
import { Pool } from 'pg';
|
||||
|
||||
const mockQuery = vi.fn();
|
||||
@@ -30,7 +30,7 @@ describe('Recipe DB Service', () => {
|
||||
beforeEach(() => {
|
||||
// FIX: Use mockImplementation with a standard function to support 'new Pool()'
|
||||
vi.mocked(Pool).mockImplementation(function() {
|
||||
console.log('[DEBUG] recipe.test.ts: Local Pool mock instantiated via "new"');
|
||||
console.log('[DEBUG] recipe.db.test.ts: Local Pool mock instantiated via "new"');
|
||||
return {
|
||||
query: mockQuery,
|
||||
connect: vi.fn().mockResolvedValue({ query: mockQuery, release: vi.fn() }),
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/services/db/recipe.ts
|
||||
import { getPool } from './connection';
|
||||
import { ForeignKeyConstraintError } from './errors';
|
||||
// src/services/db/recipe.db.ts
|
||||
import { getPool } from './connection.db';
|
||||
import { ForeignKeyConstraintError } from './errors.db';
|
||||
import { logger } from '../logger';
|
||||
import { Recipe, FavoriteRecipe, RecipeComment } from '../../types';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user