* test_s5_rollback_real.prg * Inchide golul "calea de ROLLBACK la esec partial" din docs\livrare_s5.md, sectiunea 5: pana acum * niciun test nu provocase deliberat un esec in mijlocul ScrieArticoleFacturaEditate (ofacturare_editare.prg) * pe date reale - comportamentul de rollback era verificat doar static, prin simetria codului. * * PARTEA M (mock, fara Oracle) - completeaza test_s5_validari_articole.prg/B3 (care opreste esecul * la a DOUA comanda, adica in mijlocul buclei UPDATE a liniilor pastrate): aici esecul e la PRIMA * comanda (chiar update-ul "marcheaza tot sters", primul pas al helperului) - dovedeste ca daca * pasul 1 esueaza, functia se opreste imediat, FARA nicio comanda de UPDATE/INSERT/recalcul. * * PARTEA R (real, Oracle) - NU apeleaza ScrieArticoleFacturaEditate direct (vezi ATENTIE mai jos), * ci reproduce EXACT aceeasi secventa SQL pe care helperul ar genera-o pentru un caz cu esec la * INSERT (id_articol inexistent, FK_VANZARE_DET002), prin SQLEXEC brut, in tranzactia manuala * deja folosita de test_s5_scriere_reala.prg. Dovedeste, pe id_vanzare = 1049: * 1) pasii 1-2 ai helperului (marcheaza tot sters, invie+actualizeaza liniile pastrate) chiar * produc starea asteptata NECOMISA, verificata pe aceeasi conexiune; * 2) o linie noua VALIDA se INSEREaza cu succes, tot necomis - proba de executie PARTIALA reala; * 3) o linie noua cu id_articol inexistent arunca eroarea Oracle reala (ORA-02291) - exact * punctul unde helperul s-ar opri (EXIT din SCAN al pasului 3, recalculeaza_totaluri_vanzari * NU s-ar mai apela); * 4) tranzactia se inchide cu ROLLBACK (tiparul apelantilor: lnSucces=Iif(...,1,-1), apoi * MyInchideTranzactie(Iif(lnSucces<0,2,1))); * 5) verificat INDEPENDENT prin sqlplus, dupa terminarea procesului VFP: VANZARI_DETALII pentru * 1049 are EXACT acelasi continut (id_vanzare_det/sters/cantitate/pret/pret_achizitie) ca * inainte de test - nimic din pasii 1-3 nu a supravietuit. * * ATENTIE - GASIT IN TIMPUL ACESTUI TEST, NU REPARAT (cod de productie interzis pentru acest agent): * apelul goExecutor.oExecuta() - EXACT calea Oracle folosita de ScrieArticoleFacturaEditate * pentru toate comenzile ei - AGATA procesul VFP headless la o eroare Oracle REALA (nu simulata), * sub tranzactie manuala (SQLSetprop Transactions=2), chiar daca Oracle a raspuns deja cu eroarea * (sesiunea Oracle arata INACTIVE/"SQL*Net message from client", 0 sesiuni blocante, nicio fereastra * de dialog Windows - EnumWindows arata doar fereastra principala). Reprodus de doua ori izolat, cu * un script minimal (~15 linii, test_repro_hang_fk.prg, sters dupa investigatie), inclusiv fara nicio * legatura cu ScrieArticoleFacturaEditate - deci NU e un bug al helperului, ci al executorului comun * (COMUN\programe\oproceduri_comune.prg, clasa oexecutor, PROCEDURE oExecute). SQLEXEC brut (fara * goExecutor) pe ACEEASI comanda intoarce eroarea instant (lnRes=-1, AERROR populat) - vezi * test_repro_hang_fk_raw.prg (sters dupa investigatie). Suspiciune, neconfirmata: ramurile de eroare * din oExecute apeleaza necondiotionat goLog.Log() cand Type('goLog')='O' - posibil sa incerce o * comanda Oracle proprie pe aceeasi conexiune, aflata deja sub tranzactie manuala. Consecinta pentru * livrare: comportamentul REAL al aplicatiei (nu doar al helperului izolat) la un esec Oracle propriu-zis * in acest lant de scriere NU a putut fi verificat capat-la-capat prin apelul functiei de productie - * doar prin reproducerea manuala a secventei ei SQL. De raportat separat echipei, nu de reparat aici. * * Harness identic cu test_s5_scriere_reala.prg: do_deschide/inchide_tranzactie reproduse inline dupa * _frm_base.vc2:252-302. Nu atinge cod de aplicatie. QUIT la final. SET SAFETY OFF SET TALK OFF LOCAL lcLog lcLog = 'D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\test_s5_rollback_real_log.txt' STRTOFILE('start ' + TTOC(DATETIME()) + CHR(13) + CHR(10), lcLog) PUBLIC gcErrLog, gcTestLog, gnPass, gnFail gcErrLog = lcLog gcTestLog = lcLog gnPass = 0 gnFail = 0 ON ERROR DO test_error_handler WITH ERROR(), MESSAGE(), PROGRAM(), LINENO() SET PROCEDURE TO D:\ROA\ROAFACTURARE\COMUN\utile\Teste\mock_amessagebox.prg ADDITIVE DO ('D:\ROA\ROAFACTURARE\COMUN\utile\Teste\test_init_env_auto.prg') WITH 'CENTRAL', 'MARIUSM_AUTO', 'ROMFASTSOFT' IF gnHandle <= 0 STRTOFILE('EROARE: conectare esuata' + CHR(13) + CHR(10), lcLog, 1) QUIT ENDIF SET PROCEDURE TO D:\ROA\ROAFACTURARE\COMUN\programe\ofacturare_editare.prg ADDITIVE PUBLIC gnIdVanzareTest gnIdVanzareTest = 1049 DO parte_m_mock WITH lcLog DO parte_r_real WITH lcLog, gnIdVanzareTest STRTOFILE(CHR(13) + CHR(10) + 'REZULTAT: ' + TRANSFORM(gnPass) + ' PASS / ' + TRANSFORM(gnFail) + ' FAIL' + CHR(13) + CHR(10), lcLog, 1) STRTOFILE('done ' + TTOC(DATETIME()) + CHR(13) + CHR(10), lcLog, 1) QUIT PROCEDURE asserteaza LPARAMETERS tcNume, tlConditie, tcValoare IF m.tlConditie gnPass = gnPass + 1 STRTOFILE('PASS ' + m.tcNume + ' [' + m.tcValoare + ']' + CHR(13) + CHR(10), gcTestLog, 1) ELSE gnFail = gnFail + 1 STRTOFILE('FAIL ' + m.tcNume + ' [' + m.tcValoare + ']' + CHR(13) + CHR(10), gcTestLog, 1) ENDIF ENDPROC *-- executa SELECT-ul prin goExecutor (cai care CITESC - fara risc, vezi ATENTIE din antet) si *-- intoarce codul de succes; cursorul vechi se inchide intai PROCEDURE ExecSel LPARAMETERS tcSql, tcCursor LOCAL lnQ IF USED(tcCursor) USE IN (tcCursor) ENDIF lnQ = goExecutor.oExecute(tcSql, tcCursor) RETURN lnQ ENDPROC *-- reproduce _frm_base.vc2:252-265 (do_deschide_tranzactie), fara sa instantieze niciun formular PROCEDURE MyDeschideTranzactie LOCAL llReturn, lnSuccesLocal IF TYPE('goExecutor') = 'O' goExecutor.oExecuta([select * from dual]) ENDIF lnSuccesLocal = SQLSetprop(gnHandle, "Transactions", 2) llReturn = IIF(lnSuccesLocal < 0, .F., .T.) RETURN llReturn ENDPROC *-- reproduce _frm_base.vc2:279-302 (do_inchide_tranzactie), fara sa instantieze niciun formular PROCEDURE MyInchideTranzactie LPARAMETERS tnTip LOCAL llReturn, lnSuccesLocal IF tnTip = 1 lnSuccesLocal = SQLCOMMIT(gnHandle) ELSE lnSuccesLocal = SQLROLLBACK(gnHandle) ENDIF IF lnSuccesLocal < 0 llReturn = .F. ELSE lnSuccesLocal = SQLSetprop(gnHandle, "Transactions", 1) llReturn = IIF(lnSuccesLocal < 0, .F., .T.) ENDIF RETURN llReturn ENDPROC *===================================================================================== *-- PARTEA M: mock pe goExecutor - esec chiar la PRIMA comanda (pasul 1, "marcheaza tot sters") *===================================================================================== PROCEDURE parte_m_mock LPARAMETERS tcLog STRTOFILE(CHR(13) + CHR(10) + '========== PARTEA M - esec mock la prima comanda (pasul 1) ==========' + CHR(13) + CHR(10), tcLog, 1) LOCAL loExecutorReal loExecutorReal = goExecutor *-- obiectul mock se construieste O SINGURA DATA, inline in program - vezi capcana l, testare-ui-vfp.md goExecutor = CREATEOBJECT('dummyexecutorrollback') goExecutor.nFailAtCall = 1 IF USED('crstvdrb') USE IN crstvdrb ENDIF CreeazaCursorArticoleGol('crstvdrb') SELECT crstvdrb APPEND BLANK REPLACE id_vanzare_det WITH 701, id_articol WITH 1, cantitate WITH 1, pret WITH 1, sters WITH 0 IN crstvdrb IF USED('crstvanzrb') USE IN crstvanzrb ENDIF CREATE CURSOR crstvanzrb (id_vanzare I, discount N(18,4) NULL) INSERT INTO crstvanzrb (id_vanzare, discount) VALUES (99999, NULL) LOCAL llRet llRet = ScrieArticoleFacturaEditate(99999, 'crstvdrb', 'crstvanzrb') DO asserteaza WITH 'M1: esec la prima comanda (pasul 1) -> functia intoarce .F. imediat', llRet == .F., TRANSFORM(llRet) DO asserteaza WITH 'M2: esec la prima comanda -> NICIO alta comanda incercata (1 comanda total, nu update/insert/recalcul)', ; goExecutor.nApeluri == 1, 'nApeluri=' + TRANSFORM(goExecutor.nApeluri) goExecutor = loExecutorReal IF USED('crstvdrb') USE IN crstvdrb ENDIF IF USED('crstvanzrb') USE IN crstvanzrb ENDIF IF USED('crsMockSqlRb') USE IN crsMockSqlRb ENDIF ENDPROC *===================================================================================== *-- PARTEA R: aceeasi secventa SQL pe care ScrieArticoleFacturaEditate ar genera-o pentru un caz cu *-- esec la INSERT, prin SQLEXEC brut (vezi ATENTIE din antetul fisierului - goExecutor.oExecuta pe *-- o comanda care esueaza real agata procesul, deci NU e folosit aici pentru comenzile de scriere) *===================================================================================== PROCEDURE parte_r_real LPARAMETERS tcLog, tnIdVanzare LOCAL lcSql, lnQ, lnRes, lnArtLipsa, lnArtValid LOCAL lnDet1, lnDet2, lnDet3, lnCant1Inainte, lnPret1 LOCAL lnNrLiniiInainte, lcSnapshotInainte, lcSnapshotDupa LOCAL lnCodInainte, lnCodDupa, lnNrLiniiInTranz, lnStersDet1InTranz LOCAL lnSuccesFinal, lnNrLiniiDupa STRTOFILE(CHR(13) + CHR(10) + '========== PARTEA R - esec Oracle real (FK id_articol), rollback (id_vanzare=' + ; TRANSFORM(tnIdVanzare) + ') ==========' + CHR(13) + CHR(10), tcLog, 1) *-- id_articol garantat inexistent - verificat inainte de scriere, nu presupus lnArtLipsa = 999999999 lnQ = ExecSel([select count(*) as nr from nom_articole where id_articol = ] + TRANSFORM(lnArtLipsa), 'v_chk') DO asserteaza WITH 'R0: id_articol folosit pentru esec chiar NU exista in NOM_ARTICOLE', ; lnQ > 0 AND v_chk.nr = 0, 'nr=' + IIF(lnQ > 0, TRANSFORM(v_chk.nr), 'eroare citire') IF USED('v_chk') USE IN v_chk ENDIF *-- starea INAINTE, citita direct din Oracle lcSql = [select cod from vanzari where id_vanzare = ] + TRANSFORM(tnIdVanzare) lnQ = ExecSel(lcSql, 'v_vz0') IF lnQ <= 0 OR RECCOUNT('v_vz0') <> 1 DO asserteaza WITH 'R0: VANZARI citit', .F., 'lnQ=' + TRANSFORM(lnQ) RETURN ENDIF lnCodInainte = v_vz0.cod USE IN v_vz0 lcSql = [select id_vanzare_det, id_articol, nvl(sters,0) as sters, nvl(cantitate,0) as cantitate, ] + ; [nvl(pret,0) as pret, nvl(pret_achizitie,0) as pret_achizitie ] + ; [from vanzari_detalii where id_vanzare = ] + TRANSFORM(tnIdVanzare) + [ order by id_vanzare_det] lnQ = ExecSel(lcSql, 'v_lin0') IF lnQ <= 0 DO asserteaza WITH 'R0: VANZARI_DETALII citit', .F., 'lnQ=' + TRANSFORM(lnQ) RETURN ENDIF lnNrLiniiInainte = RECCOUNT('v_lin0') lcSnapshotInainte = '' lnDet1 = 0 lnDet2 = 0 lnDet3 = 0 SELECT v_lin0 SCAN IF sters = 0 DO CASE CASE lnDet1 = 0 lnDet1 = id_vanzare_det lnCant1Inainte = cantitate lnPret1 = pret CASE lnDet2 = 0 lnDet2 = id_vanzare_det CASE lnDet3 = 0 lnDet3 = id_vanzare_det ENDCASE ENDIF lcSnapshotInainte = lcSnapshotInainte + TRANSFORM(id_vanzare_det) + ':' + TRANSFORM(sters) + ':' + ; TRANSFORM(cantitate) + ':' + TRANSFORM(pret) + ':' + TRANSFORM(pret_achizitie) + '|' ENDSCAN GO TOP IN v_lin0 lnArtValid = v_lin0.id_articol USE IN v_lin0 STRTOFILE(' INAINTE: cod=' + TRANSFORM(lnCodInainte) + ' linii=' + TRANSFORM(lnNrLiniiInainte) + ; ' pastrate=' + TRANSFORM(lnDet1) + '/' + TRANSFORM(lnDet2) + '/' + TRANSFORM(lnDet3) + ; ' snapshot=[' + lcSnapshotInainte + ']' + CHR(13) + CHR(10), tcLog, 1) DO asserteaza WITH 'R0: documentul are cel putin 3 linii active (pastrate)', lnDet1 > 0 AND lnDet2 > 0 AND lnDet3 > 0, ; TRANSFORM(lnDet1) + '/' + TRANSFORM(lnDet2) + '/' + TRANSFORM(lnDet3) IF lnDet1 = 0 OR lnDet2 = 0 OR lnDet3 = 0 RETURN ENDIF *-- lantul de scriere, tiparul exact al apelantilor: pornire tranzactie -> comenzi -> esec -> ROLLBACK lnSuccesFinal = 1 TRY IF !MyDeschideTranzactie() DO asserteaza WITH 'R1: tranzactia s-a deschis', .F., 'SQLSetprop a esuat' RETURN ENDIF *-- PASUL 1 al helperului: marcheaza tot sters (ofacturare_editare.prg:489-490) lcSql = [update vanzari_detalii set sters = 1, id_utils = ] + TRANSFORM(gnIdUtil) + [, dataoras = sysdate ] + ; [where id_vanzare = ] + TRANSFORM(tnIdVanzare) + [ and sters = 0] lnRes = SQLEXEC(gnHandle, lcSql) STRTOFILE(' PAS1 marcheaza tot sters => ' + TRANSFORM(lnRes) + CHR(13) + CHR(10), tcLog, 1) IF lnRes < 0 lnSuccesFinal = -1 ENDIF *-- PASUL 2 al helperului: invie+actualizeaza liniile pastrate (:498-501) - cele 3 identificate mai sus, *-- prima cu cantitatea dublata (ca sa se poata verifica direct efectul UPDATE-ului) IF lnSuccesFinal > 0 lcSql = [update vanzari_detalii set sters = 0, cantitate = ] + TRANSFORM(lnCant1Inainte * 2) + ; [, pret = ] + TRANSFORM(lnPret1) + [, pret_cu_tva = 0, id_utils = ] + TRANSFORM(gnIdUtil) + ; [, dataoras = sysdate where id_vanzare_det = ] + TRANSFORM(lnDet1) + [ and id_vanzare = ] + TRANSFORM(tnIdVanzare) lnRes = SQLEXEC(gnHandle, lcSql) STRTOFILE(' PAS2 update linie pastrata det=' + TRANSFORM(lnDet1) + ' => ' + TRANSFORM(lnRes) + CHR(13) + CHR(10), tcLog, 1) IF lnRes < 0 lnSuccesFinal = -1 ENDIF ENDIF IF lnSuccesFinal > 0 lcSql = [update vanzari_detalii set sters = 0, id_utils = ] + TRANSFORM(gnIdUtil) + [, dataoras = sysdate ] + ; [where id_vanzare_det = ] + TRANSFORM(lnDet2) + [ and id_vanzare = ] + TRANSFORM(tnIdVanzare) lnRes = SQLEXEC(gnHandle, lcSql) STRTOFILE(' PAS2 update linie pastrata det=' + TRANSFORM(lnDet2) + ' => ' + TRANSFORM(lnRes) + CHR(13) + CHR(10), tcLog, 1) IF lnRes < 0 lnSuccesFinal = -1 ENDIF ENDIF IF lnSuccesFinal > 0 lcSql = [update vanzari_detalii set sters = 0, id_utils = ] + TRANSFORM(gnIdUtil) + [, dataoras = sysdate ] + ; [where id_vanzare_det = ] + TRANSFORM(lnDet3) + [ and id_vanzare = ] + TRANSFORM(tnIdVanzare) lnRes = SQLEXEC(gnHandle, lcSql) STRTOFILE(' PAS2 update linie pastrata det=' + TRANSFORM(lnDet3) + ' => ' + TRANSFORM(lnRes) + CHR(13) + CHR(10), tcLog, 1) IF lnRes < 0 lnSuccesFinal = -1 ENDIF ENDIF *-- proba intermediara: starea NECOMISA dupa pasii 1-2, pe aceeasi conexiune IF lnSuccesFinal > 0 lcSql = [select count(*) as nr from vanzari_detalii where id_vanzare = ] + TRANSFORM(tnIdVanzare) + [ and sters = 0] lnQ = ExecSel(lcSql, 'v_activ') lnNrLiniiInTranz = IIF(lnQ > 0, v_activ.nr, -1) USE IN v_activ lcSql = [select nvl(sters,0) as sters, cantitate from vanzari_detalii where id_vanzare_det = ] + TRANSFORM(lnDet1) lnQ = ExecSel(lcSql, 'v_det1') lnStersDet1InTranz = IIF(lnQ > 0, v_det1.sters, -1) STRTOFILE(' IN TRANZACTIE dupa pasii 1-2: linii active=' + TRANSFORM(lnNrLiniiInTranz) + ' det=' + TRANSFORM(lnDet1) + ; ' sters=' + TRANSFORM(lnStersDet1InTranz) + ' cantitate=' + IIF(lnQ > 0, TRANSFORM(v_det1.cantitate), '?') + CHR(13) + CHR(10), tcLog, 1) USE IN v_det1 DO asserteaza WITH 'R2: dupa pasii 1-2, cele 3 linii pastrate sunt active (sters=0), NECOMIS', ; lnNrLiniiInTranz = 3, 'linii active in tranzactie=' + TRANSFORM(lnNrLiniiInTranz) DO asserteaza WITH 'R3: linia pastrata are cantitatea actualizata deja, NECOMIS (proba de executie partiala)', ; lnStersDet1InTranz = 0, 'sters=' + TRANSFORM(lnStersDet1InTranz) ENDIF *-- PASUL 3 al helperului: INSERT linii noi (:519-530) - prima VALIDA, trebuie sa reuseasca IF lnSuccesFinal > 0 lcSql = [insert into vanzari_detalii (id_vanzare, id_articol, cantitate, pret, pret_cu_tva, proc_tvav, ] + ; [id_gestiune, pret_achizitie, id_utils, dataoras) values (] + TRANSFORM(tnIdVanzare) + [,] + TRANSFORM(lnArtValid) + ; [,1,33.33,0,1.21,-1000,44.44,] + TRANSFORM(gnIdUtil) + [,sysdate)] lnRes = SQLEXEC(gnHandle, lcSql) STRTOFILE(' PAS3 insert linie noua VALIDA (id_articol=' + TRANSFORM(lnArtValid) + ') => ' + TRANSFORM(lnRes) + CHR(13) + CHR(10), tcLog, 1) IF lnRes < 0 lnSuccesFinal = -1 ENDIF ENDIF *-- proba intermediara: linia noua e deja acolo, NECOMIS IF lnSuccesFinal > 0 lcSql = [select count(*) as nr from vanzari_detalii where id_vanzare = ] + TRANSFORM(tnIdVanzare) lnQ = ExecSel(lcSql, 'v_cnt') lnNrLiniiInTranz = IIF(lnQ > 0, v_cnt.nr, -1) USE IN v_cnt STRTOFILE(' IN TRANZACTIE dupa insertul valid: total linii=' + TRANSFORM(lnNrLiniiInTranz) + ' (erau ' + TRANSFORM(lnNrLiniiInainte) + ')' + CHR(13) + CHR(10), tcLog, 1) DO asserteaza WITH 'R4: linia noua VALIDA a fost inserata deja, NECOMIS (proba de executie partiala)', ; lnNrLiniiInTranz = lnNrLiniiInainte + 1, 'linii=' + TRANSFORM(lnNrLiniiInTranz) ENDIF *-- PASUL 3, a doua linie: id_articol inexistent - trebuie sa ARUNCE ORA-02291 si sa opreasca aici *-- (exact ce ar face SCAN-ul helperului: EXIT la primul esec, fara recalculeaza_totaluri_vanzari) IF lnSuccesFinal > 0 lcSql = [insert into vanzari_detalii (id_vanzare, id_articol, cantitate, pret, pret_cu_tva, proc_tvav, ] + ; [id_gestiune, pret_achizitie, id_utils, dataoras) values (] + TRANSFORM(tnIdVanzare) + [,] + TRANSFORM(lnArtLipsa) + ; [,1,1,0,1.21,-1000,1,] + TRANSFORM(gnIdUtil) + [,sysdate)] lnRes = SQLEXEC(gnHandle, lcSql) STRTOFILE(' PAS3 insert linie noua INVALIDA (id_articol=' + TRANSFORM(lnArtLipsa) + ') => ' + TRANSFORM(lnRes) + CHR(13) + CHR(10), tcLog, 1) IF lnRes < 0 LOCAL laErrRb(1) AERROR(laErrRb) STRTOFILE(' eroare Oracle: ' + TRANSFORM(laErrRb(2)) + CHR(13) + CHR(10), tcLog, 1) DO asserteaza WITH 'R5: insertul invalid arunca eroarea asteptata (FK_VANZARE_DET002 / ORA-02291)', ; 'FK_VANZARE_DET002' $ TRANSFORM(laErrRb(2)) OR 'ORA-02291' $ TRANSFORM(laErrRb(2)), TRANSFORM(laErrRb(2)) lnSuccesFinal = -1 ELSE DO asserteaza WITH 'R5: insertul invalid a esuat asa cum era asteptat', .F., 'lnRes=' + TRANSFORM(lnRes) + ' (nu a esuat!)' ENDIF ENDIF *-- pasul 4 al helperului (recalculeaza_totaluri_vanzari) NU se apeleaza aici - exact ca in helper, *-- care s-ar opri la EXIT-ul din SCAN inainte sa ajunga la el *-- tiparul apelantilor: lnSucces=Iif(...,1,-1) -> MyInchideTranzactie(Iif(lnSucces<0,2,1)) IF MyInchideTranzactie(IIF(lnSuccesFinal < 0, 2, 1)) STRTOFILE(' tranzactie inchisa cu ' + IIF(lnSuccesFinal < 0, 'ROLLBACK', 'COMMIT') + CHR(13) + CHR(10), tcLog, 1) ELSE STRTOFILE(' FAIL la inchiderea tranzactiei' + CHR(13) + CHR(10), tcLog, 1) ENDIF CATCH TO loExc STRTOFILE(' EXCEPTIE: ' + TRANSFORM(loExc.ErrorNo) + ' [' + loExc.Message + '] ' + ; loExc.Procedure + ':' + TRANSFORM(loExc.LineNo) + ' -> ROLLBACK' + CHR(13) + CHR(10), tcLog, 1) lnSuccesFinal = -99 MyInchideTranzactie(2) ENDTRY DO asserteaza WITH 'R6: secventa s-a oprit la insertul invalid (lnSuccesFinal<0)', lnSuccesFinal < 0, 'lnSuccesFinal=' + TRANSFORM(lnSuccesFinal) DO asserteaza WITH 'R7: tranzactia s-a inchis cu ROLLBACK (nu COMMIT)', lnSuccesFinal < 0, 'lnSuccesFinal=' + TRANSFORM(lnSuccesFinal) *-- verificarea de FOND: dupa rollback, documentul e EXACT cum era - pe aceeasi conexiune (prima *-- linie de aparare); verificarea INDEPENDENTA, prin sqlplus dupa QUIT, e cea care conteaza lcSql = [select cod from vanzari where id_vanzare = ] + TRANSFORM(tnIdVanzare) lnQ = ExecSel(lcSql, 'v_vz1') lnCodDupa = IIF(lnQ > 0, v_vz1.cod, -1) USE IN v_vz1 DO asserteaza WITH 'R8: dupa ROLLBACK, VANZARI.COD e neschimbat', lnCodDupa = lnCodInainte, TRANSFORM(lnCodInainte) + ' -> ' + TRANSFORM(lnCodDupa) lcSql = [select count(*) as nr from vanzari_detalii where id_vanzare = ] + TRANSFORM(tnIdVanzare) lnQ = ExecSel(lcSql, 'v_cnt1') lnNrLiniiDupa = IIF(lnQ > 0, v_cnt1.nr, -1) USE IN v_cnt1 DO asserteaza WITH 'R9: dupa ROLLBACK, numarul de linii ale documentului a revenit la valoarea initiala', ; lnNrLiniiDupa = lnNrLiniiInainte, 'dupa=' + TRANSFORM(lnNrLiniiDupa) + ' inainte=' + TRANSFORM(lnNrLiniiInainte) lcSql = [select id_vanzare_det, nvl(sters,0) as sters, nvl(cantitate,0) as cantitate, ] + ; [nvl(pret,0) as pret, nvl(pret_achizitie,0) as pret_achizitie ] + ; [from vanzari_detalii where id_vanzare = ] + TRANSFORM(tnIdVanzare) + [ order by id_vanzare_det] lnQ = ExecSel(lcSql, 'v_lin1') lcSnapshotDupa = '' IF lnQ > 0 SELECT v_lin1 SCAN lcSnapshotDupa = lcSnapshotDupa + TRANSFORM(id_vanzare_det) + ':' + TRANSFORM(sters) + ':' + ; TRANSFORM(cantitate) + ':' + TRANSFORM(pret) + ':' + TRANSFORM(pret_achizitie) + '|' ENDSCAN ENDIF IF USED('v_lin1') USE IN v_lin1 ENDIF STRTOFILE(' DUPA ROLLBACK: snapshot=[' + lcSnapshotDupa + ']' + CHR(13) + CHR(10), tcLog, 1) DO asserteaza WITH 'R10: dupa ROLLBACK, snapshot-ul liniilor (det:sters:cant:pret:pret_ach) e IDENTIC cu cel de dinaintea testului', ; lcSnapshotDupa == lcSnapshotInainte, 'dupa=[' + lcSnapshotDupa + ']' ENDPROC PROCEDURE test_error_handler LPARAMETERS tnError, tcMessage, tcProgram, tnLineNo STRTOFILE('EROARE ' + TRANSFORM(tnError) + ' [' + tcProgram + ':' + TRANSFORM(tnLineNo) + '] ' + tcMessage + CHR(13) + CHR(10), gcErrLog, 1) ENDPROC *-- mock de goExecutor pentru PARTEA M - esueaza la apelul cu numarul nFailAtCall (implicit 1 = chiar *-- prima comanda); jurnalizeaza in crsMockSqlRb, separat de crsMockSql (folosit de alta suita) DEFINE CLASS dummyexecutorrollback AS Custom cEroare = '' nFailAtCall = 1 nApeluri = 0 PROCEDURE oExecuta LPARAMETERS tcSql, tcCursor LOCAL lnUrmator This.nApeluri = This.nApeluri + 1 IF !USED('crsMockSqlRb') CREATE CURSOR crsMockSqlRb (nr I, sql M) ENDIF SELECT crsMockSqlRb lnUrmator = RECCOUNT('crsMockSqlRb') + 1 INSERT INTO crsMockSqlRb (nr, sql) VALUES (m.lnUrmator, m.tcSql) IF !EMPTY(m.tcCursor) IF USED(m.tcCursor) USE IN (m.tcCursor) ENDIF CREATE CURSOR (m.tcCursor) (id_temp N(1)) ENDIF IF This.nFailAtCall > 0 AND This.nApeluri == This.nFailAtCall This.cEroare = 'esec simulat de test la apelul ' + TRANSFORM(This.nApeluri) RETURN .F. ENDIF RETURN .T. ENDPROC ENDDEFINE