Skip to content

Commit b6f67fa

Browse files
zknprgoogle-labs-jules[bot]claude
committed
test: consolidate error-path coverage from overlapping Jules PRs
Several Jules PRs added distinct error-path tests to the same test files, making them mutually conflicting on GitHub. This consolidates the distinct cases into one change (dropping overlaps): - databaseModel: save/saveAs writeToFile -> serializeDatabase fallback (#409); undo/redo modification error handling (#378) - sqlite-db: createDatabaseEngine file-read errors (#392); safeRollback warn-on-error (#389); executeQuery iteration error frees statement (#384) - nativeWorker: export NativeWorkerProcess + handleData deserialization-error logging (#407); createNativeDatabaseConnection error context (#412) - tableExporter: stream-write -> memory export fallback (#400); export error surfacing (#388) Subsumes #404/#368 (covered by #409) and #393 (covered by #378). Unit suite: 311 passing (was 299). Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 98d8ea5 commit b6f67fa

5 files changed

Lines changed: 770 additions & 4 deletions

File tree

src/nativeWorker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ function writeMessage(stream: NodeJS.WritableStream, msg: unknown): void {
157157
*
158158
* Manages the txiki-js child process and provides RPC-style communication.
159159
*/
160-
class NativeWorkerProcess {
160+
export class NativeWorkerProcess {
161161
private process: ChildProcess | null = null;
162162
private messageId = 0;
163163
private pendingRequests = new Map<number, {

tests/unit/databaseModel.test.ts

Lines changed: 343 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import './vscode_mock_setup';
2-
import { describe, it, before, after, beforeEach } from 'node:test';
2+
import { describe, it, before, after, beforeEach, afterEach } from 'node:test';
33
import assert from 'node:assert';
44
import { mockVscode } from './mocks/vscode';
55

@@ -118,3 +118,345 @@ describe('isAutoCommitEnabled', () => {
118118
await setupEnvironmentAndTest(undefined, 'ssh-remote', 2 /* Workspace */, false);
119119
});
120120
});
121+
122+
123+
124+
125+
describe('DatabaseDocument save/saveAs fallback', () => {
126+
let DatabaseDocument: any;
127+
128+
beforeEach(() => {
129+
// Clear module cache to ensure we get a fresh instance that uses our mocked workerFactory
130+
const moduleCache = require('module')._cache;
131+
Object.keys(moduleCache).forEach(key => {
132+
if (key.includes('src/') && !key.includes('workerFactory') && !key.includes('mocks')) {
133+
delete moduleCache[key];
134+
}
135+
});
136+
137+
const workerFactoryPath = require.resolve('../../src/workerFactory');
138+
moduleCache[workerFactoryPath] = {
139+
id: workerFactoryPath,
140+
filename: workerFactoryPath,
141+
loaded: true,
142+
exports: {
143+
createDatabaseConnection: async () => ({
144+
establishConnection: async () => ({
145+
databaseOps: {
146+
engineKind: Promise.resolve('wasm'),
147+
writeToFile: async () => { throw new Error('Simulated write failure'); },
148+
serializeDatabase: async () => new Uint8Array([1, 2, 3]),
149+
applyModifications: async () => {}
150+
},
151+
isReadOnly: false
152+
}),
153+
workerMethods: {
154+
[Symbol.dispose]: () => {}
155+
}
156+
})
157+
}
158+
};
159+
160+
const dbModel = require('../../src/databaseModel');
161+
DatabaseDocument = dbModel.DatabaseDocument;
162+
});
163+
164+
const createFileUri = (path: string) => {
165+
return {
166+
scheme: 'file',
167+
authority: '',
168+
path: path,
169+
query: '',
170+
fragment: '',
171+
fsPath: path,
172+
with: () => ({}),
173+
toJSON: () => ({})
174+
};
175+
};
176+
177+
const createDocBypassingFactory = (dbOps: any) => {
178+
const mockViewerProvider = {
179+
reporter: {},
180+
isVerified: true,
181+
context: { extensionUri: createFileUri('/ext') },
182+
forceReadOnly: false,
183+
outputChannel: undefined
184+
};
185+
const fileUri = createFileUri('/test/db.sqlite');
186+
187+
return new (DatabaseDocument as any)(
188+
mockViewerProvider,
189+
fileUri,
190+
null, // tracker
191+
false, // autoCommitEnabled
192+
{ databaseOps: dbOps, isReadOnly: false },
193+
{ [Symbol.dispose]: () => {} }, // workerMethods
194+
async () => {}, // establishConnection
195+
{} // reporter
196+
);
197+
};
198+
199+
it('saveAs: falls back to buffer transfer when writeToFile fails for file URI', async () => {
200+
let serialized = false;
201+
const dbOps = {
202+
engineKind: Promise.resolve('wasm'),
203+
writeToFile: async () => { throw new Error('Simulated write failure'); },
204+
serializeDatabase: async () => {
205+
serialized = true;
206+
return new Uint8Array([1, 2, 3]);
207+
}
208+
};
209+
210+
const doc = createDocBypassingFactory(dbOps);
211+
212+
const targetUri = createFileUri('/test/target.sqlite');
213+
214+
let statCalled = false;
215+
let writeFileCalled = false;
216+
217+
const originalFs = mockVscode.workspace.fs;
218+
mockVscode.workspace.fs = {
219+
...originalFs,
220+
stat: async () => {
221+
statCalled = true;
222+
return { size: 100 }; // Less than getFileSizeLimit()
223+
},
224+
writeFile: async (uri: any, content: any) => {
225+
writeFileCalled = true;
226+
assert.deepStrictEqual(content, new Uint8Array([1, 2, 3]));
227+
},
228+
readFile: async () => new Uint8Array([])
229+
} as any;
230+
231+
const originalConsoleWarn = console.warn;
232+
let consoleWarnCalled = false;
233+
console.warn = (msg: string, err: any) => {
234+
if (msg && msg.includes('Direct write failed')) {
235+
consoleWarnCalled = true;
236+
} else {
237+
originalConsoleWarn(msg, err);
238+
}
239+
};
240+
241+
try {
242+
await doc.saveAs(targetUri, undefined);
243+
244+
assert.strictEqual(consoleWarnCalled, true, 'console.warn should be called');
245+
assert.strictEqual(statCalled, true, 'fs.stat should be called');
246+
assert.strictEqual(serialized, true, 'serializeDatabase should be called');
247+
assert.strictEqual(writeFileCalled, true, 'fs.writeFile should be called');
248+
} finally {
249+
mockVscode.workspace.fs = originalFs;
250+
console.warn = originalConsoleWarn;
251+
}
252+
});
253+
254+
it('save: falls back to buffer transfer when writeToFile fails for file URI', async () => {
255+
let serialized = false;
256+
const dbOps = {
257+
engineKind: Promise.resolve('wasm'),
258+
writeToFile: async () => { throw new Error('Simulated write failure'); },
259+
serializeDatabase: async () => {
260+
serialized = true;
261+
return new Uint8Array([1, 2, 3]);
262+
}
263+
};
264+
265+
const doc = createDocBypassingFactory(dbOps);
266+
267+
let writeFileCalled = false;
268+
269+
const originalFs = mockVscode.workspace.fs;
270+
mockVscode.workspace.fs = {
271+
...originalFs,
272+
stat: async () => {
273+
return { size: 100 };
274+
},
275+
writeFile: async (uri: any, content: any) => {
276+
writeFileCalled = true;
277+
assert.deepStrictEqual(content, new Uint8Array([1, 2, 3]));
278+
},
279+
readFile: async () => new Uint8Array([])
280+
} as any;
281+
282+
const originalConsoleWarn = console.warn;
283+
let consoleWarnCalled = false;
284+
console.warn = (msg: string, err: any) => {
285+
if (msg && msg.includes('Direct write failed')) {
286+
consoleWarnCalled = true;
287+
} else {
288+
originalConsoleWarn(msg, err);
289+
}
290+
};
291+
292+
try {
293+
await doc.save();
294+
295+
assert.strictEqual(consoleWarnCalled, true, 'console.warn should be called');
296+
assert.strictEqual(serialized, true, 'serializeDatabase should be called');
297+
assert.strictEqual(writeFileCalled, true, 'fs.writeFile should be called');
298+
} finally {
299+
mockVscode.workspace.fs = originalFs;
300+
console.warn = originalConsoleWarn;
301+
}
302+
});
303+
});
304+
305+
306+
describe('DatabaseDocument undo/redo error handling', () => {
307+
let mockViewerProvider: any;
308+
let mockOpenContext: any;
309+
let originalShowErrorMessage: any;
310+
let originalL10n: any;
311+
312+
beforeEach(() => {
313+
originalShowErrorMessage = mockVscode.window.showErrorMessage;
314+
originalL10n = mockVscode.l10n.t;
315+
316+
mockVscode.l10n.t = (key: string, ...args: any[]) => {
317+
let res = key;
318+
args.forEach((arg, i) => {
319+
res = res.replace(`{${i}}`, String(arg));
320+
});
321+
return res;
322+
};
323+
324+
mockViewerProvider = {
325+
reporter: undefined,
326+
isVerified: true,
327+
context: { extensionUri: mockVscode.Uri.parse('file:///ext') },
328+
forceReadOnly: false
329+
};
330+
331+
mockOpenContext = {
332+
backupId: undefined
333+
};
334+
335+
const moduleCache = require('module')._cache;
336+
const workerFactoryPath = require.resolve('../../src/workerFactory');
337+
moduleCache[workerFactoryPath] = {
338+
id: workerFactoryPath,
339+
filename: workerFactoryPath,
340+
loaded: true,
341+
exports: {
342+
createDatabaseConnection: () => {
343+
return Promise.resolve({
344+
workerMethods: {
345+
open: () => Promise.resolve({ isReadOnly: false, bufferInfo: {} }),
346+
exec: () => Promise.resolve(),
347+
},
348+
establishConnection: () => Promise.resolve({ isReadOnly: false }),
349+
databaseOps: {
350+
undoModification: () => Promise.resolve(),
351+
redoModification: () => Promise.resolve(),
352+
close: () => Promise.resolve(),
353+
getSchema: () => Promise.resolve([]),
354+
query: () => Promise.resolve([])
355+
}
356+
});
357+
}
358+
}
359+
};
360+
361+
});
362+
363+
afterEach(() => {
364+
mockVscode.window.showErrorMessage = originalShowErrorMessage;
365+
mockVscode.l10n.t = originalL10n;
366+
});
367+
368+
it('should show error message when undoModification fails', async () => {
369+
let errorMessageShown = false;
370+
mockVscode.window.showErrorMessage = async (msg?: string) => {
371+
if (msg?.includes('Test Undo Error')) {
372+
errorMessageShown = true;
373+
}
374+
};
375+
376+
// We clear module cache of databaseModel so it uses the mocked workerFactory
377+
delete require('module')._cache[require.resolve('../../src/databaseModel')];
378+
const { DatabaseDocument } = require('../../src/databaseModel');
379+
380+
const uri = mockVscode.Uri.parse('file:///test.db');
381+
const doc = await DatabaseDocument.create(mockViewerProvider, uri, mockOpenContext);
382+
383+
// Mock database operations to throw error on undo
384+
const dbOps = doc.databaseOperations;
385+
if(dbOps) {
386+
dbOps.undoModification = () => Promise.reject(new Error("Test Undo Error"));
387+
} else {
388+
// force override via any
389+
(doc as any).connectionState = {
390+
databaseOps: {
391+
undoModification: () => Promise.reject(new Error("Test Undo Error")),
392+
redoModification: () => Promise.resolve()
393+
}
394+
};
395+
}
396+
397+
let undoAction: (() => Promise<void>) | undefined;
398+
doc.onDidChange((modification: any) => {
399+
undoAction = modification.undo;
400+
});
401+
402+
doc.recordModification({
403+
label: 'Test Mod',
404+
action: 'Test Mod Action',
405+
sql: [],
406+
inverseSql: []
407+
});
408+
409+
assert.ok(undoAction, 'Undo action should be emitted');
410+
411+
await undoAction();
412+
413+
assert.strictEqual(errorMessageShown, true, 'Error message should be shown for failed undo');
414+
});
415+
416+
it('should show error message when redoModification fails', async () => {
417+
let errorMessageShown = false;
418+
mockVscode.window.showErrorMessage = async (msg?: string) => {
419+
if (msg?.includes('Test Redo Error')) {
420+
errorMessageShown = true;
421+
}
422+
};
423+
424+
const { DatabaseDocument } = require('../../src/databaseModel');
425+
const uri = mockVscode.Uri.parse('file:///test.db');
426+
const doc = await DatabaseDocument.create(mockViewerProvider, uri, mockOpenContext);
427+
428+
// Mock database operations to throw error on redo
429+
const dbOps = doc.databaseOperations;
430+
if(dbOps) {
431+
dbOps.redoModification = () => Promise.reject(new Error("Test Redo Error"));
432+
} else {
433+
(doc as any).connectionState = {
434+
databaseOps: {
435+
undoModification: () => Promise.resolve(),
436+
redoModification: () => Promise.reject(new Error("Test Redo Error"))
437+
}
438+
};
439+
}
440+
441+
let redoAction: (() => Promise<void>) | undefined;
442+
let undoAction: (() => Promise<void>) | undefined;
443+
doc.onDidChange((modification: any) => {
444+
undoAction = modification.undo;
445+
redoAction = modification.redo;
446+
});
447+
448+
doc.recordModification({
449+
label: 'Test Mod',
450+
action: 'Test Mod Action',
451+
sql: [],
452+
inverseSql: []
453+
});
454+
455+
assert.ok(redoAction, 'Redo action should be emitted');
456+
457+
await undoAction!();
458+
await redoAction();
459+
460+
assert.strictEqual(errorMessageShown, true, 'Error message should be shown for failed redo');
461+
});
462+
});

0 commit comments

Comments
 (0)