From d0122786db3fe13bafba74854fcd9d961d21ae5d Mon Sep 17 00:00:00 2001 From: John Krauss Date: Thu, 12 May 2016 14:21:31 -0400 Subject: [PATCH 1/7] first-pass automatic testing --- .gitignore | 2 ++ scripts/README.md | 15 +++++++++ scripts/autotest.py | 66 ++++++++++++++++++++++++++++++++++++++++ scripts/requirements.txt | 3 ++ 4 files changed, 86 insertions(+) create mode 100644 scripts/README.md create mode 100644 scripts/autotest.py create mode 100644 scripts/requirements.txt diff --git a/.gitignore b/.gitignore index 4e3e7f4..a855859 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ src/pg/observatory--current--dev.sql src/pg/observatory--dev--current.sql src/pg/observatory--dev.sql +venv +*.pyc diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..16e0803 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,15 @@ +## Automatic tests and utilities + +### Installation + +Python 2.7 should cover you. Virtualenv recommended. + + virtualenv venv + source venv/bin/activate + pip install -r requirements.txt + +### Execution + +Run automated tests against a hostname: + + (venv) OBS_HOSTNAME= OBS_API_KEY=foobar nosetests autotest.py diff --git a/scripts/autotest.py b/scripts/autotest.py new file mode 100644 index 0000000..092c800 --- /dev/null +++ b/scripts/autotest.py @@ -0,0 +1,66 @@ +from nose.tools import assert_equal +from nose_parameterized import parameterized + +import os +import re +import requests + +HOSTNAME = os.environ['OBS_HOSTNAME'] +API_KEY = os.environ['OBS_API_KEY'] + +def query(q, **options): + ''' + Query the account. Returned is the response, wrapped by the requests + library. + ''' + url = 'https://{hostname}/api/v2/sql'.format(hostname=HOSTNAME) + params = options.copy() + params['q'] = re.sub(r'\s+', ' ', q) + params['api_key'] = API_KEY + return requests.get(url, params=params) + +MEASURE_COLUMNS = [(r['id'], ) for r in query(''' +SELECT id FROM observatory.obs_column +WHERE type ILIKE 'numeric' +AND weight > 0 +''').json()['rows']] + +CATEGORY_COLUMNS = [(r['id'], ) for r in query(''' +SELECT id FROM observatory.obs_column +WHERE type ILIKE 'text' +AND weight > 0 +''').json()['rows']] + +BOUNDARY_COLUMNS = [(r['id'], ) for r in query(''' +SELECT id FROM observatory.obs_column +WHERE type ILIKE 'geometry' +AND weight > 0 +''').json()['rows']] + + +@parameterized(MEASURE_COLUMNS) +def test_measure_points(column_id): + resp = query(''' +SELECT * +FROM cdb_observatory.OBS_GetMeasure(cdb_observatory._TestPoint(), + '{column_id}') + '''.format(column_id=column_id)) + assert_equal(resp.status_code, 200) + +@parameterized(CATEGORY_COLUMNS) +def test_category_points(column_id): + resp = query(''' +SELECT * +FROM cdb_observatory.OBS_GetCategory(cdb_observatory._TestPoint(), + '{column_id}') + '''.format(column_id=column_id)) + assert_equal(resp.status_code, 200) + +@parameterized(BOUNDARY_COLUMNS) +def test_boundary_points(column_id): + resp = query(''' +SELECT * +FROM cdb_observatory.OBS_GetBoundary(cdb_observatory._TestPoint(), + '{column_id}') + '''.format(column_id=column_id)) + assert_equal(resp.status_code, 200) diff --git a/scripts/requirements.txt b/scripts/requirements.txt new file mode 100644 index 0000000..838bc0e --- /dev/null +++ b/scripts/requirements.txt @@ -0,0 +1,3 @@ +requests +nose +nose_parameterized From 9a5c8777cebe1cf037169a71aa52c4f24335e098 Mon Sep 17 00:00:00 2001 From: John Krauss Date: Thu, 12 May 2016 16:23:00 -0400 Subject: [PATCH 2/7] use different test points for different measures --- scripts/autotest.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/scripts/autotest.py b/scripts/autotest.py index 092c800..dab5dc5 100644 --- a/scripts/autotest.py +++ b/scripts/autotest.py @@ -37,30 +37,35 @@ WHERE type ILIKE 'geometry' AND weight > 0 ''').json()['rows']] +def default_point(column_id): + ''' + Returns default test point for the column_id. + ''' + if column_id.startswith('es.ine'): + return 'CDB_LatLng(40.39, -3.7)' + elif column_id.startswith('us.zillow'): + return 'CDB_LatLng(41.76, -73.52)' + else: + return 'CDB_LatLng(40.7, -73.9)' + @parameterized(MEASURE_COLUMNS) def test_measure_points(column_id): resp = query(''' -SELECT * -FROM cdb_observatory.OBS_GetMeasure(cdb_observatory._TestPoint(), - '{column_id}') - '''.format(column_id=column_id)) +SELECT * FROM cdb_observatory.OBS_GetMeasure({point}, '{column_id}') + '''.format(column_id=column_id, point=default_point(column_id))) assert_equal(resp.status_code, 200) @parameterized(CATEGORY_COLUMNS) def test_category_points(column_id): resp = query(''' -SELECT * -FROM cdb_observatory.OBS_GetCategory(cdb_observatory._TestPoint(), - '{column_id}') - '''.format(column_id=column_id)) +SELECT * FROM cdb_observatory.OBS_GetCategory({point}, '{column_id}') + '''.format(column_id=column_id, point=default_point(column_id))) assert_equal(resp.status_code, 200) @parameterized(BOUNDARY_COLUMNS) def test_boundary_points(column_id): resp = query(''' -SELECT * -FROM cdb_observatory.OBS_GetBoundary(cdb_observatory._TestPoint(), - '{column_id}') - '''.format(column_id=column_id)) +SELECT * FROM cdb_observatory.OBS_GetBoundary({point}, '{column_id}') + '''.format(column_id=column_id, point=default_point(column_id))) assert_equal(resp.status_code, 200) From cc798730a2a7b187622c8d997a142ed1fb4d9a24 Mon Sep 17 00:00:00 2001 From: John Krauss Date: Thu, 12 May 2016 16:36:05 -0400 Subject: [PATCH 3/7] make sure theres actually data there --- scripts/autotest.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/autotest.py b/scripts/autotest.py index dab5dc5..085edd8 100644 --- a/scripts/autotest.py +++ b/scripts/autotest.py @@ -1,4 +1,4 @@ -from nose.tools import assert_equal +from nose.tools import assert_equal, assert_is_not_none from nose_parameterized import parameterized import os @@ -55,6 +55,9 @@ def test_measure_points(column_id): SELECT * FROM cdb_observatory.OBS_GetMeasure({point}, '{column_id}') '''.format(column_id=column_id, point=default_point(column_id))) assert_equal(resp.status_code, 200) + rows = resp.json()['rows'] + assert_equal(1, len(rows)) + assert_is_not_none(rows[0].values()[0]) @parameterized(CATEGORY_COLUMNS) def test_category_points(column_id): @@ -62,6 +65,9 @@ def test_category_points(column_id): SELECT * FROM cdb_observatory.OBS_GetCategory({point}, '{column_id}') '''.format(column_id=column_id, point=default_point(column_id))) assert_equal(resp.status_code, 200) + rows = resp.json()['rows'] + assert_equal(1, len(rows)) + assert_is_not_none(rows[0].values()[0]) @parameterized(BOUNDARY_COLUMNS) def test_boundary_points(column_id): @@ -69,3 +75,6 @@ def test_boundary_points(column_id): SELECT * FROM cdb_observatory.OBS_GetBoundary({point}, '{column_id}') '''.format(column_id=column_id, point=default_point(column_id))) assert_equal(resp.status_code, 200) + rows = resp.json()['rows'] + assert_equal(1, len(rows)) + assert_is_not_none(rows[0].values()[0]) From 02a529f0fc052282a277a79ed12a2966b74c309a Mon Sep 17 00:00:00 2001 From: John Krauss Date: Mon, 16 May 2016 11:28:27 -0400 Subject: [PATCH 4/7] better test points --- scripts/autotest.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/autotest.py b/scripts/autotest.py index 085edd8..25e2ccb 100644 --- a/scripts/autotest.py +++ b/scripts/autotest.py @@ -41,10 +41,17 @@ def default_point(column_id): ''' Returns default test point for the column_id. ''' - if column_id.startswith('es.ine'): + if column_id == 'whosonfirst.wof_disputed_geom': + return 'CDB_LatLng(33.78, 76.57)' + elif column_id == 'whosonfirst.wof_marinearea_geom': + return 'CDB_LatLng(43.33, -68.47)' + elif column_id in ('us.census.tiger.school_district_elementary', + 'us.census.tiger.school_district_secondary'): + return 'CDB_LatLng(40.7025, -73.7067)' + elif column_id.startswith('es.ine'): return 'CDB_LatLng(40.39, -3.7)' elif column_id.startswith('us.zillow'): - return 'CDB_LatLng(41.76, -73.52)' + return 'CDB_LatLng(28.3305906291771, -81.3544048197256)' else: return 'CDB_LatLng(40.7, -73.9)' From 8563ca7e45498ad5cd09e9d7eb6fca27083c6612 Mon Sep 17 00:00:00 2001 From: John Krauss Date: Mon, 16 May 2016 11:52:41 -0400 Subject: [PATCH 5/7] additional options for testing --- scripts/autotest.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/scripts/autotest.py b/scripts/autotest.py index 25e2ccb..56a55ad 100644 --- a/scripts/autotest.py +++ b/scripts/autotest.py @@ -7,35 +7,40 @@ import requests HOSTNAME = os.environ['OBS_HOSTNAME'] API_KEY = os.environ['OBS_API_KEY'] +META_HOSTNAME = os.environ.get('OBS_META_HOSTNAME', HOSTNAME) +META_API_KEY = os.environ.get('OBS_META_API_KEY', API_KEY) +USE_SCHEMA = 'OBS_USE_SCHEMA' in os.environ -def query(q, **options): + +def query(q, is_meta=False, **options): ''' Query the account. Returned is the response, wrapped by the requests library. ''' - url = 'https://{hostname}/api/v2/sql'.format(hostname=HOSTNAME) + url = 'https://{hostname}/api/v2/sql'.format( + hostname=META_HOSTNAME if is_meta else HOSTNAME) params = options.copy() params['q'] = re.sub(r'\s+', ' ', q) - params['api_key'] = API_KEY + params['api_key'] = META_API_KEY if is_meta else API_KEY return requests.get(url, params=params) MEASURE_COLUMNS = [(r['id'], ) for r in query(''' SELECT id FROM observatory.obs_column WHERE type ILIKE 'numeric' AND weight > 0 -''').json()['rows']] +''', is_meta=True).json()['rows']] CATEGORY_COLUMNS = [(r['id'], ) for r in query(''' SELECT id FROM observatory.obs_column WHERE type ILIKE 'text' AND weight > 0 -''').json()['rows']] +''', is_meta=True).json()['rows']] BOUNDARY_COLUMNS = [(r['id'], ) for r in query(''' SELECT id FROM observatory.obs_column WHERE type ILIKE 'geometry' AND weight > 0 -''').json()['rows']] +''', is_meta=True).json()['rows']] def default_point(column_id): ''' @@ -59,8 +64,10 @@ def default_point(column_id): @parameterized(MEASURE_COLUMNS) def test_measure_points(column_id): resp = query(''' -SELECT * FROM cdb_observatory.OBS_GetMeasure({point}, '{column_id}') - '''.format(column_id=column_id, point=default_point(column_id))) +SELECT * FROM {schema}OBS_GetMeasure({point}, '{column_id}') + '''.format(column_id=column_id, + schema='cdb_observatory.' if USE_SCHEMA else '', + point=default_point(column_id))) assert_equal(resp.status_code, 200) rows = resp.json()['rows'] assert_equal(1, len(rows)) @@ -69,8 +76,10 @@ SELECT * FROM cdb_observatory.OBS_GetMeasure({point}, '{column_id}') @parameterized(CATEGORY_COLUMNS) def test_category_points(column_id): resp = query(''' -SELECT * FROM cdb_observatory.OBS_GetCategory({point}, '{column_id}') - '''.format(column_id=column_id, point=default_point(column_id))) +SELECT * FROM {schema}OBS_GetCategory({point}, '{column_id}') + '''.format(column_id=column_id, + schema='cdb_observatory.' if USE_SCHEMA else '', + point=default_point(column_id))) assert_equal(resp.status_code, 200) rows = resp.json()['rows'] assert_equal(1, len(rows)) @@ -79,8 +88,10 @@ SELECT * FROM cdb_observatory.OBS_GetCategory({point}, '{column_id}') @parameterized(BOUNDARY_COLUMNS) def test_boundary_points(column_id): resp = query(''' -SELECT * FROM cdb_observatory.OBS_GetBoundary({point}, '{column_id}') - '''.format(column_id=column_id, point=default_point(column_id))) +SELECT * FROM {schema}OBS_GetBoundary({point}, '{column_id}') + '''.format(column_id=column_id, + schema='cdb_observatory.' if USE_SCHEMA else '', + point=default_point(column_id))) assert_equal(resp.status_code, 200) rows = resp.json()['rows'] assert_equal(1, len(rows)) From cd82837a519dd95becb89319b8206323bfeeb7da Mon Sep 17 00:00:00 2001 From: John Krauss Date: Mon, 16 May 2016 15:32:40 -0400 Subject: [PATCH 6/7] use school district point for clipped geoms too --- scripts/autotest.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/autotest.py b/scripts/autotest.py index 56a55ad..622686d 100644 --- a/scripts/autotest.py +++ b/scripts/autotest.py @@ -51,7 +51,9 @@ def default_point(column_id): elif column_id == 'whosonfirst.wof_marinearea_geom': return 'CDB_LatLng(43.33, -68.47)' elif column_id in ('us.census.tiger.school_district_elementary', - 'us.census.tiger.school_district_secondary'): + 'us.census.tiger.school_district_secondary', + 'us.census.tiger.school_district_elementary_clipped', + 'us.census.tiger.school_district_secondary_clipped'): return 'CDB_LatLng(40.7025, -73.7067)' elif column_id.startswith('es.ine'): return 'CDB_LatLng(40.39, -3.7)' From f17dc6d8ff2ec1d1ea36d1ea278768ba665ad165 Mon Sep 17 00:00:00 2001 From: Mario de Frutos Date: Tue, 17 May 2016 11:06:43 +0200 Subject: [PATCH 7/7] Added return statements when missing --- src/pg/sql/41_observatory_augmentation.sql | 5 +++++ src/pg/sql/44_observatory_geometries.sql | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/src/pg/sql/41_observatory_augmentation.sql b/src/pg/sql/41_observatory_augmentation.sql index 98a8ec7..87b1cc0 100644 --- a/src/pg/sql/41_observatory_augmentation.sql +++ b/src/pg/sql/41_observatory_augmentation.sql @@ -172,6 +172,7 @@ BEGIN RAISE NOTICE 'Point % is outside of the data region', ST_AsText(geom); -- TODO this should return JSON RETURN QUERY SELECT '{}'::text[], '{}'::NUMERIC[]; + RETURN; END IF; IF data_table_info IS NULL THEN @@ -198,6 +199,7 @@ BEGIN SELECT unnest($1) $query$ USING results; + RETURN; END; $$ LANGUAGE plpgsql; @@ -722,6 +724,7 @@ BEGIN THEN RAISE NOTICE 'Point % is outside of the data region', ST_AsText(geom); RETURN QUERY SELECT '{}'::text[], '{}'::text[]; + RETURN; END IF; EXECUTE ' @@ -735,6 +738,7 @@ BEGIN THEN RAISE NOTICE 'No data table found for this location'; RETURN QUERY SELECT NULL::json; + RETURN; END IF; EXECUTE @@ -749,6 +753,7 @@ BEGIN THEN RAISE NOTICE 'No geometry id for this location'; RETURN QUERY SELECT NULL::json; + RETURN; END IF; query := 'SELECT ARRAY['; diff --git a/src/pg/sql/44_observatory_geometries.sql b/src/pg/sql/44_observatory_geometries.sql index 9dd5852..c904f6c 100644 --- a/src/pg/sql/44_observatory_geometries.sql +++ b/src/pg/sql/44_observatory_geometries.sql @@ -274,6 +274,7 @@ BEGIN THEN RAISE NOTICE 'No boundaries found for bounding box ''%'' in ''%''', ST_AsText(geom), boundary_id; RETURN QUERY SELECT NULL::geometry, NULL::text; + RETURN; END IF; RAISE NOTICE 'target_table: %', target_table; @@ -286,6 +287,7 @@ BEGIN WHERE ST_%s($1, the_geom) ', geom_colname, geoid_colname, target_table, overlap_type) USING geom; + RETURN; END; $$ LANGUAGE plpgsql; @@ -328,6 +330,7 @@ BEGIN time_span, overlap_type ); + RETURN; END; $$ LANGUAGE plpgsql; @@ -380,6 +383,7 @@ BEGIN circle_boundary, boundary_id, time_span); + RETURN; END; $$ LANGUAGE plpgsql; @@ -416,6 +420,7 @@ BEGIN THEN RAISE NOTICE 'No boundaries found for bounding box ''%'' in ''%''', ST_AsText(geom), boundary_id; RETURN QUERY SELECT NULL::geometry, NULL::text; + RETURN; END IF; RAISE NOTICE 'target_table: %', target_table; @@ -428,6 +433,7 @@ BEGIN WHERE ST_%s($1, the_geom) ', geom_colname, geom_colname, geoid_colname, target_table, overlap_type) USING geom; + RETURN; END; $$ LANGUAGE plpgsql; @@ -469,6 +475,7 @@ BEGIN boundary_id, time_span, overlap_type); + RETURN; END; $$ LANGUAGE plpgsql; @@ -522,6 +529,7 @@ BEGIN boundary_id, time_span, overlap_type); + RETURN; END; $$ LANGUAGE plpgsql; @@ -560,6 +568,7 @@ BEGIN -- AND geom_t.timespan = '%s' <-- put in requested year -- TODO: filter by clipped vs. not so appropriate tablename are unique -- so the limit 1 can be removed + RETURN; END; $$ LANGUAGE plpgsql;