Skip to content

Commit 9663762

Browse files
committed
Add error handling tests for CouchDB and MongoDB repositories: Introduce comprehensive Jest tests for database connection failures, edge case handling, and improved _mapResult coverage. Update coverage thresholds and dependencies.
1 parent 2c02184 commit 9663762

6 files changed

Lines changed: 368 additions & 22 deletions

File tree

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import { jest } from '@jest/globals';
2+
import { CouchDbNoteRepository } from '../../src/db/couchdb-note-repository.js';
3+
4+
// We'll manually mock the nano module
5+
jest.mock('nano', () => {
6+
const mockDb = {
7+
get: jest.fn(),
8+
insert: jest.fn(),
9+
view: jest.fn(),
10+
destroy: jest.fn()
11+
};
12+
13+
const mockClient = {
14+
db: {
15+
list: jest.fn().mockResolvedValue([]),
16+
create: jest.fn().mockResolvedValue({ ok: true }),
17+
},
18+
use: jest.fn().mockReturnValue(mockDb)
19+
};
20+
21+
return jest.fn(() => mockClient);
22+
});
23+
24+
// Import nano after mocking it
25+
import nano from 'nano';
26+
27+
describe('CouchDbNoteRepository Error Handling Tests', () => {
28+
let repository;
29+
const url = 'http://admin:password@localhost:5984';
30+
const dbName = 'notes_test';
31+
32+
beforeEach(() => {
33+
// Reset all mocks before each test
34+
jest.clearAllMocks();
35+
36+
// Create mock functions with proper Jest mock methods
37+
const mockDb = {
38+
get: jest.fn(),
39+
insert: jest.fn(),
40+
view: jest.fn(),
41+
destroy: jest.fn()
42+
};
43+
44+
const mockClient = {
45+
db: {
46+
list: jest.fn(),
47+
create: jest.fn(),
48+
},
49+
use: jest.fn().mockReturnValue(mockDb)
50+
};
51+
52+
// Create the repository with the mocked client
53+
repository = new CouchDbNoteRepository(url, dbName);
54+
55+
// Replace the repository's client with our mock
56+
repository.client = mockClient;
57+
repository.db = mockDb;
58+
});
59+
60+
describe('init() method error handling', () => {
61+
it('should handle database list failure', async () => {
62+
// Mock db.list to throw an error
63+
const mockError = new Error('Failed to list databases');
64+
repository.client.db.list.mockRejectedValue(mockError);
65+
66+
await expect(repository.init()).rejects.toThrow('Failed to list databases');
67+
expect(repository.client.db.list).toHaveBeenCalled();
68+
});
69+
70+
it('should handle database creation failure', async () => {
71+
// Mock db.create to throw an error
72+
const mockError = new Error('Failed to create database');
73+
repository.client.db.list.mockResolvedValue([]);
74+
repository.client.db.create.mockRejectedValue(mockError);
75+
76+
await expect(repository.init()).rejects.toThrow('Failed to create database');
77+
expect(repository.client.db.list).toHaveBeenCalled();
78+
expect(repository.client.db.create).toHaveBeenCalledWith(dbName);
79+
});
80+
81+
it('should handle design document get error other than 404', async () => {
82+
// Mock db.get to throw a non-404 error
83+
const mockError = new Error('Internal server error');
84+
mockError.statusCode = 500;
85+
86+
// Set up the mocks to simulate the error scenario
87+
repository.client.db.list.mockResolvedValue([dbName]); // Database already exists
88+
repository.db.get.mockRejectedValue(mockError); // Design doc get fails with 500
89+
90+
await expect(repository.init()).rejects.toThrow('Internal server error');
91+
expect(repository.db.get).toHaveBeenCalledWith('_design/notes');
92+
});
93+
94+
it('should handle design document insert failure', async () => {
95+
// Mock db.get to throw a 404 error and db.insert to throw an error
96+
const getError = new Error('Not found');
97+
getError.statusCode = 404;
98+
99+
const insertError = new Error('Failed to insert design document');
100+
101+
// Set up the mocks to simulate the error scenario
102+
repository.client.db.list.mockResolvedValue([dbName]); // Database already exists
103+
repository.db.get.mockRejectedValue(getError); // Design doc get fails with 404
104+
repository.db.insert.mockRejectedValue(insertError); // Design doc insert fails
105+
106+
await expect(repository.init()).rejects.toThrow('Failed to insert design document');
107+
expect(repository.db.get).toHaveBeenCalledWith('_design/notes');
108+
expect(repository.db.insert).toHaveBeenCalled();
109+
});
110+
});
111+
112+
describe('_mapResult() method edge cases', () => {
113+
it('should handle null result', async () => {
114+
const result = repository._mapResult(null);
115+
expect(result).toEqual([]);
116+
});
117+
118+
it('should handle undefined result', async () => {
119+
const result = repository._mapResult(undefined);
120+
expect(result).toEqual([]);
121+
});
122+
123+
it('should handle result without rows property', async () => {
124+
const result = repository._mapResult({});
125+
expect(result).toEqual([]);
126+
});
127+
128+
it('should handle result with empty rows array', async () => {
129+
const result = repository._mapResult({ rows: [] });
130+
expect(result).toEqual([]);
131+
});
132+
133+
it('should handle result with rows containing null doc', async () => {
134+
const result = repository._mapResult({ rows: [{ doc: null }] });
135+
expect(result).toEqual([]);
136+
});
137+
138+
it('should handle result with rows missing doc property', async () => {
139+
const result = repository._mapResult({ rows: [{}] });
140+
expect(result).toEqual([]);
141+
});
142+
143+
it('should handle result with mixed valid and invalid rows', async () => {
144+
const validDoc = {
145+
_id: 'note1',
146+
title: 'Valid Note',
147+
content: 'Valid Content',
148+
deletedAt: null,
149+
createdAt: new Date().toISOString(),
150+
updatedAt: new Date().toISOString()
151+
};
152+
153+
const result = repository._mapResult({
154+
rows: [
155+
{ doc: validDoc },
156+
{ doc: null },
157+
{}
158+
]
159+
});
160+
161+
expect(result.length).toBe(1);
162+
expect(result[0].id).toBe('note1');
163+
});
164+
});
165+
166+
describe('Database connection failure handling', () => {
167+
beforeEach(() => {
168+
// Setup repository with a db that throws connection errors
169+
repository.db = {
170+
view: jest.fn().mockRejectedValue(new Error('Connection failed')),
171+
get: jest.fn().mockRejectedValue(new Error('Connection failed')),
172+
insert: jest.fn().mockRejectedValue(new Error('Connection failed')),
173+
destroy: jest.fn().mockRejectedValue(new Error('Connection failed'))
174+
};
175+
});
176+
177+
it('should handle connection failure in findAll()', async () => {
178+
await expect(repository.findAll()).rejects.toThrow('Connection failed');
179+
expect(repository.db.view).toHaveBeenCalled();
180+
});
181+
182+
it('should handle connection failure in findDeleted()', async () => {
183+
await expect(repository.findDeleted()).rejects.toThrow('Connection failed');
184+
expect(repository.db.view).toHaveBeenCalled();
185+
});
186+
187+
it('should handle connection failure in findAllIncludingDeleted()', async () => {
188+
await expect(repository.findAllIncludingDeleted()).rejects.toThrow('Connection failed');
189+
expect(repository.db.view).toHaveBeenCalled();
190+
});
191+
192+
it('should handle connection failure in create()', async () => {
193+
await expect(repository.create({ title: 'Test', content: 'Test' })).rejects.toThrow('Connection failed');
194+
expect(repository.db.insert).toHaveBeenCalled();
195+
});
196+
});
197+
});
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { jest } from '@jest/globals';
2+
import mongoose from 'mongoose';
3+
import { MongoDbNoteRepository } from '../../src/db/mongodb-note-repository.js';
4+
5+
jest.mock('mongoose');
6+
7+
/*
8+
// We'll manually mock the mongoose module
9+
jest.mock('mongoose', () => {
10+
return {
11+
connect: jest.fn().mockResolvedValue({}),
12+
connection: {
13+
close: jest.fn().mockResolvedValue({}),
14+
},
15+
Schema: jest.fn().mockImplementation(() => ({})),
16+
model: jest.fn().mockReturnValue({
17+
find: jest.fn().mockReturnValue({
18+
sort: jest.fn().mockResolvedValue([])
19+
}),
20+
findById: jest.fn(),
21+
findByIdAndUpdate: jest.fn(),
22+
findByIdAndDelete: jest.fn()
23+
})
24+
};
25+
});
26+
27+
// Import mongoose after mocking it
28+
import mongoose from 'mongoose';
29+
*/
30+
31+
describe('MongoDbNoteRepository Error Handling Tests', () => {
32+
let repository;
33+
const url = 'mongodb://admin:password@localhost:27017';
34+
const dbName = 'notes_test';
35+
36+
beforeEach(() => {
37+
// Reset all mocks before each test
38+
jest.clearAllMocks();
39+
40+
repository = new MongoDbNoteRepository(url, dbName);
41+
});
42+
43+
describe('init() method error handling', () => {
44+
it('should handle mongoose connection failure', async () => {
45+
// Mock mongoose.connect to throw an error
46+
const mockError = new Error('Failed to connect to MongoDB');
47+
mongoose.connect = jest.fn().mockRejectedValue(mockError);
48+
49+
await expect(repository.init()).rejects.toThrow('Failed to connect to MongoDB');
50+
expect(mongoose.connect).toHaveBeenCalled();
51+
});
52+
53+
it('should handle schema creation failure', async () => {
54+
// Mock mongoose.Schema to throw an error
55+
mongoose.connect = jest.fn().mockResolvedValue({});
56+
mongoose.Schema = jest.fn().mockImplementation(() => {
57+
throw new Error('Failed to create schema');
58+
});
59+
60+
await expect(repository.init()).rejects.toThrow('Failed to create schema');
61+
expect(mongoose.connect).toHaveBeenCalled();
62+
});
63+
64+
it('should handle model creation failure', async () => {
65+
// Mock mongoose.model to throw an error
66+
mongoose.connect = jest.fn().mockResolvedValue({});
67+
mongoose.Schema = jest.fn().mockImplementation(() => ({}));
68+
mongoose.model = jest.fn().mockImplementation(() => {
69+
throw new Error('Failed to create model');
70+
});
71+
72+
await expect(repository.init()).rejects.toThrow('Failed to create model');
73+
expect(mongoose.connect).toHaveBeenCalled();
74+
expect(mongoose.Schema).toHaveBeenCalled();
75+
expect(mongoose.model).toHaveBeenCalled();
76+
});
77+
});
78+
79+
describe('Database connection failure handling', () => {
80+
beforeEach(() => {
81+
// Setup repository with a NoteModel that throws connection errors
82+
repository.NoteModel = {
83+
find: jest.fn().mockReturnValue({
84+
sort: jest.fn().mockRejectedValue(new Error('Connection failed'))
85+
}),
86+
findById: jest.fn().mockRejectedValue(new Error('Connection failed')),
87+
findByIdAndUpdate: jest.fn().mockRejectedValue(new Error('Connection failed')),
88+
findByIdAndDelete: jest.fn().mockRejectedValue(new Error('Connection failed'))
89+
};
90+
});
91+
92+
it('should handle connection failure in findAll()', async () => {
93+
await expect(repository.findAll()).rejects.toThrow('Connection failed');
94+
expect(repository.NoteModel.find).toHaveBeenCalled();
95+
});
96+
97+
it('should handle connection failure in findDeleted()', async () => {
98+
await expect(repository.findDeleted()).rejects.toThrow('Connection failed');
99+
expect(repository.NoteModel.find).toHaveBeenCalled();
100+
});
101+
102+
it('should handle connection failure in findAllIncludingDeleted()', async () => {
103+
await expect(repository.findAllIncludingDeleted()).rejects.toThrow('Connection failed');
104+
expect(repository.NoteModel.find).toHaveBeenCalled();
105+
});
106+
107+
it('should handle connection failure in findById()', async () => {
108+
await expect(repository.findById('123')).rejects.toThrow('Connection failed');
109+
expect(repository.NoteModel.findById).toHaveBeenCalledWith('123');
110+
});
111+
112+
it('should handle connection failure in update()', async () => {
113+
const noteData = { title: 'Test', content: 'Test' };
114+
await expect(repository.update('123', noteData)).rejects.toThrow('Connection failed');
115+
expect(repository.NoteModel.findByIdAndUpdate).toHaveBeenCalled();
116+
});
117+
118+
it('should handle connection failure in moveToRecycleBin()', async () => {
119+
await expect(repository.moveToRecycleBin('123')).rejects.toThrow('Connection failed');
120+
expect(repository.NoteModel.findByIdAndUpdate).toHaveBeenCalled();
121+
});
122+
123+
it('should handle connection failure in restore()', async () => {
124+
await expect(repository.restore('123')).rejects.toThrow('Connection failed');
125+
expect(repository.NoteModel.findByIdAndUpdate).toHaveBeenCalled();
126+
});
127+
128+
it('should handle connection failure in permanentDelete()', async () => {
129+
await expect(repository.permanentDelete('123')).rejects.toThrow('Connection failed');
130+
expect(repository.NoteModel.findByIdAndDelete).toHaveBeenCalledWith('123');
131+
});
132+
});
133+
134+
describe('create() method error handling', () => {
135+
it('should handle validation errors in create()', async () => {
136+
// Setup a NoteModel that throws validation error
137+
const mockError = new Error('Validation failed');
138+
mockError.name = 'ValidationError';
139+
140+
const mockSave = jest.fn().mockRejectedValue(mockError);
141+
142+
repository.NoteModel = jest.fn().mockImplementation(() => ({
143+
save: mockSave
144+
}));
145+
146+
await expect(repository.create({ title: '', content: '' })).rejects.toThrow('Validation failed');
147+
});
148+
149+
it('should handle connection errors in create()', async () => {
150+
// Setup a NoteModel that throws connection error
151+
const mockError = new Error('Connection failed');
152+
153+
const mockSave = jest.fn().mockRejectedValue(mockError);
154+
155+
repository.NoteModel = jest.fn().mockImplementation(() => ({
156+
save: mockSave
157+
}));
158+
159+
await expect(repository.create({ title: 'Test', content: 'Test' })).rejects.toThrow('Connection failed');
160+
});
161+
});
162+
});

jest.config.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ export default {
1414
coverageDirectory: "coverage",
1515
coverageThreshold: {
1616
global: {
17-
branches: 81,
18-
functions: 88,
19-
lines: 89,
20-
statements: 88
17+
branches: 86,
18+
functions: 92,
19+
lines: 94,
20+
statements: 93
2121
}
2222
},
2323
verbose: true

0 commit comments

Comments
 (0)