import unittest try: # Python 2.x import BaseHTTPServer as http except ImportError: # Python 3.x from http import server as http import contextlib from httptestserver import Server from tilequeue.wof import make_wof_url_neighbourhood_fetcher, \ WofProcessor import datetime # a mock wof model which does nothing - these tests are about # fetching the data, not parsing it. class _NullWofModel(object): def __init__(self): self.added = 0 self.updated = 0 self.removed = 0 def find_previous_neighbourhood_meta(self): return [] def sync_neighbourhoods( self, neighbourhoods_to_add, neighbourhoods_to_update, ids_to_remove): self.added = self.added + len(neighbourhoods_to_add) self.updated = self.updated + len(neighbourhoods_to_update) self.removed = self.removed + len(ids_to_remove) def insert_neighbourhoods(self, neighbourhoods): pass def update_visible_timestamp(self, zoom, day): return set() class _WofHandlerContext(object): def __init__(self, failure_count=0, content={}, failure_code=500): self.request_counts = {} self.failure_count = failure_count self.content = content self.failure_code = failure_code class _WofErrorHandler(http.BaseHTTPRequestHandler): def __init__(self, context, *args): self.wof_ctx = context http.BaseHTTPRequestHandler.__init__(self, *args) def do_GET(self): request_count = self.wof_ctx.request_counts.get(self.path, 0) if request_count < self.wof_ctx.failure_count: self.wof_ctx.request_counts[self.path] = request_count + 1 self.send_response(self.wof_ctx.failure_code) self.end_headers() self.wfile.write("") else: self.send_response(200) content_type, content \ = self.wof_ctx.content.get(self.path, ('text/plain', '')) self.send_header('Content-Type', content_type) self.end_headers() self.wfile.write(content) # fake Redis object to keep the code happy class _NullRedisTOI(object): def fetch_tiles_of_interest(self): return [] # guard function to run a test HTTP server on another thread and reap it when # it goes out of scope. @contextlib.contextmanager def _test_http_server(handler): server = Server('127.0.0.1', 0, 'http', handler) server.start() yield server # simple logger that's easy to turn the output on and off. class _SimpleLogger(object): def __init__(self, verbose=True): self.verbose = verbose def info(self, msg): if self.verbose: print "INFO: %s" % msg def warn(self, msg): if self.verbose: print "WARN: %s" % msg def error(self, msg): if self.verbose: print "ERROR: %s" % msg class TestWofHttp(unittest.TestCase): def _simple_test(self, num_failures=0, failure_code=500, max_retries=3): context = _WofHandlerContext(num_failures, { '/meta/neighbourhoods.csv': ( 'text/plain; charset=utf-8', "bbox,cessation,deprecated,file_hash,fullname,geom_hash," "geom_latitude,geom_longitude,id,inception,iso,lastmodified," "lbl_latitude,lbl_longitude,name,parent_id,path,placetype," "source,superseded_by,supersedes\n" "\"0,0,0,0\",u,,00000000000000000000000000000000,," "00000000000000000000000000000000,0,0,1,u,,0,0,0,Null Island," "-1,1/1.geojson,neighbourhood,null,,\n" ), '/meta/microhoods.csv': ( 'text/plain; charset=utf-8', "bbox,cessation,deprecated,file_hash,fullname,geom_hash," "geom_latitude,geom_longitude,id,inception,iso,lastmodified," "lbl_latitude,lbl_longitude,name,parent_id,path,placetype," "source,superseded_by,supersedes\n" ), '/meta/macrohoods.csv': ( 'text/plain; charset=utf-8', "bbox,cessation,deprecated,file_hash,fullname,geom_hash," "geom_latitude,geom_longitude,id,inception,iso,lastmodified," "lbl_latitude,lbl_longitude,name,parent_id,path,placetype," "source,superseded_by,supersedes\n" ), '/data/1/1.geojson': ( 'application/json; charset=utf-8', '{"id":1,"type":"Feature","properties":{"wof:id":1,' + '"wof:name":"Null Island","lbl:latitude":0.0,' + '"lbl:longitude":0.0,"wof:placetype":"neighbourhood"},' + '"geometry":{"coordinates":[0,0],"type":"Point"}}' ) }, failure_code) def handler(*args): return _WofErrorHandler(context, *args) model = _NullWofModel() with _test_http_server(handler) as server: fetcher = make_wof_url_neighbourhood_fetcher( server.url('/meta/neighbourhoods.csv'), server.url('/meta/microhoods.csv'), server.url('/meta/macrohoods.csv'), server.url('/meta/boroughs.csv'), server.url('/data'), 1, max_retries) redis = _NullRedisTOI() def intersector(dummy1, dummy2, dummy3): return [], None def enqueuer(dummy): pass logger = _SimpleLogger(False) today = datetime.date.today() processor = WofProcessor(fetcher, model, redis, intersector, enqueuer, logger, today) processor() self.assertEqual(model.added, 1) self.assertEqual(model.updated, 0) self.assertEqual(model.removed, 0) # if there are no failures, then the process should complete correctly. def test_without_failures(self): self._simple_test(0, 502) # if there are fewer failures than the number of retries, then it should # process without an error. def test_with_single_failure(self): self._simple_test(1, 502) # however, if we try to fetch a URL and it's missing then that really # should be an error - probably indicates that we're not using the right # logic to form the URLs. def test_with_missing(self): with self.assertRaises(AssertionError): self._simple_test(1, 404) # if we try to fetch a URL and it's forbidden then that really should be an # error - probably indicates a configuration problem with WOF. def test_with_forbidden(self): with self.assertRaises(AssertionError): self._simple_test(1, 403)