@@ -3801,22 +3801,31 @@ void putItems__whenCreatingBatchRepeatsStableId__thenCountedOnce() {
38013801 // (ClickHouse async inserts report 0 before commit). A stable id repeated inside that
38023802 // first batch is therefore counted twice, while ClickHouse collapses it to one row.
38033803 var duplicatedId = TestIdGeneratorFactory .create ().generateId ();
3804+ var distinctId = TestIdGeneratorFactory .create ().generateId ();
3805+
3806+ // The repeated id wins with its LAST submitted content: ClickHouse keeps one row per
3807+ // dataset_item_id and reads take the newest, so "second" survives and "first" does not.
3808+ var winningDuplicate = DatasetItem .builder ()
3809+ .id (duplicatedId )
3810+ .datasetItemId (duplicatedId )
3811+ .source (DatasetItemSource .SDK )
3812+ .data (Map .of ("value" , JsonUtils .getJsonNodeFromString ("\" second\" " )))
3813+ .build ();
3814+ var distinctItem = DatasetItem .builder ()
3815+ .id (distinctId )
3816+ .datasetItemId (distinctId )
3817+ .source (DatasetItemSource .SDK )
3818+ .data (Map .of ("value" , JsonUtils .getJsonNodeFromString ("\" third\" " )))
3819+ .build ();
3820+
38043821 var items = List .of (
38053822 DatasetItem .builder ()
38063823 .id (duplicatedId )
38073824 .source (DatasetItemSource .SDK )
38083825 .data (Map .of ("value" , JsonUtils .getJsonNodeFromString ("\" first\" " )))
38093826 .build (),
3810- DatasetItem .builder ()
3811- .id (duplicatedId )
3812- .source (DatasetItemSource .SDK )
3813- .data (Map .of ("value" , JsonUtils .getJsonNodeFromString ("\" second\" " )))
3814- .build (),
3815- DatasetItem .builder ()
3816- .id (TestIdGeneratorFactory .create ().generateId ())
3817- .source (DatasetItemSource .SDK )
3818- .data (Map .of ("value" , JsonUtils .getJsonNodeFromString ("\" third\" " )))
3819- .build ());
3827+ winningDuplicate ,
3828+ distinctItem );
38203829
38213830 datasetResourceClient .createDatasetItems (DatasetItemBatch .builder ()
38223831 .datasetId (datasetId )
@@ -3826,10 +3835,14 @@ void putItems__whenCreatingBatchRepeatsStableId__thenCountedOnce() {
38263835
38273836 var version = getLatestVersion (datasetId );
38283837
3829- // Two distinct ids went in, so the version holds two rows.
3838+ // Assert on the rows themselves, not just how many: a bug that kept the wrong revision
3839+ // of the duplicate, or dropped the distinct item and kept both duplicates, would leave
3840+ // the count at 2 and slip through a size-only check.
38303841 var stored = datasetResourceClient .getDatasetItems (
38313842 datasetId , 1 , 100 , version .versionHash (), API_KEY , TEST_WORKSPACE ).content ();
3832- assertThat (stored ).hasSize (2 );
3843+ assertThat (stored )
3844+ .usingRecursiveFieldByFieldElementComparatorIgnoringFields (IGNORED_FIELDS_DATA_ITEM )
3845+ .containsExactlyInAnyOrder (winningDuplicate , distinctItem );
38333846
38343847 // items_total must agree with what is actually stored.
38353848 assertThat (version .itemsTotal ()).isEqualTo (stored .size ());
@@ -3845,6 +3858,14 @@ void putItems__whenDuplicateStableIdSpansBatchesInGroup__thenCountedOnce() {
38453858 // one version. The first batch creates it, later batches append. A stable id present in
38463859 // both must contribute exactly one item to the total.
38473860 var sharedId = TestIdGeneratorFactory .create ().generateId ();
3861+ var otherId = TestIdGeneratorFactory .create ().generateId ();
3862+
3863+ var otherItem = DatasetItem .builder ()
3864+ .id (otherId )
3865+ .datasetItemId (otherId )
3866+ .source (DatasetItemSource .SDK )
3867+ .data (Map .of ("value" , JsonUtils .getJsonNodeFromString ("\" other\" " )))
3868+ .build ();
38483869
38493870 datasetResourceClient .createDatasetItems (DatasetItemBatch .builder ()
38503871 .datasetId (datasetId )
@@ -3855,28 +3876,31 @@ void putItems__whenDuplicateStableIdSpansBatchesInGroup__thenCountedOnce() {
38553876 .source (DatasetItemSource .SDK )
38563877 .data (Map .of ("value" , JsonUtils .getJsonNodeFromString ("\" first\" " )))
38573878 .build (),
3858- DatasetItem .builder ()
3859- .id (TestIdGeneratorFactory .create ().generateId ())
3860- .source (DatasetItemSource .SDK )
3861- .data (Map .of ("value" , JsonUtils .getJsonNodeFromString ("\" other\" " )))
3862- .build ()))
3879+ otherItem ))
38633880 .build (), TEST_WORKSPACE , API_KEY );
38643881
3882+ // The second batch re-sends the shared id with new content, so it is an update:
3883+ // one row, holding the later revision.
3884+ var updatedShared = DatasetItem .builder ()
3885+ .id (sharedId )
3886+ .datasetItemId (sharedId )
3887+ .source (DatasetItemSource .SDK )
3888+ .data (Map .of ("value" , JsonUtils .getJsonNodeFromString ("\" updated\" " )))
3889+ .build ();
3890+
38653891 datasetResourceClient .createDatasetItems (DatasetItemBatch .builder ()
38663892 .datasetId (datasetId )
38673893 .batchGroupId (batchGroupId )
3868- .items (List .of (DatasetItem .builder ()
3869- .id (sharedId )
3870- .source (DatasetItemSource .SDK )
3871- .data (Map .of ("value" , JsonUtils .getJsonNodeFromString ("\" updated\" " )))
3872- .build ()))
3894+ .items (List .of (updatedShared ))
38733895 .build (), TEST_WORKSPACE , API_KEY );
38743896
38753897 var version = getLatestVersion (datasetId );
38763898 var stored = datasetResourceClient .getDatasetItems (
38773899 datasetId , 1 , 100 , version .versionHash (), API_KEY , TEST_WORKSPACE ).content ();
38783900
3879- assertThat (stored ).hasSize (2 );
3901+ assertThat (stored )
3902+ .usingRecursiveFieldByFieldElementComparatorIgnoringFields (IGNORED_FIELDS_DATA_ITEM )
3903+ .containsExactlyInAnyOrder (updatedShared , otherItem );
38803904 assertThat (version .itemsTotal ()).isEqualTo (stored .size ());
38813905 }
38823906
0 commit comments