Skip to content

Commit

Permalink
Merge pull request #17 from TextureHQ/victor/add-provider-name
Browse files Browse the repository at this point in the history
Add provider name
  • Loading branch information
victorquinn authored Oct 10, 2024
2 parents 386922d + e171092 commit eb95052
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 12 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "weather-plus",
"version": "1.0.0",
"version": "1.0.1",
"description": "Weather Plus is a powerful wrapper around various Weather APIs that simplifies adding weather data to your application",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
Expand Down
23 changes: 23 additions & 0 deletions src/cache.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Cache } from './cache';
import { RedisClientType } from 'redis';
import { IWeatherUnits } from './interfaces';

describe('Cache', () => {
let cache: Cache;
Expand Down Expand Up @@ -199,5 +200,27 @@ describe('Cache', () => {

jest.useRealTimers();
});

test('Cache stores and retrieves weather data including provider', async () => {
// Arrange
const cache = new Cache();
const key = 'geohash_key';
const weatherData = {
dewPoint: { value: 8, unit: IWeatherUnits.C },
humidity: { value: 65, unit: IWeatherUnits.percent },
temperature: { value: 16, unit: IWeatherUnits.C },
conditions: { value: 'Rainy', unit: IWeatherUnits.string },
provider: 'nws',
};

// Act
await cache.set(key, JSON.stringify(weatherData));
const cachedData = await cache.get(key);

// Assert
expect(cachedData).not.toBeNull();
expect(JSON.parse(cachedData!)).toEqual(weatherData);
expect(JSON.parse(cachedData!).provider).toBe('nws'); // Verify provider name
});
});
});
1 change: 1 addition & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ describe('WeatherPlus Library', () => {
const weatherPlus = new WeatherPlus();
const response = await weatherPlus.getWeather(lat, lng);
const expectedResponse: IWeatherData = {
provider: 'nws',
dewPoint: {
value: 20,
unit: 'C',
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export interface IWeatherData {
[IWeatherKey.humidity]: IRelativeHumidity;
[IWeatherKey.temperature]: ITemperature;
[IWeatherKey.conditions]: IConditions;
provider: string;
}

export type IBaseWeatherProperty<T, U extends IWeatherUnits> = {
value: T;
unit: U | keyof typeof IWeatherUnits;
Expand Down
2 changes: 2 additions & 0 deletions src/providers/nws/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ describe('NWSProvider', () => {
humidity: { value: 80, unit: 'percent' },
temperature: { value: 20, unit: 'C' },
conditions: { value: 'Clear', unit: 'string' },
provider: 'nws',
});
});

Expand Down Expand Up @@ -132,6 +133,7 @@ describe('NWSProvider', () => {
humidity: { value: 80, unit: 'percent' },
temperature: { value: 20, unit: 'C' },
conditions: { value: 'Clear', unit: 'string' },
provider: 'nws',
});
});
});
2 changes: 2 additions & 0 deletions src/providers/nws/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export class NWSProvider implements IWeatherProvider {
if (Object.keys(data).length === 0) {
throw new Error('Invalid observation data');
}
data.provider = 'nws';

return data as IWeatherData;
} catch (error) {
Expand Down Expand Up @@ -152,6 +153,7 @@ async function fetchLatestObservation(
function convertToWeatherData(observation: any): IWeatherData {
const properties = observation.properties;
return {
provider: 'nws',
dewPoint: {
value: properties.dewpoint.value,
unit:
Expand Down
1 change: 1 addition & 0 deletions src/providers/openweather/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ describe('OpenWeatherProvider', () => {
humidity: { value: 80, unit: 'percent' },
temperature: { value: 20, unit: 'C' },
conditions: { value: 'clear sky', unit: 'string' },
provider: 'openweather',
});
});

Expand Down
1 change: 1 addition & 0 deletions src/providers/openweather/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class OpenWeatherProvider implements IWeatherProvider {

function convertToWeatherData(data: IOpenWeatherResponse): IWeatherData {
return {
provider: 'openweather',
dewPoint: {
value: data.current.dew_point,
unit: IWeatherUnits.C,
Expand Down
97 changes: 86 additions & 11 deletions src/weatherService.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { WeatherService } from './weatherService';
import { InvalidProviderLocationError } from './errors';
import { NWSProvider } from './providers/nws/client';
import { OpenWeatherProvider } from './providers/openweather/client';
import { IWeatherUnits, IWeatherData } from './interfaces';
import { IWeatherProvider } from './providers/IWeatherProvider';

jest.mock('./cache', () => {
return {
Expand Down Expand Up @@ -30,7 +30,13 @@ jest.mock('./providers/nws/client', () => {
'The NWS provider only supports locations within the United States.'
);
}
return { provider: 'nws', lat, lng, weather: 'sunny' };
return {
dewPoint: { value: 10, unit: IWeatherUnits.C },
humidity: { value: 80, unit: IWeatherUnits.percent },
temperature: { value: 15, unit: IWeatherUnits.C },
conditions: { value: 'Sunny', unit: IWeatherUnits.string },
provider: 'nws',
} as IWeatherData;
}
}

Expand All @@ -46,7 +52,13 @@ jest.mock('./providers/openweather/client', () => {

class MockOpenWeatherProvider extends originalModule.OpenWeatherProvider {
async getWeather(lat: number, lng: number) {
return { provider: 'openweather', lat, lng, weather: 'cloudy' };
return {
dewPoint: { value: 12, unit: IWeatherUnits.C },
humidity: { value: 70, unit: IWeatherUnits.percent },
temperature: { value: 18, unit: IWeatherUnits.C },
conditions: { value: 'Cloudy', unit: IWeatherUnits.string },
provider: 'openweather',
} as IWeatherData;
}
}

Expand All @@ -62,16 +74,26 @@ describe('WeatherService', () => {

beforeEach(() => {
jest.clearAllMocks();
weatherService = new WeatherService({ providers: ['nws'] });
weatherService = new WeatherService({
providers: ['nws'],
});
});

it('should return weather data for a location inside the United States', async () => {
const lat = 38.8977; // Washington, D.C.
const lng = -77.0365;

const expectedWeatherData: IWeatherData = {
dewPoint: { value: 10, unit: IWeatherUnits.C },
humidity: { value: 80, unit: IWeatherUnits.percent },
temperature: { value: 15, unit: IWeatherUnits.C },
conditions: { value: 'Sunny', unit: IWeatherUnits.string },
provider: 'nws',
};

const weather = await weatherService.getWeather(lat, lng);

expect(weather).toEqual({ provider: 'nws', lat, lng, weather: 'sunny' });
expect(weather).toEqual(expectedWeatherData);
});

it('should fallback to the next provider if the first provider does not support the location', async () => {
Expand All @@ -81,18 +103,28 @@ describe('WeatherService', () => {
openweather: 'your-openweather-api-key',
},
});

const lat = 51.5074; // London, UK
const lng = -0.1278;

const expectedWeatherData: IWeatherData = {
dewPoint: { value: 12, unit: IWeatherUnits.C },
humidity: { value: 70, unit: IWeatherUnits.percent },
temperature: { value: 18, unit: IWeatherUnits.C },
conditions: { value: 'Cloudy', unit: IWeatherUnits.string },
provider: 'openweather',
};

const weather = await weatherService.getWeather(lat, lng);

expect(weather).toEqual({ provider: 'openweather', lat, lng, weather: 'cloudy' });
expect(weather).toEqual(expectedWeatherData);
});

it('should throw InvalidProviderLocationError if no provider supports the location', async () => {
weatherService = new WeatherService({
providers: ['nws'],
});

const lat = 51.5074; // London, UK
const lng = -0.1278;

Expand All @@ -103,17 +135,25 @@ describe('WeatherService', () => {

it('should handle invalid latitude or longitude', async () => {
const lat = 100; // Invalid latitude
const lng = 200;
const lng = 200; // Invalid longitude

await expect(weatherService.getWeather(lat, lng)).rejects.toThrow(
'Invalid latitude or longitude'
);
});

it('should use cached weather data if available', async () => {
const cachedWeatherData = {
dewPoint: { value: 11, unit: IWeatherUnits.C },
humidity: { value: 75, unit: IWeatherUnits.percent },
temperature: { value: 16, unit: IWeatherUnits.C },
conditions: { value: 'Overcast', unit: IWeatherUnits.string },
provider: 'nws',
};

const cacheGetMock = jest
.fn()
.mockResolvedValue(JSON.stringify({ cached: true }));
.mockResolvedValue(JSON.stringify(cachedWeatherData));
const cacheSetMock = jest.fn();

// Replace cache methods with mocks
Expand All @@ -125,13 +165,14 @@ describe('WeatherService', () => {

const weather = await weatherService.getWeather(lat, lng);

expect(weather).toEqual({ cached: true });
expect(weather).toEqual(cachedWeatherData);
expect(cacheGetMock).toHaveBeenCalled();
expect(cacheSetMock).not.toHaveBeenCalled();
});

it('should rethrow generic errors from provider.getWeather', async () => {
const genericError = new Error('Generic provider error');

// Mock the provider's getWeather to throw a generic error
jest
.spyOn(weatherService['providers'][0], 'getWeather')
Expand Down Expand Up @@ -189,7 +230,6 @@ describe('WeatherService', () => {
});

it('should throw an error if an unsupported provider is specified', () => {
// Use type assertion to bypass TypeScript error for testing purposes
expect(() => {
new WeatherService({ providers: ['unsupportedProvider' as any] });
}).toThrow('Provider unsupportedProvider is not supported yet');
Expand All @@ -200,4 +240,39 @@ describe('WeatherService', () => {
new WeatherService({ providers: ['openweather'] });
}).toThrow('OpenWeather provider requires an API key.');
});

it('should verify that the provider name is included in the weather data', async () => {
// Arrange
const lat = 37.7749; // San Francisco
const lng = -122.4194;

const mockWeatherData: IWeatherData = {
dewPoint: { value: 12, unit: IWeatherUnits.C },
humidity: { value: 70, unit: IWeatherUnits.percent },
temperature: { value: 18, unit: IWeatherUnits.C },
conditions: { value: 'Partly Cloudy', unit: IWeatherUnits.string },
provider: 'openweather',
};

const mockProvider: IWeatherProvider = {
name: 'openweather',
getWeather: jest.fn().mockResolvedValue(mockWeatherData),
};

const weatherService = new WeatherService({
providers: ['openweather'],
apiKeys: { openweather: 'test-api-key' },
});

// Inject mock provider
(weatherService as any).providers = [mockProvider];

// Act
const result = await weatherService.getWeather(lat, lng);

// Assert
expect(mockProvider.getWeather).toHaveBeenCalledWith(lat, lng);
expect(result).toEqual(mockWeatherData);
expect(result.provider).toBe('openweather'); // Verify provider name
});
});

0 comments on commit eb95052

Please sign in to comment.