Harness: runner, generator, tasks T01 T13 T14 T15, CLAUDE.md

This commit is contained in:
Kral
2026-10-02 21:42:51 +02:00
commit 1c6c85492e
54 changed files with 3732 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
CLASS {{p}}t01_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}if_iban_check.
METHODS setup.
METHODS valid_de FOR TESTING.
METHODS valid_letters_in_body FOR TESTING.
METHODS valid_all_countries FOR TESTING.
METHODS lower_case_ok FOR TESTING.
METHODS empty FOR TESTING.
METHODS only_spaces FOR TESTING.
METHODS bad_chars FOR TESTING.
METHODS digit_in_country FOR TESTING.
METHODS unknown_country FOR TESTING.
METHODS wrong_length FOR TESTING.
METHODS wrong_checksum FOR TESTING.
METHODS order_chars_before_country FOR TESTING.
ENDCLASS.
CLASS {{p}}t01_hidden IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}iban_validator( ).
ENDMETHOD.
METHOD valid_de.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-ok
act = mo_cut->check( `DE89 3704 0044 0532 0130 00` ) ).
ENDMETHOD.
METHOD valid_letters_in_body.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-ok
act = mo_cut->check( `GB82 WEST 1234 5698 7654 32` ) ).
ENDMETHOD.
METHOD valid_all_countries.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-ok
act = mo_cut->check( `NL91 ABNA 0417 1643 00` ) msg = `NL` ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-ok
act = mo_cut->check( `FR14 2004 1010 0505 0001 3M02 606` ) msg = `FR` ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-ok
act = mo_cut->check( `TR33 0006 1005 1978 6457 8413 26` ) msg = `TR` ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-ok
act = mo_cut->check( `CH93 0076 2011 6238 5295 7` ) msg = `CH` ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-ok
act = mo_cut->check( `AT61 1904 3002 3457 3201` ) msg = `AT` ).
ENDMETHOD.
METHOD lower_case_ok.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-ok
act = mo_cut->check( `de89370400440532013000` ) ).
ENDMETHOD.
METHOD empty.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-empty
act = mo_cut->check( `` ) ).
ENDMETHOD.
METHOD only_spaces.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-empty
act = mo_cut->check( ` ` ) ).
ENDMETHOD.
METHOD bad_chars.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-chars
act = mo_cut->check( `DE89-3704-0044-0532-0130-00` ) ).
ENDMETHOD.
METHOD digit_in_country.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-chars
act = mo_cut->check( `1E89370400440532013000` ) ).
ENDMETHOD.
METHOD unknown_country.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-country
act = mo_cut->check( `XX89370400440532013000` ) ).
ENDMETHOD.
METHOD wrong_length.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-length
act = mo_cut->check( `DE8937040044053201300` ) ).
ENDMETHOD.
METHOD wrong_checksum.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-checksum
act = mo_cut->check( `DE89 3704 0044 0532 0130 01` ) ).
ENDMETHOD.
METHOD order_chars_before_country.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-chars
act = mo_cut->check( `XX!!` ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,84 @@
CLASS {{p}}iban_validator DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_iban_check.
PRIVATE SECTION.
TYPES:
BEGIN OF ty_country,
code TYPE c LENGTH 2,
length TYPE i,
END OF ty_country,
tt_country TYPE HASHED TABLE OF ty_country WITH UNIQUE KEY code.
METHODS normalize
IMPORTING iv_iban TYPE string
RETURNING VALUE(rv_iban) TYPE string.
METHODS has_valid_format
IMPORTING iv_iban TYPE string
RETURNING VALUE(rv_valid) TYPE abap_bool.
METHODS country_lengths
RETURNING VALUE(rt_country) TYPE tt_country.
METHODS remainder_97
IMPORTING iv_iban TYPE string
RETURNING VALUE(rv_mod) TYPE i.
ENDCLASS.
CLASS {{p}}iban_validator IMPLEMENTATION.
METHOD {{p}}if_iban_check~check.
DATA(lv_iban) = normalize( iv_iban ).
IF lv_iban IS INITIAL.
rv_reason = {{p}}if_iban_check=>c_reason-empty.
RETURN.
ENDIF.
IF has_valid_format( lv_iban ) = abap_false.
rv_reason = {{p}}if_iban_check=>c_reason-chars.
RETURN.
ENDIF.
DATA(lt_country) = country_lengths( ).
ASSIGN lt_country[ code = substring( val = lv_iban len = 2 ) ] TO FIELD-SYMBOL(<ls_country>).
IF sy-subrc <> 0.
rv_reason = {{p}}if_iban_check=>c_reason-country.
RETURN.
ENDIF.
IF strlen( lv_iban ) <> <ls_country>-length.
rv_reason = {{p}}if_iban_check=>c_reason-length.
RETURN.
ENDIF.
IF remainder_97( lv_iban ) <> 1.
rv_reason = {{p}}if_iban_check=>c_reason-checksum.
RETURN.
ENDIF.
rv_reason = {{p}}if_iban_check=>c_reason-ok.
ENDMETHOD.
METHOD normalize.
rv_iban = to_upper( replace( val = iv_iban sub = ` ` with = `` occ = 0 ) ).
ENDMETHOD.
METHOD has_valid_format.
rv_valid = xsdbool( matches( val = iv_iban pcre = `^[A-Z]{2}[0-9]{2}[A-Z0-9]*$` ) ).
ENDMETHOD.
METHOD country_lengths.
rt_country = VALUE #( ( code = 'DE' length = 22 )
( code = 'GB' length = 22 )
( code = 'TR' length = 26 )
( code = 'FR' length = 27 )
( code = 'NL' length = 18 )
( code = 'AT' length = 20 )
( code = 'CH' length = 21 ) ).
ENDMETHOD.
METHOD remainder_97.
DATA(lv_moved) = substring( val = iv_iban off = 4 ) && substring( val = iv_iban len = 4 ).
DO strlen( lv_moved ) TIMES.
DATA(lv_char) = substring( val = lv_moved off = sy-index - 1 len = 1 ).
IF lv_char CO '0123456789'.
rv_mod = ( rv_mod * 10 + CONV i( lv_char ) ) MOD 97.
ELSE.
rv_mod = ( rv_mod * 100 + find( val = sy-abcde sub = lv_char ) + 10 ) MOD 97.
ENDIF.
ENDDO.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,48 @@
CLASS ltc_iban_validator DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}if_iban_check.
METHODS setup.
METHODS valid_iban_is_ok FOR TESTING.
METHODS empty_input FOR TESTING.
METHODS invalid_character FOR TESTING.
METHODS unknown_country FOR TESTING.
METHODS wrong_length FOR TESTING.
METHODS wrong_checksum FOR TESTING.
ENDCLASS.
CLASS ltc_iban_validator IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}iban_validator( ).
ENDMETHOD.
METHOD valid_iban_is_ok.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-ok
act = mo_cut->check( `GB82 WEST 1234 5698 7654 32` ) ).
ENDMETHOD.
METHOD empty_input.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-empty
act = mo_cut->check( ` ` ) ).
ENDMETHOD.
METHOD invalid_character.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-chars
act = mo_cut->check( `DE89*370400440532013000` ) ).
ENDMETHOD.
METHOD unknown_country.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-country
act = mo_cut->check( `US89370400440532013000` ) ).
ENDMETHOD.
METHOD wrong_length.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-length
act = mo_cut->check( `NL91ABNA041716430` ) ).
ENDMETHOD.
METHOD wrong_checksum.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_iban_check=>c_reason-checksum
act = mo_cut->check( `GB82 WEST 1234 5698 7654 33` ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,15 @@
INTERFACE {{p}}if_iban_check PUBLIC.
TYPES ty_reason TYPE c LENGTH 10.
CONSTANTS:
BEGIN OF c_reason,
ok TYPE ty_reason VALUE 'OK',
empty TYPE ty_reason VALUE 'EMPTY',
chars TYPE ty_reason VALUE 'CHARS',
country TYPE ty_reason VALUE 'COUNTRY',
length TYPE ty_reason VALUE 'LENGTH',
checksum TYPE ty_reason VALUE 'CHECKSUM',
END OF c_reason.
METHODS check
IMPORTING iv_iban TYPE string
RETURNING VALUE(rv_reason) TYPE ty_reason.
ENDINTERFACE.

45
tasks/T01/spec.md Normal file
View File

@@ -0,0 +1,45 @@
# 1. Goal
Check the format and the checksum of an IBAN. A payment program calls the check
before it sends a payment. The program needs one reason code for each IBAN.
# 2. Open questions
None.
# 3. Context
- Interface {{P}}IF_IBAN_CHECK exists in package $TMP. It is active.
- The interface has the type TY_REASON (CHAR 10), the constant structure
C_REASON (OK, EMPTY, CHARS, COUNTRY, LENGTH, CHECKSUM), and one method:
CHECK IMPORTING iv_iban TYPE string RETURNING VALUE(rv_reason) TYPE ty_reason.
# 4. Contract
- Create the class {{P}}IBAN_VALIDATOR in package $TMP.
- The class is public, final, and has a public constructor without parameters.
- The class implements {{P}}IF_IBAN_CHECK.
- Do not add other public methods.
# 5. Business rules
Apply the rules in this sequence. Return the reason of the first rule that fails.
1. Remove all spaces. Convert all letters to upper case.
2. If the result is empty, return EMPTY.
3. The result must contain only the letters A to Z and the digits 0 to 9.
Positions 1 and 2 must be letters. Positions 3 and 4 must be digits. If not,
return CHARS.
4. Positions 1 and 2 are the country code. Use this list of codes and lengths:
DE 22, GB 22, TR 26, FR 27, NL 18, AT 20, CH 21. If the code is not in the
list, return COUNTRY.
5. If the length is not the length of the country, return LENGTH.
6. Calculate the checksum: move the first 4 characters to the end. Replace each
letter with a number (A = 10, B = 11, ..., Z = 35). Calculate the remainder
of this number divided by 97. If the remainder is not 1, return CHECKSUM.
7. Else return OK.
# 6. Constraints
- Release target: 7.58.
- Coding standards: Clean ABAP. Method length below 40 statements. No global
variables. No comment that restates the code.
- Out of scope: do not change {{P}}IF_IBAN_CHECK.
# 7. Acceptance
- The class is active and has no syntax error.
- The hidden tests pass.
- Write your own ABAP Unit tests for the class.

50
tasks/T01/task.json Normal file
View File

@@ -0,0 +1,50 @@
{
"id": "T01",
"category": "A",
"difficulty": 1,
"release_target": "v758",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 40,
"max_activations": 10
},
"seed": [
{
"type": "INTF",
"name": "{{P}}IF_IBAN_CHECK",
"file": "seed/if_iban_check.intf.abap",
"description": "IBAN check contract"
}
],
"contract": [
{
"type": "CLAS",
"name": "{{P}}IBAN_VALIDATOR",
"implements": "{{P}}IF_IBAN_CHECK"
}
],
"out_of_scope": [
"{{P}}IF_IBAN_CHECK"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}T01_HIDDEN",
"file": "hidden/t01_hidden.clas.abap",
"description": "T01 hidden tests"
}
],
"reference": [
{
"type": "CLAS",
"name": "{{P}}IBAN_VALIDATOR",
"file": "reference/iban_validator.clas.abap",
"description": "IBAN validator",
"testclasses_file": "reference/iban_validator.testclasses.abap"
}
],
"craft_checks": [
"method_length",
"db_operation_in_loop"
]
}

View File

@@ -0,0 +1,70 @@
CLASS {{p}}t13_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS call IMPORTING iv_type TYPE string iv_amount TYPE decfloat34 iv_fee TYPE decfloat34 DEFAULT 7
EXPORTING ev_skip TYPE abap_bool ev_fee TYPE decfloat34 ev_subrc TYPE sysubrc.
METHODS invoice_percent FOR TESTING.
METHODS invoice_minimum FOR TESTING.
METHODS invoice_maximum FOR TESTING.
METHODS invoice_rounding FOR TESTING.
METHODS credit_memo_skips FOR TESTING.
METHODS other_type_zero FOR TESTING.
METHODS negative_amount FOR TESTING.
METHODS skip_false_for_invoice FOR TESTING.
ENDCLASS.
CLASS {{p}}t13_hidden IMPLEMENTATION.
METHOD call.
ev_fee = iv_fee.
CALL FUNCTION '{{P}}EVT_FEE'
EXPORTING iv_doc_type = iv_type iv_amount = iv_amount
IMPORTING ev_skip = ev_skip
CHANGING cv_fee = ev_fee
EXCEPTIONS invalid_amount = 1 OTHERS = 2.
ev_subrc = sy-subrc.
ENDMETHOD.
METHOD invoice_percent.
call( EXPORTING iv_type = `IN` iv_amount = 1000 IMPORTING ev_fee = DATA(lv_fee) ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 20 ) act = lv_fee ).
ENDMETHOD.
METHOD invoice_minimum.
call( EXPORTING iv_type = `IN` iv_amount = 10 IMPORTING ev_fee = DATA(lv_fee) ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 1 ) act = lv_fee ).
ENDMETHOD.
METHOD invoice_maximum.
call( EXPORTING iv_type = `IN` iv_amount = 100000 IMPORTING ev_fee = DATA(lv_fee) ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 50 ) act = lv_fee ).
ENDMETHOD.
METHOD invoice_rounding.
call( EXPORTING iv_type = `IN` iv_amount = '123.25' IMPORTING ev_fee = DATA(lv_fee) ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '2.47' ) act = lv_fee ).
ENDMETHOD.
METHOD credit_memo_skips.
call( EXPORTING iv_type = `CR` iv_amount = 500 iv_fee = 7
IMPORTING ev_skip = DATA(lv_skip) ev_fee = DATA(lv_fee) ).
cl_abap_unit_assert=>assert_equals( exp = abap_true act = lv_skip ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 7 ) act = lv_fee ).
ENDMETHOD.
METHOD other_type_zero.
call( EXPORTING iv_type = `XX` iv_amount = 500 IMPORTING ev_fee = DATA(lv_fee) ev_skip = DATA(lv_skip) ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 0 ) act = lv_fee ).
cl_abap_unit_assert=>assert_equals( exp = abap_false act = lv_skip ).
ENDMETHOD.
METHOD negative_amount.
call( EXPORTING iv_type = `IN` iv_amount = -1 IMPORTING ev_subrc = DATA(lv_rc) ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = lv_rc ).
ENDMETHOD.
METHOD skip_false_for_invoice.
call( EXPORTING iv_type = `IN` iv_amount = 100 IMPORTING ev_skip = DATA(lv_skip) ).
cl_abap_unit_assert=>assert_equals( exp = abap_false act = lv_skip ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,30 @@
FUNCTION {{p}}evt_fee
IMPORTING
iv_doc_type TYPE string
iv_amount TYPE decfloat34
EXPORTING
ev_skip TYPE abap_bool
CHANGING
cv_fee TYPE decfloat34
EXCEPTIONS
invalid_amount.
ev_skip = abap_false.
IF iv_amount < 0.
RAISE invalid_amount.
ENDIF.
CASE iv_doc_type.
WHEN 'IN'.
cv_fee = round( val = iv_amount * 2 / 100 dec = 2 mode = cl_abap_math=>round_half_up ).
IF cv_fee < 1.
cv_fee = 1.
ELSEIF cv_fee > 50.
cv_fee = 50.
ENDIF.
WHEN 'CR'.
ev_skip = abap_true.
WHEN OTHERS.
cv_fee = 0.
ENDCASE.
ENDFUNCTION.

View File

@@ -0,0 +1,52 @@
CLASS {{p}}t13_test DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS fee_for IMPORTING iv_type TYPE string iv_amount TYPE decfloat34
RETURNING VALUE(rv_fee) TYPE decfloat34.
METHODS invoice_fee FOR TESTING.
METHODS fee_limits FOR TESTING.
METHODS credit_memo FOR TESTING.
METHODS negative_amount FOR TESTING.
ENDCLASS.
CLASS {{p}}t13_test IMPLEMENTATION.
METHOD fee_for.
DATA lv_skip TYPE abap_bool.
CALL FUNCTION '{{P}}EVT_FEE'
EXPORTING iv_doc_type = iv_type iv_amount = iv_amount
IMPORTING ev_skip = lv_skip
CHANGING cv_fee = rv_fee
EXCEPTIONS invalid_amount = 1 OTHERS = 2.
cl_abap_unit_assert=>assert_subrc( exp = 0 ).
ENDMETHOD.
METHOD invoice_fee.
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 4 ) act = fee_for( iv_type = `IN` iv_amount = 200 ) ).
ENDMETHOD.
METHOD fee_limits.
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 1 ) act = fee_for( iv_type = `IN` iv_amount = 5 ) ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 50 ) act = fee_for( iv_type = `IN` iv_amount = 9000 ) ).
ENDMETHOD.
METHOD credit_memo.
DATA lv_skip TYPE abap_bool.
DATA lv_fee TYPE decfloat34 VALUE 3.
CALL FUNCTION '{{P}}EVT_FEE'
EXPORTING iv_doc_type = `CR` iv_amount = 100
IMPORTING ev_skip = lv_skip
CHANGING cv_fee = lv_fee.
cl_abap_unit_assert=>assert_true( lv_skip ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 3 ) act = lv_fee ).
ENDMETHOD.
METHOD negative_amount.
DATA lv_fee TYPE decfloat34.
CALL FUNCTION '{{P}}EVT_FEE'
EXPORTING iv_doc_type = `IN` iv_amount = -5
CHANGING cv_fee = lv_fee
EXCEPTIONS invalid_amount = 1 OTHERS = 2.
cl_abap_unit_assert=>assert_equals( exp = 1 act = sy-subrc ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,14 @@
FUNCTION {{p}}evt_sample
IMPORTING
iv_doc_type TYPE string
iv_amount TYPE decfloat34
EXPORTING
ev_skip TYPE abap_bool
CHANGING
cv_fee TYPE decfloat34
EXCEPTIONS
invalid_amount.
ev_skip = abap_false.
ENDFUNCTION.

35
tasks/T13/spec.md Normal file
View File

@@ -0,0 +1,35 @@
# 1. Goal
Calculate a processing fee for a posted document. The posting program calls one
event module for each document. The event module must have the same interface as
the sample event module, because the posting program calls all event modules in
the same way.
# 2. Open questions
None.
# 3. Context
- Function group {{P}}FG_EVT exists in package $TMP.
- Function module {{P}}EVT_SAMPLE exists in this group. It is the sample event
module. Its interface is the interface that all event modules must use.
# 4. Contract
- Create the function module {{P}}EVT_FEE in function group {{P}}FG_EVT.
- Use exactly the interface of {{P}}EVT_SAMPLE: the same parameter names, kinds,
types, and exceptions.
# 5. Business rules
1. If the amount is less than 0, raise the exception INVALID_AMOUNT.
2. Set the skip flag to false, except in rule 4.
3. Document type IN: the fee is 2 % of the amount, rounded half up to 2
decimals. The minimum fee is 1.00. The maximum fee is 50.00.
4. Document type CR: set the skip flag to true. Do not change the fee.
5. Other document types: the fee is 0.
# 6. Constraints
- Release target: 7.58.
- Out of scope: do not change {{P}}EVT_SAMPLE.
# 7. Acceptance
- The function module is active and has no syntax error.
- The hidden tests pass.
- Write ABAP Unit tests for the function module in a global test class.

80
tasks/T13/task.json Normal file
View File

@@ -0,0 +1,80 @@
{
"id": "T13",
"category": "F",
"object_type": "FUNC",
"difficulty": 2,
"release_target": "v758",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 40,
"max_activations": 10
},
"seed": [
{
"type": "FUGR",
"name": "{{P}}FG_EVT",
"description": "Fee events"
},
{
"type": "FUNC",
"name": "{{P}}EVT_SAMPLE",
"functionGroup": "{{P}}FG_EVT",
"file": "seed/evt_sample.func.abap",
"description": "Sample event module"
}
],
"contract": [
{
"type": "FUNC",
"name": "{{P}}EVT_FEE",
"functionGroup": "{{P}}FG_EVT",
"params": [
{
"name": "iv_doc_type",
"type": "string"
},
{
"name": "iv_amount",
"type": "decfloat34"
},
{
"name": "ev_skip",
"type": "abap_bool"
},
{
"name": "cv_fee",
"type": "decfloat34"
}
]
}
],
"out_of_scope": [
"{{P}}EVT_SAMPLE"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}T13_HIDDEN",
"file": "hidden/t13_hidden.clas.abap",
"description": "T13 hidden tests"
}
],
"reference": [
{
"type": "FUNC",
"name": "{{P}}EVT_FEE",
"functionGroup": "{{P}}FG_EVT",
"file": "reference/evt_fee.func.abap",
"description": "Fee event module"
},
{
"type": "CLAS",
"name": "{{P}}T13_TEST",
"file": "reference/t13_test.clas.abap",
"description": "T13 own tests"
}
],
"craft_checks": [
"method_length"
]
}

View File

@@ -0,0 +1,72 @@
CLASS {{p}}t14_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
TYPES tt_region TYPE RANGE OF {{p}}sale-region.
METHODS run IMPORTING iv_from TYPE d it_region TYPE tt_region OPTIONAL
RETURNING VALUE(rr_data) TYPE REF TO data.
METHODS value_of IMPORTING ir_data TYPE REF TO data iv_row TYPE i iv_col TYPE string
RETURNING VALUE(rv_value) TYPE string.
METHODS lines_of IMPORTING ir_data TYPE REF TO data RETURNING VALUE(rv_lines) TYPE i.
METHODS all_regions FOR TESTING.
METHODS sorted_by_total FOR TESTING.
METHODS totals_and_counts FOR TESTING.
METHODS region_filter FOR TESTING.
METHODS date_filter FOR TESTING.
ENDCLASS.
CLASS {{p}}t14_hidden IMPLEMENTATION.
METHOD run.
cl_salv_bs_runtime_info=>set( display = abap_false metadata = abap_false data = abap_true ).
SUBMIT {{p}}sales_sum WITH p_from = iv_from WITH s_reg IN it_region AND RETURN.
TRY.
cl_salv_bs_runtime_info=>get_data_ref( IMPORTING r_data = rr_data ).
CATCH cx_salv_bs_sc_runtime_info.
ENDTRY.
cl_salv_bs_runtime_info=>clear_all( ).
ENDMETHOD.
METHOD lines_of.
FIELD-SYMBOLS <lt_data> TYPE STANDARD TABLE.
IF ir_data IS BOUND.
ASSIGN ir_data->* TO <lt_data>.
rv_lines = lines( <lt_data> ).
ENDIF.
ENDMETHOD.
METHOD value_of.
FIELD-SYMBOLS <lt_data> TYPE STANDARD TABLE.
ASSIGN ir_data->* TO <lt_data>.
ASSIGN <lt_data>[ iv_row ] TO FIELD-SYMBOL(<ls_line>).
ASSIGN COMPONENT iv_col OF STRUCTURE <ls_line> TO FIELD-SYMBOL(<lv_value>).
cl_abap_unit_assert=>assert_subrc( exp = 0 msg = |Column { iv_col } missing| ).
rv_value = condense( CONV string( <lv_value> ) ).
ENDMETHOD.
METHOD all_regions.
cl_abap_unit_assert=>assert_equals( exp = 3 act = lines_of( run( '20260101' ) ) ).
ENDMETHOD.
METHOD sorted_by_total.
DATA(lr_data) = run( '20251201' ).
cl_abap_unit_assert=>assert_equals( exp = `APAC` act = value_of( ir_data = lr_data iv_row = 1 iv_col = `REGION` ) ).
cl_abap_unit_assert=>assert_equals( exp = `EU` act = value_of( ir_data = lr_data iv_row = 3 iv_col = `REGION` ) ).
ENDMETHOD.
METHOD totals_and_counts.
DATA(lr_data) = run( '20260101' ).
cl_abap_unit_assert=>assert_equals( exp = `US` act = value_of( ir_data = lr_data iv_row = 1 iv_col = `REGION` ) ).
cl_abap_unit_assert=>assert_equals( exp = `150.00` act = value_of( ir_data = lr_data iv_row = 2 iv_col = `TOTAL` ) ).
cl_abap_unit_assert=>assert_equals( exp = `2` act = value_of( ir_data = lr_data iv_row = 2 iv_col = `COUNT` ) ).
ENDMETHOD.
METHOD region_filter.
DATA(lr_data) = run( iv_from = '20260101' it_region = VALUE #( ( sign = 'I' option = 'EQ' low = 'EU' ) ) ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = lines_of( lr_data ) ).
cl_abap_unit_assert=>assert_equals( exp = `150.00` act = value_of( ir_data = lr_data iv_row = 1 iv_col = `TOTAL` ) ).
ENDMETHOD.
METHOD date_filter.
cl_abap_unit_assert=>assert_equals( exp = 2 act = lines_of( run( '20260201' ) ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,89 @@
REPORT {{p}}sales_sum.
DATA gv_region TYPE {{p}}sale-region.
PARAMETERS p_from TYPE d OBLIGATORY.
SELECT-OPTIONS s_reg FOR gv_region.
CLASS lcl_report DEFINITION FINAL.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_line,
region TYPE {{p}}sale-region,
total TYPE {{p}}sale-amount,
count TYPE i,
END OF ty_line,
tt_line TYPE STANDARD TABLE OF ty_line WITH EMPTY KEY.
TYPES tt_region TYPE RANGE OF {{p}}sale-region.
METHODS select_totals
IMPORTING iv_from TYPE d
it_region TYPE tt_region
RETURNING VALUE(rt_lines) TYPE tt_line.
METHODS display
CHANGING ct_lines TYPE tt_line.
ENDCLASS.
CLASS lcl_report IMPLEMENTATION.
METHOD select_totals.
SELECT region, SUM( amount ) AS total, COUNT( * ) AS count
FROM {{p}}sale
WHERE sale_date >= @iv_from
AND region IN @it_region
GROUP BY region
ORDER BY total DESCENDING
INTO CORRESPONDING FIELDS OF TABLE @rt_lines.
ENDMETHOD.
METHOD display.
TRY.
cl_salv_table=>factory( IMPORTING r_salv_table = DATA(lo_alv)
CHANGING t_table = ct_lines ).
lo_alv->display( ).
CATCH cx_salv_msg INTO DATA(lx_error).
MESSAGE lx_error TYPE 'I' DISPLAY LIKE 'E'.
ENDTRY.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA(go_report) = NEW lcl_report( ).
DATA(gt_lines) = go_report->select_totals( iv_from = p_from it_region = s_reg[] ).
go_report->display( CHANGING ct_lines = gt_lines ).
CLASS ltc_report DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
CLASS-DATA go_env TYPE REF TO if_osql_test_environment.
CLASS-METHODS class_setup.
CLASS-METHODS class_teardown.
METHODS totals_per_region FOR TESTING.
METHODS region_filter FOR TESTING.
ENDCLASS.
CLASS ltc_report IMPLEMENTATION.
METHOD class_setup.
go_env = cl_osql_test_environment=>create( i_dependency_list = VALUE #( ( '{{P}}SALE' ) ) ).
DATA lt_sale TYPE STANDARD TABLE OF {{p}}sale WITH EMPTY KEY.
lt_sale = VALUE #( ( sale_id = 1 region = 'A' sale_date = '20260101' amount = 10 )
( sale_id = 2 region = 'A' sale_date = '20260102' amount = 5 )
( sale_id = 3 region = 'B' sale_date = '20260101' amount = 40 ) ).
go_env->insert_test_data( lt_sale ).
ENDMETHOD.
METHOD class_teardown.
go_env->destroy( ).
ENDMETHOD.
METHOD totals_per_region.
DATA(lt_lines) = NEW lcl_report( )->select_totals( iv_from = '20260101' it_region = VALUE #( ) ).
cl_abap_unit_assert=>assert_equals( exp = 2 act = lines( lt_lines ) ).
cl_abap_unit_assert=>assert_equals( exp = 'B' act = lt_lines[ 1 ]-region ).
cl_abap_unit_assert=>assert_equals( exp = 2 act = lt_lines[ 2 ]-count ).
ENDMETHOD.
METHOD region_filter.
DATA(lt_lines) = NEW lcl_report( )->select_totals(
iv_from = '20260101' it_region = VALUE #( ( sign = 'I' option = 'EQ' low = 'A' ) ) ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = lines( lt_lines ) ).
cl_abap_unit_assert=>assert_equals( exp = CONV {{p}}sale-amount( 15 ) act = lt_lines[ 1 ]-total ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,12 @@
@EndUserText.label : 'Sales'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table {{p}}sale {
key client : abap.clnt not null;
key sale_id : abap.int4 not null;
region : abap.char(4);
sale_date : abap.dats;
amount : abap.dec(15,2);
}

View File

@@ -0,0 +1,17 @@
CLASS {{p}}t14_seed DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS {{p}}t14_seed IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DELETE FROM {{p}}sale.
INSERT {{p}}sale FROM TABLE @( VALUE #(
( sale_id = 1 region = 'EU' sale_date = '20260110' amount = '100.00' )
( sale_id = 2 region = 'EU' sale_date = '20260210' amount = '50.00' )
( sale_id = 3 region = 'US' sale_date = '20260115' amount = '300.00' )
( sale_id = 4 region = 'APAC' sale_date = '20251231' amount = '500.00' )
( sale_id = 5 region = 'APAC' sale_date = '20260301' amount = '20.00' ) ) ).
out->write( sy-dbcnt ).
ENDMETHOD.
ENDCLASS.

33
tasks/T14/spec.md Normal file
View File

@@ -0,0 +1,33 @@
# 1. Goal
Show the sales total for each region in an ALV list. The sales team uses the
list to compare regions for a period.
# 2. Open questions
None.
# 3. Context
- Table {{P}}SALE exists in package $TMP. It contains data.
Fields: SALE_ID (INT4, key), REGION (CHAR 4), SALE_DATE (DATS), AMOUNT (DEC 15,2).
# 4. Contract
- Create the executable program {{P}}SALES_SUM in package $TMP.
- Selection screen:
- Parameter P_FROM, type DATS, mandatory: the first sale date to include.
- Select-option S_REG for the region.
- Show the result with CL_SALV_TABLE. The output table has these columns, with
these names: REGION, TOTAL (sum of AMOUNT), COUNT (number of sales).
# 5. Business rules
1. Include a sale only if SALE_DATE is on or after P_FROM and REGION is in S_REG.
2. Show one line for each region that has at least one included sale.
3. Sort the lines by TOTAL, the highest total first.
# 6. Constraints
- Release target: 7.58.
- Coding standards: Clean ABAP. Read the data with one database access.
- Out of scope: do not change {{P}}SALE.
# 7. Acceptance
- The program is active and has no syntax error.
- The hidden tests pass.
- Write ABAP Unit tests as local test classes in the program.

18
tasks/T14/task.json Normal file
View File

@@ -0,0 +1,18 @@
{
"id": "T14", "category": "B", "object_type": "PROG", "difficulty": 2,
"release_target": "v758", "expected_outcome": "implement",
"budget": {"max_tool_calls": 40, "max_activations": 10},
"seed": [
{"type": "TABL", "name": "{{P}}SALE", "file": "seed/sale.tabl.asabap", "description": "Sales"},
{"type": "CLAS", "name": "{{P}}T14_SEED", "file": "seed/t14_seed.clas.abap", "description": "T14 seed data", "run": true}
],
"contract": [
{"type": "PROG", "name": "{{P}}SALES_SUM", "parameters": ["p_from", "s_reg"]}
],
"out_of_scope": ["{{P}}SALE"],
"hidden_tests": [{"type": "CLAS", "name": "{{P}}T14_HIDDEN", "file": "hidden/t14_hidden.clas.abap",
"description": "T14 hidden tests"}],
"reference": [{"type": "PROG", "name": "{{P}}SALES_SUM", "file": "reference/sales_sum.prog.abap",
"description": "Sales per region"}],
"craft_checks": ["db_operation_in_loop", "method_length"]
}

View File

@@ -0,0 +1,64 @@
CLASS {{p}}t15_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
CLASS-DATA go_env TYPE REF TO if_cds_test_environment.
CLASS-METHODS class_setup.
CLASS-METHODS class_teardown.
METHODS setup.
METHODS totals FOR TESTING.
METHODS item_counts FOR TESTING.
METHODS order_without_items FOR TESTING.
METHODS customer_shown FOR TESTING.
METHODS decimals FOR TESTING.
ENDCLASS.
CLASS {{p}}t15_hidden IMPLEMENTATION.
METHOD class_setup.
go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_ORDER_TOTAL' ).
ENDMETHOD.
METHOD class_teardown.
go_env->destroy( ).
ENDMETHOD.
METHOD setup.
DATA lt_ord TYPE STANDARD TABLE OF {{p}}ord WITH EMPTY KEY.
DATA lt_itm TYPE STANDARD TABLE OF {{p}}itm WITH EMPTY KEY.
go_env->clear_doubles( ).
lt_ord = VALUE #( ( order_id = 'O1' customer = 'ALPHA' )
( order_id = 'O2' customer = 'BETA' )
( order_id = 'O3' customer = 'GAMMA' ) ).
lt_itm = VALUE #( ( order_id = 'O1' item_no = 1 quantity = 3 price = '10.00' )
( order_id = 'O1' item_no = 2 quantity = 1 price = '2.50' )
( order_id = 'O1' item_no = 3 quantity = 4 price = '0.25' )
( order_id = 'O2' item_no = 1 quantity = 10 price = '1.99' ) ).
go_env->insert_test_data( lt_ord ).
go_env->insert_test_data( lt_itm ).
ENDMETHOD.
METHOD totals.
SELECT SINGLE total_amount FROM {{p}}i_order_total WHERE order_id = 'O1' INTO @DATA(lv_total).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '33.50' ) act = CONV decfloat34( lv_total ) ).
ENDMETHOD.
METHOD item_counts.
SELECT SINGLE item_count FROM {{p}}i_order_total WHERE order_id = 'O1' INTO @DATA(lv_count).
cl_abap_unit_assert=>assert_equals( exp = 3 act = lv_count ).
ENDMETHOD.
METHOD order_without_items.
SELECT COUNT(*) FROM {{p}}i_order_total INTO @DATA(lv_rows).
cl_abap_unit_assert=>assert_equals( exp = 2 act = lv_rows ).
ENDMETHOD.
METHOD customer_shown.
SELECT SINGLE customer FROM {{p}}i_order_total WHERE order_id = 'O2' INTO @DATA(lv_customer).
cl_abap_unit_assert=>assert_equals( exp = 'BETA' act = lv_customer ).
ENDMETHOD.
METHOD decimals.
SELECT SINGLE total_amount FROM {{p}}i_order_total WHERE order_id = 'O2' INTO @DATA(lv_total).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '19.90' ) act = CONV decfloat34( lv_total ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,14 @@
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Order totals'
define view entity {{P}}I_ORDER_TOTAL
as select from {{p}}ord as head
inner join {{p}}itm as item on item.order_id = head.order_id
{
key head.order_id as order_id,
head.customer as customer,
count( * ) as item_count,
sum( cast( item.quantity as abap.dec( 10, 0 ) ) * item.price ) as total_amount
}
group by
head.order_id,
head.customer

View File

@@ -0,0 +1,38 @@
CLASS {{p}}t15_test DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
CLASS-DATA go_env TYPE REF TO if_cds_test_environment.
CLASS-METHODS class_setup.
CLASS-METHODS class_teardown.
METHODS setup.
METHODS total_of_one_order FOR TESTING.
ENDCLASS.
CLASS {{p}}t15_test IMPLEMENTATION.
METHOD class_setup.
go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_ORDER_TOTAL' ).
ENDMETHOD.
METHOD class_teardown.
go_env->destroy( ).
ENDMETHOD.
METHOD setup.
go_env->clear_doubles( ).
ENDMETHOD.
METHOD total_of_one_order.
DATA lt_ord TYPE STANDARD TABLE OF {{p}}ord WITH EMPTY KEY.
DATA lt_itm TYPE STANDARD TABLE OF {{p}}itm WITH EMPTY KEY.
lt_ord = VALUE #( ( order_id = 'A' customer = 'C1' ) ).
lt_itm = VALUE #( ( order_id = 'A' item_no = 1 quantity = 2 price = '1.50' )
( order_id = 'A' item_no = 2 quantity = 1 price = '4.00' ) ).
go_env->insert_test_data( lt_ord ).
go_env->insert_test_data( lt_itm ).
SELECT SINGLE item_count, total_amount FROM {{p}}i_order_total WHERE order_id = 'A'
INTO @DATA(ls_total).
cl_abap_unit_assert=>assert_equals( exp = 2 act = ls_total-item_count ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 7 ) act = CONV decfloat34( ls_total-total_amount ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,13 @@
@EndUserText.label : 'Order item'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table {{p}}itm {
key client : abap.clnt not null;
key order_id : abap.char(10) not null;
key item_no : abap.int4 not null;
product : abap.char(10);
quantity : abap.int4;
price : abap.dec(15,2);
}

View File

@@ -0,0 +1,11 @@
@EndUserText.label : 'Order header'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table {{p}}ord {
key client : abap.clnt not null;
key order_id : abap.char(10) not null;
customer : abap.char(10);
order_date : abap.dats;
}

33
tasks/T15/spec.md Normal file
View File

@@ -0,0 +1,33 @@
# 1. Goal
Give the total value of each order. A report and an OData service will read the
totals, so the calculation must be in the database, in a CDS view.
# 2. Open questions
None.
# 3. Context
- Table {{P}}ORD (order header), package $TMP. Key: ORDER_ID (CHAR 10).
Fields: CUSTOMER (CHAR 10), ORDER_DATE (DATS).
- Table {{P}}ITM (order item), package $TMP. Key: ORDER_ID (CHAR 10), ITEM_NO (INT4).
Fields: PRODUCT (CHAR 10), QUANTITY (INT4), PRICE (DEC 15,2, price for one unit).
# 4. Contract
- Create the CDS view entity {{P}}I_ORDER_TOTAL in package $TMP.
- Elements, with these names: ORDER_ID (key), CUSTOMER, ITEM_COUNT, TOTAL_AMOUNT.
- No authorization check (#NOT_REQUIRED).
# 5. Business rules
1. Show one row for each order that has at least one item. Do not show an order
without items.
2. ITEM_COUNT is the number of items of the order.
3. TOTAL_AMOUNT is the sum of QUANTITY multiplied by PRICE for all items of the
order, with 2 decimals.
# 6. Constraints
- Release target: 7.58.
- Out of scope: do not change {{P}}ORD and {{P}}ITM.
# 7. Acceptance
- The view is active and has no syntax error.
- The hidden tests pass.
- Write ABAP Unit tests with CL_CDS_TEST_ENVIRONMENT in a global test class.

22
tasks/T15/task.json Normal file
View File

@@ -0,0 +1,22 @@
{
"id": "T15", "category": "B", "object_type": "DDLS", "difficulty": 2,
"release_target": "v758", "expected_outcome": "implement",
"budget": {"max_tool_calls": 40, "max_activations": 10},
"seed": [
{"type": "TABL", "name": "{{P}}ORD", "file": "seed/ord.tabl.asabap", "description": "Order header"},
{"type": "TABL", "name": "{{P}}ITM", "file": "seed/itm.tabl.asabap", "description": "Order item"}
],
"contract": [
{"type": "DDLS", "name": "{{P}}I_ORDER_TOTAL",
"fields": ["ORDER_ID", "CUSTOMER", "ITEM_COUNT", "TOTAL_AMOUNT"]}
],
"out_of_scope": ["{{P}}ORD", "{{P}}ITM"],
"hidden_tests": [{"type": "CLAS", "name": "{{P}}T15_HIDDEN", "file": "hidden/t15_hidden.clas.abap",
"description": "T15 hidden tests"}],
"reference": [
{"type": "DDLS", "name": "{{P}}I_ORDER_TOTAL", "file": "reference/i_order_total.ddls.asddls",
"description": "Order totals"},
{"type": "CLAS", "name": "{{P}}T15_TEST", "file": "reference/t15_test.clas.abap", "description": "T15 own tests"}
],
"craft_checks": []
}