@tenonhq/sincronia-core 0.0.78 → 0.0.80
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 +1 -1
- package/dist/FileUtils.js +19 -13
- package/dist/MultiScopeWatcher.js +212 -84
- package/dist/allScopesCommands.js +4 -2
- package/dist/appUtils.js +23 -12
- package/dist/commander.js +17 -0
- package/dist/commands.js +38 -1
- package/dist/config.js +4 -4
- package/dist/snClient.js +81 -1
- package/dist/tests/ensureUpdateSetWarnings.test.js +218 -0
- package/dist/tests/errorLogLevels.test.js +273 -0
- package/dist/tests/fileContextSkipReason.test.js +116 -0
- package/dist/tests/globalDebounce.test.js +307 -0
- package/dist/tests/multi-scope-watcher.test.js +109 -7
- package/dist/tests/pushFiles.test.js +162 -0
- package/dist/tests/rateLimitCoordination.test.js +271 -0
- package/dist/tests/retryOnHttpErr.test.js +154 -0
- package/dist/tests/scopeCaching.test.js +124 -0
- package/dist/tests/serializeUpdateSetConfig.test.js +325 -0
- package/dist/tests/taskClear.test.js +170 -0
- package/dist/tests/taskStaleness.test.js +220 -0
- package/dist/tests/validateTaskId.test.js +304 -0
- package/dist/tests/verifyUpdateSetSwitch.test.js +277 -0
- package/dist/updateSetCommands.js +59 -2
- package/package.json +1 -1
- package/skills/sinc-configure-pipeline.md +19 -19
- package/skills/sinc-debug-build.md +7 -7
- package/skills/sinc-setup-project.md +2 -2
- package/skills/sinc-troubleshoot-sync.md +5 -5
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* US-005: Serialize update set config writes under scope lock
|
|
4
|
+
*
|
|
5
|
+
* Tests that:
|
|
6
|
+
* - ensureUpdateSetForScope() calls are serialized via scopeLock
|
|
7
|
+
* - 3 concurrent scope writes all persist correctly
|
|
8
|
+
* - After writing, config is verified by re-reading
|
|
9
|
+
* - No mapping is lost due to concurrent writes
|
|
10
|
+
*/
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
// --- Mock setup ---
|
|
16
|
+
var mockFsStore = {};
|
|
17
|
+
jest.mock("fs", function () {
|
|
18
|
+
return {
|
|
19
|
+
existsSync: jest.fn(function (p) {
|
|
20
|
+
return p in mockFsStore;
|
|
21
|
+
}),
|
|
22
|
+
readFileSync: jest.fn(function (p) {
|
|
23
|
+
if (p in mockFsStore)
|
|
24
|
+
return mockFsStore[p];
|
|
25
|
+
throw new Error("ENOENT: " + p);
|
|
26
|
+
}),
|
|
27
|
+
writeFileSync: jest.fn(function (p, data) {
|
|
28
|
+
mockFsStore[p] = data;
|
|
29
|
+
}),
|
|
30
|
+
statSync: jest.fn(function () { return { mtimeMs: Date.now() }; }),
|
|
31
|
+
promises: {
|
|
32
|
+
readFile: jest.fn(function (p) {
|
|
33
|
+
if (p in mockFsStore)
|
|
34
|
+
return Promise.resolve(mockFsStore[p]);
|
|
35
|
+
return Promise.reject(new Error("ENOENT: " + p));
|
|
36
|
+
}),
|
|
37
|
+
writeFile: jest.fn(),
|
|
38
|
+
readdir: jest.fn(),
|
|
39
|
+
mkdir: jest.fn(),
|
|
40
|
+
access: jest.fn(),
|
|
41
|
+
stat: jest.fn(),
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
var mockSNClient = {
|
|
46
|
+
getScopeId: jest.fn(),
|
|
47
|
+
getUserSysId: jest.fn(),
|
|
48
|
+
getCurrentAppUserPrefSysId: jest.fn(),
|
|
49
|
+
updateCurrentAppUserPref: jest.fn(),
|
|
50
|
+
createCurrentAppUserPref: jest.fn(),
|
|
51
|
+
getCurrentUpdateSetUserPref: jest.fn(),
|
|
52
|
+
changeScope: jest.fn().mockResolvedValue(undefined),
|
|
53
|
+
client: {
|
|
54
|
+
get: jest.fn(),
|
|
55
|
+
},
|
|
56
|
+
changeUpdateSet: jest.fn().mockResolvedValue(undefined),
|
|
57
|
+
createUpdateSet: jest.fn(),
|
|
58
|
+
};
|
|
59
|
+
jest.mock("../snClient", function () {
|
|
60
|
+
return {
|
|
61
|
+
defaultClient: jest.fn(function () { return mockSNClient; }),
|
|
62
|
+
unwrapSNResponse: jest.fn(function (val) { return val; }),
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
jest.mock("chokidar", function () {
|
|
66
|
+
return {
|
|
67
|
+
watch: jest.fn(function () {
|
|
68
|
+
return {
|
|
69
|
+
on: jest.fn().mockReturnThis(),
|
|
70
|
+
close: jest.fn(),
|
|
71
|
+
};
|
|
72
|
+
}),
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
jest.mock("lodash", function () {
|
|
76
|
+
var actual = jest.requireActual("lodash");
|
|
77
|
+
return {
|
|
78
|
+
...actual,
|
|
79
|
+
debounce: jest.fn(function (fn) {
|
|
80
|
+
var wrapper = jest.fn();
|
|
81
|
+
wrapper.cancel = jest.fn();
|
|
82
|
+
wrapper.flush = jest.fn(function () { return fn(); });
|
|
83
|
+
return wrapper;
|
|
84
|
+
}),
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
jest.mock("../FileUtils", function () {
|
|
88
|
+
return {
|
|
89
|
+
getFileContextFromPath: jest.fn(),
|
|
90
|
+
getFileContextWithSkipReason: jest.fn(),
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
jest.mock("../appUtils", function () {
|
|
94
|
+
return {
|
|
95
|
+
groupAppFiles: jest.fn(),
|
|
96
|
+
pushFiles: jest.fn(),
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
jest.mock("../logMessages", function () {
|
|
100
|
+
return { logFilePush: jest.fn() };
|
|
101
|
+
});
|
|
102
|
+
jest.mock("../recentEdits", function () {
|
|
103
|
+
return { writeRecentEdit: jest.fn() };
|
|
104
|
+
});
|
|
105
|
+
jest.mock("../Logger", function () {
|
|
106
|
+
return {
|
|
107
|
+
logger: {
|
|
108
|
+
info: jest.fn(),
|
|
109
|
+
error: jest.fn(),
|
|
110
|
+
warn: jest.fn(),
|
|
111
|
+
debug: jest.fn(),
|
|
112
|
+
success: jest.fn(),
|
|
113
|
+
getLogLevel: jest.fn().mockReturnValue("info"),
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
jest.mock("../config", function () {
|
|
118
|
+
return {
|
|
119
|
+
loadConfigs: jest.fn().mockResolvedValue(undefined),
|
|
120
|
+
getConfig: jest.fn().mockReturnValue({
|
|
121
|
+
sourceDirectory: "src",
|
|
122
|
+
buildDirectory: "build",
|
|
123
|
+
rules: [],
|
|
124
|
+
includes: {},
|
|
125
|
+
excludes: {},
|
|
126
|
+
tableOptions: {},
|
|
127
|
+
refreshInterval: 30,
|
|
128
|
+
scopes: {
|
|
129
|
+
x_scope_a: { sourceDirectory: "src/x_scope_a" },
|
|
130
|
+
x_scope_b: { sourceDirectory: "src/x_scope_b" },
|
|
131
|
+
x_scope_c: { sourceDirectory: "src/x_scope_c" },
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
getRootDir: jest.fn().mockReturnValue("/project"),
|
|
135
|
+
updateManifest: jest.fn(),
|
|
136
|
+
getManifest: jest.fn(),
|
|
137
|
+
getSourcePath: jest.fn().mockReturnValue("/project/src"),
|
|
138
|
+
getScopeManifestPath: jest.fn(function (scope) { return "/project/sinc.manifest." + scope + ".json"; }),
|
|
139
|
+
getManifestPath: jest.fn().mockReturnValue("/project/sinc.manifest.json"),
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
jest.mock("axios", function () {
|
|
143
|
+
return {
|
|
144
|
+
default: {
|
|
145
|
+
create: jest.fn(function () {
|
|
146
|
+
return {
|
|
147
|
+
get: jest.fn().mockResolvedValue({ data: { result: null } }),
|
|
148
|
+
};
|
|
149
|
+
}),
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
// --- Imports ---
|
|
154
|
+
const fs_1 = __importDefault(require("fs"));
|
|
155
|
+
const Logger_1 = require("../Logger");
|
|
156
|
+
const MultiScopeWatcher_1 = require("../MultiScopeWatcher");
|
|
157
|
+
// --- Helpers ---
|
|
158
|
+
var CONFIG_PATH = "/fake/.sinc-update-sets.json";
|
|
159
|
+
var TASK_PATH = "/fake/.sinc-active-task.json";
|
|
160
|
+
function setActiveTask(task) {
|
|
161
|
+
var taskPath = require("path").resolve(process.cwd(), ".sinc-active-task.json");
|
|
162
|
+
mockFsStore[taskPath] = JSON.stringify(task);
|
|
163
|
+
}
|
|
164
|
+
function getConfigPath() {
|
|
165
|
+
return require("path").resolve(process.cwd(), ".sinc-update-sets.json");
|
|
166
|
+
}
|
|
167
|
+
function readPersistedConfig() {
|
|
168
|
+
var configPath = getConfigPath();
|
|
169
|
+
if (configPath in mockFsStore) {
|
|
170
|
+
return JSON.parse(mockFsStore[configPath]);
|
|
171
|
+
}
|
|
172
|
+
return {};
|
|
173
|
+
}
|
|
174
|
+
function clearFsStore() {
|
|
175
|
+
Object.keys(mockFsStore).forEach(function (k) { delete mockFsStore[k]; });
|
|
176
|
+
}
|
|
177
|
+
// --- Tests ---
|
|
178
|
+
describe("US-005: Serialize update set config writes under scope lock", function () {
|
|
179
|
+
beforeEach(function () {
|
|
180
|
+
jest.clearAllMocks();
|
|
181
|
+
clearFsStore();
|
|
182
|
+
(0, MultiScopeWatcher_1.stopMultiScopeWatching)();
|
|
183
|
+
// Default: scope switching succeeds
|
|
184
|
+
mockSNClient.getScopeId.mockResolvedValue([{ sys_id: "scope_sys_id" }]);
|
|
185
|
+
mockSNClient.getUserSysId.mockResolvedValue([{ sys_id: "user_sys_id" }]);
|
|
186
|
+
mockSNClient.getCurrentAppUserPrefSysId.mockResolvedValue([{ sys_id: "pref_sys_id" }]);
|
|
187
|
+
mockSNClient.updateCurrentAppUserPref.mockResolvedValue({});
|
|
188
|
+
mockSNClient.createCurrentAppUserPref.mockResolvedValue({});
|
|
189
|
+
mockSNClient.changeScope.mockResolvedValue(undefined);
|
|
190
|
+
mockSNClient.changeUpdateSet.mockResolvedValue(undefined);
|
|
191
|
+
// Mock: existing update set found for each scope
|
|
192
|
+
mockSNClient.client.get.mockResolvedValue({
|
|
193
|
+
data: {
|
|
194
|
+
result: [{ sys_id: "us_found_123", name: "CU-TASK123 Update Set", state: "in progress" }],
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
afterEach(function () {
|
|
199
|
+
(0, MultiScopeWatcher_1.stopMultiScopeWatching)();
|
|
200
|
+
});
|
|
201
|
+
it("3 concurrent ensureUpdateSetForScope calls all persist correctly", async function () {
|
|
202
|
+
// Set up active task so ensureUpdateSetForScope will create update sets
|
|
203
|
+
setActiveTask({
|
|
204
|
+
taskId: "TASK123",
|
|
205
|
+
taskName: "Test Task",
|
|
206
|
+
taskDescription: "desc",
|
|
207
|
+
updateSetName: "CU-TASK123 Update Set",
|
|
208
|
+
description: "desc",
|
|
209
|
+
taskUrl: "https://example.com",
|
|
210
|
+
scopes: {},
|
|
211
|
+
});
|
|
212
|
+
// Return different sys_ids for each scope's search
|
|
213
|
+
var callCount = 0;
|
|
214
|
+
mockSNClient.client.get.mockImplementation(function () {
|
|
215
|
+
callCount++;
|
|
216
|
+
return Promise.resolve({
|
|
217
|
+
data: {
|
|
218
|
+
result: [{ sys_id: "us_" + callCount, name: "CU-TASK123 Scope " + callCount, state: "in progress" }],
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
// Call all 3 concurrently — they should serialize under scopeLock
|
|
223
|
+
var watcher = MultiScopeWatcher_1.multiScopeWatcher;
|
|
224
|
+
var p1 = watcher.withScopeLock(function () { return watcher.ensureUpdateSetForScope("x_scope_a"); });
|
|
225
|
+
var p2 = watcher.withScopeLock(function () { return watcher.ensureUpdateSetForScope("x_scope_b"); });
|
|
226
|
+
var p3 = watcher.withScopeLock(function () { return watcher.ensureUpdateSetForScope("x_scope_c"); });
|
|
227
|
+
await Promise.all([p1, p2, p3]);
|
|
228
|
+
// All 3 scopes should be in the persisted config
|
|
229
|
+
var config = readPersistedConfig();
|
|
230
|
+
expect(config.x_scope_a).toBeDefined();
|
|
231
|
+
expect(config.x_scope_a.sys_id).toBe("us_1");
|
|
232
|
+
expect(config.x_scope_b).toBeDefined();
|
|
233
|
+
expect(config.x_scope_b.sys_id).toBe("us_2");
|
|
234
|
+
expect(config.x_scope_c).toBeDefined();
|
|
235
|
+
expect(config.x_scope_c.sys_id).toBe("us_3");
|
|
236
|
+
});
|
|
237
|
+
it("re-reads config before writing to preserve other scopes' mappings", async function () {
|
|
238
|
+
// Pre-populate config with scope_a already mapped
|
|
239
|
+
var configPath = getConfigPath();
|
|
240
|
+
mockFsStore[configPath] = JSON.stringify({
|
|
241
|
+
x_scope_a: { sys_id: "existing_a", name: "Existing A" },
|
|
242
|
+
});
|
|
243
|
+
setActiveTask({
|
|
244
|
+
taskId: "TASK456",
|
|
245
|
+
taskName: "Test",
|
|
246
|
+
taskDescription: "",
|
|
247
|
+
updateSetName: "CU-TASK456",
|
|
248
|
+
description: "",
|
|
249
|
+
taskUrl: "",
|
|
250
|
+
scopes: {},
|
|
251
|
+
});
|
|
252
|
+
mockSNClient.client.get.mockResolvedValue({
|
|
253
|
+
data: {
|
|
254
|
+
result: [{ sys_id: "us_new_b", name: "CU-TASK456 B", state: "in progress" }],
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
var watcher = MultiScopeWatcher_1.multiScopeWatcher;
|
|
258
|
+
await watcher.withScopeLock(function () { return watcher.ensureUpdateSetForScope("x_scope_b"); });
|
|
259
|
+
// Both scope_a (pre-existing) and scope_b (new) should be present
|
|
260
|
+
var config = readPersistedConfig();
|
|
261
|
+
expect(config.x_scope_a).toBeDefined();
|
|
262
|
+
expect(config.x_scope_a.sys_id).toBe("existing_a");
|
|
263
|
+
expect(config.x_scope_b).toBeDefined();
|
|
264
|
+
expect(config.x_scope_b.sys_id).toBe("us_new_b");
|
|
265
|
+
});
|
|
266
|
+
it("verifies write after saving config and logs debug confirmation", async function () {
|
|
267
|
+
setActiveTask({
|
|
268
|
+
taskId: "TASK789",
|
|
269
|
+
taskName: "Test",
|
|
270
|
+
taskDescription: "",
|
|
271
|
+
updateSetName: "CU-TASK789",
|
|
272
|
+
description: "",
|
|
273
|
+
taskUrl: "",
|
|
274
|
+
scopes: {},
|
|
275
|
+
});
|
|
276
|
+
mockSNClient.client.get.mockResolvedValue({
|
|
277
|
+
data: {
|
|
278
|
+
result: [{ sys_id: "us_verified", name: "CU-TASK789", state: "in progress" }],
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
var watcher = MultiScopeWatcher_1.multiScopeWatcher;
|
|
282
|
+
await watcher.withScopeLock(function () { return watcher.ensureUpdateSetForScope("x_scope_a"); });
|
|
283
|
+
// Should log a debug message confirming verification
|
|
284
|
+
expect(Logger_1.logger.debug).toHaveBeenCalledWith(expect.stringContaining("Update set config verified"));
|
|
285
|
+
});
|
|
286
|
+
it("throws and logs error when write verification fails", async function () {
|
|
287
|
+
setActiveTask({
|
|
288
|
+
taskId: "TASKFAIL",
|
|
289
|
+
taskName: "Test",
|
|
290
|
+
taskDescription: "",
|
|
291
|
+
updateSetName: "CU-TASKFAIL",
|
|
292
|
+
description: "",
|
|
293
|
+
taskUrl: "",
|
|
294
|
+
scopes: {},
|
|
295
|
+
});
|
|
296
|
+
mockSNClient.client.get.mockResolvedValue({
|
|
297
|
+
data: {
|
|
298
|
+
result: [{ sys_id: "us_fail", name: "CU-TASKFAIL", state: "in progress" }],
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
// Make writeFileSync succeed but readFileSync return empty on verification re-read
|
|
302
|
+
var writeCount = 0;
|
|
303
|
+
fs_1.default.writeFileSync.mockImplementation(function (p, data) {
|
|
304
|
+
// Write succeeds but data is "corrupted" — store empty config
|
|
305
|
+
mockFsStore[p] = JSON.stringify({});
|
|
306
|
+
});
|
|
307
|
+
var watcher = MultiScopeWatcher_1.multiScopeWatcher;
|
|
308
|
+
// The error is caught by ensureUpdateSetForScope's try/catch, which logs error + warn
|
|
309
|
+
await watcher.withScopeLock(function () { return watcher.ensureUpdateSetForScope("x_scope_a"); });
|
|
310
|
+
// Should log error about verification failure
|
|
311
|
+
expect(Logger_1.logger.error).toHaveBeenCalledWith(expect.stringContaining("write verification failed"));
|
|
312
|
+
});
|
|
313
|
+
it("skips API calls when scope already has a mapping", async function () {
|
|
314
|
+
// Pre-populate config with scope_a already mapped
|
|
315
|
+
var configPath = getConfigPath();
|
|
316
|
+
mockFsStore[configPath] = JSON.stringify({
|
|
317
|
+
x_scope_a: { sys_id: "existing_a", name: "Existing A" },
|
|
318
|
+
});
|
|
319
|
+
var watcher = MultiScopeWatcher_1.multiScopeWatcher;
|
|
320
|
+
await watcher.withScopeLock(function () { return watcher.ensureUpdateSetForScope("x_scope_a"); });
|
|
321
|
+
// Should not have called any ServiceNow APIs
|
|
322
|
+
expect(mockSNClient.changeScope).not.toHaveBeenCalled();
|
|
323
|
+
expect(mockSNClient.client.get).not.toHaveBeenCalled();
|
|
324
|
+
});
|
|
325
|
+
});
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const commands_1 = require("../commands");
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
// Mock logger
|
|
40
|
+
var logMessages = [];
|
|
41
|
+
jest.mock("../Logger", function () {
|
|
42
|
+
return {
|
|
43
|
+
logger: {
|
|
44
|
+
setLogLevel: jest.fn(),
|
|
45
|
+
success: jest.fn(function (msg) {
|
|
46
|
+
logMessages.push({ level: "success", msg: msg });
|
|
47
|
+
}),
|
|
48
|
+
info: jest.fn(function (msg) {
|
|
49
|
+
logMessages.push({ level: "info", msg: msg });
|
|
50
|
+
}),
|
|
51
|
+
error: jest.fn(function (msg) {
|
|
52
|
+
logMessages.push({ level: "error", msg: msg });
|
|
53
|
+
}),
|
|
54
|
+
warn: jest.fn(function (msg) {
|
|
55
|
+
logMessages.push({ level: "warn", msg: msg });
|
|
56
|
+
}),
|
|
57
|
+
debug: jest.fn(),
|
|
58
|
+
getInternalLogger: jest.fn(function () {
|
|
59
|
+
return { error: jest.fn() };
|
|
60
|
+
}),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
// Mock fs
|
|
65
|
+
jest.mock("fs");
|
|
66
|
+
var mockFs = fs;
|
|
67
|
+
// Mock other imports to prevent side effects
|
|
68
|
+
jest.mock("../config", function () {
|
|
69
|
+
return {
|
|
70
|
+
getConfig: jest.fn(function () { return {}; }),
|
|
71
|
+
loadConfigs: jest.fn(),
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
jest.mock("../appUtils", function () { return {}; });
|
|
75
|
+
jest.mock("../snClient", function () {
|
|
76
|
+
return {
|
|
77
|
+
defaultClient: jest.fn(),
|
|
78
|
+
unwrapSNResponse: jest.fn(),
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
jest.mock("../FileLogger", function () {
|
|
82
|
+
return { fileLogger: { debug: jest.fn() } };
|
|
83
|
+
});
|
|
84
|
+
jest.mock("../logMessages", function () {
|
|
85
|
+
return {
|
|
86
|
+
logPushResults: jest.fn(),
|
|
87
|
+
logBuildResults: jest.fn(),
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
jest.mock("../gitUtils", function () {
|
|
91
|
+
return { gitDiffToEncodedPaths: jest.fn() };
|
|
92
|
+
});
|
|
93
|
+
jest.mock("../FileUtils", function () {
|
|
94
|
+
return { encodedPathsToFilePaths: jest.fn() };
|
|
95
|
+
});
|
|
96
|
+
jest.mock("inquirer", function () {
|
|
97
|
+
return { prompt: jest.fn() };
|
|
98
|
+
});
|
|
99
|
+
describe("taskClearCommand", function () {
|
|
100
|
+
var taskPath;
|
|
101
|
+
var defaultArgs = { logLevel: "info" };
|
|
102
|
+
beforeEach(function () {
|
|
103
|
+
logMessages = [];
|
|
104
|
+
taskPath = path.resolve(process.cwd(), ".sinc-active-task.json");
|
|
105
|
+
jest.clearAllMocks();
|
|
106
|
+
});
|
|
107
|
+
it("should remove .sinc-active-task.json when it exists", async function () {
|
|
108
|
+
var taskData = JSON.stringify({
|
|
109
|
+
taskId: "abc123",
|
|
110
|
+
taskName: "My Test Task",
|
|
111
|
+
updateSetName: "CU-abc123",
|
|
112
|
+
});
|
|
113
|
+
mockFs.existsSync.mockImplementation(function (p) {
|
|
114
|
+
return p === taskPath;
|
|
115
|
+
});
|
|
116
|
+
mockFs.readFileSync.mockImplementation(function (p) {
|
|
117
|
+
if (p === taskPath)
|
|
118
|
+
return taskData;
|
|
119
|
+
throw new Error("file not found");
|
|
120
|
+
});
|
|
121
|
+
mockFs.unlinkSync.mockImplementation(function () { });
|
|
122
|
+
await (0, commands_1.taskClearCommand)(defaultArgs);
|
|
123
|
+
expect(mockFs.unlinkSync).toHaveBeenCalledWith(taskPath);
|
|
124
|
+
expect(logMessages.some(function (m) {
|
|
125
|
+
return m.level === "success" && m.msg.indexOf("My Test Task") !== -1;
|
|
126
|
+
})).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
it("should show informational message when no active task exists", async function () {
|
|
129
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
130
|
+
await (0, commands_1.taskClearCommand)(defaultArgs);
|
|
131
|
+
expect(mockFs.unlinkSync).not.toHaveBeenCalled();
|
|
132
|
+
expect(logMessages.some(function (m) {
|
|
133
|
+
return m.level === "info" && m.msg.indexOf("No active task") !== -1;
|
|
134
|
+
})).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
it("should still remove file even if JSON is invalid", async function () {
|
|
137
|
+
mockFs.existsSync.mockImplementation(function (p) {
|
|
138
|
+
return p === taskPath;
|
|
139
|
+
});
|
|
140
|
+
mockFs.readFileSync.mockImplementation(function () {
|
|
141
|
+
return "not valid json";
|
|
142
|
+
});
|
|
143
|
+
mockFs.unlinkSync.mockImplementation(function () { });
|
|
144
|
+
await (0, commands_1.taskClearCommand)(defaultArgs);
|
|
145
|
+
expect(mockFs.unlinkSync).toHaveBeenCalledWith(taskPath);
|
|
146
|
+
expect(logMessages.some(function (m) {
|
|
147
|
+
return m.level === "success" && m.msg.indexOf("removed") !== -1;
|
|
148
|
+
})).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
it("should use taskId as fallback when taskName is missing", async function () {
|
|
151
|
+
var taskData = JSON.stringify({
|
|
152
|
+
taskId: "def456",
|
|
153
|
+
updateSetName: "CU-def456",
|
|
154
|
+
});
|
|
155
|
+
mockFs.existsSync.mockImplementation(function (p) {
|
|
156
|
+
return p === taskPath;
|
|
157
|
+
});
|
|
158
|
+
mockFs.readFileSync.mockImplementation(function (p) {
|
|
159
|
+
if (p === taskPath)
|
|
160
|
+
return taskData;
|
|
161
|
+
throw new Error("file not found");
|
|
162
|
+
});
|
|
163
|
+
mockFs.unlinkSync.mockImplementation(function () { });
|
|
164
|
+
await (0, commands_1.taskClearCommand)(defaultArgs);
|
|
165
|
+
expect(mockFs.unlinkSync).toHaveBeenCalledWith(taskPath);
|
|
166
|
+
expect(logMessages.some(function (m) {
|
|
167
|
+
return m.level === "success" && m.msg.indexOf("def456") !== -1;
|
|
168
|
+
})).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
});
|