diff --git a/PlotLine.Tests/Program.cs b/PlotLine.Tests/Program.cs index 3d4e6a5..e3665a8 100644 --- a/PlotLine.Tests/Program.cs +++ b/PlotLine.Tests/Program.cs @@ -176,6 +176,7 @@ static void ProjectHardDeleteClearsStoryIntelligenceDependenciesInTransaction() { var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine"); var repository = File.ReadAllText(Path.Combine(root, "Data/Repositories.cs")); + var cascadeMigration = File.ReadAllText(Path.Combine(root, "Sql/159_Phase25D_ManuscriptSourceHardDeleteCascades.sql")); Assert(repository.Contains("using var transaction = connection.BeginTransaction()", StringComparison.Ordinal), "Project hard delete should be wrapped by a repository transaction."); Assert(repository.Contains("ProjectHardDeleteStoryIntelligenceDependencyCleanupSql", StringComparison.Ordinal), "Project hard delete should run scoped Story Intelligence dependency cleanup before the stored procedure."); @@ -186,6 +187,10 @@ static void ProjectHardDeleteClearsStoryIntelligenceDependenciesInTransaction() Assert(repository.Contains("StoryMemoryProcessedSceneResults", StringComparison.Ordinal), "Hard delete cleanup should remove durable story memory scene-result rows."); Assert(repository.Contains("StoryMemoryImportPreferences", StringComparison.Ordinal), "Hard delete cleanup should remove durable story memory import rows."); Assert(repository.Contains("WHERE ProjectID = @ProjectID", StringComparison.Ordinal), "Hard delete cleanup must be scoped by project id."); + Assert(cascadeMigration.Contains("FK_SceneManuscriptSources_Scenes", StringComparison.Ordinal) + && cascadeMigration.Contains("REFERENCES dbo.Scenes(SceneID) ON DELETE CASCADE", StringComparison.Ordinal), "Scene manuscript source text should not block permanent scene deletion."); + Assert(cascadeMigration.Contains("FK_ManuscriptStructuralChapterSources_Chapters", StringComparison.Ordinal) + && cascadeMigration.Contains("REFERENCES dbo.Chapters(ChapterID) ON DELETE CASCADE", StringComparison.Ordinal), "Structural chapter source text should not block permanent chapter deletion."); } static void PermanentProjectDeleteKeepsArchiveAndNameSafetyGates() diff --git a/PlotLine/Sql/159_Phase25D_ManuscriptSourceHardDeleteCascades.sql b/PlotLine/Sql/159_Phase25D_ManuscriptSourceHardDeleteCascades.sql new file mode 100644 index 0000000..ac3d387 --- /dev/null +++ b/PlotLine/Sql/159_Phase25D_ManuscriptSourceHardDeleteCascades.sql @@ -0,0 +1,64 @@ +SET ANSI_NULLS ON; +GO +SET QUOTED_IDENTIFIER ON; +GO + +IF OBJECT_ID(N'dbo.SceneManuscriptSources', N'U') IS NOT NULL + AND EXISTS + ( + SELECT 1 + FROM sys.foreign_keys + WHERE name = N'FK_SceneManuscriptSources_Scenes' + AND parent_object_id = OBJECT_ID(N'dbo.SceneManuscriptSources') + AND delete_referential_action_desc <> N'CASCADE' + ) +BEGIN + ALTER TABLE dbo.SceneManuscriptSources DROP CONSTRAINT FK_SceneManuscriptSources_Scenes; + + ALTER TABLE dbo.SceneManuscriptSources WITH CHECK + ADD CONSTRAINT FK_SceneManuscriptSources_Scenes + FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID) ON DELETE CASCADE; +END; +GO + +IF OBJECT_ID(N'dbo.SceneManuscriptSources', N'U') IS NOT NULL + AND NOT EXISTS + ( + SELECT 1 + FROM sys.foreign_keys + WHERE name = N'FK_SceneManuscriptSources_Scenes' + AND parent_object_id = OBJECT_ID(N'dbo.SceneManuscriptSources') + AND delete_referential_action_desc = N'CASCADE' + ) + THROW 52010, 'Scene manuscript source hard-delete cascade was not applied.', 1; +GO + +IF OBJECT_ID(N'dbo.ManuscriptStructuralChapterSources', N'U') IS NOT NULL + AND NOT EXISTS + ( + SELECT 1 + FROM sys.foreign_keys + WHERE name = N'FK_ManuscriptStructuralChapterSources_Chapters' + AND parent_object_id = OBJECT_ID(N'dbo.ManuscriptStructuralChapterSources') + AND delete_referential_action_desc = N'CASCADE' + ) + THROW 52011, 'Structural chapter source hard-delete cascade was not applied.', 1; +GO + +IF OBJECT_ID(N'dbo.ManuscriptStructuralChapterSources', N'U') IS NOT NULL + AND EXISTS + ( + SELECT 1 + FROM sys.foreign_keys + WHERE name = N'FK_ManuscriptStructuralChapterSources_Chapters' + AND parent_object_id = OBJECT_ID(N'dbo.ManuscriptStructuralChapterSources') + AND delete_referential_action_desc <> N'CASCADE' + ) +BEGIN + ALTER TABLE dbo.ManuscriptStructuralChapterSources DROP CONSTRAINT FK_ManuscriptStructuralChapterSources_Chapters; + + ALTER TABLE dbo.ManuscriptStructuralChapterSources WITH CHECK + ADD CONSTRAINT FK_ManuscriptStructuralChapterSources_Chapters + FOREIGN KEY (ChapterID) REFERENCES dbo.Chapters(ChapterID) ON DELETE CASCADE; +END; +GO