@t1mmen/srtd 0.4.3 → 0.4.4
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/dist/__tests__/apply.test.js +2 -2
- package/dist/__tests__/build.test.js +4 -4
- package/dist/__tests__/build.test.js.map +1 -1
- package/dist/__tests__/vitest.setup.js +2 -1
- package/dist/__tests__/vitest.setup.js.map +1 -1
- package/dist/__tests__/watch.test.js +2 -0
- package/dist/__tests__/watch.test.js.map +1 -1
- package/dist/commands/_app.js +0 -8
- package/dist/commands/_app.js.map +1 -1
- package/dist/commands/apply.js +3 -5
- package/dist/commands/apply.js.map +1 -1
- package/dist/commands/build.js +5 -6
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/clear.js +10 -2
- package/dist/commands/clear.js.map +1 -1
- package/dist/commands/index.js +6 -12
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/register.d.ts +1 -1
- package/dist/commands/register.js +62 -16
- package/dist/commands/register.js.map +1 -1
- package/dist/commands/watch.js +25 -28
- package/dist/commands/watch.js.map +1 -1
- package/dist/components/Branding.js +1 -1
- package/dist/components/Branding.js.map +1 -1
- package/dist/components/ProcessingResults.js +26 -14
- package/dist/components/ProcessingResults.js.map +1 -1
- package/dist/components/Quittable.js +6 -4
- package/dist/components/Quittable.js.map +1 -1
- package/dist/components/StatBadge.d.ts +6 -0
- package/dist/components/StatBadge.js +14 -0
- package/dist/components/StatBadge.js.map +1 -0
- package/dist/components/TimeSince.js +9 -5
- package/dist/components/TimeSince.js.map +1 -1
- package/dist/hooks/useDatabaseConnection.js +2 -1
- package/dist/hooks/useDatabaseConnection.js.map +1 -1
- package/dist/hooks/useFullscreen.d.ts +1 -0
- package/dist/hooks/useFullscreen.js +18 -0
- package/dist/hooks/useFullscreen.js.map +1 -0
- package/dist/hooks/useTemplateProcessor.js +0 -6
- package/dist/hooks/useTemplateProcessor.js.map +1 -1
- package/dist/lib/templateManager.js +41 -26
- package/dist/lib/templateManager.js.map +1 -1
- package/dist/lib/templateManager.test.js +791 -488
- package/dist/lib/templateManager.test.js.map +1 -1
- package/dist/utils/applyMigrations.test.js +4 -1
- package/dist/utils/applyMigrations.test.js.map +1 -1
- package/dist/utils/databaseConnection.js +21 -19
- package/dist/utils/databaseConnection.js.map +1 -1
- package/dist/utils/databaseConnection.test.js +1 -4
- package/dist/utils/databaseConnection.test.js.map +1 -1
- package/dist/utils/logger.js +6 -5
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/registerTemplate.js +1 -2
- package/dist/utils/registerTemplate.js.map +1 -1
- package/package.json +2 -1
|
@@ -56,14 +56,15 @@ import { default as path, join, relative } from 'node:path';
|
|
|
56
56
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
57
57
|
import { TEST_FN_PREFIX } from '../__tests__/vitest.setup.js';
|
|
58
58
|
import { calculateMD5 } from '../utils/calculateMD5.js';
|
|
59
|
-
import { connect
|
|
59
|
+
import { connect } from '../utils/databaseConnection.js';
|
|
60
60
|
import { ensureDirectories } from '../utils/ensureDirectories.js';
|
|
61
61
|
import { TemplateManager } from './templateManager.js';
|
|
62
|
+
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
62
63
|
describe('TemplateManager', () => {
|
|
63
64
|
const testContext = {
|
|
64
65
|
testId: 0,
|
|
65
|
-
testDir:
|
|
66
|
-
testFunctionName:
|
|
66
|
+
testDir: tmpdir(),
|
|
67
|
+
testFunctionName: TEST_FN_PREFIX,
|
|
67
68
|
templateCounter: 0,
|
|
68
69
|
};
|
|
69
70
|
beforeEach(async () => {
|
|
@@ -100,7 +101,6 @@ describe('TemplateManager', () => {
|
|
|
100
101
|
client.release();
|
|
101
102
|
}
|
|
102
103
|
await fs.rm(testContext.testDir, { recursive: true, force: true });
|
|
103
|
-
disconnect();
|
|
104
104
|
});
|
|
105
105
|
// Helper to generate unique template names
|
|
106
106
|
const getNextTemplateName = (prefix = 'template') => {
|
|
@@ -128,167 +128,289 @@ describe('TemplateManager', () => {
|
|
|
128
128
|
return createTemplate(name, content, dir);
|
|
129
129
|
};
|
|
130
130
|
it('should create migration file when template changes', async () => {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
132
|
+
try {
|
|
133
|
+
await createTemplateWithFunc('basic', '_file_change');
|
|
134
|
+
const manager = __addDisposableResource(env_1, await TemplateManager.create(testContext.testDir), false);
|
|
135
|
+
await manager.processTemplates({ generateFiles: true });
|
|
136
|
+
const migrations = await fs.readdir(join(testContext.testDir, 'test-migrations'));
|
|
137
|
+
expect(migrations.length).toBe(1);
|
|
138
|
+
}
|
|
139
|
+
catch (e_1) {
|
|
140
|
+
env_1.error = e_1;
|
|
141
|
+
env_1.hasError = true;
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
__disposeResources(env_1);
|
|
145
|
+
}
|
|
136
146
|
});
|
|
137
147
|
it('should not allow building WIP templates', async () => {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
148
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
149
|
+
try {
|
|
150
|
+
await createTemplateWithFunc('file.wip', '_wip_wont_build');
|
|
151
|
+
const manager = __addDisposableResource(env_2, await TemplateManager.create(testContext.testDir), false);
|
|
152
|
+
await manager.processTemplates({ generateFiles: true });
|
|
153
|
+
const migrations = await fs.readdir(join(testContext.testDir, 'test-migrations'));
|
|
154
|
+
expect(migrations.filter(m => m.includes(`wip`))).toHaveLength(0);
|
|
155
|
+
}
|
|
156
|
+
catch (e_2) {
|
|
157
|
+
env_2.error = e_2;
|
|
158
|
+
env_2.hasError = true;
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
__disposeResources(env_2);
|
|
162
|
+
}
|
|
143
163
|
});
|
|
144
164
|
it('should maintain separate build and local logs', async () => {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
165
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
166
|
+
try {
|
|
167
|
+
const templatePath = join(testContext.testDir, 'test-templates', `template_${testContext.testId}_1.sql`);
|
|
168
|
+
const templateContent = `CREATE FUNCTION ${testContext.testFunctionName}() RETURNS void AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`;
|
|
169
|
+
await fs.writeFile(templatePath, templateContent);
|
|
170
|
+
const manager = __addDisposableResource(env_3, await TemplateManager.create(testContext.testDir), false);
|
|
171
|
+
// Build writes to build log
|
|
172
|
+
await manager.processTemplates({ generateFiles: true });
|
|
173
|
+
const buildLog = JSON.parse(await fs.readFile(join(testContext.testDir, '.buildlog-test.json'), 'utf-8'));
|
|
174
|
+
const relPath = relative(testContext.testDir, templatePath);
|
|
175
|
+
expect(buildLog.templates[relPath].lastBuildHash).toBeDefined();
|
|
176
|
+
// Apply writes to local log
|
|
177
|
+
await manager.processTemplates({ apply: true });
|
|
178
|
+
const localLog = JSON.parse(await fs.readFile(join(testContext.testDir, '.buildlog-test.local.json'), 'utf-8'));
|
|
179
|
+
expect(localLog.templates[relPath].lastAppliedHash).toBeDefined();
|
|
180
|
+
}
|
|
181
|
+
catch (e_3) {
|
|
182
|
+
env_3.error = e_3;
|
|
183
|
+
env_3.hasError = true;
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
__disposeResources(env_3);
|
|
187
|
+
}
|
|
158
188
|
});
|
|
159
189
|
it('should track template state correctly', async () => {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
190
|
+
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
191
|
+
try {
|
|
192
|
+
const templatePath = join(testContext.testDir, 'test-templates', `template_${testContext.testId}_1.sql`);
|
|
193
|
+
const templateContent = `CREATE FUNCTION ${testContext.testFunctionName}() RETURNS void AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`;
|
|
194
|
+
await fs.writeFile(templatePath, templateContent);
|
|
195
|
+
const manager = __addDisposableResource(env_4, await TemplateManager.create(testContext.testDir), false);
|
|
196
|
+
// Initially no state
|
|
197
|
+
let status = await manager.getTemplateStatus(templatePath);
|
|
198
|
+
expect(status.buildState.lastBuildHash).toBeUndefined();
|
|
199
|
+
expect(status.buildState.lastAppliedHash).toBeUndefined();
|
|
200
|
+
// After build
|
|
201
|
+
await manager.processTemplates({ generateFiles: true });
|
|
202
|
+
status = await manager.getTemplateStatus(templatePath);
|
|
203
|
+
expect(status.buildState.lastBuildHash).toBeDefined();
|
|
204
|
+
expect(status.buildState.lastBuildDate).toBeDefined();
|
|
205
|
+
// After apply
|
|
206
|
+
await manager.processTemplates({ apply: true });
|
|
207
|
+
status = await manager.getTemplateStatus(templatePath);
|
|
208
|
+
expect(status.buildState.lastAppliedHash).toBeDefined();
|
|
209
|
+
expect(status.buildState.lastAppliedDate).toBeDefined();
|
|
210
|
+
}
|
|
211
|
+
catch (e_4) {
|
|
212
|
+
env_4.error = e_4;
|
|
213
|
+
env_4.hasError = true;
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
__disposeResources(env_4);
|
|
217
|
+
}
|
|
178
218
|
});
|
|
179
219
|
it('should handle rapid template changes', async () => {
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
changes
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
220
|
+
const env_5 = { stack: [], error: void 0, hasError: false };
|
|
221
|
+
try {
|
|
222
|
+
const templatePath = join(testContext.testDir, 'test-templates', `template_${testContext.testId}_1.sql`);
|
|
223
|
+
const baseContent = `CREATE OR REPLACE FUNCTION ${testContext.testFunctionName}() RETURNS void AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`;
|
|
224
|
+
await fs.writeFile(templatePath, baseContent);
|
|
225
|
+
const manager = __addDisposableResource(env_5, await TemplateManager.create(testContext.testDir), false);
|
|
226
|
+
const changes = [];
|
|
227
|
+
manager.on('templateChanged', async (template) => {
|
|
228
|
+
changes.push(template.currentHash);
|
|
229
|
+
});
|
|
230
|
+
const watcher = await manager.watch();
|
|
231
|
+
await wait(100);
|
|
232
|
+
// Make rapid changes
|
|
233
|
+
for (let i = 0; i < 5; i++) {
|
|
234
|
+
await fs.writeFile(templatePath, `${baseContent}\n-- Change ${i}`);
|
|
235
|
+
await wait(100);
|
|
236
|
+
}
|
|
237
|
+
await wait(500);
|
|
238
|
+
watcher.close();
|
|
239
|
+
expect(changes.length).toBeGreaterThanOrEqual(1);
|
|
240
|
+
expect(new Set(changes).size).toBe(changes.length); // All changes should be unique
|
|
241
|
+
}
|
|
242
|
+
catch (e_5) {
|
|
243
|
+
env_5.error = e_5;
|
|
244
|
+
env_5.hasError = true;
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
__disposeResources(env_5);
|
|
248
|
+
}
|
|
199
249
|
}, 10000);
|
|
200
250
|
it('should apply WIP templates directly to database', async () => {
|
|
201
|
-
const
|
|
202
|
-
const templateContent = `CREATE FUNCTION ${testContext.testFunctionName}() RETURNS void AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`;
|
|
203
|
-
await fs.writeFile(templatePath, templateContent);
|
|
204
|
-
const manager = await TemplateManager.create(testContext.testDir);
|
|
205
|
-
const result = await manager.processTemplates({ apply: true });
|
|
206
|
-
expect(result.errors).toHaveLength(0);
|
|
207
|
-
const client = await connect();
|
|
251
|
+
const env_6 = { stack: [], error: void 0, hasError: false };
|
|
208
252
|
try {
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
253
|
+
const templatePath = join(testContext.testDir, 'test-templates', `template_${testContext.testId}_1.wip.sql`);
|
|
254
|
+
const templateContent = `CREATE FUNCTION ${testContext.testFunctionName}() RETURNS void AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`;
|
|
255
|
+
await fs.writeFile(templatePath, templateContent);
|
|
256
|
+
const manager = __addDisposableResource(env_6, await TemplateManager.create(testContext.testDir), false);
|
|
257
|
+
const result = await manager.processTemplates({ apply: true });
|
|
258
|
+
expect(result.errors).toHaveLength(0);
|
|
259
|
+
const client = await connect();
|
|
260
|
+
try {
|
|
261
|
+
const res = await client.query(`SELECT COUNT(*) FROM pg_proc WHERE proname = $1`, [
|
|
262
|
+
testContext.testFunctionName,
|
|
263
|
+
]);
|
|
264
|
+
expect(Number.parseInt(res.rows[0].count)).toBe(1);
|
|
265
|
+
}
|
|
266
|
+
finally {
|
|
267
|
+
client.release();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
catch (e_6) {
|
|
271
|
+
env_6.error = e_6;
|
|
272
|
+
env_6.hasError = true;
|
|
213
273
|
}
|
|
214
274
|
finally {
|
|
215
|
-
|
|
275
|
+
__disposeResources(env_6);
|
|
216
276
|
}
|
|
217
277
|
});
|
|
218
278
|
it('should handle sequential template operations', async () => {
|
|
219
|
-
|
|
220
|
-
const manager = await TemplateManager.create(testContext.testDir);
|
|
221
|
-
const client = await connect();
|
|
279
|
+
const env_7 = { stack: [], error: void 0, hasError: false };
|
|
222
280
|
try {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
281
|
+
const tmpls = await Promise.all([...Array(5)].map((_, i) => createTemplateWithFunc(`sequencetest_${i}`, `_sequence_test_${i}`)));
|
|
282
|
+
expect(tmpls).toHaveLength(5);
|
|
283
|
+
const manager = __addDisposableResource(env_7, await TemplateManager.create(testContext.testDir), false);
|
|
284
|
+
const client = await connect();
|
|
285
|
+
await wait(100);
|
|
286
|
+
try {
|
|
287
|
+
// Start transaction
|
|
288
|
+
await client.query('BEGIN');
|
|
289
|
+
const result = await manager.processTemplates({ apply: true, force: true });
|
|
290
|
+
// Add retry logic for verification
|
|
291
|
+
const verifyFunctions = async (retries = 3, delay = 200) => {
|
|
292
|
+
try {
|
|
293
|
+
const allFunctions = await client.query(`SELECT proname FROM pg_proc WHERE proname LIKE $1`, [`${testContext.testFunctionName}_sequence_test_%`]);
|
|
294
|
+
expect(allFunctions.rows).toHaveLength(5);
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
console.log('Flakey test failed verifying functions:', error, 'retries', retries);
|
|
298
|
+
if (retries === 0)
|
|
299
|
+
throw error;
|
|
300
|
+
await wait(delay);
|
|
301
|
+
await verifyFunctions(retries - 1, delay * 2);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
await verifyFunctions();
|
|
305
|
+
await client.query('COMMIT');
|
|
306
|
+
expect(result.errors).toHaveLength(0);
|
|
307
|
+
expect(result.applied).toHaveLength(5);
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
await client.query('ROLLBACK');
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
finally {
|
|
314
|
+
client.release();
|
|
315
|
+
}
|
|
232
316
|
}
|
|
233
|
-
catch (
|
|
234
|
-
|
|
235
|
-
|
|
317
|
+
catch (e_7) {
|
|
318
|
+
env_7.error = e_7;
|
|
319
|
+
env_7.hasError = true;
|
|
236
320
|
}
|
|
237
321
|
finally {
|
|
238
|
-
|
|
239
|
-
await client.query('ROLLBACK');
|
|
240
|
-
client.release();
|
|
322
|
+
__disposeResources(env_7);
|
|
241
323
|
}
|
|
242
324
|
});
|
|
243
325
|
it('should generate unique timestamps for multiple templates', async () => {
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
326
|
+
const env_8 = { stack: [], error: void 0, hasError: false };
|
|
327
|
+
try {
|
|
328
|
+
const templates = await Promise.all([...Array(10)].map((_, i) => createTemplateWithFunc(`timestamptest_${i}`, `_unique_timestamps_${i}`)));
|
|
329
|
+
const manager = __addDisposableResource(env_8, await TemplateManager.create(testContext.testDir), false);
|
|
330
|
+
await manager.processTemplates({ generateFiles: true });
|
|
331
|
+
const migrations = await fs.readdir(join(testContext.testDir, 'test-migrations'));
|
|
332
|
+
const timestamps = migrations.map(m => m.split('_')[0]);
|
|
333
|
+
const uniqueTimestamps = new Set(timestamps);
|
|
334
|
+
expect(uniqueTimestamps.size).toBe(templates.length);
|
|
335
|
+
expect(timestamps).toEqual([...timestamps].sort());
|
|
336
|
+
}
|
|
337
|
+
catch (e_8) {
|
|
338
|
+
env_8.error = e_8;
|
|
339
|
+
env_8.hasError = true;
|
|
340
|
+
}
|
|
341
|
+
finally {
|
|
342
|
+
__disposeResources(env_8);
|
|
343
|
+
}
|
|
252
344
|
});
|
|
253
345
|
it('should handle mix of working and broken templates', async () => {
|
|
254
|
-
|
|
255
|
-
await createTemplate(`a-test-bad.sql`, 'INVALID SQL SYNTAX;');
|
|
256
|
-
const manager = await TemplateManager.create(testContext.testDir);
|
|
257
|
-
const result = await manager.processTemplates({ apply: true });
|
|
258
|
-
expect(result.errors).toHaveLength(1);
|
|
259
|
-
expect(result.applied).toHaveLength(1);
|
|
260
|
-
const client = await connect();
|
|
346
|
+
const env_9 = { stack: [], error: void 0, hasError: false };
|
|
261
347
|
try {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
348
|
+
await createTemplateWithFunc(`a-test-good`, '_good_and_broken_mix');
|
|
349
|
+
await createTemplate(`a-test-bad.sql`, 'INVALID SQL SYNTAX;');
|
|
350
|
+
const manager = __addDisposableResource(env_9, await TemplateManager.create(testContext.testDir), false);
|
|
351
|
+
const result = await manager.processTemplates({ apply: true });
|
|
352
|
+
expect(result.errors).toHaveLength(1);
|
|
353
|
+
expect(result.applied).toHaveLength(1);
|
|
354
|
+
const client = await connect();
|
|
355
|
+
try {
|
|
356
|
+
const res = await client.query(`SELECT COUNT(*) FROM pg_proc WHERE proname = $1`, [
|
|
357
|
+
`${testContext.testFunctionName}_good_and_broken_mix`,
|
|
358
|
+
]);
|
|
359
|
+
expect(Number.parseInt(res.rows[0].count)).toBe(1);
|
|
360
|
+
}
|
|
361
|
+
finally {
|
|
362
|
+
client.release();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
catch (e_9) {
|
|
366
|
+
env_9.error = e_9;
|
|
367
|
+
env_9.hasError = true;
|
|
266
368
|
}
|
|
267
369
|
finally {
|
|
268
|
-
|
|
370
|
+
__disposeResources(env_9);
|
|
269
371
|
}
|
|
270
372
|
});
|
|
271
373
|
it('should handle database errors gracefully', async () => {
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
374
|
+
const env_10 = { stack: [], error: void 0, hasError: false };
|
|
375
|
+
try {
|
|
376
|
+
const manager = __addDisposableResource(env_10, await TemplateManager.create(testContext.testDir), false);
|
|
377
|
+
await createTemplate(`test-error.sql`, 'SELECT 1/0;'); // Division by zero error
|
|
378
|
+
const result = await manager.processTemplates({ apply: true });
|
|
379
|
+
expect(result.errors).toHaveLength(1);
|
|
380
|
+
expect(result.errors[0]?.error).toMatch(/division by zero/i);
|
|
381
|
+
}
|
|
382
|
+
catch (e_10) {
|
|
383
|
+
env_10.error = e_10;
|
|
384
|
+
env_10.hasError = true;
|
|
385
|
+
}
|
|
386
|
+
finally {
|
|
387
|
+
__disposeResources(env_10);
|
|
388
|
+
}
|
|
277
389
|
});
|
|
278
390
|
it('should handle file system errors', async () => {
|
|
279
391
|
const errorPath = join(testContext.testDir, 'test-templates', `test-error.sql`);
|
|
280
392
|
try {
|
|
281
|
-
|
|
282
|
-
await fs.chmod(errorPath, 0o000);
|
|
283
|
-
const manager = await TemplateManager.create(testContext.testDir);
|
|
393
|
+
const env_11 = { stack: [], error: void 0, hasError: false };
|
|
284
394
|
try {
|
|
285
|
-
await
|
|
395
|
+
await createTemplate(`test-error.sql`, 'SELECT 1;');
|
|
396
|
+
await fs.chmod(errorPath, 0o000);
|
|
397
|
+
const manager = __addDisposableResource(env_11, await TemplateManager.create(testContext.testDir), false);
|
|
398
|
+
try {
|
|
399
|
+
await manager.processTemplates({ generateFiles: true });
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
expect(error).toBeDefined();
|
|
403
|
+
}
|
|
404
|
+
// Cleanup for afterEach
|
|
405
|
+
await fs.chmod(errorPath, 0o644);
|
|
286
406
|
}
|
|
287
|
-
catch (
|
|
288
|
-
|
|
407
|
+
catch (e_11) {
|
|
408
|
+
env_11.error = e_11;
|
|
409
|
+
env_11.hasError = true;
|
|
410
|
+
}
|
|
411
|
+
finally {
|
|
412
|
+
__disposeResources(env_11);
|
|
289
413
|
}
|
|
290
|
-
// Cleanup for afterEach
|
|
291
|
-
await fs.chmod(errorPath, 0o644);
|
|
292
414
|
}
|
|
293
415
|
catch (error) {
|
|
294
416
|
expect(error).toBeDefined();
|
|
@@ -302,17 +424,29 @@ describe('TemplateManager', () => {
|
|
|
302
424
|
}
|
|
303
425
|
});
|
|
304
426
|
it('should handle large batches of templates', async () => {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
427
|
+
const env_12 = { stack: [], error: void 0, hasError: false };
|
|
428
|
+
try {
|
|
429
|
+
// Create 50 templates
|
|
430
|
+
await Promise.all([...Array(50)].map((_, i) => createTemplateWithFunc(`test_${i}`, `_large_batch_${i}`)));
|
|
431
|
+
const manager = __addDisposableResource(env_12, await TemplateManager.create(testContext.testDir), false);
|
|
432
|
+
const result = await manager.processTemplates({ generateFiles: true });
|
|
433
|
+
expect(result.errors).toHaveLength(0);
|
|
434
|
+
const migrations = await fs.readdir(join(testContext.testDir, 'test-migrations'));
|
|
435
|
+
expect(migrations.length).toBe(50);
|
|
436
|
+
}
|
|
437
|
+
catch (e_12) {
|
|
438
|
+
env_12.error = e_12;
|
|
439
|
+
env_12.hasError = true;
|
|
440
|
+
}
|
|
441
|
+
finally {
|
|
442
|
+
__disposeResources(env_12);
|
|
443
|
+
}
|
|
312
444
|
});
|
|
313
445
|
it('should handle templates with complex SQL', async () => {
|
|
314
|
-
const
|
|
315
|
-
|
|
446
|
+
const env_13 = { stack: [], error: void 0, hasError: false };
|
|
447
|
+
try {
|
|
448
|
+
const testFunctionName = `${testContext.testFunctionName}_complex`;
|
|
449
|
+
const complexSQL = `
|
|
316
450
|
CREATE OR REPLACE FUNCTION ${testFunctionName}(
|
|
317
451
|
param1 integer DEFAULT 100,
|
|
318
452
|
OUT result1 integer,
|
|
@@ -337,426 +471,595 @@ describe('TemplateManager', () => {
|
|
|
337
471
|
END;
|
|
338
472
|
$$ LANGUAGE plpgsql;
|
|
339
473
|
`;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
474
|
+
await createTemplate(`test-complex.sql`, complexSQL);
|
|
475
|
+
const manager = __addDisposableResource(env_13, await TemplateManager.create(testContext.testDir), false);
|
|
476
|
+
const result = await manager.processTemplates({ apply: true });
|
|
477
|
+
expect(result.errors).toHaveLength(0);
|
|
478
|
+
const client = await connect();
|
|
479
|
+
try {
|
|
480
|
+
const res = await client.query(`
|
|
347
481
|
SELECT proname, pronargs, prorettype::regtype::text as return_type
|
|
348
482
|
FROM pg_proc
|
|
349
483
|
WHERE proname = $1
|
|
350
484
|
`, [testFunctionName]);
|
|
351
|
-
|
|
352
|
-
|
|
485
|
+
expect(res.rows).toHaveLength(1);
|
|
486
|
+
expect(res.rows[0].return_type).toBe('record');
|
|
487
|
+
}
|
|
488
|
+
finally {
|
|
489
|
+
client.release();
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
catch (e_13) {
|
|
493
|
+
env_13.error = e_13;
|
|
494
|
+
env_13.hasError = true;
|
|
353
495
|
}
|
|
354
496
|
finally {
|
|
355
|
-
|
|
497
|
+
__disposeResources(env_13);
|
|
356
498
|
}
|
|
357
499
|
});
|
|
358
500
|
it('should maintain template state across manager instances', async () => {
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
501
|
+
const env_14 = { stack: [], error: void 0, hasError: false };
|
|
502
|
+
try {
|
|
503
|
+
const template = await createTemplateWithFunc(`test`, 'maintain_state');
|
|
504
|
+
// First manager instance
|
|
505
|
+
const manager1 = __addDisposableResource(env_14, await TemplateManager.create(testContext.testDir), false);
|
|
506
|
+
await manager1.processTemplates({ generateFiles: true });
|
|
507
|
+
// Second manager instance should see the state
|
|
508
|
+
const manager2 = __addDisposableResource(env_14, await TemplateManager.create(testContext.testDir), false);
|
|
509
|
+
const status = await manager2.getTemplateStatus(template);
|
|
510
|
+
expect(status.buildState.lastBuildHash).toBeDefined();
|
|
511
|
+
}
|
|
512
|
+
catch (e_14) {
|
|
513
|
+
env_14.error = e_14;
|
|
514
|
+
env_14.hasError = true;
|
|
515
|
+
}
|
|
516
|
+
finally {
|
|
517
|
+
__disposeResources(env_14);
|
|
518
|
+
}
|
|
367
519
|
});
|
|
368
520
|
it('should handle template additions in watch mode', async () => {
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
changes
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
521
|
+
const env_15 = { stack: [], error: void 0, hasError: false };
|
|
522
|
+
try {
|
|
523
|
+
const manager = __addDisposableResource(env_15, await TemplateManager.create(testContext.testDir), false);
|
|
524
|
+
const changes = [];
|
|
525
|
+
manager.on('templateChanged', template => {
|
|
526
|
+
changes.push(template.name);
|
|
527
|
+
});
|
|
528
|
+
const watcher = await manager.watch();
|
|
529
|
+
// Add new template after watch started
|
|
530
|
+
await createTemplateWithFunc('new', '_watch_addition');
|
|
531
|
+
await wait(150);
|
|
532
|
+
watcher.close();
|
|
533
|
+
expect(changes).toContain(`new_${testContext.testId}_1`);
|
|
534
|
+
}
|
|
535
|
+
catch (e_15) {
|
|
536
|
+
env_15.error = e_15;
|
|
537
|
+
env_15.hasError = true;
|
|
538
|
+
}
|
|
539
|
+
finally {
|
|
540
|
+
__disposeResources(env_15);
|
|
541
|
+
}
|
|
380
542
|
});
|
|
381
543
|
it('should handle templates in deep subdirectories', async () => {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
changes
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
544
|
+
const env_16 = { stack: [], error: void 0, hasError: false };
|
|
545
|
+
try {
|
|
546
|
+
// Create nested directory structure
|
|
547
|
+
const depth = 5;
|
|
548
|
+
const templatePaths = [];
|
|
549
|
+
for (let i = 1; i <= depth; i++) {
|
|
550
|
+
const dir = [...Array(i)].map((_, idx) => `level${idx + 1}`).join('/');
|
|
551
|
+
const templatePath = await createTemplateWithFunc(`depth-test_${i}`, `_depth_${i}`, dir);
|
|
552
|
+
templatePaths.push(templatePath);
|
|
553
|
+
}
|
|
554
|
+
const manager = __addDisposableResource(env_16, await TemplateManager.create(testContext.testDir), false);
|
|
555
|
+
const changes = [];
|
|
556
|
+
manager.on('templateChanged', template => {
|
|
557
|
+
changes.push(template.name);
|
|
558
|
+
});
|
|
559
|
+
const watcher = await manager.watch();
|
|
560
|
+
await wait(depth * 100 * 1.1);
|
|
561
|
+
watcher.close();
|
|
562
|
+
expect(changes.length).toBe(depth);
|
|
563
|
+
// Verify each template was detected
|
|
564
|
+
for (let i = 1; i <= depth; i++) {
|
|
565
|
+
expect(changes).toContain(`depth-test_${i}_${testContext.testId}_${i}`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
catch (e_16) {
|
|
569
|
+
env_16.error = e_16;
|
|
570
|
+
env_16.hasError = true;
|
|
571
|
+
}
|
|
572
|
+
finally {
|
|
573
|
+
__disposeResources(env_16);
|
|
402
574
|
}
|
|
403
575
|
});
|
|
404
576
|
it('should only watch SQL files', async () => {
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
changes
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const client = await connect();
|
|
423
|
-
const manager = await TemplateManager.create(testContext.testDir);
|
|
424
|
-
const changes = new Set();
|
|
425
|
-
const count = 5;
|
|
426
|
-
const watcher = await manager.watch();
|
|
427
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
428
|
-
manager.on('templateChanged', template => {
|
|
429
|
-
changes.add(template.name);
|
|
430
|
-
});
|
|
431
|
-
// Create multiple templates simultaneously
|
|
432
|
-
try {
|
|
433
|
-
await createTemplateWithFunc(`rapid_test_1`, '_batch_changes_1');
|
|
434
|
-
await createTemplateWithFunc(`rapid_test_2`, '_batch_changes_2');
|
|
435
|
-
await createTemplateWithFunc(`rapid_test_3`, '_batch_changes_3');
|
|
436
|
-
await createTemplateWithFunc(`rapid_test_4`, '_batch_changes_4', 'deep');
|
|
437
|
-
await createTemplateWithFunc(`rapid_test_5`, '_batch_changes_5', 'deep/nested');
|
|
577
|
+
const env_17 = { stack: [], error: void 0, hasError: false };
|
|
578
|
+
try {
|
|
579
|
+
const manager = __addDisposableResource(env_17, await TemplateManager.create(testContext.testDir), false);
|
|
580
|
+
const changes = [];
|
|
581
|
+
manager.on('templateChanged', template => {
|
|
582
|
+
changes.push(template.name);
|
|
583
|
+
});
|
|
584
|
+
const watcher = await manager.watch();
|
|
585
|
+
await wait(100);
|
|
586
|
+
// Create various file types
|
|
587
|
+
await fs.writeFile(join(testContext.testDir, 'test-templates/test.txt'), 'not sql');
|
|
588
|
+
await fs.writeFile(join(testContext.testDir, 'test-templates/test.md'), 'not sql');
|
|
589
|
+
await createTemplateWithFunc(`sql`, '_watch_sql_only');
|
|
590
|
+
await wait(500);
|
|
591
|
+
watcher.close();
|
|
592
|
+
expect(changes).toHaveLength(1);
|
|
593
|
+
expect(changes[0]).toBe(`sql_${testContext.testId}_1`);
|
|
438
594
|
}
|
|
439
|
-
catch (
|
|
440
|
-
|
|
441
|
-
|
|
595
|
+
catch (e_17) {
|
|
596
|
+
env_17.error = e_17;
|
|
597
|
+
env_17.hasError = true;
|
|
442
598
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
watcher.close();
|
|
446
|
-
expect(changes.size).toBe(count); // Should detect all 5 templates
|
|
447
|
-
for (let i = 1; i <= count; i++) {
|
|
448
|
-
expect(changes.has(`rapid_test_${i}_${testContext.testId}_${i}`)).toBe(true);
|
|
599
|
+
finally {
|
|
600
|
+
__disposeResources(env_17);
|
|
449
601
|
}
|
|
450
|
-
|
|
451
|
-
|
|
602
|
+
});
|
|
603
|
+
it('should handle multiple template changes simultaneously', async () => {
|
|
604
|
+
const env_18 = { stack: [], error: void 0, hasError: false };
|
|
452
605
|
try {
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
606
|
+
const client = await connect();
|
|
607
|
+
const manager = __addDisposableResource(env_18, await TemplateManager.create(testContext.testDir), false);
|
|
608
|
+
const changes = new Set();
|
|
609
|
+
const count = 5;
|
|
610
|
+
const watcher = await manager.watch();
|
|
611
|
+
await wait(100);
|
|
612
|
+
manager.on('templateChanged', template => {
|
|
613
|
+
changes.add(template.name);
|
|
614
|
+
});
|
|
615
|
+
// Create multiple templates simultaneously
|
|
616
|
+
try {
|
|
617
|
+
await createTemplateWithFunc(`rapid_test_1`, '_batch_changes_1');
|
|
618
|
+
await createTemplateWithFunc(`rapid_test_2`, '_batch_changes_2');
|
|
619
|
+
await createTemplateWithFunc(`rapid_test_3`, '_batch_changes_3');
|
|
620
|
+
await createTemplateWithFunc(`rapid_test_4`, '_batch_changes_4', 'deep');
|
|
621
|
+
await createTemplateWithFunc(`rapid_test_5`, '_batch_changes_5', 'deep/nested');
|
|
622
|
+
}
|
|
623
|
+
catch (error) {
|
|
624
|
+
console.error('Error creating templates:', error);
|
|
625
|
+
throw error;
|
|
626
|
+
}
|
|
627
|
+
// Give enough time for all changes to be detected
|
|
628
|
+
await wait(count * 100 * 1.1);
|
|
629
|
+
watcher.close();
|
|
630
|
+
expect(changes.size).toBe(count); // Should detect all 5 templates
|
|
631
|
+
for (let i = 1; i <= count; i++) {
|
|
632
|
+
expect(changes.has(`rapid_test_${i}_${testContext.testId}_${i}`)).toBe(true);
|
|
633
|
+
}
|
|
634
|
+
// Verify all templates were processed
|
|
635
|
+
await wait(100);
|
|
636
|
+
try {
|
|
637
|
+
const res = await client.query(`SELECT proname FROM pg_proc WHERE proname LIKE $1`, [
|
|
638
|
+
`${testContext.testFunctionName}_batch_changes_%`,
|
|
639
|
+
]);
|
|
640
|
+
// expect(res).toBe('');
|
|
641
|
+
expect(res.rows).toHaveLength(count);
|
|
642
|
+
}
|
|
643
|
+
catch (error) {
|
|
644
|
+
console.error('Error querying functions:', error);
|
|
645
|
+
}
|
|
646
|
+
finally {
|
|
647
|
+
client.release();
|
|
648
|
+
}
|
|
458
649
|
}
|
|
459
|
-
catch (
|
|
460
|
-
|
|
650
|
+
catch (e_18) {
|
|
651
|
+
env_18.error = e_18;
|
|
652
|
+
env_18.hasError = true;
|
|
461
653
|
}
|
|
462
654
|
finally {
|
|
463
|
-
|
|
655
|
+
__disposeResources(env_18);
|
|
464
656
|
}
|
|
465
657
|
}, 15000);
|
|
466
658
|
it('should handle rapid bulk template creation realistically', async () => {
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
resolveProcessing
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
659
|
+
const env_19 = { stack: [], error: void 0, hasError: false };
|
|
660
|
+
try {
|
|
661
|
+
const TEMPLATE_COUNT = 50;
|
|
662
|
+
const manager = __addDisposableResource(env_19, await TemplateManager.create(testContext.testDir), false);
|
|
663
|
+
const processed = new Set();
|
|
664
|
+
const failed = new Set();
|
|
665
|
+
const inProgress = new Set();
|
|
666
|
+
const events = [];
|
|
667
|
+
let resolveProcessing;
|
|
668
|
+
const processingComplete = new Promise(resolve => {
|
|
669
|
+
resolveProcessing = resolve;
|
|
670
|
+
});
|
|
671
|
+
manager.on('templateChanged', ({ name }) => {
|
|
672
|
+
events.push({ event: 'changed', template: name, time: Date.now() });
|
|
673
|
+
inProgress.add(name);
|
|
674
|
+
});
|
|
675
|
+
manager.on('templateApplied', ({ name }) => {
|
|
676
|
+
events.push({ event: 'applied', template: name, time: Date.now() });
|
|
677
|
+
processed.add(name);
|
|
678
|
+
inProgress.delete(name);
|
|
679
|
+
if (processed.size + failed.size === TEMPLATE_COUNT) {
|
|
680
|
+
resolveProcessing();
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
manager.on('templateError', ({ template: { name }, error }) => {
|
|
684
|
+
events.push({ event: 'error', template: name, time: Date.now() });
|
|
685
|
+
failed.add(name);
|
|
686
|
+
inProgress.delete(name);
|
|
687
|
+
console.error('Template error:', { name, error });
|
|
688
|
+
if (processed.size + failed.size === TEMPLATE_COUNT) {
|
|
689
|
+
resolveProcessing();
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
const watcher = await manager.watch();
|
|
693
|
+
// Create all templates
|
|
694
|
+
await Promise.all(Array.from({ length: TEMPLATE_COUNT }, (_, i) => createTemplateWithFunc(`bulk_${i + 1}`, `_bulk_${i + 1}`)));
|
|
695
|
+
await processingComplete;
|
|
696
|
+
watcher.close();
|
|
697
|
+
expect(processed.size + failed.size).toBe(TEMPLATE_COUNT);
|
|
698
|
+
expect(inProgress.size).toBe(0);
|
|
699
|
+
expect(failed.size).toBe(0);
|
|
700
|
+
}
|
|
701
|
+
catch (e_19) {
|
|
702
|
+
env_19.error = e_19;
|
|
703
|
+
env_19.hasError = true;
|
|
704
|
+
}
|
|
705
|
+
finally {
|
|
706
|
+
__disposeResources(env_19);
|
|
707
|
+
}
|
|
506
708
|
});
|
|
507
709
|
it('should cleanup resources when disposed', async () => {
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
changes
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
710
|
+
const env_20 = { stack: [], error: void 0, hasError: false };
|
|
711
|
+
try {
|
|
712
|
+
const manager = __addDisposableResource(env_20, await TemplateManager.create(testContext.testDir), false);
|
|
713
|
+
const changes = [];
|
|
714
|
+
manager.on('templateChanged', template => {
|
|
715
|
+
changes.push(template.name);
|
|
716
|
+
});
|
|
717
|
+
await manager.watch();
|
|
718
|
+
// Create template before disposal
|
|
719
|
+
await createTemplateWithFunc(`before-dispose`, 'before_dispose');
|
|
720
|
+
await wait(100);
|
|
721
|
+
// Dispose and verify cleanup
|
|
722
|
+
manager[Symbol.dispose]();
|
|
723
|
+
// Try creating template after disposal
|
|
724
|
+
await createTemplateWithFunc(`after-dispose`, 'after_dispose');
|
|
725
|
+
await wait(100);
|
|
726
|
+
expect(changes).toHaveLength(1);
|
|
727
|
+
expect(changes[0]).toBe(`before-dispose_${testContext.testId}_1`);
|
|
728
|
+
}
|
|
729
|
+
catch (e_20) {
|
|
730
|
+
env_20.error = e_20;
|
|
731
|
+
env_20.hasError = true;
|
|
732
|
+
}
|
|
733
|
+
finally {
|
|
734
|
+
__disposeResources(env_20);
|
|
735
|
+
}
|
|
524
736
|
});
|
|
525
737
|
it('should auto-cleanup with using statement', async () => {
|
|
526
738
|
const changes = [];
|
|
527
739
|
await (async () => {
|
|
528
|
-
const
|
|
740
|
+
const env_21 = { stack: [], error: void 0, hasError: false };
|
|
529
741
|
try {
|
|
530
|
-
const manager = __addDisposableResource(
|
|
742
|
+
const manager = __addDisposableResource(env_21, await TemplateManager.create(testContext.testDir), false);
|
|
531
743
|
manager.on('templateChanged', template => {
|
|
532
744
|
changes.push(template.name);
|
|
533
745
|
});
|
|
534
746
|
await manager.watch();
|
|
535
747
|
await createTemplateWithFunc(`during-scope`, 'during_scope');
|
|
536
|
-
await
|
|
748
|
+
await wait(100);
|
|
537
749
|
}
|
|
538
|
-
catch (
|
|
539
|
-
|
|
540
|
-
|
|
750
|
+
catch (e_21) {
|
|
751
|
+
env_21.error = e_21;
|
|
752
|
+
env_21.hasError = true;
|
|
541
753
|
}
|
|
542
754
|
finally {
|
|
543
|
-
__disposeResources(
|
|
755
|
+
__disposeResources(env_21);
|
|
544
756
|
}
|
|
545
757
|
})();
|
|
546
758
|
// After scope exit, create another template
|
|
547
759
|
await createTemplateWithFunc(`after-scope`, 'after_scope');
|
|
548
|
-
await
|
|
549
|
-
expect(changes).toHaveLength(1);
|
|
760
|
+
await wait(100);
|
|
550
761
|
expect(changes[0]).toBe(`during-scope_${testContext.testId}_1`);
|
|
762
|
+
expect(changes).toHaveLength(1);
|
|
551
763
|
});
|
|
552
764
|
it('should not process unchanged templates', async () => {
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
changes
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
765
|
+
const env_22 = { stack: [], error: void 0, hasError: false };
|
|
766
|
+
try {
|
|
767
|
+
const templatePath = await createTemplateWithFunc(`initial_will_remain_unchanged`, 'unchanged_tmpl');
|
|
768
|
+
const manager = __addDisposableResource(env_22, await TemplateManager.create(testContext.testDir), false);
|
|
769
|
+
await manager.watch();
|
|
770
|
+
// First processing
|
|
771
|
+
await manager.processTemplates({ apply: true });
|
|
772
|
+
// Get the status after first processing
|
|
773
|
+
const statusAfterFirstRun = await manager.getTemplateStatus(templatePath);
|
|
774
|
+
const changes = [];
|
|
775
|
+
manager.on('templateChanged', template => {
|
|
776
|
+
changes.push(template.name);
|
|
777
|
+
});
|
|
778
|
+
// Process again without changes
|
|
779
|
+
await manager.processTemplates({ apply: true });
|
|
780
|
+
// Get status after second run
|
|
781
|
+
const statusAfterSecondRun = await manager.getTemplateStatus(templatePath);
|
|
782
|
+
expect(changes).toHaveLength(0);
|
|
783
|
+
expect(statusAfterSecondRun.buildState.lastBuildHash).toBe(statusAfterFirstRun.buildState.lastBuildHash);
|
|
784
|
+
expect(statusAfterSecondRun.buildState.lastAppliedHash).toBe(statusAfterFirstRun.buildState.lastAppliedHash);
|
|
785
|
+
}
|
|
786
|
+
catch (e_22) {
|
|
787
|
+
env_22.error = e_22;
|
|
788
|
+
env_22.hasError = true;
|
|
789
|
+
}
|
|
790
|
+
finally {
|
|
791
|
+
__disposeResources(env_22);
|
|
792
|
+
}
|
|
571
793
|
});
|
|
572
794
|
it('should only process modified templates in batch', async () => {
|
|
573
|
-
|
|
574
|
-
const template1 = await createTemplateWithFunc(`modified_tmpl_1`, 'mod_1');
|
|
575
|
-
await createTemplateWithFunc(`modified_tmpl_2`, 'mod_2');
|
|
576
|
-
const manager = await TemplateManager.create(testContext.testDir);
|
|
577
|
-
// First processing of both
|
|
578
|
-
await manager.processTemplates({ apply: true });
|
|
579
|
-
const changes = [];
|
|
580
|
-
manager.on('templateChanged', template => {
|
|
581
|
-
changes.push(template.name);
|
|
582
|
-
});
|
|
583
|
-
// Modify only template1
|
|
795
|
+
const env_23 = { stack: [], error: void 0, hasError: false };
|
|
584
796
|
try {
|
|
585
|
-
|
|
797
|
+
// Create two templates
|
|
798
|
+
const template1 = await createTemplateWithFunc(`modified_tmpl_1`, 'mod_1');
|
|
799
|
+
await createTemplateWithFunc(`modified_tmpl_2`, 'mod_2');
|
|
800
|
+
const manager = __addDisposableResource(env_23, await TemplateManager.create(testContext.testDir), false);
|
|
801
|
+
// First processing of both
|
|
802
|
+
await manager.processTemplates({ apply: true });
|
|
803
|
+
const changes = [];
|
|
804
|
+
manager.on('templateChanged', template => {
|
|
805
|
+
changes.push(template.name);
|
|
806
|
+
});
|
|
807
|
+
// Modify only template1
|
|
808
|
+
try {
|
|
809
|
+
const tmpl1content = await fs.readFile(template1, 'utf-8');
|
|
810
|
+
await fs.writeFile(template1, `${tmpl1content}\n-- Modified`);
|
|
811
|
+
}
|
|
812
|
+
catch (error) {
|
|
813
|
+
console.error('Test: Error modifying template:', error);
|
|
814
|
+
throw error;
|
|
815
|
+
}
|
|
816
|
+
// Process both templates again
|
|
817
|
+
await manager.processTemplates({ apply: true });
|
|
818
|
+
expect(changes).toHaveLength(1);
|
|
819
|
+
expect(changes[0]).toBe(`modified_tmpl_1_${testContext.testId}_1`);
|
|
586
820
|
}
|
|
587
|
-
catch (
|
|
588
|
-
|
|
589
|
-
|
|
821
|
+
catch (e_23) {
|
|
822
|
+
env_23.error = e_23;
|
|
823
|
+
env_23.hasError = true;
|
|
824
|
+
}
|
|
825
|
+
finally {
|
|
826
|
+
__disposeResources(env_23);
|
|
590
827
|
}
|
|
591
|
-
// Process both templates again
|
|
592
|
-
await manager.processTemplates({ apply: true });
|
|
593
|
-
expect(changes).toHaveLength(1);
|
|
594
|
-
expect(changes[0]).toBe(`modified_tmpl_1_${testContext.testId}_1`);
|
|
595
828
|
});
|
|
596
829
|
it('should correctly update local buildlog on apply', async () => {
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
830
|
+
const env_24 = { stack: [], error: void 0, hasError: false };
|
|
831
|
+
try {
|
|
832
|
+
const templatePath = await createTemplateWithFunc(`buildlog`, '_buildlog');
|
|
833
|
+
const manager = __addDisposableResource(env_24, await TemplateManager.create(testContext.testDir), false);
|
|
834
|
+
const localBuildlogPath = join(testContext.testDir, '.buildlog-test.local.json');
|
|
835
|
+
// Initial apply
|
|
836
|
+
await manager.processTemplates({ apply: true });
|
|
837
|
+
const initialLog = JSON.parse(await fs.readFile(localBuildlogPath, 'utf-8'));
|
|
838
|
+
const relPath = relative(testContext.testDir, templatePath);
|
|
839
|
+
const initialHash = initialLog.templates[relPath].lastAppliedHash;
|
|
840
|
+
const initialContent = await fs.readFile(templatePath, 'utf-8');
|
|
841
|
+
expect(initialHash).toBeDefined();
|
|
842
|
+
// Modify template
|
|
843
|
+
await fs.writeFile(templatePath, `${initialContent}\n-- Modified`);
|
|
844
|
+
await wait(100);
|
|
845
|
+
const changedContent = await fs.readFile(templatePath, 'utf-8');
|
|
846
|
+
// Second apply
|
|
847
|
+
await manager.processTemplates({ apply: true });
|
|
848
|
+
await wait(100);
|
|
849
|
+
const updatedLog = JSON.parse(await fs.readFile(localBuildlogPath, 'utf-8'));
|
|
850
|
+
const newHash = updatedLog.templates[relPath].lastAppliedHash;
|
|
851
|
+
const manualMd5 = await calculateMD5(changedContent);
|
|
852
|
+
expect(newHash).toBeDefined();
|
|
853
|
+
expect(newHash).toBe(manualMd5);
|
|
854
|
+
expect(newHash).not.toBe(initialHash);
|
|
855
|
+
}
|
|
856
|
+
catch (e_24) {
|
|
857
|
+
env_24.error = e_24;
|
|
858
|
+
env_24.hasError = true;
|
|
859
|
+
}
|
|
860
|
+
finally {
|
|
861
|
+
__disposeResources(env_24);
|
|
862
|
+
}
|
|
620
863
|
});
|
|
621
864
|
it('should skip apply if template hash matches local buildlog', async () => {
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
865
|
+
const env_25 = { stack: [], error: void 0, hasError: false };
|
|
866
|
+
try {
|
|
867
|
+
const templatePath = await createTemplateWithFunc(`skip`, '_skip_apply');
|
|
868
|
+
const manager = __addDisposableResource(env_25, await TemplateManager.create(testContext.testDir), false);
|
|
869
|
+
const localBuildlogPath = join(testContext.testDir, '.buildlog-test.local.json');
|
|
870
|
+
// Initial apply
|
|
871
|
+
await manager.processTemplates({ apply: true });
|
|
872
|
+
const initialLog = JSON.parse(await fs.readFile(localBuildlogPath, 'utf-8'));
|
|
873
|
+
const relPath = relative(testContext.testDir, templatePath);
|
|
874
|
+
const initialHash = initialLog.templates[relPath].lastAppliedHash;
|
|
875
|
+
const initialDate = initialLog.templates[relPath].lastAppliedDate;
|
|
876
|
+
// Wait a bit to ensure timestamp would be different
|
|
877
|
+
await wait(100);
|
|
878
|
+
// Apply again without changes
|
|
879
|
+
await manager.processTemplates({ apply: true });
|
|
880
|
+
const updatedLog = JSON.parse(await fs.readFile(localBuildlogPath, 'utf-8'));
|
|
881
|
+
// Hash and date should remain exactly the same since no changes were made
|
|
882
|
+
expect(updatedLog.templates[relPath].lastAppliedHash).toBe(initialHash);
|
|
883
|
+
expect(updatedLog.templates[relPath].lastAppliedDate).toBe(initialDate);
|
|
884
|
+
}
|
|
885
|
+
catch (e_25) {
|
|
886
|
+
env_25.error = e_25;
|
|
887
|
+
env_25.hasError = true;
|
|
888
|
+
}
|
|
889
|
+
finally {
|
|
890
|
+
__disposeResources(env_25);
|
|
891
|
+
}
|
|
639
892
|
});
|
|
640
893
|
it('should not reapply unchanged templates in watch mode', async () => {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
changed
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
const
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
894
|
+
const env_26 = { stack: [], error: void 0, hasError: false };
|
|
895
|
+
try {
|
|
896
|
+
// Create multiple templates
|
|
897
|
+
const templates = await Promise.all([
|
|
898
|
+
createTemplateWithFunc(`watch-stable_1`, '_watch_1'),
|
|
899
|
+
createTemplateWithFunc(`watch-stable_2`, '_watch_2'),
|
|
900
|
+
]);
|
|
901
|
+
const manager = __addDisposableResource(env_26, await TemplateManager.create(testContext.testDir), false);
|
|
902
|
+
const applied = [];
|
|
903
|
+
const changed = [];
|
|
904
|
+
manager.on('templateChanged', template => {
|
|
905
|
+
changed.push(template.name);
|
|
906
|
+
});
|
|
907
|
+
manager.on('templateApplied', template => {
|
|
908
|
+
applied.push(template.name);
|
|
909
|
+
});
|
|
910
|
+
// First watch session
|
|
911
|
+
const watcher1 = await manager.watch();
|
|
912
|
+
await wait(100);
|
|
913
|
+
await watcher1.close();
|
|
914
|
+
// Record initial state
|
|
915
|
+
const initialApplied = [...applied];
|
|
916
|
+
const initialChanged = [...changed];
|
|
917
|
+
applied.length = 0;
|
|
918
|
+
changed.length = 0;
|
|
919
|
+
// Second watch session without any changes
|
|
920
|
+
const watcher2 = await manager.watch();
|
|
921
|
+
await wait(100);
|
|
922
|
+
await watcher2.close();
|
|
923
|
+
expect(initialApplied).toHaveLength(2); // First run should apply both
|
|
924
|
+
expect(initialChanged).toHaveLength(2); // First run should detect both
|
|
925
|
+
expect(applied).toHaveLength(0); // Second run should apply none
|
|
926
|
+
expect(changed).toHaveLength(0); // Second run should detect none
|
|
927
|
+
// Verify the buildlog state
|
|
928
|
+
const localBuildlogPath = join(testContext.testDir, '.buildlog-test.local.json');
|
|
929
|
+
const buildLog = JSON.parse(await fs.readFile(localBuildlogPath, 'utf-8'));
|
|
930
|
+
for (const templatePath of templates) {
|
|
931
|
+
const relPath = relative(testContext.testDir, templatePath);
|
|
932
|
+
const content = await fs.readFile(templatePath, 'utf-8');
|
|
933
|
+
const hash = await calculateMD5(content);
|
|
934
|
+
expect(buildLog.templates[relPath].lastAppliedHash).toBe(hash);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
catch (e_26) {
|
|
938
|
+
env_26.error = e_26;
|
|
939
|
+
env_26.hasError = true;
|
|
940
|
+
}
|
|
941
|
+
finally {
|
|
942
|
+
__disposeResources(env_26);
|
|
680
943
|
}
|
|
681
944
|
});
|
|
682
945
|
it('should process unapplied templates on startup', async () => {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
946
|
+
const env_27 = { stack: [], error: void 0, hasError: false };
|
|
947
|
+
try {
|
|
948
|
+
// Create template but don't process it
|
|
949
|
+
await createTemplateWithFunc(`startup-test`, '_startup_test');
|
|
950
|
+
// Create a new manager instance
|
|
951
|
+
const manager = __addDisposableResource(env_27, await TemplateManager.create(testContext.testDir), false);
|
|
952
|
+
const changes = [];
|
|
953
|
+
const applied = [];
|
|
954
|
+
manager.on('templateChanged', t => changes.push(t.name));
|
|
955
|
+
manager.on('templateApplied', t => applied.push(t.name));
|
|
956
|
+
// Start watching - this should process the template
|
|
957
|
+
await manager.watch();
|
|
958
|
+
await wait(100);
|
|
959
|
+
expect(changes).toHaveLength(1);
|
|
960
|
+
expect(applied).toHaveLength(1);
|
|
961
|
+
expect(changes[0]).toBe(`startup-test_${testContext.testId}_1`);
|
|
962
|
+
expect(applied[0]).toBe(`startup-test_${testContext.testId}_1`);
|
|
963
|
+
}
|
|
964
|
+
catch (e_27) {
|
|
965
|
+
env_27.error = e_27;
|
|
966
|
+
env_27.hasError = true;
|
|
967
|
+
}
|
|
968
|
+
finally {
|
|
969
|
+
__disposeResources(env_27);
|
|
970
|
+
}
|
|
698
971
|
});
|
|
699
972
|
it('should handle error state transitions correctly', async () => {
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
{
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
973
|
+
const env_28 = { stack: [], error: void 0, hasError: false };
|
|
974
|
+
try {
|
|
975
|
+
const templatePath = await createTemplateWithFunc(`error-state`, '_error_test');
|
|
976
|
+
const manager = __addDisposableResource(env_28, await TemplateManager.create(testContext.testDir), false);
|
|
977
|
+
const states = [];
|
|
978
|
+
manager.on('templateChanged', () => states.push({ type: 'changed' }));
|
|
979
|
+
manager.on('templateApplied', () => states.push({ type: 'applied' }));
|
|
980
|
+
manager.on('templateError', ({ error }) => states.push({ type: 'error', error: String(error) }));
|
|
981
|
+
// First apply should succeed
|
|
982
|
+
await manager.processTemplates({ apply: true });
|
|
983
|
+
// Modify template to be invalid
|
|
984
|
+
await fs.writeFile(templatePath, 'INVALID SQL;');
|
|
985
|
+
await manager.processTemplates({ apply: true });
|
|
986
|
+
// Fix template with valid SQL
|
|
987
|
+
await fs.writeFile(templatePath, `CREATE OR REPLACE FUNCTION ${testContext.testFunctionName}() RETURNS void AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`);
|
|
988
|
+
await manager.processTemplates({ apply: true });
|
|
989
|
+
expect(states).toEqual([
|
|
990
|
+
{ type: 'changed' },
|
|
991
|
+
{ type: 'applied' },
|
|
992
|
+
{ type: 'changed' },
|
|
993
|
+
{ type: 'error', error: expect.stringMatching(/syntax error/) },
|
|
994
|
+
{ type: 'changed' },
|
|
995
|
+
{ type: 'applied' },
|
|
996
|
+
]);
|
|
997
|
+
}
|
|
998
|
+
catch (e_28) {
|
|
999
|
+
env_28.error = e_28;
|
|
1000
|
+
env_28.hasError = true;
|
|
1001
|
+
}
|
|
1002
|
+
finally {
|
|
1003
|
+
__disposeResources(env_28);
|
|
1004
|
+
}
|
|
722
1005
|
});
|
|
723
1006
|
it('should maintain correct state through manager restarts', async () => {
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
1007
|
+
const env_29 = { stack: [], error: void 0, hasError: false };
|
|
1008
|
+
try {
|
|
1009
|
+
const templatePath = await createTemplateWithFunc(`restart-test`, 'restart_test');
|
|
1010
|
+
// First manager instance
|
|
1011
|
+
const manager1 = __addDisposableResource(env_29, await TemplateManager.create(testContext.testDir), false);
|
|
1012
|
+
await manager1.processTemplates({ apply: true });
|
|
1013
|
+
// Get initial state
|
|
1014
|
+
const status1 = await manager1.getTemplateStatus(templatePath);
|
|
1015
|
+
const initialHash = status1.buildState.lastAppliedHash;
|
|
1016
|
+
// Modify template
|
|
1017
|
+
await fs.writeFile(templatePath, `${await fs.readFile(templatePath, 'utf-8')}\n-- Modified`);
|
|
1018
|
+
// Create new manager instance
|
|
1019
|
+
const manager2 = __addDisposableResource(env_29, await TemplateManager.create(testContext.testDir), false);
|
|
1020
|
+
const changes = [];
|
|
1021
|
+
const applied = [];
|
|
1022
|
+
manager2.on('templateChanged', t => changes.push(t.name));
|
|
1023
|
+
manager2.on('templateApplied', t => applied.push(t.name));
|
|
1024
|
+
await manager2.watch();
|
|
1025
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1026
|
+
// Verify state was maintained and change was detected
|
|
1027
|
+
const status2 = await manager2.getTemplateStatus(templatePath);
|
|
1028
|
+
expect(status2.buildState.lastAppliedHash).not.toBe(initialHash);
|
|
1029
|
+
expect(changes).toContain(`restart-test_${testContext.testId}_1`);
|
|
1030
|
+
expect(applied).toContain(`restart-test_${testContext.testId}_1`);
|
|
1031
|
+
}
|
|
1032
|
+
catch (e_29) {
|
|
1033
|
+
env_29.error = e_29;
|
|
1034
|
+
env_29.hasError = true;
|
|
1035
|
+
}
|
|
1036
|
+
finally {
|
|
1037
|
+
__disposeResources(env_29);
|
|
1038
|
+
}
|
|
746
1039
|
});
|
|
747
1040
|
it('should properly format and propagate error messages', async () => {
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
1041
|
+
const env_30 = { stack: [], error: void 0, hasError: false };
|
|
1042
|
+
try {
|
|
1043
|
+
const templatePath = await createTemplateWithFunc(`error-format`, 'error_format');
|
|
1044
|
+
const manager = __addDisposableResource(env_30, await TemplateManager.create(testContext.testDir), false);
|
|
1045
|
+
const errors = [];
|
|
1046
|
+
manager.on('templateError', err => errors.push(err));
|
|
1047
|
+
// Create invalid SQL
|
|
1048
|
+
await fs.writeFile(templatePath, 'SELECT * FROM nonexistent_table;');
|
|
1049
|
+
await manager.processTemplates({ apply: true });
|
|
1050
|
+
expect(errors).toHaveLength(1);
|
|
1051
|
+
const error = errors[0]?.error;
|
|
1052
|
+
expect(typeof error).toBe('string');
|
|
1053
|
+
expect(error).not.toMatch(/\[object Object\]/);
|
|
1054
|
+
expect(error).toMatch(/relation.*does not exist/i);
|
|
1055
|
+
}
|
|
1056
|
+
catch (e_30) {
|
|
1057
|
+
env_30.error = e_30;
|
|
1058
|
+
env_30.hasError = true;
|
|
1059
|
+
}
|
|
1060
|
+
finally {
|
|
1061
|
+
__disposeResources(env_30);
|
|
1062
|
+
}
|
|
760
1063
|
});
|
|
761
1064
|
});
|
|
762
1065
|
//# sourceMappingURL=templateManager.test.js.map
|