From 7ef998b651a56bc9bf1b9c93d00d7a9e50593b1b Mon Sep 17 00:00:00 2001 From: Rijul Poudel Date: Tue, 27 Oct 2026 13:22:32 -0500 Subject: [PATCH 1/4] [test] Download all attachments in record set --- specifyweb/backend/attachment_gw/tests.py | 89 ++++++++++++++++++++--- 1 file changed, 77 insertions(+), 12 deletions(-) diff --git a/specifyweb/backend/attachment_gw/tests.py b/specifyweb/backend/attachment_gw/tests.py index 501deb776c1..5c16eb26747 100644 --- a/specifyweb/backend/attachment_gw/tests.py +++ b/specifyweb/backend/attachment_gw/tests.py @@ -1,16 +1,81 @@ -""" -This file demonstrates writing tests using the unittest module. These will pass -when you run "manage.py test". +import io +import zipfile +from unittest.mock import patch -Replace this with more appropriate tests for your application. -""" +from django.test import override_settings -from django.test import TestCase +from specifyweb.specify import models +from specifyweb.specify.tests.test_api import ApiTests -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. - """ - self.assertEqual(1 + 1, 2) +@override_settings(WEB_ATTACHMENT_KEY="test-key") +class TestDownloadAllWithRecordSet(ApiTests): + def setUp(self): + super().setUp() + self.client.force_login(self.specifyuser) + + self.attachment = models.Attachment.objects.create( + origfilename="photo.jpg", + attachmentlocation="stored-photo.jpg", + ) + models.Collectionobjectattachment.objects.create( + collectionmemberid=self.collection.id, + ordinal=0, + attachment=self.attachment, + collectionobject=self.collectionobjects[0], + ) + + self.other_attachment = models.Attachment.objects.create( + origfilename="other.jpg", + attachmentlocation="stored-other.jpg", + ) + models.Collectionobjectattachment.objects.create( + collectionmemberid=self.collection.id, + ordinal=0, + attachment=self.other_attachment, + collectionobject=self.collectionobjects[1], + ) + + self.recordset = models.Recordset.objects.create( + collectionmemberid=self.collection.id, + dbtableid=models.Collectionobject.specify_model.tableId, + name="Record set with attachment", + type=0, + specifyuser=self.specifyuser, + ) + models.Recordsetitem.objects.create( + recordset=self.recordset, + recordid=self.collectionobjects[0].id, + ) + + @patch("specifyweb.backend.attachment_gw.views.requests.Session.get") + def test_download_all_includes_record_set_attachments(self, get): + get.return_value.status_code = 200 + get.return_value.iter_content.return_value = [b"image-bytes"] + + with patch( + "specifyweb.backend.attachment_gw.views.server_urls", + {"read": "http://asset.test/read"}, + ), patch( + "specifyweb.backend.attachment_gw.views.server_time_delta", 0 + ): + response = self.client.post( + "/attachment_gw/download_all/", + { + "attachmentlocations": [], + "origfilenames": [], + "recordsetid": self.recordset.id, + }, + content_type="application/json", + ) + self.assertEqual(response.status_code, 200) + content = b"".join(response.streaming_content) + + get.assert_called_once() + self.assertEqual( + get.call_args.kwargs["params"]["filename"], "stored-photo.jpg" + ) + + with zipfile.ZipFile(io.BytesIO(content)) as archive: + self.assertEqual(archive.namelist(), ["photo.jpg"]) + self.assertEqual(archive.read("photo.jpg"), b"image-bytes") From c97626bb3b848e1decd384fc58c2a60890c58c4a Mon Sep 17 00:00:00 2001 From: Rijul Poudel Date: Tue, 27 Oct 2026 13:22:32 -0500 Subject: [PATCH 2/4] [test] Edit existing record set --- specifyweb/specify/tests/test_record_sets.py | 50 ++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/specifyweb/specify/tests/test_record_sets.py b/specifyweb/specify/tests/test_record_sets.py index 5393734fce7..bbb44c15f15 100644 --- a/specifyweb/specify/tests/test_record_sets.py +++ b/specifyweb/specify/tests/test_record_sets.py @@ -1,5 +1,6 @@ -from specifyweb.specify.tests.test_api import ApiTests -from specifyweb.specify.models import Recordset, Recordsetitem, Collectionobject +from specifyweb.specify.api.crud import get_resource, update_obj +from specifyweb.specify.models import Collectionobject, Recordset, Recordsetitem +from specifyweb.specify.tests.test_api import ApiTests, skip_perms_check @@ -107,4 +108,47 @@ def test_create_record_set_from_query_results(self): self.assertEqual( recordset.recordsetitems.count(), matching_cos.count(), - ) \ No newline at end of file + ) + + +class RecordSetEditingTests(ApiTests): + def test_edit_existing_record_set(self): + """Edit an existing record set by renaming it and replacing one item.""" + recordset = Recordset.objects.create( + collectionmemberid=self.collection.id, + dbtableid=Collectionobject.specify_model.tableId, + name="Original record set", + type=0, + specifyuser=self.specifyuser, + ) + first = self.collectionobjects[0] + second = self.collectionobjects[1] + third = self.collectionobjects[2] + recordset.recordsetitems.create(recordid=first.id) + recordset.recordsetitems.create(recordid=second.id) + + data = get_resource("recordset", recordset.id, skip_perms_check) + data["name"] = "Edited record set" + data["recordsetitems"] = [ + {"recordid": item.recordid} + for item in recordset.recordsetitems.all() + if item.recordid != second.id + ] + data["recordsetitems"].append({"recordid": third.id}) + + update_obj( + self.collection, + self.agent, + "recordset", + data["id"], + data["version"], + data, + ) + + recordset.refresh_from_db() + self.assertEqual(recordset.name, "Edited record set") + self.assertEqual(recordset.version, data["version"] + 1) + self.assertEqual( + set(recordset.recordsetitems.values_list("recordid", flat=True)), + {first.id, third.id}, + ) From e2c59425715626ca276c5ba6fbb6034a66c6e0d1 Mon Sep 17 00:00:00 2001 From: Rijul Poudel Date: Tue, 27 Oct 2026 13:37:17 -0500 Subject: [PATCH 3/4] [test] Add record set to record set --- specifyweb/specify/tests/test_record_sets.py | 72 +++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/specifyweb/specify/tests/test_record_sets.py b/specifyweb/specify/tests/test_record_sets.py index bbb44c15f15..c0dcd372020 100644 --- a/specifyweb/specify/tests/test_record_sets.py +++ b/specifyweb/specify/tests/test_record_sets.py @@ -1,4 +1,6 @@ -from specifyweb.specify.api.crud import get_resource, update_obj +from specifyweb.specify.api.crud import get_resource, post_resource, update_obj +from specifyweb.specify.api.exceptions import RecordSetException +from specifyweb.specify.api.serializers import uri_for_model from specifyweb.specify.models import Collectionobject, Recordset, Recordsetitem from specifyweb.specify.tests.test_api import ApiTests, skip_perms_check @@ -152,3 +154,71 @@ def test_edit_existing_record_set(self): set(recordset.recordsetitems.values_list("recordid", flat=True)), {first.id, third.id}, ) + + +class RecordSetNestingTests(ApiTests): + def test_add_record_set_to_record_set(self): + """Add an existing record set as a record in another record set.""" + inner = Recordset.objects.create( + collectionmemberid=self.collection.id, + dbtableid=Collectionobject.specify_model.tableId, + name="Inner record set", + type=0, + specifyuser=self.specifyuser, + ) + inner.recordsetitems.create(recordid=self.collectionobjects[0].id) + + outer = Recordset.objects.create( + collectionmemberid=self.collection.id, + dbtableid=Recordset.specify_model.tableId, + name="Outer record set", + type=0, + specifyuser=self.specifyuser, + ) + + post_resource( + self.collection, + self.agent, + "recordsetitem", + { + "recordid": inner.id, + "recordset": uri_for_model("recordset", outer.id), + }, + ) + + nested = post_resource( + self.collection, + self.agent, + "recordset", + { + "name": "Nested record set", + "dbtableid": Collectionobject.specify_model.tableId, + "type": 0, + "specifyuser": uri_for_model( + "specifyuser", self.specifyuser.id + ), + }, + recordsetid=outer.id, + ) + + with self.assertRaises(RecordSetException): + post_resource( + self.collection, + self.agent, + "collectionobject", + { + "collection": uri_for_model( + "collection", self.collection.id + ), + "catalognumber": "not-a-record-set", + }, + recordsetid=outer.id, + ) + + outer.refresh_from_db() + self.assertEqual( + set(outer.recordsetitems.values_list("recordid", flat=True)), + {inner.id, nested.id}, + ) + self.assertEqual(inner.recordsetitems.count(), 1) + From 86921b75e0c05619ae62d005cb4a8c035eda8631 Mon Sep 17 00:00:00 2001 From: Rijul Poudel Date: Tue, 27 Oct 2026 13:37:27 -0500 Subject: [PATCH 4/4] [test] Rename record set --- specifyweb/specify/tests/test_record_sets.py | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/specifyweb/specify/tests/test_record_sets.py b/specifyweb/specify/tests/test_record_sets.py index c0dcd372020..ce86270765f 100644 --- a/specifyweb/specify/tests/test_record_sets.py +++ b/specifyweb/specify/tests/test_record_sets.py @@ -222,3 +222,39 @@ def test_add_record_set_to_record_set(self): ) self.assertEqual(inner.recordsetitems.count(), 1) + +class RecordSetRenameTests(ApiTests): + def test_rename_record_set(self): + """Rename an existing record set without changing its records.""" + recordset = Recordset.objects.create( + collectionmemberid=self.collection.id, + dbtableid=Collectionobject.specify_model.tableId, + name="Original record set name", + type=0, + specifyuser=self.specifyuser, + ) + first = self.collectionobjects[0] + second = self.collectionobjects[1] + recordset.recordsetitems.create(recordid=first.id) + recordset.recordsetitems.create(recordid=second.id) + + data = get_resource("recordset", recordset.id, skip_perms_check) + data["name"] = "Renamed record set" + + update_obj( + self.collection, + self.agent, + "recordset", + data["id"], + data["version"], + data, + ) + + recordset.refresh_from_db() + self.assertEqual(recordset.name, "Renamed record set") + self.assertEqual(recordset.version, data["version"] + 1) + self.assertEqual( + set(recordset.recordsetitems.values_list("recordid", flat=True)), + {first.id, second.id}, + ) +