Added unit tests for export

pull/1086/head
Piero Toffanin 2021-11-05 13:10:48 -04:00
rodzic 7401bf6e1d
commit 4f7ee61030
5 zmienionych plików z 243 dodań i 2 usunięć

Wyświetl plik

@ -480,7 +480,7 @@ class Export(TaskNestedView):
formula = request.data.get('formula')
bands = request.data.get('bands')
rescale = request.data.get('rescale')
export_format = request.data.get('format', 'gtiff')
export_format = request.data.get('format', 'laz' if asset_type == 'georeferenced_model' else 'gtiff')
epsg = request.data.get('epsg')
color_map = request.data.get('color_map')
hillshade = request.data.get('hillshade')
@ -499,6 +499,19 @@ class Export(TaskNestedView):
if asset_type == 'georeferenced_model' and not export_format in ['laz', 'las', 'ply', 'csv']:
raise exceptions.ValidationError(_("Unsupported format: %(value)s") % {'value': export_format})
# Default color map, hillshade
if asset_type in ['dsm', 'dtm'] and export_format != 'gtiff':
if color_map is None:
color_map = 'viridis'
if hillshade is None:
hillshade = 6
if color_map is not None:
try:
colormap.get(color_map)
except InvalidColorMapName:
raise exceptions.ValidationError(_("Not a valid color_map value"))
if epsg is not None:
try:
epsg = int(epsg)

Wyświetl plik

@ -0,0 +1,14 @@
import React from 'react';
import { shallow } from 'enzyme';
import ExportAssetDialog from '../ExportAssetDialog';
const taskMock = require('../../tests/utils/MockLoader').load("task.json");
describe('<ExportAssetDialog />', () => {
it('renders without exploding', () => {
const wrapper = shallow(<ExportAssetDialog onHide={() => {}}
asset={"orthophoto"}
task={taskMock} />);
expect(wrapper.exists()).toBe(true);
})
});

Wyświetl plik

@ -0,0 +1,14 @@
import React from 'react';
import { shallow } from 'enzyme';
import ExportAssetPanel from '../ExportAssetPanel';
const taskMock = require('../../tests/utils/MockLoader').load("task.json");
describe('<ExportAssetPanel />', () => {
it('renders without exploding', () => {
const wrapper = shallow(<ExportAssetPanel
asset={"orthophoto"}
task={taskMock} />);
expect(wrapper.exists()).toBe(true);
})
});

Wyświetl plik

@ -0,0 +1,200 @@
import os
import time
from worker.celery import app as celery
import logging
import json
import requests
from PIL import Image
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APIClient
from app.plugins.signals import task_completed
from app.tests.classes import BootTransactionTestCase
from app.models import Project, Task
from nodeodm.models import ProcessingNode
from nodeodm import status_codes
import worker
from worker.tasks import TestSafeAsyncResult
from .utils import start_processing_node, clear_test_media_root, catch_signal
# We need to test in a TransactionTestCase because
# task processing happens on a separate thread, and normal TestCases
# do not commit changes to the DB, so spawning a new thread will show no
# data in it.
from webodm import settings
logger = logging.getLogger('app.logger')
DELAY = 2 # time to sleep for during process launch, background processing, etc.
class TestApiTask(BootTransactionTestCase):
def setUp(self):
super().setUp()
def tearDown(self):
clear_test_media_root()
def test_exports(self):
client = APIClient()
with start_processing_node():
user = User.objects.get(username="testuser")
self.assertFalse(user.is_superuser)
other_user = User.objects.get(username="testuser2")
project = Project.objects.create(
owner=user,
name="test project"
)
other_project = Project.objects.create(
owner=other_user,
name="another test project"
)
# Start processing node
# Create processing node
pnode = ProcessingNode.objects.create(hostname="localhost", port=11223)
# task creation via file upload
image1 = open("app/fixtures/tiny_drone_image.jpg", 'rb')
image2 = open("app/fixtures/tiny_drone_image_2.jpg", 'rb')
client.login(username="testuser", password="test1234")
# Normal case with images[], name and processing node parameter
res = client.post("/api/projects/{}/tasks/".format(project.id), {
'images': [image1, image2],
'name': 'test task',
'processing_node': pnode.id
}, format="multipart")
self.assertTrue(res.status_code == status.HTTP_201_CREATED)
image1.close()
image2.close()
# Should have returned the id of the newly created task
task = Task.objects.latest('created_at')
params = [
('orthophoto', {'formula': 'NDVI', 'bands': 'RGN'}, status.HTTP_200_OK),
('dsm', {'epsg': 4326}, status.HTTP_200_OK),
('dtm', {'epsg': 4326}, status.HTTP_200_OK),
('georeferenced_model', {'epsg': 4326}, status.HTTP_200_OK)
]
# Cannot export stuff
for p in params:
asset_type, data, _ = p
res = client.post("/api/projects/{}/tasks/{}/{}/export".format(project.id, task.id, asset_type), data)
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)
# Assign processing node to task via API
res = client.patch("/api/projects/{}/tasks/{}/".format(project.id, task.id), {
'processing_node': pnode.id
})
self.assertTrue(res.status_code == status.HTTP_200_OK)
retry_count = 0
while task.status != status_codes.COMPLETED:
worker.tasks.process_pending_tasks()
time.sleep(DELAY)
task.refresh_from_db()
retry_count += 1
if retry_count > 10:
break
self.assertEqual(task.status, status_codes.COMPLETED)
# Can export stuff (basic)
for p in params:
asset_type, data, exp_status = p
res = client.post("/api/projects/{}/tasks/{}/{}/export".format(project.id, task.id, asset_type), data)
self.assertEqual(res.status_code, exp_status)
reply = json.loads(res.content.decode("utf-8"))
self.assertTrue("celery_task_id" in reply)
celery_task_id = reply["celery_task_id"]
# More exhaustive export testing
params = [
('orthophoto', {}, True, ".tif", status.HTTP_200_OK),
('orthophoto', {'format': 'gtiff'}, True, ".tif", status.HTTP_200_OK),
('orthophoto', {'format': 'gtiff-rgb', 'rescale': "10,100"}, False, ".tif", status.HTTP_200_OK),
('orthophoto', {'format': 'laz'}, False, ".tif", status.HTTP_400_BAD_REQUEST),
('orthophoto', {'format': 'jpg', 'epsg': 4326}, False, ".jpg", status.HTTP_200_OK),
('orthophoto', {'format': 'jpg', 'epsg': 4326, 'rescale': '10,200'}, False, ".jpg", status.HTTP_200_OK),
('orthophoto', {'format': 'png'}, False, ".png", status.HTTP_200_OK),
('orthophoto', {'format': 'kmz'}, False, ".kmz", status.HTTP_200_OK),
('orthophoto', {'formula': 'NDVI'}, False, "-NDVI.tif", status.HTTP_400_BAD_REQUEST),
('orthophoto', {'bands': 'RGN'}, False, "-NDVI.tif", status.HTTP_400_BAD_REQUEST),
('orthophoto', {'bands': 'RGN', 'formula': 'NDVI'}, False, "-NDVI.tif", status.HTTP_200_OK),
('dsm', {'format': 'gtiff'}, True, ".tif", status.HTTP_200_OK),
('dsm', {'epsg': 4326}, False, ".tif", status.HTTP_200_OK),
('dsm', {'format': 'jpg', 'epsg': 4326}, False, ".jpg", status.HTTP_200_OK),
('dsm', {'format': 'jpg', 'color_map': 'jet', 'hillshade': 0, 'epsg': 3857}, False, ".jpg", status.HTTP_200_OK),
('dsm', {'epsg': 4326, 'format': 'jpg'}, False, ".jpg", status.HTTP_200_OK),
('dsm', {'epsg': 4326, 'format': 'gtiff-rgb'}, False, ".tif", status.HTTP_200_OK),
('dsm', {'format': 'kmz'}, False, ".kmz", status.HTTP_200_OK),
('dsm', {'color_map': 'viridis', 'hillshade': 2, 'format': 'png'}, False, ".png", status.HTTP_200_OK),
('dsm', {'rescale': 'invalid-but-works-cuz-gtiff'}, True, ".tif", status.HTTP_200_OK),
('dsm', {'epsg': 'invalid'}, False, ".tif", status.HTTP_400_BAD_REQUEST),
('dsm', {'format': 'invalid'}, False, ".tif", status.HTTP_400_BAD_REQUEST),
('dsm', {'hillshade': 'invalid'}, False, ".tif", status.HTTP_400_BAD_REQUEST),
('dsm', {'color_map': 'invalid'}, False, ".tif", status.HTTP_400_BAD_REQUEST),
('dsm', {'format': 'gtiff-rgb', 'rescale': 'invalid'}, False, ".tif", status.HTTP_400_BAD_REQUEST),
('dsm', {'format': 'las'}, False, ".tif", status.HTTP_400_BAD_REQUEST),
('dtm', {'format': 'gtiff'}, True, ".tif", status.HTTP_200_OK),
('dtm', {'epsg': 4326}, False, ".tif", status.HTTP_200_OK),
('georeferenced_model', {}, True, ".laz", status.HTTP_200_OK),
('georeferenced_model', {'format': 'las'}, False, ".las", status.HTTP_200_OK),
('georeferenced_model', {'format': 'ply'}, False, ".ply", status.HTTP_200_OK),
('georeferenced_model', {'format': 'csv'}, False, ".csv", status.HTTP_200_OK),
('georeferenced_model', {'format': 'las', 'epsg': 4326}, False, ".las", status.HTTP_200_OK),
('georeferenced_model', {'format': 'tif'}, False, ".laz", status.HTTP_400_BAD_REQUEST),
]
for p in params:
asset_type, data, shortcut_link, extension, exp_status = p
logger.info("Testing {}".format(p))
res = client.post("/api/projects/{}/tasks/{}/{}/export".format(project.id, task.id, asset_type), data)
self.assertEqual(res.status_code, exp_status)
reply = json.loads(res.content.decode("utf-8"))
if res.status_code == status.HTTP_200_OK:
self.assertTrue("filename" in reply)
self.assertEqual(reply["filename"], "test-task-" + asset_type + extension)
if shortcut_link:
self.assertFalse("celery_task_id" in reply)
self.assertTrue("url" in reply)
# Can download
res = client.get(reply["url"])
self.assertEqual(res.status_code, status.HTTP_200_OK)
else:
self.assertTrue("celery_task_id" in reply)
self.assertFalse("url" in reply)
cres = TestSafeAsyncResult(celery_task_id)
c = 0
while not cres.ready():
time.sleep(0.2)
c += 1
if c > 50:
self.assertTrue(False)
break
res = client.get("/api/workers/get/{}?filename={}".format(celery_task_id, reply["filename"]))
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertEqual(res._headers['content-disposition'][1], 'attachment; filename={}'.format(reply["filename"]))
else:
self.assertTrue(len(reply[0]) > 0) # Error message

@ -1 +1 @@
Subproject commit 2fb254e378abfa42ddd04bd0662583b9dbe277bf
Subproject commit dc32c0a232e78fd5ba0ffa0d3837b50679b0a66b