Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raise test coverage significantly #14

Merged
merged 3 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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": "0.0.20",
"version": "0.1.0",
"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
9 changes: 9 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import WeatherPlus from './index';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { IWeatherData } from './interfaces';
import { InvalidProviderLocationError } from './errors';

jest.mock('redis', () => {
const mGet = jest.fn();
Expand Down Expand Up @@ -120,4 +121,12 @@ describe('WeatherPlus Library', () => {
}
expect(response).toEqual(expectedResponse);
});

// Add this test case
it('should export InvalidProviderLocationError', () => {
expect(InvalidProviderLocationError).toBeDefined();
const error = new InvalidProviderLocationError('Test error');
expect(error).toBeInstanceOf(Error);
expect(error.message).toBe('Test error');
});
});
88 changes: 78 additions & 10 deletions src/providers/nws/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,31 @@ describe('NWSProvider', () => {
mock.restore();
});

it('should fetch and convert weather data from NWS API', async () => {
// Mock fetchObservationStationUrl
const mockObservationStationUrl = () => {
mock
.onGet(`https://api.weather.gov/points/${latInUS},${lngInUS}`)
.reply(200, {
properties: {
observationStations: 'https://api.weather.gov/gridpoints/XYZ/123,456/stations',
},
});
};

const mockObservationData = {
properties: {
dewpoint: { value: 10, unitCode: 'wmoUnit:degC' },
relativeHumidity: { value: 80 },
temperature: { value: 20, unitCode: 'wmoUnit:degC' },
textDescription: 'Clear',
},
};

const mockLatestObservation = (stationId: string, data = mockObservationData) => {
mock.onGet(`${stationId}/observations/latest`).reply(200, data);
};

it('should fetch and convert weather data from NWS API', async () => {
mockObservationStationUrl();

// Mock fetchNearbyStations
mock
Expand All @@ -40,14 +56,7 @@ describe('NWSProvider', () => {
});

// Mock fetchLatestObservation
mock.onGet('station123/observations/latest').reply(200, {
properties: {
dewpoint: { value: 10, unitCode: 'wmoUnit:degC' },
relativeHumidity: { value: 80 },
temperature: { value: 20, unitCode: 'wmoUnit:degC' },
textDescription: 'Clear',
},
});
mockLatestObservation('station123');

const weatherData = await provider.getWeather(latInUS, lngInUS);

Expand All @@ -66,4 +75,63 @@ describe('NWSProvider', () => {
});

// Add additional tests as needed

// Add this test case
it('should throw an error if no observation stations are found', async () => {
mockObservationStationUrl();

// Mock fetchNearbyStations with empty features array
mock
.onGet('https://api.weather.gov/gridpoints/XYZ/123,456/stations')
.reply(200, {
features: [],
});

await expect(provider.getWeather(latInUS, lngInUS)).rejects.toThrow('No stations found');
});

// Add this test case
it('should throw an error on invalid observation data', async () => {
mockObservationStationUrl();

// Mock fetchNearbyStations
mock
.onGet('https://api.weather.gov/gridpoints/XYZ/123,456/stations')
.reply(200, {
features: [{ id: 'station123' }],
});

// Mock fetchLatestObservation with invalid data
mock.onGet('station123/observations/latest').reply(200, {
properties: {} // Empty properties object to simulate invalid data
});

await expect(provider.getWeather(latInUS, lngInUS)).rejects.toThrow('Invalid observation data');
});

// Add this test case
it('should skip stations if fetching data from a station fails', async () => {
mockObservationStationUrl();

// Mock fetchNearbyStations with multiple stations
mock
.onGet('https://api.weather.gov/gridpoints/XYZ/123,456/stations')
.reply(200, {
features: [{ id: 'station123' }, { id: 'station456' }],
});

// Mock fetchLatestObservation to fail for the first station
mock.onGet('station123/observations/latest').reply(500);
// Mock fetchLatestObservation to succeed for the second station
mockLatestObservation('station456');

const weatherData = await provider.getWeather(latInUS, lngInUS);

expect(weatherData).toEqual({
dewPoint: { value: 10, unit: 'C' },
humidity: { value: 80, unit: 'percent' },
temperature: { value: 20, unit: 'C' },
conditions: { value: 'Clear', unit: 'string' },
});
});
});
8 changes: 8 additions & 0 deletions src/providers/nws/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export class NWSProvider implements IWeatherProvider {
) && stations.length > 0
);

if (weatherData.length === 0) {
throw new Error('Invalid observation data');
}

for (const key of WEATHER_KEYS) {
const value = weatherData.find((data) => data[key]);

Expand All @@ -68,6 +72,10 @@ export class NWSProvider implements IWeatherProvider {
}
}

if (Object.keys(data).length === 0) {
throw new Error('Invalid observation data');
}

return data as IWeatherData;
} catch (error) {
log('Error in getWeather:', error);
Expand Down
16 changes: 16 additions & 0 deletions src/providers/openweather/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,20 @@ describe('OpenWeatherProvider', () => {
conditions: { value: 'clear sky', unit: 'string' },
});
});

// Add this test case
it('should throw an error if no API key is provided', () => {
expect(() => {
new OpenWeatherProvider('');
}).toThrow('OpenWeather provider requires an API key.');
});

// Add this test case
it('should handle errors from the OpenWeather API', async () => {
mock
.onGet('https://api.openweathermap.org/data/3.0/onecall')
.reply(500);

await expect(provider.getWeather(lat, lng)).rejects.toThrow();
});
});
10 changes: 10 additions & 0 deletions src/providers/providerFactory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ProviderFactory } from './providerFactory';
import { ProviderNotSupportedError } from '../errors';

describe('ProviderFactory', () => {
it('should throw ProviderNotSupportedError for unsupported providers', () => {
expect(() => {
ProviderFactory.createProvider('unsupportedProvider');
}).toThrow(ProviderNotSupportedError);
});
});
12 changes: 12 additions & 0 deletions src/weatherService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,16 @@ describe('WeatherService', () => {
expect(cacheGetMock).toHaveBeenCalled();
expect(cacheSetMock).not.toHaveBeenCalled();
});

// Add this test case
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['provider'], 'getWeather').mockRejectedValue(genericError);

const lat = 38.8977; // Valid US location
const lng = -77.0365;

await expect(weatherService.getWeather(lat, lng)).rejects.toThrow('Generic provider error');
});
});
Loading