noaa_weather_client 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +1 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +7 -0
  7. data/.yardopts +2 -0
  8. data/Gemfile +8 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +107 -0
  11. data/Rakefile +8 -0
  12. data/bin/noaa_weather_client +43 -0
  13. data/data/xml/current_observation.xsd +79 -0
  14. data/data/xml/dwml.xsd +97 -0
  15. data/data/xml/location.xsd +142 -0
  16. data/data/xml/meta_data.xsd +100 -0
  17. data/data/xml/moreWeatherInformation.xsd +23 -0
  18. data/data/xml/ndfd_data.xsd +43 -0
  19. data/data/xml/parameters.xsd +1173 -0
  20. data/data/xml/summarizationType.xsd +29 -0
  21. data/data/xml/time_layout.xsd +51 -0
  22. data/lib/noaa_weather_client.rb +9 -0
  23. data/lib/noaa_weather_client/cli.rb +53 -0
  24. data/lib/noaa_weather_client/cli/templates.rb +53 -0
  25. data/lib/noaa_weather_client/client.rb +61 -0
  26. data/lib/noaa_weather_client/errors.rb +7 -0
  27. data/lib/noaa_weather_client/responses/current_observation.rb +93 -0
  28. data/lib/noaa_weather_client/responses/forecast.rb +84 -0
  29. data/lib/noaa_weather_client/responses/generic_response.rb +9 -0
  30. data/lib/noaa_weather_client/responses/lat_lon_list.rb +25 -0
  31. data/lib/noaa_weather_client/responses/reactive_xml_response.rb +29 -0
  32. data/lib/noaa_weather_client/responses/station.rb +28 -0
  33. data/lib/noaa_weather_client/responses/stations.rb +41 -0
  34. data/lib/noaa_weather_client/responses/validatable_xml_response.rb +22 -0
  35. data/lib/noaa_weather_client/rest_client_factory.rb +12 -0
  36. data/lib/noaa_weather_client/services/calculate_distance_between_lat_lon.rb +20 -0
  37. data/lib/noaa_weather_client/services/current_observations.rb +32 -0
  38. data/lib/noaa_weather_client/services/find_nearest_station.rb +16 -0
  39. data/lib/noaa_weather_client/services/forecast_by_day.rb +52 -0
  40. data/lib/noaa_weather_client/services/postal_code_to_coordinate.rb +36 -0
  41. data/lib/noaa_weather_client/services/rest_service.rb +28 -0
  42. data/lib/noaa_weather_client/services/soap_service.rb +16 -0
  43. data/lib/noaa_weather_client/services/weather_stations.rb +32 -0
  44. data/lib/noaa_weather_client/soap_client_factory.rb +17 -0
  45. data/lib/noaa_weather_client/station_filters.rb +8 -0
  46. data/lib/noaa_weather_client/version.rb +3 -0
  47. data/lib/noaa_weather_client/xml_parser_factory.rb +9 -0
  48. data/noaa_weather_client.gemspec +27 -0
  49. data/spec/fixtures/vcr_cassettes/current_observations.yml +25890 -0
  50. data/spec/fixtures/vcr_cassettes/forecast_by_day_3.yml +772 -0
  51. data/spec/fixtures/vcr_cassettes/forecast_by_day_7.yml +829 -0
  52. data/spec/fixtures/vcr_cassettes/nearest_weather_station.yml +25842 -0
  53. data/spec/fixtures/vcr_cassettes/postal_code_to_coordinate.yml +75 -0
  54. data/spec/fixtures/vcr_cassettes/weather_stations.yml +25842 -0
  55. data/spec/fixtures/xml/forecast.xml +144 -0
  56. data/spec/lib/noaa_client/client_spec.rb +93 -0
  57. data/spec/lib/noaa_client/responses/current_observation_spec.rb +122 -0
  58. data/spec/lib/noaa_client/responses/forecast_spec.rb +66 -0
  59. data/spec/lib/noaa_client/responses/lat_lon_list_spec.rb +30 -0
  60. data/spec/lib/noaa_client/responses/station_spec.rb +53 -0
  61. data/spec/lib/noaa_client/responses/stations_spec.rb +86 -0
  62. data/spec/lib/noaa_client/rest_client_factory_spec.rb +15 -0
  63. data/spec/lib/noaa_client/services/calculate_distance_between_lat_lon_spec.rb +16 -0
  64. data/spec/lib/noaa_client/services/current_observations_spec.rb +47 -0
  65. data/spec/lib/noaa_client/services/find_nearest_station_spec.rb +36 -0
  66. data/spec/lib/noaa_client/services/forecast_by_day_spec.rb +62 -0
  67. data/spec/lib/noaa_client/services/postal_code_to_coordinate_spec.rb +41 -0
  68. data/spec/lib/noaa_client/services/rest_service_spec.rb +45 -0
  69. data/spec/lib/noaa_client/services/soap_service_spec.rb +56 -0
  70. data/spec/lib/noaa_client/services/weather_stations_spec.rb +40 -0
  71. data/spec/lib/noaa_client/soap_client_factory_spec.rb +13 -0
  72. data/spec/lib/noaa_client/xml_parser_factory_spec.rb +14 -0
  73. data/spec/spec_helper.rb +31 -0
  74. metadata +228 -0
@@ -0,0 +1,53 @@
1
+ require 'nokogiri'
2
+ require_relative '../../../spec_helper'
3
+ require_relative '../../../../lib/noaa_weather_client/responses/station'
4
+
5
+ module NoaaWeatherClient
6
+ module Responses
7
+ describe Station do
8
+ let(:source) {
9
+ <<-xml
10
+ <station>
11
+ <station_id>TAPA</station_id>
12
+ <state>AG</state>
13
+ <station_name>Vc Bird Intl Airport Antigua</station_name>
14
+ <latitude>17.117</latitude>
15
+ <longitude>-61.783</longitude>
16
+ <html_url>http://weather.noaa.gov/weather/current/TAPA.html</html_url>
17
+ <rss_url>http://weather.gov/xml/current_obs/TAPA.rss</rss_url>
18
+ <xml_url>http://weather.gov/xml/current_obs/TAPA.xml</xml_url>
19
+ </station>
20
+ xml
21
+ }
22
+ let(:station) { Station.new Nokogiri::XML.parse(source) }
23
+
24
+ it "accepts an attributes hash" do
25
+ station
26
+ end
27
+
28
+ it "exposes station id" do
29
+ expect(station.station_id).to eq('TAPA')
30
+ end
31
+
32
+ it "exposes station name" do
33
+ expect(station.station_name).to eq('Vc Bird Intl Airport Antigua')
34
+ end
35
+
36
+ it "exposes state" do
37
+ expect(station.state).to eq('AG')
38
+ end
39
+
40
+ it "exposes latitude" do
41
+ expect(station.latitude).to eq(17.117)
42
+ end
43
+
44
+ it "exposes longitude" do
45
+ expect(station.longitude).to eq(-61.783)
46
+ end
47
+
48
+ it "exposes xml url" do
49
+ expect(station.xml_url).to eq('http://w1.weather.gov/xml/current_obs/TAPA.xml')
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,86 @@
1
+ require 'nokogiri'
2
+ require_relative '../../../spec_helper'
3
+ require_relative '../../../../lib/noaa_weather_client/responses/stations'
4
+
5
+ module NoaaWeatherClient
6
+ module Responses
7
+ describe Stations do
8
+ let(:fake_response) { STATIONS_XML }
9
+ let(:stations) { Stations.new fake_response }
10
+
11
+ it "requires a response" do
12
+ stations
13
+ expect { Stations.new }.to raise_error(ArgumentError)
14
+ end
15
+
16
+ it "exposes a list of stations via enumerable" do
17
+ count = 0
18
+ expect { stations.each { |d| count += 1 } }.to change { count }.by(2)
19
+ end
20
+
21
+ it "passes station xml to the station class" do
22
+ mock_station_class = double(new: nil)
23
+ first, last = Nokogiri::XML.parse(fake_response).css('station')
24
+ expect(mock_station_class).to receive(:new).with { |station|
25
+ station.to_xml == first.to_xml
26
+ }
27
+ expect(mock_station_class).to receive(:new).with { |station|
28
+ station.to_xml == last.to_xml
29
+ }
30
+ Stations.new(fake_response, station_class: mock_station_class).each { |s| }
31
+ end
32
+
33
+ it "allows fetching of stations via array#fetch" do
34
+ expect(stations.fetch(0).station_id).to eq('TAPA')
35
+ end
36
+
37
+ it "allows fetching of stations via array#[]" do
38
+ expect(stations[0].station_id).to eq('TAPA')
39
+ end
40
+
41
+ it "exposes number of stations via size" do
42
+ expect(stations.size).to eq(2)
43
+ end
44
+
45
+ it "exposes source xml" do
46
+ expect(stations.to_xml).to eq(Nokogiri::XML.parse(fake_response).to_xml)
47
+ end
48
+
49
+ STATIONS_XML =<<-RESPONSE
50
+ <?xml version="1.0" encoding="UTF-8"?>
51
+ <wx_station_index>
52
+ <credit>NOAA's National Weather Service</credit>
53
+ <credit_URL>http://weather.gov/</credit_URL>
54
+ <image>
55
+ <url>http://weather.gov/images/xml_logo.gif</url>
56
+ <title>NOAA's National Weather Service</title>
57
+ <link>http://weather.gov</link>
58
+ </image>
59
+ <suggested_pickup>08:00 EST</suggested_pickup>
60
+ <suggested_pickup_period>1140</suggested_pickup_period>
61
+ <station>
62
+ <station_id>TAPA</station_id>
63
+ <state>AG</state>
64
+ <station_name>Vc Bird Intl Airport Antigua</station_name>
65
+ <latitude>17.117</latitude>
66
+ <longitude>-61.783</longitude>
67
+ <html_url>http://weather.noaa.gov/weather/current/TAPA.html</html_url>
68
+ <rss_url>http://weather.gov/xml/current_obs/TAPA.rss</rss_url>
69
+ <xml_url>http://weather.gov/xml/current_obs/TAPA.xml</xml_url>
70
+ </station>
71
+
72
+ <station>
73
+ <station_id>TKPN</station_id>
74
+ <state>AG</state>
75
+ <station_name>Charlestown/Newcast</station_name>
76
+ <latitude>17.2</latitude>
77
+ <longitude>-62.583</longitude>
78
+ <html_url>http://weather.noaa.gov/weather/current/TKPN.html</html_url>
79
+ <rss_url>http://weather.gov/xml/current_obs/TKPN.rss</rss_url>
80
+ <xml_url>http://weather.gov/xml/current_obs/TKPN.xml</xml_url>
81
+ </station>
82
+ </wx_station_index>
83
+ RESPONSE
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,15 @@
1
+ require 'uri'
2
+ require_relative '../../spec_helper'
3
+ require_relative '../../../lib/noaa_weather_client/rest_client_factory'
4
+
5
+ module NoaaWeatherClient
6
+ describe RestClientFactory do
7
+ it "builds a rest client with the provided url" do
8
+ mock_provider = double()
9
+ url = "http://www.google.com"
10
+ uri = URI(url)
11
+ expect(mock_provider).to receive(:new).with(uri.host, uri.port)
12
+ RestClientFactory.build_client provider: mock_provider, url: url
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ require_relative '../../../spec_helper'
2
+ require_relative '../../../../lib/noaa_weather_client/services/calculate_distance_between_lat_lon'
3
+
4
+ module NoaaWeatherClient
5
+ module Services
6
+ describe CalculateDistanceBetweenLatLon do
7
+ let(:springfield) { [ 37.1962, -93.2861 ] }
8
+ let(:kansas_city) { [ 39.1000, -94.5800 ] }
9
+
10
+ it "calculates the distance between two points" do
11
+ expect(CalculateDistanceBetweenLatLon.get_distance(*springfield, *kansas_city))
12
+ .to eq(240.02560432981815)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,47 @@
1
+ require_relative '../../../spec_helper'
2
+ require_relative '../../../../lib/noaa_weather_client/services/current_observations'
3
+
4
+ module NoaaWeatherClient
5
+ module Services
6
+ describe CurrentObservations do
7
+ let(:mock_station) { double(xml_url: 'http://w1.weather.gov/xml/current_obs/KSGF.xml') }
8
+ let(:current_observations) { CurrentObservations.new options }
9
+
10
+ it "accepts an options hash" do
11
+ CurrentObservations.new {}
12
+ end
13
+
14
+ context "#fetch" do
15
+ let(:options) { { rest_service: double(object_from_response: nil) } }
16
+
17
+ it "requires a station" do
18
+ current_observations.fetch mock_station, options
19
+ expect { current_observations.fetch }.to raise_error(ArgumentError)
20
+ end
21
+
22
+ it "accepts an options hash" do
23
+ current_observations.fetch mock_station, options
24
+ end
25
+
26
+ it "passes the stations xml url to the service" do
27
+ expect(options[:rest_service]).to receive(:object_from_response)
28
+ .with(anything, mock_station.xml_url, anything)
29
+ current_observations.fetch mock_station
30
+ end
31
+
32
+ it "passes the get action to the service" do
33
+ expect(options[:rest_service]).to receive(:object_from_response)
34
+ .with(:get, anything, anything)
35
+ current_observations.fetch mock_station
36
+ end
37
+
38
+ it "passes an optional response class to the service" do
39
+ options[:response_class] = :some_response_class
40
+ expect(options[:rest_service]).to receive(:object_from_response)
41
+ .with(anything, anything, hash_including(response_class: options[:response_class]))
42
+ current_observations.fetch mock_station
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,36 @@
1
+ require_relative '../../../spec_helper'
2
+ require_relative '../../../../lib/noaa_weather_client/services/find_nearest_station'
3
+
4
+ module NoaaWeatherClient
5
+ module Services
6
+ describe FindNearestStation do
7
+ let(:location) { double(latitude:39.1000, longitude: -94.5800) } #kansas city
8
+ let(:washington_dc) { double(station_name: 'Washington D.C.', latitude: 38.8951 , longitude: -77.0367) }
9
+ let(:detroit) { double(station_name: 'Detroit', latitude: 42.3314 , longitude: -83.0458) }
10
+ let(:stations) { [ washington_dc, detroit ] }
11
+
12
+ it "requires latitude, longitude, and a list of stations" do
13
+ FindNearestStation.find(location.latitude, location.longitude, stations)
14
+ expect { FindNearestStation.find }.to raise_error(ArgumentError)
15
+ expect { FindNearestStation.find location.latitude}.to raise_error(ArgumentError)
16
+ expect { FindNearestStation.find location.latitude, location.longitude}.to raise_error(ArgumentError)
17
+ end
18
+
19
+ it "accepts an options hash" do
20
+ FindNearestStation.find(location.latitude, location.longitude, stations, {})
21
+ end
22
+
23
+ it "accepts a callable filter for the stations" do
24
+ mock_filter = ->(station) { station == washington_dc ? true : false }
25
+ FindNearestStation.find(location.latitude, location.longitude, stations, { filter: mock_filter })
26
+ end
27
+
28
+ it "returns the closest of the provided stations" do
29
+ expect(
30
+ FindNearestStation.find(location.latitude, location.longitude, stations)
31
+ .station_name
32
+ ).to eq(detroit.station_name)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,62 @@
1
+ require_relative '../../../spec_helper'
2
+ require_relative '../../../../lib/noaa_weather_client/services/forecast_by_day'
3
+
4
+ module NoaaWeatherClient
5
+ module Services
6
+ describe ForecastByDay do
7
+ it "accepts an options hash" do
8
+ ForecastByDay.new {}
9
+ end
10
+
11
+ context "#fetch" do
12
+ let(:options) { { soap_service: double(object_from_response: nil) } }
13
+ let(:forecast) { ForecastByDay.new options }
14
+ let(:lat) { 37.1962 }
15
+ let(:lon) { -93.2861 }
16
+
17
+ it "requires lat and lon" do
18
+ forecast.fetch lat, lon
19
+ expect { forecast.fetch }.to raise_error(ArgumentError)
20
+ end
21
+
22
+ it "accepts an options hash" do
23
+ forecast.fetch lat, lon, {}
24
+ end
25
+
26
+ it "passes optional arguments to the service" do
27
+ args = { key: 'value' }
28
+ expect(options[:soap_service]).to receive(:object_from_response)
29
+ .with(anything, hash_including(key: 'value'), anything)
30
+ forecast.fetch lat, lon, args
31
+ end
32
+
33
+ it "passes lat as string to the service" do
34
+ expect(options[:soap_service]).to receive(:object_from_response)
35
+ .with(anything, hash_including(latitude: lat.to_s), anything)
36
+ forecast.fetch lat, lon
37
+ end
38
+
39
+ it "passes lon as string to the service" do
40
+ expect(options[:soap_service]).to receive(:object_from_response)
41
+ .with(anything, hash_including(longitude: lon.to_s), anything)
42
+ forecast.fetch lat, lon
43
+ end
44
+
45
+ it "passes the soap action to the service" do
46
+ expect(options[:soap_service]).to receive(:object_from_response)
47
+ .with(:ndf_dgen_by_day, anything, anything)
48
+ forecast.fetch lat, lon
49
+ end
50
+
51
+ it "passes an optional response class to the service" do
52
+ options[:response_class] = :some_response_class
53
+ expect(options[:soap_service]).to receive(:object_from_response)
54
+ .with(anything, anything, hash_including(response_class: options[:response_class]))
55
+
56
+ forecast = ForecastByDay.new options
57
+ forecast.fetch lat, lon
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,41 @@
1
+ require_relative '../../../spec_helper'
2
+ require_relative '../../../../lib/noaa_weather_client/services/postal_code_to_coordinate'
3
+
4
+ module NoaaWeatherClient
5
+ module Services
6
+ describe PostalCodeToCoordinate do
7
+ it "accepts an options hash" do
8
+ PostalCodeToCoordinate.new {}
9
+ end
10
+
11
+ context "#resolve" do
12
+ let(:options) { { rest_service: double(object_from_response: nil) } }
13
+ let(:zip) { 90210 }
14
+ let(:zip_lat_lon) { PostalCodeToCoordinate.new options }
15
+
16
+ it "accepts an options hash" do
17
+ zip_lat_lon.resolve options
18
+ end
19
+
20
+ it "passes action to the service" do
21
+ expect(options[:rest_service]).to receive(:object_from_response)
22
+ .with(:get, anything, anything)
23
+ zip_lat_lon.resolve zip
24
+ end
25
+
26
+ it "passes zip as string to the service" do
27
+ expect(options[:rest_service]).to receive(:object_from_response)
28
+ .with(anything, /#{zip}/, anything)
29
+ zip_lat_lon.resolve zip
30
+ end
31
+
32
+ it "passes an optional response class to the service" do
33
+ options[:response_class] = :some_response_class
34
+ expect(options[:rest_service]).to receive(:object_from_response)
35
+ .with(anything, anything, hash_including(response_class: options[:response_class]))
36
+ zip_lat_lon.resolve zip
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,45 @@
1
+ require_relative '../../../spec_helper'
2
+ require_relative '../../../../lib/noaa_weather_client/services/rest_service'
3
+
4
+ module NoaaWeatherClient
5
+ module Services
6
+ describe RestService do
7
+ let(:fake_response) { double(body: 'fake_response_body') }
8
+ let(:mock_client) { double(request: fake_response) }
9
+ let(:mock_response_class) { double(new: nil) }
10
+ let(:implementer) { Class.new { include RestService }.new }
11
+ let(:args) { [ :get, 'http://www.google.com', { client: mock_client } ] }
12
+
13
+ context "#object_from_response" do
14
+ it "requires an action and url" do
15
+ implementer.object_from_response :get, 'http://www.google.com', client: mock_client
16
+ expect { implementer.object_from_response }.to raise_error(ArgumentError)
17
+ end
18
+
19
+ it "accepts an options hash" do
20
+ implementer.object_from_response :get, 'http://www.google.com', client: mock_client
21
+ end
22
+
23
+ it "builds a request from the args" do
24
+ expect(implementer).to receive(:build_request_for_action).with(*args)
25
+ implementer.object_from_response(*args)
26
+ end
27
+
28
+ it "passes the rest response to the response class" do
29
+ expect(mock_client).to receive(:request).and_return(fake_response)
30
+ expect(mock_response_class).to receive(:new).with(fake_response.body)
31
+ action, url, opts = *args
32
+ implementer.object_from_response(action, url, opts.merge(response_class: mock_response_class))
33
+ end
34
+
35
+ it "returns a new wrapped response body" do
36
+ expect(mock_response_class).to receive(:new).with(fake_response.body).and_return(mock_response_class)
37
+ action, url, opts = *args
38
+ expect(
39
+ implementer.object_from_response(action, url, opts.merge(response_class: mock_response_class))
40
+ ).to eq(mock_response_class)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,56 @@
1
+ require_relative '../../../spec_helper'
2
+ require_relative '../../../../lib/noaa_weather_client/services/soap_service'
3
+
4
+ module NoaaWeatherClient
5
+ module Services
6
+ describe SoapService do
7
+ let(:fake_response) { double(body: 'fake_response_body') }
8
+ let(:mock_client) { double(call: fake_response) }
9
+ let(:mock_response_class) { double(new: nil) }
10
+ let(:implementer) { Class.new { include SoapService }.new }
11
+
12
+ context "#object_from_response" do
13
+ it "requires a soap action and message" do
14
+ implementer.object_from_response :soap_action, :message, client: mock_client
15
+ expect { implementer.object_from_response }.to raise_error(ArgumentError)
16
+ end
17
+
18
+ it "accepts an options hash" do
19
+ implementer.object_from_response :soap_action, :message, client: mock_client
20
+ end
21
+
22
+ it "calls the soap client with the soap action" do
23
+ expect(mock_client).to receive(:call).with(:soap_action, anything)
24
+ implementer.object_from_response :soap_action, :message, client: mock_client
25
+ end
26
+
27
+ it "calls the soap client with the message" do
28
+ expect(mock_client).to receive(:call).with(anything, hash_including(message: :message))
29
+ implementer.object_from_response :soap_action, :message, client: mock_client
30
+ end
31
+
32
+ it "passes the soap response to the response" do
33
+ expect(mock_client).to receive(:call).and_return(fake_response)
34
+ expect(mock_response_class).to receive(:new).with(fake_response.body)
35
+ implementer.object_from_response :soap_action, :message, client: mock_client, response_class: mock_response_class
36
+ end
37
+
38
+ it "returns a new wrapped response body" do
39
+ expect(mock_response_class).to receive(:new).with(fake_response.body).and_return(mock_response_class)
40
+ expect(implementer.object_from_response(:soap_action,
41
+ :message,
42
+ client: mock_client,
43
+ response_class: mock_response_class)).to eq(mock_response_class)
44
+ end
45
+
46
+ context "when a Savon:Error occurs" do
47
+ it "raises a CommunicationError" do
48
+ allow(mock_client).to receive(:call).and_raise(Savon::Error)
49
+ expect { implementer.object_from_response :soap_action, :message, client: mock_client }
50
+ .to raise_error(Errors::CommunicationError)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end