From 6c8b5053e26aeee2d76b72270256986702f1d67b Mon Sep 17 00:00:00 2001 From: "Simon C. Kessler" Date: Thu, 9 Apr 2026 13:10:47 +0200 Subject: [PATCH] =?UTF-8?q?Log=20tests=20durchgef=C3=BChrt=20und=20korrekt?= =?UTF-8?q?uren=20in=20net=20storage=20package=20diesbez=C3=BCglich?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 7 +- database/packages/pck_net_storage.pkb | 182 +++++++++++++++++--------- database/tables/lg_app_log.tab | 2 +- database/tests/app_log_tests.sql | 61 +++++++++ 4 files changed, 187 insertions(+), 65 deletions(-) create mode 100644 database/tests/app_log_tests.sql diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 0a37a01..14473a9 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,7 +1,12 @@ { "permissions": { "allow": [ - "WebSearch" + "WebSearch", + "Bash(sed -n '388,408p' \"C:\\\\src\\\\Galabau\\\\glb-spielwiese\\\\database\\\\packages\\\\pck_net_storage.pkb\")", + "Bash(sed -n '420,438p' \"C:\\\\src\\\\Galabau\\\\glb-spielwiese\\\\database\\\\packages\\\\pck_net_storage.pkb\")", + "Bash(sed -n '465,478p' \"C:\\\\src\\\\Galabau\\\\glb-spielwiese\\\\database\\\\packages\\\\pck_net_storage.pkb\")", + "Bash(sed -n '523,535p' \"C:\\\\src\\\\Galabau\\\\glb-spielwiese\\\\database\\\\packages\\\\pck_net_storage.pkb\")", + "Bash(sed -n '582,600p' \"C:\\\\src\\\\Galabau\\\\glb-spielwiese\\\\database\\\\packages\\\\pck_net_storage.pkb\")" ] } } diff --git a/database/packages/pck_net_storage.pkb b/database/packages/pck_net_storage.pkb index 342e052..0f1422f 100644 --- a/database/packages/pck_net_storage.pkb +++ b/database/packages/pck_net_storage.pkb @@ -2,6 +2,11 @@ create or replace package body pck_net_storage as c_log_module constant lg_app_log.log_module%type := 'NETZLAUFWERK'; + type t_object_path is record ( + path varchar2(1024) + ,filename varchar2(256) + ); + -- ==================== Private Helpers ==================== function f_build_url ( @@ -24,11 +29,11 @@ create or replace package body pck_net_storage as l_base varchar2(1024); begin l_base := 'https://objectstorage.' - || pck_system.f_get_par_wert_by_programmid('NET_STORAGE_REGION') + || pck_system.f_get_par_wert_by_programmid('NETSTORE_REGION') || '.oraclecloud.com/n/' - || pck_system.f_get_par_wert_by_programmid('NET_STORAGE_NAMESPACE') + || pck_system.f_get_par_wert_by_programmid('NETSTORE_NAMESPACE') || '/b/' - || pck_system.f_get_par_wert_by_programmid('NET_STORAGE_BUCKET'); + || pck_system.f_get_par_wert_by_programmid('NETSTORE_BUCKET'); if i_action is not null then @@ -42,6 +47,40 @@ create or replace package body pck_net_storage as end if; end f_build_url; + function f_split_object_key (i_object_key in varchar2) return t_object_path + /*Kopf------------------------------------------------------------------------------------------------ + -- Beschreibung: Extrahiert Pfad und Dateiname aus einem OCI-Objektschlüssel. + -- Bei Ordner-Keys (trailing Slash) wird der Ordnername als Dateiname zurückgegeben. + ------------------------------------------------------------------------------------------------------ + -- Parameter: i_object_key Vollständiger Objektschlüssel (z.B. mandant/Eingang/Import/datei.pdf) + ------------------------------------------------------------------------------------------------------ + -- Rückgabe: t_object_path Record mit path (inkl. trailing Slash) und filename + ------------------------------------------------------------------------------------------------------ + -- MA Datum Änderung + -- SCK 2026-04-09 Funktion erstellt + ------------------------------------------------------------------------------------------------Kopf*/ + is + l_key varchar2(1024); + l_pos number; + l_result t_object_path; + begin + -- Trailing Slash entfernen damit Ordner-Keys genauso behandelt werden wie Datei-Keys + l_key := rtrim(i_object_key, '/'); + + l_pos := instr(l_key, '/', -1); + + if l_pos > 0 + then + l_result.path := substr(l_key, 1, l_pos); + l_result.filename := substr(l_key, l_pos + 1); + else + l_result.path := null; + l_result.filename := l_key; + end if; + + return l_result; + end f_split_object_key; + procedure p_assert_allowed (i_object_key in varchar2) /*Kopf------------------------------------------------------------------------------------------------ -- Beschreibung: Prüft den Objektschlüssel auf Path-Traversal-Angriffe und Tenant-Scope. @@ -60,7 +99,7 @@ create or replace package body pck_net_storage as raise_application_error(-20004, 'Path traversal attempt detected'); end if; - l_tenant_prefix := pck_system.f_get_par_wert_by_programmid('NET_STORAGE_TENANT_ID'); + l_tenant_prefix := pck_system.f_get_par_wert_by_programmid('NETSTORE_TENANT_ID'); if l_tenant_prefix is not null and length(l_tenant_prefix) > 0 then @@ -81,7 +120,7 @@ create or replace package body pck_net_storage as /*Kopf------------------------------------------------------------------------------------------------ -- Beschreibung: Führt einen HTTP-Request gegen die OCI Object Storage API aus. -- Wertet den HTTP-Statuscode aus und löst bei Fehler einen Application Error aus. - -- Authentifizierung erfolgt über APEX Web Credential (NET_STORAGE_APEX_CREDENTIAL_ID). + -- Authentifizierung erfolgt über APEX Web Credential (NETSTORE_CRED_ID). ------------------------------------------------------------------------------------------------------ -- Parameter: i_method HTTP-Methode (GET, PUT, DELETE, POST, HEAD) -- i_url Vollständige Ziel-URL @@ -95,15 +134,16 @@ create or replace package body pck_net_storage as -- SCK 2026-04-08 Funktion erstellt ------------------------------------------------------------------------------------------------Kopf*/ is - l_headers apex_web_service.vc_arr2; - l_values apex_web_service.vc_arr2; l_response clob; l_status number; + l_header_index number := 1; begin + if i_content_type is not null then - l_headers(1) := 'Content-Type'; - l_values(1) := i_content_type; + apex_web_service.g_request_headers(l_header_index).name := 'Content-Type'; + apex_web_service.g_request_headers(l_header_index).value := i_content_type; + l_header_index := l_header_index + 1; end if; l_response := apex_web_service.make_rest_request( @@ -111,9 +151,7 @@ create or replace package body pck_net_storage as ,p_http_method => i_method ,p_body => coalesce(i_body_clob, empty_clob()) ,p_body_blob => coalesce(i_body_blob, empty_blob()) - ,p_http_headers => l_headers - ,p_http_values => l_values - ,p_credential_static_id => pck_system.f_get_par_wert_by_programmid('NET_STORAGE_APEX_CREDENTIAL_ID') + ,p_credential_static_id => pck_system.f_get_par_wert_by_programmid('NETSTORE_CRED_ID') ); l_status := apex_web_service.g_status_code; @@ -309,7 +347,7 @@ create or replace package body pck_net_storage as l_response := apex_web_service.make_rest_request_b( p_url => f_build_url(i_object_key) ,p_http_method => 'GET' - ,p_credential_static_id => pck_system.f_get_par_wert_by_programmid('NET_STORAGE_APEX_CREDENTIAL_ID') + ,p_credential_static_id => pck_system.f_get_par_wert_by_programmid('NETSTORE_CRED_ID') ); l_status := apex_web_service.g_status_code; @@ -345,21 +383,24 @@ create or replace package body pck_net_storage as ------------------------------------------------------------------------------------------------Kopf*/ is l_response clob; + l_obj_path t_object_path; begin pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES'); p_assert_allowed(i_object_key); - l_response := f_make_request( - i_method => 'PUT' - ,i_url => f_build_url(i_object_key) - ,i_body_blob => i_content - ,i_content_type => i_content_type - ); + -- TEST + --l_response := f_make_request( + -- i_method => 'PUT' + -- ,i_url => f_build_url(i_object_key) + -- ,i_body_blob => i_content + -- ,i_content_type => i_content_type + --); + l_obj_path := f_split_object_key(i_object_key); pck_log.p_info( i_module => c_log_module ,i_action => 'UPLOAD' - ,i_message => 'Datei hochgeladen' + ,i_message => 'Datei "' || l_obj_path.filename || '" hochgeladen | Ordner: ' || l_obj_path.path ,i_object_ref => i_object_key ); end p_upload_object; @@ -375,19 +416,22 @@ create or replace package body pck_net_storage as ------------------------------------------------------------------------------------------------Kopf*/ is l_response clob; + l_obj_path t_object_path; begin pck_mitarbeiterrecht.p_hat_recht('ADMIN'); p_assert_allowed(i_object_key); - l_response := f_make_request( - i_method => 'DELETE' - ,i_url => f_build_url(i_object_key) - ); + -- TEST + --l_response := f_make_request( + -- i_method => 'DELETE' + -- ,i_url => f_build_url(i_object_key) + --); + l_obj_path := f_split_object_key(i_object_key); pck_log.p_info( i_module => c_log_module ,i_action => 'DELETE' - ,i_message => 'Datei gelöscht' + ,i_message => 'Datei "' || l_obj_path.filename || '" gelöscht | Ordner: ' || l_obj_path.path ,i_object_ref => i_object_key ); end p_delete_object; @@ -405,34 +449,37 @@ create or replace package body pck_net_storage as is l_objects t_net_storage_tab; l_response clob; + l_obj_path t_object_path; begin pck_mitarbeiterrecht.p_hat_recht('ADMIN'); p_assert_allowed(i_prefix); + -- TEST -- Alle Objekte im Prefix auflisten (kein Delimiter = rekursiv, alle Tiefen) - l_objects := f_list_objects_internal( - i_prefix => i_prefix - ,i_delimiter => '' - ,i_start_with => null - ,i_limit => 0 - ); + --l_objects := f_list_objects_internal( + -- i_prefix => i_prefix + -- ,i_delimiter => '' + -- ,i_start_with => null + -- ,i_limit => 0 + --); -- Nur echte Objekte löschen, keine Pseudo-Ordner - for rec in (select object_name, is_folder from table(l_objects)) - loop - if rec.is_folder = 'N' - then - l_response := f_make_request( - i_method => 'DELETE' - ,i_url => f_build_url(rec.object_name) - ); - end if; - end loop; + --for rec in (select object_name, is_folder from table(l_objects)) + --loop + -- if rec.is_folder = 'N' + -- then + -- l_response := f_make_request( + -- i_method => 'DELETE' + -- ,i_url => f_build_url(rec.object_name) + -- ); + -- end if; + --end loop; + l_obj_path := f_split_object_key(i_prefix); pck_log.p_info( i_module => c_log_module ,i_action => 'DELETE_FOLDER' - ,i_message => 'Ordner rekursiv gelöscht' + ,i_message => 'Ordner "' || l_obj_path.filename || '" rekursiv gelöscht | Pfad: ' || l_obj_path.path ,i_object_ref => i_prefix ); end p_delete_folder; @@ -456,6 +503,7 @@ create or replace package body pck_net_storage as l_new_key varchar2(1024); l_body clob; l_response clob; + l_obj_path t_object_path; begin pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES'); p_assert_allowed(i_object_key); @@ -478,17 +526,19 @@ create or replace package body pck_net_storage as into l_body from dual; - l_response := f_make_request( - i_method => 'POST' - ,i_url => f_build_url(i_action => 'renameObject') - ,i_body_clob => l_body - ,i_content_type => 'application/json' - ); + -- TEST + --l_response := f_make_request( + -- i_method => 'POST' + -- ,i_url => f_build_url(i_action => 'renameObject') + -- ,i_body_clob => l_body + -- ,i_content_type => 'application/json' + --); + l_obj_path := f_split_object_key(i_object_key); pck_log.p_info( i_module => c_log_module ,i_action => 'RENAME' - ,i_message => 'Datei umbenannt: ' || i_object_key || ' -> ' || l_new_key + ,i_message => 'Datei "' || l_obj_path.filename || '" umbenannt in "' || i_new_name || '" | Ordner: ' || l_obj_path.path ,i_object_ref => i_object_key ); end p_rename_object; @@ -513,6 +563,7 @@ create or replace package body pck_net_storage as l_new_key varchar2(1024); l_body clob; l_response clob; + l_obj_path t_object_path; begin pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES'); p_assert_allowed(i_object_key); @@ -535,17 +586,19 @@ create or replace package body pck_net_storage as into l_body from dual; - l_response := f_make_request( - i_method => 'POST' - ,i_url => f_build_url(i_action => 'renameObject') - ,i_body_clob => l_body - ,i_content_type => 'application/json' - ); + -- TEST + --l_response := f_make_request( + -- i_method => 'POST' + -- ,i_url => f_build_url(i_action => 'renameObject') + -- ,i_body_clob => l_body + -- ,i_content_type => 'application/json' + --); + l_obj_path := f_split_object_key(i_object_key); pck_log.p_info( i_module => c_log_module ,i_action => 'MOVE' - ,i_message => 'Datei verschoben: ' || i_object_key || ' -> ' || l_new_key + ,i_message => 'Datei "' || l_obj_path.filename || '" verschoben | Von: ' || l_obj_path.path || ' | Nach: ' || i_target_prefix ,i_object_ref => i_object_key ); end p_move_object; @@ -567,6 +620,7 @@ create or replace package body pck_net_storage as is l_folder_key varchar2(1024); l_response clob; + l_obj_path t_object_path; begin pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES'); p_assert_allowed(i_prefix); @@ -575,17 +629,19 @@ create or replace package body pck_net_storage as l_folder_key := i_prefix || i_folder_name || '/'; p_assert_allowed(l_folder_key); - l_response := f_make_request( - i_method => 'PUT' - ,i_url => f_build_url(l_folder_key) - ,i_body_blob => empty_blob() - ,i_content_type => 'application/octet-stream' - ); + -- TEST + --l_response := f_make_request( + -- i_method => 'PUT' + -- ,i_url => f_build_url(l_folder_key) + -- ,i_body_blob => empty_blob() + -- ,i_content_type => 'application/octet-stream' + --); + l_obj_path := f_split_object_key(l_folder_key); pck_log.p_info( i_module => c_log_module ,i_action => 'CREATE_FOLDER' - ,i_message => 'Ordner angelegt' + ,i_message => 'Ordner "' || l_obj_path.filename || '" angelegt | Pfad: ' || l_obj_path.path ,i_object_ref => l_folder_key ); end p_create_folder; diff --git a/database/tables/lg_app_log.tab b/database/tables/lg_app_log.tab index bdc1019..d7ef2a2 100644 --- a/database/tables/lg_app_log.tab +++ b/database/tables/lg_app_log.tab @@ -12,5 +12,5 @@ create table lg_app_log ( ); alter table lg_app_log - add constraint pk_lg_app_log primary key (lg_app_log_id) + add constraint pk_lg_app_log primary key (log_id) using index enable; diff --git a/database/tests/app_log_tests.sql b/database/tests/app_log_tests.sql new file mode 100644 index 0000000..823afa1 --- /dev/null +++ b/database/tests/app_log_tests.sql @@ -0,0 +1,61 @@ +select * +from lg_app_log +; + +--truncate table lg_app_log; + +/* + Test Calls für logs + */ +begin + -- Neuen Batch-Ordner für heutigen Import anlegen + pck_net_storage.p_create_folder( + i_prefix => 'testmandant-42/Eingang/Verarbeitet 2026/' + ,i_folder_name => 'Batch-2026-04-09' + ); + + -- Drei PDFs kommen im Import an + pck_net_storage.p_upload_object( + i_object_key => 'testmandant-42/Eingang/Import/scan_001.pdf' + ,i_content => empty_blob() + ,i_content_type => 'application/pdf' + ); + pck_net_storage.p_upload_object( + i_object_key => 'testmandant-42/Eingang/Import/scan_002.pdf' + ,i_content => empty_blob() + ,i_content_type => 'application/pdf' + ); + pck_net_storage.p_upload_object( + i_object_key => 'testmandant-42/Eingang/Import/scan_003.pdf' + ,i_content => empty_blob() + ,i_content_type => 'application/pdf' + ); + + -- Scans werden nach Sichtung benannt + pck_net_storage.p_rename_object( + i_object_key => 'testmandant-42/Eingang/Import/scan_001.pdf' + ,i_new_name => 'rechnung_mueller_2026-04.pdf' + ); + pck_net_storage.p_rename_object( + i_object_key => 'testmandant-42/Eingang/Import/scan_002.pdf' + ,i_new_name => 'angebot_huber_2026-04.pdf' + ); + + -- scan_003 ist ein Duplikat, wird gelöscht + pck_net_storage.p_delete_object( + i_object_key => 'testmandant-42/Eingang/Import/scan_003.pdf' + ); + + -- Verarbeitete Dateien in den Batch-Ordner verschieben + pck_net_storage.p_move_object( + i_object_key => 'testmandant-42/Eingang/Import/rechnung_mueller_2026-04.pdf' + ,i_target_prefix => 'testmandant-42/Eingang/Verarbeitet 2026/Batch-2026-04-09/' + ); + pck_net_storage.p_move_object( + i_object_key => 'testmandant-42/Eingang/Import/angebot_huber_2026-04.pdf' + ,i_target_prefix => 'testmandant-42/Eingang/Verarbeitet 2026/Batch-2026-04-09/' + ); + + commit; +end; +/