diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceRefactoringTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceRefactoringTests.cs index 68ab51364c..db1d9f46ba 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceRefactoringTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceRefactoringTests.cs @@ -547,7 +547,111 @@ internal sealed class ContentServiceRefactoringTests : UmbracoIntegrationTestWit #region Transaction Boundary Tests - // Tests 13-15 will be added in Task 6 + /// + /// Test 13: Verifies that multiple operations within an uncompleted scope all roll back together. + /// + [Test] + public void AmbientScope_NestedOperationsShareTransaction() + { + // Arrange + var content1 = ContentBuilder.CreateSimpleContent(ContentType, "RollbackTest1", -1); + var content2 = ContentBuilder.CreateSimpleContent(ContentType, "RollbackTest2", -1); + + // Act - Create scope, save content, but don't complete the scope + using (var scope = ScopeProvider.CreateScope()) + { + ContentService.Save(content1); + ContentService.Save(content2); + + // Verify content has IDs (was saved within transaction) + Assert.That(content1.Id, Is.GreaterThan(0), "Content1 should have an ID"); + Assert.That(content2.Id, Is.GreaterThan(0), "Content2 should have an ID"); + + // v1.2: Note - IDs are captured for debugging but cannot be used after rollback + // since they were assigned within the rolled-back transaction + var id1 = content1.Id; + var id2 = content2.Id; + + // DON'T call scope.Complete() - should roll back + } + + // Assert - Content should not exist after rollback + // We can't use the IDs because they were assigned in the rolled-back transaction + // Instead, search by name + var foundContent = ContentService.GetRootContent() + .Where(c => c.Name == "RollbackTest1" || c.Name == "RollbackTest2") + .ToList(); + + Assert.That(foundContent, Is.Empty, "Content should not exist after transaction rollback"); + } + + /// + /// Test 14: Verifies that multiple operations within a completed scope all commit together. + /// + [Test] + public void AmbientScope_CompletedScopeCommitsAllOperations() + { + // Arrange + var content1 = ContentBuilder.CreateSimpleContent(ContentType, "CommitTest1", -1); + var content2 = ContentBuilder.CreateSimpleContent(ContentType, "CommitTest2", -1); + int id1, id2; + + // Act - Create scope, save content, and complete the scope + using (var scope = ScopeProvider.CreateScope()) + { + ContentService.Save(content1); + ContentService.Save(content2); + + id1 = content1.Id; + id2 = content2.Id; + + scope.Complete(); // Commit transaction + } + + // Assert - Content should exist after commit + var retrieved1 = ContentService.GetById(id1); + var retrieved2 = ContentService.GetById(id2); + + Assert.That(retrieved1, Is.Not.Null, "Content1 should exist after commit"); + Assert.That(retrieved2, Is.Not.Null, "Content2 should exist after commit"); + Assert.That(retrieved1!.Name, Is.EqualTo("CommitTest1")); + Assert.That(retrieved2!.Name, Is.EqualTo("CommitTest2")); + } + + /// + /// Test 15: Verifies MoveToRecycleBin within an uncompleted scope rolls back completely. + /// + [Test] + public void AmbientScope_MoveToRecycleBinRollsBackCompletely() + { + // Arrange - Create and save content OUTSIDE the test scope so it persists + var content = ContentBuilder.CreateSimpleContent(ContentType, "MoveRollbackTest", -1); + ContentService.Save(content); + var contentId = content.Id; + + // Verify content exists and is not trashed + var beforeMove = ContentService.GetById(contentId); + Assert.That(beforeMove, Is.Not.Null, "Content should exist before test"); + Assert.That(beforeMove!.Trashed, Is.False, "Content should not be trashed before test"); + + // Act - Move to recycle bin within an uncompleted scope + using (var scope = ScopeProvider.CreateScope()) + { + ContentService.MoveToRecycleBin(content); + + // Verify it's trashed within the transaction + var duringMove = ContentService.GetById(contentId); + Assert.That(duringMove!.Trashed, Is.True, "Content should be trashed within transaction"); + + // DON'T call scope.Complete() - should roll back + } + + // Assert - Content should be back to original state after rollback + var afterRollback = ContentService.GetById(contentId); + Assert.That(afterRollback, Is.Not.Null, "Content should still exist after rollback"); + Assert.That(afterRollback!.Trashed, Is.False, "Content should not be trashed after rollback"); + Assert.That(afterRollback.ParentId, Is.EqualTo(-1), "Content should be at root level after rollback"); + } #endregion