* test_s5_validari_articole.prg * Acopera S5 (scrierea sumelor editate in Oracle - decizii 38-41, docs\handoff_s5.md), functionalitate * NETESTATA la data scrierii acestui fisier (niciun test nu atingea inca pret_achizitie/id_vanzare_set * sau helperul de scriere). Doua sectiuni independente: * * A) Cele 5 validari noi din frm_modific2024.inainte_de_do_termin (omodificari.vc2:14328-14366), * apelate DIRECT pe o instanta reala (nu prin UI): cantitate<=0, pret NULL, id_articol nul/0 * blocheaza (RETURN .F.); zero linii active cu document in VANZARI si linie noua cu pret_achizitie * 0/NULL cer CONFIRMARE (amessagebox 4+32), nu blocare - ambele ramuri (Da continua, Nu opreste) * sunt verificate, plus un caz de control (cursor valid, nicio validare nu se declanseaza). * Documentul e real (FACTURA_ARTICOLE, descoperit prin proprietate - descopera_caz_test.prg), * tvd e mutat manual intre stari valide/invalide si restaurat dupa fiecare sub-caz. * * B) Garda de no-op si SQL-ul generat de ScrieArticoleFacturaEditate (ofacturare_editare.prg) - * complet headless, PE CURSOARE CONSTRUITE IN TEST (crstvdtest/crstvanztest, structura reala din * CreeazaCursorArticoleGol), FARA Oracle: goExecutor e inlocuit cu un mock (dummyexecutor) care * doar inregistreaza textul comenzilor intr-un cursor si intoarce o valoare configurabila - * goExecutor real (conectat la MARIUSM_AUTO) ramane neatins tot timpul sectiunii B. * Garda de no-op: tnIdVanzare<=0, alias articole neinchis, alias vanzare fara exact 1 rand, * Reccount(alias articole)=0 (cazul critic - un esec de incarcare Oracle NU trebuie sa goleasca * factura, vezi handoff_s5.md "Cale de pierdere de date, inchisa") - toate patru trebuie sa intoarca * .T. FARA sa apeleze goExecutor.oExecuta vreodata. * Clasificarea liniilor (pastrata/noua/noua-si-stearsa/stearsa/componenta de set) e verificata prin * SQL-ul efectiv generat de helper pe un cursor cu cate un rand din fiecare categorie. * Oprirea la primul esec Oracle e verificata explicit (mock configurat sa esueze la un apel anume). * * Nu scrie in Oracle (mock pe goExecutor in sectiunea B; sectiunea A nu apeleaza deloc scriere - * inainte_de_do_termin e doar validare). Nu da COMMIT. QUIT la final. SET SAFETY OFF SET TALK OFF LOCAL lcLog lcLog = 'D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\test_s5_validari_articole_log.txt' STRTOFILE('start' + CHR(13) + CHR(10), lcLog) PUBLIC gcErrLog gcErrLog = lcLog ON ERROR DO test_error_handler WITH ERROR(), MESSAGE(), PROGRAM(), LINENO() *!* mock amessagebox INCARCAT PRIMUL (testare-ui-vfp.md, capcana g - verificat pe teren in *!* test_writeback_buton1.prg, acelasi folder): la nume duplicat de procedura VFP foloseste *!* fisierul cautat PRIMUL, deci trebuie deschis inaintea lantului din test_init_env_auto 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 SET PROCEDURE TO D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\descopera_caz_test.prg ADDITIVE *!* test_init_env_auto incarca clasa omodificari din working copy-ul ROACONT (alt SET PATH) - *!* o eliberam si o reincarcam pe cea DE SUB TEST, cu cale completa RELEASE CLASSLIB omodificari SET CLASSLIB TO D:\ROA\ROAFACTURARE\COMUN\clase\omodificari.vcx ADDITIVE PUBLIC gnPass, gnFail gnPass = 0 gnFail = 0 PUBLIC gnMockRaspuns, gcMockUltimMesaj, gnMockUltimTip gnMockRaspuns = 6 gcMockUltimMesaj = '' gnMockUltimTip = 0 LOCAL lnVerifMock lnVerifMock = amessagebox('verificare mock', 4+32, 'test') DO asserteaza WITH 'mock amessagebox activ (umbreste amessagebox real)', lnVerifMock == 6, TRANSFORM(lnVerifMock) *============================================================================================ * SECTIUNEA A - validarile din inainte_de_do_termin, pe o instanta reala frm_modific2024 *============================================================================================ LOCAL llCazA llCazA = DescoperaCazTest('FACTURA_ARTICOLE', 'crscaz_a') IF !llCazA STRTOFILE('EROARE: cazul FACTURA_ARTICOLE nu a fost gasit' + CHR(13) + CHR(10), lcLog, 1) STRTOFILE('REZULTAT: ' + TRANSFORM(gnPass) + ' PASS / ' + TRANSFORM(gnFail) + ' FAIL' + CHR(13) + CHR(10), lcLog, 1) QUIT ENDIF STRTOFILE('caz A: cod=' + TRANSFORM(crscaz_a.cod) + ' an=' + TRANSFORM(crscaz_a.an) + ' luna=' + TRANSFORM(crscaz_a.luna) + ; ' id_vanzare=' + TRANSFORM(crscaz_a.id_vanzare) + CHR(13) + CHR(10), lcLog, 1) LOCAL llOk, lnIdSet, loForm llOk = IncarcaCursoareModificareNota(crscaz_a.cod, crscaz_a.an, crscaz_a.luna, .F.) IF !llOk OR !Used('tact') OR Reccount('tact') = 0 STRTOFILE('EROARE: IncarcaCursoareModificareNota a esuat pe caz A' + CHR(13) + CHR(10), lcLog, 1) STRTOFILE('REZULTAT: ' + TRANSFORM(gnPass) + ' PASS / ' + TRANSFORM(gnFail) + ' FAIL' + CHR(13) + CHR(10), lcLog, 1) QUIT ENDIF Select a.*, Iif(Nvl(id_jtva_coloana,0)=0,0,1) As cu_Tva From tact a Into Cursor tact Readwrite Go Top In tact lnIdSet = tact.id_set IF !Used('crsJtvaTemp') update_jtva_coloane("", "crsJtvaTemp", 6) ENDIF Select tact loForm = Createobject([frm_modific2024], lnIdSet) loForm.WindowType = 0 loForm.Show() DO asserteaza WITH 'formularul are articole (lAreArticoleVanzari)', loForm.lAreArticoleVanzari, TRANSFORM(loForm.lAreArticoleVanzari) DO asserteaza WITH 'conditie initiala: tvd are cel putin o linie activa', Used('tvd') AND Reccount('tvd') > 0, TRANSFORM(Reccount('tvd')) IF !Used('tvd') OR Reccount('tvd') = 0 STRTOFILE('EROARE: tvd fara linii, sectiunea A sarita' + CHR(13) + CHR(10), lcLog, 1) ELSE *!* --- A1: caz de control - cursorul incarcat din Oracle e valid, nicio validare nu trebuie sa se declanseze --- LOCAL llRet1 gnMockRaspuns = 6 gcMockUltimMesaj = '' llRet1 = loForm.inainte_de_do_termin() DO asserteaza WITH 'caz de control: document real valid -> inainte_de_do_termin() = .T., nicio validare S5 declansata', ; llRet1 == .T. AND !('cantitatea' $ Lower(gcMockUltimMesaj)) AND !('pret' $ Lower(gcMockUltimMesaj)) AND !('articol' $ Lower(gcMockUltimMesaj)), ; 'llRet=' + TRANSFORM(llRet1) + ' ultimMesaj=[' + gcMockUltimMesaj + ']' *!* --- A2: cantitate <= 0 pe o linie activa -> blocheaza --- LOCAL lnOrigCant, llRet2 SELECT tvd GO TOP lnOrigCant = tvd.cantitate REPLACE cantitate WITH 0 IN tvd gcMockUltimMesaj = '' llRet2 = loForm.inainte_de_do_termin() DO asserteaza WITH 'cantitate<=0 pe linie activa -> blocheaza (RETURN .F.)', llRet2 == .F., TRANSFORM(llRet2) DO asserteaza WITH 'cantitate<=0 -> mesajul mentioneaza cantitatea', 'cantitatea' $ Lower(gcMockUltimMesaj), gcMockUltimMesaj SELECT tvd GO TOP REPLACE cantitate WITH lnOrigCant IN tvd *!* --- A3: pret NULL pe o linie activa -> ar trebui sa blocheze, DAR verificat empiric ca *!* tvd.pret (populat de IncarcaArticoleFactura din VVANZARI_ARTICOLE, singura cale reala cand *!* ofacturare_editare.prg e incarcat) e structural NOT NULL - REPLACE ... WITH .NULL. arunca *!* eroarea VFP 1581 "Field PRET does not accept null values", nu doar pe liniile persistate *!* (VANZARI_DETALII.PRET e NOT NULL in Oracle - coloana provine din view), ci si pe o linie *!* noua adaugata prin APPEND BLANK pe ACELASI cursor (restrictia e la nivel de coloana, nu de *!* rand). Concluzie: validarea Isnull(pret) din inainte_de_do_termin (omodificari.vc2:14340) *!* e cod NEATINGIBIL pe fluxul real - nu poate fi provocata nici pe linii existente, nici pe *!* linii noi construite prin fluxul aplicatiei. Se raporteaza, nu se repara (fara UI, cod *!* de productie interzis pentru acest agent) - marcat FAIL asteptat, ca sa nu se numere ca regresie. LOCAL lnOrigPret, llRet3, llPretNullPosibil SELECT tvd GO TOP lnOrigPret = tvd.pret llPretNullPosibil = .T. TRY REPLACE pret WITH .NULL. IN tvd CATCH llPretNullPosibil = .F. ENDTRY IF llPretNullPosibil gcMockUltimMesaj = '' llRet3 = loForm.inainte_de_do_termin() DO asserteaza WITH 'pret NULL pe linie activa -> blocheaza (RETURN .F.)', llRet3 == .F., TRANSFORM(llRet3) DO asserteaza WITH 'pret NULL -> mesajul mentioneaza pretul', 'pret' $ Lower(gcMockUltimMesaj), gcMockUltimMesaj SELECT tvd GO TOP REPLACE pret WITH lnOrigPret IN tvd ELSE STRTOFILE('FAIL asteptat pret NULL pe linie activa: tvd.pret e structural NOT NULL (eroare VFP 1581 la REPLACE) - ' + ; 'validarea Isnull(pret) din inainte_de_do_termin e neatingibila pe fluxul real, de raportat echipei' + CHR(13) + CHR(10), gcErrLog, 1) ENDIF *!* --- A4: id_articol nul sau 0 pe o linie activa -> blocheaza --- LOCAL lnOrigIdArt, llRet4 SELECT tvd GO TOP lnOrigIdArt = tvd.id_articol REPLACE id_articol WITH 0 IN tvd gcMockUltimMesaj = '' llRet4 = loForm.inainte_de_do_termin() DO asserteaza WITH 'id_articol=0 pe linie activa -> blocheaza (RETURN .F.)', llRet4 == .F., TRANSFORM(llRet4) DO asserteaza WITH 'id_articol=0 -> mesajul mentioneaza articolul', 'articol' $ Lower(gcMockUltimMesaj), gcMockUltimMesaj SELECT tvd GO TOP REPLACE id_articol WITH lnOrigIdArt IN tvd *!* --- A5: zero linii active, document cu rand in tvanz -> CONFIRMARE, nu blocare (ambele ramuri) --- LOCAL llRet5da, llRet5nu SELECT tvd REPLACE ALL sters WITH 1 IN tvd gnMockRaspuns = 6 gcMockUltimMesaj = '' llRet5da = loForm.inainte_de_do_termin() DO asserteaza WITH 'zero linii active + Da la confirmare -> continua (.T.)', llRet5da == .T., TRANSFORM(llRet5da) DO asserteaza WITH 'zero linii active -> mesajul de confirmare mentioneaza liniile sterse', 'linii' $ Lower(gcMockUltimMesaj), gcMockUltimMesaj gnMockRaspuns = 7 gcMockUltimMesaj = '' llRet5nu = loForm.inainte_de_do_termin() DO asserteaza WITH 'zero linii active + Nu la confirmare -> opreste (.F.)', llRet5nu == .F., TRANSFORM(llRet5nu) gnMockRaspuns = 6 SELECT tvd REPLACE ALL sters WITH 0 IN tvd *!* --- A6: linie NOUA (id_vanzare_det=0) cu pret_achizitie 0/NULL -> CONFIRMARE, nu blocare (ambele ramuri) --- *!* linia noua e altfel valida (cantitate/pret/id_articol completate) ca sa nu declanseze A2/A3/A4 mai devreme LOCAL llRet6da, llRet6nu SELECT tvd APPEND BLANK REPLACE id_vanzare WITH loForm.nIdVanzare, id_vanzare_det WITH 0, id_articol WITH 999999, ; cantitate WITH 1, pret WITH 10, pret_cu_tva WITH 0, proc_tvav WITH 1.19, sters WITH 0, ; denumire WITH 'LINIE TEST S5', pret_achizitie WITH 0 IN tvd gnMockRaspuns = 6 gcMockUltimMesaj = '' llRet6da = loForm.inainte_de_do_termin() DO asserteaza WITH 'linie noua cu pret_achizitie=0 + Da la confirmare -> continua (.T.)', llRet6da == .T., TRANSFORM(llRet6da) DO asserteaza WITH 'linie noua cu pret_achizitie=0 -> mesajul mentioneaza pretul de achizitie', 'achizitie' $ Lower(gcMockUltimMesaj), gcMockUltimMesaj gnMockRaspuns = 7 gcMockUltimMesaj = '' llRet6nu = loForm.inainte_de_do_termin() DO asserteaza WITH 'linie noua cu pret_achizitie=0 + Nu la confirmare -> opreste (.F.)', llRet6nu == .F., TRANSFORM(llRet6nu) *!* linia de test nu trebuie sa ramana dupa pret_achizitie NULL (nu doar 0) - verificare suplimentara, apoi curatare gnMockRaspuns = 6 SELECT tvd GO BOTTOM REPLACE pret_achizitie WITH .NULL. IN tvd gcMockUltimMesaj = '' LOCAL llRet6null llRet6null = loForm.inainte_de_do_termin() DO asserteaza WITH 'linie noua cu pret_achizitie NULL (nu doar 0) -> aceeasi confirmare', 'achizitie' $ Lower(gcMockUltimMesaj), gcMockUltimMesaj SELECT tvd GO BOTTOM DELETE IN tvd ENDIF loForm.Release() IF Used('actactan') Use In actactan ENDIF IF Used('tact') Use In tact ENDIF IF Used('rul_temp') Use In rul_temp ENDIF IF Used('trul') Use In trul ENDIF IF Used('rul_temp_obinv') Use In rul_temp_obinv ENDIF IF Used('trul_obinv') Use In trul_obinv ENDIF IF Used('tvanz') Use In tvanz ENDIF IF Used('tvd') Use In tvd ENDIF *============================================================================================ * SECTIUNEA B - ScrieArticoleFacturaEditate: garda de no-op + SQL generat, FARA Oracle (mock pe goExecutor) *============================================================================================ LOCAL loExecutorReal loExecutorReal = goExecutor *!* obiectul mock se construieste O SINGURA DATA, INLINE in programul principal (nu intr-un helper - *!* CREATEOBJECT apelat dintr-o PROCEDURA se comporta gresit in acest runtime headless, capcana l, *!* testare-ui-vfp.md) - ResetMockSql de mai jos doar goleste cursorul si reseteaza contorul pe *!* obiectul deja existent, fara sa mai construiasca altul goExecutor = Createobject('dummyexecutor') *!* --- B0: garda de no-op, patru cazuri - niciunul nu trebuie sa apeleze goExecutor.oExecuta --- DO ResetMockSql *!* tnIdVanzare <= 0 CreeazaCursorArticoleGol('crstvdtest') SELECT crstvdtest APPEND BLANK REPLACE id_vanzare_det WITH 100, id_articol WITH 1, cantitate WITH 1, pret WITH 1, sters WITH 0 IN crstvdtest IF Used('crstvanztest') Use In crstvanztest ENDIF CREATE CURSOR crstvanztest (id_vanzare I, discount N(18,4) NULL) INSERT INTO crstvanztest (id_vanzare, discount) VALUES (12345, NULL) LOCAL llRetB1, llRetB1b llRetB1 = ScrieArticoleFacturaEditate(0, 'crstvdtest', 'crstvanztest') DO asserteaza WITH 'no-op: tnIdVanzare=0 -> .T. fara sa scrie', llRetB1 == .T. AND Reccount('crsMockSql') == 0, 'ret=' + TRANSFORM(llRetB1) + ' comenzi=' + TRANSFORM(Reccount('crsMockSql')) llRetB1b = ScrieArticoleFacturaEditate(-5, 'crstvdtest', 'crstvanztest') DO asserteaza WITH 'no-op: tnIdVanzare negativ -> .T. fara sa scrie', llRetB1b == .T. AND Reccount('crsMockSql') == 0, 'ret=' + TRANSFORM(llRetB1b) + ' comenzi=' + TRANSFORM(Reccount('crsMockSql')) *!* alias articole neinchis (nume inexistent) LOCAL llRetB2 llRetB2 = ScrieArticoleFacturaEditate(12345, 'crstvd_nu_exista', 'crstvanztest') DO asserteaza WITH 'no-op: alias articole neinchis -> .T. fara sa scrie', llRetB2 == .T. AND Reccount('crsMockSql') == 0, 'ret=' + TRANSFORM(llRetB2) + ' comenzi=' + TRANSFORM(Reccount('crsMockSql')) *!* alias vanzare fara exact 1 rand: 0 randuri LOCAL llRetB3zero, llRetB3doi SELECT crstvanztest ZAP llRetB3zero = ScrieArticoleFacturaEditate(12345, 'crstvdtest', 'crstvanztest') DO asserteaza WITH 'no-op: alias vanzare cu 0 randuri -> .T. fara sa scrie', llRetB3zero == .T. AND Reccount('crsMockSql') == 0, 'ret=' + TRANSFORM(llRetB3zero) + ' comenzi=' + TRANSFORM(Reccount('crsMockSql')) *!* alias vanzare fara exact 1 rand: 2 randuri INSERT INTO crstvanztest (id_vanzare, discount) VALUES (12345, NULL) INSERT INTO crstvanztest (id_vanzare, discount) VALUES (12345, NULL) llRetB3doi = ScrieArticoleFacturaEditate(12345, 'crstvdtest', 'crstvanztest') DO asserteaza WITH 'no-op: alias vanzare cu 2 randuri -> .T. fara sa scrie', llRetB3doi == .T. AND Reccount('crsMockSql') == 0, 'ret=' + TRANSFORM(llRetB3doi) + ' comenzi=' + TRANSFORM(Reccount('crsMockSql')) *!* Reccount(alias articole)=0 - cazul critic: un esec de incarcare Oracle nu trebuie sa goleasca factura LOCAL llRetB4 SELECT crstvanztest ZAP INSERT INTO crstvanztest (id_vanzare, discount) VALUES (12345, NULL) SELECT crstvdtest ZAP DO asserteaza WITH 'conditie initiala B4: crstvdtest deschis, 0 randuri', Used('crstvdtest') AND Reccount('crstvdtest') == 0, TRANSFORM(Reccount('crstvdtest')) llRetB4 = ScrieArticoleFacturaEditate(12345, 'crstvdtest', 'crstvanztest') DO asserteaza WITH 'no-op CRITIC: Reccount(alias articole)=0 -> .T. fara sa scrie (nu goleste factura la esec de incarcare)', ; llRetB4 == .T. AND Reccount('crsMockSql') == 0, 'ret=' + TRANSFORM(llRetB4) + ' comenzi=' + TRANSFORM(Reccount('crsMockSql')) *!* --- B1: scriere normala - un rand din fiecare categorie, verifica SQL-ul si clasificarea --- DO ResetMockSql IF Used('crstvdtest') Use In crstvdtest ENDIF CreeazaCursorArticoleGol('crstvdtest') SELECT crstvdtest *!* rand 1: pastrata, modificata (lmodificat ignorat de helper - decizia 38 invie TOATE liniile pastrate) APPEND BLANK REPLACE id_vanzare_det WITH 555, id_articol WITH 111, cantitate WITH 3, pret WITH 12.5, pret_cu_tva WITH 0, sters WITH 0, lmodificat WITH .T., denumire WITH 'PASTRATA MODIFICATA' IN crstvdtest *!* rand 2: pastrata, nemodificata - trebuie sa primeasca UPDATE la fel ca randul 1 (decizia 38) APPEND BLANK REPLACE id_vanzare_det WITH 556, id_articol WITH 112, cantitate WITH 1, pret WITH 5, pret_cu_tva WITH 0, sters WITH 0, lmodificat WITH .F., denumire WITH 'PASTRATA NEMODIFICATA' IN crstvdtest *!* rand 3: stearsa (linie persistata, sters=1) - ramane sters prin pasul 1 (marcheaza tot), fara UPDATE propriu APPEND BLANK REPLACE id_vanzare_det WITH 557, id_articol WITH 113, cantitate WITH 1, pret WITH 1, sters WITH 1, denumire WITH 'STEARSA' IN crstvdtest *!* rand 4: linie noua (id_vanzare_det=0) - INSERT; explicatie cu apostrof, ca sa verifice OracleSpecialCharacters APPEND BLANK REPLACE id_vanzare_det WITH 0, id_articol WITH 222, cantitate WITH 2, pret WITH 20, pret_cu_tva WITH 0, sters WITH 0, ; pret_achizitie WITH 15.5, explicatie WITH "test 'x'", denumire WITH 'NOUA' IN crstvdtest *!* rand 5: noua-si-stearsa (id_vanzare_det=0, sters=1) - se ignora, fara SQL propriu APPEND BLANK REPLACE id_vanzare_det WITH 0, id_articol WITH 223, cantitate WITH 1, pret WITH 1, sters WITH 1, denumire WITH 'NOUA STEARSA' IN crstvdtest *!* rand 6: componenta de set, pastrata (id_vanzare_set<>0, id_vanzare_det>0) - scrisa la fel ca o linie pastrata normala APPEND BLANK REPLACE id_vanzare_det WITH 558, id_articol WITH 114, id_vanzare_set WITH 999, cantitate WITH 1, pret WITH 7, sters WITH 0, denumire WITH 'COMPONENTA SET' IN crstvdtest IF Used('crstvanztest') Use In crstvanztest ENDIF CREATE CURSOR crstvanztest (id_vanzare I, discount N(18,4) NULL) INSERT INTO crstvanztest (id_vanzare, discount) VALUES (12345, NULL) LOCAL llRetB5 llRetB5 = ScrieArticoleFacturaEditate(12345, 'crstvdtest', 'crstvanztest') DO asserteaza WITH 'scriere normala: intoarce .T.', llRetB5 == .T., TRANSFORM(llRetB5) DO asserteaza WITH 'total comenzi = 6 (1 marcheaza-tot + 3 UPDATE pastrate + 1 INSERT noua + 1 recalcul)', ; Reccount('crsMockSql') == 6, TRANSFORM(Reccount('crsMockSql')) LOCAL lnCntMarcheazaTot, lnCntUpdate, lnCntInsert, lnCntRecalc SELECT crsMockSql lnCntMarcheazaTot = 0 lnCntUpdate = 0 lnCntInsert = 0 lnCntRecalc = 0 SCAN IF 'update vanzari_detalii set sters = 1,' $ Lower(sql) AND 'and sters = 0' $ Lower(sql) AND 'where id_vanzare = 12345' $ Lower(sql) lnCntMarcheazaTot = lnCntMarcheazaTot + 1 ENDIF IF 'update vanzari_detalii set sters = 0, cantitate' $ Lower(sql) lnCntUpdate = lnCntUpdate + 1 ENDIF IF 'insert into vanzari_detalii' $ Lower(sql) lnCntInsert = lnCntInsert + 1 ENDIF IF 'recalculeaza_totaluri_vanzari' $ Lower(sql) lnCntRecalc = lnCntRecalc + 1 ENDIF ENDSCAN DO asserteaza WITH 'clasificare: exact 1 comanda "marcheaza tot sters" (pasul 1, decizia 38)', lnCntMarcheazaTot == 1, TRANSFORM(lnCntMarcheazaTot) DO asserteaza WITH 'clasificare: exact 3 UPDATE (liniile pastrate 555/556/558, inclusiv nemodificata si componenta de set)', lnCntUpdate == 3, TRANSFORM(lnCntUpdate) DO asserteaza WITH 'clasificare: exact 1 INSERT (linia noua 4, id_vanzare_det=0/sters<>1)', lnCntInsert == 1, TRANSFORM(lnCntInsert) DO asserteaza WITH 'clasificare: exact 1 recalcul de totaluri (pasul final)', lnCntRecalc == 1, TRANSFORM(lnCntRecalc) LOCAL llU555, llU556, llU558, llU557Absent, llU0Absent SELECT crsMockSql llU555 = .F. llU556 = .F. llU558 = .F. llU557Absent = .T. llU0Absent = .T. SCAN FOR 'update vanzari_detalii set sters = 0, cantitate' $ Lower(sql) IF 'where id_vanzare_det = 555 and' $ Lower(sql) llU555 = .T. ENDIF IF 'where id_vanzare_det = 556 and' $ Lower(sql) llU556 = .T. ENDIF IF 'where id_vanzare_det = 558 and' $ Lower(sql) llU558 = .T. ENDIF IF 'where id_vanzare_det = 557 and' $ Lower(sql) llU557Absent = .F. ENDIF IF 'where id_vanzare_det = 0 and' $ Lower(sql) llU0Absent = .F. ENDIF ENDSCAN DO asserteaza WITH 'clasificare: UPDATE-ul contine exact liniile pastrate asteptate (555, 556, 558) si niciuna in plus (557 stearsa, 0 = noua, absente)', ; llU555 AND llU556 AND llU558 AND llU557Absent AND llU0Absent, ; '555=' + TRANSFORM(llU555) + ' 556=' + TRANSFORM(llU556) + ' 558=' + TRANSFORM(llU558) + ' 557absent=' + TRANSFORM(llU557Absent) + ' 0absent=' + TRANSFORM(llU0Absent) LOCAL llUpdateAncorat SELECT crsMockSql llUpdateAncorat = .T. SCAN FOR 'update vanzari_detalii set sters = 0, cantitate' $ Lower(sql) IF !('and id_vanzare = 12345' $ Lower(sql)) llUpdateAncorat = .F. ENDIF ENDSCAN DO asserteaza WITH 'fiecare UPDATE de linie pastrata e ancorat pe document (and id_vanzare = 12345)', llUpdateAncorat, TRANSFORM(llUpdateAncorat) LOCAL lcSqlInsert, llInsertOk lcSqlInsert = '' SELECT crsMockSql SCAN FOR 'insert into vanzari_detalii' $ Lower(sql) lcSqlInsert = sql ENDSCAN llInsertOk = ('15.5000' $ lcSqlInsert) AND ("test ''x''" $ lcSqlInsert) AND ('12345' $ lcSqlInsert) AND ('222' $ lcSqlInsert) DO asserteaza WITH 'INSERT: pret_achizitie citit din cursor (15.5000), explicatie escapata (apostrof dublat), id_vanzare/id_articol corecte', llInsertOk, lcSqlInsert LOCAL lcSqlRecalc lcSqlRecalc = '' SELECT crsMockSql SCAN FOR 'recalculeaza_totaluri_vanzari' $ Lower(sql) lcSqlRecalc = sql ENDSCAN *!* NULL e literal in sursa helperului ([NULL], majuscul) - comparatia se face pe textul lowercase in ambele parti DO asserteaza WITH 'recalcul: discount NULL in cursor -> parametru NULL (pastreaza discountul curent din baza, decizia 41)', ; 'recalculeaza_totaluri_vanzari(12345,null)' $ Lower(Strtran(lcSqlRecalc, ' ', '')), lcSqlRecalc *!* --- B2: recalcul cu discount NEnul in cursor - parametrul trebuie sa fie valoarea, nu NULL --- DO ResetMockSql SELECT crstvanztest ZAP INSERT INTO crstvanztest (id_vanzare, discount) VALUES (12345, 5) LOCAL llRetB6, lcSqlRecalc2 llRetB6 = ScrieArticoleFacturaEditate(12345, 'crstvdtest', 'crstvanztest') lcSqlRecalc2 = '' SELECT crsMockSql SCAN FOR 'recalculeaza_totaluri_vanzari' $ Lower(sql) lcSqlRecalc2 = sql ENDSCAN DO asserteaza WITH 'recalcul: discount=5 in cursor -> parametrul e 5.0000, nu NULL', ; llRetB6 == .T. AND ('recalculeaza_totaluri_vanzari(12345,5.0000)' $ Lower(Strtran(lcSqlRecalc2, ' ', ''))), lcSqlRecalc2 *!* --- B3: oprire la primul esec Oracle - mock configurat sa esueze la a doua comanda --- DO ResetMockSql goExecutor.nFailAtCall = 2 IF Used('crstvdtest2') Use In crstvdtest2 ENDIF CreeazaCursorArticoleGol('crstvdtest2') SELECT crstvdtest2 APPEND BLANK REPLACE id_vanzare_det WITH 601, id_articol WITH 1, cantitate WITH 1, pret WITH 1, sters WITH 0 IN crstvdtest2 APPEND BLANK REPLACE id_vanzare_det WITH 602, id_articol WITH 2, cantitate WITH 1, pret WITH 1, sters WITH 0 IN crstvdtest2 SELECT crstvanztest ZAP INSERT INTO crstvanztest (id_vanzare, discount) VALUES (12345, NULL) LOCAL llRetB7 llRetB7 = ScrieArticoleFacturaEditate(12345, 'crstvdtest2', 'crstvanztest') DO asserteaza WITH 'esec Oracle la a doua comanda -> functia intoarce .F.', llRetB7 == .F., TRANSFORM(llRetB7) DO asserteaza WITH 'esec Oracle: se opreste la primul esec, nu mai incearca a treia comanda (2 comenzi total, nu 3+)', ; Reccount('crsMockSql') == 2, TRANSFORM(Reccount('crsMockSql')) goExecutor.nFailAtCall = 0 STRTOFILE('REZULTAT: ' + TRANSFORM(gnPass) + ' PASS / ' + TRANSFORM(gnFail) + ' FAIL' + CHR(13) + CHR(10), lcLog, 1) goExecutor = loExecutorReal IF Used('crstvdtest') Use In crstvdtest ENDIF IF Used('crstvdtest2') Use In crstvdtest2 ENDIF IF Used('crstvanztest') Use In crstvanztest ENDIF IF Used('crsMockSql') Use In crsMockSql ENDIF QUIT *!* goleste jurnalul de comenzi si contorul de apeluri PE OBIECTUL DEJA EXISTENT (nu recreeaza *!* goExecutor - vezi comentariul de la constructia lui in main, capcana l). crsMockSql ramane *!* MEREU deschis (recreat gol, nu doar inchis) - RECCOUNT() pe un alias niciodata deschis arunca *!* eroarea 13 "Alias is not found" in loc sa intoarca 0 (verificat empiric, nu asa cum presupunea *!* prima varianta a acestui fisier) PROCEDURE ResetMockSql IF Used('crsMockSql') Use In crsMockSql ENDIF CREATE CURSOR crsMockSql (nr I, sql M) goExecutor.nApeluri = 0 goExecutor.nFailAtCall = 0 ENDPROC PROCEDURE asserteaza LPARAMETERS tcNume, tlConditie, tcValoare IF m.tlConditie gnPass = gnPass + 1 STRTOFILE('PASS ' + m.tcNume + CHR(13) + CHR(10), gcErrLog, 1) ELSE gnFail = gnFail + 1 STRTOFILE('FAIL ' + m.tcNume + ' (valoare: ' + m.tcValoare + ')' + CHR(13) + CHR(10), gcErrLog, 1) ENDIF 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 sectiunea B - NU atinge Oracle: doar inregistreaza textul fiecarei *!* comenzi intr-un cursor (crsMockSql) si intoarce .T., sau .F. o singura data la apelul cu numarul *!* nFailAtCall (0 = nu esueaza niciodata) - ca sa verifice oprirea la primul esec fara sa opreasca *!* toate apelurile ulterioare din suita. DEFINE CLASS la finalul fisierului (capcana depanare_testare_vfp.md #7) DEFINE CLASS dummyexecutor AS Custom cEroare = '' nFailAtCall = 0 nApeluri = 0 PROCEDURE oExecuta LPARAMETERS tcSql, tcCursor LOCAL lnUrmator This.nApeluri = This.nApeluri + 1 IF !Used('crsMockSql') CREATE CURSOR crsMockSql (nr I, sql M) ENDIF SELECT crsMockSql lnUrmator = Reccount('crsMockSql') + 1 INSERT INTO crsMockSql (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