Browse Source

Fetch place from Bragi: make tests pass or ignore outdated tests

tags/0.3.6^2
Bèr Kessels 4 months ago
parent
commit
4cc154744c

+ 10
- 2
app/models/base.rb View File

@@ -5,6 +5,8 @@ module Hours
##
# Base model that can read from a query database
class Base
attr_accessor :error

def initialize(attributes = {}, ignore_undefined_attributes = false)
attributes.each do |key, value|
setter = "#{key}="
@@ -15,8 +17,8 @@ module Hours
end

def self.from_geojson_feature(feature)
attrs = feature.properties.fetch('geocoding', {})
tags = attrs.fetch('properties', [])
attrs = feature.properties.fetch('geocoding', {}) || {}
tags = attrs.fetch('properties', []) || []

# Turn # [{ key => 'name', value => 'someval' }] into
# { 'name' => someval }
@@ -24,6 +26,12 @@ module Hours

new(extra_props.merge(attrs), true)
end

protected

def null_island
GeoRuby::SimpleFeatures::Point.from_xy(0, 0)
end
end
end
end

+ 13
- 2
app/models/place.rb View File

@@ -6,6 +6,13 @@ require_relative 'base.rb'

module Hours
module Models
# Utility class to hold a place that is empty
class NullPlace < Base
def region
''
end
end

##
# A Place is a readonly model for querying the projection of "places".
# * Readonly: it is not enforced on model or ORM level, but should be
@@ -15,9 +22,9 @@ module Hours
ITERATOR = OpeningHoursConverter::Iterator.new
PARSER = OpeningHoursConverter::OpeningHoursParser.new

attr_accessor :id, :geometry, :place_id, :name,
attr_accessor :id, :place_id, :name,
:region_slug, :distance, :website
attr_writer :address, :opening_hours
attr_writer :address, :opening_hours, :geometry

def status
return :unknown if opening_hours&.empty?
@@ -50,6 +57,10 @@ module Hours
@opening_hours || ''
end

def geometry
@geometry || null_island
end

private

def get_intervals_as_week(date_ranges, date_in_week)

+ 0
- 7
app/models/place_index.rb View File

@@ -7,13 +7,6 @@ require_relative 'place.rb'

module Hours
module Models
# Utility class to hold a place that is empty
class NullPlace
def region
''
end
end

##
# A collection of places
class PlaceIndex < BaseCollection

+ 16
- 13
app/projections/query.rb View File

@@ -32,23 +32,26 @@ module Hours
end

# Query handler that queries the projection table for a single node
class PlaceQuery < BaseQuery
def handle(id)
result = dataset[id: id]
Hours::Models::Place.new(result) if result
class PlaceQuery
def self.build
faraday = Faraday.new(url: ENV['BRAGI_URL']) do |conn|
conn.response :raise_error
conn.adapter Faraday.default_adapter
end
new(faraday)
end

def uuid_for(place_id)
dataset.where(place_id: place_id).get(:id)
def initialize(http_client)
@http_client = http_client
end
end

# Query handler that queries the projection table for all nodes
class PlacesQuery < BaseQuery
def handle(page = 1)
@page = page
pagy, records = pagy(dataset)
Hours::Models::PlaceIndex.new(records, pagy)
def handle(id)
response = @http_client.get("features/#{id}")
@place = Hours::Models::Place.from_geojson_feature(
RGeo::GeoJSON.decode(response.body).first
)
rescue Faraday::ServerError, JSON::ParserError => e
Hours::Models::NullPlace.new(error: e.message)
end
end


+ 2
- 2
app/views/place.erb View File

@@ -1,9 +1,9 @@
<% set_content_for(:closing_js,
"var map = L.map('map').setView([#{@place.location.y}, #{@place.location.x}], 16);
"var map = L.map('map').setView([#{@place.geometry.y}, #{@place.geometry.x}], 16);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors'
}).addTo(map);
L.marker([#{@place.location.y}, #{@place.location.x}]).addTo(map);"
L.marker([#{@place.geometry.y}, #{@place.geometry.x}]).addTo(map);"
)
%>
<% set_content_for(:region_name, @place.address.city) %>

+ 1
- 25
lib/app.rb View File

@@ -107,11 +107,6 @@ module Hours
settings.sprockets_env.call(env)
end

get '/places' do
places_index = Hours::Projections::PlacesQuery.build.handle(page_param)
body render_json(places_index.to_a, page_links(places_index.paginator))
end

put '/places' do
command = Hours::AddPlaceCommand.build(json_params)
Hours::CommandHandler.new.handle(command)
@@ -137,29 +132,10 @@ module Hours
end

get %r{/places/(?<id>[\w:]+)}, provides: 'html' do
# TODO: move into query.
faraday = Faraday.new(url: ENV['BRAGI_URL']) do |conn|
conn.response :raise_error
conn.adapter Faraday.default_adapter
end
response = faraday.get("features/#{params[:id]}")
@place = Hours::Models::Place.from_geojson_feature(
RGeo::GeoJSON.decode(response.body).first
)
@place = Hours::Projections::PlaceQuery.build.handle(params[:id])
erb :place
rescue Faraday::ResourceNotFound
404
end

get %r{/places/(?<uuid>#{UUID_REGEX})}, provides: 'json' do
handle_get_place do
body render_json(@place)
end
end

get '/places/:place_id', provides: 'json' do
uuid = Hours::Projections::PlaceQuery.build.uuid_for(params[:place_id])
redirect to("/places/#{uuid}"), 303
end
end
end

+ 1
- 0
test/integration/api/add_places_test.rb View File

@@ -4,6 +4,7 @@ require 'test_helper'
require_relative Hours.base_path.join('test/support/workflows/add_region.rb')

describe 'add place' do
before { skip 'Reimplement a create that posts to bragi' }
describe 'POST /places' do
let(:lat) { 51.84 }
let(:lon) { 5.86 }

+ 0
- 133
test/integration/api/view_places_test.rb View File

@@ -1,133 +0,0 @@
# frozen_string_literal: true

require 'test_helper'
require 'json_expressions/minitest'
require 'timecop'

require_relative Hours.base_path.join('test/support/workflows/add_place.rb')

describe 'api view places' do
include Workflows::AddPlace
let(:input) { [json_fixtures('input/hm_burchtstraat.json')] }

before do
header('Accept', 'application/json')
end

describe 'GET /places' do
it 'returns a list of places' do
Timecop.travel(die_wende) do
assert Time.now.hour < 21 # Before 21:00
assert Time.now.thursday?

get '/places'
assert_status(200)

expected = json_fixtures('output/places.json')
expected[:data][0][:links][:self] = String
expected[:data][0][:id] = String

parsed = JSON.parse(last_response.body, symbolize_names: true)

assert_json_match(expected, parsed)
end
end

describe 'with 21 places' do
let(:input) { list_of_geojson(21) }

it 'paginates the places' do
get '/places'

parsed = JSON.parse(last_response.body, symbolize_names: true)
assert_equal parsed[:data].length, 20

expected = json_fixtures('output/places.json')
expected[:data] = [].ignore_extra_values
expected[:links][:last] = '/places?page=2'
expected[:links][:next] = '/places?page=2'

assert_json_match(expected, parsed)

# Move to the second page
get parsed[:links][:next]
parsed = JSON.parse(last_response.body, symbolize_names: true)
expected[:links][:self] = '/places?page=2'
expected[:links][:last] = '/places?page=2'
expected[:links][:prev] = '/places?page=1'
expected[:links][:next] = nil
assert_json_match(expected, parsed)

assert_equal parsed[:data].length, 1
end
end

it 'on a thursday, after 21:00 it is closed' do
after_hours = die_wende + (60 * 60 * 4)
Timecop.travel(after_hours) do
assert Time.now.hour > 21 # After 21:00

get '/places'

assert_status(200)
parsed = JSON.parse(last_response.body, symbolize_names: true)

expected = {
"data": [
{
attributes: { status: 'closed' }.ignore_extra_keys!
}.ignore_extra_keys!
]
}.ignore_extra_keys!

assert_json_match(expected, parsed)
end
end
end

describe 'GET places/:uuid' do
it 'shows details of a place by UUID' do
Timecop.travel(die_wende) do
get "/places/#{hm_id}"
assert_status(200)

expected = json_fixtures('output/hm_burchtstraat.json')
expected[:data][:links][:self] = String
expected[:data][:id] = String
assert_json_match(expected, last_response.body)
end
end
end

describe 'GET places/:place_id' do
it 'forwards client with 303 to place by UUID' do
place = Hours::Aggregates::Place.new(hm_id, [])
# Extract the place_id from the place aggregate
changes = place.add(json_fixtures('input/hm_burchtstraat.json')).changes
place_id = changes.first.body['properties']['place_id']

get "/places/#{place_id}"
assert_status(303)
assert_includes(last_response.headers['Location'], "/places/#{hm_id}")
end
end

describe 'unparsable opening hours' do
let(:input) { [json_fixtures('input/hacked.json')] }

it 'handles unparsable opening hours strings' do
get "/places/#{hm_id}"
assert_status(200)

expected = {
'data' => {
'attributes' => {
'status' => 'unknown', # Should default to false
'open_this_week' => {} # Should defailt to an empty week hash
}.ignore_extra_keys!
}.ignore_extra_keys!
}.ignore_extra_keys!
assert_json_match(expected, last_response.body)
end
end
end

+ 4
- 1
test/integration/web/view_regions_test.rb View File

@@ -9,7 +9,10 @@ describe 'web views regions' do
include WebTestHelpers
include Workflows::AddPlace

before { Workflows::AddRegion.new(self).call }
before do
skip
Workflows::AddRegion.new(self).call
end

describe 'GET /in/nijmegen' do
let(:regions) { [json_fixtures('input/nijmegen_city.json')] }

+ 14
- 0
test/models/place_test.rb View File

@@ -50,6 +50,20 @@ describe Hours::Models::Place do
end
end

describe 'geometry' do
let(:nijmegen) { GeoRuby::SimpleFeatures::Point.from_xy(51.8425, 5.85278) }

it 'takes the value if set' do
subject.geometry = nijmegen
assert_equal nijmegen, subject.geometry
end

it 'falls back to null_island if not set' do
subject.geometry = nil
assert_equal null_island, subject.geometry
end
end

describe 'opening_hours' do
let(:opening_hours) { '1989 Mo-Fr 10:00-12:30,13:00-19:00' }


+ 39
- 42
test/projections/query_test.rb View File

@@ -1,65 +1,63 @@
# frozen_string_literal: true

# TODO: split into separate unit files

require 'test_helper'
require 'geo_ruby/geojson'

require_relative Hours.base_path.join('test/support/workflows/add_place.rb')
require_relative Hours.base_path.join('test/support/workflows/add_region.rb')

describe Hours::Projections::PlacesQuery do
# TODO: we might not need to run through the entire event process here.
# instead, we could insert data directly into the query database.
include Workflows::AddPlace
let(:hm) { json_fixtures('input/hm_burchtstraat.json') }
let(:input) { [hm] }

subject do
Hours::Projections::PlacesQuery.build
end

it '#handle fetches a list of Places through Sequel model' do
assert_kind_of Hours::Models::PlaceIndex, subject.handle
assert_kind_of Hours::Models::Place, subject.handle.first
describe Hours::Projections::PlaceQuery do
let(:body) do
{
type: 'FeatureCollection',
features: [
{ type: 'Feature',
geometry: { type: 'Point', coordinates: [102.0, 0.5] },
properties: {} }
]
}
end

it 'pages all from :query_places in pages of 20' do
20.times.each do
assert subject.dataset.insert(
id: SecureRandom.uuid,
place_id: SecureRandom.hex,
region_slug: ''
)
end
let(:bragi_response) { OpenStruct.new(body: body.to_json, code: 200) }
let(:emtpy_response) { OpenStruct.new(body: '{}', code: 200) }
let(:error_response) { OpenStruct.new(body: '', code: 500) }

assert_equal 21, Hours::Projections::PlacesQuery.build.dataset.count
let(:place_id) { 'poi:osm:node:989225414' }

assert_equal 20, subject.handle(1).count
let(:http_client) do
Minitest::Mock.new
end
end

describe Hours::Projections::PlaceQuery do
include Workflows::AddPlace
let(:input) { [json_fixtures('input/hm_burchtstraat.json')] }

subject do
Hours::Projections::PlaceQuery.build
Hours::Projections::PlaceQuery.new(http_client)
end

it '#handle fetches a single Place' do
assert_kind_of Hours::Models::Place, subject.handle(aggregate_id)
http_client.expect(:get, bragi_response, ["features/#{place_id}"])
assert_kind_of Hours::Models::Place, subject.handle(place_id)
assert_mock http_client
end

it '#uuid_for fetches the UUID for a place_id' do
place_id = subject.handle(aggregate_id).place_id
refute_nil place_id
assert_equal aggregate_id, subject.uuid_for(place_id)
it 'handles broken body from HTTP service' do
http_client.expect(:get, error_response, [String])
assert_kind_of Hours::Models::NullPlace, subject.handle(place_id)
assert_mock http_client
end

it 'handles HTTP errors from HTTP service' do
error = ->(_args) { raise Faraday::ServerError, '503' }
http_client = OpenStruct.new(get: '')

http_client.stub(:get, error) do
result = Hours::Projections::PlaceQuery.new(http_client).handle('')
assert_equal '503', result.error
end
end
end

describe Hours::Projections::RegionQuery do
include Workflows::AddPlace
let(:input) { [json_fixtures('input/hm_burchtstraat.json')] }

before do
skip 'Implement region query from bragi'
end
let(:regions) do
[
json_fixtures('input/nijmegen_city.json'),
@@ -73,7 +71,6 @@ describe Hours::Projections::RegionQuery do
subject do
Hours::Projections::RegionQuery.build
end
before { Workflows::AddRegion.new(self).call }

it '#handle fetches a Region' do
assert_kind_of Hours::Models::Region, subject.handle('nijmegen')

Loading…
Cancel
Save