zhongrj
2025-11-24 276323dce9613867abb3f58a4cc2abbfb2fd0dea
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
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),
                ('georeferenced_model', {'epsg': 4326, 'resample': 2.5}, 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