@@ -38,6 +38,262 @@ describe('createRouter – jobs (log)', () => {
3838 await tearDownRouters ( ) ;
3939 } ) ;
4040
41+ describe . each ( supportedDatabaseIds ) (
42+ 'GET /projects/:projectId/log (init phase) - %p' ,
43+ databaseId => {
44+ let client : Knex ;
45+ let x2aDatabase : X2ADatabaseService ;
46+ let project : { id : string } ;
47+
48+ beforeEach ( async ( ) => {
49+ const dbSetup = await createDatabase ( databaseId ) ;
50+ client = dbSetup . client ;
51+ x2aDatabase = X2ADatabaseService . create ( {
52+ logger : mockServices . logger . mock ( ) ,
53+ dbClient : client ,
54+ } ) ;
55+
56+ project = await createTestProject ( x2aDatabase ) ;
57+ } , LONG_TEST_TIMEOUT ) ;
58+
59+ it (
60+ 'should return logs from database for finished init job with success status' ,
61+ async ( ) => {
62+ await createTestJob ( x2aDatabase , {
63+ projectId : project . id ,
64+ moduleId : null ,
65+ phase : 'init' ,
66+ status : 'success' ,
67+ log : 'Init job logs from database' ,
68+ } ) ;
69+
70+ const app = await createApp ( client ) ;
71+
72+ const response = await request ( app )
73+ . get ( `/projects/${ project . id } /log` )
74+ . send ( ) ;
75+
76+ expect ( response . status ) . toBe ( 200 ) ;
77+ expect ( response . type ) . toBe ( 'text/plain' ) ;
78+ expect ( response . text ) . toBe ( 'Init job logs from database' ) ;
79+ } ,
80+ LONG_TEST_TIMEOUT ,
81+ ) ;
82+
83+ it (
84+ 'should return logs from database for finished init job with error status' ,
85+ async ( ) => {
86+ await createTestJob ( x2aDatabase , {
87+ projectId : project . id ,
88+ moduleId : null ,
89+ phase : 'init' ,
90+ status : 'error' ,
91+ log : 'Init failed: migration plan error' ,
92+ } ) ;
93+
94+ const app = await createApp ( client ) ;
95+
96+ const response = await request ( app )
97+ . get ( `/projects/${ project . id } /log` )
98+ . send ( ) ;
99+
100+ expect ( response . status ) . toBe ( 200 ) ;
101+ expect ( response . type ) . toBe ( 'text/plain' ) ;
102+ expect ( response . text ) . toBe ( 'Init failed: migration plan error' ) ;
103+ } ,
104+ LONG_TEST_TIMEOUT ,
105+ ) ;
106+
107+ it (
108+ 'should return logs from Kubernetes for running init job' ,
109+ async ( ) => {
110+ await createTestJob ( x2aDatabase , {
111+ projectId : project . id ,
112+ moduleId : null ,
113+ phase : 'init' ,
114+ status : 'running' ,
115+ k8sJobName : 'init-k8s-job' ,
116+ } ) ;
117+
118+ const mockGetJobLogs = jest
119+ . fn ( )
120+ . mockResolvedValue ( 'Init job logs from Kubernetes' ) ;
121+ const app = await createApp ( client , undefined , undefined , {
122+ getJobLogs : mockGetJobLogs ,
123+ } ) ;
124+
125+ const response = await request ( app )
126+ . get ( `/projects/${ project . id } /log` )
127+ . send ( ) ;
128+
129+ expect ( response . status ) . toBe ( 200 ) ;
130+ expect ( response . type ) . toBe ( 'text/plain' ) ;
131+ expect ( response . text ) . toBe ( 'Init job logs from Kubernetes' ) ;
132+ expect ( mockGetJobLogs ) . toHaveBeenCalledWith ( 'init-k8s-job' , false ) ;
133+ } ,
134+ LONG_TEST_TIMEOUT ,
135+ ) ;
136+
137+ it (
138+ 'should call getJobLogs with streaming=true when streaming query param is set' ,
139+ async ( ) => {
140+ await createTestJob ( x2aDatabase , {
141+ projectId : project . id ,
142+ moduleId : null ,
143+ phase : 'init' ,
144+ status : 'running' ,
145+ k8sJobName : 'init-k8s-job' ,
146+ } ) ;
147+
148+ const mockGetJobLogs = jest
149+ . fn ( )
150+ . mockResolvedValue ( 'Streaming init logs' ) ;
151+ const app = await createApp ( client , undefined , undefined , {
152+ getJobLogs : mockGetJobLogs ,
153+ } ) ;
154+
155+ const response = await request ( app )
156+ . get ( `/projects/${ project . id } /log?streaming=true` )
157+ . send ( ) ;
158+
159+ expect ( response . status ) . toBe ( 200 ) ;
160+ expect ( mockGetJobLogs ) . toHaveBeenCalledWith ( 'init-k8s-job' , true ) ;
161+ } ,
162+ LONG_TEST_TIMEOUT ,
163+ ) ;
164+
165+ it (
166+ 'should return empty logs when init job has no k8sJobName' ,
167+ async ( ) => {
168+ await createTestJob ( x2aDatabase , {
169+ projectId : project . id ,
170+ moduleId : null ,
171+ phase : 'init' ,
172+ status : 'pending' ,
173+ } ) ;
174+
175+ const app = await createApp ( client ) ;
176+
177+ const response = await request ( app )
178+ . get ( `/projects/${ project . id } /log` )
179+ . send ( ) ;
180+
181+ expect ( response . status ) . toBe ( 200 ) ;
182+ expect ( response . type ) . toBe ( 'text/plain' ) ;
183+ expect ( response . text ) . toBe ( '' ) ;
184+ } ,
185+ LONG_TEST_TIMEOUT ,
186+ ) ;
187+
188+ it (
189+ 'should return 404 when no init job found for project' ,
190+ async ( ) => {
191+ const app = await createApp ( client ) ;
192+
193+ const response = await request ( app )
194+ . get ( `/projects/${ project . id } /log` )
195+ . send ( ) ;
196+
197+ expect ( response . status ) . toBe ( 404 ) ;
198+ expect ( response . body ) . toMatchObject ( {
199+ error : {
200+ name : 'NotFoundError' ,
201+ message : expect . stringContaining ( 'No init job found' ) ,
202+ } ,
203+ } ) ;
204+ } ,
205+ LONG_TEST_TIMEOUT ,
206+ ) ;
207+
208+ it (
209+ 'should return 404 when project does not exist' ,
210+ async ( ) => {
211+ const app = await createApp ( client ) ;
212+
213+ const response = await request ( app )
214+ . get ( `/projects/${ nonExistentId } /log` )
215+ . send ( ) ;
216+
217+ expect ( response . status ) . toBe ( 404 ) ;
218+ expect ( response . body ) . toMatchObject ( {
219+ error : {
220+ name : 'NotFoundError' ,
221+ message : 'Project not found for the "user:default/mock" user.' ,
222+ } ,
223+ } ) ;
224+ } ,
225+ LONG_TEST_TIMEOUT ,
226+ ) ;
227+
228+ it (
229+ 'should return latest init job logs when multiple init jobs exist' ,
230+ async ( ) => {
231+ await createTestJob ( x2aDatabase , {
232+ projectId : project . id ,
233+ moduleId : null ,
234+ phase : 'init' ,
235+ status : 'success' ,
236+ log : 'Old init logs' ,
237+ } ) ;
238+
239+ await new Promise ( resolve => setTimeout ( resolve , 10 ) ) ;
240+
241+ await createTestJob ( x2aDatabase , {
242+ projectId : project . id ,
243+ moduleId : null ,
244+ phase : 'init' ,
245+ status : 'success' ,
246+ log : 'New init logs' ,
247+ } ) ;
248+
249+ const app = await createApp ( client ) ;
250+
251+ const response = await request ( app )
252+ . get ( `/projects/${ project . id } /log` )
253+ . send ( ) ;
254+
255+ expect ( response . status ) . toBe ( 200 ) ;
256+ expect ( response . text ) . toBe ( 'New init logs' ) ;
257+ } ,
258+ LONG_TEST_TIMEOUT ,
259+ ) ;
260+
261+ it (
262+ 'should return 403 when user has neither x2a.user nor x2a admin permissions' ,
263+ async ( ) => {
264+ await createTestJob ( x2aDatabase , {
265+ projectId : project . id ,
266+ moduleId : null ,
267+ phase : 'init' ,
268+ status : 'success' ,
269+ log : 'Init logs' ,
270+ } ) ;
271+
272+ const app = await createApp (
273+ client ,
274+ AuthorizeResult . DENY ,
275+ undefined ,
276+ undefined ,
277+ AuthorizeResult . DENY ,
278+ ) ;
279+
280+ const response = await request ( app )
281+ . get ( `/projects/${ project . id } /log` )
282+ . send ( ) ;
283+
284+ expect ( response . status ) . toBe ( 403 ) ;
285+ expect ( response . body ) . toMatchObject ( {
286+ error : {
287+ name : 'NotAllowedError' ,
288+ message : 'The user is not allowed to read projects.' ,
289+ } ,
290+ } ) ;
291+ } ,
292+ LONG_TEST_TIMEOUT ,
293+ ) ;
294+ } ,
295+ ) ;
296+
41297 describe . each ( supportedDatabaseIds ) (
42298 'GET /projects/:projectId/modules/:moduleId/log - %p' ,
43299 databaseId => {
@@ -56,7 +312,7 @@ describe('createRouter – jobs (log)', () => {
56312
57313 project = await createTestProject ( x2aDatabase ) ;
58314 module = await createTestModule ( x2aDatabase , project . id ) ;
59- } ) ;
315+ } , LONG_TEST_TIMEOUT ) ;
60316
61317 it (
62318 'should return logs from database for finished job with success status' ,
0 commit comments