@langadventurellc/task-trellis-mcp 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -323
- package/dist/__tests__/e2e/autoPrune.e2e.test.d.ts +2 -0
- package/dist/__tests__/e2e/autoPrune.e2e.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/autoPrune.e2e.test.js +533 -0
- package/dist/__tests__/e2e/autoPrune.e2e.test.js.map +1 -0
- package/dist/__tests__/e2e/configuration/activation.e2e.test.js +6 -6
- package/dist/__tests__/e2e/configuration/activation.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/configuration/commandLineArgs.e2e.test.js +55 -6
- package/dist/__tests__/e2e/configuration/commandLineArgs.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/configuration/directorySetup.e2e.test.js +19 -19
- package/dist/__tests__/e2e/configuration/directorySetup.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/configuration/invalidConfig.e2e.test.js +9 -9
- package/dist/__tests__/e2e/configuration/invalidConfig.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/configuration/preActivation.e2e.test.js +15 -15
- package/dist/__tests__/e2e/configuration/preActivation.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/crud/createObject.e2e.test.js +34 -34
- package/dist/__tests__/e2e/crud/createObject.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/crud/deleteObject.e2e.test.js +19 -19
- package/dist/__tests__/e2e/crud/deleteObject.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/crud/fileValidation.e2e.test.js +32 -32
- package/dist/__tests__/e2e/crud/fileValidation.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/crud/getObject.e2e.test.js +143 -22
- package/dist/__tests__/e2e/crud/getObject.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/crud/listObjects.e2e.test.js +561 -42
- package/dist/__tests__/e2e/crud/listObjects.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/crud/updateObject.e2e.test.js +497 -25
- package/dist/__tests__/e2e/crud/updateObject.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/hierarchicalPrerequisites.e2e.test.d.ts +2 -0
- package/dist/__tests__/e2e/hierarchicalPrerequisites.e2e.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/hierarchicalPrerequisites.e2e.test.js +319 -0
- package/dist/__tests__/e2e/hierarchicalPrerequisites.e2e.test.js.map +1 -0
- package/dist/__tests__/e2e/infrastructure/client.e2e.test.js +4 -4
- package/dist/__tests__/e2e/infrastructure/client.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/infrastructure/server.e2e.test.js +6 -6
- package/dist/__tests__/e2e/infrastructure/server.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/utils/extractObjectIds.d.ts +1 -1
- package/dist/__tests__/e2e/utils/extractObjectIds.js +1 -1
- package/dist/__tests__/e2e/utils/mcpTestClient.d.ts.map +1 -1
- package/dist/__tests__/e2e/utils/mcpTestClient.js +3 -2
- package/dist/__tests__/e2e/utils/mcpTestClient.js.map +1 -1
- package/dist/__tests__/e2e/utils/parseListObjectsResponse.d.ts +1 -1
- package/dist/__tests__/e2e/utils/parseListObjectsResponse.js +1 -1
- package/dist/__tests__/e2e/utils/parseUpdateObjectResponse.d.ts +1 -1
- package/dist/__tests__/e2e/utils/parseUpdateObjectResponse.js +1 -1
- package/dist/__tests__/e2e/workflow/appendLog.e2e.test.js +4 -4
- package/dist/__tests__/e2e/workflow/appendLog.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/workflow/appendModifiedFiles.e2e.test.js +15 -15
- package/dist/__tests__/e2e/workflow/appendModifiedFiles.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/workflow/claimTask.e2e.test.js +44 -0
- package/dist/__tests__/e2e/workflow/claimTask.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/workflow/completeTask.e2e.test.js +4 -4
- package/dist/__tests__/e2e/workflow/completeTask.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/workflow/prerequisites.e2e.test.js +43 -43
- package/dist/__tests__/e2e/workflow/prerequisites.e2e.test.js.map +1 -1
- package/dist/__tests__/e2e/workflow/taskLifecycle.e2e.test.js +19 -19
- package/dist/__tests__/e2e/workflow/taskLifecycle.e2e.test.js.map +1 -1
- package/dist/__tests__/serverStartup.test.d.ts +2 -0
- package/dist/__tests__/serverStartup.test.d.ts.map +1 -0
- package/dist/__tests__/serverStartup.test.js +171 -0
- package/dist/__tests__/serverStartup.test.js.map +1 -0
- package/dist/configuration/ServerConfig.d.ts +2 -1
- package/dist/configuration/ServerConfig.d.ts.map +1 -1
- package/dist/repositories/Repository.d.ts +2 -1
- package/dist/repositories/Repository.d.ts.map +1 -1
- package/dist/repositories/local/LocalRepository.d.ts +2 -1
- package/dist/repositories/local/LocalRepository.d.ts.map +1 -1
- package/dist/repositories/local/LocalRepository.js +4 -0
- package/dist/repositories/local/LocalRepository.js.map +1 -1
- package/dist/repositories/local/__tests__/getChildrenOf.test.d.ts +2 -0
- package/dist/repositories/local/__tests__/getChildrenOf.test.d.ts.map +1 -0
- package/dist/repositories/local/__tests__/getChildrenOf.test.js +306 -0
- package/dist/repositories/local/__tests__/getChildrenOf.test.js.map +1 -0
- package/dist/repositories/local/__tests__/getObjects.test.js +309 -0
- package/dist/repositories/local/__tests__/getObjects.test.js.map +1 -1
- package/dist/repositories/local/deleteObjectById.d.ts.map +1 -1
- package/dist/repositories/local/deleteObjectById.js +2 -0
- package/dist/repositories/local/deleteObjectById.js.map +1 -1
- package/dist/repositories/local/getChildrenOf.d.ts +11 -0
- package/dist/repositories/local/getChildrenOf.d.ts.map +1 -0
- package/dist/repositories/local/getChildrenOf.js +73 -0
- package/dist/repositories/local/getChildrenOf.js.map +1 -0
- package/dist/repositories/local/getObjects.d.ts +1 -1
- package/dist/repositories/local/getObjects.d.ts.map +1 -1
- package/dist/repositories/local/getObjects.js +25 -6
- package/dist/repositories/local/getObjects.js.map +1 -1
- package/dist/server.js +39 -15
- package/dist/server.js.map +1 -1
- package/dist/services/TaskTrellisService.d.ts +4 -13
- package/dist/services/TaskTrellisService.d.ts.map +1 -1
- package/dist/services/local/LocalTaskTrellisService.d.ts +3 -9
- package/dist/services/local/LocalTaskTrellisService.d.ts.map +1 -1
- package/dist/services/local/LocalTaskTrellisService.js +4 -8
- package/dist/services/local/LocalTaskTrellisService.js.map +1 -1
- package/dist/services/local/__tests__/appendModifiedFiles.test.js +1 -0
- package/dist/services/local/__tests__/appendModifiedFiles.test.js.map +1 -1
- package/dist/services/local/__tests__/appendObjectLog.test.js +1 -0
- package/dist/services/local/__tests__/appendObjectLog.test.js.map +1 -1
- package/dist/services/local/__tests__/claimTask.test.js +127 -131
- package/dist/services/local/__tests__/claimTask.test.js.map +1 -1
- package/dist/services/local/__tests__/completeTask.test.js +30 -28
- package/dist/services/local/__tests__/completeTask.test.js.map +1 -1
- package/dist/services/local/__tests__/createObject.test.js +1 -0
- package/dist/services/local/__tests__/createObject.test.js.map +1 -1
- package/dist/services/local/__tests__/listObjects.test.js +135 -10
- package/dist/services/local/__tests__/listObjects.test.js.map +1 -1
- package/dist/services/local/__tests__/pruneClosed.test.js +446 -186
- package/dist/services/local/__tests__/pruneClosed.test.js.map +1 -1
- package/dist/services/local/__tests__/updateObject.test.js +234 -27
- package/dist/services/local/__tests__/updateObject.test.js.map +1 -1
- package/dist/services/local/claimTask.d.ts.map +1 -1
- package/dist/services/local/claimTask.js +25 -34
- package/dist/services/local/claimTask.js.map +1 -1
- package/dist/services/local/completeTask.d.ts +1 -1
- package/dist/services/local/completeTask.d.ts.map +1 -1
- package/dist/services/local/completeTask.js +4 -40
- package/dist/services/local/completeTask.js.map +1 -1
- package/dist/services/local/listObjects.d.ts +1 -1
- package/dist/services/local/listObjects.d.ts.map +1 -1
- package/dist/services/local/listObjects.js +10 -1
- package/dist/services/local/listObjects.js.map +1 -1
- package/dist/services/local/pruneClosed.d.ts.map +1 -1
- package/dist/services/local/pruneClosed.js +63 -6
- package/dist/services/local/pruneClosed.js.map +1 -1
- package/dist/services/local/updateObject.d.ts +2 -1
- package/dist/services/local/updateObject.d.ts.map +1 -1
- package/dist/services/local/updateObject.js +28 -1
- package/dist/services/local/updateObject.js.map +1 -1
- package/dist/tools/__tests__/appendModifiedFilesTool.test.js +1 -0
- package/dist/tools/__tests__/appendModifiedFilesTool.test.js.map +1 -1
- package/dist/tools/__tests__/appendObjectLogTool.test.js +1 -0
- package/dist/tools/__tests__/appendObjectLogTool.test.js.map +1 -1
- package/dist/tools/__tests__/claimTaskTool.test.js +1 -1
- package/dist/tools/__tests__/claimTaskTool.test.js.map +1 -1
- package/dist/tools/__tests__/completeTaskTool.test.js +23 -16
- package/dist/tools/__tests__/completeTaskTool.test.js.map +1 -1
- package/dist/tools/__tests__/createObjectTool.test.js +1 -0
- package/dist/tools/__tests__/createObjectTool.test.js.map +1 -1
- package/dist/tools/__tests__/deleteObjectTool.test.js +1 -0
- package/dist/tools/__tests__/deleteObjectTool.test.js.map +1 -1
- package/dist/tools/__tests__/getObjectTool.test.js +1 -0
- package/dist/tools/__tests__/getObjectTool.test.js.map +1 -1
- package/dist/tools/__tests__/listObjectsTool.test.js +160 -1
- package/dist/tools/__tests__/listObjectsTool.test.js.map +1 -1
- package/dist/tools/__tests__/updateObjectTool.test.js +39 -10
- package/dist/tools/__tests__/updateObjectTool.test.js.map +1 -1
- package/dist/tools/appendModifiedFilesTool.d.ts +2 -2
- package/dist/tools/appendModifiedFilesTool.js +4 -4
- package/dist/tools/appendModifiedFilesTool.js.map +1 -1
- package/dist/tools/appendObjectLogTool.d.ts +3 -3
- package/dist/tools/appendObjectLogTool.js +4 -4
- package/dist/tools/appendObjectLogTool.js.map +1 -1
- package/dist/tools/completeTaskTool.d.ts +1 -1
- package/dist/tools/completeTaskTool.d.ts.map +1 -1
- package/dist/tools/completeTaskTool.js +1 -1
- package/dist/tools/completeTaskTool.js.map +1 -1
- package/dist/tools/createObjectTool.d.ts +8 -8
- package/dist/tools/createObjectTool.js +13 -13
- package/dist/tools/createObjectTool.js.map +1 -1
- package/dist/tools/deleteObjectTool.d.ts +3 -3
- package/dist/tools/deleteObjectTool.js +12 -12
- package/dist/tools/deleteObjectTool.js.map +1 -1
- package/dist/tools/getObjectTool.d.ts +3 -3
- package/dist/tools/getObjectTool.d.ts.map +1 -1
- package/dist/tools/getObjectTool.js +12 -7
- package/dist/tools/getObjectTool.js.map +1 -1
- package/dist/tools/index.d.ts +7 -9
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +22 -28
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/listObjectsTool.d.ts +20 -11
- package/dist/tools/listObjectsTool.d.ts.map +1 -1
- package/dist/tools/listObjectsTool.js +112 -40
- package/dist/tools/listObjectsTool.js.map +1 -1
- package/dist/tools/updateObjectTool.d.ts +12 -7
- package/dist/tools/updateObjectTool.d.ts.map +1 -1
- package/dist/tools/updateObjectTool.js +20 -14
- package/dist/tools/updateObjectTool.js.map +1 -1
- package/dist/utils/__tests__/checkHierarchicalPrerequisitesComplete.test.d.ts +2 -0
- package/dist/utils/__tests__/checkHierarchicalPrerequisitesComplete.test.d.ts.map +1 -0
- package/dist/utils/__tests__/checkHierarchicalPrerequisitesComplete.test.js +206 -0
- package/dist/utils/__tests__/checkHierarchicalPrerequisitesComplete.test.js.map +1 -0
- package/dist/utils/__tests__/checkPrerequisitesComplete.test.js +5 -0
- package/dist/utils/__tests__/checkPrerequisitesComplete.test.js.map +1 -1
- package/dist/utils/__tests__/filterUnavailableObjects.test.js +51 -25
- package/dist/utils/__tests__/filterUnavailableObjects.test.js.map +1 -1
- package/dist/utils/__tests__/isRequiredForOtherObjects.test.js +5 -0
- package/dist/utils/__tests__/isRequiredForOtherObjects.test.js.map +1 -1
- package/dist/utils/__tests__/updateParentHierarchy.test.d.ts +2 -0
- package/dist/utils/__tests__/updateParentHierarchy.test.d.ts.map +1 -0
- package/dist/utils/__tests__/updateParentHierarchy.test.js +137 -0
- package/dist/utils/__tests__/updateParentHierarchy.test.js.map +1 -0
- package/dist/utils/autoCompleteParentHierarchy.d.ts +11 -0
- package/dist/utils/autoCompleteParentHierarchy.d.ts.map +1 -0
- package/dist/utils/autoCompleteParentHierarchy.js +49 -0
- package/dist/utils/autoCompleteParentHierarchy.js.map +1 -0
- package/dist/utils/checkHierarchicalPrerequisitesComplete.d.ts +14 -0
- package/dist/utils/checkHierarchicalPrerequisitesComplete.d.ts.map +1 -0
- package/dist/utils/checkHierarchicalPrerequisitesComplete.js +47 -0
- package/dist/utils/checkHierarchicalPrerequisitesComplete.js.map +1 -0
- package/dist/utils/filterUnavailableObjects.d.ts +6 -4
- package/dist/utils/filterUnavailableObjects.d.ts.map +1 -1
- package/dist/utils/filterUnavailableObjects.js +16 -22
- package/dist/utils/filterUnavailableObjects.js.map +1 -1
- package/dist/utils/index.d.ts +3 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +7 -3
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/updateParentHierarchy.d.ts +11 -0
- package/dist/utils/updateParentHierarchy.d.ts.map +1 -0
- package/dist/utils/updateParentHierarchy.js +40 -0
- package/dist/utils/updateParentHierarchy.js.map +1 -0
- package/dist/validation/__tests__/validateObjectCreation.test.js +1 -0
- package/dist/validation/__tests__/validateObjectCreation.test.js.map +1 -1
- package/dist/validation/__tests__/validateParentExists.test.js +1 -0
- package/dist/validation/__tests__/validateParentExists.test.js.map +1 -1
- package/dist/validation/__tests__/validateStatusTransition.test.js +1 -0
- package/dist/validation/__tests__/validateStatusTransition.test.js.map +1 -1
- package/package.json +1 -1
- package/dist/__tests__/e2e/crud/replaceObjectBodyRegex.e2e.test.d.ts +0 -2
- package/dist/__tests__/e2e/crud/replaceObjectBodyRegex.e2e.test.d.ts.map +0 -1
- package/dist/__tests__/e2e/crud/replaceObjectBodyRegex.e2e.test.js +0 -693
- package/dist/__tests__/e2e/crud/replaceObjectBodyRegex.e2e.test.js.map +0 -1
- package/dist/__tests__/e2e/workflow/pruneClosed.e2e.test.d.ts +0 -2
- package/dist/__tests__/e2e/workflow/pruneClosed.e2e.test.d.ts.map +0 -1
- package/dist/__tests__/e2e/workflow/pruneClosed.e2e.test.js +0 -352
- package/dist/__tests__/e2e/workflow/pruneClosed.e2e.test.js.map +0 -1
- package/dist/services/local/__tests__/replaceObjectBodyRegex.test.d.ts +0 -2
- package/dist/services/local/__tests__/replaceObjectBodyRegex.test.d.ts.map +0 -1
- package/dist/services/local/__tests__/replaceObjectBodyRegex.test.js +0 -283
- package/dist/services/local/__tests__/replaceObjectBodyRegex.test.js.map +0 -1
- package/dist/services/local/replaceObjectBodyRegex.d.ts +0 -8
- package/dist/services/local/replaceObjectBodyRegex.d.ts.map +0 -1
- package/dist/services/local/replaceObjectBodyRegex.js +0 -85
- package/dist/services/local/replaceObjectBodyRegex.js.map +0 -1
- package/dist/tools/__tests__/pruneClosedTool.test.d.ts +0 -2
- package/dist/tools/__tests__/pruneClosedTool.test.d.ts.map +0 -1
- package/dist/tools/__tests__/pruneClosedTool.test.js +0 -112
- package/dist/tools/__tests__/pruneClosedTool.test.js.map +0 -1
- package/dist/tools/__tests__/replaceObjectBodyRegexTool.test.d.ts +0 -2
- package/dist/tools/__tests__/replaceObjectBodyRegexTool.test.d.ts.map +0 -1
- package/dist/tools/__tests__/replaceObjectBodyRegexTool.test.js +0 -89
- package/dist/tools/__tests__/replaceObjectBodyRegexTool.test.js.map +0 -1
- package/dist/tools/pruneClosedTool.d.ts +0 -27
- package/dist/tools/pruneClosedTool.d.ts.map +0 -1
- package/dist/tools/pruneClosedTool.js +0 -57
- package/dist/tools/pruneClosedTool.js.map +0 -1
- package/dist/tools/replaceObjectBodyRegexTool.d.ts +0 -36
- package/dist/tools/replaceObjectBodyRegexTool.d.ts.map +0 -1
- package/dist/tools/replaceObjectBodyRegexTool.js +0 -67
- package/dist/tools/replaceObjectBodyRegexTool.js.map +0 -1
- package/dist/utils/ReplaceStringOptions.d.ts +0 -12
- package/dist/utils/ReplaceStringOptions.d.ts.map +0 -1
- package/dist/utils/ReplaceStringOptions.js +0 -3
- package/dist/utils/ReplaceStringOptions.js.map +0 -1
- package/dist/utils/__tests__/replaceStringWithRegex.test.d.ts +0 -2
- package/dist/utils/__tests__/replaceStringWithRegex.test.d.ts.map +0 -1
- package/dist/utils/__tests__/replaceStringWithRegex.test.js +0 -281
- package/dist/utils/__tests__/replaceStringWithRegex.test.js.map +0 -1
- package/dist/utils/replaceStringWithRegex.d.ts +0 -45
- package/dist/utils/replaceStringWithRegex.d.ts.map +0 -1
- package/dist/utils/replaceStringWithRegex.js +0 -91
- package/dist/utils/replaceStringWithRegex.js.map +0 -1
|
@@ -2,212 +2,472 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const models_1 = require("../../../models");
|
|
4
4
|
const pruneClosed_1 = require("../pruneClosed");
|
|
5
|
-
jest.mock("../../../models/isClosed", () => ({
|
|
6
|
-
isClosed: jest.fn(),
|
|
7
|
-
}));
|
|
8
|
-
const { isClosed } = require("../../../models/isClosed");
|
|
9
5
|
describe("pruneClosed", () => {
|
|
10
6
|
let mockRepository;
|
|
7
|
+
const mockDate = new Date("2025-01-15T12:00:00Z");
|
|
11
8
|
beforeEach(() => {
|
|
12
9
|
mockRepository = {
|
|
13
10
|
getObjectById: jest.fn(),
|
|
14
11
|
getObjects: jest.fn(),
|
|
15
12
|
saveObject: jest.fn(),
|
|
16
13
|
deleteObject: jest.fn(),
|
|
14
|
+
getChildrenOf: jest.fn(),
|
|
17
15
|
};
|
|
18
|
-
|
|
16
|
+
jest.clearAllMocks();
|
|
17
|
+
jest.useFakeTimers();
|
|
18
|
+
jest.setSystemTime(mockDate);
|
|
19
19
|
});
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
type: models_1.TrellisObjectType.TASK,
|
|
23
|
-
title: `Test ${id}`,
|
|
24
|
-
status,
|
|
25
|
-
priority: models_1.TrellisObjectPriority.MEDIUM,
|
|
26
|
-
parent: undefined,
|
|
27
|
-
prerequisites: [],
|
|
28
|
-
affectedFiles: new Map(),
|
|
29
|
-
log: [],
|
|
30
|
-
schema: "1.0",
|
|
31
|
-
childrenIds: [],
|
|
32
|
-
created: new Date().toISOString(),
|
|
33
|
-
updated: updatedTime,
|
|
34
|
-
body: "",
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
jest.useRealTimers();
|
|
35
22
|
});
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(2);
|
|
97
|
-
expect(mockRepository.deleteObject).toHaveBeenCalledWith("T-old-done", true);
|
|
98
|
-
expect(mockRepository.deleteObject).toHaveBeenCalledWith("T-old-wont-do", true);
|
|
99
|
-
expect(result.content[0].text).toContain("Pruned 2 closed objects");
|
|
100
|
-
expect(result.content[0].text).toContain("T-old-done");
|
|
101
|
-
expect(result.content[0].text).toContain("T-old-wont-do");
|
|
102
|
-
});
|
|
103
|
-
it("should handle deletion failures gracefully", async () => {
|
|
104
|
-
const oldClosedObject1 = createMockObject("T-old-closed-1", models_1.TrellisObjectStatus.DONE, new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString());
|
|
105
|
-
const oldClosedObject2 = createMockObject("T-old-closed-2", models_1.TrellisObjectStatus.DONE, new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString());
|
|
106
|
-
mockRepository.getObjects.mockResolvedValue([
|
|
107
|
-
oldClosedObject1,
|
|
108
|
-
oldClosedObject2,
|
|
109
|
-
]);
|
|
110
|
-
isClosed.mockReturnValue(true);
|
|
111
|
-
// First deletion fails, second succeeds
|
|
112
|
-
mockRepository.deleteObject
|
|
113
|
-
.mockRejectedValueOnce(new Error("Permission denied"))
|
|
114
|
-
.mockResolvedValueOnce(undefined);
|
|
115
|
-
// Mock console.error to check error logging
|
|
116
|
-
const consoleSpy = jest.spyOn(console, "error").mockImplementation();
|
|
117
|
-
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 60);
|
|
118
|
-
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(2);
|
|
119
|
-
expect(consoleSpy).toHaveBeenCalledWith("Failed to delete object T-old-closed-1:", expect.any(Error));
|
|
120
|
-
expect(result.content[0].text).toContain("Pruned 1 closed objects");
|
|
121
|
-
expect(result.content[0].text).toContain("T-old-closed-2");
|
|
122
|
-
expect(result.content[0].text).not.toContain("T-old-closed-1");
|
|
123
|
-
consoleSpy.mockRestore();
|
|
124
|
-
});
|
|
125
|
-
it("should handle repository getObjects error", async () => {
|
|
126
|
-
const errorMessage = "Repository connection failed";
|
|
127
|
-
mockRepository.getObjects.mockRejectedValue(new Error(errorMessage));
|
|
128
|
-
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 60);
|
|
129
|
-
expect(result.content[0].text).toContain("Error pruning closed objects");
|
|
130
|
-
expect(result.content[0].text).toContain(errorMessage);
|
|
131
|
-
});
|
|
132
|
-
it("should handle empty object list", async () => {
|
|
133
|
-
mockRepository.getObjects.mockResolvedValue([]);
|
|
134
|
-
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 60);
|
|
135
|
-
expect(mockRepository.deleteObject).not.toHaveBeenCalled();
|
|
136
|
-
expect(result.content[0].text).toContain("Pruned 0 closed objects");
|
|
23
|
+
const createMockObject = (id, status, daysAgo) => {
|
|
24
|
+
const updatedTime = new Date(mockDate.getTime() - daysAgo * 24 * 60 * 60 * 1000);
|
|
25
|
+
return {
|
|
26
|
+
id,
|
|
27
|
+
type: models_1.TrellisObjectType.TASK,
|
|
28
|
+
title: `Test ${id}`,
|
|
29
|
+
status,
|
|
30
|
+
priority: models_1.TrellisObjectPriority.MEDIUM,
|
|
31
|
+
prerequisites: [],
|
|
32
|
+
affectedFiles: new Map(),
|
|
33
|
+
log: [],
|
|
34
|
+
schema: "v1.0",
|
|
35
|
+
childrenIds: [],
|
|
36
|
+
body: "",
|
|
37
|
+
created: updatedTime.toISOString(),
|
|
38
|
+
updated: updatedTime.toISOString(),
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
describe("day-based age calculation", () => {
|
|
42
|
+
it("should delete objects older than 1 day", async () => {
|
|
43
|
+
const objects = [
|
|
44
|
+
createMockObject("T-old", models_1.TrellisObjectStatus.DONE, 2), // 2 days old
|
|
45
|
+
createMockObject("T-recent", models_1.TrellisObjectStatus.DONE, 0), // Today
|
|
46
|
+
];
|
|
47
|
+
mockRepository.getObjects.mockResolvedValue(objects);
|
|
48
|
+
mockRepository.getChildrenOf.mockResolvedValue([]);
|
|
49
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
50
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
51
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(1);
|
|
52
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("T-old", true);
|
|
53
|
+
expect(result.content[0].text).toContain("Pruned 1 closed objects older than 1 days");
|
|
54
|
+
expect(result.content[0].text).toContain("Deleted objects: T-old");
|
|
55
|
+
});
|
|
56
|
+
it("should delete objects older than 7 days", async () => {
|
|
57
|
+
const objects = [
|
|
58
|
+
createMockObject("T-very-old", models_1.TrellisObjectStatus.DONE, 10), // 10 days old
|
|
59
|
+
createMockObject("T-old", models_1.TrellisObjectStatus.DONE, 5), // 5 days old
|
|
60
|
+
createMockObject("T-recent", models_1.TrellisObjectStatus.DONE, 1), // 1 day old
|
|
61
|
+
];
|
|
62
|
+
mockRepository.getObjects.mockResolvedValue(objects);
|
|
63
|
+
mockRepository.getChildrenOf.mockResolvedValue([]);
|
|
64
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
65
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 7);
|
|
66
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(1);
|
|
67
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("T-very-old", true);
|
|
68
|
+
expect(result.content[0].text).toContain("Pruned 1 closed objects older than 7 days");
|
|
69
|
+
});
|
|
70
|
+
it("should delete objects older than 30 days", async () => {
|
|
71
|
+
const objects = [
|
|
72
|
+
createMockObject("T-ancient", models_1.TrellisObjectStatus.DONE, 45), // 45 days old
|
|
73
|
+
createMockObject("T-old", models_1.TrellisObjectStatus.DONE, 20), // 20 days old
|
|
74
|
+
];
|
|
75
|
+
mockRepository.getObjects.mockResolvedValue(objects);
|
|
76
|
+
mockRepository.getChildrenOf.mockResolvedValue([]);
|
|
77
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
78
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 30);
|
|
79
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(1);
|
|
80
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("T-ancient", true);
|
|
81
|
+
expect(result.content[0].text).toContain("Pruned 1 closed objects older than 30 days");
|
|
82
|
+
});
|
|
137
83
|
});
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
84
|
+
describe("objects with no children", () => {
|
|
85
|
+
it("should delete closed objects with no children (existing behavior)", async () => {
|
|
86
|
+
const objects = [
|
|
87
|
+
createMockObject("T-done", models_1.TrellisObjectStatus.DONE, 5),
|
|
88
|
+
createMockObject("T-wont-do", models_1.TrellisObjectStatus.WONT_DO, 3),
|
|
89
|
+
];
|
|
90
|
+
mockRepository.getObjects.mockResolvedValue(objects);
|
|
91
|
+
mockRepository.getChildrenOf.mockResolvedValue([]);
|
|
92
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
93
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
94
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(2);
|
|
95
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("T-done", true);
|
|
96
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("T-wont-do", true);
|
|
97
|
+
expect(result.content[0].text).toContain("Pruned 2 closed objects older than 1 days");
|
|
98
|
+
expect(result.content[0].text).toContain("Deleted objects: T-done, T-wont-do");
|
|
99
|
+
});
|
|
100
|
+
it("should not delete open objects", async () => {
|
|
101
|
+
const objects = [
|
|
102
|
+
createMockObject("T-open", models_1.TrellisObjectStatus.OPEN, 5),
|
|
103
|
+
createMockObject("T-in-progress", models_1.TrellisObjectStatus.IN_PROGRESS, 5),
|
|
104
|
+
createMockObject("T-done", models_1.TrellisObjectStatus.DONE, 5),
|
|
105
|
+
];
|
|
106
|
+
mockRepository.getObjects.mockResolvedValue(objects);
|
|
107
|
+
mockRepository.getChildrenOf.mockResolvedValue([]);
|
|
108
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
109
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
110
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(1);
|
|
111
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("T-done", true);
|
|
112
|
+
expect(result.content[0].text).toContain("Pruned 1 closed objects older than 1 days");
|
|
113
|
+
});
|
|
146
114
|
});
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
115
|
+
describe("hierarchical child validation", () => {
|
|
116
|
+
it("should skip closed parent with open children", async () => {
|
|
117
|
+
const parentObject = createMockObject("F-parent", models_1.TrellisObjectStatus.DONE, 5);
|
|
118
|
+
const openChild = createMockObject("T-open-child", models_1.TrellisObjectStatus.OPEN, 1);
|
|
119
|
+
mockRepository.getObjects.mockResolvedValue([parentObject]);
|
|
120
|
+
mockRepository.getChildrenOf.mockResolvedValue([openChild]);
|
|
121
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
122
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
123
|
+
expect(mockRepository.getChildrenOf).toHaveBeenCalledWith("F-parent", true);
|
|
124
|
+
expect(mockRepository.deleteObject).not.toHaveBeenCalled();
|
|
125
|
+
expect(result.content[0].text).toContain("Pruned 0 closed objects older than 1 days");
|
|
126
|
+
expect(result.content[0].text).toContain("Skipped 1 objects with open children: F-parent");
|
|
127
|
+
});
|
|
128
|
+
it("should delete closed parent with only closed children", async () => {
|
|
129
|
+
const parentObject = createMockObject("F-parent", models_1.TrellisObjectStatus.DONE, 5);
|
|
130
|
+
const closedChild1 = createMockObject("T-done-child", models_1.TrellisObjectStatus.DONE, 3);
|
|
131
|
+
const closedChild2 = createMockObject("T-wont-do-child", models_1.TrellisObjectStatus.WONT_DO, 2);
|
|
132
|
+
mockRepository.getObjects.mockResolvedValue([parentObject]);
|
|
133
|
+
mockRepository.getChildrenOf.mockResolvedValue([
|
|
134
|
+
closedChild1,
|
|
135
|
+
closedChild2,
|
|
136
|
+
]);
|
|
137
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
138
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
139
|
+
expect(mockRepository.getChildrenOf).toHaveBeenCalledWith("F-parent", true);
|
|
140
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(1);
|
|
141
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("F-parent", true);
|
|
142
|
+
expect(result.content[0].text).toContain("Pruned 1 closed objects older than 1 days");
|
|
143
|
+
expect(result.content[0].text).toContain("Deleted objects: F-parent");
|
|
144
|
+
});
|
|
145
|
+
it("should handle mixed scenarios with some parents having open children and others not", async () => {
|
|
146
|
+
const parentWithOpenChild = createMockObject("F-parent-1", models_1.TrellisObjectStatus.DONE, 5);
|
|
147
|
+
const parentWithClosedChildren = createMockObject("F-parent-2", models_1.TrellisObjectStatus.DONE, 5);
|
|
148
|
+
const parentWithNoChildren = createMockObject("T-orphan", models_1.TrellisObjectStatus.DONE, 5);
|
|
149
|
+
const openChild = createMockObject("T-open", models_1.TrellisObjectStatus.OPEN, 1);
|
|
150
|
+
const closedChild = createMockObject("T-closed", models_1.TrellisObjectStatus.DONE, 1);
|
|
151
|
+
mockRepository.getObjects.mockResolvedValue([
|
|
152
|
+
parentWithOpenChild,
|
|
153
|
+
parentWithClosedChildren,
|
|
154
|
+
parentWithNoChildren,
|
|
155
|
+
]);
|
|
156
|
+
mockRepository.getChildrenOf.mockImplementation((parentId) => {
|
|
157
|
+
if (parentId === "F-parent-1")
|
|
158
|
+
return Promise.resolve([openChild]);
|
|
159
|
+
if (parentId === "F-parent-2")
|
|
160
|
+
return Promise.resolve([closedChild]);
|
|
161
|
+
return Promise.resolve([]); // No children for T-orphan
|
|
162
|
+
});
|
|
163
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
164
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
165
|
+
// With recursive checking, we call getChildrenOf for each object and their descendants
|
|
166
|
+
expect(mockRepository.getChildrenOf).toHaveBeenCalledTimes(4);
|
|
167
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(2);
|
|
168
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("F-parent-2", true);
|
|
169
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("T-orphan", true);
|
|
170
|
+
expect(result.content[0].text).toContain("Pruned 2 closed objects older than 1 days");
|
|
171
|
+
expect(result.content[0].text).toContain("Deleted objects: F-parent-2, T-orphan");
|
|
172
|
+
expect(result.content[0].text).toContain("Skipped 1 objects with open children: F-parent-1");
|
|
173
|
+
});
|
|
155
174
|
});
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
175
|
+
describe("multi-level hierarchies", () => {
|
|
176
|
+
it("should handle grandparent → parent → child hierarchy correctly", async () => {
|
|
177
|
+
const grandparent = createMockObject("E-grandparent", models_1.TrellisObjectStatus.DONE, 5);
|
|
178
|
+
const parent = createMockObject("F-parent", models_1.TrellisObjectStatus.DONE, 4);
|
|
179
|
+
const openChild = createMockObject("T-open-child", models_1.TrellisObjectStatus.OPEN, 1);
|
|
180
|
+
mockRepository.getObjects.mockResolvedValue([grandparent, parent]);
|
|
181
|
+
mockRepository.getChildrenOf.mockImplementation((parentId) => {
|
|
182
|
+
if (parentId === "E-grandparent")
|
|
183
|
+
return Promise.resolve([parent]); // Parent is closed
|
|
184
|
+
if (parentId === "F-parent")
|
|
185
|
+
return Promise.resolve([openChild]); // Child is open
|
|
186
|
+
return Promise.resolve([]);
|
|
187
|
+
});
|
|
188
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
189
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
190
|
+
// Both grandparent and parent should be skipped because they have open descendants
|
|
191
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(0);
|
|
192
|
+
expect(result.content[0].text).toContain("Pruned 0 closed objects older than 1 days");
|
|
193
|
+
expect(result.content[0].text).toContain("Skipped 2 objects with open children: E-grandparent, F-parent");
|
|
194
|
+
});
|
|
162
195
|
});
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
for (const { age, expectedCutoff } of testCases) {
|
|
171
|
-
const objectBeforeCutoff = createMockObject(`T-before-${age}`, models_1.TrellisObjectStatus.DONE, new Date(expectedCutoff - 1000).toISOString());
|
|
172
|
-
const objectAfterCutoff = createMockObject(`T-after-${age}`, models_1.TrellisObjectStatus.DONE, new Date(expectedCutoff + 1000).toISOString());
|
|
196
|
+
describe("deep hierarchy recursive descendant checking", () => {
|
|
197
|
+
it("should protect ancestors when deep descendant is open (4+ levels)", async () => {
|
|
198
|
+
const project = createMockObject("P-project", models_1.TrellisObjectStatus.DONE, 10);
|
|
199
|
+
const epic = createMockObject("E-epic", models_1.TrellisObjectStatus.DONE, 8);
|
|
200
|
+
const feature = createMockObject("F-feature", models_1.TrellisObjectStatus.DONE, 6);
|
|
201
|
+
const task = createMockObject("T-task", models_1.TrellisObjectStatus.DONE, 4);
|
|
202
|
+
const subtask = createMockObject("ST-subtask", models_1.TrellisObjectStatus.OPEN, 2);
|
|
173
203
|
mockRepository.getObjects.mockResolvedValue([
|
|
174
|
-
|
|
175
|
-
|
|
204
|
+
project,
|
|
205
|
+
epic,
|
|
206
|
+
feature,
|
|
207
|
+
task,
|
|
176
208
|
]);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
209
|
+
mockRepository.getChildrenOf.mockImplementation((parentId) => {
|
|
210
|
+
if (parentId === "P-project")
|
|
211
|
+
return Promise.resolve([epic]);
|
|
212
|
+
if (parentId === "E-epic")
|
|
213
|
+
return Promise.resolve([feature]);
|
|
214
|
+
if (parentId === "F-feature")
|
|
215
|
+
return Promise.resolve([task]);
|
|
216
|
+
if (parentId === "T-task")
|
|
217
|
+
return Promise.resolve([subtask]);
|
|
218
|
+
return Promise.resolve([]);
|
|
219
|
+
});
|
|
220
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
221
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
222
|
+
// All ancestors should be protected due to the open subtask at the deepest level
|
|
223
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(0);
|
|
224
|
+
expect(result.content[0].text).toContain("Pruned 0 closed objects older than 1 days");
|
|
225
|
+
expect(result.content[0].text).toContain("Skipped 4 objects with open children: P-project, E-epic, F-feature, T-task");
|
|
226
|
+
});
|
|
227
|
+
it("should allow deletion when all deep descendants are closed", async () => {
|
|
228
|
+
const project = createMockObject("P-project", models_1.TrellisObjectStatus.DONE, 10);
|
|
229
|
+
const epic = createMockObject("E-epic", models_1.TrellisObjectStatus.DONE, 8);
|
|
230
|
+
const feature = createMockObject("F-feature", models_1.TrellisObjectStatus.DONE, 6);
|
|
231
|
+
const task1 = createMockObject("T-task1", models_1.TrellisObjectStatus.DONE, 4);
|
|
232
|
+
const task2 = createMockObject("T-task2", models_1.TrellisObjectStatus.WONT_DO, 3);
|
|
233
|
+
const subtask1 = createMockObject("ST-subtask1", models_1.TrellisObjectStatus.DONE, 2);
|
|
234
|
+
const subtask2 = createMockObject("ST-subtask2", models_1.TrellisObjectStatus.WONT_DO, 1);
|
|
235
|
+
mockRepository.getObjects.mockResolvedValue([
|
|
236
|
+
project,
|
|
237
|
+
epic,
|
|
238
|
+
feature,
|
|
239
|
+
task1,
|
|
240
|
+
task2,
|
|
241
|
+
]);
|
|
242
|
+
mockRepository.getChildrenOf.mockImplementation((parentId) => {
|
|
243
|
+
if (parentId === "P-project")
|
|
244
|
+
return Promise.resolve([epic]);
|
|
245
|
+
if (parentId === "E-epic")
|
|
246
|
+
return Promise.resolve([feature]);
|
|
247
|
+
if (parentId === "F-feature")
|
|
248
|
+
return Promise.resolve([task1, task2]);
|
|
249
|
+
if (parentId === "T-task1")
|
|
250
|
+
return Promise.resolve([subtask1]);
|
|
251
|
+
if (parentId === "T-task2")
|
|
252
|
+
return Promise.resolve([subtask2]);
|
|
253
|
+
return Promise.resolve([]);
|
|
254
|
+
});
|
|
255
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
256
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
257
|
+
// All objects should be deleted since all descendants are closed
|
|
258
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(5);
|
|
259
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("P-project", true);
|
|
260
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("E-epic", true);
|
|
261
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("F-feature", true);
|
|
262
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("T-task1", true);
|
|
263
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("T-task2", true);
|
|
264
|
+
expect(result.content[0].text).toContain("Pruned 5 closed objects older than 1 days");
|
|
265
|
+
});
|
|
266
|
+
it("should handle mixed branches - some with open descendants, some without", async () => {
|
|
267
|
+
const epic = createMockObject("E-epic", models_1.TrellisObjectStatus.DONE, 8);
|
|
268
|
+
const feature1 = createMockObject("F-feature1", models_1.TrellisObjectStatus.DONE, 6);
|
|
269
|
+
const feature2 = createMockObject("F-feature2", models_1.TrellisObjectStatus.DONE, 6);
|
|
270
|
+
const task1 = createMockObject("T-task1", models_1.TrellisObjectStatus.OPEN, 4); // Open task
|
|
271
|
+
const task2 = createMockObject("T-task2", models_1.TrellisObjectStatus.DONE, 4); // Closed task
|
|
272
|
+
const subtask = createMockObject("ST-subtask", models_1.TrellisObjectStatus.DONE, 2);
|
|
273
|
+
mockRepository.getObjects.mockResolvedValue([
|
|
274
|
+
epic,
|
|
275
|
+
feature1,
|
|
276
|
+
feature2,
|
|
277
|
+
task2,
|
|
278
|
+
]);
|
|
279
|
+
mockRepository.getChildrenOf.mockImplementation((parentId) => {
|
|
280
|
+
if (parentId === "E-epic")
|
|
281
|
+
return Promise.resolve([feature1, feature2]);
|
|
282
|
+
if (parentId === "F-feature1")
|
|
283
|
+
return Promise.resolve([task1]); // Branch with open task
|
|
284
|
+
if (parentId === "F-feature2")
|
|
285
|
+
return Promise.resolve([task2]); // Branch with closed task
|
|
286
|
+
if (parentId === "T-task2")
|
|
287
|
+
return Promise.resolve([subtask]);
|
|
288
|
+
return Promise.resolve([]);
|
|
289
|
+
});
|
|
290
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
291
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
292
|
+
// Epic and feature1 should be protected due to open task1
|
|
293
|
+
// Feature2 and task2 should be deleted (all descendants closed)
|
|
294
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(2);
|
|
295
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("F-feature2", true);
|
|
296
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("T-task2", true);
|
|
297
|
+
expect(result.content[0].text).toContain("Pruned 2 closed objects older than 1 days");
|
|
298
|
+
expect(result.content[0].text).toContain("Skipped 2 objects with open children: E-epic, F-feature1");
|
|
299
|
+
});
|
|
300
|
+
it("should handle circular reference protection gracefully", async () => {
|
|
301
|
+
const obj1 = createMockObject("T-obj1", models_1.TrellisObjectStatus.DONE, 5);
|
|
302
|
+
const obj2 = createMockObject("T-obj2", models_1.TrellisObjectStatus.DONE, 5);
|
|
303
|
+
mockRepository.getObjects.mockResolvedValue([obj1, obj2]);
|
|
304
|
+
// Create circular reference: obj1 -> obj2 -> obj1
|
|
305
|
+
mockRepository.getChildrenOf.mockImplementation((parentId) => {
|
|
306
|
+
if (parentId === "T-obj1")
|
|
307
|
+
return Promise.resolve([obj2]);
|
|
308
|
+
if (parentId === "T-obj2")
|
|
309
|
+
return Promise.resolve([obj1]);
|
|
310
|
+
return Promise.resolve([]);
|
|
311
|
+
});
|
|
312
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
313
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
314
|
+
// Should complete without infinite loop and delete both objects
|
|
315
|
+
// (since they're all closed, the circular reference shouldn't prevent deletion)
|
|
316
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(2);
|
|
317
|
+
expect(result.content[0].text).toContain("Pruned 2 closed objects older than 1 days");
|
|
318
|
+
});
|
|
319
|
+
it("should handle empty hierarchy levels gracefully", async () => {
|
|
320
|
+
const parent = createMockObject("F-parent", models_1.TrellisObjectStatus.DONE, 5);
|
|
321
|
+
const middleChild = createMockObject("T-middle", models_1.TrellisObjectStatus.DONE, 4);
|
|
322
|
+
const leafChild = createMockObject("ST-leaf", models_1.TrellisObjectStatus.OPEN, 2);
|
|
323
|
+
mockRepository.getObjects.mockResolvedValue([parent, middleChild]);
|
|
324
|
+
mockRepository.getChildrenOf.mockImplementation((parentId) => {
|
|
325
|
+
if (parentId === "F-parent")
|
|
326
|
+
return Promise.resolve([middleChild]);
|
|
327
|
+
if (parentId === "T-middle")
|
|
328
|
+
return Promise.resolve([leafChild]);
|
|
329
|
+
return Promise.resolve([]);
|
|
330
|
+
});
|
|
331
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
332
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
333
|
+
// Both parent and middle should be protected due to open leaf
|
|
334
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(0);
|
|
335
|
+
expect(result.content[0].text).toContain("Skipped 2 objects with open children: F-parent, T-middle");
|
|
336
|
+
});
|
|
337
|
+
it("should handle child query errors gracefully in recursive checking", async () => {
|
|
338
|
+
const parent = createMockObject("F-parent", models_1.TrellisObjectStatus.DONE, 5);
|
|
339
|
+
const failingChild = createMockObject("T-failing", models_1.TrellisObjectStatus.DONE, 4);
|
|
340
|
+
mockRepository.getObjects.mockResolvedValue([parent]);
|
|
341
|
+
mockRepository.getChildrenOf.mockImplementation((parentId) => {
|
|
342
|
+
if (parentId === "F-parent")
|
|
343
|
+
return Promise.resolve([failingChild]);
|
|
344
|
+
if (parentId === "T-failing")
|
|
345
|
+
return Promise.reject(new Error("Query failed"));
|
|
346
|
+
return Promise.resolve([]);
|
|
347
|
+
});
|
|
348
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
349
|
+
const consoleSpy = jest.spyOn(console, "error").mockImplementation();
|
|
350
|
+
const _result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
351
|
+
// Should handle error gracefully and proceed with deletion
|
|
352
|
+
// (when we can't check descendants, we err on the side of deletion)
|
|
353
|
+
expect(consoleSpy).toHaveBeenCalledWith("Error checking descendants for T-failing:", expect.any(Error));
|
|
180
354
|
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(1);
|
|
181
|
-
expect(mockRepository.deleteObject).toHaveBeenCalledWith(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
355
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("F-parent", true);
|
|
356
|
+
consoleSpy.mockRestore();
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
describe("error handling", () => {
|
|
360
|
+
it("should handle errors when child queries fail gracefully", async () => {
|
|
361
|
+
const objects = [
|
|
362
|
+
createMockObject("F-parent-1", models_1.TrellisObjectStatus.DONE, 5),
|
|
363
|
+
createMockObject("F-parent-2", models_1.TrellisObjectStatus.DONE, 5),
|
|
364
|
+
];
|
|
365
|
+
mockRepository.getObjects.mockResolvedValue(objects);
|
|
366
|
+
mockRepository.getChildrenOf.mockImplementation((parentId) => {
|
|
367
|
+
if (parentId === "F-parent-1") {
|
|
368
|
+
return Promise.reject(new Error("Failed to query children"));
|
|
369
|
+
}
|
|
370
|
+
return Promise.resolve([]); // No children for parent-2
|
|
371
|
+
});
|
|
372
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
373
|
+
const consoleSpy = jest.spyOn(console, "error").mockImplementation();
|
|
374
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
375
|
+
// Should continue processing other objects even if one child query fails
|
|
376
|
+
// With recursive checking, parent-1 gets deleted too (error in descendant check is handled gracefully)
|
|
377
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(2);
|
|
378
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("F-parent-1", true);
|
|
379
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledWith("F-parent-2", true);
|
|
380
|
+
expect(consoleSpy).toHaveBeenCalledWith("Error checking descendants for F-parent-1:", expect.any(Error));
|
|
381
|
+
expect(result.content[0].text).toContain("Pruned 2 closed objects older than 1 days");
|
|
382
|
+
consoleSpy.mockRestore();
|
|
383
|
+
});
|
|
384
|
+
it("should handle repository deletion errors gracefully", async () => {
|
|
385
|
+
const objects = [
|
|
386
|
+
createMockObject("T-fail", models_1.TrellisObjectStatus.DONE, 5),
|
|
387
|
+
createMockObject("T-success", models_1.TrellisObjectStatus.DONE, 5),
|
|
388
|
+
];
|
|
389
|
+
mockRepository.getObjects.mockResolvedValue(objects);
|
|
390
|
+
mockRepository.getChildrenOf.mockResolvedValue([]);
|
|
391
|
+
mockRepository.deleteObject.mockImplementation((id) => {
|
|
392
|
+
if (id === "T-fail") {
|
|
393
|
+
return Promise.reject(new Error("Deletion failed"));
|
|
394
|
+
}
|
|
395
|
+
return Promise.resolve();
|
|
396
|
+
});
|
|
397
|
+
const consoleSpy = jest.spyOn(console, "error").mockImplementation();
|
|
398
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
399
|
+
expect(mockRepository.deleteObject).toHaveBeenCalledTimes(2);
|
|
400
|
+
expect(consoleSpy).toHaveBeenCalledWith("Failed to delete object T-fail:", expect.any(Error));
|
|
401
|
+
expect(result.content[0].text).toContain("Pruned 1 closed objects older than 1 days");
|
|
402
|
+
expect(result.content[0].text).toContain("Deleted objects: T-success");
|
|
403
|
+
consoleSpy.mockRestore();
|
|
404
|
+
});
|
|
405
|
+
it("should handle repository getObjects error gracefully", async () => {
|
|
406
|
+
mockRepository.getObjects.mockRejectedValue(new Error("Failed to fetch objects"));
|
|
407
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
408
|
+
expect(result.content[0].text).toContain("Error pruning closed objects: Failed to fetch objects");
|
|
409
|
+
expect(mockRepository.deleteObject).not.toHaveBeenCalled();
|
|
410
|
+
});
|
|
186
411
|
});
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
412
|
+
describe("scope filtering", () => {
|
|
413
|
+
it("should include scope in message when provided", async () => {
|
|
414
|
+
const objects = [
|
|
415
|
+
createMockObject("T-scoped", models_1.TrellisObjectStatus.DONE, 5),
|
|
416
|
+
];
|
|
417
|
+
mockRepository.getObjects.mockResolvedValue(objects);
|
|
418
|
+
mockRepository.getChildrenOf.mockResolvedValue([]);
|
|
419
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
420
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1, "P-test-project");
|
|
421
|
+
expect(mockRepository.getObjects).toHaveBeenCalledWith(true, "P-test-project");
|
|
422
|
+
expect(result.content[0].text).toContain("Pruned 1 closed objects older than 1 days in scope P-test-project");
|
|
423
|
+
});
|
|
424
|
+
it("should handle empty results with scope", async () => {
|
|
425
|
+
mockRepository.getObjects.mockResolvedValue([]);
|
|
426
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1, "P-empty-project");
|
|
427
|
+
expect(result.content[0].text).toContain("Pruned 0 closed objects older than 1 days in scope P-empty-project");
|
|
428
|
+
});
|
|
201
429
|
});
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
430
|
+
describe("edge cases", () => {
|
|
431
|
+
it("should handle objects with no updated timestamp gracefully", async () => {
|
|
432
|
+
const objectWithoutTimestamp = {
|
|
433
|
+
...createMockObject("T-no-timestamp", models_1.TrellisObjectStatus.DONE, 0),
|
|
434
|
+
updated: "", // Invalid timestamp
|
|
435
|
+
};
|
|
436
|
+
mockRepository.getObjects.mockResolvedValue([objectWithoutTimestamp]);
|
|
437
|
+
mockRepository.getChildrenOf.mockResolvedValue([]);
|
|
438
|
+
mockRepository.deleteObject.mockResolvedValue();
|
|
439
|
+
const _result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
440
|
+
// Invalid date comparisons result in false, so object won't be considered old enough
|
|
441
|
+
expect(mockRepository.deleteObject).not.toHaveBeenCalled();
|
|
442
|
+
});
|
|
443
|
+
it("should return disabled message for zero age threshold", async () => {
|
|
444
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 0);
|
|
445
|
+
// Should return disabled message without calling any repository methods
|
|
446
|
+
expect(mockRepository.getObjects).not.toHaveBeenCalled();
|
|
447
|
+
expect(mockRepository.getChildrenOf).not.toHaveBeenCalled();
|
|
448
|
+
expect(mockRepository.deleteObject).not.toHaveBeenCalled();
|
|
449
|
+
expect(result.content[0].text).toBe("Auto-prune disabled (threshold: 0 days)");
|
|
450
|
+
});
|
|
451
|
+
it("should return disabled message for negative age threshold", async () => {
|
|
452
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, -1);
|
|
453
|
+
// Should return disabled message without calling any repository methods
|
|
454
|
+
expect(mockRepository.getObjects).not.toHaveBeenCalled();
|
|
455
|
+
expect(mockRepository.getChildrenOf).not.toHaveBeenCalled();
|
|
456
|
+
expect(mockRepository.deleteObject).not.toHaveBeenCalled();
|
|
457
|
+
expect(result.content[0].text).toBe("Auto-prune disabled (threshold: -1 days)");
|
|
458
|
+
});
|
|
459
|
+
it("should include scope in disabled message when provided", async () => {
|
|
460
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 0, "P-test-project");
|
|
461
|
+
expect(mockRepository.getObjects).not.toHaveBeenCalled();
|
|
462
|
+
expect(result.content[0].text).toBe("Auto-prune disabled (threshold: 0 days) in scope P-test-project");
|
|
463
|
+
});
|
|
464
|
+
it("should handle empty object list", async () => {
|
|
465
|
+
mockRepository.getObjects.mockResolvedValue([]);
|
|
466
|
+
const result = await (0, pruneClosed_1.pruneClosed)(mockRepository, 1);
|
|
467
|
+
expect(mockRepository.getChildrenOf).not.toHaveBeenCalled();
|
|
468
|
+
expect(mockRepository.deleteObject).not.toHaveBeenCalled();
|
|
469
|
+
expect(result.content[0].text).toContain("Pruned 0 closed objects older than 1 days");
|
|
470
|
+
});
|
|
211
471
|
});
|
|
212
472
|
});
|
|
213
473
|
//# sourceMappingURL=pruneClosed.test.js.map
|