# encoding: utf-8
require 'rails_helper'
describe Agents::DataOutputAgent do
let(:agent) do
_agent = Agents::DataOutputAgent.new(:name => 'My Data Output Agent')
_agent.options = _agent.default_options.merge('secrets' => ['secret1', 'secret2'], 'events_to_show' => 3)
_agent.options['template']['item']['pubDate'] = "{{date}}"
_agent.options['template']['item']['category'] = "{{ category | as_object }}"
_agent.user = users(:bob)
_agent.sources << agents(:bob_website_agent)
_agent.save!
_agent
end
describe "#working?" do
it "checks if events have been received within expected receive period" do
expect(agent).not_to be_working
Agents::DataOutputAgent.async_receive agent.id, [events(:bob_website_agent_event).id]
expect(agent.reload).to be_working
two_days_from_now = 2.days.from_now
stub(Time).now { two_days_from_now }
expect(agent.reload).not_to be_working
end
end
describe "validation" do
before do
expect(agent).to be_valid
end
it "should validate presence and length of secrets" do
agent.options[:secrets] = ""
expect(agent).not_to be_valid
agent.options[:secrets] = "foo"
expect(agent).not_to be_valid
agent.options[:secrets] = "foo/bar"
expect(agent).not_to be_valid
agent.options[:secrets] = "foo.xml"
expect(agent).not_to be_valid
agent.options[:secrets] = false
expect(agent).not_to be_valid
agent.options[:secrets] = []
expect(agent).not_to be_valid
agent.options[:secrets] = ["foo.xml"]
expect(agent).not_to be_valid
agent.options[:secrets] = ["hello", true]
expect(agent).not_to be_valid
agent.options[:secrets] = ["hello"]
expect(agent).to be_valid
agent.options[:secrets] = ["hello", "world"]
expect(agent).to be_valid
end
it "should validate presence of expected_receive_period_in_days" do
agent.options[:expected_receive_period_in_days] = ""
expect(agent).not_to be_valid
agent.options[:expected_receive_period_in_days] = 0
expect(agent).not_to be_valid
agent.options[:expected_receive_period_in_days] = -1
expect(agent).not_to be_valid
end
it "should validate presence of template and template.item" do
agent.options[:template] = ""
expect(agent).not_to be_valid
agent.options[:template] = {}
expect(agent).not_to be_valid
agent.options[:template] = { 'item' => 'foo' }
expect(agent).not_to be_valid
agent.options[:template] = { 'item' => { 'title' => 'hi' } }
expect(agent).to be_valid
end
end
describe "#receive" do
it "should push to hubs when push_hubs is given" do
agent.options[:push_hubs] = %w[http://push.example.com]
agent.options[:template] = { 'link' => 'http://huginn.example.org' }
alist = nil
stub_request(:post, 'http://push.example.com/')
.with(headers: { 'Content-Type' => %r{\Aapplication/x-www-form-urlencoded\s*(?:;|\z)} })
.to_return { |request|
alist = URI.decode_www_form(request.body).sort
{ status: 200, body: 'ok' }
}
agent.receive(events(:bob_website_agent_event))
expect(alist).to eq [
["hub.mode", "publish"],
["hub.url", agent.feed_url(secret: agent.options[:secrets].first, format: :xml)]
]
end
end
describe "#receive_web_request" do
before do
current_time = Time.now
stub(Time).now { current_time }
agents(:bob_website_agent).events.destroy_all
end
it "requires a valid secret" do
content, status, content_type = agent.receive_web_request({ 'secret' => 'fake' }, 'get', 'text/xml')
expect(status).to eq(401)
expect(content).to eq("Not Authorized")
content, status, content_type = agent.receive_web_request({ 'secret' => 'fake' }, 'get', 'application/json')
expect(status).to eq(401)
expect(content).to eq({ :error => "Not Authorized" })
content, status, content_type = agent.receive_web_request({ 'secret' => 'secret1' }, 'get', 'application/json')
expect(status).to eq(200)
end
describe "outputting events as RSS and JSON" do
let!(:event1) do
agents(:bob_website_agent).create_event :payload => {
"site_title" => "XKCD",
"url" => "http://imgs.xkcd.com/comics/evolving.png",
"title" => "Evolving",
"hovertext" => "Biologists play reverse Pokemon, trying to avoid putting any one team member on the front lines long enough for the experience to cause evolution.",
"category" => []
}
end
let!(:event2) do
agents(:bob_website_agent).create_event :payload => {
"site_title" => "XKCD",
"url" => "http://imgs.xkcd.com/comics/evolving2.png",
"title" => "Evolving again",
"date" => '',
"hovertext" => "Something else",
"category" => ["Category 1", "Category 2"]
}
end
let!(:event3) do
agents(:bob_website_agent).create_event :payload => {
"site_title" => "XKCD",
"url" => "http://imgs.xkcd.com/comics/evolving0.png",
"title" => "Evolving yet again with a past date",
"date" => '2014/05/05',
"hovertext" => "A small text",
"category" => ["Some category"]
}
end
it "can output RSS" do
stub(agent).feed_link { "https://yoursite.com" }
content, status, content_type = agent.receive_web_request({ 'secret' => 'secret1' }, 'get', 'text/xml')
expect(status).to eq(200)
expect(content_type).to eq('application/rss+xml')
expect(content.gsub(/\s+/, '')).to eq Utils.unindent(<<-XML).gsub(/\s+/, '')
https://yoursite.com/favicon.ico
XKCD comics as a feed
This is a feed of recent XKCD comics, generated by Huginn
https://yoursite.com
#{Time.now.rfc2822}
#{Time.now.rfc2822}
60
-
Evolving yet again with a past date
Secret hovertext: A small text
http://imgs.xkcd.com/comics/evolving0.png
#{Time.zone.parse(event3.payload['date']).rfc2822}
Some category
#{event3.id}
-
Evolving again
Secret hovertext: Something else
http://imgs.xkcd.com/comics/evolving2.png
#{event2.created_at.rfc2822}
Category 1
Category 2
#{event2.id}
-
Evolving
Secret hovertext: Biologists play reverse Pokemon, trying to avoid putting any one team member on the front lines long enough for the experience to cause evolution.
http://imgs.xkcd.com/comics/evolving.png
#{event1.created_at.rfc2822}
#{event1.id}
XML
end
describe "with custom rss_content_type given" do
before do
agent.options['rss_content_type'] = 'text/xml'
agent.save!
end
it "can output RSS with the Content-Type" do
content, status, content_type = agent.receive_web_request({ 'secret' => 'secret1' }, 'get', 'text/xml')
expect(status).to eq(200)
expect(content_type).to eq('text/xml')
end
end
it "can output RSS with hub links when push_hubs is specified" do
stub(agent).feed_link { "https://yoursite.com" }
agent.options[:push_hubs] = %w[https://pubsubhubbub.superfeedr.com/ https://pubsubhubbub.appspot.com/]
content, status, content_type = agent.receive_web_request({ 'secret' => 'secret1' }, 'get', 'text/xml')
expect(status).to eq(200)
expect(content_type).to eq('application/rss+xml')
xml = Nokogiri::XML(content)
expect(xml.xpath('/rss/channel/atom:link[@rel="hub"]/@href').map(&:text).sort).to eq agent.options[:push_hubs].sort
end
it "can output JSON" do
agent.options['template']['item']['foo'] = "hi"
content, status, content_type = agent.receive_web_request({ 'secret' => 'secret2' }, 'get', 'application/json')
expect(status).to eq(200)
expect(content).to eq({
'title' => 'XKCD comics as a feed',
'description' => 'This is a feed of recent XKCD comics, generated by Huginn',
'pubDate' => Time.now,
'items' => [
{
'title' => 'Evolving yet again with a past date',
'description' => 'Secret hovertext: A small text',
'link' => 'http://imgs.xkcd.com/comics/evolving0.png',
'guid' => {"contents" => event3.id, "isPermaLink" => "false"},
'pubDate' => Time.zone.parse(event3.payload['date']).rfc2822,
'category' => ['Some category'],
'foo' => 'hi'
},
{
'title' => 'Evolving again',
'description' => 'Secret hovertext: Something else',
'link' => 'http://imgs.xkcd.com/comics/evolving2.png',
'guid' => {"contents" => event2.id, "isPermaLink" => "false"},
'pubDate' => event2.created_at.rfc2822,
'category' => ['Category 1', 'Category 2'],
'foo' => 'hi'
},
{
'title' => 'Evolving',
'description' => 'Secret hovertext: Biologists play reverse Pokemon, trying to avoid putting any one team member on the front lines long enough for the experience to cause evolution.',
'link' => 'http://imgs.xkcd.com/comics/evolving.png',
'guid' => {"contents" => event1.id, "isPermaLink" => "false"},
'pubDate' => event1.created_at.rfc2822,
'category' => [],
'foo' => 'hi'
}
]
})
end
describe "with custom response_headers given" do
before do
agent.options['response_headers'] = {"Access-Control-Allow-Origin" => "*", "X-My-Custom-Header" => "hello"}
agent.save!
end
it "can respond with custom headers" do
content, status, content_type, response_headers = agent.receive_web_request({ 'secret' => 'secret1' }, 'get', 'text/xml')
expect(status).to eq(200)
expect(response_headers).to eq({"Access-Control-Allow-Origin" => "*", "X-My-Custom-Header" => "hello"})
end
end
context 'with more events' do
let!(:event4) do
agents(:bob_website_agent).create_event payload: {
'site_title' => 'XKCD',
'url' => 'http://imgs.xkcd.com/comics/comic1.png',
'title' => 'Comic 1',
'date' => '',
'hovertext' => 'Hovertext for Comic 1'
}
end
let!(:event5) do
agents(:bob_website_agent).create_event payload: {
'site_title' => 'XKCD',
'url' => 'http://imgs.xkcd.com/comics/comic2.png',
'title' => 'Comic 2',
'date' => '',
'hovertext' => 'Hovertext for Comic 2'
}
end
let!(:event6) do
agents(:bob_website_agent).create_event payload: {
'site_title' => 'XKCD',
'url' => 'http://imgs.xkcd.com/comics/comic3.png',
'title' => 'Comic 3',
'date' => '',
'hovertext' => 'Hovertext for Comic 3'
}
end
describe 'limiting' do
it 'can select the last `events_to_show` events' do
agent.options['events_to_show'] = 2
content, _status, _content_type = agent.receive_web_request({ 'secret' => 'secret2' }, 'get', 'application/json')
expect(content['items'].map {|i| i["title"] }).to eq(["Comic 3", "Comic 2"])
end
end
end
describe 'ordering' do
before do
agent.options['events_order'] = ['{{hovertext}}']
agent.options['events_list_order'] = ['{{title}}']
end
it 'can reorder the last `events_to_show` events based on a Liquid expression' do
agent.options['events_to_show'] = 2
asc_content, _status, _content_type = agent.receive_web_request({ 'secret' => 'secret2' }, 'get', 'application/json')
expect(asc_content['items'].map {|i| i["title"] }).to eq(["Evolving", "Evolving again"])
agent.options['events_to_show'] = 40
asc_content, _status, _content_type = agent.receive_web_request({ 'secret' => 'secret2' }, 'get', 'application/json')
expect(asc_content['items'].map {|i| i["title"] }).to eq(["Evolving", "Evolving again", "Evolving yet again with a past date"])
agent.options['events_list_order'] = [['{{title}}', 'string', true]]
desc_content, _status, _content_type = agent.receive_web_request({ 'secret' => 'secret2' }, 'get', 'application/json')
expect(desc_content['items']).to eq(asc_content['items'].reverse)
end
end
describe "interpolating \"events\"" do
before do
agent.options['template']['title'] = "XKCD comics as a feed{% if events.first.site_title %} ({{events.first.site_title}}){% endif %}"
agent.save!
end
it "can output RSS" do
stub(agent).feed_link { "https://yoursite.com" }
content, status, content_type = agent.receive_web_request({ 'secret' => 'secret1' }, 'get', 'text/xml')
expect(status).to eq(200)
expect(content_type).to eq('application/rss+xml')
expect(Nokogiri(content).at('/rss/channel/title/text()').text).to eq('XKCD comics as a feed (XKCD)')
end
it "can output JSON" do
content, status, content_type = agent.receive_web_request({ 'secret' => 'secret2' }, 'get', 'application/json')
expect(status).to eq(200)
expect(content['title']).to eq('XKCD comics as a feed (XKCD)')
end
context "with event with \"events\"" do
before do
agent.sources.first.create_event payload: {
'site_title' => 'XKCD',
'url' => 'http://imgs.xkcd.com/comics/comicX.png',
'title' => 'Comic X',
'date' => '',
'hovertext' => 'Hovertext for Comic X',
'events' => 'Events!'
}
agent.options['template']['item']['events_data'] = "{{ events }}"
agent.save!
end
it "can access the value without being overridden" do
content, status, content_type = agent.receive_web_request({ 'secret' => 'secret2' }, 'get', 'application/json')
expect(status).to eq(200)
expect(content['items'].first['events_data']).to eq('Events!')
end
end
end
describe "with a specified icon" do
before do
agent.options['template']['icon'] = 'https://somesite.com/icon.png'
agent.save!
end
it "can output RSS" do
stub(agent).feed_link { "https://yoursite.com" }
content, status, content_type = agent.receive_web_request({ 'secret' => 'secret1' }, 'get', 'text/xml')
expect(status).to eq(200)
expect(content_type).to eq('application/rss+xml')
expect(Nokogiri(content).at('/rss/channel/atom:icon/text()').text).to eq('https://somesite.com/icon.png')
end
end
describe "with media namespace not set" do
before do
agent.options['ns_media'] = nil
agent.save!
end
it "can output RSS" do
stub(agent).feed_link { "https://yoursite.com" }
content, status, content_type = agent.receive_web_request({ 'secret' => 'secret1' }, 'get', 'text/xml')
expect(status).to eq(200)
expect(content_type).to eq('application/rss+xml')
doc = Nokogiri(content)
namespaces = doc.collect_namespaces
expect(namespaces).not_to include("xmlns:media")
end
end
describe "with media namespace set true" do
before do
agent.options['ns_media'] = 'true'
agent.save!
end
it "can output RSS" do
stub(agent).feed_link { "https://yoursite.com" }
content, status, content_type = agent.receive_web_request({ 'secret' => 'secret1' }, 'get', 'text/xml')
expect(status).to eq(200)
expect(content_type).to eq('application/rss+xml')
doc = Nokogiri(content)
namespaces = doc.collect_namespaces
expect(namespaces).to include(
"xmlns:media" => 'http://search.yahoo.com/mrss/'
)
end
end
describe "with media namespace set false" do
before do
agent.options['ns_media'] = 'false'
agent.save!
end
it "can output RSS" do
stub(agent).feed_link { "https://yoursite.com" }
content, status, content_type = agent.receive_web_request({ 'secret' => 'secret1' }, 'get', 'text/xml')
expect(status).to eq(200)
expect(content_type).to eq('application/rss+xml')
doc = Nokogiri(content)
namespaces = doc.collect_namespaces
expect(namespaces).not_to include("xmlns:media")
end
end
describe "with itunes namespace not set" do
before do
agent.options['ns_itunes'] = nil
agent.save!
end
it "can output RSS" do
stub(agent).feed_link { "https://yoursite.com" }
content, status, content_type = agent.receive_web_request({ 'secret' => 'secret1' }, 'get', 'text/xml')
expect(status).to eq(200)
expect(content_type).to eq('application/rss+xml')
doc = Nokogiri(content)
namespaces = doc.collect_namespaces
expect(namespaces).not_to include("xmlns:itunes")
expect(doc.at("/rss/channel/*[local-name()='itunes:image']")).to be_nil
end
end
describe "with itunes namespace set true" do
before do
agent.options['ns_itunes'] = 'true'
agent.save!
end
it "can output RSS" do
stub(agent).feed_link { "https://yoursite.com" }
content, status, content_type = agent.receive_web_request({ 'secret' => 'secret1' }, 'get', 'text/xml')
expect(status).to eq(200)
expect(content_type).to eq('application/rss+xml')
doc = Nokogiri(content)
namespaces = doc.collect_namespaces
expect(namespaces).to include(
"xmlns:itunes" => 'http://www.itunes.com/dtds/podcast-1.0.dtd'
)
expect(doc.at('/rss/channel/itunes:image').attr('href')).to eq('https://yoursite.com/favicon.ico')
end
end
describe "with itunes namespace set false" do
before do
agent.options['ns_itunes'] = 'false'
agent.save!
end
it "can output RSS" do
stub(agent).feed_link { "https://yoursite.com" }
content, status, content_type = agent.receive_web_request({ 'secret' => 'secret1' }, 'get', 'text/xml')
expect(status).to eq(200)
expect(content_type).to eq('application/rss+xml')
doc = Nokogiri(content)
namespaces = doc.collect_namespaces
expect(namespaces).not_to include("xmlns:itunes")
end
end
end
describe "outputting nesting" do
before do
agent.options['template']['item']['enclosure'] = {
"_attributes" => {
"type" => "audio/mpeg",
"url" => "{{media_url}}"
}
}
agent.options['template']['item']['foo'] = {
"_attributes" => {
"attr" => "attr-value-{{foo}}"
},
"_contents" => "Foo: {{foo}}"
}
agent.options['template']['item']['nested'] = {
"_attributes" => {
"key" => "value"
},
"_contents" => {
"title" => "some title"
}
}
agent.options['template']['item']['simpleNested'] = {
"title" => "some title",
"complex" => {
"_attributes" => {
"key" => "value"
},
"_contents" => {
"first" => {
"_attributes" => {
"a" => "b"
},
"_contents" => {
"second" => "value"
}
}
}
}
}
agent.save!
end
let!(:event) do
agents(:bob_website_agent).create_event :payload => {
"url" => "http://imgs.xkcd.com/comics/evolving.png",
"title" => "Evolving",
"hovertext" => "Biologists play reverse Pokemon, trying to avoid putting any one team member on the front lines long enough for the experience to cause evolution.",
"media_url" => "http://google.com/audio.mpeg",
"category" => ["Category 1", "Category 2"],
"foo" => 1
}
end
it "can output JSON" do
content, status, content_type = agent.receive_web_request({ 'secret' => 'secret2' }, 'get', 'application/json')
expect(status).to eq(200)
expect(content['items'].first).to eq(
{
'title' => 'Evolving',
'description' => 'Secret hovertext: Biologists play reverse Pokemon, trying to avoid putting any one team member on the front lines long enough for the experience to cause evolution.',
'link' => 'http://imgs.xkcd.com/comics/evolving.png',
'guid' => {"contents" => event.id, "isPermaLink" => "false"},
'pubDate' => event.created_at.rfc2822,
'category' => ['Category 1', 'Category 2'],
'enclosure' => {
"type" => "audio/mpeg",
"url" => "http://google.com/audio.mpeg"
},
'foo' => {
'attr' => 'attr-value-1',
'contents' => 'Foo: 1'
},
'nested' => {
"key" => "value",
"title" => "some title"
},
'simpleNested' => {
"title" => "some title",
"complex" => {
"key"=>"value",
"first" => {
"a" => "b",
"second"=>"value"
}
}
}
}
)
end
it "can output RSS" do
stub(agent).feed_link { "https://yoursite.com" }
content, status, content_type = agent.receive_web_request({ 'secret' => 'secret1' }, 'get', 'text/xml')
expect(status).to eq(200)
expect(content_type).to eq('application/rss+xml')
expect(content.gsub(/\s+/, '')).to eq Utils.unindent(<<-XML).gsub(/\s+/, '')
https://yoursite.com/favicon.ico
XKCD comics as a feed
This is a feed of recent XKCD comics, generated by Huginn
https://yoursite.com
#{Time.now.rfc2822}
#{Time.now.rfc2822}
60
-
Evolving
Secret hovertext: Biologists play reverse Pokemon, trying to avoid putting any one team member on the front lines long enough for the experience to cause evolution.
http://imgs.xkcd.com/comics/evolving.png
#{event.created_at.rfc2822}
Category 1
Category 2
Foo: 1
some title
some title
value
#{event.id}
XML
end
end
end
end