Repository: ResonantGeoData/ResonantGeoData

Issue:
Proper testing for rgd_client
We need to implement real tests for the Python client

The tests for this would require running RGD with data prepopulated in the background then executing the client tests


Discussion:

Resolving patch:
diff --git a/rgd-client/rgd_client/rgdc.py b/rgd-client/rgd_client/rgdc.py
--- a/rgd-client/rgd_client/rgdc.py
+++ b/rgd-client/rgd_client/rgdc.py
@@ -238,7 +238,11 @@ def search(
             limit=limit,
             offset=offset,
         )
-        return list(limit_offset_pager(self.session, 'rgd/search', params=params))
+
+        r = self.session.get('rgd/search', params=params)
+        r.raise_for_status()
+
+        return r.json()
 
     def create_raster_stac(self, raster: Dict) -> Dict:
         """Create a raster entry using STAC format."""


Test patch:
diff --git a/rgd-client/tests/__init__.py b/rgd-client/tests/__init__.py
new file mode 100644
diff --git a/rgd-client/tests/conftest.py b/rgd-client/tests/conftest.py
new file mode 100644
--- /dev/null
+++ b/rgd-client/tests/conftest.py
@@ -0,0 +1,25 @@
+from django.contrib.auth.models import User
+import pytest
+
+from rgd_client import Rgdc
+
+from .data_fixtures import generate_fixtures
+
+# dynamic fixtures: populate commands
+for (name, fixture) in generate_fixtures():
+    globals()[name] = fixture
+
+
+@pytest.fixture
+def py_client(live_server):
+
+    params = {'username': 'test@kitware.com', 'email': 'test@kitware.com', 'password': 'password'}
+
+    user = User.objects.create_user(is_staff=True, is_superuser=True, **params)
+    user.save()
+
+    client = Rgdc(
+        username=params['username'], password=params['password'], api_url=f'{live_server.url}/api'
+    )
+
+    return client
diff --git a/rgd-client/tests/data_fixtures.py b/rgd-client/tests/data_fixtures.py
new file mode 100644
--- /dev/null
+++ b/rgd-client/tests/data_fixtures.py
@@ -0,0 +1,21 @@
+from django.core.management import call_command
+import pytest
+
+sources = [
+    'rgd_3d_demo',
+    'rgd_fmv_demo',
+    'rgd_geometry_demo',
+    'rgd_imagery_demo',
+]
+
+# dynamically creates fixtures for above management commands
+
+
+def generate_fixtures():
+    for s in sources:
+
+        @pytest.fixture
+        def data_fixture():
+            call_command(s)
+
+        yield (s, data_fixture)
diff --git a/rgd-client/tests/manage.py b/rgd-client/tests/manage.py
new file mode 100755
--- /dev/null
+++ b/rgd-client/tests/manage.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+import os
+import sys
+
+from django.core.management import execute_from_command_line
+
+
+def main() -> None:
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'test_project.settings')
+    execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/rgd-client/tests/test_client.py b/rgd-client/tests/test_client.py
new file mode 100644
--- /dev/null
+++ b/rgd-client/tests/test_client.py
@@ -0,0 +1,80 @@
+import json
+
+import pytest
+
+bbox = {
+    'type': 'Polygon',
+    'coordinates': [
+        [
+            [-105.45091240368326, 39.626245373878696],
+            [-105.45091240368326, 39.929904289147274],
+            [-104.88775649170178, 39.929904289147274],
+            [-104.88775649170178, 39.626245373878696],
+            [-105.45091240368326, 39.626245373878696],
+        ]
+    ],
+}
+
+
+@pytest.mark.django_db(transaction=True)
+def test_basic_search(py_client, rgd_imagery_demo):
+
+    q = py_client.search(query=json.dumps(bbox), predicate='intersects')
+
+    assert any(x['subentry_name'] == 'afie_1.jpg' for x in q)
+
+    assert any(
+        x['subentry_name'] == 'LC08_L1TP_034032_20200429_20200509_01_T1_sr_band1.tif' for x in q
+    )
+
+
+@pytest.mark.django_db(transaction=True)
+def test_inspect_raster(py_client, rgd_imagery_demo):
+
+    q = py_client.search(query=json.dumps(bbox), predicate='intersects')
+
+    raster_meta = next(
+        (
+            x
+            for x in q
+            if x['subentry_name'] == 'LC08_L1TP_034032_20200429_20200509_01_T1_sr_band1.tif'
+        ),
+        None,
+    )
+
+    assert raster_meta is not None
+
+    try:
+        raster = py_client.get_raster(raster_meta)
+        count = len(raster['parent_raster']['image_set']['images'])
+    except Exception:
+        pytest.fail('Failed to get raster from meta')
+
+    for i in range(count):
+        try:
+            py_client.download_raster_thumbnail(raster_meta, band=i)
+        except Exception:
+            pytest.fail(f'Failed to download raster thumbnail {i}')
+
+
+@pytest.mark.django_db(transaction=True)
+def test_download_raster(py_client, rgd_imagery_demo):
+
+    q = py_client.search(query=json.dumps(bbox), predicate='intersects')
+
+    assert len(q) >= 1
+
+    try:
+        py_client.download_raster(q[0])
+    except Exception as e:
+        print(e)
+        pytest.fail('Failed to download raster image set')
+
+
+# TODO: figure out TemplateDoesNotExist error
+# def test_basic_stac_search(rgd_imagery_demo):
+
+#     try:
+#         py_client.search_raster_stac(query=json.dumps(bbox), predicate='intersects')
+#     except Exception:
+#         pytest.fail('Failed STAC search')
diff --git a/rgd-client/tests/test_project/__init__.py b/rgd-client/tests/test_project/__init__.py
new file mode 100644
diff --git a/rgd-client/tests/test_project/celery.py b/rgd-client/tests/test_project/celery.py
new file mode 100644
--- /dev/null
+++ b/rgd-client/tests/test_project/celery.py
@@ -0,0 +1,17 @@
+import os
+
+from celery import Celery
+
+# import configurations.importer
+#
+os.environ['DJANGO_SETTINGS_MODULE'] = 'test_project.settings'
+# if not os.environ.get('DJANGO_CONFIGURATION'):
+#     raise ValueError('The environment variable "DJANGO_CONFIGURATION" must be set.')
+# configurations.importer.install()
+
+# Using a string config_source means the worker doesn't have to serialize
+# the configuration object to child processes.
+app = Celery('test_project', config_source='django.conf:settings', namespace='CELERY')
+
+# Load task modules from all registered Django app configs.
+app.autodiscover_tasks()
diff --git a/rgd-client/tests/test_project/settings.py b/rgd-client/tests/test_project/settings.py
new file mode 100644
--- /dev/null
+++ b/rgd-client/tests/test_project/settings.py
@@ -0,0 +1,22 @@
+from rgd_testing_utils.settings import *  # noqa
+
+INSTALLED_APPS += [  # noqa
+    'rgd_3d',
+    'rgd_fmv',
+    'rgd_geometry',
+    'rgd_imagery',
+    # Swagger
+    'drf_yasg',
+    'django_extensions',
+]
+
+ROOT_URLCONF = 'test_project.urls'
+WSGI_APPLICATION = 'test_project.wsgi.application'
+
+# Swagger
+REFETCH_SCHEMA_WITH_AUTH = True
+REFETCH_SCHEMA_ON_LOGOUT = True
+OPERATIONS_SORTER = 'alpha'
+DEEP_LINKING = True
+
+STATIC_URL = '/static/'
diff --git a/rgd-client/tests/test_project/urls.py b/rgd-client/tests/test_project/urls.py
new file mode 100644
--- /dev/null
+++ b/rgd-client/tests/test_project/urls.py
@@ -0,0 +1,44 @@
+from django.contrib import admin
+from django.urls import include, path, re_path
+from django.views.generic.base import RedirectView
+from drf_yasg import openapi
+from drf_yasg.views import get_schema_view
+from rest_framework import permissions
+
+urlpatterns = [
+    path('admin/', admin.site.urls),
+    path('accounts/', include('allauth.urls')),
+    path('oauth/', include('oauth2_provider.urls', namespace='oauth2_provider')),
+    path('api/s3-upload/', include('s3_file_field.urls')),
+    path('', include('rgd.urls')),
+    path('', include('rgd_3d.urls')),
+    path('', include('rgd_fmv.urls')),
+    path('', include('rgd_geometry.urls')),
+    path('', include('rgd_imagery.urls')),
+    # Redirect homepage to RGD core app homepage
+    path(r'', RedirectView.as_view(url='rgd', permanent=False), name='index'),
+]
+
+schema_view = get_schema_view(
+    openapi.Info(
+        title='ResonantGeoData API',
+        default_version='v1',
+        description='ResonantGeoData',
+        # terms_of_service='https://www.google.com/policies/terms/',
+        contact=openapi.Contact(email='kitware@kitare.com'),
+        license=openapi.License(name='Apache 2.0'),
+    ),
+    public=False,
+    permission_classes=(permissions.AllowAny,),
+    patterns=urlpatterns,
+)
+
+urlpatterns += [
+    re_path(
+        r'^swagger(?P<format>\.json|\.yaml)$',
+        schema_view.without_ui(cache_timeout=0),
+        name='schema-json',
+    ),
+    path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
+    path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
+]
diff --git a/rgd-client/tests/test_project/wsgi.py b/rgd-client/tests/test_project/wsgi.py
new file mode 100644
--- /dev/null
+++ b/rgd-client/tests/test_project/wsgi.py
@@ -0,0 +1,7 @@
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'test_project.settings')
+
+application = get_wsgi_application()
diff --git a/rgd-client/tests/test_status.py b/rgd-client/tests/test_status.py
new file mode 100644
--- /dev/null
+++ b/rgd-client/tests/test_status.py
@@ -0,0 +1,11 @@
+import requests
+
+import rgd_client
+
+
+def test_rgd_version():
+    assert rgd_client.__version__  # Make sure not None
+
+
+def test_server_status(live_server):
+    assert requests.get(live_server.url).status_code == 200
diff --git a/rgd-client/tests/test_version.py b/rgd-client/tests/test_version.py
deleted file mode 100644
--- a/rgd-client/tests/test_version.py
+++ /dev/null
@@ -1,5 +0,0 @@
-import rgd_client
-
-
-def test_rgd_version():
-    assert rgd_client.__version__  # Make sure not None
diff --git a/testing-utils/rgd_testing_utils/settings.py b/testing-utils/rgd_testing_utils/settings.py
--- a/testing-utils/rgd_testing_utils/settings.py
+++ b/testing-utils/rgd_testing_utils/settings.py
@@ -96,7 +96,10 @@
 DEFAULT_FILE_STORAGE = 'minio_storage.storage.MinioMediaStorage'
 MINIO_STORAGE_ENDPOINT = os.environ.get('MINIO_STORAGE_ENDPOINT', 'minio:9000')
 MINIO_STORAGE_MEDIA_URL = os.environ.get(
-    'MINIO_STORAGE_MEDIA_URL', 'http://localhost:9000/django-storage'
+    'MINIO_STORAGE_MEDIA_URL',
+    'http://minio:9000/django-storage'
+    if MINIO_STORAGE_ENDPOINT == 'minio:9000'
+    else 'http://localhost:9000/django-storage',
 )
 MINIO_STORAGE_USE_HTTPS = False
 MINIO_STORAGE_ACCESS_KEY = os.environ.get('MINIO_STORAGE_ACCESS_KEY', 'minioAccessKey')