7
7
before { Rails . application . load_tasks }
8
8
before { allow ( Rails . env ) . to receive ( :test? ) . and_return ( true ) }
9
9
10
- let ( :yaml_path ) { Rails . root . join ( 'tmp' , 'error_test_news.yml' ) }
11
- let ( :fetch_task ) { Rake ::Task [ 'news:fetch' ] }
12
- let ( :logger_mock ) { instance_double ( ActiveSupport ::BroadcastLogger ) }
10
+ let ( :yaml_path ) { Rails . root . join ( 'tmp' , 'error_test_news.yml' ) }
11
+ let ( :fetch_task ) { Rake ::Task [ 'news:fetch' ] }
12
+ let ( :logger_mock ) { instance_double ( ActiveSupport ::BroadcastLogger ) }
13
+
14
+ around do |example |
15
+ File . delete ( yaml_path ) if File . exist? ( yaml_path )
16
+ example . run
17
+ File . delete ( yaml_path ) if File . exist? ( yaml_path )
18
+ end
13
19
14
20
before do
15
21
ENV [ 'NEWS_YAML_PATH' ] = yaml_path . to_s
16
22
fetch_task . reenable
17
-
18
- # ロガーのモック設定
19
23
allow ( ActiveSupport ::BroadcastLogger ) . to receive ( :new ) . and_return ( logger_mock )
20
24
allow ( logger_mock ) . to receive ( :info )
21
25
allow ( logger_mock ) . to receive ( :warn )
24
28
after do
25
29
ENV . delete ( 'NEWS_YAML_PATH' )
26
30
ENV . delete ( 'NEWS_RSS_PATH' )
27
- File . delete ( yaml_path ) if File . exist? ( yaml_path )
28
31
end
29
32
30
- describe 'ネットワークエラーのハンドリング' do
31
- context 'safe_open がネットワークエラーで例外を投げる場合' do
32
- before do
33
- ENV [ 'NEWS_RSS_PATH' ] = 'https://example.com/feed.rss'
34
- allow_any_instance_of ( Object ) . to receive ( :safe_open ) . and_raise ( Net ::OpenTimeout , '接続タイムアウト' )
35
- end
36
-
37
- it 'エラーをログに記録し、処理を継続する' do
38
- expect ( logger_mock ) . to receive ( :warn ) . with ( /⚠️ Failed to fetch .+: 接続タイムアウト/ )
39
- expect { fetch_task . invoke } . not_to raise_error
40
-
41
- # 空の news.yml が作成される
42
- expect ( File . exist? ( yaml_path ) ) . to be true
43
- yaml_content = YAML . safe_load ( File . read ( yaml_path ) , permitted_classes : [ Time ] )
44
- expect ( yaml_content [ 'news' ] ) . to eq ( [ ] )
45
- end
46
- end
47
-
48
- context 'HTTPエラーレスポンスの場合' do
49
- before do
50
- ENV [ 'NEWS_RSS_PATH' ] = 'https://example.com/feed.rss'
51
- allow_any_instance_of ( Object ) . to receive ( :safe_open ) . and_raise ( Net ::HTTPServerException , '500 Internal Server Error' )
52
- end
53
-
54
- it 'エラーをログに記録し、処理を継続する' do
55
- expect ( logger_mock ) . to receive ( :warn ) . with ( /⚠️ Failed to fetch .+: 500 Internal Server Error/ )
56
- expect { fetch_task . invoke } . not_to raise_error
57
- end
58
- end
59
-
60
- context '不正なURLの場合' do
33
+ describe 'ネットワーク・RSSエラー時の挙動' do
34
+ context 'ネットワークエラーの場合' do
61
35
before do
62
- ENV [ 'NEWS_RSS_PATH' ] = 'https://example.com/feed. rss'
63
- allow_any_instance_of ( Object ) . to receive ( :safe_open ) . and_raise ( '不正なURLです: https://example.com/feed.rss ')
36
+ ENV [ 'NEWS_RSS_PATH' ] = 'https://invalid-url. example.com/rss'
37
+ allow ( self ) . to receive ( :safe_open ) . and_raise ( Net :: OpenTimeout , '接続タイムアウト ')
64
38
end
65
39
66
- it 'エラーをログに記録し、処理を継続する ' do
67
- expect ( logger_mock ) . to receive ( :warn ) . with ( /⚠️ Failed to fetch .+: 不正なURLです / )
40
+ it 'warnログを出し、空のnews.ymlを生成する ' do
41
+ expect ( logger_mock ) . to receive ( :warn ) . with ( /⚠️ Failed to fetch .+/ )
68
42
expect { fetch_task . invoke } . not_to raise_error
43
+ yaml = YAML . safe_load ( File . read ( yaml_path ) , permitted_classes : [ Time ] )
44
+ expect ( yaml [ 'news' ] ) . to eq ( [ ] )
69
45
end
70
46
end
71
- end
72
47
73
- describe '不正なRSSのハンドリング' do
74
- context 'RSS::Parser.parse が失敗する場合' do
48
+ context 'RSS::Parser.parseが失敗する場合' do
75
49
before do
76
50
ENV [ 'NEWS_RSS_PATH' ] = 'https://example.com/feed.rss'
77
-
78
- # safe_open は成功するが、不正なXMLを返す
79
- allow_any_instance_of ( Object ) . to receive ( :safe_open ) . and_return ( '<invalid>not valid rss</invalid>' )
51
+ allow ( self ) . to receive ( :safe_open ) . and_return ( '<invalid>not valid rss</invalid>' )
80
52
end
81
53
82
- it 'エラーをログに記録し、処理を継続する ' do
83
- expect ( logger_mock ) . to receive ( :warn ) . with ( /⚠️ Failed to fetch .+: / )
54
+ it 'warnログを出し、空のnews.ymlを生成する ' do
55
+ expect ( logger_mock ) . to receive ( :warn ) . with ( /⚠️ Failed to fetch .+/ )
84
56
expect { fetch_task . invoke } . not_to raise_error
85
-
86
- # 空の news.yml が作成される
87
- expect ( File . exist? ( yaml_path ) ) . to be true
88
- yaml_content = YAML . safe_load ( File . read ( yaml_path ) , permitted_classes : [ Time ] )
89
- expect ( yaml_content [ 'news' ] ) . to eq ( [ ] )
57
+ yaml = YAML . safe_load ( File . read ( yaml_path ) , permitted_classes : [ Time ] )
58
+ expect ( yaml [ 'news' ] ) . to eq ( [ ] )
90
59
end
91
60
end
92
61
93
62
context '空のRSSフィードの場合' do
94
63
before do
95
64
ENV [ 'NEWS_RSS_PATH' ] = 'https://example.com/feed.rss'
96
-
97
- # 有効だが空のRSSフィード
98
65
empty_rss = <<~RSS
99
66
<?xml version="1.0" encoding="UTF-8"?>
100
67
<rss version="2.0">
105
72
</channel>
106
73
</rss>
107
74
RSS
108
-
109
- allow_any_instance_of ( Object ) . to receive ( :safe_open ) . and_return ( empty_rss )
75
+ allow ( self ) . to receive ( :safe_open ) . and_return ( empty_rss )
110
76
end
111
77
112
- it '空の配列として処理し、エラーにならない' do
113
- expect { fetch_task . invoke } . not_to raise_error
114
-
115
- yaml_content = YAML . safe_load ( File . read ( yaml_path ) , permitted_classes : [ Time ] )
116
- expect ( yaml_content [ 'news' ] ) . to eq ( [ ] )
117
- end
118
- end
119
-
120
- context 'RSSアイテムに必須フィールドが欠けている場合' do
121
- before do
122
- ENV [ 'NEWS_RSS_PATH' ] = 'https://example.com/feed.rss'
123
-
124
- # linkやpubDateが欠けているRSS
125
- invalid_rss = <<~RSS
126
- <?xml version="1.0" encoding="UTF-8"?>
127
- <rss version="2.0">
128
- <channel>
129
- <title>Invalid Feed</title>
130
- <description>Invalid RSS Feed</description>
131
- <link>https://example.com</link>
132
- <item>
133
- <title>タイトルのみの記事</title>
134
- <!-- link と pubDate が欠けている -->
135
- </item>
136
- </channel>
137
- </rss>
138
- RSS
139
-
140
- allow_any_instance_of ( Object ) . to receive ( :safe_open ) . and_return ( invalid_rss )
141
- end
142
-
143
- it 'エラーをログに記録し、処理を継続する' do
144
- expect ( logger_mock ) . to receive ( :warn ) . with ( /⚠️ Failed to fetch .+/ )
78
+ it '空配列でnews.ymlを生成する' do
145
79
expect { fetch_task . invoke } . not_to raise_error
80
+ yaml = YAML . safe_load ( File . read ( yaml_path ) , permitted_classes : [ Time ] )
81
+ expect ( yaml [ 'news' ] ) . to eq ( [ ] )
146
82
end
147
83
end
148
84
end
149
85
150
86
describe '破損したYAMLファイルのハンドリング' do
151
- context '既存のYAMLファイルが破損している場合 ' do
87
+ context '既存のYAMLが破損している場合 ' do
152
88
before do
153
89
ENV [ 'NEWS_RSS_PATH' ] = Rails . root . join ( 'spec' , 'fixtures' , 'sample_news.rss' ) . to_s
154
-
155
- # 破損したYAMLファイルを作成
156
90
File . write ( yaml_path , "invalid yaml content:\n - broken\n indentation:\n - here" )
157
91
end
158
92
159
93
it 'YAML読み込みエラーが発生し、タスクが失敗する' do
160
- # YAML.safe_load のエラーは rescue されないため、タスク全体が失敗する
161
94
expect { fetch_task . invoke } . to raise_error ( Psych ::SyntaxError )
162
95
end
163
96
end
164
97
165
- context '既存のYAMLファイルが不正な構造の場合 ' do
98
+ context '既存のYAMLが不正な構造の場合 ' do
166
99
before do
167
100
ENV [ 'NEWS_RSS_PATH' ] = Rails . root . join ( 'spec' , 'fixtures' , 'sample_news.rss' ) . to_s
168
-
169
- # 不正な構造のYAMLファイル(newsキーがない)
170
101
File . write ( yaml_path , { 'invalid_key' => [ { 'id' => 1 } ] } . to_yaml )
171
102
end
172
103
173
- it '空の配列として扱い、処理を継続する' do
174
- expect { fetch_task . invoke } . not_to raise_error
175
-
176
- # 新しいデータで上書きされる
177
- yaml_content = YAML . safe_load ( File . read ( yaml_path ) , permitted_classes : [ Time ] )
178
- expect ( yaml_content [ 'news' ] ) . to be_an ( Array )
179
- expect ( yaml_content [ 'news' ] . size ) . to be > 0
180
- end
181
- end
182
-
183
- context '許可されていないクラスを含むYAMLファイルの場合' do
184
- before do
185
- ENV [ 'NEWS_RSS_PATH' ] = Rails . root . join ( 'spec' , 'fixtures' , 'sample_news.rss' ) . to_s
186
-
187
- # DateTimeオブジェクトを含むYAML(Timeのみ許可されている)
188
- yaml_content = {
189
- 'news' => [
190
- {
191
- 'id' => 1 ,
192
- 'url' => 'https://example.com/test' ,
193
- 'title' => 'テスト' ,
194
- 'published_at' => DateTime . now
195
- }
196
- ]
197
- }
198
-
199
- # 強制的にDateTimeオブジェクトを含むYAMLを作成
200
- File . write ( yaml_path , yaml_content . to_yaml . gsub ( '!ruby/object:DateTime' , '!ruby/object:DateTime' ) )
201
- end
202
-
203
- it 'YAML読み込みエラーが発生し、タスクが失敗する' do
204
- expect { fetch_task . invoke } . to raise_error ( Psych ::DisallowedClass )
205
- end
206
- end
207
- end
208
-
209
- describe '複数のエラーが同時に発生する場合' do
210
- context '複数のRSSフィードで異なるエラーが発生する場合' do
211
- before do
212
- # 複数のフィードURLを環境変数経由では設定できないため、
213
- # デフォルトの動作をオーバーライドする
214
- allow ( Rails . env ) . to receive ( :test? ) . and_return ( false )
215
- allow ( Rails . env ) . to receive ( :staging? ) . and_return ( false )
216
- ENV . delete ( 'NEWS_RSS_PATH' )
217
-
218
- # 最初のフィードはネットワークエラー
219
- allow_any_instance_of ( Object ) . to receive ( :safe_open )
220
- . with ( 'https://news.coderdojo.jp/feed/' )
221
- . and_raise ( Net ::OpenTimeout , 'タイムアウト' )
222
- end
223
-
224
- it '各エラーをログに記録し、処理を継続する' do
225
- expect ( logger_mock ) . to receive ( :warn ) . with ( /⚠️ Failed to fetch .+: タイムアウト/ )
226
- expect { fetch_task . invoke } . not_to raise_error
227
-
228
- # 空の news.yml が作成される
229
- expect ( File . exist? ( yaml_path ) ) . to be true
230
- yaml_content = YAML . safe_load ( File . read ( yaml_path ) , permitted_classes : [ Time ] )
231
- expect ( yaml_content [ 'news' ] ) . to eq ( [ ] )
232
- end
233
- end
234
- end
235
-
236
- describe 'エラーリカバリー' do
237
- context 'ネットワークエラー後に再実行した場合' do
238
- before do
239
- ENV [ 'NEWS_RSS_PATH' ] = Rails . root . join ( 'spec' , 'fixtures' , 'sample_news.rss' ) . to_s
240
- end
241
-
242
- it '正常に処理される' do
243
- # 最初はネットワークエラー
244
- allow_any_instance_of ( Object ) . to receive ( :safe_open ) . and_raise ( Net ::OpenTimeout , 'タイムアウト' )
245
- expect { fetch_task . invoke } . not_to raise_error
246
-
247
- # エラー時は空のYAMLが作成される
248
- yaml_content = YAML . safe_load ( File . read ( yaml_path ) , permitted_classes : [ Time ] )
249
- expect ( yaml_content [ 'news' ] ) . to eq ( [ ] )
250
-
251
- # safe_openのモックを解除して正常動作に戻す
252
- allow_any_instance_of ( Object ) . to receive ( :safe_open ) . and_call_original
253
-
254
- # タスクを再実行可能にする
255
- fetch_task . reenable
256
-
257
- # 再実行すると正常に処理される
104
+ it '空配列として扱い、正常に上書きされる' do
258
105
expect { fetch_task . invoke } . not_to raise_error
259
- yaml_content = YAML . safe_load ( File . read ( yaml_path ) , permitted_classes : [ Time ] )
260
- expect ( yaml_content [ 'news' ] . size ) . to be > 0
106
+ yaml = YAML . safe_load ( File . read ( yaml_path ) , permitted_classes : [ Time ] )
107
+ expect ( yaml [ 'news' ] ) . to be_an ( Array )
108
+ expect ( yaml [ 'news' ] . size ) . to be > 0
261
109
end
262
110
end
263
111
end
264
- end
112
+ end
0 commit comments