parent
2a531a19fe
commit
e163be6cf0
@ -0,0 +1,15 @@ |
|||||||
|
import { GeocodingProvider } from './clients/geocoding/types'; |
||||||
|
import { WeatherProvider } from './clients/weather/types'; |
||||||
|
import { PackagesRepository } from './storage/types'; |
||||||
|
|
||||||
|
export const packagesRepositoryDependency = Symbol('packages repository'); |
||||||
|
export const geocodingProviderDependency = Symbol('geocoding provider'); |
||||||
|
export const weatherProviderDependency = Symbol('weather provider'); |
||||||
|
|
||||||
|
export type Dependencies = { |
||||||
|
[packagesRepositoryDependency]: PackagesRepository; |
||||||
|
[geocodingProviderDependency]: GeocodingProvider; |
||||||
|
[weatherProviderDependency]: WeatherProvider; |
||||||
|
}; |
||||||
|
|
||||||
|
export type Dependency<T extends keyof Dependencies> = Dependencies[T]; |
@ -0,0 +1,176 @@ |
|||||||
|
import { GeocodingProvider } from './clients/geocoding/types'; |
||||||
|
import { WeatherProvider } from './clients/weather/types'; |
||||||
|
import { PackagesService } from './packages.service'; |
||||||
|
import { PackageInfo, PackagesRepository } from './storage/types'; |
||||||
|
|
||||||
|
describe('PackagesService', () => { |
||||||
|
describe('with mock providers', () => { |
||||||
|
const defaultPackage = { |
||||||
|
articleName: 'fake name', |
||||||
|
articlePrice: 123, |
||||||
|
articleQuantity: 456, |
||||||
|
senderAddress: 'fake address', |
||||||
|
SKU: 'fake sku', |
||||||
|
status: 'delivery', |
||||||
|
} satisfies Partial<PackageInfo>; |
||||||
|
const packageCold: PackageInfo = { |
||||||
|
...defaultPackage, |
||||||
|
carrier: 'DHL', |
||||||
|
trackingNumber: 'package-cold', |
||||||
|
receiverAddress: 'Olympic stadium, Esperanza City', |
||||||
|
}; |
||||||
|
const packageWarm: PackageInfo = { |
||||||
|
...defaultPackage, |
||||||
|
carrier: 'DHL', |
||||||
|
trackingNumber: 'package-warm', |
||||||
|
receiverAddress: 'Mitsubishi headquarters, Tōgenkyō', |
||||||
|
}; |
||||||
|
const packageNoGeolocation: PackageInfo = { |
||||||
|
...defaultPackage, |
||||||
|
carrier: 'GLS', |
||||||
|
trackingNumber: 'package-no-geolocation', |
||||||
|
receiverAddress: 'Perhaps the stars', |
||||||
|
}; |
||||||
|
const packageNoWeather: PackageInfo = { |
||||||
|
...defaultPackage, |
||||||
|
carrier: 'GLS', |
||||||
|
trackingNumber: 'package-no-weather', |
||||||
|
receiverAddress: |
||||||
|
'Headquarters of the United Nations, Lagos, Nigeria', |
||||||
|
}; |
||||||
|
const packages = [ |
||||||
|
packageCold, |
||||||
|
packageWarm, |
||||||
|
packageNoGeolocation, |
||||||
|
packageNoWeather, |
||||||
|
]; |
||||||
|
const packagesRepository: PackagesRepository = { |
||||||
|
getPackage: (requestedCarrier, requestedTrackingNumber) => { |
||||||
|
const foundPackage = packages.find( |
||||||
|
({ carrier, trackingNumber }) => |
||||||
|
carrier === requestedCarrier && |
||||||
|
trackingNumber === requestedTrackingNumber, |
||||||
|
); |
||||||
|
if (!foundPackage) { |
||||||
|
return Promise.resolve({ found: false }); |
||||||
|
} |
||||||
|
|
||||||
|
return Promise.resolve({ |
||||||
|
found: true, |
||||||
|
value: foundPackage, |
||||||
|
}); |
||||||
|
}, |
||||||
|
}; |
||||||
|
const geocodingProvider: GeocodingProvider = { |
||||||
|
geocode: (query) => { |
||||||
|
switch (query) { |
||||||
|
case packageCold.receiverAddress: |
||||||
|
return Promise.resolve({ |
||||||
|
latitude: -63.4, |
||||||
|
longitude: -57, |
||||||
|
}); |
||||||
|
case packageWarm.receiverAddress: |
||||||
|
return Promise.resolve({ latitude: 0, longitude: 107 }); |
||||||
|
case packageNoWeather.receiverAddress: |
||||||
|
return Promise.resolve({ |
||||||
|
latitude: 6.5, |
||||||
|
longitude: 3.4, |
||||||
|
}); |
||||||
|
default: |
||||||
|
throw new Error('unknown address'); |
||||||
|
} |
||||||
|
}, |
||||||
|
}; |
||||||
|
const weatherCold = { |
||||||
|
apparentTemperature: '0°C', |
||||||
|
temperature: '-52°C', |
||||||
|
relativeHumidity: '30%', |
||||||
|
}; |
||||||
|
const weatherWarm = { |
||||||
|
apparentTemperature: '35°C', |
||||||
|
temperature: '30°C', |
||||||
|
relativeHumidity: '80%', |
||||||
|
}; |
||||||
|
const weatherProvider: WeatherProvider = { |
||||||
|
getCurrentWeather: ({ longitude, latitude }) => { |
||||||
|
if (latitude < -60) { |
||||||
|
return Promise.resolve(weatherCold); |
||||||
|
} |
||||||
|
|
||||||
|
if (latitude === 0 && longitude > 100) { |
||||||
|
return Promise.resolve(weatherWarm); |
||||||
|
} |
||||||
|
|
||||||
|
throw new Error('No weather data'); |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
const packagesService = new PackagesService( |
||||||
|
packagesRepository, |
||||||
|
geocodingProvider, |
||||||
|
weatherProvider, |
||||||
|
); |
||||||
|
|
||||||
|
it('returns weather for packages with available weather', async () => { |
||||||
|
expect( |
||||||
|
await packagesService.getPackageInfoWithWeather( |
||||||
|
packageCold.carrier, |
||||||
|
packageCold.trackingNumber, |
||||||
|
), |
||||||
|
).toEqual({ |
||||||
|
packageData: packageCold, |
||||||
|
receiverWeather: weatherCold, |
||||||
|
}); |
||||||
|
|
||||||
|
expect( |
||||||
|
await packagesService.getPackageInfoWithWeather( |
||||||
|
packageWarm.carrier, |
||||||
|
packageWarm.trackingNumber, |
||||||
|
), |
||||||
|
).toEqual({ |
||||||
|
packageData: packageWarm, |
||||||
|
receiverWeather: weatherWarm, |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
it('does not return weather for packages with unavailable weather', async () => { |
||||||
|
expect( |
||||||
|
await packagesService.getPackageInfoWithWeather( |
||||||
|
packageNoWeather.carrier, |
||||||
|
packageNoWeather.trackingNumber, |
||||||
|
), |
||||||
|
).toEqual({ |
||||||
|
packageData: packageNoWeather, |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
it('does not return weather for packages with unresolvable address', async () => { |
||||||
|
expect( |
||||||
|
await packagesService.getPackageInfoWithWeather( |
||||||
|
packageNoGeolocation.carrier, |
||||||
|
packageNoGeolocation.trackingNumber, |
||||||
|
), |
||||||
|
).toEqual({ |
||||||
|
packageData: packageNoGeolocation, |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
it('returns null for non-existent packages', async () => { |
||||||
|
expect( |
||||||
|
await packagesService.getPackageInfoWithWeather( |
||||||
|
'DHL', |
||||||
|
'non-existent', |
||||||
|
), |
||||||
|
).toBe(null); |
||||||
|
}); |
||||||
|
|
||||||
|
it('returns null for packages with wrong carrier name', async () => { |
||||||
|
expect( |
||||||
|
await packagesService.getPackageInfoWithWeather( |
||||||
|
'UPS', |
||||||
|
packageCold.trackingNumber, |
||||||
|
), |
||||||
|
).toBe(null); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,50 @@ |
|||||||
|
import { Inject, Injectable } from '@nestjs/common'; |
||||||
|
import { |
||||||
|
Dependencies, |
||||||
|
geocodingProviderDependency, |
||||||
|
packagesRepositoryDependency, |
||||||
|
weatherProviderDependency, |
||||||
|
} from './dependencies'; |
||||||
|
import { CarrierType } from './types'; |
||||||
|
|
||||||
|
@Injectable() |
||||||
|
export class PackagesService { |
||||||
|
constructor( |
||||||
|
@Inject(packagesRepositoryDependency) |
||||||
|
private readonly packagesRepository: Dependencies[typeof packagesRepositoryDependency], |
||||||
|
@Inject(geocodingProviderDependency) |
||||||
|
private readonly geocodingProvider: Dependencies[typeof geocodingProviderDependency], |
||||||
|
@Inject(weatherProviderDependency) |
||||||
|
private readonly weatherProvider: Dependencies[typeof weatherProviderDependency], |
||||||
|
) {} |
||||||
|
|
||||||
|
async getPackageInfoWithWeather( |
||||||
|
carrier: CarrierType, |
||||||
|
trackingNumber: string, |
||||||
|
) { |
||||||
|
const packageDataResponse = await this.packagesRepository.getPackage( |
||||||
|
carrier, |
||||||
|
trackingNumber, |
||||||
|
); |
||||||
|
|
||||||
|
if (!packageDataResponse.found) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
const packageData = packageDataResponse.value; |
||||||
|
|
||||||
|
try { |
||||||
|
const receiverLocation = await this.geocodingProvider.geocode( |
||||||
|
packageData.receiverAddress, |
||||||
|
); |
||||||
|
const receiverWeather = |
||||||
|
await this.weatherProvider.getCurrentWeather(receiverLocation); |
||||||
|
return { |
||||||
|
packageData, |
||||||
|
receiverWeather, |
||||||
|
}; |
||||||
|
} catch { |
||||||
|
return { packageData }; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue