Skip to content

Рефакторинг тестов и фиксы#637

Merged
theshadowco merged 10 commits into
developfrom
feature/refactTest
Jul 6, 2026
Merged

Рефакторинг тестов и фиксы#637
theshadowco merged 10 commits into
developfrom
feature/refactTest

Conversation

@theshadowco

@theshadowco theshadowco commented Jul 6, 2026

Copy link
Copy Markdown
Member

Описание

  1. Переработаны тестовые утиллиты
  2. Переработаны тесты для более простого их сопровождения
  3. скорректированы значения интерфейсов по умолчанию
  4. хранение пути для макета изменено с Path на URI
  5. скорректировано заполнение режима совместимости расширений
  6. скорректирован формат длины для стандартных реквизитов кода и номера
  7. реализовано чтение стилей и интерфейсов обычного приложения для формата едт
  8. скорректированы конверторы для сериализации в json для тестов
  9. скорректирован алгоритм определения mdotype для некоторых дочерних
  10. исправлено чтение элементов форм для обоих форматов

Связанные задачи

Closes

Чеклист

Общие

  • Ветка PR обновлена из develop
  • Отладочные, закомментированные и прочие, не имеющие смысла участки кода удалены
  • Изменения покрыты тестами
  • Обязательные действия перед коммитом выполнены (запускал команду gradlew precommit)

Дополнительно

Summary by CodeRabbit

  • New Features

    • Expanded support for recognizing additional object and child types, improving how related items are identified across configurations.
    • Added broader fixture-based validation coverage for many metadata objects and form data cases.
  • Bug Fixes

    • Updated default compatibility mode values for configurations to better match current behavior.
    • Improved handling of external data sources, web services, templates, and forms for more reliable loading and comparison.
    • Added support for an additional built-in value type name during conversion.

2. хранение пути для макета изменено с Path на URI
3. скорректировано заполнение режима совместимости расширений
4. скорректирован формат длины для стандартных реквизитов кода и номера
5. реализовано чтение стилей и интерфейсов обычного приложения для формата едт
6. скорректированы конверторы для сериализации в json для тестов
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@theshadowco, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 28 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 209cb3e4-260f-4b2a-b0ee-31baf5835453

📥 Commits

Reviewing files that changed from the base of the PR and between 95c2d79 and 2c7c3f5.

📒 Files selected for processing (6)
  • src/test/java/com/github/_1c_syntax/bsl/mdo/AccumulationRegisterTest.java
  • src/test/java/com/github/_1c_syntax/bsl/mdo/children/ObjectFormTest.java
  • src/test/java/com/github/_1c_syntax/bsl/mdo/children/ObjectTemplateTest.java
  • src/test/java/com/github/_1c_syntax/bsl/test_utils/Fixtures.java
  • src/test/java/com/github/_1c_syntax/bsl/test_utils/assertions/MDCollectionAssert.java
  • src/test/java/com/github/_1c_syntax/bsl/test_utils/assertions/ModuleCollectionAssert.java
📝 Walkthrough

Walkthrough

This PR adds explicit getMdoType() implementations across many MDO child classes, changes default interface compatibility modes to TAXI, migrates storage data paths from Path to URI, reworks FormElementType key lookup, updates several reader/converter classes (including a new EDT MetaDataObjectConverter), and replaces the test infrastructure (MDTestUtilsFixtures/Assertions) with corresponding rewrites of most test classes.

Changes

Production code changes

Layer / File(s) Summary
getMdoType() overrides
src/main/java/.../mdo/children/*.java
Adds getMdoType() returning specific MDOType constants to numerous MDO child classes; removes an unused modules field from ObjectTemplate.
Compatibility mode defaults
Configuration.java, ConfigurationExtension.java, MDCReaderContext.java
Changes default interfaceCompatibilityMode to TAXI and adds a branch writing configuration-extension compatibility mode.
Path to URI migration
DataCompositionSchema.java, TemplateData.java, DataCompositionSchemaConverter.java, PredefinedData.java
Switches dataPath type from Path to URI across storage classes and the converter that constructs it.
FormElementType key lookup
FormElementType.java
Removes several enum members and introduces a computeKeys() helper overriding specific name-to-type mappings.
Reader/converter infrastructure
build.gradle.kts, DesignerRootWrapper.java, EDTReader.java, edt/converter/MetaDataObjectConverter.java, Unmarshaller.java, ValueTypeConverter.java, StdAtrInfo.java, FormElementReaderContext.java, MDReaderContext.java, CodeSeries.java, designer/DesignerReader.java, designer/converter/MetaDataObjectConverter.java
Moves DesignerRootWrapper package, adds a new EDT MetaDataObjectConverter, updates EDT path resolution/aliasing, extInfo unmarshalling, and minor supporting fixes/import cleanups.

Test fixture infrastructure and test suite rewrite

Layer / File(s) Summary
Fixtures/Assertions foundation
test_utils/Fixtures.java, TestCollectionConverter.java, TestURIConverter.java, assertions/*.java
Introduces the Fixtures loading/caching/serialization utility and new AssertJ-style collection assertion helpers replacing MDTestUtils.
Fixture generators
GenerateChildFixtures.java, GenerateFormDataFixtures.java, GenerateTemplateDataFixtures.java
Adds disabled JUnit utilities that generate JSON fixtures for children/form/template data.
Fixture comparison tests
mdo/collect/ChildFixtureComparisonTest.java, FixtureComparisonTest.java, DefaultFormTest.java, SynonymTest.java
Adds dynamic and parameterized tests validating fixtures against serialized MDOs and default form/synonym behavior.
New children tests
mdo/children/*Test.java
Adds new parameterized tests for MDO child types validating ownership/child relationships and data fixtures.
mdclasses test migration
ConfigurationTest.java, ExternalDataProcessorTest.java, ExternalReportTest.java, MDClassesSolutionTest.java, MDClassesTest.java, RightsTest.java, ValueTypeTest.java
Migrates tests to Fixtures/Assertions, adjusting CSV inputs and expected counts.
mdo test migration
mdo/*Test.java (many files)
Rewrites MDO tests to use Fixtures.get and Assertions containment checks instead of MDTestUtils-based field assertions.

Estimated code review effort: 4 (Complex) | ~75 minutes

Possibly related PRs

Poem

A rabbit hopped through fields of types,
Adding getMdoType where none had rights.
Paths turned to URIs overnight,
While fixtures bloomed in JSON light.
Old MDTestUtils took its bow —
Fixtures and Assertions run the show now! 🐇

🚥 Pre-merge checks | ✅ 5
✅ 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 matches the PR’s main themes: test refactoring plus functional fixes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/refactTest

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.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Test Results

  486 files  +   84    486 suites  +84   11m 43s ⏱️ + 2m 55s
1 228 tests +  854  1 225 ✅ +  851   3 💤 + 3  0 ❌ ±0 
7 368 runs  +5 040  7 350 ✅ +5 022  18 💤 +18  0 ❌ ±0 

Results for commit 2c7c3f5. ± Comparison against base commit b55ee1f.

This pull request removes 140 and adds 994 tests. Note that renamed tests count towards both.
com.github._1c_syntax.bsl.examples.ValueTypeTest ‑ [1] "true", "mdclasses", "_edt"
com.github._1c_syntax.bsl.examples.ValueTypeTest ‑ [1] "true", "ssl_3_1", "_edt"
com.github._1c_syntax.bsl.examples.ValueTypeTest ‑ [3] "true", "ssl_3_2", "_edt"
com.github._1c_syntax.bsl.mdclasses.ConfigurationTest ‑ [1] "false", "mdclasses_5_1"
com.github._1c_syntax.bsl.mdclasses.ConfigurationTest ‑ [1] "true", "mdclasses", "_edt"
com.github._1c_syntax.bsl.mdclasses.ConfigurationTest ‑ [1] "true", "mdclasses_unknown", "_edt"
com.github._1c_syntax.bsl.mdclasses.ConfigurationTest ‑ [1] "true", "ssl_3_1", "_edt"
com.github._1c_syntax.bsl.mdclasses.ConfigurationTest ‑ [1] "true", "ssl_3_2", "_edt"
com.github._1c_syntax.bsl.mdclasses.ConfigurationTest ‑ [2] "false", "mdclasses"
com.github._1c_syntax.bsl.mdclasses.ConfigurationTest ‑ [2] "false", "ssl_3_2"
…
com.github._1c_syntax.bsl.examples.ValueTypeTest ‑ [1] "true", "mdclasses"
com.github._1c_syntax.bsl.examples.ValueTypeTest ‑ [1] "true", "ssl_3_1"
com.github._1c_syntax.bsl.examples.ValueTypeTest ‑ [3] "true", "ssl_3_2"
com.github._1c_syntax.bsl.mdclasses.ConfigurationTest ‑ [10] "false", "mdclasses_3_27", "Configuration"
com.github._1c_syntax.bsl.mdclasses.ConfigurationTest ‑ [11] "false", "mdclasses_5_1", "Configuration"
com.github._1c_syntax.bsl.mdclasses.ConfigurationTest ‑ [12] "true", "mdclasses_5_1", "Configuration"
com.github._1c_syntax.bsl.mdclasses.ConfigurationTest ‑ [1] "true", "mdclasses", "Configuration"
com.github._1c_syntax.bsl.mdclasses.ConfigurationTest ‑ [1] "true", "ssl_3_1"
com.github._1c_syntax.bsl.mdclasses.ConfigurationTest ‑ [1] "true", "ssl_3_1", "Configuration"
com.github._1c_syntax.bsl.mdclasses.ConfigurationTest ‑ [2] "false", "mdclasses", "Configuration"
…

♻️ This comment has been updated with latest results.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/test/java/com/github/_1c_syntax/bsl/test_utils/TestURIConverter.java (1)

33-68: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use slash-separated URI fragments here TestURIConverter matches a URI path, but these constants come from Path.of(...).toString(). That makes the .replace(...) calls stop matching on Windows, so fixture normalization leaves the platform-specific path segments behind. Use explicit / literals or normalize the constants once.

🤖 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 `@src/test/java/com/github/_1c_syntax/bsl/test_utils/TestURIConverter.java`
around lines 33 - 68, The URI normalization in TestURIConverter is mixing URI
path matching with Path.of(...).toString(), which breaks the replace chain on
Windows because the separators no longer match. Update the constants used by
toString(Object) to use slash-separated URI fragments (or normalize them once up
front) so the .replace(...) calls for WORKDIR, EDTDIR, DESIGNERDIR, and the
other directory markers consistently strip fixture paths across platforms.
🧹 Nitpick comments (7)
src/test/java/com/github/_1c_syntax/bsl/mdo/children/ObjectFormTest.java (1)

110-148: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract duplicated fixture-discovery logic shared with ObjectTemplateTest.

discoverObjectFormDataRefs, parseParentRef, and the FormDataFixtureRef record are near-identical to discoverObjectTemplateDataRefs/parseParentRef/TemplateDataFixtureRef in ObjectTemplateTest.java (differing only by the fixture sub-directory name and record type). Consider extracting a generic helper (e.g. in Fixtures) parameterized by sub-directory name and a BiFunction constructing the ref, to avoid the duplicate and keep both test classes in sync.

🤖 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 `@src/test/java/com/github/_1c_syntax/bsl/mdo/children/ObjectFormTest.java`
around lines 110 - 148, Extract the duplicated fixture-discovery flow in
ObjectFormTest by reusing the same helper pattern used in ObjectTemplateTest for
discoverObjectTemplateDataRefs, parseParentRef, and the fixture ref record. Move
the shared directory-walking and parent-ref parsing logic into a generic utility
(for example in Fixtures) that accepts the fixture sub-directory name and a
constructor callback for the ref type, then update discoverObjectFormDataRefs
and FormDataFixtureRef to delegate to it.
src/test/java/com/github/_1c_syntax/bsl/mdo/CalculationRegisterTest.java (1)

90-93: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Unguarded getFirst() on recalculations list.

calculationRegister.getRecalculations().getFirst() throws NoSuchElementException if the list is empty for any of the four CSV rows/fixtures. Given the fixture-driven parameterization, an empty list would produce a confusing failure rather than a clear assertion message.

🧪 Suggested guard
-    var recalc = calculationRegister.getRecalculations().getFirst();
+    assertThat(calculationRegister.getRecalculations()).isNotEmpty();
+    var recalc = calculationRegister.getRecalculations().getFirst();
🤖 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 `@src/test/java/com/github/_1c_syntax/bsl/mdo/CalculationRegisterTest.java`
around lines 90 - 93, The test in CalculationRegisterTest is calling getFirst()
on the recalculations list without checking whether it is empty, which can throw
a confusing exception for fixture-driven cases. Update the assertion around
getRecalculations() to guard against an empty list first, then only inspect the
first recalculation when one is present; keep the existing Module::isProtected
check tied to the same recalc variable.
src/test/java/com/github/_1c_syntax/bsl/mdo/CommonFormTest.java (1)

117-118: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use PathUtils.getBaseName(fixtureFile) directly fixtureFile.getFileName() is redundant here, and importing PathUtils is clearer than the fully qualified reference.

🤖 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 `@src/test/java/com/github/_1c_syntax/bsl/mdo/CommonFormTest.java` around lines
117 - 118, The fixture name extraction in CommonFormTest is using an unnecessary
getFileName() call and a fully qualified PathUtils reference. Update the formRef
assignment in the test setup to use PathUtils.getBaseName directly on
fixtureFile, and use the imported PathUtils symbol so the intent is clearer and
the code is simpler.
src/test/java/com/github/_1c_syntax/bsl/mdo/CommonTemplateTest.java (2)

112-135: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Duplicate filesystem traversal across the two @TestFactory methods.

discoverCommonTemplateDataRefs() walks Fixtures.FIXTURES_PATH twice (once per Designer/EDT factory). Low impact given test-only scope, but could be memoized if fixture discovery grows expensive.

🤖 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 `@src/test/java/com/github/_1c_syntax/bsl/mdo/CommonTemplateTest.java` around
lines 112 - 135, The fixture discovery logic in discoverCommonTemplateDataRefs()
is traversing the filesystem separately for both `@TestFactory` paths, which
duplicates work. Refactor the CommonTemplateTest setup so the discovered
TemplateDataFixtureRef list is computed once and reused by the Designer and EDT
factories, for example by memoizing the result in a shared helper or cached
field. Keep the discovery behavior unchanged while centralizing the call to
discoverCommonTemplateDataRefs().

137-144: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing @Nullable on a method that can return null.

toGroupRef returns null when the ref lacks a dot or the type can't be mapped, but the return type isn't annotated. Per coding guidelines, jspecify null-safety annotations should be used.

🏷️ Suggested annotation
-  private static String toGroupRef(String mdoRef) {
+  private static `@Nullable` String toGroupRef(String mdoRef) {

As per coding guidelines, "Use @Nullable and @NonNull annotations from org.jspecify for null safety."

🤖 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 `@src/test/java/com/github/_1c_syntax/bsl/mdo/CommonTemplateTest.java` around
lines 137 - 144, The toGroupRef method can return null in the invalid-ref and
unmapped-type paths, so add the appropriate org.jspecify `@Nullable` annotation to
its return type/signature in CommonTemplateTest. Keep the existing logic
unchanged, and ensure the annotation matches the project’s null-safety
conventions used alongside MDOType.fromValue and toGroupRef.

Source: Coding guidelines

src/test/java/com/github/_1c_syntax/bsl/test_utils/Fixtures.java (1)

135-262: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Full classpath scan repeated on every serialization call.

asJson/write call createXstream, which runs a fresh ClassGraph scan over three packages plus reflective Class.forName + field introspection for every object serialized. Given FixtureComparisonTest/ChildFixtureComparisonTest invoke asJson once per fixture file (potentially dozens/hundreds of dynamic tests), this repeats an expensive classpath scan on every single test instead of once per suite.

Consider caching two XStream instances (compact/non-compact) built once and reused across calls.

♻️ Suggested caching approach
+  private static final Map<Boolean, XStream> xstreamCache = new ConcurrentHashMap<>();
+
   public static String asJson(Object obj) {
-    var xstream = createXstream(obj instanceof CF);
+    var xstream = xstreamCache.computeIfAbsent(obj instanceof CF, Fixtures::createXstream);
     xstream.registerConverter(new JavaBeanConverter(xstream.getMapper(), getBeanProvider(), obj.getClass()), -20);
     return xstream.toXML(obj);
   }
🤖 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 `@src/test/java/com/github/_1c_syntax/bsl/test_utils/Fixtures.java` around
lines 135 - 262, The serialization path in Fixtures.asJson/write rebuilds
XStream and reruns the full ClassGraph scan on every call, which is too
expensive for repeated fixture tests. Move the XStream setup from createXstream
into a cached singleton-style initialization and reuse two prebuilt instances
for the compact and non-compact cases, then have asJson select the appropriate
cached instance before registering the per-object JavaBeanConverter.
src/main/java/com/github/_1c_syntax/bsl/reader/common/context/MDCReaderContext.java (1)

109-113: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Magic number 99 should reference a named constant.

The sentinel check configurationExtensionCompatibilityMode.getVersion() == 99 hardcodes an implementation detail of CompatibilityMode (presumably "unset"/default version) without a named constant, making the intent unclear and fragile to future changes in CompatibilityMode.

♻️ Suggested refactor (pending confirmation of the actual constant name)
-    } else if (configurationExtensionCompatibilityMode.getVersion() == 99) {
+    } else if (configurationExtensionCompatibilityMode.getVersion() == CompatibilityMode.NOT_SET_VERSION) {

Please confirm whether CompatibilityMode exposes a named constant for this sentinel value.

🤖 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
`@src/main/java/com/github/_1c_syntax/bsl/reader/common/context/MDCReaderContext.java`
around lines 109 - 113, The sentinel check in MDCReaderContext uses a hardcoded
version value, so replace configurationExtensionCompatibilityMode.getVersion()
== 99 with a named constant from CompatibilityMode if one exists, or introduce
one there and use it here. Update the conditional in the context-writing logic
to reference that constant so the intent is clear and the value is centralized.
🤖 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
`@src/main/java/com/github/_1c_syntax/bsl/reader/edt/converter/MetaDataObjectConverter.java`:
- Around line 44-67: In MetaDataObjectConverter.unmarshal, the initial
reader.moveDown() is not balanced before returning, leaving the
HierarchicalStreamReader at the wrong depth. Update unmarshal to restore the
reader state by calling reader.moveUp() after the child processing loop and
before return readerContext.build(), keeping the existing readDesignerProperties
and ExtendXStream logic unchanged.

In `@src/test/java/com/github/_1c_syntax/bsl/mdo/AccumulationRegisterTest.java`:
- Around line 72-104: The test in AccumulationRegisterTest contains an
accidental duplicate “AttributeOwner” assertion block; remove the repeated block
so only one copy of the getAllAttributes, getStorageFields, and
getPlainStorageFields assertions remains. Use the existing
AccumulationRegisterTest assertions section and the repeated
getAllAttributes/getStorageFields/getPlainStorageFields calls to identify and
delete the extra copy without changing the intended coverage.

In `@src/test/java/com/github/_1c_syntax/bsl/mdo/CommonFormTest.java`:
- Around line 106-138: The fixture discovery in discoverCommonFormDataRefs can
fail silently and produce an empty test set, so make the failure explicit
instead of returning zero dynamic tests. Add a guard after discovery in the
CommonFormTest flow (or inside discoverCommonFormDataRefs) to assert the refs
list is non-empty, and have toGroupRef fail loudly or log/throw when
MDOType.fromValue cannot map a type instead of returning null. Use the existing
discoverCommonFormDataRefs and toGroupRef helpers to keep the fix localized and
ensure broken fixture layout or mapping issues surface as test failures.

In
`@src/test/java/com/github/_1c_syntax/bsl/test_utils/assertions/Assertions.java`:
- Around line 41-44: The Assertions.assertThat overload currently casts
Collection<? extends MD> to List<MD>, which will fail for non-List collections
such as Set. Update this method to either accept List<MD> explicitly or convert
the incoming collection to a new ArrayList before delegating to
MDCollectionAssert.assertThat, keeping the existing method name so call sites
remain easy to locate.

In
`@src/test/java/com/github/_1c_syntax/bsl/test_utils/assertions/JsonAssert.java`:
- Around line 100-119: The message parsing in JsonAssert.formatMessage skips
valid matches when the target phrase starts at index 0 because the checks use
indexOf(...) > 0. Update the three branches handling "Could not find match for
element", "Missing:", and "Unexpected:" to accept zero-based matches as well, so
the path-prefixed formatting still applies when there is no leading path text.
Keep the fix localized to the JsonAssert formatting logic.

In
`@src/test/java/com/github/_1c_syntax/bsl/test_utils/assertions/ModuleCollectionAssert.java`:
- Around line 146-158: The diagnostic labels in ModuleCollectionAssert’s failure
message are swapped, so the values from missing and extra are described
incorrectly. Update the String.format message in the comparison/fail path to
match the same wording used by MDCollectionAssert.check(), ensuring missing is
labeled as absent from expected and extra is labeled as absent from actual, and
keep the values passed to formatCollection aligned with those labels.
- Around line 90-114: The flatten() helper in ModuleCollectionAssert should
handle null varargs entries the same way as MDCollectionAssert. Update the
collection loop to skip any null collection before calling isEmpty(), so a
missing owner collection does not trigger an NPE. Keep the fix localized to
flatten(Collection<?>...) and preserve the existing ModuleOwner and Module
handling logic.

---

Outside diff comments:
In `@src/test/java/com/github/_1c_syntax/bsl/test_utils/TestURIConverter.java`:
- Around line 33-68: The URI normalization in TestURIConverter is mixing URI
path matching with Path.of(...).toString(), which breaks the replace chain on
Windows because the separators no longer match. Update the constants used by
toString(Object) to use slash-separated URI fragments (or normalize them once up
front) so the .replace(...) calls for WORKDIR, EDTDIR, DESIGNERDIR, and the
other directory markers consistently strip fixture paths across platforms.

---

Nitpick comments:
In
`@src/main/java/com/github/_1c_syntax/bsl/reader/common/context/MDCReaderContext.java`:
- Around line 109-113: The sentinel check in MDCReaderContext uses a hardcoded
version value, so replace configurationExtensionCompatibilityMode.getVersion()
== 99 with a named constant from CompatibilityMode if one exists, or introduce
one there and use it here. Update the conditional in the context-writing logic
to reference that constant so the intent is clear and the value is centralized.

In `@src/test/java/com/github/_1c_syntax/bsl/mdo/CalculationRegisterTest.java`:
- Around line 90-93: The test in CalculationRegisterTest is calling getFirst()
on the recalculations list without checking whether it is empty, which can throw
a confusing exception for fixture-driven cases. Update the assertion around
getRecalculations() to guard against an empty list first, then only inspect the
first recalculation when one is present; keep the existing Module::isProtected
check tied to the same recalc variable.

In `@src/test/java/com/github/_1c_syntax/bsl/mdo/children/ObjectFormTest.java`:
- Around line 110-148: Extract the duplicated fixture-discovery flow in
ObjectFormTest by reusing the same helper pattern used in ObjectTemplateTest for
discoverObjectTemplateDataRefs, parseParentRef, and the fixture ref record. Move
the shared directory-walking and parent-ref parsing logic into a generic utility
(for example in Fixtures) that accepts the fixture sub-directory name and a
constructor callback for the ref type, then update discoverObjectFormDataRefs
and FormDataFixtureRef to delegate to it.

In `@src/test/java/com/github/_1c_syntax/bsl/mdo/CommonFormTest.java`:
- Around line 117-118: The fixture name extraction in CommonFormTest is using an
unnecessary getFileName() call and a fully qualified PathUtils reference. Update
the formRef assignment in the test setup to use PathUtils.getBaseName directly
on fixtureFile, and use the imported PathUtils symbol so the intent is clearer
and the code is simpler.

In `@src/test/java/com/github/_1c_syntax/bsl/mdo/CommonTemplateTest.java`:
- Around line 112-135: The fixture discovery logic in
discoverCommonTemplateDataRefs() is traversing the filesystem separately for
both `@TestFactory` paths, which duplicates work. Refactor the CommonTemplateTest
setup so the discovered TemplateDataFixtureRef list is computed once and reused
by the Designer and EDT factories, for example by memoizing the result in a
shared helper or cached field. Keep the discovery behavior unchanged while
centralizing the call to discoverCommonTemplateDataRefs().
- Around line 137-144: The toGroupRef method can return null in the invalid-ref
and unmapped-type paths, so add the appropriate org.jspecify `@Nullable`
annotation to its return type/signature in CommonTemplateTest. Keep the existing
logic unchanged, and ensure the annotation matches the project’s null-safety
conventions used alongside MDOType.fromValue and toGroupRef.

In `@src/test/java/com/github/_1c_syntax/bsl/test_utils/Fixtures.java`:
- Around line 135-262: The serialization path in Fixtures.asJson/write rebuilds
XStream and reruns the full ClassGraph scan on every call, which is too
expensive for repeated fixture tests. Move the XStream setup from createXstream
into a cached singleton-style initialization and reuse two prebuilt instances
for the compact and non-compact cases, then have asJson select the appropriate
cached instance before registering the per-object JavaBeanConverter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

Comment on lines +44 to +67
@SneakyThrows
@Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
reader.moveDown();
var nodeClassName = reader.getNodeName();
Class<?> realClass = ExtendXStream.getRealClass(reader, nodeClassName);
if (realClass == null) {
LOGGER.error("Unexpected type `{}`, path: `{}`", nodeClassName, ExtendXStream.getCurrentPath(reader));
throw new IllegalStateException("Unexpected type: " + nodeClassName);
}

var readerContext = new MDReaderContext(reader);

while (reader.hasMoreChildren()) {
reader.moveDown();
var name = reader.getNodeName();
if ("Properties".equals(name)) {
readDesignerProperties(reader, context, readerContext);
}
reader.moveUp();
}

return readerContext.build();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the converter and any tests or helper patterns around it.
git ls-files | rg 'MetaDataObjectConverter\.java|ExtendXStream|MDReaderContext|Interface\.mdo|Style\.mdo|ConverterTest|XStream' -n

echo '--- MetaDataObjectConverter.java outline ---'
ast-grep outline src/main/java/com/github/_1c_syntax/bsl/reader/edt/converter/MetaDataObjectConverter.java --view expanded || true

echo '--- Relevant file excerpt ---'
sed -n '1,220p' src/main/java/com/github/_1c_syntax/bsl/reader/edt/converter/MetaDataObjectConverter.java

echo '--- Search for moveDown/moveUp in related code ---'
rg -n "moveDown\(|moveUp\(" src/main/java src/test/java | sed -n '1,220p'

Repository: 1c-syntax/mdclasses

Length of output: 16201


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search XStream contracts in the repository or bundled docs if present.
rg -n "A call to moveDown must be balanced with a call to moveUp|HierarchicalStreamReader|moveDown.*moveUp" . | sed -n '1,220p'

Repository: 1c-syntax/mdclasses

Length of output: 24883


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect any tests that exercise this converter or neighboring EDT readers.
rg -n "MetaDataObjectConverter|Interface\.mdo|Style\.mdo|reader/edt" src/test/java src/main/java | sed -n '1,240p'

Repository: 1c-syntax/mdclasses

Length of output: 476


🌐 Web query:

XStream HierarchicalStreamReader moveDown moveUp contract javadoc

💡 Result:

In the XStream library, the HierarchicalStreamReader interface defines the following contract for the moveDown and moveUp methods: moveDown: This method selects the current child as the current node [1][2][3]. Crucially, the Javadoc specifies that every call to moveDown must be balanced with a corresponding call to moveUp [1][3]. moveUp: This method selects the parent node as the current node [2][3]. These methods are used to navigate hierarchical data structures during deserialization. By adhering to the requirement that moveDown calls must be balanced by moveUp calls, implementers ensure that the reader correctly tracks its position within the node hierarchy [1][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- EDT MetaDataObjectConverter around unmarshal ---'
sed -n '1,180p' src/main/java/com/github/_1c_syntax/bsl/reader/edt/converter/MetaDataObjectConverter.java

echo '--- designer MetaDataObjectConverter around unmarshal ---'
sed -n '1,170p' src/main/java/com/github/_1c_syntax/bsl/reader/designer/converter/MetaDataObjectConverter.java

echo '--- MDReaderContext constructor and build ---'
sed -n '1,220p' src/main/java/com/github/_1c_syntax/bsl/reader/common/context/MDReaderContext.java

echo '--- EDT Reader unmarshal entrypoint ---'
sed -n '220,290p' src/main/java/com/github/_1c_syntax/bsl/reader/edt/EDTReader.java

echo '--- EDT converter unmarshal dispatcher ---'
sed -n '1,180p' src/main/java/com/github/_1c_syntax/bsl/reader/edt/converter/Unmarshaller.java

Repository: 1c-syntax/mdclasses

Length of output: 25319


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- ExtendXStream.readValue and related helpers ---'
sed -n '140,240p' src/main/java/com/github/_1c_syntax/bsl/reader/common/xstream/ExtendXStream.java

echo '--- ReadConverter interface / base helpers ---'
sed -n '1,220p' src/main/java/com/github/_1c_syntax/bsl/reader/common/xstream/ReadConverter.java

echo '--- EDT designer-related converters for comparison ---'
sed -n '1,140p' src/main/java/com/github/_1c_syntax/bsl/reader/edt/converter/MdoReferenceConverter.java
sed -n '1,120p' src/main/java/com/github/_1c_syntax/bsl/reader/edt/converter/ExternalSourceConverter.java

Repository: 1c-syntax/mdclasses

Length of output: 8578


Balance the initial moveDown() before returning. The method descends into the child node but never calls moveUp() before return readerContext.build();, leaving the reader at the wrong depth and risking later parsing errors.

🤖 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
`@src/main/java/com/github/_1c_syntax/bsl/reader/edt/converter/MetaDataObjectConverter.java`
around lines 44 - 67, In MetaDataObjectConverter.unmarshal, the initial
reader.moveDown() is not balanced before returning, leaving the
HierarchicalStreamReader at the wrong depth. Update unmarshal to restore the
reader state by calling reader.moveUp() after the child processing loop and
before return readerContext.build(), keeping the existing readDesignerProperties
and ExtendXStream logic unchanged.

Comment thread src/test/java/com/github/_1c_syntax/bsl/mdo/AccumulationRegisterTest.java Outdated
Comment on lines +106 to +138
private static List<FormDataFixtureRef> discoverCommonFormDataRefs() {
List<FormDataFixtureRef> refs = new ArrayList<>();
try (var packsStream = Files.list(Fixtures.FIXTURES_PATH)) {
packsStream.filter(Files::isDirectory)
.forEach(packDir -> {
var packName = packDir.getFileName().toString();
var formdataDir = packDir.resolve("formdata");
if (!Files.isDirectory(formdataDir)) return;
try (var filesStream = Files.list(formdataDir)) {
filesStream.filter(path -> path.toString().endsWith(".json"))
.forEach(fixtureFile -> {
var formRef = org.apache.commons.io.file.PathUtils
.getBaseName(fixtureFile.getFileName());
if (!formRef.startsWith("CommonForm.")) return;
var groupRef = toGroupRef(formRef);
if (groupRef != null) {
refs.add(new FormDataFixtureRef(packName, formRef, groupRef, fixtureFile));
}
});
} catch (IOException e) { throw new RuntimeException(e); }
});
} catch (IOException e) { throw new RuntimeException(e); }
return refs;
}

private static String toGroupRef(String mdoRef) {
var dotIndex = mdoRef.indexOf('.');
if (dotIndex < 0) return null;
var typeStr = mdoRef.substring(0, dotIndex);
var name = mdoRef.substring(dotIndex + 1);
var mdoTypeOpt = MDOType.fromValue(typeStr);
return mdoTypeOpt.map(mdoType -> mdoType.groupName() + "." + name).orElse(null);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Silent zero-test outcome if fixture discovery/mapping fails.

If Fixtures.FIXTURES_PATH/formdata layout changes or toGroupRef fails to map a type (returns null at Line 137 without any error/log), discoverCommonFormDataRefs() can silently return an empty list. compareFormDataDesigner/compareFormDataEDT would then generate zero dynamic tests — a vacuous pass that hides fixture coverage regressions.

Consider asserting the discovered list is non-empty (or failing loudly when toGroupRef can't map a type) so a broken discovery path surfaces as a test failure rather than silent success.

🧪 Suggested guard
   private static List<FormDataFixtureRef> discoverCommonFormDataRefs() {
     List<FormDataFixtureRef> refs = new ArrayList<>();
     try (var packsStream = Files.list(Fixtures.FIXTURES_PATH)) {
       ...
     } catch (IOException e) { throw new RuntimeException(e); }
+    if (refs.isEmpty()) {
+      throw new IllegalStateException("No CommonForm formdata fixtures discovered under " + Fixtures.FIXTURES_PATH);
+    }
     return refs;
   }
📝 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
private static List<FormDataFixtureRef> discoverCommonFormDataRefs() {
List<FormDataFixtureRef> refs = new ArrayList<>();
try (var packsStream = Files.list(Fixtures.FIXTURES_PATH)) {
packsStream.filter(Files::isDirectory)
.forEach(packDir -> {
var packName = packDir.getFileName().toString();
var formdataDir = packDir.resolve("formdata");
if (!Files.isDirectory(formdataDir)) return;
try (var filesStream = Files.list(formdataDir)) {
filesStream.filter(path -> path.toString().endsWith(".json"))
.forEach(fixtureFile -> {
var formRef = org.apache.commons.io.file.PathUtils
.getBaseName(fixtureFile.getFileName());
if (!formRef.startsWith("CommonForm.")) return;
var groupRef = toGroupRef(formRef);
if (groupRef != null) {
refs.add(new FormDataFixtureRef(packName, formRef, groupRef, fixtureFile));
}
});
} catch (IOException e) { throw new RuntimeException(e); }
});
} catch (IOException e) { throw new RuntimeException(e); }
return refs;
}
private static String toGroupRef(String mdoRef) {
var dotIndex = mdoRef.indexOf('.');
if (dotIndex < 0) return null;
var typeStr = mdoRef.substring(0, dotIndex);
var name = mdoRef.substring(dotIndex + 1);
var mdoTypeOpt = MDOType.fromValue(typeStr);
return mdoTypeOpt.map(mdoType -> mdoType.groupName() + "." + name).orElse(null);
}
private static List<FormDataFixtureRef> discoverCommonFormDataRefs() {
List<FormDataFixtureRef> refs = new ArrayList<>();
try (var packsStream = Files.list(Fixtures.FIXTURES_PATH)) {
packsStream.filter(Files::isDirectory)
.forEach(packDir -> {
var packName = packDir.getFileName().toString();
var formdataDir = packDir.resolve("formdata");
if (!Files.isDirectory(formdataDir)) return;
try (var filesStream = Files.list(formdataDir)) {
filesStream.filter(path -> path.toString().endsWith(".json"))
.forEach(fixtureFile -> {
var formRef = org.apache.commons.io.file.PathUtils
.getBaseName(fixtureFile.getFileName());
if (!formRef.startsWith("CommonForm.")) return;
var groupRef = toGroupRef(formRef);
if (groupRef != null) {
refs.add(new FormDataFixtureRef(packName, formRef, groupRef, fixtureFile));
}
});
} catch (IOException e) { throw new RuntimeException(e); }
});
} catch (IOException e) { throw new RuntimeException(e); }
if (refs.isEmpty()) {
throw new IllegalStateException("No CommonForm formdata fixtures discovered under " + Fixtures.FIXTURES_PATH);
}
return refs;
}
private static String toGroupRef(String mdoRef) {
var dotIndex = mdoRef.indexOf('.');
if (dotIndex < 0) return null;
var typeStr = mdoRef.substring(0, dotIndex);
var name = mdoRef.substring(dotIndex + 1);
var mdoTypeOpt = MDOType.fromValue(typeStr);
return mdoTypeOpt.map(mdoType -> mdoType.groupName() + "." + name).orElse(null);
}
🤖 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 `@src/test/java/com/github/_1c_syntax/bsl/mdo/CommonFormTest.java` around lines
106 - 138, The fixture discovery in discoverCommonFormDataRefs can fail silently
and produce an empty test set, so make the failure explicit instead of returning
zero dynamic tests. Add a guard after discovery in the CommonFormTest flow (or
inside discoverCommonFormDataRefs) to assert the refs list is non-empty, and
have toGroupRef fail loudly or log/throw when MDOType.fromValue cannot map a
type instead of returning null. Use the existing discoverCommonFormDataRefs and
toGroupRef helpers to keep the fix localized and ensure broken fixture layout or
mapping issues surface as test failures.

Comment on lines +41 to +44
@SuppressWarnings("unchecked")
public static MDCollectionAssert assertThat(Collection<? extends MD> actual, boolean ignored) {
return MDCollectionAssert.assertThat((List<MD>) actual, ignored);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file and related assertion types.
git ls-files 'src/test/java/com/github/_1c_syntax/bsl/test_utils/assertions/**' | sed -n '1,200p'

echo
echo '--- Assertions.java ---'
sed -n '1,140p' src/test/java/com/github/_1c_syntax/bsl/test_utils/assertions/Assertions.java

echo
echo '--- Search MDCollectionAssert ---'
rg -n "class MDCollectionAssert|static .*assertThat\(.*List<MD>|assertThat\(Collection|assertThat\(List" src/test/java/com/github/_1c_syntax/bsl/test_utils/assertions -S

Repository: 1c-syntax/mdclasses

Length of output: 3413


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- MDCollectionAssert.java ---'
sed -n '1,220p' src/test/java/com/github/_1c_syntax/bsl/test_utils/assertions/MDCollectionAssert.java

echo
echo '--- Call sites of Assertions.assertThat(..., boolean) for MD collections ---'
rg -n "assertThat\([^)]*MD|assertThat\([^)]*Collection<\? extends MD>|Assertions\.assertThat\(" src/test/java src/main/java -S

Repository: 1c-syntax/mdclasses

Length of output: 28612


Avoid casting Collection<? extends MD> to List<MD> here. A non-List collection like a Set will fail with ClassCastException; wrap it with new ArrayList<>(actual) or narrow the overload to List<MD>.

🤖 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
`@src/test/java/com/github/_1c_syntax/bsl/test_utils/assertions/Assertions.java`
around lines 41 - 44, The Assertions.assertThat overload currently casts
Collection<? extends MD> to List<MD>, which will fail for non-List collections
such as Set. Update this method to either accept List<MD> explicitly or convert
the incoming collection to a new ArrayList before delegating to
MDCollectionAssert.assertThat, keeping the existing method name so call sites
remain easy to locate.

Comment on lines +100 to +119
// "path Could not find match for element {...}"
int noMatchIdx = trimmed.indexOf("Could not find match for element");
if (noMatchIdx > 0) {
String path = trimmed.substring(0, noMatchIdx).trim();
return " Cannot match element at: " + path;
}

// "path Missing: ..."
int missingIdx = trimmed.indexOf("Missing:");
if (missingIdx > 0) {
String path = trimmed.substring(0, missingIdx).trim();
return " Missing: " + path + "\n " + trimmed.substring(missingIdx);
}

// "path Unexpected: ..."
int unexpectedIdx = trimmed.indexOf("Unexpected:");
if (unexpectedIdx > 0) {
String path = trimmed.substring(0, unexpectedIdx).trim();
return " Unexpected: " + path + "\n " + trimmed.substring(unexpectedIdx);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Boundary check > 0 misses matches at the start of the message.

Each branch uses indexOf(...) > 0 to decide whether to format the path-prefixed message. If the target phrase (e.g., "Could not find match for element") occurs at index 0 (no path prefix), the condition is false and the branch is skipped, falling through to worse formatting (the huge-message truncation or raw dump). Use >= 0 to also handle a zero-length prefix.

🐛 Suggested fix
     int noMatchIdx = trimmed.indexOf("Could not find match for element");
-    if (noMatchIdx > 0) {
+    if (noMatchIdx >= 0) {
       String path = trimmed.substring(0, noMatchIdx).trim();
       return "  Cannot match element at: " + path;
     }

     // "path Missing: ..."
     int missingIdx = trimmed.indexOf("Missing:");
-    if (missingIdx > 0) {
+    if (missingIdx >= 0) {
       String path = trimmed.substring(0, missingIdx).trim();
       return "  Missing: " + path + "\n  " + trimmed.substring(missingIdx);
     }

     // "path Unexpected: ..."
     int unexpectedIdx = trimmed.indexOf("Unexpected:");
-    if (unexpectedIdx > 0) {
+    if (unexpectedIdx >= 0) {
       String path = trimmed.substring(0, unexpectedIdx).trim();
       return "  Unexpected: " + path + "\n  " + trimmed.substring(unexpectedIdx);
     }
📝 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
// "path Could not find match for element {...}"
int noMatchIdx = trimmed.indexOf("Could not find match for element");
if (noMatchIdx > 0) {
String path = trimmed.substring(0, noMatchIdx).trim();
return " Cannot match element at: " + path;
}
// "path Missing: ..."
int missingIdx = trimmed.indexOf("Missing:");
if (missingIdx > 0) {
String path = trimmed.substring(0, missingIdx).trim();
return " Missing: " + path + "\n " + trimmed.substring(missingIdx);
}
// "path Unexpected: ..."
int unexpectedIdx = trimmed.indexOf("Unexpected:");
if (unexpectedIdx > 0) {
String path = trimmed.substring(0, unexpectedIdx).trim();
return " Unexpected: " + path + "\n " + trimmed.substring(unexpectedIdx);
}
// "path Could not find match for element {...}"
int noMatchIdx = trimmed.indexOf("Could not find match for element");
if (noMatchIdx >= 0) {
String path = trimmed.substring(0, noMatchIdx).trim();
return " Cannot match element at: " + path;
}
// "path Missing: ..."
int missingIdx = trimmed.indexOf("Missing:");
if (missingIdx >= 0) {
String path = trimmed.substring(0, missingIdx).trim();
return " Missing: " + path + "\n " + trimmed.substring(missingIdx);
}
// "path Unexpected: ..."
int unexpectedIdx = trimmed.indexOf("Unexpected:");
if (unexpectedIdx >= 0) {
String path = trimmed.substring(0, unexpectedIdx).trim();
return " Unexpected: " + path + "\n " + trimmed.substring(unexpectedIdx);
}
🤖 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
`@src/test/java/com/github/_1c_syntax/bsl/test_utils/assertions/JsonAssert.java`
around lines 100 - 119, The message parsing in JsonAssert.formatMessage skips
valid matches when the target phrase starts at index 0 because the checks use
indexOf(...) > 0. Update the three branches handling "Could not find match for
element", "Missing:", and "Unexpected:" to accept zero-based matches as well, so
the path-prefixed formatting still applies when there is no leading path text.
Keep the fix localized to the JsonAssert formatting logic.

Comment on lines +90 to +114
private List<Module> flatten(Collection<?>... collections) {
List<Module> result = new ArrayList<>();
for (var collection : collections) {
if (collection.isEmpty()) {
continue;
}
var first = collection.iterator().next();
if (first instanceof ModuleOwner) {
// коллекция содержит ModuleOwner (Form, Command, и т.д.) — извлекаем модули
for (var item : collection) {
if (item instanceof ModuleOwner owner) {
result.addAll(owner.getAllModules());
}
}
} else if (first instanceof Module) {
// коллекция содержит Module — просто добавляем
for (var item : collection) {
if (item instanceof Module module) {
result.add(module);
}
}
}
}
return result;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Missing null-guard in flatten() — inconsistent with MDCollectionAssert.

MDCollectionAssert.flatten guards with collection == null || collection.isEmpty(), but this method only checks collection.isEmpty(). A null element in the varargs (e.g. an owner collection that wasn't initialized) will NPE here instead of being skipped.

🛡️ Proposed fix
     for (var collection : collections) {
-      if (collection.isEmpty()) {
+      if (collection == null || collection.isEmpty()) {
         continue;
       }
📝 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
private List<Module> flatten(Collection<?>... collections) {
List<Module> result = new ArrayList<>();
for (var collection : collections) {
if (collection.isEmpty()) {
continue;
}
var first = collection.iterator().next();
if (first instanceof ModuleOwner) {
// коллекция содержит ModuleOwner (Form, Command, и т.д.) — извлекаем модули
for (var item : collection) {
if (item instanceof ModuleOwner owner) {
result.addAll(owner.getAllModules());
}
}
} else if (first instanceof Module) {
// коллекция содержит Module — просто добавляем
for (var item : collection) {
if (item instanceof Module module) {
result.add(module);
}
}
}
}
return result;
}
private List<Module> flatten(Collection<?>... collections) {
List<Module> result = new ArrayList<>();
for (var collection : collections) {
if (collection == null || collection.isEmpty()) {
continue;
}
var first = collection.iterator().next();
if (first instanceof ModuleOwner) {
// коллекция содержит ModuleOwner (Form, Command, и т.д.) — извлекаем модули
for (var item : collection) {
if (item instanceof ModuleOwner owner) {
result.addAll(owner.getAllModules());
}
}
} else if (first instanceof Module) {
// коллекция содержит Module — просто добавляем
for (var item : collection) {
if (item instanceof Module module) {
result.add(module);
}
}
}
}
return result;
}
🤖 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
`@src/test/java/com/github/_1c_syntax/bsl/test_utils/assertions/ModuleCollectionAssert.java`
around lines 90 - 114, The flatten() helper in ModuleCollectionAssert should
handle null varargs entries the same way as MDCollectionAssert. Update the
collection loop to skip any null collection before calling isEmpty(), so a
missing owner collection does not trigger an NPE. Keep the fix localized to
flatten(Collection<?>...) and preserve the existing ModuleOwner and Module
handling logic.

Comment on lines +146 to +158
String msg = String.format(
"""
Ожидаемые и фактические модули различаются:
ожидаются (expected):
%s

отсутствуют в expected (extra):
%s
""",
formatCollection(missing),
formatCollection(extra)
);
failWithMessage(msg);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Diagnostic message labels are swapped — "expected"/"extra" don't match the values printed.

The message labels the first %s (which is missing) as "ожидаются (expected)" and the second %s (which is extra) as "отсутствуют в expected (extra)". Compare with MDCollectionAssert.check(), which correctly labels missing as "отсутствуют в expected (missing)" and extra as "отсутствуют в actual (extra)". As written, a test failure here will show confusing/incorrect diagnostics.

🐛 Proposed fix
     String msg = String.format(
       """
       Ожидаемые и фактические модули различаются:
-        ожидаются (expected):
+        отсутствуют в expected (missing):
         %s
       
-        отсутствуют в expected (extra):
+        отсутствуют в actual (extra):
         %s
       """,
       formatCollection(missing),
       formatCollection(extra)
     );
📝 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
String msg = String.format(
"""
Ожидаемые и фактические модули различаются:
ожидаются (expected):
%s
отсутствуют в expected (extra):
%s
""",
formatCollection(missing),
formatCollection(extra)
);
failWithMessage(msg);
String msg = String.format(
"""
Ожидаемые и фактические модули различаются:
отсутствуют в expected (missing):
%s
отсутствуют в actual (extra):
%s
""",
formatCollection(missing),
formatCollection(extra)
);
failWithMessage(msg);
🤖 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
`@src/test/java/com/github/_1c_syntax/bsl/test_utils/assertions/ModuleCollectionAssert.java`
around lines 146 - 158, The diagnostic labels in ModuleCollectionAssert’s
failure message are swapped, so the values from missing and extra are described
incorrectly. Update the String.format message in the comparison/fail path to
match the same wording used by MDCollectionAssert.check(), ensuring missing is
labeled as absent from expected and extra is labeled as absent from actual, and
keep the values passed to formatCollection aligned with those labels.

@sonarqubecloud

sonarqubecloud Bot commented Jul 6, 2026

Copy link
Copy Markdown

@theshadowco theshadowco merged commit 992ab1f into develop Jul 6, 2026
19 checks passed
@theshadowco theshadowco deleted the feature/refactTest branch July 6, 2026 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant