Google Maps Bug
This commit is contained in:
@@ -51,6 +51,34 @@ export class CacheService {
|
||||
logger.error('Cache delete error', { key, error });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all keys matching the provided pattern (without namespace prefix).
|
||||
* Uses SCAN to avoid blocking Redis for large keyspaces.
|
||||
*/
|
||||
async deletePattern(pattern: string): Promise<void> {
|
||||
try {
|
||||
const namespacedPattern = this.prefix + pattern;
|
||||
let cursor = '0';
|
||||
|
||||
do {
|
||||
const [nextCursor, keys] = await redis.scan(
|
||||
cursor,
|
||||
'MATCH',
|
||||
namespacedPattern,
|
||||
'COUNT',
|
||||
100
|
||||
);
|
||||
cursor = nextCursor;
|
||||
|
||||
if (keys.length > 0) {
|
||||
await redis.del(...keys);
|
||||
}
|
||||
} while (cursor !== '0');
|
||||
} catch (error) {
|
||||
logger.error('Cache delete pattern error', { pattern, error });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const cacheService = new CacheService();
|
||||
|
||||
@@ -315,6 +315,7 @@ describe('Admin Catalog Integration Tests', () => {
|
||||
it('should invalidate cache after create operation', async () => {
|
||||
// Set a cache value
|
||||
await redis.set('mvp:platform:vehicle-data:makes:2024', JSON.stringify([]), 3600);
|
||||
await redis.set('mvp:platform:years', JSON.stringify([2024]), 3600);
|
||||
|
||||
// Create make (should invalidate cache)
|
||||
await app.inject({
|
||||
@@ -324,12 +325,11 @@ describe('Admin Catalog Integration Tests', () => {
|
||||
payload: { name: 'Honda' }
|
||||
});
|
||||
|
||||
// Check if cache was invalidated (implementation depends on invalidateVehicleData)
|
||||
// Note: Current implementation logs warning but doesn't actually invalidate
|
||||
// This test documents expected behavior
|
||||
const cacheValue = await redis.get('mvp:platform:vehicle-data:makes:2024');
|
||||
// Cache should be invalidated or remain (depending on implementation)
|
||||
expect(cacheValue).toBeDefined();
|
||||
const yearsCacheValue = await redis.get('mvp:platform:years');
|
||||
|
||||
expect(cacheValue).toBeNull();
|
||||
expect(yearsCacheValue).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -114,6 +114,15 @@ export class PlatformCacheService {
|
||||
* Invalidate all vehicle data cache (for admin operations)
|
||||
*/
|
||||
async invalidateVehicleData(): Promise<void> {
|
||||
logger.warn('Vehicle data cache invalidation not implemented (requires pattern deletion)');
|
||||
try {
|
||||
const yearsKey = this.prefix + 'years';
|
||||
await Promise.all([
|
||||
this.cacheService.del(yearsKey),
|
||||
this.cacheService.deletePattern(this.prefix + 'vehicle-data:*')
|
||||
]);
|
||||
logger.debug('Vehicle data cache invalidated');
|
||||
} catch (error) {
|
||||
logger.error('Vehicle data cache invalidation failed', { error });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,12 @@ import { StationsService } from '../domain/stations.service';
|
||||
import { StationsRepository } from '../data/stations.repository';
|
||||
import { pool } from '../../../core/config/database';
|
||||
import { logger } from '../../../core/logging/logger';
|
||||
import { StationSearchBody, SaveStationBody, StationParams } from '../domain/stations.types';
|
||||
import {
|
||||
StationSearchBody,
|
||||
SaveStationBody,
|
||||
StationParams,
|
||||
UpdateSavedStationBody
|
||||
} from '../domain/stations.types';
|
||||
|
||||
export class StationsController {
|
||||
private stationsService: StationsService;
|
||||
@@ -50,7 +55,14 @@ export class StationsController {
|
||||
async saveStation(request: FastifyRequest<{ Body: SaveStationBody }>, reply: FastifyReply) {
|
||||
try {
|
||||
const userId = (request as any).user.sub;
|
||||
const { placeId, nickname, notes, isFavorite } = request.body;
|
||||
const {
|
||||
placeId,
|
||||
nickname,
|
||||
notes,
|
||||
isFavorite,
|
||||
has93Octane,
|
||||
has93OctaneEthanolFree
|
||||
} = request.body;
|
||||
|
||||
if (!placeId) {
|
||||
return reply.code(400).send({
|
||||
@@ -62,7 +74,9 @@ export class StationsController {
|
||||
const result = await this.stationsService.saveStation(placeId, userId, {
|
||||
nickname,
|
||||
notes,
|
||||
isFavorite
|
||||
isFavorite,
|
||||
has93Octane,
|
||||
has93OctaneEthanolFree
|
||||
});
|
||||
|
||||
return reply.code(201).send(result);
|
||||
@@ -82,6 +96,38 @@ export class StationsController {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async updateSavedStation(
|
||||
request: FastifyRequest<{ Params: StationParams; Body: UpdateSavedStationBody }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const userId = (request as any).user.sub;
|
||||
const { placeId } = request.params;
|
||||
|
||||
const result = await this.stationsService.updateSavedStation(placeId, userId, request.body);
|
||||
|
||||
return reply.code(200).send(result);
|
||||
} catch (error: any) {
|
||||
logger.error('Error updating saved station', {
|
||||
error,
|
||||
placeId: request.params.placeId,
|
||||
userId: (request as any).user?.sub
|
||||
});
|
||||
|
||||
if (error.message.includes('not found')) {
|
||||
return reply.code(404).send({
|
||||
error: 'Not Found',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
|
||||
return reply.code(500).send({
|
||||
error: 'Internal server error',
|
||||
message: 'Failed to update saved station'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getSavedStations(request: FastifyRequest, reply: FastifyReply) {
|
||||
try {
|
||||
@@ -122,4 +168,4 @@ export class StationsController {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ import { FastifyPluginAsync } from 'fastify';
|
||||
import {
|
||||
StationSearchBody,
|
||||
SaveStationBody,
|
||||
StationParams
|
||||
StationParams,
|
||||
UpdateSavedStationBody
|
||||
} from '../domain/stations.types';
|
||||
import { StationsController } from './stations.controller';
|
||||
|
||||
@@ -30,6 +31,15 @@ export const stationsRoutes: FastifyPluginAsync = async (
|
||||
handler: stationsController.saveStation.bind(stationsController)
|
||||
});
|
||||
|
||||
// PATCH /api/stations/saved/:placeId - Update saved station metadata
|
||||
fastify.patch<{ Params: StationParams; Body: UpdateSavedStationBody }>(
|
||||
'/stations/saved/:placeId',
|
||||
{
|
||||
preHandler: [fastify.authenticate],
|
||||
handler: stationsController.updateSavedStation.bind(stationsController)
|
||||
}
|
||||
);
|
||||
|
||||
// GET /api/stations/saved - Get user's saved stations
|
||||
fastify.get('/stations/saved', {
|
||||
preHandler: [fastify.authenticate],
|
||||
|
||||
@@ -45,14 +45,37 @@ export class StationsRepository {
|
||||
return this.mapCacheRow(result.rows[0]);
|
||||
}
|
||||
|
||||
async saveStation(userId: string, placeId: string, data?: { nickname?: string; notes?: string; isFavorite?: boolean }): Promise<SavedStation> {
|
||||
async saveStation(
|
||||
userId: string,
|
||||
placeId: string,
|
||||
data?: {
|
||||
nickname?: string;
|
||||
notes?: string;
|
||||
isFavorite?: boolean;
|
||||
has93Octane?: boolean;
|
||||
has93OctaneEthanolFree?: boolean;
|
||||
}
|
||||
): Promise<SavedStation> {
|
||||
const query = `
|
||||
INSERT INTO saved_stations (user_id, place_id, nickname, notes, is_favorite)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
INSERT INTO saved_stations (
|
||||
user_id,
|
||||
place_id,
|
||||
nickname,
|
||||
notes,
|
||||
is_favorite,
|
||||
has_93_octane,
|
||||
has_93_octane_ethanol_free
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
ON CONFLICT (user_id, place_id) DO UPDATE
|
||||
SET nickname = COALESCE($3, saved_stations.nickname),
|
||||
notes = COALESCE($4, saved_stations.notes),
|
||||
is_favorite = COALESCE($5, saved_stations.is_favorite),
|
||||
has_93_octane = COALESCE($6, saved_stations.has_93_octane),
|
||||
has_93_octane_ethanol_free = CASE
|
||||
WHEN $6 IS NOT NULL AND $6 = false THEN false
|
||||
ELSE COALESCE($7, saved_stations.has_93_octane_ethanol_free)
|
||||
END,
|
||||
updated_at = NOW()
|
||||
RETURNING *
|
||||
`;
|
||||
@@ -62,12 +85,58 @@ export class StationsRepository {
|
||||
placeId,
|
||||
data?.nickname,
|
||||
data?.notes,
|
||||
data?.isFavorite || false
|
||||
data?.isFavorite ?? false,
|
||||
data?.has93Octane ?? false,
|
||||
data?.has93OctaneEthanolFree ?? false
|
||||
]);
|
||||
|
||||
return this.mapSavedRow(result.rows[0]);
|
||||
}
|
||||
|
||||
async updateSavedStation(
|
||||
userId: string,
|
||||
placeId: string,
|
||||
data: {
|
||||
nickname?: string;
|
||||
notes?: string;
|
||||
isFavorite?: boolean;
|
||||
has93Octane?: boolean;
|
||||
has93OctaneEthanolFree?: boolean;
|
||||
}
|
||||
): Promise<SavedStation | null> {
|
||||
const query = `
|
||||
UPDATE saved_stations
|
||||
SET
|
||||
nickname = COALESCE($3, nickname),
|
||||
notes = COALESCE($4, notes),
|
||||
is_favorite = COALESCE($5, is_favorite),
|
||||
has_93_octane = COALESCE($6, has_93_octane),
|
||||
has_93_octane_ethanol_free = CASE
|
||||
WHEN $6 IS NOT NULL AND $6 = false THEN false
|
||||
ELSE COALESCE($7, has_93_octane_ethanol_free)
|
||||
END,
|
||||
updated_at = NOW()
|
||||
WHERE user_id = $1 AND place_id = $2
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const result = await this.pool.query(query, [
|
||||
userId,
|
||||
placeId,
|
||||
data.nickname,
|
||||
data.notes,
|
||||
data.isFavorite,
|
||||
data.has93Octane,
|
||||
data.has93OctaneEthanolFree
|
||||
]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.mapSavedRow(result.rows[0]);
|
||||
}
|
||||
|
||||
async getUserSavedStations(userId: string): Promise<SavedStation[]> {
|
||||
const query = `
|
||||
SELECT * FROM saved_stations
|
||||
@@ -107,11 +176,14 @@ export class StationsRepository {
|
||||
id: row.id,
|
||||
userId: row.user_id,
|
||||
stationId: row.place_id,
|
||||
placeId: row.place_id,
|
||||
nickname: row.nickname,
|
||||
notes: row.notes,
|
||||
isFavorite: row.is_favorite,
|
||||
has93Octane: row.has_93_octane ?? false,
|
||||
has93OctaneEthanolFree: row.has_93_octane_ethanol_free ?? false,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,13 @@
|
||||
|
||||
import { StationsRepository } from '../data/stations.repository';
|
||||
import { googleMapsClient } from '../external/google-maps/google-maps.client';
|
||||
import { StationSearchRequest, StationSearchResponse, SavedStation } from './stations.types';
|
||||
import {
|
||||
StationSearchRequest,
|
||||
StationSearchResponse,
|
||||
SavedStation,
|
||||
StationSavedMetadata,
|
||||
UpdateSavedStationBody
|
||||
} from './stations.types';
|
||||
import { logger } from '../../../core/logging/logger';
|
||||
|
||||
export class StationsService {
|
||||
@@ -22,14 +28,43 @@ export class StationsService {
|
||||
request.longitude,
|
||||
request.radius || 5000
|
||||
);
|
||||
|
||||
// Fetch saved stations for prioritization and metadata
|
||||
const savedStations = await this.repository.getUserSavedStations(userId);
|
||||
const savedMap = new Map(savedStations.map(saved => [saved.placeId, saved]));
|
||||
|
||||
// Cache stations for future reference
|
||||
for (const station of stations) {
|
||||
await this.repository.cacheStation(station);
|
||||
|
||||
// Enrich station with saved metadata
|
||||
const saved = savedMap.get(station.placeId);
|
||||
if (saved) {
|
||||
station.isSaved = true;
|
||||
station.savedMetadata = this.mapSavedMetadata(saved);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by distance
|
||||
stations.sort((a, b) => (a.distance || 0) - (b.distance || 0));
|
||||
// Sort saved stations first, favorites next, then distance
|
||||
stations.sort((a, b) => {
|
||||
const aSaved = a.savedMetadata ? 1 : 0;
|
||||
const bSaved = b.savedMetadata ? 1 : 0;
|
||||
|
||||
if (aSaved !== bSaved) {
|
||||
return bSaved - aSaved;
|
||||
}
|
||||
|
||||
const aFavorite = a.savedMetadata?.isFavorite ? 1 : 0;
|
||||
const bFavorite = b.savedMetadata?.isFavorite ? 1 : 0;
|
||||
|
||||
if (aFavorite !== bFavorite) {
|
||||
return bFavorite - aFavorite;
|
||||
}
|
||||
|
||||
const aDistance = a.distance ?? Number.POSITIVE_INFINITY;
|
||||
const bDistance = b.distance ?? Number.POSITIVE_INFINITY;
|
||||
return aDistance - bDistance;
|
||||
});
|
||||
|
||||
return {
|
||||
stations,
|
||||
@@ -45,7 +80,13 @@ export class StationsService {
|
||||
async saveStation(
|
||||
placeId: string,
|
||||
userId: string,
|
||||
data?: { nickname?: string; notes?: string; isFavorite?: boolean }
|
||||
data?: {
|
||||
nickname?: string;
|
||||
notes?: string;
|
||||
isFavorite?: boolean;
|
||||
has93Octane?: boolean;
|
||||
has93OctaneEthanolFree?: boolean;
|
||||
}
|
||||
) {
|
||||
// Get station details from cache
|
||||
const station = await this.repository.getCachedStation(placeId);
|
||||
@@ -62,6 +103,25 @@ export class StationsService {
|
||||
station
|
||||
};
|
||||
}
|
||||
|
||||
async updateSavedStation(
|
||||
placeId: string,
|
||||
userId: string,
|
||||
data: UpdateSavedStationBody
|
||||
) {
|
||||
const updated = await this.repository.updateSavedStation(userId, placeId, data);
|
||||
|
||||
if (!updated) {
|
||||
throw new Error('Saved station not found');
|
||||
}
|
||||
|
||||
const station = await this.repository.getCachedStation(placeId);
|
||||
|
||||
return {
|
||||
...updated,
|
||||
station
|
||||
};
|
||||
}
|
||||
|
||||
async getUserSavedStations(userId: string) {
|
||||
const savedStations = await this.repository.getUserSavedStations(userId);
|
||||
@@ -87,4 +147,14 @@ export class StationsService {
|
||||
throw new Error('Saved station not found');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private mapSavedMetadata(saved: SavedStation): StationSavedMetadata {
|
||||
return {
|
||||
nickname: saved.nickname,
|
||||
notes: saved.notes,
|
||||
isFavorite: saved.isFavorite,
|
||||
has93Octane: saved.has93Octane,
|
||||
has93OctaneEthanolFree: saved.has93OctaneEthanolFree
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ export interface Station {
|
||||
isOpen?: boolean;
|
||||
rating?: number;
|
||||
photoUrl?: string;
|
||||
isSaved?: boolean;
|
||||
savedMetadata?: StationSavedMetadata;
|
||||
}
|
||||
|
||||
export interface StationSearchRequest {
|
||||
@@ -41,9 +43,12 @@ export interface SavedStation {
|
||||
id: string;
|
||||
userId: string;
|
||||
stationId: string;
|
||||
placeId: string;
|
||||
nickname?: string;
|
||||
notes?: string;
|
||||
isFavorite: boolean;
|
||||
has93Octane: boolean;
|
||||
has93OctaneEthanolFree: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -61,8 +66,26 @@ export interface SaveStationBody {
|
||||
nickname?: string;
|
||||
notes?: string;
|
||||
isFavorite?: boolean;
|
||||
has93Octane?: boolean;
|
||||
has93OctaneEthanolFree?: boolean;
|
||||
}
|
||||
|
||||
export interface StationParams {
|
||||
placeId: string;
|
||||
}
|
||||
}
|
||||
|
||||
export interface UpdateSavedStationBody {
|
||||
nickname?: string;
|
||||
notes?: string;
|
||||
isFavorite?: boolean;
|
||||
has93Octane?: boolean;
|
||||
has93OctaneEthanolFree?: boolean;
|
||||
}
|
||||
|
||||
export interface StationSavedMetadata {
|
||||
nickname?: string;
|
||||
notes?: string;
|
||||
isFavorite: boolean;
|
||||
has93Octane: boolean;
|
||||
has93OctaneEthanolFree: boolean;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
-- Add 93 octane metadata to saved stations
|
||||
ALTER TABLE saved_stations
|
||||
ADD COLUMN IF NOT EXISTS has_93_octane BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
ALTER TABLE saved_stations
|
||||
ADD COLUMN IF NOT EXISTS has_93_octane_ethanol_free BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- Backfill existing rows with defaults
|
||||
UPDATE saved_stations
|
||||
SET has_93_octane = COALESCE(has_93_octane, false),
|
||||
has_93_octane_ethanol_free = COALESCE(has_93_octane_ethanol_free, false);
|
||||
@@ -52,9 +52,12 @@ export const mockSavedStations: SavedStation[] = [
|
||||
id: '550e8400-e29b-41d4-a716-446655440000',
|
||||
userId: 'user123',
|
||||
stationId: mockStations[0].placeId,
|
||||
placeId: mockStations[0].placeId,
|
||||
nickname: 'Work Gas Station',
|
||||
notes: 'Usually has good prices, rewards program available',
|
||||
isFavorite: true,
|
||||
has93Octane: true,
|
||||
has93OctaneEthanolFree: true,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
updatedAt: new Date('2024-01-15')
|
||||
},
|
||||
@@ -62,9 +65,12 @@ export const mockSavedStations: SavedStation[] = [
|
||||
id: '550e8400-e29b-41d4-a716-446655440001',
|
||||
userId: 'user123',
|
||||
stationId: mockStations[1].placeId,
|
||||
placeId: mockStations[1].placeId,
|
||||
nickname: 'Home Station',
|
||||
notes: 'Closest to apartment',
|
||||
isFavorite: true,
|
||||
has93Octane: true,
|
||||
has93OctaneEthanolFree: false,
|
||||
createdAt: new Date('2024-01-05'),
|
||||
updatedAt: new Date('2024-01-10')
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ describe('StationsService', () => {
|
||||
cacheStation: jest.fn().mockResolvedValue(undefined),
|
||||
getCachedStation: jest.fn(),
|
||||
saveStation: jest.fn(),
|
||||
getUserSavedStations: jest.fn(),
|
||||
getUserSavedStations: jest.fn().mockResolvedValue([]),
|
||||
updateSavedStation: jest.fn(),
|
||||
deleteSavedStation: jest.fn()
|
||||
} as unknown as jest.Mocked<StationsRepository>;
|
||||
|
||||
@@ -51,6 +52,7 @@ describe('StationsService', () => {
|
||||
expect(result.stations).toHaveLength(3);
|
||||
expect(result.stations[0]?.name).toBe('Shell Gas Station - Downtown');
|
||||
expect(mockRepository.cacheStation).toHaveBeenCalledTimes(3);
|
||||
expect(mockRepository.getUserSavedStations).toHaveBeenCalledWith(mockUserId);
|
||||
});
|
||||
|
||||
it('should sort stations by distance', async () => {
|
||||
@@ -108,6 +110,32 @@ describe('StationsService', () => {
|
||||
|
||||
expect(mockRepository.cacheStation).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should prioritize saved stations and include saved metadata', async () => {
|
||||
const savedStation = mockSavedStations[0];
|
||||
if (!savedStation) throw new Error('Mock saved station not found');
|
||||
mockRepository.getUserSavedStations.mockResolvedValue([savedStation]);
|
||||
|
||||
const stationsWithDistance = [
|
||||
{ ...mockStations[0], distance: 900 },
|
||||
{ ...mockStations[1], distance: 100 }
|
||||
];
|
||||
|
||||
(googleMapsClient.searchNearbyStations as jest.Mock).mockResolvedValue(
|
||||
stationsWithDistance
|
||||
);
|
||||
|
||||
const result = await service.searchNearbyStations(
|
||||
{
|
||||
latitude: searchCoordinates.sanFrancisco.latitude,
|
||||
longitude: searchCoordinates.sanFrancisco.longitude
|
||||
},
|
||||
mockUserId
|
||||
);
|
||||
|
||||
expect(result.stations[0]?.placeId).toBe(savedStation.stationId);
|
||||
expect(result.stations[0]?.savedMetadata?.has93Octane).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('saveStation', () => {
|
||||
@@ -153,13 +181,17 @@ describe('StationsService', () => {
|
||||
await service.saveStation(station.placeId, mockUserId, {
|
||||
nickname: 'Favorite Station',
|
||||
notes: 'Best prices in area',
|
||||
isFavorite: true
|
||||
isFavorite: true,
|
||||
has93Octane: true,
|
||||
has93OctaneEthanolFree: false
|
||||
});
|
||||
|
||||
expect(mockRepository.saveStation).toHaveBeenCalledWith(mockUserId, station.placeId, {
|
||||
nickname: 'Favorite Station',
|
||||
notes: 'Best prices in area',
|
||||
isFavorite: true
|
||||
isFavorite: true,
|
||||
has93Octane: true,
|
||||
has93OctaneEthanolFree: false
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -188,6 +220,40 @@ describe('StationsService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateSavedStation', () => {
|
||||
it('should update saved station metadata', async () => {
|
||||
const savedStation = mockSavedStations[0];
|
||||
const station = mockStations[0];
|
||||
if (!savedStation || !station) throw new Error('Mock data not found');
|
||||
|
||||
mockRepository.updateSavedStation.mockResolvedValue(savedStation);
|
||||
mockRepository.getCachedStation.mockResolvedValue(station);
|
||||
|
||||
const result = await service.updateSavedStation(savedStation.stationId, mockUserId, {
|
||||
has93Octane: true,
|
||||
has93OctaneEthanolFree: true
|
||||
});
|
||||
|
||||
expect(mockRepository.updateSavedStation).toHaveBeenCalledWith(
|
||||
mockUserId,
|
||||
savedStation.stationId,
|
||||
{
|
||||
has93Octane: true,
|
||||
has93OctaneEthanolFree: true
|
||||
}
|
||||
);
|
||||
expect(result.station).toEqual(station);
|
||||
});
|
||||
|
||||
it('should throw if saved station not found', async () => {
|
||||
mockRepository.updateSavedStation.mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
service.updateSavedStation('unknown', mockUserId, { nickname: 'Update' })
|
||||
).rejects.toThrow('Saved station not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeSavedStation', () => {
|
||||
it('should delete a saved station', async () => {
|
||||
const savedStation = mockSavedStations[0];
|
||||
|
||||
Reference in New Issue
Block a user