Skip to content

Test: start a test suite for migration utils functions#8225

Open
CarolineDenis wants to merge 58 commits into
issue-8058from
issue-8224
Open

Test: start a test suite for migration utils functions#8225
CarolineDenis wants to merge 58 commits into
issue-8058from
issue-8224

Conversation

@CarolineDenis

@CarolineDenis CarolineDenis commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Fixes #8224

Checklist

  • Self-review the PR after opening it to make sure the changes look good and
    self-explanatory (or properly documented)
  • Add relevant issue to release milestone
  • Add pr to documentation list
  • Add automated tests
  • Add a reverse migration if a migration is present in the PR
  • Add migration function to
    def fix_schema_config(stdout: WriteToStdOut | None = None):

Testing instructions

Summary by CodeRabbit

  • New Features

    • Updated the key migration flow to use a centralized routine for de-duplicating discipline-scoped app resource directories.
  • Bug Fixes

    • Ensured selectseries defaults are consistently enforced to False during migrations.
    • Improved command error handling by explicitly re-raising exceptions after logging.
  • Tests

    • Added extensive unit and Django TestCase coverage for schema reader/writer defaults, idempotent bulk-create behavior, deduplication logic, and key-migration command ordering/dispatch.
    • Added additional command/database integration tests to verify idempotency across repeated runs.

@CarolineDenis CarolineDenis added this to the 7.12.0.7 milestone Jun 16, 2026
@github-project-automation github-project-automation Bot moved this to 📋Back Log in General Tester Board Jun 16, 2026
@CarolineDenis CarolineDenis linked an issue Jun 16, 2026 that may be closed by this pull request
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a96078a4-7250-4eec-ab70-124be4ce55d3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds comprehensive unit tests for migration utilities and management command, extracts discipline resource directory deduplication to a dedicated module, and consolidates migration helper imports. The test suite spans bulk creation, schema operations, default data initialization, deduplication, and tectonic ranks. Integration tests verify the migration command is idempotent across multiple runs without creating duplicates.

Changes

Migration Utils Unit Test Suite

Layer / File(s) Summary
Tests package initialization and bulk_create idempotent helper
specifyweb/specify/migration_utils/tests/__init__.py, specifyweb/specify/migration_utils/tests/test_bulk_create.py
Initializes the tests package and adds BulkCreateSplocaleitemstrIdempotentTest, which mocks model/queryset behavior and verifies idempotent bulk creation returns correct count, filter invocation, and bulk-create payload for locale item strings.
Helper 0003 cotype picklist migration test
specifyweb/specify/migration_utils/tests/test_helper_0003_cotype_picklist.py
Adds Helper0003CotypePicklistTest that configures mocked apps to return model stubs, mocks queryset chains to represent missing container-item and item-string records, and verifies Splocalecontaineritem and Splocaleitemstr creation calls.
Schema reader utility tests
specifyweb/specify/migration_utils/tests/test_schema_reader.py
Adds SchemaReaderTests covering hidden override detection and normalization, schema_overrides.json loading and field key normalization, hidden-field aggregation per discipline, explicit override filtering, and pure string/type conversion helpers.
Schema writer ORM interaction tests
specifyweb/specify/migration_utils/tests/test_schema_writer.py
Adds SchemaWriterTests covering bulk-create helper invocation for default schema config and revert logic that deletes container items and locale item-string records via queryset filtering.
Deduplication utility tests
specifyweb/specify/migration_utils/tests/test_deduplication.py
Adds comprehensive test coverage for SQL-based schema config deduplication via database cursor, ORM-based container/item/string deduplication with targeted __in filtering, combined orchestration, and idempotent behavior when no duplicates exist.
Default collection object type and treedef initialization tests
specifyweb/specify/migration_utils/tests/test_default_cots.py
Adds tests for default data setup: collection type creation per discipline, treedef discipline/institution link assignment, picklist item creation when picklists are created, cotype picklist per-collection creation, and taxon treedef discipline updates.
Tectonic ranks migration helper tests
specifyweb/specify/migration_utils/tests/test_tectonic_ranks.py
Adds tests validating default tectonic rank creation across missing disciplines, root node creation with logging per tree, discipline treedef creation via bulk-create and queryset update, and discipline link fixes.
Helper 0031 selectseries default test
specifyweb/specify/migration_utils/tests/test_helper_0031_add_default_for_selectseries.py
Adds MakeSelectSeriesFalseTests that creates Spquery records with various smushed values, invokes the migration helper, and verifies only None and False are normalized to False while True remains unchanged.
Helper 0039 loan and gift agent fields test
specifyweb/specify/migration_utils/tests/test_helper_0039_agent_fields_for_loan_and_gift.py
Adds UpdateLoanAndGiftAgentFieldsTests that patches the schema config update helper, verifies 10 calls with expected hidden=True default.

Management Command Test Infrastructure and Integration Tests

Layer / File(s) Summary
Migration test base classes and helpers
specifyweb/specify/management/commands/tests/test_migration_base.py
Creates MigrationCommandTestCase with wired stdout/stderr and helpers for recording and asserting ordered section calls; creates MigrationDatabaseTestCase extending ApiTests.
Command dispatch and section ordering tests
specifyweb/specify/management/commands/tests/command_tests.py
Adds KeyMigrationCommandTests verifying full pipeline section dispatch order with verbose stdout, subset selection without verbose, apply_patches dispatch with apps registry, and unknown function error handling.
Section invocation order and log_and_run tests
specifyweb/specify/management/commands/tests/test_run_key_migration_functions_order.py
Adds KeyMigrationSectionTests verifying section execution order with captured calls, logs without stdout, and fix_tectonic_ranks/fix_misc invoke internal functions in expected sequence.
App resource directory migration section tests
specifyweb/specify/management/commands/tests/app_resource_tests.py
Adds AppResourceTests verifying create_missing_app_resource_dirs writes formatted summary when stdout is provided, produces no output when stdout is None, and fix_app_resource_dirs invokes creation then deduplication in order.
Business rules migration section tests
specifyweb/specify/management/commands/tests/business_rules_tests.py
Adds BusinessRulesMigrationTests and BusinessRulesDatabaseTests verifying fix_business_rules runs in order, apply_default_uniqueness_rules skips existing constraints, and catnum_rule_editable updates only matching rules.
Collection object type migration section tests
specifyweb/specify/management/commands/tests/cots_tests.py
Adds CotsMigrationTests and CotsDatabaseTests exercising three ORM-backed migrations: taxon treedef discipline assignment, idempotent cotype picklist creation per collection, and cogtype type picklist creation with predefined items.
Permissions migration section tests
specifyweb/specify/management/commands/tests/permissions_tests.py
Adds PermissionsMigrationTests verifying fix_permissions runs sections in order and initialize_permissions passes expected arguments including migrate_sp6_users=False.
Schema config migration section tests
specifyweb/specify/management/commands/tests/schema_config_tests.py
Adds SchemaConfigTests verifying fix_schema_config initializes schema defaults in task order; adds KeyMigrationSelectedHelperDatabaseTests with database tests for locale string bulk-create idempotency and container item/string deduplication repointing.
Tectonic migration section database tests
specifyweb/specify/management/commands/tests/tectonic_tests.py
Adds TectonicDatabaseTests verifying create_default_tectonic_ranks creates ordered rank chain with parent links and create_root_tectonic_node idempotently creates single root Tectonicunit.
Discipline resource dir deduplication database test
specifyweb/specify/management/commands/tests/test_deduplicate_discipline.py
Adds KeyMigrationAppResourceDirDatabaseTests exercising deduplicate_discipline_resource_dirs to verify deletion of empty duplicates, tie-breaking by id, and preservation of scoped directories.
Full pipeline idempotency integration test
specifyweb/specify/management/commands/tests/test_run_key_migration_functions.py
Adds RunKeyMigrationFunctionsTests tracking record counts across multiple models, simulating Specify 7 usage twice with between-run data, verifying first run creates records and second run produces no duplicates while preserving between-run data.

Deduplication Helper Extraction and Import Updates

Layer / File(s) Summary
Discipline resource dir deduplication helper extraction
specifyweb/specify/migration_utils/deduplication.py
Adds deduplicate_discipline_resource_dirs(apps) that deletes per-discipline duplicate SpAppResourceDir rows by identifying empty directories with no related persisted views/resources and keeping the oldest per discipline.
Management command and migration import updates
specifyweb/specify/management/commands/run_key_migration_functions.py, specifyweb/specify/migrations/0031_add_default_for_selectseries.py
Updates management command to import deduplicate_discipline_resource_dirs and deduplicate_schema_config_orm from deduplication module, imports make_selectseries_false from dedicated helper_0031 module, removes local deduplication implementation, and ensures exceptions are re-raised.

Possibly related issues

  • specify/specify7#8042: Implements the idempotency test for run_key_migration_functions as specified, verifying the command produces no duplicate records on second run with between-run data insertion.

Possibly related PRs

  • specify/specify7#6308: Both PRs modify the run_key_migration_functions flow and command execution.
  • specify/specify7#8009: PR #8225 refactors deduplication helpers that align with the deterministic deduplication logic introduced in PR #8009.
  • specify/specify7#8039: PR #8225 adds tests exercising bulk-create idempotency and deduplication helpers used to prevent duplicate locale-schema records.

Suggested reviewers

  • grantfitzsimmons
  • acwhite211
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Testing Instructions ⚠️ Warning PR description's "Testing instructions" section contains only empty template comments with no actual testing steps, procedures, or verification guidance for the ~2,700 lines of new test code added. Fill in the "Testing instructions" section with clear steps: (1) how to run the test suite, (2) which Django test commands to use, (3) expected test pass/fail behavior, and (4) any special setup needed for database migration tests.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: introducing a comprehensive test suite for migration utility functions.
Linked Issues check ✅ Passed The PR adds extensive unit tests for migration utility functions, directly addressing issue #8224's requirement to add unit tests for migration code.
Out of Scope Changes check ✅ Passed All changes are focused on adding test coverage for migration utilities and a refactoring of import paths to support the testing infrastructure.
Automatic Tests ✅ Passed PR includes comprehensive automatic tests with 20 test files containing 35 test classes and 61 test methods, covering migration utilities and command functions as required by issue #8224.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-8224

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@CarolineDenis

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 8

♻️ Duplicate comments (1)
specifyweb/specify/migration_utils/tests/test_helper_0003_cotype_picklist.py (1)

63-63: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Same issue: passing _mock_new_parent instead of a proper apps mock.

This line has the same problem as line 36. Use a proper mock apps object.

🤖 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/migration_utils/tests/test_helper_0003_cotype_picklist.py`
at line 63, The call to create_cotype_splocalecontaineritem is passing
mock_container._mock_new_parent as the apps argument, but this should be a
proper mock apps object instead. Replace the _mock_new_parent argument with a
properly constructed mock apps object (following the same pattern used to fix
the similar issue at line 36) to ensure the test passes the correct type of
dependency to the function.
🧹 Nitpick comments (1)
specifyweb/specify/migration_utils/tests/test_schema_reader.py (1)

122-122: 💤 Low value

Remove trailing blank line.

The file ends with an extra blank line after the closing of the if __name__ == '__main__' block.

🤖 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/migration_utils/tests/test_schema_reader.py` at line 122,
There is an extra blank line at the end of the file after the if __name__ ==
'__main__' block. Remove the trailing blank line at the end of the
test_schema_reader.py file to clean up the file formatting.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@specifyweb/specify/migration_utils/tests/test_bulk_create.py`:
- Around line 46-50: The test is named with "idempotent" but doesn't actually
verify idempotent behavior. After the first assertion where result equals 2, add
a second call to the same function being tested using the same input rows, then
assert that this second call returns 0 to confirm that no duplicate rows are
created. This verifies that the function properly checks for existing rows and
avoids redundant bulk_create operations when called with identical data.
- Around line 16-22: The mock queryset setup in the test does not properly
simulate the chained filter calls used in the real function. The real
implementation calls filter twice in succession before order_by, but the current
mock setup only chains filter once. Modify the mock_qs setup so that the first
filter call returns an object whose filter method can be called again, and that
second filter call returns an object with an order_by method. Specifically,
ensure mock_qs.filter.return_value has its own filter method configured to
return an object that has order_by chained properly, allowing the double filter
pattern to work correctly.
- Around line 11-12: The `@patch` decorator is replacing the actual
bulk_create_splocaleitemstr_idempotent function with a mock, preventing the test
from verifying the real implementation logic. Remove the `@patch` decorator
entirely and instead mock only the internal ORM dependencies (like
model.objects.filter and model.objects.bulk_create) that the actual function
uses. Update the test assertions to verify that the real function correctly
calls these mocked ORM methods with the expected parameters (FK filters,
language filters, and bulk_create calls), rather than asserting on the mock of
the function itself.

In
`@specifyweb/specify/migration_utils/tests/test_helper_0003_cotype_picklist.py`:
- Line 36: The function create_cotype_splocalecontaineritem is being called with
mock_container._mock_new_parent, which is an internal MagicMock attribute and
not a valid apps object with the required get_model method. Create a proper mock
apps object that implements the get_model(app_label, model_name) method as
expected by the function signature, then pass this mock apps object as the
argument to the create_cotype_splocalecontaineritem call instead of the
_mock_new_parent attribute.
- Around line 9-11: The test is patching module-level names that don't actually
exist in helper_0003_cotype_picklist.py, but the real
create_cotype_splocalecontaineritem function dynamically obtains models using
apps.get_model('specify', ModelName). Replace the three `@patch` decorators
targeting Splocalecontainer, Splocalecontaineritem, and Splocaleitemstr with a
single `@patch` for the apps parameter, then configure apps.get_model.side_effect
to return the appropriate mock models based on the model name argument passed to
it. Apply this same pattern to both test methods in the file.

In `@specifyweb/specify/migration_utils/tests/test_schema_reader.py`:
- Around line 28-48: The test assertion for mock_path is incorrect because it
expects the full concatenated path string, but the actual function constructs
the path using chained division operators starting with
Path(settings.SPECIFY_CONFIG_DIR). Update the test to properly mock the Path
chaining behavior by setting up mock_path.return_value to support the
__truediv__ method (which implements the / operator) and chain operations, then
correct the assertion to expect mock_path to be called once with just "/config"
instead of the full path string. The mock path instance should also have
exists() and open() methods properly configured to support the chaining pattern.

In `@specifyweb/specify/migration_utils/tests/test_schema_writer.py`:
- Around line 12-16: The test patches are targeting non-existent module-level
imports in schema_writer, but the real
`update_table_schema_config_with_defaults` function obtains models through
`apps.get_model()` calls, not from direct imports. Instead of patching
`Splocalecontainer`, `Splocalecontaineritem`, `Splocaleitemstr` as if they were
module-level imports, create a mock `apps` object, configure its `get_model()`
method to return the appropriate mock models for each model name (specify,
Splocalecontainer), (specify, Splocalecontaineritem), and (specify,
Splocaleitemstr), and pass this mock as a parameter to the
`update_table_schema_config_with_defaults` function call in the test. Also patch
the `apps` import in the schema_writer module itself.
- Around line 65-72: The test is patching module-level names that the function
`revert_table_schema_config` does not use. Instead, the function obtains models
via `apps.get_model()`, but the test calls
`revert_table_schema_config("TestTable")` without passing an `apps` argument,
causing it to use the real Django registry (global_apps). Remove the three
`@patch` decorators for Splocalecontainer, Splocaleitemstr, and
Splocalecontaineritem. Instead, create a mock `apps` object that is configured
to return the mocked model instances when `get_model()` is called with the
appropriate model names, and pass this mock `apps` object as a parameter to the
`revert_table_schema_config` function call.

---

Duplicate comments:
In
`@specifyweb/specify/migration_utils/tests/test_helper_0003_cotype_picklist.py`:
- Line 63: The call to create_cotype_splocalecontaineritem is passing
mock_container._mock_new_parent as the apps argument, but this should be a
proper mock apps object instead. Replace the _mock_new_parent argument with a
properly constructed mock apps object (following the same pattern used to fix
the similar issue at line 36) to ensure the test passes the correct type of
dependency to the function.

---

Nitpick comments:
In `@specifyweb/specify/migration_utils/tests/test_schema_reader.py`:
- Line 122: There is an extra blank line at the end of the file after the if
__name__ == '__main__' block. Remove the trailing blank line at the end of the
test_schema_reader.py file to clean up the file formatting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 09e00975-8c37-4bc2-91a7-45fae919b02c

📥 Commits

Reviewing files that changed from the base of the PR and between 4c752cc and 6c779a5.

📒 Files selected for processing (5)
  • specifyweb/specify/migration_utils/tests/__init__.py
  • specifyweb/specify/migration_utils/tests/test_bulk_create.py
  • specifyweb/specify/migration_utils/tests/test_helper_0003_cotype_picklist.py
  • specifyweb/specify/migration_utils/tests/test_schema_reader.py
  • specifyweb/specify/migration_utils/tests/test_schema_writer.py

Comment thread specifyweb/specify/migration_utils/tests/test_bulk_create.py Outdated
Comment thread specifyweb/specify/migration_utils/tests/test_bulk_create.py Outdated
Comment thread specifyweb/specify/migration_utils/tests/test_bulk_create.py Outdated
Comment thread specifyweb/specify/migration_utils/tests/test_helper_0003_cotype_picklist.py Outdated
Comment thread specifyweb/specify/migration_utils/tests/test_helper_0003_cotype_picklist.py Outdated
Comment thread specifyweb/specify/migration_utils/tests/test_schema_reader.py Outdated
Comment thread specifyweb/specify/migration_utils/tests/test_schema_writer.py Outdated
Comment thread specifyweb/specify/migration_utils/tests/test_schema_writer.py Outdated
@github-project-automation github-project-automation Bot moved this from 📋Back Log to Dev Attention Needed in General Tester Board Jun 17, 2026
@CarolineDenis

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@CarolineDenis

Copy link
Copy Markdown
Contributor Author

@CodeRabbit full review

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 8

🧹 Nitpick comments (4)
specifyweb/specify/migration_utils/tests/test_helper_0039_agent_fields_for_loan_and_gift.py (1)

1-1: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Remove unused import.

MagicMock is imported but never used in the test.

🧹 Proposed fix
-from unittest.mock import patch, MagicMock
+from unittest.mock import patch
🤖 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/migration_utils/tests/test_helper_0039_agent_fields_for_loan_and_gift.py`
at line 1, The import statement at the beginning of the test file imports
MagicMock from unittest.mock, but MagicMock is never used in the test code.
Remove MagicMock from the import statement, leaving only the imports that are
actually used in the test.
specifyweb/specify/migration_utils/tests/test_helper_0003_cotype_picklist.py (1)

60-61: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Strengthen assertions for both locale string creations

mock_itemstr.objects.create.assert_called() is too permissive for this path. This scenario should verify both expected creates (label + description), otherwise a partial regression can still pass.

Proposed assertion update
-        mock_itemstr.objects.create.assert_called()
+        self.assertEqual(mock_itemstr.objects.create.call_count, 2)
+        mock_itemstr.objects.create.assert_any_call(
+            language="en",
+            itemname=created_item,
+            text=helper_0003_cotype_picklist.COT_TEXT,
+        )
+        mock_itemstr.objects.create.assert_any_call(
+            language="en",
+            itemdesc=created_item,
+            text=helper_0003_cotype_picklist.COT_TEXT,
+        )
🤖 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/migration_utils/tests/test_helper_0003_cotype_picklist.py`
around lines 60 - 61, The assertion on mock_itemstr.objects.create is too
permissive as it only verifies the method was called at least once. In this test
path, both a label and description locale string should be created, so
strengthen the assertion in test_helper_0003_cotype_picklist.py to verify that
exactly two create calls are made to mock_itemstr.objects.create. Replace
assert_called() with assert_called_with() or use assert_has_calls() to ensure
both the label and description string creations are verified, preventing partial
regressions where only one create occurs instead of the expected two.
specifyweb/specify/migration_utils/tests/test_tectonic_ranks.py (1)

130-168: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use distinct root_rank mocks per tree to verify per-tree binding.

Right now both TectonicUnit.objects.create assertions can pass even if the implementation accidentally reuses a single rank object across trees. Using per-call get_or_create results will make this test catch that regression.

Proposed test hardening
-        root_rank = MagicMock()
-        TectonicUnitTreeDefItem.objects.get_or_create.return_value = (
-            root_rank,
-            True,
-        )
+        root_rank1 = MagicMock()
+        root_rank2 = MagicMock()
+        TectonicUnitTreeDefItem.objects.get_or_create.side_effect = [
+            (root_rank1, True),
+            (root_rank2, True),
+        ]
@@
         TectonicUnit.objects.create.assert_any_call(
@@
-            definitionitem=root_rank,
+            definitionitem=root_rank1,
         )
@@
         TectonicUnit.objects.create.assert_any_call(
@@
-            definitionitem=root_rank,
+            definitionitem=root_rank2,
         )
🤖 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/migration_utils/tests/test_tectonic_ranks.py` around lines
130 - 168, The test currently uses the same root_rank mock object for both
trees, which means the test won't catch if the implementation accidentally
reuses a single rank across trees instead of creating distinct ones per tree.
Create two separate mock objects (for example, root_rank_1 and root_rank_2),
configure TectonicUnitTreeDefItem.objects.get_or_create to return different
mocks for each call using side_effect (returning tuples like (root_rank_1, True)
then (root_rank_2, True)), and then update the two assert_any_call assertions to
verify that the correct distinct rank mock is passed to each
TectonicUnit.objects.create call for its respective tree.
specifyweb/specify/management/commands/tests/app_resource_tests.py (1)

70-75: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Avoid asserting the literal <lambda> log token.

This assertion is brittle and tied to an implementation detail, not behavior. Prefer asserting call order/content semantically (or using a named callable in the command) so harmless refactors don’t break this test.

🤖 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/app_resource_tests.py` around
lines 70 - 75, The test assertion in app_resource_tests.py is brittle because it
asserts on the literal string "Running <lambda>..." which depends on
implementation details. Either modify the stdout.call_args_list assertion to
remove the lambda call check and only verify the meaningful calls like "Running
deduplicate_discipline_resource_dirs..." (testing the semantic behavior and call
order), or refactor the command code to use a named callable function instead of
a lambda function so the log message reflects a stable, descriptive name rather
than the implementation detail of using an anonymous lambda.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@specifyweb/specify/management/commands/tests/cots_tests.py`:
- Around line 7-8: The CotsDatabaseTests class currently inherits from
MigrationCommandTestCase, but since it performs real ORM operations and requires
API/fixture state attributes like self.collection, self.discipline, and
self.collectionobjecttype, it should instead inherit from a database-oriented
test base class. Replace the import statement to import the appropriate database
test base class instead of MigrationCommandTestCase, and then update the
CotsDatabaseTests class definition to inherit from this database-oriented base
class.

In `@specifyweb/specify/management/commands/tests/schema_config_tests.py`:
- Around line 144-149: The test assertion checking that keeper.text equals
"Updated Name" expects update behavior that is not guaranteed by the
bulk_create_splocaleitemstr_idempotent helper function. The helper is designed
only to deduplicate and create objects idempotently, not to update existing
records. Remove the assertion self.assertEqual(keeper.text, "Updated Name") from
the test and keep only the assertions that verify the actual guaranteed
behavior: that created_count is 1 and that the duplicate object no longer exists
in the database.
- Around line 31-61: The names list in this test includes migration function
names that are not currently part of the active fix_schema_config runnable
sequence (some are commented out or disabled upstream). When
patch.object(rkm.usc, name) attempts to patch these non-existent functions, the
test will fail before it can validate the actual ordering behavior. Remove all
function names from the names list that are not currently active in the
fix_schema_config contract, keeping only those that are actually being executed
in the current sequence.
- Around line 199-210: The test is attempting to refresh database objects
(duplicate_name and duplicate_desc) that are actually deleted by the
deduplicate_containeritems_and_strings function, causing a mismatch between
expected test behavior and actual function behavior. Remove the refresh_from_db
calls on duplicate_name and duplicate_desc since these rows no longer exist
after deduplication, and update the assertions to verify that the duplicate
strings are deleted rather than repointed. Ensure the test only checks for the
existence of keeper's names through keeper.names.values_list and confirms that
conflicting duplicate entries are removed from the Splocaleitemstr table.

In `@specifyweb/specify/management/commands/tests/tectonic_tests.py`:
- Around line 6-12: The TectonicDatabaseTests class inherits from plain TestCase
but the test method
test_create_default_tectonic_ranks_creates_chain_and_assigns_discipline uses
self.discipline without initialization, which will cause runtime failures.
Change the inheritance of TectonicDatabaseTests from TestCase to the appropriate
migration database base class that provisions test data and fixtures (including
the discipline instance), so that self.discipline is properly available for use
in the test methods.

In
`@specifyweb/specify/management/commands/tests/test_run_key_migration_functions.py`:
- Around line 44-48: The record_counts() function is counting objects using the
implicit default database alias, but the migration command being tested
(run_key_migration_connection) executes within use_migration_connection()
context which explicitly uses the 'migrations' alias. If these aliases target
different databases, the count comparison will be invalid. Update the
record_counts() function to explicitly route all model.objects.count() queries
to the 'migrations' database alias by using the .using("migrations") method on
each model's queryset to ensure counts are taken from the same database that the
command operates on.

In `@specifyweb/specify/migration_utils/deduplication.py`:
- Around line 179-183: The Exists query in the SpAppResourceDir.objects.filter
is only checking for timestampcreated__lt, which misses rows with identical
timestamps. Modify the filter to also handle the tie-break case where timestamps
are equal but the ID is lower. Add an OR condition (using Q objects) that checks
for either an earlier timestamp OR (equal timestamp AND lower ID) to ensure
duplicates with identical timestamps are properly identified as
earlier_exists=True and deduplicated based on the lower ID value.

In `@specifyweb/specify/migration_utils/tests/test_bulk_create.py`:
- Around line 66-70: The test currently inspects only the last call to
bulk_create using call_args and asserts the length is 2, but since bulk_create
is executed once per FK group (itemname and itemdesc) with one object each, it
will be called twice with one object per call. Replace the assertion on
call_args with assertions using call_args_list to verify all calls to the
bulk_create method. Check that bulk_create was called the expected number of
times and that each individual call in the list contains the correct number of
objects per group.

---

Nitpick comments:
In `@specifyweb/specify/management/commands/tests/app_resource_tests.py`:
- Around line 70-75: The test assertion in app_resource_tests.py is brittle
because it asserts on the literal string "Running <lambda>..." which depends on
implementation details. Either modify the stdout.call_args_list assertion to
remove the lambda call check and only verify the meaningful calls like "Running
deduplicate_discipline_resource_dirs..." (testing the semantic behavior and call
order), or refactor the command code to use a named callable function instead of
a lambda function so the log message reflects a stable, descriptive name rather
than the implementation detail of using an anonymous lambda.

In
`@specifyweb/specify/migration_utils/tests/test_helper_0003_cotype_picklist.py`:
- Around line 60-61: The assertion on mock_itemstr.objects.create is too
permissive as it only verifies the method was called at least once. In this test
path, both a label and description locale string should be created, so
strengthen the assertion in test_helper_0003_cotype_picklist.py to verify that
exactly two create calls are made to mock_itemstr.objects.create. Replace
assert_called() with assert_called_with() or use assert_has_calls() to ensure
both the label and description string creations are verified, preventing partial
regressions where only one create occurs instead of the expected two.

In
`@specifyweb/specify/migration_utils/tests/test_helper_0039_agent_fields_for_loan_and_gift.py`:
- Line 1: The import statement at the beginning of the test file imports
MagicMock from unittest.mock, but MagicMock is never used in the test code.
Remove MagicMock from the import statement, leaving only the imports that are
actually used in the test.

In `@specifyweb/specify/migration_utils/tests/test_tectonic_ranks.py`:
- Around line 130-168: The test currently uses the same root_rank mock object
for both trees, which means the test won't catch if the implementation
accidentally reuses a single rank across trees instead of creating distinct ones
per tree. Create two separate mock objects (for example, root_rank_1 and
root_rank_2), configure TectonicUnitTreeDefItem.objects.get_or_create to return
different mocks for each call using side_effect (returning tuples like
(root_rank_1, True) then (root_rank_2, True)), and then update the two
assert_any_call assertions to verify that the correct distinct rank mock is
passed to each TectonicUnit.objects.create call for its respective tree.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: be5667aa-4669-4fcd-9100-220c010d3fff

📥 Commits

Reviewing files that changed from the base of the PR and between 4a208b1 and ba79e48.

📒 Files selected for processing (27)
  • specifyweb/specify/management/commands/run_key_migration_functions.py
  • specifyweb/specify/management/commands/tests/__init__.py
  • specifyweb/specify/management/commands/tests/app_resource_tests.py
  • specifyweb/specify/management/commands/tests/business_rules_tests.py
  • specifyweb/specify/management/commands/tests/command_tests.py
  • specifyweb/specify/management/commands/tests/cots_tests.py
  • specifyweb/specify/management/commands/tests/permissions_tests.py
  • specifyweb/specify/management/commands/tests/schema_config_tests.py
  • specifyweb/specify/management/commands/tests/tectonic_tests.py
  • specifyweb/specify/management/commands/tests/test_deduplicate_discipline.py
  • specifyweb/specify/management/commands/tests/test_migration_base.py
  • specifyweb/specify/management/commands/tests/test_run_key_migration_functions.py
  • specifyweb/specify/management/commands/tests/test_run_key_migration_functions_order.py
  • specifyweb/specify/migration_utils/deduplication.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0003_cotype_picklist.py
  • specifyweb/specify/migration_utils/migration_helpers/helper_0031_add_default_for_selectseries.py
  • specifyweb/specify/migration_utils/tests/__init__.py
  • specifyweb/specify/migration_utils/tests/test_bulk_create.py
  • specifyweb/specify/migration_utils/tests/test_deduplication.py
  • specifyweb/specify/migration_utils/tests/test_default_cots.py
  • specifyweb/specify/migration_utils/tests/test_helper_0003_cotype_picklist.py
  • specifyweb/specify/migration_utils/tests/test_helper_0031_add_default_for_selectseries.py
  • specifyweb/specify/migration_utils/tests/test_helper_0039_agent_fields_for_loan_and_gift.py
  • specifyweb/specify/migration_utils/tests/test_schema_reader.py
  • specifyweb/specify/migration_utils/tests/test_schema_writer.py
  • specifyweb/specify/migration_utils/tests/test_tectonic_ranks.py
  • specifyweb/specify/migrations/0031_add_default_for_selectseries.py
💤 Files with no reviewable changes (1)
  • specifyweb/specify/migration_utils/migration_helpers/helper_0003_cotype_picklist.py

Comment thread specifyweb/specify/management/commands/tests/cots_tests.py Outdated
Comment thread specifyweb/specify/management/commands/tests/schema_config_tests.py
Comment thread specifyweb/specify/management/commands/tests/schema_config_tests.py
Comment thread specifyweb/specify/management/commands/tests/schema_config_tests.py Outdated
Comment thread specifyweb/specify/management/commands/tests/tectonic_tests.py Outdated
Comment on lines +44 to +48
def record_counts():
return {
name: model.objects.count()
for name, model in TRACKED_MODELS.items()
}

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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect command-side DB context and router hooks
rg -n -C3 'def use_migration_connection|use_migration_connection|db_for_read|db_for_write|DATABASE_ROUTERS' specifyweb

# 2) Confirm this test currently uses implicit managers
rg -n -C2 'def record_counts|objects\.count\(|objects\.create\(|call_command\("run_key_migration_functions"' \
  specifyweb/specify/management/commands/tests/test_run_key_migration_functions.py

# 3) Inspect DATABASES aliases in settings
fd -i 'settings*.py' | xargs -r rg -n -C2 'DATABASES|["'\'']default["'\'']|["'\'']migrations["'\'']'

Repository: specify/specify7

Length of output: 11189


Verify DB alias consistency between test fixture/count helpers and command execution.

run_key_migration_connection() runs inside use_migration_connection() which explicitly routes operations to the 'migrations' database alias. This test creates rows and snapshots counts through implicit .objects managers outside that context, which will route to the default database alias. If these aliases target different backing databases, the idempotency assertion validates the wrong dataset.

Please verify your DATABASES configuration and if needed, pin setup/count helpers to the same alias (for example, .using("migrations")).

🤖 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/test_run_key_migration_functions.py`
around lines 44 - 48, The record_counts() function is counting objects using the
implicit default database alias, but the migration command being tested
(run_key_migration_connection) executes within use_migration_connection()
context which explicitly uses the 'migrations' alias. If these aliases target
different databases, the count comparison will be invalid. Update the
record_counts() function to explicitly route all model.objects.count() queries
to the 'migrations' database alias by using the .using("migrations") method on
each model's queryset to ensure counts are taken from the same database that the
command operates on.

Comment on lines +179 to +183
earlier_exists=Exists(
SpAppResourceDir.objects.filter(
discipline_id=OuterRef('discipline_id'),
timestampcreated__lt=OuterRef('timestampcreated'),
**common_filters

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Check dedup query predicate (strict timestamp less-than only):"
rg -n -C3 'timestampcreated__lt=OuterRef' specifyweb/specify/migration_utils/deduplication.py

echo
echo "Check test expectation requiring tie-break on equal timestamp:"
rg -n -C5 'test_deduplicate_discipline_resource_dirs_tie_breaks_on_id|timestampcreated=timestamp' specifyweb/specify/management/commands/tests/test_deduplicate_discipline.py

Repository: specify/specify7

Length of output: 1590


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check test status ==="
# Find the test and see if there's any indication it's skipped or expected to fail
rg -n -A20 'def test_deduplicate_discipline_resource_dirs_tie_breaks_on_id' specifyweb/specify/management/commands/tests/test_deduplicate_discipline.py

echo
echo "=== Check both query patterns in detail ==="
# Line 90 context
rg -n -B5 -A5 'timestampcreated__lt=OuterRef' specifyweb/specify/migration_utils/deduplication.py | head -30

echo
echo "=== Verify imports in deduplication.py ==="
head -20 specifyweb/specify/migration_utils/deduplication.py

Repository: specify/specify7

Length of output: 2579


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check for RowNumber usage in deduplication logic ==="
rg -n -B10 -A10 'RowNumber' specifyweb/specify/migration_utils/deduplication.py

echo
echo "=== Full context for line 90 function (Container dedup) ==="
sed -n '80,95p' specifyweb/specify/migration_utils/deduplication.py

echo
echo "=== Check if test has skip/xfail markers ==="
rg -n -B2 'def test_deduplicate_discipline_resource_dirs_tie_breaks_on_id' specifyweb/specify/management/commands/tests/test_deduplicate_discipline.py

echo
echo "=== Check if the SpAppResourceDir function is even called ==="
rg -n 'def deduplicate_discipline_resource_dirs' specifyweb/specify/migration_utils/deduplication.py

Repository: specify/specify7

Length of output: 2611


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Full deduplicate_discipline_resource_dirs function ==="
sed -n '160,190p' specifyweb/specify/migration_utils/deduplication.py

echo
echo "=== Check what happens after finding duplicates ==="
sed -n '160,200p' specifyweb/specify/migration_utils/deduplication.py

Repository: specify/specify7

Length of output: 2446


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check Container dedup function for comparison ==="
sed -n '75,100p' specifyweb/specify/migration_utils/deduplication.py

echo
echo "=== Look for any post-query tie-breaking logic ==="
rg -n 'rn\s*>|RowNumber|order_by.*id' specifyweb/specify/migration_utils/deduplication.py | head -20

echo
echo "=== Check if there's any additional filtering in deduplicate_discipline_resource_dirs after the query ==="
sed -n '160,195p' specifyweb/specify/migration_utils/deduplication.py

Repository: specify/specify7

Length of output: 3091


Add deterministic tie-break logic for equal timestamps.

The timestampcreated__lt filter at line 182 only catches earlier rows, missing duplicates with identical timestamps. The test test_deduplicate_discipline_resource_dirs_tie_breaks_on_id expects these to be deduplicated by keeping the lower ID, but the current query never identifies them as earlier_exists=True. Add an ID-based tie-break condition for equal timestamps.

Proposed fix
-from django.db.models import Exists, OuterRef
+from django.db.models import Exists, OuterRef, Q
...
                 earlier_exists=Exists(
                     SpAppResourceDir.objects.filter(
                         discipline_id=OuterRef('discipline_id'),
-                        timestampcreated__lt=OuterRef('timestampcreated'),
+                        Q(timestampcreated__lt=OuterRef('timestampcreated'))
+                        | Q(
+                            timestampcreated=OuterRef('timestampcreated'),
+                            id__lt=OuterRef('id'),
+                        ),
                         **common_filters
                 )
             )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
earlier_exists=Exists(
SpAppResourceDir.objects.filter(
discipline_id=OuterRef('discipline_id'),
timestampcreated__lt=OuterRef('timestampcreated'),
**common_filters
earlier_exists=Exists(
SpAppResourceDir.objects.filter(
discipline_id=OuterRef('discipline_id'),
Q(timestampcreated__lt=OuterRef('timestampcreated'))
| Q(
timestampcreated=OuterRef('timestampcreated'),
id__lt=OuterRef('id'),
),
**common_filters
🤖 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/migration_utils/deduplication.py` around lines 179 - 183,
The Exists query in the SpAppResourceDir.objects.filter is only checking for
timestampcreated__lt, which misses rows with identical timestamps. Modify the
filter to also handle the tie-break case where timestamps are equal but the ID
is lower. Add an OR condition (using Q objects) that checks for either an
earlier timestamp OR (equal timestamp AND lower ID) to ensure duplicates with
identical timestamps are properly identified as earlier_exists=True and
deduplicated based on the lower ID value.

Comment thread specifyweb/specify/migration_utils/tests/test_bulk_create.py Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Dev Attention Needed

Development

Successfully merging this pull request may close these issues.

Add unit tests for migration code

1 participant