Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test to prevent regressions if BadLayerHandler does not work during packaging or process_projectfile #1089

Merged
merged 1 commit into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docker-app/qfieldcloud/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1781,6 +1781,20 @@ def save(self, *args, **kwargs):
self.clean()
return super().save(*args, **kwargs)

def get_feedback_step_data(self, step_name: str) -> dict[str, Any] | None:
"""Extract a step data of a job's feedback.
Args:
step_name (str): name of the step to extract data from.
Returns:
dict[str, Any] | None: data as dict if the step has been found, else None.
"""
for step in self.feedback["steps"]:
if step["id"] == step_name:
return step
return None


class PackageJob(Job):
def save(self, *args, **kwargs):
Expand Down
211 changes: 211 additions & 0 deletions docker-app/qfieldcloud/core/tests/test_jobs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import logging

from qfieldcloud.authentication.models import AuthToken
from qfieldcloud.core.models import (
Job,
PackageJob,
Person,
Project,
)
from rest_framework import status
from rest_framework.response import Response
from rest_framework.test import APITransactionTestCase

from .utils import (
set_subscription,
setup_subscription_plans,
testdata_path,
wait_for_project_ok_status,
)

logging.disable(logging.CRITICAL)


class QfcTestCase(APITransactionTestCase):
def setUp(self):
setup_subscription_plans()

# Create a user
self.u1 = Person.objects.create_user(username="u1", password="abc123")
self.t1 = AuthToken.objects.get_or_create(user=self.u1)[0]

# Create a project
self.p1 = Project.objects.create(name="p1", is_public=False, owner=self.u1)

def upload_file(
self,
project: Project,
local_filename: str,
remote_filename: str,
) -> Response:
"""Upload a file to QFieldCloud using API.

Args:
project (Project): project that should contain the file.
local_filename (str): name of the local file to upload, should be in `testdata` folder.
remote_filename (str): name of the uploaded file.

Returns:
Response: response to the POST HTTP request.
"""
file_path = testdata_path(local_filename)
response = self.client.post(
f"/api/v1/files/{project.id}/{remote_filename}/",
{
"file": open(file_path, "rb"),
},
format="multipart",
)
return response

def assertLayerData(
self, layer_data: dict, is_valid: bool, is_localized: bool, error_code: str
) -> None:
self.assertEquals(layer_data["is_valid"], is_valid)
self.assertEquals(layer_data["is_localized"], is_localized)
self.assertEquals(layer_data["error_code"], error_code)

def test_bad_layer_handler_values_for_process_projectfile_job(self):
# Test that BadLayerHandler is parsing data properly during process projectfile jobs
self.client.credentials(HTTP_AUTHORIZATION="Token " + self.t1.key)

# Push the QGIS project and datasource GPKG files
response = self.upload_file(self.p1, "bumblebees.gpkg", "bumblebees.gpkg")

self.assertEqual(response.status_code, status.HTTP_201_CREATED)
gounux marked this conversation as resolved.
Show resolved Hide resolved

response = self.upload_file(
self.p1, "simple_bumblebees_wrong_localized.qgs", "project.qgs"
)

self.assertEqual(response.status_code, status.HTTP_201_CREATED)

wait_for_project_ok_status(self.p1)
processprojectfile_job = Job.objects.filter(
project=self.p1, type=Job.Type.PROCESS_PROJECTFILE
).latest("updated_at")

self.assertEqual(processprojectfile_job.status, Job.Status.FINISHED)
self.assertIsNotNone(processprojectfile_job.feedback)

self.p1.refresh_from_db()

self.assertIsNotNone(self.p1.project_details)

# extract layer data from job
processfile_layers = processprojectfile_job.get_feedback_step_data(
"project_details"
)["returns"]["project_details"]["layers_by_id"]

# "valid" localized layer -> QFC considers it as invalid
self.assertLayerData(
processfile_layers["valid_localized_point_layer_id"],
is_valid=False,
is_localized=True,
error_code="localized_dataprovider",
)

# invalid localized layer
self.assertLayerData(
processfile_layers["invalid_localized_polygon_layer_id"],
is_valid=False,
is_localized=True,
error_code="localized_dataprovider",
)

# invalid layer (datasource does not exist)
self.assertLayerData(
processfile_layers["invalid_point_layer_id"],
is_valid=False,
is_localized=False,
error_code="invalid_dataprovider",
)

def test_bad_layer_handler_values_for_package_job(self):
# Test that BadLayerHandler is parsing data properly during package jobs
self.client.credentials(HTTP_AUTHORIZATION="Token " + self.t1.key)

# Push the QGIS project and datasource GPKG files
response = self.upload_file(self.p1, "bumblebees.gpkg", "bumblebees.gpkg")

self.assertEqual(response.status_code, status.HTTP_201_CREATED)

response = self.upload_file(
self.p1, "simple_bumblebees_wrong_localized.qgs", "project.qgs"
)

self.assertEqual(response.status_code, status.HTTP_201_CREATED)

# Create Package job and wait for it to finish
set_subscription(
self.u1,
"keep_10",
storage_mb=10,
is_premium=True,
is_external_db_supported=True,
)
package_job = PackageJob.objects.create(
type=Job.Type.PROCESS_PROJECTFILE,
project=self.p1,
created_by=self.u1,
)

self.assertEqual(package_job.status, Job.Status.PENDING)

wait_for_project_ok_status(self.p1)

package_job.refresh_from_db()

self.assertEqual(package_job.status, Job.Status.FINISHED)
self.assertIsNotNone(package_job.feedback)

# extract layer data from job
qgis_layers = package_job.get_feedback_step_data("qgis_layers_data")["returns"][
"layers_by_id"
]
qfield_layers = package_job.get_feedback_step_data("qfield_layer_data")[
"returns"
]["layers_by_id"]

# "valid" localized layer -> QFC considers it as invalid (QGIS)
self.assertLayerData(
qgis_layers["valid_localized_point_layer_id"],
is_valid=False,
is_localized=True,
error_code="localized_dataprovider",
)

# invalid localized layer (QGIS)
self.assertLayerData(
qgis_layers["invalid_localized_polygon_layer_id"],
is_valid=False,
is_localized=True,
error_code="localized_dataprovider",
)

# "valid" localized layer -> QFC considers it as invalid (QField)
self.assertLayerData(
qfield_layers["valid_localized_point_layer_id"],
is_valid=False,
is_localized=True,
error_code="localized_dataprovider",
)

# invalid localized layer (QField)
self.assertLayerData(
qfield_layers["invalid_localized_polygon_layer_id"],
is_valid=False,
is_localized=True,
error_code="localized_dataprovider",
)

# invalid layer is not present in qfield layers
self.assertNotIn("invalid_point_layer_id", qfield_layers)

# invalid layer (datasource does not exist) (QGIS)
self.assertLayerData(
qgis_layers["invalid_point_layer_id"],
is_valid=False,
is_localized=False,
error_code="invalid_dataprovider",
)
Loading
Loading