77 before { Rails . application . load_tasks }
88 before { allow ( Rails . env ) . to receive ( :test? ) . and_return ( true ) }
99
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
1319
1420 before do
1521 ENV [ 'NEWS_YAML_PATH' ] = yaml_path . to_s
1622 fetch_task . reenable
17-
18- # ロガーのモック設定
1923 allow ( ActiveSupport ::BroadcastLogger ) . to receive ( :new ) . and_return ( logger_mock )
2024 allow ( logger_mock ) . to receive ( :info )
2125 allow ( logger_mock ) . to receive ( :warn )
2428 after do
2529 ENV . delete ( 'NEWS_YAML_PATH' )
2630 ENV . delete ( 'NEWS_RSS_PATH' )
27- File . delete ( yaml_path ) if File . exist? ( yaml_path )
2831 end
2932
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
6135 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 , '接続タイムアウト ')
6438 end
6539
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 .+/ )
6842 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 ( [ ] )
6945 end
7046 end
71- end
7247
73- describe '不正なRSSのハンドリング' do
74- context 'RSS::Parser.parse が失敗する場合' do
48+ context 'RSS::Parser.parseが失敗する場合' do
7549 before do
7650 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>' )
8052 end
8153
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 .+/ )
8456 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 ( [ ] )
9059 end
9160 end
9261
9362 context '空のRSSフィードの場合' do
9463 before do
9564 ENV [ 'NEWS_RSS_PATH' ] = 'https://example.com/feed.rss'
96-
97- # 有効だが空のRSSフィード
9865 empty_rss = <<~RSS
9966 <?xml version="1.0" encoding="UTF-8"?>
10067 <rss version="2.0">
10572 </channel>
10673 </rss>
10774 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 )
11076 end
11177
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
14579 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 ( [ ] )
14682 end
14783 end
14884 end
14985
15086 describe '破損したYAMLファイルのハンドリング' do
151- context '既存のYAMLファイルが破損している場合 ' do
87+ context '既存のYAMLが破損している場合 ' do
15288 before do
15389 ENV [ 'NEWS_RSS_PATH' ] = Rails . root . join ( 'spec' , 'fixtures' , 'sample_news.rss' ) . to_s
154-
155- # 破損したYAMLファイルを作成
15690 File . write ( yaml_path , "invalid yaml content:\n - broken\n indentation:\n - here" )
15791 end
15892
15993 it 'YAML読み込みエラーが発生し、タスクが失敗する' do
160- # YAML.safe_load のエラーは rescue されないため、タスク全体が失敗する
16194 expect { fetch_task . invoke } . to raise_error ( Psych ::SyntaxError )
16295 end
16396 end
16497
165- context '既存のYAMLファイルが不正な構造の場合 ' do
98+ context '既存のYAMLが不正な構造の場合 ' do
16699 before do
167100 ENV [ 'NEWS_RSS_PATH' ] = Rails . root . join ( 'spec' , 'fixtures' , 'sample_news.rss' ) . to_s
168-
169- # 不正な構造のYAMLファイル(newsキーがない)
170101 File . write ( yaml_path , { 'invalid_key' => [ { 'id' => 1 } ] } . to_yaml )
171102 end
172103
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
258105 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
261109 end
262110 end
263111 end
264- end
112+ end
0 commit comments