Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
9a86fe8
Test: start a test suite for migration utils functions
CarolineDenis Jun 16, 2026
fe23a15
Merge remote-tracking branch 'origin/issue-8058' into issue-8224
CarolineDenis Jun 17, 2026
ea17a32
Test: create seperate test for bulk_create_splocaleitemstr_idempotent
CarolineDenis Jun 17, 2026
3867ead
Test: create tests for schema_writer
CarolineDenis Jun 17, 2026
0a09095
Test: create tests for migration helper 0003r
CarolineDenis Jun 17, 2026
a10438b
Test: Fix helper 0003 tests
CarolineDenis Jun 17, 2026
8b86924
Reverts
CarolineDenis Jun 17, 2026
bc80a06
Merge remote-tracking branch 'origin/issue-8058' into issue-8224
CarolineDenis Jun 17, 2026
3d76f47
Change schema_Reader tests
CarolineDenis Jun 17, 2026
7950165
Update schema_reader tests
CarolineDenis Jun 17, 2026
c14c0f4
Revert
CarolineDenis Jun 17, 2026
d5f1dbb
Fix test_bulk_create
CarolineDenis Jun 17, 2026
404bc2e
Fix test_0003 helper
CarolineDenis Jun 17, 2026
6c779a5
Comment
CarolineDenis Jun 17, 2026
db7216a
fix: fix mock
CarolineDenis Jun 17, 2026
51ac810
fix: update mock functions
CarolineDenis Jun 17, 2026
a8e9662
Refactor: bulk create tests
CarolineDenis Jun 17, 2026
8467571
Refactor: bulk create tests
CarolineDenis Jun 17, 2026
7f8257a
fix: improve schema reader tests
CarolineDenis Jun 17, 2026
b2be20a
fix: update schema writer tests
CarolineDenis Jun 17, 2026
fcdf255
Refactor: bulk create tests rename kwargs
CarolineDenis Jun 17, 2026
410864a
fix: remove unecessary patch
CarolineDenis Jun 17, 2026
c4bc5d0
Test: test assert delete in schema writer
CarolineDenis Jun 17, 2026
efabfd6
Merge remote-tracking branch 'origin/issue-8058' into issue-8224
CarolineDenis Jun 17, 2026
49b0371
Test: mock apps
CarolineDenis Jun 17, 2026
f85a824
Todo
CarolineDenis Jun 17, 2026
8968e0c
Add test for deduplication
CarolineDenis Jun 18, 2026
17459b3
Add test for default cots
CarolineDenis Jun 18, 2026
9060ef5
fix: add test for tectonic ranks
CarolineDenis Jun 18, 2026
999f8da
fix: add default for selectseries migration
CarolineDenis Jun 18, 2026
4ad3ce5
fix: add tests for run key migration
CarolineDenis Jun 18, 2026
053b06c
fix: Clear cached schema override state between tests
CarolineDenis Jun 18, 2026
ffdf85f
fix: Assert call
CarolineDenis Jun 18, 2026
d7161ec
fix: Revert run_key_migration chnages
CarolineDenis Jun 18, 2026
c3cf4d7
Merge remote-tracking branch 'origin/issue-8058' into issue-8224
CarolineDenis Jun 22, 2026
faae90d
Add second test file for run_key_migration_functions
CarolineDenis Jun 22, 2026
8d8622c
Add test suite for select series migration helper 0031
CarolineDenis Jun 22, 2026
e8f1eb7
Test: Update test_run_key_migration_functions_2
CarolineDenis Jun 22, 2026
4409b79
Fix: Add decorator
CarolineDenis Jun 22, 2026
b6a523e
Fix: Import ApiTest
CarolineDenis Jun 22, 2026
a831e7e
Test: Add tests for helper_0039
CarolineDenis Jun 22, 2026
1e95d61
Refactor: Remove unecessary agent-loan-gift test
CarolineDenis Jun 22, 2026
72eaaff
Refactor: Move dedup logic to deduplication file
CarolineDenis Jun 22, 2026
ba79e48
Test: Add a new test suite for run key migration indepotency
CarolineDenis Jun 22, 2026
8dbe71b
Fix: Update cots_tests.py
CarolineDenis Jun 22, 2026
06e8f30
Fix: Align expected migration function names
CarolineDenis Jun 22, 2026
f7bb933
Fix: assertion
CarolineDenis Jun 22, 2026
ec885eb
Fix: fix keeper logic
CarolineDenis Jun 22, 2026
88d3588
Fix: Change tectonic class
CarolineDenis Jun 22, 2026
9d48f36
Fix: Adjust bulk-create assertions
CarolineDenis Jun 22, 2026
59f0ecb
Fix: Renaming wrong migration helper title
CarolineDenis Jun 23, 2026
be044b4
Fix: Renaming wrong migration helper title import
CarolineDenis Jun 23, 2026
8202bc1
Test: Add test suite for migration helper 0007
CarolineDenis Jun 23, 2026
42b37a9
Test: Add test suite for migration helper 0017
CarolineDenis Jun 23, 2026
7c8b866
Test: Add test suite for migration helper 0008, 0004, 0012
CarolineDenis Jun 23, 2026
b65e892
Test: Add test for helper_0013_collectionobjectgroup_parentcog
CarolineDenis Jun 23, 2026
d698e09
Fix: Deduplicate discipline test
CarolineDenis Jun 23, 2026
9630fcb
Test: Skip run_key_migration test
CarolineDenis Jun 23, 2026
0ad2b28
Fix: Fix failing migration tests by initializing required system pick…
CarolineDenis Jun 24, 2026
4b24248
Merge remote-tracking branch 'origin/issue-8058' into issue-8224
CarolineDenis Jun 24, 2026
bfeb633
fix: update test_default_cots
CarolineDenis Jun 24, 2026
d8748e3
fix: update test_default_cots
CarolineDenis Jun 24, 2026
7a60c83
fix: use database for picklist agetype tests
melton-jason Jun 24, 2026
acf8bc8
fix: cogtype picklist test
melton-jason Jun 24, 2026
1bab9ba
Merge branch 'issue-8058' into issue-8224
melton-jason Jun 24, 2026
492a6ff
fix: use all on queryset over manager in test
melton-jason Jun 24, 2026
cd4af73
fix: improve previous migration test file
CarolineDenis Jun 25, 2026
206ba5b
Test: Add test suite for migration helper 0020
CarolineDenis Jun 25, 2026
a6757ce
Test: Add test suite for migration helper 0024
CarolineDenis Jun 25, 2026
d7e6f58
Test: Add test suite for migration helper 0027
CarolineDenis Jun 25, 2026
a872096
Test: Add test suite for migration helper 0035
CarolineDenis Jun 25, 2026
38a5f6f
Test: Add test suite for migration helper 0018
CarolineDenis Jun 25, 2026
3d9f34e
Test: Add test suite for migration helper 0021
CarolineDenis Jun 25, 2026
910d73c
Test: Add test suite for migration helper 0021 -2
CarolineDenis Jun 25, 2026
b37b179
Test: Add test suite for migration helper 0032
CarolineDenis Jun 25, 2026
3334863
Test: Add test suite for migration helper 0033
CarolineDenis Jun 25, 2026
f0d04f9
Test: Add test suite for migration helper 0042
CarolineDenis Jun 25, 2026
8cdc580
fix: remove import
CarolineDenis Jun 25, 2026
b077f0d
Test
Jun 25, 2026
e7f4071
Test: Add test suite for migration helper_0015
Jun 30, 2026
0663b28
Test: Add test suite for migration helper_0023
Jun 30, 2026
57f2355
Test: Add test suite for migration helper_0034
Jun 30, 2026
7a77b3d
Test: Add test suite for migration helper_0023
Jun 30, 2026
2b5a896
Test: Add test suite for migration helper_0040
Jun 30, 2026
5b33006
Test: Improve test suite for migration helpers
Jun 30, 2026
adcf24d
Test: Fix assert test suite for migration helper_0040
Jul 1, 2026
bcebcb3
Test: Add container desc to test suite
Jul 1, 2026
6fa769f
Test: Fix age citation test
Jul 1, 2026
95aaefe
Test: Improve test_run_key order
Jul 1, 2026
68e7fb3
Test: Improve helper test 0042
Jul 1, 2026
118e4b3
Test: Assert first run of key migration
Jul 1, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from specifyweb.specify.migration_utils.migration_helpers.helper_0003_cotype_picklist import create_cotype_splocalecontaineritem, create_cotype_picklist
from specifyweb.specify.migration_utils.migration_helpers.helper_0004_stratigraphy_age import create_agetype_picklist, create_strat_table_schema_config_with_defaults
from specifyweb.specify.migration_utils.migration_helpers.helper_0007_schema_config_update import create_cogtype_picklist
from specifyweb.specify.migration_utils.migration_helpers.helper_0008_schema_config_update import update_relative_age_fields
from specifyweb.specify.migration_utils.migration_helpers.helper_0008_ageCitations_fix import update_relative_age_fields
from specifyweb.specify.migration_utils.migration_helpers.helper_0012_add_cojo_to_schema_config import add_cojo_to_schema_config
from specifyweb.specify.migration_utils.migration_helpers.helper_0013_collectionobjectgroup_parentcog import update_cog_schema_config
from specifyweb.specify.migration_utils.migration_helpers.helper_0015_add_version_to_ages import update_age_schema_config
Expand All @@ -31,7 +31,7 @@
from specifyweb.specify.migration_utils.migration_helpers.helper_0040_components import create_table_schema_config_with_defaults, remove_componentparent_item
from specifyweb.specify.migration_utils.migration_helpers.helper_0042_discipline_type_picklist import create_discipline_type_picklist
from specifyweb.specify.migration_utils.router import use_migration_connection
from specifyweb.specify.migration_utils.misc_migrations import make_selectseries_false
from specifyweb.specify.migration_utils.migration_helpers.helper_0031_add_default_for_selectseries import make_selectseries_false
from specifyweb.specify.migration_utils.tectonic_ranks import create_default_tectonic_ranks, create_root_tectonic_node, fix_tectonic_unit_treedef_discipline_links
from specifyweb.backend.patches.migration_utils import apply_migrations as apply_patches

Expand Down Expand Up @@ -268,4 +268,4 @@ def handle(self, *args, **options):
self.stdout.write(self.style.SUCCESS(f"Applied {func_name}"))
except Exception:
logger.exception("An error occurred while running key migrations")
raise
raise
Empty file.
76 changes: 76 additions & 0 deletions specifyweb/specify/management/commands/tests/app_resource_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from unittest.mock import Mock, patch, sentinel, call
from specifyweb.specify.management.commands import run_key_migration_functions as rkm
from specifyweb.specify.management.commands.tests.test_migration_base import MigrationCommandTestCase


class AppResourceTests(MigrationCommandTestCase):
def test_create_missing_app_resource_dirs_writes_summary(self):
stdout = Mock()

ensure_dirs_path = (
"specifyweb.backend.setup_tool.app_resource_defaults."
"ensure_all_discipline_resource_dirs"
)
with patch(
ensure_dirs_path,
return_value={"total_disciplines": 4, "created": 2, "updated": 1},
) as ensure_dirs:
rkm.create_missing_app_resource_dirs(stdout, sentinel.apps)

ensure_dirs.assert_called_once_with()
stdout.assert_called_once_with(
"Ensured discipline app resource directories: total=4, created=2, updated=1"
)

def test_create_missing_app_resource_dirs_without_stdout_writes_nothing(self):
ensure_dirs_path = (
"specifyweb.backend.setup_tool.app_resource_defaults."
"ensure_all_discipline_resource_dirs"
)
with patch(
ensure_dirs_path,
return_value={"total_disciplines": 4, "created": 2, "updated": 1},
) as ensure_dirs:
rkm.create_missing_app_resource_dirs(None, sentinel.apps)

ensure_dirs.assert_called_once_with()

def test_fix_app_resource_dirs_runs_creation_then_deduplication(self):
calls = []
stdout = Mock()

def create_missing_app_resource_dirs(stdout_arg, apps):
calls.append(("create_missing_app_resource_dirs", stdout_arg, apps))

def deduplicate_discipline_resource_dirs(apps):
calls.append(("deduplicate_discipline_resource_dirs", apps))

with (
patch.object(rkm, "apps", sentinel.apps),
patch.object(
rkm,
"create_missing_app_resource_dirs",
create_missing_app_resource_dirs,
),
patch.object(
rkm,
"deduplicate_discipline_resource_dirs",
deduplicate_discipline_resource_dirs,
),
):
rkm.fix_app_resource_dirs(stdout)

self.assertEqual(
calls,
[
("create_missing_app_resource_dirs", stdout, sentinel.apps),
("deduplicate_discipline_resource_dirs", sentinel.apps),
],
)
self.assertEqual(
stdout.call_args_list,
[
call("Running <lambda>..."),
call("Running deduplicate_discipline_resource_dirs..."),
],
)
105 changes: 105 additions & 0 deletions specifyweb/specify/management/commands/tests/business_rules_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from unittest.mock import patch
from types import SimpleNamespace

from django.apps import apps as django_apps
from django.test import TestCase

from specifyweb.backend.businessrules.models import UniquenessRule, UniquenessRuleField
from specifyweb.specify.models import Discipline
from specifyweb.specify.management.commands import run_key_migration_functions as rkm
from specifyweb.specify.management.commands.tests.test_migration_base import MigrationCommandTestCase


class BusinessRulesMigrationTests(MigrationCommandTestCase):
def test_fix_business_rules_runs_migrations_in_order(self):
names = [
"apply_default_uniqueness_rules_to_disciplines",
"catnum_rule_editable",
"fix_global_default_rules",
]
self._assert_section_calls(
rkm.fix_business_rules,
[(rkm, name) for name in names],
names,
)

def test_apply_default_uniqueness_rules_skips_existing_db_constraints(self):
discipline_without_constraint = SimpleNamespace(id=1)
discipline_with_constraint = SimpleNamespace(id=2)

class FakeDiscipline:
objects = SimpleNamespace(
all=lambda: [discipline_without_constraint, discipline_with_constraint]
)

class FakeUniquenessRuleManager:
def filter(self, discipline, isDatabaseConstraint):
self.last_is_database_constraint = isDatabaseConstraint
return SimpleNamespace(
exists=lambda: discipline is discipline_with_constraint
)

fake_uniqueness_rule_manager = FakeUniquenessRuleManager()

class FakeUniquenessRule:
objects = fake_uniqueness_rule_manager

class FakeApps:
def get_model(self, app_label, model_name):
return {
("specify", "Discipline"): FakeDiscipline,
("businessrules", "UniquenessRule"): FakeUniquenessRule,
}[(app_label, model_name)]

fake_apps = FakeApps()

with patch.object(rkm, "apply_default_uniqueness_rules") as apply_rules:
rkm.apply_default_uniqueness_rules_to_disciplines(fake_apps)

apply_rules.assert_called_once_with(
discipline_without_constraint,
registry=fake_apps,
)
self.assertIs(fake_uniqueness_rule_manager.last_is_database_constraint, True)


class BusinessRulesDatabaseTests(TestCase):
def test_catnum_rule_editable_only_updates_matching_catalog_number_rule(self):
discipline = Discipline.objects.create(name="Test Discipline")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate Discipline model definition and inspect required fields
ast-grep run --pattern 'class Discipline($_):
  $$$' --lang python specifyweb/specify/models.py

Repository: specify/specify7

Length of output: 4331


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the test around the create call and nearby fixtures/utilities.
sed -n '1,180p' specifyweb/specify/management/commands/tests/business_rules_tests.py

printf '\n---\n'

# Check whether this test module defines or imports helpers/fixtures for Discipline requirements.
rg -n "Discipline|Division|DataType|GeographyTreeDef|GeologicTimePeriodTreeDef|factory|fixture" specifyweb/specify/management/commands/tests/business_rules_tests.py

Repository: specify/specify7

Length of output: 4291


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the save path and the test base for any fixtures/setup that supply required Discipline fields.
ast-grep outline specifyweb/specify/models.py --match 'def custom_save' --view expanded
printf '\n---\n'
sed -n '1,220p' specifyweb/specify/management/commands/tests/test_migration_base.py

Repository: specify/specify7

Length of output: 2063


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect custom_save and the Discipline model class for any automatic defaulting of required fields.
sed -n '1,80p' specifyweb/specify/models.py

printf '\n---\n'

# Check for any Discipline-specific save hooks/default fixtures elsewhere.
rg -n "def custom_save|Discipline\(|geographytreedef|geologictimeperiodtreedef|datatype =" specifyweb/specify -g '!**/migrations/**'

Repository: specify/specify7

Length of output: 16030


Populate the required Discipline fields here. Discipline requires division, datatype, geographytreedef, and geologictimeperiodtreedef; objects.create(name=...) will fail before this test reaches catnum_rule_editable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specifyweb/specify/management/commands/tests/business_rules_tests.py` at line
68, Populate the required fields when creating the test Discipline in
business_rules_tests instead of using only name. Update the
Discipline.objects.create call in the test setup to supply division, datatype,
geographytreedef, and geologictimeperiodtreedef so the fixture can be created
successfully before reaching catnum_rule_editable.

matching_rule = UniquenessRule.objects.create(
modelName="Collectionobject",
discipline=discipline,
isDatabaseConstraint=True,
)
UniquenessRuleField.objects.create(
uniquenessrule=matching_rule,
fieldPath="catalogNumber",
isScope=False,
)
UniquenessRuleField.objects.create(
uniquenessrule=matching_rule,
fieldPath="collection",
isScope=True,
)
nonmatching_rule = UniquenessRule.objects.create(
modelName="Collectionobject",
discipline=discipline,
isDatabaseConstraint=True,
)
UniquenessRuleField.objects.create(
uniquenessrule=nonmatching_rule,
fieldPath="catalogNumber",
isScope=False,
)
UniquenessRuleField.objects.create(
uniquenessrule=nonmatching_rule,
fieldPath="discipline",
isScope=True,
)

rkm.catnum_rule_editable(django_apps)

matching_rule.refresh_from_db()
nonmatching_rule.refresh_from_db()
self.assertFalse(matching_rule.isDatabaseConstraint)
self.assertTrue(nonmatching_rule.isDatabaseConstraint)
66 changes: 66 additions & 0 deletions specifyweb/specify/management/commands/tests/command_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from io import StringIO
from unittest.mock import Mock, patch, sentinel

from specifyweb.specify.management.commands import run_key_migration_functions as rkm
from specifyweb.specify.management.commands.tests.test_migration_base import MigrationCommandTestCase


class KeyMigrationCommandTests(MigrationCommandTestCase):
def test_full_pipeline_dispatches_sections_in_order_with_verbose_stdout(self):
calls = []

def section(name):
def wrapped(stdout):
calls.append((name, stdout is not None))
return wrapped

command = self._command()
command.funcs = {name: section(name) for name in self.section_names}

command.handle(functions=[], verbose=True)

self.assertEqual(calls, [(name, True) for name in self.section_names])

def test_selected_sections_run_in_requested_order_without_verbose_stdout(self):
calls = []

def section(name):
def wrapped(stdout):
calls.append((name, stdout))
return wrapped

command = self._command()
command.funcs = {name: section(name) for name in self.section_names}

command.handle(
functions=["fix_misc", "fix_cots", "fix_permissions"],
verbose=False,
)

self.assertEqual(
calls,
[
("fix_misc", None),
("fix_cots", None),
("fix_permissions", None),
],
)

def test_apply_patches_dispatch_passes_apps_registry_not_stdout(self):
command = self._command()

with patch.object(rkm, "apps", sentinel.apps), patch.object(rkm, "apply_patches") as apply_patches:
command.handle(functions=["apply_patches"], verbose=True)

apply_patches.assert_called_once_with(sentinel.apps)

def test_unknown_function_writes_error_and_dispatches_nothing(self):
stdout = StringIO()
stderr = StringIO()
command = rkm.Command(stdout=stdout, stderr=stderr)
command.funcs = {"known": Mock()}

command.handle(functions=["unknown"], verbose=True)

command.funcs["known"].assert_not_called()
self.assertIn("Unknown function: unknown", stderr.getvalue())
94 changes: 94 additions & 0 deletions specifyweb/specify/management/commands/tests/cots_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from django.apps import apps as django_apps
from specifyweb.specify import models
from specifyweb.specify.management.commands import run_key_migration_functions as rkm
from specifyweb.specify.management.commands.tests.test_migration_base import (
MigrationCommandTestCase,
MigrationDatabaseTestCase,
)

class CotsMigrationTests(MigrationCommandTestCase):
def test_fix_cots_runs_migrations_in_order(self):
names = [
"create_default_collection_types",
"create_default_discipline_for_tree_defs",
"create_cogtype_type_picklist",
"set_discipline_for_taxon_treedefs",
"fix_taxon_treedef_discipline_links",
"create_cotype_picklist"
]

self._assert_section_calls(
rkm.fix_cots,
[(rkm, name) for name in names],
names,
)


class CotsDatabaseTests(MigrationDatabaseTestCase):
def test_set_discipline_for_taxon_treedefs_uses_collection_discipline(self):
taxon_tree_def = models.Taxontreedef.objects.create(
name=f"Unlinked Taxon Tree {self.collection.id}",
)
self.collectionobjecttype.taxontreedef = taxon_tree_def
self.collectionobjecttype.save()

rkm.set_discipline_for_taxon_treedefs(django_apps)

taxon_tree_def.refresh_from_db()
self.assertEqual(taxon_tree_def.discipline_id, self.discipline.id)

def test_create_cotype_picklist_creates_readonly_system_picklist_idempotently(self):
new_collection = models.Collection.objects.create(
catalognumformatname="test",
collectionname=f"TestCollection{self.collection.id}",
isembeddedcollectingevent=False,
discipline=self.discipline,
)
models.Picklist.objects.filter(
collection__in=[self.collection, new_collection],
name="CollectionObjectType",
).delete()

rkm.create_cotype_picklist(django_apps)
rkm.create_cotype_picklist(django_apps)

for collection in [self.collection, new_collection]:
picklists = models.Picklist.objects.filter(
collection=collection,
name="CollectionObjectType",
)
self.assertEqual(picklists.count(), 1)
picklist = picklists.get()
self.assertTrue(picklist.issystem)
self.assertTrue(picklist.readonly)
self.assertEqual(picklist.type, 1)
self.assertEqual(picklist.tablename, "collectionobjecttype")
self.assertEqual(picklist.sizelimit, -1)
self.assertEqual(picklist.sorttype, 1)
self.assertEqual(picklist.formatter, "CollectionObjectType")

def test_create_cogtype_type_picklist_creates_default_items_idempotently(self):
models.Picklist.objects.filter(
collection=self.collection,
name="SystemCOGTypes",
).delete()

rkm.create_cogtype_type_picklist(django_apps)
rkm.create_cogtype_type_picklist(django_apps)

picklist = models.Picklist.objects.get(
collection=self.collection,
name="SystemCOGTypes",
)
self.assertFalse(picklist.issystem)
self.assertFalse(picklist.readonly)
self.assertEqual(picklist.type, 0)
self.assertEqual(
set(picklist.picklistitems.values_list("title", "value")),
{
("Discrete", "Discrete"),
("Consolidated", "Consolidated"),
("Drill Core", "Drill Core"),
},
)
self.assertEqual(picklist.picklistitems.count(), 3)
Loading
Loading