require File.join(File.dirname(__FILE__), "test_helper")
# spec sketch is here:
# http://mulube.unfuddle.com/projects/9384/notebooks/2319/pages/102977/latest
#TODO: split this into separate files, testing the xml parsing, the xml generation, etc.
class TestEnvironment < Test::Unit::TestCase
include LibXMLTestHelper
#TODO: this is mostly testing eeml V005 parser, not environment generally
test "creating Env obj from unparseable XML" do
assert_raises BadXML do
environment = Environment.new_from_eeml("this is bad xml")
end
assert_raises BadXML do
environment = Environment.new_from_eeml("12, 14, 99, a csv!, 99")
end
end
# Create an object from an eeml document
test 'creating Env obj - attrs' do
env = create_env_from_xml_file_one
# simple text-nodes available as attributes
assert_equal('title here', env.title)
assert_equal('description here', env.description)
assert_equal('http://example.com/api/1247.xml', env.feed_url)
assert_equal('http://example.com/studio/', env.website)
assert_equal('frozen', env.status)
assert_equal('someone@example.com', env.email)
assert_equal('http://example.com/some/icon.gif', env.icon)
#env attrs
assert_equal('1247', env.identifier)
assert_equal('2009-02-11T10:56:56Z', env.updated.strftime("%Y-%m-%dT%H:%M:%SZ"))
assert_equal('http://example.com/creator/', env.creator)
end
test 'location parses ok' do
env = create_env_from_xml_file_one
#
# Up on the roof (somewhere)
# 50.1
# 48.7
# 1.34
#
assert_not_nil env.location
location = env.location
assert_equal "Up on the roof (somewhere)", location.name
assert_equal "50.1", location.latitude
assert_equal "48.7", location.longitude
assert_equal "1.34", location.elevation
assert_equal "physical", location.domain
assert_equal "outdoor", location.exposure
assert_equal "mobile", location.disposition
end
test "datastreams parse ok" do
env = create_env_from_xml_file_one
assert_equal 3, env.datastreams.size
#the expected values for the datastreams in the test doc
expectations_list = [
#id, tag array, min, max, value, unit_symbol, unit_type, unit_value
['0', ['tagD0'], '-9999.0', '1022.0', '0', 'C', 'basicSI', 'Celsius'],
['1', [], '0.0', '1023.0', '33', nil, nil, nil],
['2', ['tagD2a', 'tagD2b', 'tagD2c'], '23.4', '1021.0', '42.1', nil, nil, nil]
]
env.datastreams.each_with_index do |ds, i|
e_identifier, e_tags, e_min_value, e_max_value, e_value, e_unit_symbol, e_unit_type, e_unit_value = expectations_list[i]
assert_equal e_identifier, ds.identifier
assert_equal e_tags.size, ds.tags.size, "should have right num of tags"
e_tags.each do |expected_tag|
ds.tags.member? expected_tag
end
assert_equal e_min_value, ds.min_value
assert_equal e_max_value, ds.max_value
assert_equal e_value, ds.value
assert_equal e_unit_symbol, ds.unit_symbol
assert_equal e_unit_type, ds.unit_type
assert_equal e_unit_value, ds.unit_value
end
end
implement "should not try to parse huge xml string" do
#TODO: arguably, fetcher should also have rejected huge string
end
# should this actually pass? Location is essentially a datastream in it's own right, so a feed
# that only specified location should probably be alright.
test "passes if no datastreams" do
original_xml = File.read('test/data/doc_1.xml')
test_xml = remove_nodes_called('data', original_xml)
environment = Environment.new_from_eeml(test_xml)
assert_equal('title here', environment.title)
end
test "fail if any datastream has no value" do
test_xml = remove_first_node_called('value', read_xml_file_one)
assert_raises(DataMissingValue) do
Environment.new_from_eeml(test_xml)
end
end
test "fail if any datastream has two values" do
new_value_snippet=<<-END
10
14
END
original_xml = read_xml_file_one
test_xml = replace_first_node_called('value', original_xml, new_value_snippet)
assert_raises(DataHasMultipleValues) { Environment.new_from_eeml(test_xml) }
end
test "fail if any datastream has two units" do
new_unit_snippet =<<-END
Celsius
Celsius
END
original_xml = read_xml_file_one
test_xml = replace_first_node_called('unit', original_xml, new_unit_snippet)
assert_raises(DataHasMultipleUnits) { Environment.new_from_eeml(test_xml) }
end
test "ok if no location" do
original_xml = read_xml_file_one
test_xml = remove_first_node_called('location', original_xml)
env = Environment.new_from_eeml(test_xml)
# all other attributes still accessible
assert_equal('title here', env.title)
assert_equal('description here', env.description)
assert_equal('http://example.com/api/1247.xml', env.feed_url)
assert_equal('http://example.com/studio/', env.website)
assert_equal('frozen', env.status)
assert_equal('someone@example.com', env.email)
assert_equal('http://example.com/some/icon.gif', env.icon)
#env attrs
assert_equal('1247', env.identifier)
assert_equal('2009-02-11T10:56:56Z', env.updated.strftime(XML_TIME_FORMAT_STRING))
assert_equal('http://example.com/creator/', env.creator)
end
test "ok parsing most minimal eeml file" do
env = create_env_from_xml_file('minimal.xml')
assert_equal 1, env.datastreams.size
datastream = env.datastreams[0]
assert_equal "0", datastream.identifier
# this value.value is a bit ugly
assert_equal "36.2", datastream.value
end
test "fails if no default namespace provided" do
assert_raise(MissingNamespace) {env = create_env_from_xml_file('no_namespace.xml')}
end
test "creates multiple environments from list xml" do
xml = File.read('test/data/list.xml')
list = Environment.new_list_from_eeml(xml)
assert 3, list.size
list.each do |obj|
assert obj.is_a? Environment
end
titles = list.map {|e| e.title}.sort
expected_titles = ["Badgerpower", "CurrentCost for house", "Power and Temperature"]
assert_equal expected_titles, titles
#TODO: check the environments fully (that no aliasing or other confusion has occurred in their parsing)
end
test "creates zero environments from list xml when has none" do
original_xml = File.read('test/data/list.xml')
xml = remove_nodes_called('environment', original_xml)
list = Environment.new_list_from_eeml(xml)
assert list.empty?, "should be empty list"
end
def read_xml_file_one
return File.read('test/data/doc_1.xml')
end
test "parses_all_previously_acceptable_feeds" do
passed_filenames = []
dir_name = 'test/data/real_xmls/v005/oks/'
#check dir exists:
filenames = Dir.entries(dir_name).select{|name| name =~ /.xml/}.sort
assert filenames.size > 1, "should be at least one file found in #{dir_name}"
filenames.each do |filename|
begin
xml = File.read(File.join(dir_name, filename))
env = Environment.new_from_eeml(xml)
passed_filenames << filename
rescue
#FileUtils.mv(File.join(dir_name, filename), 'test/real_xmls/v005/questioned')
raise "While parsing previously acceptable xml in file #{filename} got exception: #{$!.class}: #{$!}." +
"\nSTART OF XML SNIPPET from #{filename}\n#{xml[0..100]}\nEND OF XML SNIPPET" +
"\n Prior to error, successfully parsing #{passed_filenames.size} previously-ok xmls"
end
end
puts "passed all %d files" % passed_filenames.size
end
implement "throw detailed exception if missing mandatory node" do
original_xml = read_xml_file_one
#we'll test for
%w(status email).each do |missing_node_name|
test_xml = remove_first_node_called(missing_node_name, original_xml) #make test document from generic one
#should throw an exception with the right details
begin
Environment.new_from_eeml(test_xml)
flunk "should've raised an exception"
rescue MissingNode => e
assert_equal missing_node_name, e.sought_description, 'should have correct missing node name'
end
end
end
test "ignore unexpected attributes on title node" do
original_xml = read_xml_file_one
test_xml = original_xml.sub(/
/, '')
assert test_xml =~ /myattr/, 'substitution should have placed unexpected "myattr"'
env = Environment.new_from_eeml(test_xml)
assert_equal 'title here', env.title
end
test "handle missing optional nodes" do
original_xml = read_xml_file_one
#we'll remove the following
['title', 'website', ['feed', 'feed_url']].each do |missing_node_name, accessor_name|
accessor_name = missing_node_name if accessor_name.nil?
test_xml = remove_first_node_called(missing_node_name, original_xml) #make test document from generic one
env = Environment.new_from_eeml(test_xml)
assert_nil env.send((accessor_name).to_sym)
end
end
# implement "fail if location missing" do
# original_xml = read_xml_file_one
# test_xml = remove_first_node_called('location', original_xml)
# #should throw an exception with the right details
# begin
# Environment.new_from_eeml(test_xml)
# flunk "should've raised an exception for missing location"
# rescue MissingNode => e
# assert_equal 'location', e.sought_description, 'should have correct missing node name'
# end
# end
# --- ----------------------------------------------------
# --- construction stuff ----------------------------------------------------
# --- ----------------------------------------------------
test "env constructor uses params" do
env = Environment.new(:identifier => 'whatever', :creator => 'http://www.example.com/ourstudio/',
:feed_url => 'http://www.example.com/ourstudio/feeds/house.xml')
assert_equal 'whatever', env.identifier
assert_equal 'http://www.example.com/ourstudio/', env.creator
assert_equal 'http://www.example.com/ourstudio/feeds/house.xml', env.feed_url
end
# --- ------------------------------------------------------
# --- json input stuff ------------------------------------------------------
# --- ------------------------------------------------------
test "creating Env obj from a JSON string" do
env = create_env_from_json_file_one
assert_not_nil env
assert_instance_of Environment, env
assert_equal('title here', env.title)
assert_equal('description here', env.description)
assert_equal('http://example.com/api/1247.xml', env.feed_url)
assert_equal('http://example.com/studio/', env.website)
assert_equal('frozen', env.status)
assert_equal('someone@example.com', env.email)
assert_equal('http://example.com/some/icon.gif', env.icon)
#env attrs
assert_equal('1247', env.identifier)
assert_equal('2009-02-11T10:56:56Z', env.updated.strftime("%Y-%m-%dT%H:%M:%SZ"))
end
test "location parses ok from json" do
env = create_env_from_json_file_one
assert_not_nil env.location
location = env.location
assert_equal "Up on the roof (somewhere)", location.name
assert_equal "50.1", location.latitude
assert_equal "48.7", location.longitude
assert_equal "1.34", location.elevation
assert_equal "physical", location.domain
assert_equal "outdoor", location.exposure
assert_equal "mobile", location.disposition
end
test "datastreams parse ok from json" do
env = create_env_from_json_file_one
assert_equal 3, env.datastreams.size
#the expected values for the datastreams in the test doc
expectations_list = [
#id, tag array, min, max, value, unit_symbol, unit_type, unit_value
['0', ['tagD0'], '-9999.0', '1022.0', '0', 'C', 'basicSI', 'Celsius'],
['1', [], '0.0', '1023.0', '33', nil, nil, nil],
['2', ['tagD2a', 'tagD2b', 'tagD2c'], '23.4', '1021.0', '42.1', nil, nil, nil]
]
env.datastreams.each_with_index do |ds, i|
e_identifier, e_tags, e_min_value, e_max_value, e_value, e_unit_symbol, e_unit_type, e_unit_value = expectations_list[i]
assert_equal e_identifier, ds.identifier
assert_equal e_tags.size, ds.tags.size, "should have right num of tags"
e_tags.each do |expected_tag|
ds.tags.member? expected_tag
end
assert_equal e_min_value, ds.min_value
assert_equal e_max_value, ds.max_value
assert_equal e_value, ds.value
assert_equal e_unit_symbol, ds.unit_symbol
assert_equal e_unit_type, ds.unit_type
assert_equal e_unit_value, ds.unit_value
end
end
# --- -------------------------------------------------------
# --- csv input stuff -------------------------------------------------------
# --- -------------------------------------------------------
def create_env_with_datastream_values(values)
e = Environment.new
values.each do |v|
e.add_datastream DataStream.new(:value => v)
end
assert_equal values.size, e.datastreams.size
return e
end
test "update from csv values" do
#add a helper method to get an environments datastream values (ignoring min, max) as a simple array
#TODO: note, can't unload the class before other tests, so be careful with this approach to conveniences.
class Eeml::Environment
def values_quick
datastreams.map {|ds| ds.value}
end
end
values = ['11.01', '2', 'thirdval', '40'].freeze
#this env has NO datastreams, so each value will add a ds.
env = Environment.new
env.update_datastreams_from_csv_values!(values)
assert_equal values, env.values_quick
#When env has FEWER datastreams than the csv
#it should update the ones it has, AND add extra datastreams for the excess in the csv
#TODO: check this assumption:
# ALL datastreams should take their new value from csv, keeping previous min, max values.
env = create_env_with_datastream_values(%w(e1 e2 e3))
env.update_datastreams_from_csv_values!(values)
assert_equal values, env.values_quick
#When env has MORE datastreams than the csv
#it "should remove extra datastreams if less values are published to the csv feed" do
env = create_env_with_datastream_values(%w(e1 e2 e3 e4 e5))
env.update_datastreams_from_csv_values!(values)
assert_equal values, env.values_quick
#WHEN env has SAME number of ds as the csv
env = create_env_with_datastream_values(%w(e1 e2 e3 e4))
env.update_datastreams_from_csv_values!(values)
assert_equal values, env.values_quick
end
# --- -----------------------------------------------------
# --- eeml output stuff -----------------------------------------------------
# --- -----------------------------------------------------
test "should output to xml ok" do
env = create_env_from_xml_file_one
xml_output = env.to_eeml
assert_not_nil xml_output
#File.open('out.xml', 'w') {|f| f.write xml_output }
expected_xml = File.read('test/data/doc_1.xml')
assert_equal expected_xml, xml_output
end
implement "output - check that 'feed' url is correctly assembled" do
end
test "to_eeml of empty env should work" do
env = Environment.new
output_xml = env.to_eeml
#TODO: Review this 'expected' xml document for when env's empty. I've cheated so far.
# Perhaps we don't allow serialization at all when basics are absent.
expected_xml = File.read('test/data/out_empty.xml')
assert_equal expected_xml, output_xml
end
# --- -----------------------------------------------------
# --- json output stuff -----------------------------------------------------
# --- -----------------------------------------------------
#DEV NOTE: use jsondiff tool to visual-diff expected and actual json http://tlrobinson.net/projects/js/jsondiff/
#TODO: decide what trailing whitespace should be output after json, and eeml, and correct tests
test "basic output in public json format" do
input_xml = File.read('test/data/doc_2.xml')
env = create_env_from_xml_string(input_xml)
output_json = env.to_json
expected_json = File.read('test/data/doc_2_expected.json').rstrip
expected_hash = JSON.parse(expected_json)
output_hash = JSON.parse(output_json)
assert_equal(expected_hash, output_hash)
end
test "basic output in public json format - no location" do
input_xml = File.read('test/data/doc_2.xml')
input_xml = remove_first_node_called('location', input_xml)
env = create_env_from_xml_string(input_xml)
output_json = env.to_json
output_hash = JSON.parse(output_json)
expected_json = File.read('test/data/doc_2_expected.json')
expected_hash = JSON.parse(expected_json)
assert_not_nil expected_hash.delete("location")
assert_json_hashes_equal(expected_hash, output_hash)
end
test "basic output in public json format - location but no latitude" do
input_xml = File.read('test/data/doc_2.xml')
input_xml = remove_first_node_called('lat', input_xml)
env = create_env_from_xml_string(input_xml)
actual_json = env.to_json
actual_hash = JSON.parse(actual_json)
assert_nil env.location.latitude
expected_json = File.read('test/data/doc_2_expected.json')
expected_hash = JSON.parse(expected_json)
expected_hash["location"].delete("lat")
#File.open('tmp_expected.json', 'w') {|f| f.write expected_hash.to_json }
#File.open('tmp_actual.json', 'w') {|f| f.write actual_hash.to_json }
assert_json_hashes_equal(expected_hash, actual_hash)
end
test "basic output in public json format - minimal env" do
env = Environment.new
assert_nil env.identifier
output_json = env.to_json
assert_not_nil output_json
end
# --- convenience stuff - don't put tests under here ------------------------------
def create_env_from_xml_string(string)
environment = Environment.new_from_eeml(string)
assert_not_nil environment
assert_equal(Environment, environment.class)
return environment
end
def create_env_from_xml_file_one
return create_env_from_xml_file('doc_1.xml')
end
def create_env_from_xml_file(test_file)
filename = 'test/data/' + test_file
eeml_doc = File.read(filename)
assert_not_nil eeml_doc, "content of test xml file shouldn't be nil for file: #{filename}"
return create_env_from_xml_string(eeml_doc)
end
def create_env_from_json_file_one
return create_env_from_json_file('test/data/doc_1.json')
end
def create_env_from_json_file(filename)
json = File.read(filename)
return Environment.new_from_json(json)
end
def prep_json_for_reading(json)
return nil if json.nil?
json.gsub(/","/, '"' + "\n" + '"').sort
end
end