DC 1'
end
def with_env(vars)
old = vars.to_h { |k, _| [k, ENV[k]] }
vars.each { |k, v| ENV[k] = v }
yield
ensure
old.each { |k, v| ENV[k] = v }
end
def test_indexnow_key_file
get '/0123456789abcdef.txt'
assert_equal 404, last_response.status
with_env('INDEXNOW_KEY' => '0123456789abcdef') do
get '/0123456789abcdef.txt'
assert last_response.ok?
assert_equal '0123456789abcdef', last_response.body
get '/fedcba9876543210.txt'
assert_equal 404, last_response.status
end
end
def test_root_varies_on_language
get '/'
assert_equal 'Accept-Language', last_response.headers['Vary']
end
def test_seo_head
hit!(3)
@dcs[3].update(photo_key: 'bingo/dc-4-0123abcd.jpg', photo_variants: true)
@dcs[0].update(info_url: 'https://example.com/dc-1')
get '/en'
body = last_response.body
assert_includes body, '
Datacenter Bingo — strikes'
assert_match %r{Datacenter Bingo Drone strikes, fires and outages at Russia's 25 largest datacenters
}, body
assert_includes body, '1 of Russia's 25 largest'
assert_includes body, ''
assert_includes body, 'src="/p/dc-4-0123abcd-480.webp"'
refute_includes body, 'og:image:width'
@dcs[3].update(photo_og: true)
get '/en'
body = last_response.body
assert_includes body, ''
assert_includes body, ''
assert_includes body, ''
assert_includes body, 'og:locale:alternate" content="uk_UA"'
ld = JSON.parse(body[%r{}m, 1])
list = ld['@graph'].find { |n| n['@type'] == 'ItemList' }
assert_equal 25, list['itemListElement'].size
assert_equal 'DC 1', list['itemListElement'][0]['item']['name']
hit = list['itemListElement'][3]['item']
assert_equal Date.today.iso8601, hit['event'][0]['startDate']
assert_equal 'Outage: Авария 3', hit['event'][0]['name']
refute list['itemListElement'][0]['item'].key?('event')
assert_equal({ '@type' => 'CreativeWork', 'url' => 'https://example.com/dc-1' }, list['itemListElement'][0]['item']['subjectOf'])
refute_includes body, 'sameAs'
assert list['dateModified']
end
def test_photo_falls_back_to_original_without_variants
@dcs[0].update(photo_key: 'bingo/dc-1-0123abcd.jpg', photo_variants: false)
assert_equal 'dc-1-0123abcd.jpg', @dcs[0].photo_file(480)
@dcs[0].update(photo_variants: true)
assert_equal 'dc-1-0123abcd-480.webp', @dcs[0].photo_file(480)
assert_equal 'dc-1-0123abcd.jpg', @dcs[0].photo_file
end
def test_photo_served_from_bucket
Aws.config[:s3] = { stub_responses: { get_object: { body: JPEG } } }
S3.instance_variable_set(:@client, nil)
@dcs[0].update(photo_key: 'bingo/dc-1-0123abcd.jpg')
get "/p/#{@dcs[0].photo_file}"
assert last_response.ok?
assert_equal 'image/jpeg', last_response.content_type
assert_includes last_response.headers['cache-control'], 'immutable'
get '/p/..%2Finvoices%2Fx.pdf'
assert_equal 404, last_response.status
ensure
Aws.config[:s3] = { stub_responses: true }
S3.instance_variable_set(:@client, nil)
end
def test_dc_about_text
@dcs[1].update(about_en: "First para.\r\n\r\nSecond.")
get '/en/dc/dc-2'
assert_includes last_response.body, 'First <b>para</b>.
Second.
'
get '/ru/dc/dc-2'
refute_includes last_response.body, 'Second.' # no fallback across languages
end
def test_footer_links_to_source
{ 'ru' => 'Исходный код на GitHub', 'en' => 'Source code on GitHub', 'uk' => 'Вихідний код на GitHub' }.each do |l, text|
get "/#{l}"
assert_includes last_response.body, %(#{text})
end
get '/en/dc/dc-1'
assert_includes last_response.body, 'github.com/asxpi/datacenterbingo'
end
def test_rss_feed
old = hit!(1, kind: 'fire')
old.update(occurred_on: Date.new(2026, 1, 2), url: 'https://example.com/fire?a=1&b=2')
hit!(3, kind: 'strike')
get '/en/feed.xml'
assert last_response.ok?
assert_equal 'application/rss+xml;charset=utf-8', last_response.content_type
require 'rexml/document'
doc = REXML::Document.new(last_response.body)
assert_equal 'Datacenter Bingo — Incident log', doc.elements['rss/channel/title'].text
items = doc.get_elements('rss/channel/item')
assert_equal 2, items.size
assert_equal 'Drone strike: ЦОД 4 — Авария 3', items[0].elements['title'].text # newest first, ru fallback
assert_equal 'https://example.org/en/dc/dc-2', items[1].elements['link'].text
assert_equal "tag:example.org,2026:incident-#{old.id}", items[1].elements['guid'].text
assert_equal 'Fri, 02 Jan 2026 00:00:00 -0000', items[1].elements['pubDate'].text
assert_includes items[1].elements['description'].text, 'Source'
get '/uk/feed.xml'
assert_includes last_response.body, 'Бінго ЦОДів — Журнал інцидентів'
get '/xx/feed.xml'
assert_equal 404, last_response.status
end
def test_json_feed
hit!(0, kind: 'outage').update(url: 'https://example.com/n')
@dcs[0].update(provider: 'yandex', photo_key: 'bingo/dc-1-0123abcd.jpg', photo_variants: true)
get '/ru/feed.json'
assert last_response.ok?
assert_equal 'application/feed+json;charset=utf-8', last_response.content_type
assert_equal '*', last_response.headers['access-control-allow-origin']
feed = JSON.parse(last_response.body)
assert_equal 'https://jsonfeed.org/version/1.1', feed['version']
assert_equal 'https://example.org/ru/feed.json', feed['feed_url']
item = feed['items'].fetch(0)
assert_equal 'Отключение: ЦОД 1 — Авария 0', item['title']
assert_equal 'https://example.org/ru/dc/dc-1', item['url']
assert_equal 'https://example.com/n', item['external_url']
assert_equal 'https://example.org/p/dc-1-0123abcd-1200.webp', item['image']
assert_equal %w[Отключение Яндекс], item['tags']
assert_equal "#{Date.today.iso8601}T00:00:00Z", item['date_published']
end
def test_open_dataset
hit!(4, kind: 'fire').update(url: 'https://example.com/f', title_en: 'Fire')
@dcs[4].update(mw: '25–30', mw_type: 'it', provider: 'rtk')
get '/data.json'
assert last_response.ok?
assert_equal '*', last_response.headers['access-control-allow-origin']
data = JSON.parse(last_response.body)
assert_equal 'CC-BY-4.0', data['license']
assert_includes data['attribution'], 'Cat Eyes OSINT'
assert_equal 25, data['datacenters'].size
dc = data['datacenters'].find { |d| d['slug'] == 'dc-5' }
assert_equal 5, dc['rank']
assert_equal 30.0, dc['mw_value']
assert_equal({ 'ru' => 'ЦОД 5', 'en' => 'ЦОД 5', 'uk' => 'ЦОД 5' }, dc['name'])
assert_equal 'https://example.org/uk/dc/dc-5', dc['pages']['uk']
assert_equal [{ 'date' => Date.today.iso8601, 'kind' => 'fire',
'title' => { 'ru' => 'Авария 4', 'en' => 'Fire', 'uk' => 'Аварія 4' },
'source_url' => 'https://example.com/f' }], dc['incidents']
end
def test_pages_link_the_feeds
get '/en'
body = last_response.body
assert_includes body, ''
assert_includes body, 'type="application/feed+json"'
assert_includes body, 'RSS · JSON Feed · Open data (CC BY 4.0)'
get '/ru/dc/dc-1'
assert_includes last_response.body, 'href="https://example.org/ru/feed.xml"'
end
def test_static_files_not_served
get '/style.css'
assert_equal 404, last_response.status
end
def test_healthz
get '/healthz'
assert last_response.ok?
assert_equal 'ok', last_response.body
end
def test_www_redirects_to_canonical_host
ENV['BINGO_HOST'] = 'bingo.example'
app = Rack::Builder.parse_file(File.join(__dir__, '..', 'config.ru'))
res = Rack::MockRequest.new(app).get('http://www.bingo.example/en?view=all')
assert_equal 301, res.status
assert_equal 'https://bingo.example/en?view=all', res.location
ensure
ENV.delete('BINGO_HOST')
end
end