Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e98bc3fc7 | ||
| ec2f143218 |
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "flyer-crawler",
|
||||
"version": "0.11.17",
|
||||
"version": "0.11.18",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "flyer-crawler",
|
||||
"version": "0.11.17",
|
||||
"version": "0.11.18",
|
||||
"dependencies": {
|
||||
"@bull-board/api": "^6.14.2",
|
||||
"@bull-board/express": "^6.14.2",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "flyer-crawler",
|
||||
"private": true,
|
||||
"version": "0.11.17",
|
||||
"version": "0.11.18",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "concurrently \"npm:start:dev\" \"vite\"",
|
||||
|
||||
@@ -1511,6 +1511,30 @@ BEGIN
|
||||
AND iph.store_location_id = na.store_location_id;
|
||||
|
||||
-- 4. Delete any history records that no longer have any data points.
|
||||
-- We need to recreate the CTE since CTEs are scoped to a single statement.
|
||||
WITH affected_days_and_locations AS (
|
||||
SELECT DISTINCT
|
||||
generate_series(f.valid_from, f.valid_to, '1 day'::interval)::date AS summary_date,
|
||||
fl.store_location_id
|
||||
FROM public.flyers f
|
||||
JOIN public.flyer_locations fl ON f.flyer_id = fl.flyer_id
|
||||
WHERE f.flyer_id = OLD.flyer_id
|
||||
),
|
||||
new_aggregates AS (
|
||||
SELECT
|
||||
adl.summary_date,
|
||||
adl.store_location_id,
|
||||
MIN(fi.price_in_cents) AS min_price,
|
||||
MAX(fi.price_in_cents) AS max_price,
|
||||
ROUND(AVG(fi.price_in_cents))::int AS avg_price,
|
||||
COUNT(fi.flyer_item_id)::int AS data_points
|
||||
FROM affected_days_and_locations adl
|
||||
LEFT JOIN public.flyer_items fi ON fi.master_item_id = OLD.master_item_id AND fi.price_in_cents IS NOT NULL
|
||||
LEFT JOIN public.flyers f ON fi.flyer_id = f.flyer_id AND adl.summary_date BETWEEN f.valid_from AND f.valid_to
|
||||
LEFT JOIN public.flyer_locations fl ON fi.flyer_id = fl.flyer_id AND adl.store_location_id = fl.store_location_id
|
||||
WHERE fl.flyer_id IS NOT NULL
|
||||
GROUP BY adl.summary_date, adl.store_location_id
|
||||
)
|
||||
DELETE FROM public.item_price_history iph
|
||||
WHERE iph.master_item_id = OLD.master_item_id
|
||||
AND NOT EXISTS (
|
||||
|
||||
@@ -2981,6 +2981,30 @@ BEGIN
|
||||
AND iph.store_location_id = na.store_location_id;
|
||||
|
||||
-- 4. Delete any history records that no longer have any data points.
|
||||
-- We need to recreate the CTE since CTEs are scoped to a single statement.
|
||||
WITH affected_days_and_locations AS (
|
||||
SELECT DISTINCT
|
||||
generate_series(f.valid_from, f.valid_to, '1 day'::interval)::date AS summary_date,
|
||||
fl.store_location_id
|
||||
FROM public.flyers f
|
||||
JOIN public.flyer_locations fl ON f.flyer_id = fl.flyer_id
|
||||
WHERE f.flyer_id = OLD.flyer_id
|
||||
),
|
||||
new_aggregates AS (
|
||||
SELECT
|
||||
adl.summary_date,
|
||||
adl.store_location_id,
|
||||
MIN(fi.price_in_cents) AS min_price,
|
||||
MAX(fi.price_in_cents) AS max_price,
|
||||
ROUND(AVG(fi.price_in_cents))::int AS avg_price,
|
||||
COUNT(fi.flyer_item_id)::int AS data_points
|
||||
FROM affected_days_and_locations adl
|
||||
LEFT JOIN public.flyer_items fi ON fi.master_item_id = OLD.master_item_id AND fi.price_in_cents IS NOT NULL
|
||||
LEFT JOIN public.flyers f ON fi.flyer_id = f.flyer_id AND adl.summary_date BETWEEN f.valid_from AND f.valid_to
|
||||
LEFT JOIN public.flyer_locations fl ON fi.flyer_id = fl.flyer_id AND adl.store_location_id = fl.store_location_id
|
||||
WHERE fl.flyer_id IS NOT NULL
|
||||
GROUP BY adl.summary_date, adl.store_location_id
|
||||
)
|
||||
DELETE FROM public.item_price_history iph
|
||||
WHERE iph.master_item_id = OLD.master_item_id
|
||||
AND NOT EXISTS (
|
||||
|
||||
@@ -224,11 +224,11 @@ describe('AuthService', () => {
|
||||
expect(result).toEqual({
|
||||
newUserProfile: mockUserProfile,
|
||||
accessToken: 'access-token',
|
||||
refreshToken: 'mocked_random_id',
|
||||
refreshToken: expect.any(String),
|
||||
});
|
||||
expect(userRepo.saveRefreshToken).toHaveBeenCalledWith(
|
||||
'user-123',
|
||||
'mocked_random_id',
|
||||
expect.any(String),
|
||||
reqLog,
|
||||
);
|
||||
});
|
||||
@@ -254,7 +254,7 @@ describe('AuthService', () => {
|
||||
);
|
||||
expect(result).toEqual({
|
||||
accessToken: 'access-token',
|
||||
refreshToken: 'mocked_random_id',
|
||||
refreshToken: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -293,10 +293,10 @@ describe('AuthService', () => {
|
||||
);
|
||||
expect(sendPasswordResetEmail).toHaveBeenCalledWith(
|
||||
'test@example.com',
|
||||
expect.stringContaining('/reset-password/mocked_random_id'),
|
||||
expect.stringMatching(/\/reset-password\/[a-f0-9]+/),
|
||||
reqLog,
|
||||
);
|
||||
expect(result).toBe('mocked_random_id');
|
||||
expect(result).toEqual(expect.any(String));
|
||||
});
|
||||
|
||||
it('should log warning and return undefined for non-existent user', async () => {
|
||||
@@ -333,7 +333,7 @@ describe('AuthService', () => {
|
||||
{ emailError },
|
||||
`Email send failure during password reset for user`,
|
||||
);
|
||||
expect(result).toBe('mocked_random_id');
|
||||
expect(result).toEqual(expect.any(String));
|
||||
});
|
||||
|
||||
it('should re-throw RepositoryError', async () => {
|
||||
|
||||
@@ -164,38 +164,8 @@ vi.mock('jsonwebtoken', () => ({
|
||||
// Mock 'bcrypt'. The service uses `import * as bcrypt from 'bcrypt'`.
|
||||
vi.mock('bcrypt');
|
||||
|
||||
// Mock 'crypto'. Supports both default import and named imports.
|
||||
// Default: import crypto from 'crypto'; crypto.randomUUID()
|
||||
// Named: import { randomUUID } from 'crypto'; randomUUID()
|
||||
vi.mock('crypto', async () => {
|
||||
const actual = await vi.importActual<typeof import('crypto')>('crypto');
|
||||
const mockRandomUUID = vi.fn(() => actual.randomUUID());
|
||||
const mockRandomBytes = vi.fn((size: number) => {
|
||||
const buffer = actual.randomBytes(size);
|
||||
// Add mocked toString for backward compatibility
|
||||
buffer.toString = vi.fn().mockImplementation((encoding) => {
|
||||
const id = 'mocked_random_id';
|
||||
console.log(
|
||||
`[DEBUG] tests-setup-unit.ts: crypto.randomBytes mock returning "${id}" for encoding "${encoding}"`,
|
||||
);
|
||||
return id;
|
||||
});
|
||||
return buffer;
|
||||
});
|
||||
|
||||
return {
|
||||
...actual,
|
||||
// Named exports for: import { randomUUID } from 'crypto'
|
||||
randomUUID: mockRandomUUID,
|
||||
randomBytes: mockRandomBytes,
|
||||
// Default export for: import crypto from 'crypto'
|
||||
default: {
|
||||
...actual,
|
||||
randomUUID: mockRandomUUID,
|
||||
randomBytes: mockRandomBytes,
|
||||
},
|
||||
};
|
||||
});
|
||||
// NOTE: We do NOT mock the 'crypto' module anymore. It works correctly without mocking in tests.
|
||||
// The previous attempt to mock it caused issues because vi.importActual returned an empty object.
|
||||
|
||||
// --- Global Mocks ---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user