abapgit-agent 1.0.0

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.
Files changed (53) hide show
  1. package/.abapGitAgent.example +11 -0
  2. package/API.md +271 -0
  3. package/CLAUDE.md +445 -0
  4. package/CLAUDE_MEM.md +88 -0
  5. package/ERROR_HANDLING.md +30 -0
  6. package/INSTALL.md +160 -0
  7. package/README.md +127 -0
  8. package/abap/CLAUDE.md +492 -0
  9. package/abap/package.devc.xml +10 -0
  10. package/abap/zcl_abgagt_agent.clas.abap +769 -0
  11. package/abap/zcl_abgagt_agent.clas.xml +15 -0
  12. package/abap/zcl_abgagt_cmd_factory.clas.abap +43 -0
  13. package/abap/zcl_abgagt_cmd_factory.clas.xml +15 -0
  14. package/abap/zcl_abgagt_command_inspect.clas.abap +192 -0
  15. package/abap/zcl_abgagt_command_inspect.clas.testclasses.abap +121 -0
  16. package/abap/zcl_abgagt_command_inspect.clas.xml +16 -0
  17. package/abap/zcl_abgagt_command_pull.clas.abap +80 -0
  18. package/abap/zcl_abgagt_command_pull.clas.testclasses.abap +87 -0
  19. package/abap/zcl_abgagt_command_pull.clas.xml +16 -0
  20. package/abap/zcl_abgagt_command_unit.clas.abap +297 -0
  21. package/abap/zcl_abgagt_command_unit.clas.xml +15 -0
  22. package/abap/zcl_abgagt_resource_health.clas.abap +25 -0
  23. package/abap/zcl_abgagt_resource_health.clas.xml +15 -0
  24. package/abap/zcl_abgagt_resource_inspect.clas.abap +62 -0
  25. package/abap/zcl_abgagt_resource_inspect.clas.xml +15 -0
  26. package/abap/zcl_abgagt_resource_pull.clas.abap +71 -0
  27. package/abap/zcl_abgagt_resource_pull.clas.xml +15 -0
  28. package/abap/zcl_abgagt_resource_unit.clas.abap +64 -0
  29. package/abap/zcl_abgagt_resource_unit.clas.xml +15 -0
  30. package/abap/zcl_abgagt_rest_handler.clas.abap +27 -0
  31. package/abap/zcl_abgagt_rest_handler.clas.xml +15 -0
  32. package/abap/zcl_abgagt_util.clas.abap +93 -0
  33. package/abap/zcl_abgagt_util.clas.testclasses.abap +84 -0
  34. package/abap/zcl_abgagt_util.clas.xml +16 -0
  35. package/abap/zif_abgagt_agent.intf.abap +134 -0
  36. package/abap/zif_abgagt_agent.intf.xml +15 -0
  37. package/abap/zif_abgagt_cmd_factory.intf.abap +7 -0
  38. package/abap/zif_abgagt_cmd_factory.intf.xml +15 -0
  39. package/abap/zif_abgagt_command.intf.abap +21 -0
  40. package/abap/zif_abgagt_command.intf.xml +15 -0
  41. package/abap/zif_abgagt_util.intf.abap +28 -0
  42. package/abap/zif_abgagt_util.intf.xml +15 -0
  43. package/bin/abapgit-agent +902 -0
  44. package/img/claude.png +0 -0
  45. package/package.json +31 -0
  46. package/scripts/claude-integration.js +351 -0
  47. package/scripts/test-integration.js +139 -0
  48. package/src/abap-client.js +314 -0
  49. package/src/agent.js +119 -0
  50. package/src/config.js +66 -0
  51. package/src/index.js +48 -0
  52. package/src/logger.js +39 -0
  53. package/src/server.js +116 -0
@@ -0,0 +1,769 @@
1
+ " TODO: Implement detailed syntax error parsing
2
+ " When a syntax error occurs, the log shows the affected object name
3
+ " but not the specific line/column. For better error reporting:
4
+ " - Parse the error message to extract object info
5
+ " - For syntax errors, query SEPSA or TRINT_OBJECT_LOG for details
6
+ " - Return structured error with line numbers and fix suggestions
7
+
8
+ CLASS zcl_abgagt_agent DEFINITION PUBLIC FINAL CREATE PUBLIC.
9
+
10
+ PUBLIC SECTION.
11
+ INTERFACES: zif_abgagt_agent.
12
+
13
+ METHODS: get_version RETURNING VALUE(rv_version) TYPE string.
14
+ METHODS: parse_file_to_object
15
+ IMPORTING
16
+ iv_file TYPE string
17
+ EXPORTING
18
+ ev_obj_type TYPE string
19
+ ev_obj_name TYPE string.
20
+
21
+ PROTECTED SECTION.
22
+
23
+ " Local type for item signature (matches abapGit structure)
24
+ TYPES: BEGIN OF ty_item_signature,
25
+ obj_type TYPE tadir-object,
26
+ obj_name TYPE tadir-obj_name,
27
+ devclass TYPE devclass,
28
+ END OF ty_item_signature.
29
+
30
+ DATA: mo_repo TYPE REF TO zif_abapgit_repo.
31
+
32
+ METHODS configure_credentials
33
+ IMPORTING
34
+ iv_url TYPE string
35
+ iv_username TYPE string
36
+ iv_password TYPE string
37
+ RAISING zcx_abapgit_exception.
38
+
39
+ METHODS prepare_deserialize_checks
40
+ IMPORTING
41
+ it_files TYPE string_table OPTIONAL
42
+ iv_transport_request TYPE string OPTIONAL
43
+ RETURNING
44
+ VALUE(rs_checks) TYPE zif_abapgit_definitions=>ty_deserialize_checks
45
+ RAISING zcx_abapgit_exception.
46
+
47
+ METHODS check_log_for_errors
48
+ RETURNING
49
+ VALUE(rv_has_error) TYPE abap_bool.
50
+
51
+ METHODS get_log_detail
52
+ RETURNING
53
+ VALUE(rv_detail) TYPE string.
54
+
55
+ METHODS get_object_lists
56
+ RETURNING
57
+ VALUE(rs_result) TYPE zif_abgagt_agent=>ty_result.
58
+
59
+ METHODS handle_exception
60
+ IMPORTING
61
+ ix_exception TYPE REF TO cx_root
62
+ RETURNING
63
+ VALUE(rs_result) TYPE zif_abgagt_agent=>ty_result.
64
+
65
+ METHODS get_test_classes
66
+ IMPORTING
67
+ iv_package TYPE devclass OPTIONAL
68
+ it_objects TYPE zif_abgagt_agent=>ty_object_keys OPTIONAL
69
+ RETURNING
70
+ VALUE(rt_classes) TYPE zif_abgagt_agent=>ty_object_keys.
71
+
72
+ METHODS run_aunit_tests
73
+ IMPORTING
74
+ it_classes TYPE zif_abgagt_agent=>ty_object_keys
75
+ RETURNING
76
+ VALUE(rt_results) TYPE zif_abgagt_agent=>ty_test_results.
77
+
78
+ METHODS count_results
79
+ IMPORTING
80
+ it_results TYPE zif_abgagt_agent=>ty_test_results
81
+ CHANGING
82
+ rs_stats TYPE zif_abgagt_agent=>ty_unit_result.
83
+
84
+ ENDCLASS.
85
+
86
+ CLASS zcl_abgagt_agent IMPLEMENTATION.
87
+
88
+ METHOD zif_abgagt_agent~pull.
89
+ DATA: lv_job_id TYPE string.
90
+ lv_job_id = |{ sy-uname }{ sy-datum }{ sy-uzeit }|.
91
+ rs_result-job_id = lv_job_id.
92
+ rs_result-success = abap_false.
93
+ rs_result-transport_request = iv_transport_request.
94
+ GET TIME STAMP FIELD rs_result-started_at.
95
+
96
+ IF iv_url IS INITIAL.
97
+ rs_result-message = 'URL is required'.
98
+ RETURN.
99
+ ENDIF.
100
+
101
+ TRY.
102
+ IF iv_username IS NOT INITIAL AND iv_password IS NOT INITIAL.
103
+ configure_credentials(
104
+ iv_url = iv_url
105
+ iv_username = iv_username
106
+ iv_password = iv_password ).
107
+ ENDIF.
108
+
109
+ DATA: li_repo TYPE REF TO zif_abapgit_repo.
110
+ zcl_abapgit_repo_srv=>get_instance( )->get_repo_from_url(
111
+ EXPORTING iv_url = iv_url
112
+ IMPORTING ei_repo = li_repo ).
113
+ mo_repo = li_repo.
114
+
115
+ IF mo_repo IS BOUND.
116
+ mo_repo->refresh( ).
117
+
118
+ DATA(ls_checks) = prepare_deserialize_checks(
119
+ it_files = it_files
120
+ iv_transport_request = iv_transport_request ).
121
+
122
+ mo_repo->create_new_log( ).
123
+
124
+ mo_repo->deserialize(
125
+ is_checks = ls_checks
126
+ ii_log = mo_repo->get_log( ) ).
127
+
128
+ " Check the abapGit log for errors and extract object lists
129
+ DATA(lv_has_error) = check_log_for_errors( ).
130
+ DATA(lv_error_detail) = get_log_detail( ).
131
+
132
+ " Extract activated and failed objects from the log
133
+ DATA(ls_obj_result) = get_object_lists( ).
134
+
135
+ rs_result-log_messages = ls_obj_result-log_messages.
136
+ rs_result-activated_objects = ls_obj_result-activated_objects.
137
+ rs_result-failed_objects = ls_obj_result-failed_objects.
138
+
139
+ " Count objects
140
+ rs_result-activated_count = lines( rs_result-activated_objects ).
141
+ rs_result-failed_count = lines( rs_result-failed_objects ).
142
+
143
+ GET TIME STAMP FIELD rs_result-finished_at.
144
+
145
+ IF lv_has_error = abap_true.
146
+ rs_result-message = 'Pull completed with errors'.
147
+ rs_result-error_detail = lv_error_detail.
148
+ ELSE.
149
+ rs_result-success = abap_true.
150
+ rs_result-message = 'Pull completed successfully'.
151
+ ENDIF.
152
+ ELSE.
153
+ rs_result-message = |Repository not found: { iv_url }|.
154
+ GET TIME STAMP FIELD rs_result-finished_at.
155
+ ENDIF.
156
+
157
+ CATCH zcx_abapgit_exception INTO DATA(lx_git).
158
+ rs_result = handle_exception( ix_exception = lx_git ).
159
+ GET TIME STAMP FIELD rs_result-finished_at.
160
+ CATCH cx_root INTO DATA(lx_error).
161
+ rs_result = handle_exception( ix_exception = lx_error ).
162
+ GET TIME STAMP FIELD rs_result-finished_at.
163
+ ENDTRY.
164
+
165
+ ENDMETHOD.
166
+
167
+ METHOD zif_abgagt_agent~get_repo_status.
168
+ DATA: li_repo TYPE REF TO zif_abapgit_repo.
169
+ TRY.
170
+ zcl_abapgit_repo_srv=>get_instance( )->get_repo_from_url(
171
+ EXPORTING iv_url = iv_url
172
+ IMPORTING ei_repo = li_repo ).
173
+ CATCH zcx_abapgit_exception.
174
+ rv_status = 'Not found'.
175
+ RETURN.
176
+ ENDTRY.
177
+
178
+ IF li_repo IS BOUND.
179
+ rv_status = 'Found'.
180
+ ELSE.
181
+ rv_status = 'Not found'.
182
+ ENDIF.
183
+ ENDMETHOD.
184
+
185
+ METHOD zif_abgagt_agent~inspect.
186
+ DATA ls_error LIKE LINE OF rs_result-errors.
187
+ DATA lv_obj_type TYPE string.
188
+ DATA lv_obj_name TYPE string.
189
+
190
+ " Convert file name to object type and name
191
+ parse_file_to_object(
192
+ EXPORTING iv_file = iv_file
193
+ IMPORTING ev_obj_type = lv_obj_type
194
+ ev_obj_name = lv_obj_name ).
195
+
196
+ rs_result-success = abap_true.
197
+ rs_result-object_type = lv_obj_type.
198
+ rs_result-object_name = lv_obj_name.
199
+
200
+ TRY.
201
+ " Check if object exists in TADIR
202
+ DATA lv_devclass TYPE devclass.
203
+ SELECT SINGLE devclass FROM tadir
204
+ INTO lv_devclass
205
+ WHERE pgmid = 'R3TR'
206
+ AND object = lv_obj_type
207
+ AND obj_name = lv_obj_name.
208
+
209
+ IF lv_devclass IS INITIAL.
210
+ rs_result-success = abap_false.
211
+ rs_result-error_count = 1.
212
+ ls_error-line = '1'.
213
+ ls_error-column = '1'.
214
+ ls_error-text = |Object { lv_obj_type } { lv_obj_name } does not exist|.
215
+ ls_error-word = ''.
216
+ APPEND ls_error TO rs_result-errors.
217
+ RETURN.
218
+ ENDIF.
219
+
220
+ " Create object structure for the specific object
221
+ DATA ls_obj TYPE scir_objs.
222
+ ls_obj-objtype = lv_obj_type.
223
+ ls_obj-objname = lv_obj_name.
224
+
225
+ DATA lt_objects TYPE scit_objs.
226
+ APPEND ls_obj TO lt_objects.
227
+
228
+ " Create unique name for inspection
229
+ DATA lv_name TYPE sci_objs.
230
+ CONCATENATE 'SYNT_' sy-uname sy-datum sy-uzeit INTO lv_name.
231
+
232
+ " Create object set
233
+ DATA(lo_objset) = cl_ci_objectset=>save_from_list(
234
+ p_name = lv_name
235
+ p_objects = lt_objects ).
236
+
237
+ " Get check variant for syntax check
238
+ DATA(lo_variant) = cl_ci_checkvariant=>get_ref(
239
+ p_user = ''
240
+ p_name = 'SYNTAX_CHECK' ).
241
+
242
+ " Create inspection
243
+ cl_ci_inspection=>create(
244
+ EXPORTING
245
+ p_user = sy-uname
246
+ p_name = lv_name
247
+ RECEIVING
248
+ p_ref = DATA(lo_inspection) ).
249
+
250
+ " Set inspection with object set and variant
251
+ lo_inspection->set(
252
+ EXPORTING
253
+ p_chkv = lo_variant
254
+ p_objs = lo_objset ).
255
+
256
+ " Save inspection
257
+ lo_inspection->save( ).
258
+
259
+ " Run inspection via RFC (for async CLI execution)
260
+ lo_inspection->run(
261
+ EXPORTING
262
+ p_howtorun = 'R'
263
+ EXCEPTIONS
264
+ invalid_check_version = 1
265
+ OTHERS = 2 ).
266
+
267
+ " Get results
268
+ DATA lt_list TYPE scit_alvlist.
269
+ lo_inspection->plain_list( IMPORTING p_list = lt_list ).
270
+
271
+ " Parse results
272
+ LOOP AT lt_list INTO DATA(ls_list).
273
+ " Only include errors for the requested object
274
+ IF ls_list-objtype = lv_obj_type AND ls_list-objname = lv_obj_name.
275
+ CLEAR ls_error.
276
+ ls_error-line = ls_list-line.
277
+ ls_error-column = ls_list-col.
278
+ ls_error-text = ls_list-text.
279
+ ls_error-word = ls_list-code.
280
+ APPEND ls_error TO rs_result-errors.
281
+ ENDIF.
282
+ ENDLOOP.
283
+
284
+ " Cleanup
285
+ lo_inspection->delete(
286
+ EXCEPTIONS
287
+ locked = 1
288
+ error_in_enqueue = 2
289
+ not_authorized = 3
290
+ exceptn_appl_exists = 4
291
+ OTHERS = 5 ).
292
+
293
+ lo_objset->delete(
294
+ EXCEPTIONS
295
+ exists_in_insp = 1
296
+ locked = 2
297
+ error_in_enqueue = 3
298
+ not_authorized = 4
299
+ exists_in_objs = 5
300
+ OTHERS = 6 ).
301
+
302
+ rs_result-error_count = lines( rs_result-errors ).
303
+ IF rs_result-error_count > 0.
304
+ rs_result-success = abap_false.
305
+ ENDIF.
306
+
307
+ CATCH cx_root INTO DATA(lx_error).
308
+ rs_result-success = abap_false.
309
+ rs_result-error_count = 1.
310
+ ls_error-line = '1'.
311
+ ls_error-column = '1'.
312
+ ls_error-text = lx_error->get_text( ).
313
+ ls_error-word = ''.
314
+ APPEND ls_error TO rs_result-errors.
315
+ ENDTRY.
316
+ ENDMETHOD.
317
+
318
+ METHOD zif_abgagt_agent~run_tests.
319
+ " Initialize result
320
+ rs_result-success = abap_false.
321
+ rs_result-test_count = 0.
322
+ rs_result-passed_count = 0.
323
+ rs_result-failed_count = 0.
324
+
325
+ " Get test classes to run
326
+ DATA(lt_test_classes) = get_test_classes(
327
+ iv_package = iv_package
328
+ it_objects = it_objects ).
329
+
330
+ IF lt_test_classes IS INITIAL.
331
+ rs_result-message = 'No test classes found'.
332
+ RETURN.
333
+ ENDIF.
334
+
335
+ rs_result-message = |Found { lines( lt_test_classes ) } test class(es)|.
336
+
337
+ " Run AUnit tests
338
+ rs_result-results = run_aunit_tests( lt_test_classes ).
339
+
340
+ IF rs_result-results IS INITIAL.
341
+ rs_result-message = |No test results - { rs_result-message }|.
342
+ RETURN.
343
+ ENDIF.
344
+
345
+ " Count results
346
+ count_results(
347
+ EXPORTING it_results = rs_result-results
348
+ CHANGING rs_stats = rs_result ).
349
+
350
+ IF rs_result-failed_count = 0.
351
+ rs_result-success = abap_true.
352
+ rs_result-message = |All { rs_result-test_count } tests passed|.
353
+ ELSE.
354
+ rs_result-message = |{ rs_result-failed_count } of { rs_result-test_count } tests failed|.
355
+ ENDIF.
356
+ ENDMETHOD.
357
+
358
+ METHOD configure_credentials.
359
+ zcl_abapgit_persist_factory=>get_user( )->set_repo_git_user_name(
360
+ iv_url = iv_url iv_username = iv_username ).
361
+ zcl_abapgit_persist_factory=>get_user( )->set_repo_login(
362
+ iv_url = iv_url iv_login = iv_username ).
363
+ zcl_abapgit_login_manager=>set_basic(
364
+ iv_uri = iv_url
365
+ iv_username = iv_username
366
+ iv_password = iv_password ).
367
+ ENDMETHOD.
368
+
369
+ METHOD parse_file_to_object.
370
+ " Parse file path to extract obj_type and obj_name
371
+ " Example: "zcl_my_class.clas.abap" -> CLAS, ZCL_MY_CLASS
372
+ " Example: "src/zcl_my_class.clas.abap" -> CLAS, ZCL_MY_CLASS
373
+
374
+ DATA lv_upper TYPE string.
375
+ lv_upper = iv_file.
376
+ TRANSLATE lv_upper TO UPPER CASE.
377
+
378
+ " Split filename by '.' to get parts
379
+ DATA lt_parts TYPE TABLE OF string.
380
+ SPLIT lv_upper AT '.' INTO TABLE lt_parts.
381
+ DATA lv_part_count TYPE i.
382
+ lv_part_count = lines( lt_parts ).
383
+
384
+ IF lv_part_count < 3.
385
+ RETURN.
386
+ ENDIF.
387
+
388
+ " Last part should be 'ABAP' for verification
389
+ READ TABLE lt_parts INDEX lv_part_count INTO DATA(lv_last).
390
+ IF lv_last <> 'ABAP'.
391
+ RETURN.
392
+ ENDIF.
393
+
394
+ " First part is obj_name (may contain path), second part is obj_type
395
+ DATA lv_obj_name TYPE string.
396
+ DATA lv_obj_type_raw TYPE string.
397
+ READ TABLE lt_parts INDEX 1 INTO lv_obj_name.
398
+ READ TABLE lt_parts INDEX 2 INTO lv_obj_type_raw.
399
+
400
+ " Convert file extension to object type
401
+ IF lv_obj_type_raw = 'CLASS'.
402
+ ev_obj_type = 'CLAS'.
403
+ ELSE.
404
+ ev_obj_type = lv_obj_type_raw.
405
+ ENDIF.
406
+
407
+ " Extract file name from obj_name (remove path prefix)
408
+ DATA lv_len TYPE i.
409
+ lv_len = strlen( lv_obj_name ).
410
+ DATA lv_offs TYPE i.
411
+ lv_offs = find( val = reverse( lv_obj_name ) sub = '/' ).
412
+ IF lv_offs > 0.
413
+ lv_offs = lv_len - lv_offs - 1.
414
+ lv_obj_name = lv_obj_name+lv_offs.
415
+ ENDIF.
416
+
417
+ " Remove leading '/' if present
418
+ IF lv_obj_name(1) = '/'.
419
+ lv_obj_name = lv_obj_name+1.
420
+ ENDIF.
421
+
422
+ ev_obj_name = lv_obj_name.
423
+ ENDMETHOD.
424
+
425
+ METHOD prepare_deserialize_checks.
426
+ rs_checks = mo_repo->deserialize_checks( ).
427
+
428
+ " Set transport request if provided
429
+ IF iv_transport_request IS NOT INITIAL.
430
+ rs_checks-transport-transport = iv_transport_request.
431
+ ENDIF.
432
+
433
+ " If specific files requested, build lookup table of (obj_type, obj_name)
434
+ DATA lt_valid_files TYPE HASHED TABLE OF zif_abapgit_definitions=>ty_item_signature
435
+ WITH UNIQUE KEY obj_type obj_name.
436
+ IF it_files IS SUPPLIED AND lines( it_files ) > 0.
437
+ LOOP AT it_files INTO DATA(lv_file).
438
+ DATA lv_obj_type TYPE string.
439
+ DATA lv_obj_name TYPE string.
440
+ parse_file_to_object(
441
+ EXPORTING iv_file = lv_file
442
+ IMPORTING ev_obj_type = lv_obj_type
443
+ ev_obj_name = lv_obj_name ).
444
+
445
+ IF lv_obj_type IS NOT INITIAL AND lv_obj_name IS NOT INITIAL.
446
+ DATA ls_sig TYPE zif_abapgit_definitions=>ty_item_signature.
447
+ ls_sig-obj_type = lv_obj_type.
448
+ ls_sig-obj_name = lv_obj_name.
449
+ INSERT ls_sig INTO TABLE lt_valid_files.
450
+ ENDIF.
451
+ ENDLOOP.
452
+ ENDIF.
453
+
454
+ " Set decision for each file
455
+ LOOP AT rs_checks-overwrite INTO DATA(ls_overwrite).
456
+ IF it_files IS SUPPLIED AND lines( it_files ) > 0.
457
+ " Check if file is in valid files list
458
+ READ TABLE lt_valid_files WITH TABLE KEY obj_type = ls_overwrite-obj_type
459
+ obj_name = ls_overwrite-obj_name
460
+ TRANSPORTING NO FIELDS.
461
+ IF sy-subrc = 0.
462
+ ls_overwrite-decision = zif_abapgit_definitions=>c_yes.
463
+ ELSE.
464
+ ls_overwrite-decision = zif_abapgit_definitions=>c_no.
465
+ ENDIF.
466
+ ELSE.
467
+ " No files specified - deserialize all
468
+ ls_overwrite-decision = zif_abapgit_definitions=>c_yes.
469
+ ENDIF.
470
+ MODIFY rs_checks-overwrite FROM ls_overwrite.
471
+ ENDLOOP.
472
+
473
+ DATA: lo_settings TYPE REF TO zcl_abapgit_settings.
474
+ lo_settings = zcl_abapgit_persist_factory=>get_settings( )->read( ).
475
+ lo_settings->set_activate_wo_popup( abap_true ).
476
+ ENDMETHOD.
477
+
478
+ METHOD check_log_for_errors.
479
+ DATA: lo_log TYPE REF TO zif_abapgit_log.
480
+
481
+ rv_has_error = abap_false.
482
+
483
+ lo_log = mo_repo->get_log( ).
484
+ IF lo_log IS BOUND.
485
+ DATA(lv_status) = lo_log->get_status( ).
486
+ IF lv_status = zif_abapgit_log=>c_status-error.
487
+ rv_has_error = abap_true.
488
+ ENDIF.
489
+ ENDIF.
490
+ ENDMETHOD.
491
+
492
+ METHOD get_log_detail.
493
+ " Extract detailed log messages including type, id, number, text, obj_type, obj_name, exception
494
+ DATA: lo_log TYPE REF TO zif_abapgit_log.
495
+
496
+ rv_detail = ''.
497
+
498
+ lo_log = mo_repo->get_log( ).
499
+ IF lo_log IS BOUND.
500
+ DATA: lt_messages TYPE zif_abapgit_log=>ty_log_outs.
501
+ DATA: ls_msg TYPE zif_abapgit_log=>ty_log_out.
502
+ lt_messages = lo_log->get_messages( ).
503
+
504
+ DATA lv_first TYPE abap_bool VALUE abap_false.
505
+
506
+ LOOP AT lt_messages INTO ls_msg.
507
+ IF ls_msg-type = 'E' OR ls_msg-type = 'A' OR ls_msg-type = 'W'.
508
+ DATA: lv_msg TYPE string.
509
+ IF ls_msg-obj_type IS NOT INITIAL AND ls_msg-obj_name IS NOT INITIAL.
510
+ lv_msg = |{ ls_msg-obj_type } { ls_msg-obj_name }: { ls_msg-text }|.
511
+ ELSE.
512
+ lv_msg = ls_msg-text.
513
+ ENDIF.
514
+ " Add exception text if available
515
+ IF ls_msg-exception IS BOUND.
516
+ lv_msg = |{ lv_msg }\nException: { ls_msg-exception->get_text( ) }|.
517
+ ENDIF.
518
+ IF lv_first = abap_false.
519
+ rv_detail = lv_msg.
520
+ lv_first = abap_true.
521
+ ELSE.
522
+ rv_detail = |{ rv_detail }&&&{ lv_msg }|.
523
+ ENDIF.
524
+ ENDIF.
525
+ ENDLOOP.
526
+
527
+ " Replace marker with newline for display
528
+ IF rv_detail IS NOT INITIAL.
529
+ rv_detail = replace( val = rv_detail sub = '&&&' with = cl_abap_char_utilities=>newline ).
530
+ rv_detail = |Error Details:{ cl_abap_char_utilities=>newline }{ rv_detail }|.
531
+ ENDIF.
532
+ ENDIF.
533
+ ENDMETHOD.
534
+
535
+ METHOD get_object_lists.
536
+ " Extract activated and failed objects from the log with full details
537
+ DATA: lo_log TYPE REF TO zif_abapgit_log.
538
+ DATA: lv_key TYPE string.
539
+
540
+ CLEAR: rs_result-log_messages, rs_result-activated_objects, rs_result-failed_objects.
541
+
542
+ lo_log = mo_repo->get_log( ).
543
+ IF lo_log IS BOUND.
544
+ DATA: lt_messages TYPE zif_abapgit_log=>ty_log_outs.
545
+ DATA: ls_msg TYPE zif_abapgit_log=>ty_log_out.
546
+ lt_messages = lo_log->get_messages( ).
547
+
548
+ LOOP AT lt_messages INTO ls_msg.
549
+ DATA: ls_object TYPE zif_abgagt_agent=>ty_object.
550
+ ls_object-type = ls_msg-type.
551
+ ls_object-id = ls_msg-id.
552
+ ls_object-number = ls_msg-number.
553
+ ls_object-text = ls_msg-text.
554
+ ls_object-obj_type = ls_msg-obj_type.
555
+ ls_object-obj_name = ls_msg-obj_name.
556
+
557
+ " Exception is a REF, need to convert to string
558
+ " Also append exception text to the message for better error reporting
559
+ IF ls_msg-exception IS BOUND.
560
+ DATA: lv_exc_text TYPE string.
561
+ lv_exc_text = ls_msg-exception->get_text( ).
562
+ ls_object-exception = lv_exc_text.
563
+ " Append exception text to the main text if it's not already there
564
+ IF lv_exc_text IS NOT INITIAL AND ls_msg-text NA lv_exc_text.
565
+ ls_object-text = |{ ls_msg-text }\nException: { lv_exc_text }|.
566
+ ENDIF.
567
+ ENDIF.
568
+
569
+ " Add all messages to log_messages table
570
+ APPEND ls_object TO rs_result-log_messages.
571
+
572
+ " Success messages (type 'S') - add to activated objects if unique
573
+ IF ls_msg-type = 'S' AND ls_msg-obj_type IS NOT INITIAL AND ls_msg-obj_name IS NOT INITIAL.
574
+ " Check for duplicates
575
+ lv_key = |{ ls_msg-obj_type }{ ls_msg-obj_name }|.
576
+ READ TABLE rs_result-activated_objects WITH KEY obj_type = ls_msg-obj_type
577
+ obj_name = ls_msg-obj_name
578
+ TRANSPORTING NO FIELDS.
579
+ IF sy-subrc <> 0.
580
+ APPEND ls_object TO rs_result-activated_objects.
581
+ ENDIF.
582
+ ENDIF.
583
+
584
+ " Error/Abort/Warning messages - add to failed objects
585
+ IF ls_msg-type = 'E' OR ls_msg-type = 'A' OR ls_msg-type = 'W'.
586
+ APPEND ls_object TO rs_result-failed_objects.
587
+ ENDIF.
588
+ ENDLOOP.
589
+ ENDIF.
590
+ ENDMETHOD.
591
+
592
+ METHOD handle_exception.
593
+ rs_result-success = abap_false.
594
+ rs_result-message = ix_exception->get_text( ).
595
+
596
+ DATA: lx_prev TYPE REF TO cx_root.
597
+ lx_prev = ix_exception->previous.
598
+ WHILE lx_prev IS BOUND.
599
+ DATA: lv_msg TYPE string.
600
+ lv_msg = lx_prev->get_text( ).
601
+ IF lv_msg IS NOT INITIAL.
602
+ rs_result-error_detail = rs_result-error_detail && '\n -> ' && lv_msg.
603
+ ENDIF.
604
+ lx_prev = lx_prev->previous.
605
+ ENDWHILE.
606
+ ENDMETHOD.
607
+
608
+ METHOD get_test_classes.
609
+ DATA: lt_tadir TYPE TABLE OF tadir.
610
+
611
+ FIELD-SYMBOLS: <ls_tadir> LIKE LINE OF lt_tadir.
612
+
613
+ " Get all test classes from package
614
+ IF iv_package IS NOT INITIAL.
615
+ SELECT * FROM tadir
616
+ INTO TABLE lt_tadir
617
+ WHERE devclass = iv_package
618
+ AND object = 'CLAS'
619
+ AND obj_name LIKE '%_TEST'
620
+ ORDER BY obj_name.
621
+
622
+ LOOP AT lt_tadir ASSIGNING <ls_tadir>.
623
+ APPEND INITIAL LINE TO rt_classes ASSIGNING FIELD-SYMBOL(<ls_class>).
624
+ <ls_class>-object_type = 'CLAS'.
625
+ <ls_class>-object_name = <ls_tadir>-obj_name.
626
+ ENDLOOP.
627
+ ENDIF.
628
+
629
+ " Add specified objects
630
+ IF it_objects IS NOT INITIAL.
631
+ LOOP AT it_objects ASSIGNING FIELD-SYMBOL(<ls_obj>).
632
+ APPEND INITIAL LINE TO rt_classes ASSIGNING <ls_class>.
633
+ <ls_class>-object_type = <ls_obj>-object_type.
634
+ <ls_class>-object_name = <ls_obj>-object_name.
635
+ ENDLOOP.
636
+ ENDIF.
637
+
638
+ " Remove duplicates
639
+ SORT rt_classes BY object_name.
640
+ DELETE ADJACENT DUPLICATES FROM rt_classes COMPARING object_name.
641
+
642
+ ENDMETHOD.
643
+
644
+ METHOD run_aunit_tests.
645
+ " Run unit tests using CL_SUT_AUNIT_RUNNER
646
+ DATA: lo_runner TYPE REF TO cl_sut_aunit_runner.
647
+
648
+ " Create runner using s_create
649
+ cl_sut_aunit_runner=>s_create(
650
+ EXPORTING
651
+ p_cov = abap_false
652
+ i_flg_api = abap_true
653
+ RECEIVING
654
+ r_ref_runner = lo_runner ).
655
+
656
+ " Configure runner
657
+ lo_runner->p_disp = abap_false. " Don't show results UI
658
+ lo_runner->p_save = abap_true. " Save values
659
+ lo_runner->p_runmd = 'E'. " Execute only (not plan)
660
+
661
+ " Set test classes
662
+ DATA lv_test_classes TYPE string.
663
+ LOOP AT it_classes ASSIGNING FIELD-SYMBOL(<ls_class>).
664
+ IF lv_test_classes IS INITIAL.
665
+ lv_test_classes = <ls_class>-object_name.
666
+ ELSE.
667
+ lv_test_classes = |{ lv_test_classes } { <ls_class>-object_name }|.
668
+ ENDIF.
669
+ ENDLOOP.
670
+
671
+ " Run tests
672
+ TRY.
673
+ lo_runner->run(
674
+ EXPORTING
675
+ i_flg_select_only = abap_false
676
+ EXCEPTIONS
677
+ OTHERS = 1 ).
678
+ CATCH cx_sut_error.
679
+ RETURN.
680
+ ENDTRY.
681
+
682
+ IF sy-subrc <> 0.
683
+ RETURN.
684
+ ENDIF.
685
+
686
+ " Get results from tab_objects
687
+ DATA(lt_objects) = lo_runner->tab_objects.
688
+
689
+ IF lt_objects IS INITIAL.
690
+ RETURN.
691
+ ENDIF.
692
+
693
+ " Process results - structure: OBJECT-TAB_TESTCLASSES-TAB_METHODS
694
+ LOOP AT lt_objects ASSIGNING FIELD-SYMBOL(<ls_object>).
695
+ DATA(lv_obj_name) = <ls_object>-obj_name.
696
+
697
+ " Loop through test classes
698
+ LOOP AT <ls_object>-tab_testclasses ASSIGNING FIELD-SYMBOL(<ls_tcl>).
699
+ DATA(lv_tcl_name) = <ls_tcl>-testclass.
700
+
701
+ " Loop through test methods
702
+ LOOP AT <ls_tcl>-tab_methods ASSIGNING FIELD-SYMBOL(<ls_method>).
703
+ " Extract fields dynamically since structure names vary
704
+ DATA: lv_methodname TYPE string,
705
+ lv_kind TYPE string,
706
+ lv_desc TYPE string,
707
+ lv_src TYPE string.
708
+
709
+ ASSIGN COMPONENT 'METHODNAME' OF STRUCTURE <ls_method> TO FIELD-SYMBOL(<lv_mname>).
710
+ IF sy-subrc = 0 AND <lv_mname> IS ASSIGNED.
711
+ lv_methodname = <lv_mname>.
712
+ ENDIF.
713
+
714
+ ASSIGN COMPONENT 'KIND' OF STRUCTURE <ls_method> TO FIELD-SYMBOL(<lv_kind>).
715
+ IF sy-subrc = 0 AND <lv_kind> IS ASSIGNED.
716
+ lv_kind = <lv_kind>.
717
+ ENDIF.
718
+
719
+ ASSIGN COMPONENT 'DESCRIPTION' OF STRUCTURE <ls_method> TO FIELD-SYMBOL(<lv_desc>).
720
+ IF sy-subrc = 0 AND <lv_desc> IS ASSIGNED.
721
+ lv_desc = <lv_desc>.
722
+ ENDIF.
723
+
724
+ ASSIGN COMPONENT 'SOURCE' OF STRUCTURE <ls_method> TO FIELD-SYMBOL(<lv_src>).
725
+ IF sy-subrc = 0 AND <lv_src> IS ASSIGNED.
726
+ lv_src = <lv_src>.
727
+ ENDIF.
728
+
729
+ DATA(ls_result) = VALUE zif_abgagt_agent=>ty_test_result(
730
+ object_type = 'CLAS'
731
+ object_name = lv_obj_name
732
+ test_method = lv_methodname
733
+ status = lv_kind
734
+ message = lv_desc
735
+ line = lv_src
736
+ ).
737
+ APPEND ls_result TO rt_results.
738
+ ENDLOOP.
739
+ ENDLOOP.
740
+ ENDLOOP.
741
+ ENDMETHOD.
742
+
743
+ METHOD count_results.
744
+ rs_stats-test_count = lines( it_results ).
745
+ rs_stats-passed_count = 0.
746
+ rs_stats-failed_count = 0.
747
+
748
+ LOOP AT it_results ASSIGNING FIELD-SYMBOL(<ls_result>).
749
+ CASE <ls_result>-status.
750
+ WHEN 'P' OR 'S'. " Passed or Success
751
+ rs_stats-passed_count = rs_stats-passed_count + 1.
752
+ WHEN 'A' OR 'E' OR 'F'. " Abort, Error, or Failed
753
+ rs_stats-failed_count = rs_stats-failed_count + 1.
754
+ WHEN OTHERS.
755
+ IF <ls_result>-message CS 'Passed' OR <ls_result>-message CS 'passed'.
756
+ rs_stats-passed_count = rs_stats-passed_count + 1.
757
+ ELSE.
758
+ rs_stats-failed_count = rs_stats-failed_count + 1.
759
+ ENDIF.
760
+ ENDCASE.
761
+ ENDLOOP.
762
+
763
+ ENDMETHOD.
764
+
765
+ METHOD get_version.
766
+ rv_version = '1.0.0'.
767
+ ENDMETHOD.
768
+
769
+ ENDCLASS.