noaa_weather_client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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