Skip to content

Commit 052cfe2

Browse files
nacchan99rakuda-san-desu
authored andcommitted
test: news RakeタスクのRSpecを追加 & fetch/importタスクを修正
- news:fetchタスクでfileスキーム/ローカルパスをsafe_openで許可(テスト用RSSの読み込みに対応) - YAML書き込み・ログ出力のパスをENV['NEWS_YAML_PATH']で上書き可能なyaml_pathに統一 - ソート後に全アイテムへ1からの連番IDを再付与 - news:import_from_yamlタスクもENV['NEWS_YAML_PATH']に対応
1 parent b0f4365 commit 052cfe2

File tree

3 files changed

+103
-18
lines changed

3 files changed

+103
-18
lines changed

lib/tasks/fetch_news.rake

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ require 'active_support/broadcast_logger'
77

88
def safe_open(url)
99
uri = URI.parse(url)
10+
return File.read(url) if uri.scheme.nil? || uri.scheme == 'file'
1011
raise "不正なURLです: #{url}" unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
11-
1212
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
1313
request = Net::HTTP::Get.new(uri)
1414
response = http.request(request)
@@ -26,25 +26,19 @@ namespace :news do
2626

2727
logger.info('==== START news:fetch ====')
2828

29-
# 既存の news.yml を読み込み
30-
yaml_path = Rails.root.join('db', 'news.yml')
29+
# YAML出力先を環境変数で上書きできるようにする
30+
yaml_path = ENV['NEWS_YAML_PATH'] ? Pathname.new(ENV['NEWS_YAML_PATH']) : Rails.root.join('db', 'news.yml')
31+
feed_urls = ENV['NEWS_RSS_PATH'] ? [ENV['NEWS_RSS_PATH']] :
32+
(Rails.env.test? || Rails.env.staging? ?
33+
[Rails.root.join('spec', 'fixtures', 'sample_news.rss').to_s] :
34+
['https://news.coderdojo.jp/feed/'])
35+
3136
existing_news = if File.exist?(yaml_path)
3237
YAML.safe_load(File.read(yaml_path), permitted_classes: [Time], aliases: true)['news'] || []
3338
else
3439
[]
3540
end
3641

37-
# テスト/ステージング環境ではサンプルファイル、本番は実サイトのフィード
38-
feed_urls = if Rails.env.test? || Rails.env.staging?
39-
[Rails.root.join('spec', 'fixtures', 'sample_news.rss').to_s]
40-
else
41-
[
42-
'https://news.coderdojo.jp/feed/'
43-
# 必要に応じて他 Dojo の RSS もここに追加可能
44-
# 'https://coderdojotokyo.org/feed',
45-
]
46-
end
47-
4842
# RSS 取得&パース
4943
new_items = feed_urls.flat_map do |url|
5044
logger.info("Fetching RSS → #{url}")
@@ -107,7 +101,11 @@ namespace :news do
107101
Time.parse(item['published_at'])
108102
}.reverse
109103

110-
File.open('db/news.yml', 'w') do |f|
104+
sorted_items.each_with_index do |item, index|
105+
item['id'] = index + 1
106+
end
107+
108+
File.open(yaml_path, 'w') do |f|
111109
formatted_items = sorted_items.map do |item|
112110
{
113111
'id' => item['id'],
@@ -120,7 +118,7 @@ namespace :news do
120118
f.write({ 'news' => formatted_items }.to_yaml)
121119
end
122120

123-
logger.info("✅ Wrote #{sorted_items.size} items to db/news.yml (#{truly_new_items_sorted.size} new, #{updated_items.size} updated)")
121+
logger.info("✅ Wrote #{sorted_items.size} items to #{yaml_path} (#{truly_new_items_sorted.size} new, #{updated_items.size} updated)")
124122
logger.info('==== END news:fetch ====')
125123
end
126124
end

lib/tasks/import_news.rake

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
require 'yaml'
22

33
namespace :news do
4-
desc 'db/news.yml を読み込んで News テーブルを upsert する'
4+
desc 'db/news.yml (またはENV指定のYAML)を読み込んで News テーブルを upsert する'
55
task import_from_yaml: :environment do
6-
yaml_path = Rails.root.join('db', 'news.yml')
6+
# ENVで上書き可能にする(なければデフォルト db/news.yml)
7+
yaml_path = ENV['NEWS_YAML_PATH'] ? Pathname.new(ENV['NEWS_YAML_PATH']) : Rails.root.join('db', 'news.yml')
78
raw = YAML.safe_load(File.read(yaml_path), permitted_classes: [Time], aliases: true)
89

910
# entries を計算

spec/tasks/news_rake_spec.rb

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
require 'rails_helper'
2+
require 'rake'
3+
require 'yaml'
4+
5+
RSpec.describe 'news Rakeタスク', type: :task do
6+
before { Rails.application.load_tasks }
7+
before { allow(Rails.env).to receive(:test?).and_return(true) }
8+
9+
# テスト用に tmp/news.yml を使う
10+
let(:yaml_path) { Rails.root.join('tmp', 'news.yml') }
11+
let(:fetch_task) { Rake::Task['news:fetch'] }
12+
let(:import_task) { Rake::Task['news:import_from_yaml'] }
13+
let(:yaml_content) { YAML.safe_load(File.read(yaml_path), permitted_classes: [Time]) }
14+
15+
around do |example|
16+
# テスト前後に一度だけ tmp/news.yml をクリア
17+
File.delete(yaml_path) if File.exist?(yaml_path)
18+
example.run
19+
File.delete(yaml_path) if File.exist?(yaml_path)
20+
end
21+
22+
describe 'news:fetch タスク' do
23+
before do
24+
ENV['NEWS_YAML_PATH'] = yaml_path.to_s
25+
ENV['NEWS_RSS_PATH'] = Rails.root.join('spec', 'fixtures', 'sample_news.rss').to_s
26+
fetch_task.reenable
27+
end
28+
29+
after do
30+
ENV.delete('NEWS_YAML_PATH')
31+
ENV.delete('NEWS_RSS_PATH')
32+
end
33+
34+
it 'サンプルRSSからニュースを取得し YAML に書き込む' do
35+
expect { fetch_task.invoke }.not_to raise_error
36+
expect(File.exist?(yaml_path)).to be true
37+
expect(yaml_content['news']).to be_an(Array)
38+
expect(yaml_content['news'].size).to eq(3)
39+
end
40+
41+
it 'ID が 1 から連番で付与される' do
42+
fetch_task.invoke
43+
ids = yaml_content['news'].map { |item| item['id'] }
44+
expect(ids).to eq([1, 2, 3])
45+
end
46+
47+
it '公開日時で降順ソートされる' do
48+
fetch_task.invoke
49+
dates = yaml_content['news'].map { |item| Time.parse(item['published_at']) }
50+
expect(dates).to eq(dates.sort.reverse)
51+
end
52+
end
53+
54+
describe 'news:import_from_yaml タスク' do
55+
let(:news_data) do
56+
{
57+
'news' => [
58+
{ 'id' => 1, 'url' => 'https://example.com/test1', 'title' => 'テスト記事1', 'published_at' => '2025-01-01T10:00:00Z' },
59+
{ 'id' => 2, 'url' => 'https://example.com/test2', 'title' => 'テスト記事2', 'published_at' => '2025-01-02T10:00:00Z' }
60+
]
61+
}
62+
end
63+
64+
before do
65+
ENV['NEWS_YAML_PATH'] = yaml_path.to_s
66+
File.write(yaml_path, news_data.to_yaml)
67+
import_task.reenable
68+
end
69+
70+
after do
71+
ENV.delete('NEWS_YAML_PATH')
72+
end
73+
74+
it 'YAML ファイルから News レコードを新規作成する' do
75+
expect { import_task.invoke }.to change(News, :count).by(2)
76+
expect(News.find_by(url: news_data['news'][0]['url']).title).to eq('テスト記事1')
77+
expect(News.find_by(url: news_data['news'][1]['url']).title).to eq('テスト記事2')
78+
end
79+
80+
it '既存レコードがあれば属性を更新する' do
81+
create(:news, url: news_data['news'][0]['url'], title: '古いタイトル')
82+
expect { import_task.invoke }.to change(News, :count).by(1)
83+
expect(News.find_by(url: news_data['news'][0]['url']).title).to eq('テスト記事1')
84+
end
85+
end
86+
end

0 commit comments

Comments
 (0)