gull 0.4.0 → 1.0.0

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.
data/spec/alert_spec.rb CHANGED
@@ -1,116 +1,162 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Gull::Alert do
6
+ def load_feature(name)
7
+ JSON.parse(File.read("spec/fixtures/features/#{name}"))
8
+ end
9
+
10
+ def wrap_features(*features)
11
+ { 'type' => 'FeatureCollection', 'features' => features }.to_json
12
+ end
13
+
14
+ def stub_alerts(json)
15
+ stub_request(:get, 'https://api.weather.gov/alerts/active')
16
+ .to_return(status: 200, body: json, headers: {})
17
+ end
18
+
4
19
  it 'should initialize with geocode' do
5
20
  alert = Gull::Alert.new
6
21
  expect(alert.geocode).not_to be_nil
7
22
  end
8
23
 
9
- it 'should fetch parsed alerts' do
10
- xml = File.read 'spec/fixtures/alerts.xml'
11
-
12
- stub_request(:get, 'http://alerts.weather.gov/cap/us.php?x=1')
13
- .with(headers: { 'Accept' => '*/*' })
14
- .to_return(status: 200, body: xml, headers: {})
24
+ it 'should fetch and parse all alerts' do
25
+ json = File.read 'spec/fixtures/alerts.json'
26
+ stub_alerts(json)
15
27
 
16
28
  alerts = Gull::Alert.fetch
17
- expect(alerts.size).to eq(3)
18
-
19
- first = alerts.first
20
- expect(first.id).to eq 'http://alerts.weather.gov/cap/wwacapget.php?x=CA125171381DD0.HeatAdvisory'
21
- expect(first.link).to eq 'http://alerts.weather.gov/cap/wwacapget.php?x=CA125171381DD0.HeatAdvisory'
22
- expect(first.alert_type).to eq 'Heat Advisory'
23
- expect(first.title).to eq 'Heat Advisory issued October 01 at 8:40AM PDT' \
24
- ' until October 03 at 9:00PM PDT by NWS'
25
- expect(first.summary).to eq 'SUMMARY TEXT'
26
-
27
- coordinates = [[27.35, -81.79], [27.14, -81.89], [27.04, -81.97],
28
- [27.04, -82.02], [27.14, -81.97], [27.35, -81.86],
29
- [27.35, -81.79]]
30
- expect(first.polygon.coordinates).to eq coordinates
31
-
32
- expect(first.effective_at).to eq Time.parse('2014-10-01T08:40:00-07:00')
33
- expect(first.expires_at).to eq Time.parse('2014-10-03T21:00:00-07:00')
34
- expect(first.updated_at).to eq Time.parse('2014-10-01T08:40:00-07:05')
35
- expect(first.published_at).to eq Time.parse('2014-10-01T08:40:00-07:06')
36
-
37
- expect(first.area).to eq 'Southern Salinas Valley, Arroyo Seco and Lake ' \
38
- 'San Antonio'
39
- expect(first.urgency).to eq :expected
40
- expect(first.severity).to eq :minor
41
- expect(first.certainty).to eq :very_likely
42
-
43
- expect(first.geocode.fips6).to be_nil
44
- expect(first.geocode.ugc).to be_nil
45
-
46
- expect(first.vtec).to eq '/O.NEW.KMTR.HT.Y.0002.141002T1900Z-141004T0400Z/'
47
-
48
- second = alerts[1]
49
- expect(second.polygon).to be_nil
50
-
51
- expect(second.geocode.fips6).to eq '006001 006013 006041 006053 006055 ' \
52
- '006069 006075 006081 006085 006087 006097'
53
- expect(second.geocode.ugc).to eq 'CAZ006 CAZ505 CAZ506 CAZ507 CAZ508 ' \
54
- 'CAZ509 CAZ510 CAZ511 CAZ512 CAZ513 CAZ516 CAZ517 CAZ518 CAZ528 ' \
55
- 'CAZ529 CAZ530'
56
-
57
- expect(second.vtec).to be_nil
58
-
59
- third = alerts[2]
60
- expect(third.vtec).to be_nil
61
- expect(third.link).to be_nil
29
+ expect(alerts.size).to eq(44)
62
30
  end
63
31
 
64
- it 'should fetch from url in options' do
65
- xml = File.read 'spec/fixtures/alerts.xml'
32
+ it 'should parse alert with polygon and no VTEC' do
33
+ feature = load_feature('polygon_no_vtec.json')
34
+ stub_alerts(wrap_features(feature))
35
+
36
+ alert = Gull::Alert.fetch.first
37
+
38
+ expect(alert.id).to eq feature['properties']['id']
39
+ expect(alert.link).to eq feature['properties']['@id']
40
+ expect(alert.alert_type).to eq 'Special Weather Statement'
41
+ expect(alert.title).to start_with 'Special Weather Statement'
42
+ expect(alert.summary).to start_with 'At 748 AM CST'
43
+ expect(alert.area).to eq 'Sevier; Howard; Hempstead'
66
44
 
67
- stub_request(:get, 'http://test.url')
68
- .with(headers: { 'Accept' => '*/*' })
69
- .to_return(status: 200, body: xml, headers: {})
45
+ expect(alert.effective_at).to eq Time.parse('2026-03-07T07:48:00-06:00')
46
+ expect(alert.expires_at).to eq Time.parse('2026-03-07T08:15:00-06:00')
47
+ expect(alert.published_at).to eq Time.parse('2026-03-07T07:48:00-06:00')
48
+ expect(alert.updated_at).to eq Time.parse('2026-03-07T07:48:00-06:00')
70
49
 
71
- alerts = Gull::Alert.fetch(url: 'http://test.url')
72
- expect(alerts.size).to eq(3)
50
+ expect(alert.urgency).to eq :expected
51
+ expect(alert.severity).to eq :moderate
52
+ expect(alert.certainty).to eq :observed
53
+
54
+ expect(alert.polygon).not_to be_nil
55
+ expect(alert.polygon.coordinates.first).to eq [34.21, -93.93]
56
+ expect(alert.polygon.coordinates.size).to eq 18
57
+
58
+ expect(alert.geocode.fips6).to eq '005133 005061 005057'
59
+ expect(alert.geocode.ugc).to eq 'ARZ050 ARZ051 ARZ060'
60
+
61
+ expect(alert.vtec).to be_nil
73
62
  end
74
63
 
75
- it 'should enable strict xml parsing via option' do
76
- xml = File.read 'spec/fixtures/bad.xml'
64
+ it 'should parse alert with polygon and VTEC' do
65
+ feature = load_feature('polygon_with_vtec.json')
66
+ stub_alerts(wrap_features(feature))
77
67
 
78
- stub_request(:get, 'http://alerts.weather.gov/cap/us.php?x=1')
79
- .with(headers: { 'Accept' => '*/*' })
80
- .to_return(status: 200, body: xml, headers: {})
68
+ alert = Gull::Alert.fetch.first
81
69
 
82
- expect { Gull::Alert.fetch(strict: true) }
83
- .to raise_error(Nokogiri::XML::SyntaxError)
70
+ expect(alert.alert_type).to eq 'Severe Thunderstorm Warning'
71
+ expect(alert.urgency).to eq :immediate
72
+ expect(alert.severity).to eq :severe
73
+ expect(alert.certainty).to eq :observed
74
+ expect(alert.polygon).not_to be_nil
75
+ expect(alert.polygon.coordinates.size).to eq 5
76
+ expect(alert.vtec).to eq '/O.NEW.KLZK.SV.W.0013.260307T1328Z-260307T1415Z/'
84
77
  end
85
78
 
86
- it 'should handle empty alerts' do
87
- xml = File.read 'spec/fixtures/empty.xml'
79
+ it 'should parse alert with null geometry' do
80
+ feature = load_feature('null_geometry.json')
81
+ stub_alerts(wrap_features(feature))
88
82
 
89
- stub_request(:get, 'http://alerts.weather.gov/cap/us.php?x=1')
90
- .with(headers: { 'Accept' => '*/*' })
91
- .to_return(status: 200, body: xml, headers: {})
83
+ alert = Gull::Alert.fetch.first
92
84
 
93
- alerts = Gull::Alert.fetch
94
- expect(alerts.size).to eq(0)
85
+ expect(alert.alert_type).to eq 'Tornado Watch'
86
+ expect(alert.urgency).to eq :future
87
+ expect(alert.severity).to eq :extreme
88
+ expect(alert.certainty).to eq :possible
89
+ expect(alert.polygon).to be_nil
90
+ expect(alert.geocode.ugc).not_to be_nil
91
+ expect(alert.geocode.fips6).not_to be_nil
92
+ expect(alert.vtec).to eq '/O.CON.KLZK.TO.A.0022.000000T0000Z-260307T1400Z/'
95
93
  end
96
94
 
97
- it 'should handle bad response' do
98
- xml = File.read 'spec/fixtures/bad.xml'
95
+ it 'should parse flood advisory with minor severity' do
96
+ feature = load_feature('flood_advisory.json')
97
+ stub_alerts(wrap_features(feature))
98
+
99
+ alert = Gull::Alert.fetch.first
100
+
101
+ expect(alert.alert_type).to eq 'Flood Advisory'
102
+ expect(alert.severity).to eq :minor
103
+ expect(alert.polygon).not_to be_nil
104
+ expect(alert.vtec).not_to be_nil
105
+ end
99
106
 
100
- stub_request(:get, 'http://alerts.weather.gov/cap/us.php?x=1')
101
- .with(headers: { 'Accept' => '*/*' })
102
- .to_return(status: 200, body: xml, headers: {})
107
+ it 'should parse blizzard warning with extreme severity' do
108
+ feature = load_feature('blizzard_warning.json')
109
+ stub_alerts(wrap_features(feature))
110
+
111
+ alert = Gull::Alert.fetch.first
112
+
113
+ expect(alert.alert_type).to eq 'Blizzard Warning'
114
+ expect(alert.severity).to eq :extreme
115
+ expect(alert.polygon).to be_nil
116
+ end
117
+
118
+ it 'should parse MultiPolygon geometry' do
119
+ feature = load_feature('multipolygon.json')
120
+ stub_alerts(wrap_features(feature))
121
+
122
+ alert = Gull::Alert.fetch.first
123
+
124
+ coordinates = [[34.57, -97.56], [34.77, -97.38],
125
+ [34.75, -97.17], [34.57, -97.56]]
126
+ expect(alert.polygon.coordinates).to eq coordinates
127
+ end
128
+
129
+ it 'should handle empty geocode' do
130
+ feature = load_feature('empty_geocode.json')
131
+ stub_alerts(wrap_features(feature))
132
+
133
+ alert = Gull::Alert.fetch.first
134
+
135
+ expect(alert.geocode.fips6).to be_nil
136
+ expect(alert.geocode.ugc).to be_nil
137
+ end
138
+
139
+ it 'should fetch with area option' do
140
+ json = File.read 'spec/fixtures/alerts.json'
141
+
142
+ stub_request(:get, 'https://api.weather.gov/alerts/active?area=OK')
143
+ .to_return(status: 200, body: json, headers: {})
144
+
145
+ alerts = Gull::Alert.fetch(area: 'OK')
146
+ expect(alerts.size).to eq(44)
147
+ end
148
+
149
+ it 'should handle empty alerts' do
150
+ json = File.read 'spec/fixtures/empty.json'
151
+ stub_alerts(json)
103
152
 
104
153
  alerts = Gull::Alert.fetch
105
154
  expect(alerts.size).to eq(0)
106
155
  end
107
156
 
108
- it 'should handle missing cap section' do
109
- xml = File.read 'spec/fixtures/missing_cap.xml'
110
-
111
- stub_request(:get, 'http://alerts.weather.gov/cap/us.php?x=1')
112
- .with(headers: { 'Accept' => '*/*' })
113
- .to_return(status: 200, body: xml, headers: {})
157
+ it 'should handle missing event' do
158
+ json = File.read 'spec/fixtures/missing_event.json'
159
+ stub_alerts(json)
114
160
 
115
161
  alerts = Gull::Alert.fetch
116
162
  expect(alerts.size).to eq 0
data/spec/client_spec.rb CHANGED
@@ -1,45 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Gull::Client do
4
- it 'should initialize with options' do
5
- xml = File.read 'spec/fixtures/alerts.xml'
6
- stub_request(:get, 'http://test.url')
7
- .with(headers: { 'Accept' => '*/*' })
8
- .to_return(status: 200, body: xml, headers: {})
9
-
10
- options = { url: 'http://test.url', strict: true }
11
- client = Gull::Client.new(options)
12
- alerts = client.fetch
13
- expect(alerts.size).to eq 3
14
-
15
- xml = File.read 'spec/fixtures/bad.xml'
16
- stub_request(:get, 'http://test.url')
17
- .with(headers: { 'Accept' => '*/*' })
18
- .to_return(status: 200, body: xml, headers: {})
19
-
20
- expect { client.fetch }
21
- .to raise_error(Nokogiri::XML::SyntaxError)
22
- end
23
-
24
- it 'should fetch alerts without options' do
25
- xml = File.read 'spec/fixtures/alerts.xml'
6
+ it 'should fetch alerts' do
7
+ json = File.read 'spec/fixtures/alerts.json'
26
8
 
27
- stub_request(:get, 'http://alerts.weather.gov/cap/us.php?x=1')
28
- .with(headers: { 'Accept' => '*/*' })
29
- .to_return(status: 200, body: xml, headers: {})
9
+ stub_request(:get, 'https://api.weather.gov/alerts/active')
10
+ .to_return(status: 200, body: json, headers: {})
30
11
 
31
12
  client = Gull::Client.new
32
13
  alerts = client.fetch
33
- expect(alerts.size).to eq 3
14
+ expect(alerts.size).to eq 44
34
15
  expect(client.errors.size).to eq 0
35
16
  end
36
17
 
37
- it 'should handle incomplete entries in xml' do
38
- xml = File.read 'spec/fixtures/missing_cap.xml'
18
+ it 'should handle features with missing event' do
19
+ json = File.read 'spec/fixtures/missing_event.json'
39
20
 
40
- stub_request(:get, 'http://alerts.weather.gov/cap/us.php?x=1')
41
- .with(headers: { 'Accept' => '*/*' })
42
- .to_return(status: 200, body: xml, headers: {})
21
+ stub_request(:get, 'https://api.weather.gov/alerts/active')
22
+ .to_return(status: 200, body: json, headers: {})
43
23
 
44
24
  client = Gull::Client.new
45
25
  alerts = client.fetch
@@ -47,41 +27,25 @@ describe Gull::Client do
47
27
  expect(client.errors.size).to eq 1
48
28
  end
49
29
 
50
- it 'should raise own error if timeout occurs' do
51
- stub_request(:get, 'http://alerts.weather.gov/cap/us.php?x=1')
52
- .with(headers: { 'Accept' => '*/*' })
53
- .to_timeout
30
+ it 'should raise error on non-success response' do
31
+ stub_request(:get, 'https://api.weather.gov/alerts/active')
32
+ .to_return(status: 503, body: '{"title":"Service Unavailable"}', headers: {})
54
33
 
55
- message = 'Timeout while connecting to NWS web service'
56
34
  client = Gull::Client.new
57
- expect { client.fetch }.to raise_error(Gull::TimeoutError, message)
35
+ expect { client.fetch }.to raise_error(Gull::HttpError, 'NWS API returned 503')
58
36
  end
59
37
 
60
- it 'should raise own error if http errors occur' do
61
- stub_request(:get, 'http://alerts.weather.gov/cap/us.php?x=1')
62
- .with(headers: { 'Accept' => '*/*' })
63
- .to_raise(HTTPClient::KeepAliveDisconnected)
64
-
65
- message = 'Could not connect to NWS web service'
66
- client = Gull::Client.new
67
- expect { client.fetch }.to raise_error(Gull::HttpError, message) do |error|
68
- expect(error.original).to be_a(HTTPClient::KeepAliveDisconnected)
69
- end
70
-
71
- stub_request(:get, 'http://alerts.weather.gov/cap/us.php?x=1')
72
- .with(headers: { 'Accept' => '*/*' })
73
- .to_raise(HTTPClient::BadResponseError)
38
+ it 'should raise own error if timeout occurs' do
39
+ stub_request(:get, 'https://api.weather.gov/alerts/active')
40
+ .to_timeout
74
41
 
75
- message = 'Could not connect to NWS web service'
42
+ message = 'Timeout while connecting to NWS web service'
76
43
  client = Gull::Client.new
77
- expect { client.fetch }.to raise_error(Gull::HttpError, message) do |error|
78
- expect(error.original).to be_a(HTTPClient::BadResponseError)
79
- end
44
+ expect { client.fetch }.to raise_error(Gull::TimeoutError, message)
80
45
  end
81
46
 
82
47
  it 'should raise own error if connection errors occur' do
83
- stub_request(:get, 'http://alerts.weather.gov/cap/us.php?x=1')
84
- .with(headers: { 'Accept' => '*/*' })
48
+ stub_request(:get, 'https://api.weather.gov/alerts/active')
85
49
  .to_raise(SocketError)
86
50
 
87
51
  message = 'Could not connect to NWS web service'
@@ -90,24 +54,31 @@ describe Gull::Client do
90
54
  expect(error.original).to be_a(SocketError)
91
55
  end
92
56
 
93
- stub_request(:get, 'http://alerts.weather.gov/cap/us.php?x=1')
94
- .with(headers: { 'Accept' => '*/*' })
57
+ stub_request(:get, 'https://api.weather.gov/alerts/active')
95
58
  .to_raise(Errno::ECONNREFUSED)
96
59
 
97
- message = 'Could not connect to NWS web service'
98
60
  client = Gull::Client.new
99
61
  expect { client.fetch }.to raise_error(Gull::HttpError, message) do |error|
100
62
  expect(error.original).to be_a(Errno::ECONNREFUSED)
101
63
  end
102
64
 
103
- stub_request(:get, 'http://alerts.weather.gov/cap/us.php?x=1')
104
- .with(headers: { 'Accept' => '*/*' })
65
+ stub_request(:get, 'https://api.weather.gov/alerts/active')
105
66
  .to_raise(Errno::ECONNRESET)
106
67
 
107
- message = 'Could not connect to NWS web service'
108
68
  client = Gull::Client.new
109
69
  expect { client.fetch }.to raise_error(Gull::HttpError, message) do |error|
110
70
  expect(error.original).to be_a(Errno::ECONNRESET)
111
71
  end
112
72
  end
73
+
74
+ it 'should filter by area' do
75
+ json = File.read 'spec/fixtures/alerts.json'
76
+
77
+ stub_request(:get, 'https://api.weather.gov/alerts/active?area=OK')
78
+ .to_return(status: 200, body: json, headers: {})
79
+
80
+ client = Gull::Client.new(area: 'OK')
81
+ alerts = client.fetch
82
+ expect(alerts.size).to eq 44
83
+ end
113
84
  end
data/spec/error_spec.rb CHANGED
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Gull::HttpError do
4
6
  it 'should instantiate and set original with current exception' do
5
7
  error = StandardError.new 'inner'
6
8
  begin
7
- fail error
9
+ raise error
8
10
  rescue StandardError
9
11
  http_error = Gull::HttpError.new 'test'
10
12
  expect(http_error.original).to eq error
11
- expect(http_error.original.message). to eq 'inner'
13
+ expect(http_error.original.message).to eq 'inner'
12
14
  end
13
15
  end
14
16
  end