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",
|
"name": "flyer-crawler",
|
||||||
"version": "0.11.17",
|
"version": "0.11.18",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "flyer-crawler",
|
"name": "flyer-crawler",
|
||||||
"version": "0.11.17",
|
"version": "0.11.18",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bull-board/api": "^6.14.2",
|
"@bull-board/api": "^6.14.2",
|
||||||
"@bull-board/express": "^6.14.2",
|
"@bull-board/express": "^6.14.2",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "flyer-crawler",
|
"name": "flyer-crawler",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.11.17",
|
"version": "0.11.18",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently \"npm:start:dev\" \"vite\"",
|
"dev": "concurrently \"npm:start:dev\" \"vite\"",
|
||||||
|
|||||||
@@ -1511,6 +1511,30 @@ BEGIN
|
|||||||
AND iph.store_location_id = na.store_location_id;
|
AND iph.store_location_id = na.store_location_id;
|
||||||
|
|
||||||
-- 4. Delete any history records that no longer have any data points.
|
-- 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
|
DELETE FROM public.item_price_history iph
|
||||||
WHERE iph.master_item_id = OLD.master_item_id
|
WHERE iph.master_item_id = OLD.master_item_id
|
||||||
AND NOT EXISTS (
|
AND NOT EXISTS (
|
||||||
|
|||||||
@@ -2981,6 +2981,30 @@ BEGIN
|
|||||||
AND iph.store_location_id = na.store_location_id;
|
AND iph.store_location_id = na.store_location_id;
|
||||||
|
|
||||||
-- 4. Delete any history records that no longer have any data points.
|
-- 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
|
DELETE FROM public.item_price_history iph
|
||||||
WHERE iph.master_item_id = OLD.master_item_id
|
WHERE iph.master_item_id = OLD.master_item_id
|
||||||
AND NOT EXISTS (
|
AND NOT EXISTS (
|
||||||
|
|||||||
@@ -224,11 +224,11 @@ describe('AuthService', () => {
|
|||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
newUserProfile: mockUserProfile,
|
newUserProfile: mockUserProfile,
|
||||||
accessToken: 'access-token',
|
accessToken: 'access-token',
|
||||||
refreshToken: 'mocked_random_id',
|
refreshToken: expect.any(String),
|
||||||
});
|
});
|
||||||
expect(userRepo.saveRefreshToken).toHaveBeenCalledWith(
|
expect(userRepo.saveRefreshToken).toHaveBeenCalledWith(
|
||||||
'user-123',
|
'user-123',
|
||||||
'mocked_random_id',
|
expect.any(String),
|
||||||
reqLog,
|
reqLog,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -254,7 +254,7 @@ describe('AuthService', () => {
|
|||||||
);
|
);
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
accessToken: 'access-token',
|
accessToken: 'access-token',
|
||||||
refreshToken: 'mocked_random_id',
|
refreshToken: expect.any(String),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -293,10 +293,10 @@ describe('AuthService', () => {
|
|||||||
);
|
);
|
||||||
expect(sendPasswordResetEmail).toHaveBeenCalledWith(
|
expect(sendPasswordResetEmail).toHaveBeenCalledWith(
|
||||||
'test@example.com',
|
'test@example.com',
|
||||||
expect.stringContaining('/reset-password/mocked_random_id'),
|
expect.stringMatching(/\/reset-password\/[a-f0-9]+/),
|
||||||
reqLog,
|
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 () => {
|
it('should log warning and return undefined for non-existent user', async () => {
|
||||||
@@ -333,7 +333,7 @@ describe('AuthService', () => {
|
|||||||
{ emailError },
|
{ emailError },
|
||||||
`Email send failure during password reset for user`,
|
`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 () => {
|
it('should re-throw RepositoryError', async () => {
|
||||||
|
|||||||
@@ -164,38 +164,8 @@ vi.mock('jsonwebtoken', () => ({
|
|||||||
// Mock 'bcrypt'. The service uses `import * as bcrypt from 'bcrypt'`.
|
// Mock 'bcrypt'. The service uses `import * as bcrypt from 'bcrypt'`.
|
||||||
vi.mock('bcrypt');
|
vi.mock('bcrypt');
|
||||||
|
|
||||||
// Mock 'crypto'. Supports both default import and named imports.
|
// NOTE: We do NOT mock the 'crypto' module anymore. It works correctly without mocking in tests.
|
||||||
// Default: import crypto from 'crypto'; crypto.randomUUID()
|
// The previous attempt to mock it caused issues because vi.importActual returned an empty object.
|
||||||
// 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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// --- Global Mocks ---
|
// --- Global Mocks ---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user