1+ import { GenericContainer } from 'testcontainers' ;
2+ import { CouchDbNoteRepository } from '../../src/db/couchdb-note-repository.js' ;
3+ import { Note } from '../../src/models/note.js' ;
4+
5+ describe ( 'CouchDbNoteRepository Integration Tests' , ( ) => {
6+ let container ;
7+ let repository ;
8+ const DB_NAME = 'notes_test' ;
9+
10+ // Helper function to wait for document availability
11+ async function waitForDocument ( id , maxAttempts = 5 , delay = 1000 ) {
12+ console . log ( `Waiting for document ${ id } to be available...` ) ;
13+ for ( let attempt = 0 ; attempt < maxAttempts ; attempt ++ ) {
14+ console . log ( `Attempt ${ attempt + 1 } /${ maxAttempts } to find document ${ id } ` ) ;
15+ try {
16+ const doc = await repository . findById ( id ) ;
17+ if ( doc ) {
18+ console . log ( `Document ${ id } found on attempt ${ attempt + 1 } :` , doc ) ;
19+ return doc ;
20+ }
21+ console . log ( `Document ${ id } not found on attempt ${ attempt + 1 } ` ) ;
22+ } catch ( error ) {
23+ console . error ( `Error finding document ${ id } on attempt ${ attempt + 1 } :` , error ) ;
24+ }
25+ await new Promise ( resolve => setTimeout ( resolve , delay ) ) ;
26+ }
27+ throw new Error ( `Document ${ id } not found after ${ maxAttempts } attempts` ) ;
28+ }
29+
30+ // Helper function to wait for document deletion
31+ async function waitForDocumentDeletion ( id , maxAttempts = 5 , delay = 3000 ) {
32+ console . log ( `Waiting for document ${ id } to be deleted...` ) ;
33+ for ( let attempt = 0 ; attempt < maxAttempts ; attempt ++ ) {
34+ console . log ( `Attempt ${ attempt + 1 } /${ maxAttempts } to check document ${ id } deletion` ) ;
35+ try {
36+ const doc = await repository . findById ( id ) ;
37+ if ( ! doc ) {
38+ console . log ( `Document ${ id } confirmed deleted on attempt ${ attempt + 1 } ` ) ;
39+ return true ;
40+ }
41+ console . log ( `Document ${ id } still exists on attempt ${ attempt + 1 } :` , doc ) ;
42+ } catch ( error ) {
43+ if ( error . statusCode === 404 ) {
44+ console . log ( `Document ${ id } confirmed deleted on attempt ${ attempt + 1 } ` ) ;
45+ return true ;
46+ }
47+ console . error ( `Error checking document ${ id } deletion on attempt ${ attempt + 1 } :` , error ) ;
48+ }
49+ console . log ( `Waiting ${ delay } ms before next attempt...` ) ;
50+ await new Promise ( resolve => setTimeout ( resolve , delay ) ) ;
51+ }
52+ throw new Error ( `Document ${ id } still exists after ${ maxAttempts } attempts` ) ;
53+ }
54+
55+ beforeAll ( async ( ) => {
56+ console . log ( 'Starting CouchDB container...' ) ;
57+ // Start CouchDB container
58+ container = await new GenericContainer ( 'couchdb:3.4.3' )
59+ . withExposedPorts ( 5984 )
60+ . withEnvironment ( {
61+ COUCHDB_USER : 'admin' ,
62+ COUCHDB_PASSWORD : 'password'
63+ } )
64+ . start ( ) ;
65+
66+ // Create repository instance
67+ const port = container . getMappedPort ( 5984 ) ;
68+ console . log ( `CouchDB container started on port ${ port } ` ) ;
69+ repository = new CouchDbNoteRepository (
70+ `http://admin:password@localhost:${ port } ` ,
71+ DB_NAME
72+ ) ;
73+
74+ // Initialize the database
75+ console . log ( 'Initializing database...' ) ;
76+ await repository . init ( ) ;
77+ console . log ( 'Database initialized successfully' ) ;
78+ } , 60000 ) ; // Increase timeout for container startup
79+
80+ afterAll ( async ( ) => {
81+ console . log ( 'Stopping CouchDB container...' ) ;
82+ if ( container ) {
83+ await container . stop ( ) ;
84+ console . log ( 'CouchDB container stopped' ) ;
85+ }
86+ } ) ;
87+
88+ beforeEach ( async ( ) => {
89+ console . log ( '\n--- Starting new test ---' ) ;
90+ // await repository.init();
91+
92+ try {
93+ const notes = await repository . findAll ( ) ;
94+ console . log ( 'Cleaning up notes:' , notes ) ;
95+ for ( const note of notes ) {
96+ console . log ( `Deleting note ${ note . id } ...` ) ;
97+ await repository . delete ( note . id ) ;
98+ await waitForDocumentDeletion ( note . id ) ;
99+ }
100+ console . log ( 'Cleanup completed' ) ;
101+ } catch ( error ) {
102+ console . error ( 'Error during cleanup:' , error ) ;
103+ }
104+ } ) ;
105+
106+ describe ( 'CRUD Operations' , ( ) => {
107+ it ( 'should create and retrieve a note' , async ( ) => {
108+ console . log ( '\nTest: should create and retrieve a note' ) ;
109+ const noteData = {
110+ title : 'Test Note' ,
111+ content : 'Test Content'
112+ } ;
113+
114+ console . log ( 'Creating note:' , noteData ) ;
115+ const createdNote = await repository . create ( noteData ) ;
116+ console . log ( 'Created note:' , createdNote ) ;
117+ expect ( createdNote ) . toBeInstanceOf ( Note ) ;
118+ expect ( createdNote . title ) . toBe ( noteData . title ) ;
119+ expect ( createdNote . content ) . toBe ( noteData . content ) ;
120+ expect ( createdNote . id ) . toBeDefined ( ) ;
121+
122+ console . log ( 'Retrieving created note...' ) ;
123+ const retrievedNote = await waitForDocument ( createdNote . id ) ;
124+ console . log ( 'Retrieved note:' , retrievedNote ) ;
125+ expect ( retrievedNote ) . toBeInstanceOf ( Note ) ;
126+ expect ( retrievedNote . id ) . toBe ( createdNote . id ) ;
127+ expect ( retrievedNote . title ) . toBe ( noteData . title ) ;
128+ expect ( retrievedNote . content ) . toBe ( noteData . content ) ;
129+ } ) ;
130+
131+ it ( 'should update a note' , async ( ) => {
132+ console . log ( '\nTest: should update a note' ) ;
133+ // Create initial note
134+ const note = await repository . create ( {
135+ title : 'Original Title' ,
136+ content : 'Original Content'
137+ } ) ;
138+ console . log ( 'Created initial note:' , note ) ;
139+
140+ console . log ( 'Verifying initial note...' ) ;
141+ const initialNote = await waitForDocument ( note . id ) ;
142+ console . log ( 'Initial note verified:' , initialNote ) ;
143+ expect ( initialNote ) . not . toBeNull ( ) ;
144+
145+ const updatedData = {
146+ title : 'Updated Title' ,
147+ content : 'Updated Content'
148+ } ;
149+
150+ console . log ( 'Updating note with:' , updatedData ) ;
151+ const updatedNote = await repository . update ( note . id , updatedData ) ;
152+ console . log ( 'Updated note:' , updatedNote ) ;
153+ expect ( updatedNote ) . toBeInstanceOf ( Note ) ;
154+ expect ( updatedNote . id ) . toBe ( note . id ) ;
155+ expect ( updatedNote . title ) . toBe ( updatedData . title ) ;
156+ expect ( updatedNote . content ) . toBe ( updatedData . content ) ;
157+
158+ console . log ( 'Verifying updated note...' ) ;
159+ const retrievedNote = await waitForDocument ( note . id ) ;
160+ console . log ( 'Retrieved updated note:' , retrievedNote ) ;
161+ expect ( retrievedNote ) . not . toBeNull ( ) ;
162+ expect ( retrievedNote . title ) . toBe ( updatedData . title ) ;
163+ expect ( retrievedNote . content ) . toBe ( updatedData . content ) ;
164+ } ) ;
165+
166+ it ( 'should delete a note' , async ( ) => {
167+ console . log ( '\nTest: should delete a note' ) ;
168+ const note = await repository . create ( {
169+ title : 'To Delete' ,
170+ content : 'Will be deleted'
171+ } ) ;
172+ console . log ( 'Created note to delete:' , note ) ;
173+
174+ console . log ( 'Verifying note exists...' ) ;
175+ const initialNote = await waitForDocument ( note . id ) ;
176+ console . log ( 'Initial note verified:' , initialNote ) ;
177+ expect ( initialNote ) . not . toBeNull ( ) ;
178+
179+ console . log ( 'Deleting note...' ) ;
180+ const deleteResult = await repository . delete ( note . id ) ;
181+ console . log ( 'Delete result:' , deleteResult ) ;
182+ expect ( deleteResult ) . toBe ( true ) ;
183+
184+ console . log ( 'Verifying note deletion...' ) ;
185+ await waitForDocumentDeletion ( note . id ) ;
186+ } ) ;
187+
188+ it ( 'should list all notes' , async ( ) => {
189+ console . log ( '\nTest: should list all notes' ) ;
190+ const notes = [
191+ { title : 'Note 1' , content : 'Content 1' } ,
192+ { title : 'Note 2' , content : 'Content 2' } ,
193+ { title : 'Note 3' , content : 'Content 3' }
194+ ] ;
195+
196+ // Create notes sequentially
197+ for ( const noteData of notes ) {
198+ console . log ( 'Creating note:' , noteData ) ;
199+ const created = await repository . create ( noteData ) ;
200+ console . log ( 'Created note:' , created ) ;
201+ await waitForDocument ( created . id ) ;
202+ }
203+
204+ // Wait for all notes to be available
205+ console . log ( 'Retrieving all notes...' ) ;
206+ const allNotes = await repository . findAll ( ) ;
207+ console . log ( 'All notes found:' , allNotes ) ;
208+
209+ expect ( allNotes ) . toHaveLength ( 3 ) ;
210+ expect ( allNotes . every ( note => note instanceof Note ) ) . toBe ( true ) ;
211+ expect ( allNotes . map ( note => note . title ) ) . toEqual (
212+ expect . arrayContaining ( notes . map ( note => note . title ) )
213+ ) ;
214+ } ) ;
215+ } ) ;
216+ } ) ;
0 commit comments