Compare commits

...

2 Commits

Author SHA1 Message Date
Gitea Actions
2e98bc3fc7 ci: Bump version to 0.11.18 [skip ci] 2026-01-20 14:18:32 +05:00
ec2f143218 logging postgres + test fixin
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 19m18s
2026-01-20 01:16:27 -08:00
6 changed files with 59 additions and 41 deletions

4
package-lock.json generated
View File

@@ -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",

View File

@@ -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\"",

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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 () => {

View File

@@ -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 ---