* test_s11_izolare_camp_1472.prg * Proba S11 (docs\raport_s11_cauza_1472_camp.md): izoleaza EXACT campul/campurile NULL din poArt * care duc lcSql la NULL in frm_facturare_articole2.do_scrie_articole (ofacturare.vc2:19977-20001). * Tipar identic cu test_s10_cablare_vfp.prg (mock pe goExecutor, NIMIC scris in Oracle): doc 630 * incarcat REAL (IncarcaAntetFacturaEditare + IncarcaLiniiFacturaEditare, citire, goExecutor real), * apoi goExecutor inlocuit cu mock INAINTE de do_scrie_articole(). Fara curatare/REPLACE ALL pe * crsfactura la Pasul 1 - vrem NULL-urile REALE, nu ascunse. * * Pasul 1: enumera (AFIELDS) toate campurile din crsfactura, logheaza VARTYPE/ISNULL pe fiecare, * pe fiecare rand. * Pasul 2: apeleaza do_scrie_articole() cu mock, capteaza lcSql brut per apel, verifica VARTYPE. * Pasul 3 (cauzalitate, sens 1): pe cursorul curatat DOAR pe campul/campurile gasite NULL la pasul 1 * SI nepazite in cod (candidatii: id_articol, id_jtva_coloana - singurele doua campuri din * lista nepazita 19978-20000 care sunt si NULL-capabile in structura crsfactura, cf. * creeaza_facturacrs, ofacturare_comun.prg:1801-1811), reruleaza - asteptam VARTYPE='C'. * Pasul 4 (cauzalitate, sens 2): reintroduce NULL pe UN SINGUR camp cauzal, reruleaza - asteptam * VARTYPE='X' din nou. LPARAMETERS tnCaz SET SAFETY OFF SET TALK OFF PUBLIC gcErrLog, gnPass, gnFail PUBLIC poDate gcErrLog = 'D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\test_s11_izolare_camp_1472_log_' + ; STRTRAN(STRTRAN(STRTRAN(TTOC(DATETIME()), '/', ''), ':', ''), ' ', '_') + '.txt' STRTOFILE('start ' + TRANSFORM(DATETIME()) + CHR(13) + CHR(10), gcErrLog) ON ERROR DO test_error_handler WITH ERROR(), MESSAGE(), PROGRAM(), LINENO() ON SHUTDOWN QUIT DO ('D:\ROA\ROAFACTURARE\COMUN\utile\Teste\test_init_env_auto_roafacturare.prg') WITH 'CENTRAL', 'MARIUSM_AUTO', 'ROMFASTSOFT' IF gnHandle <= 0 STRTOFILE('EROARE: conectare esuata' + CHR(13) + CHR(10), gcErrLog, 1) QUIT ENDIF gnIdUtil = 8 SET PROCEDURE TO D:\ROA\ROAFACTURARE\COMUN\programe\ofacturare_editare.prg ADDITIVE SET PROCEDURE TO D:\ROA\ROAFACTURARE\COMUN\programe\ofacturare_rutare_scriere.prg ADDITIVE *!* la nume duplicat de procedura castiga PRIMUL fisier din lista SET PROCEDURE, nu ultimul LOCAL lcProcExistent lcProcExistent = SET("PROCEDURE") SET PROCEDURE TO "D:\ROA\ROAFACTURARE\COMUN\utile\Teste\mock_amessagebox.prg", &lcProcExistent LOCAL lcPathVechi, lcPathNou lcPathVechi = SET("PATH") lcPathNou = 'D:\ROA\ROAFACTURARE\COMUN\Utile\Teste\achizitie_import\stub_verificare;' + lcPathVechi SET PATH TO &lcPathNou PUBLIC gnMockRaspuns, gcMockUltimMesaj, gnMockUltimTip gnMockRaspuns = 7 gcMockUltimMesaj = '' gnMockUltimTip = 0 gnPass = 0 gnFail = 0 PUBLIC gcSqlUltim, gcVartypeUltim gcSqlUltim = '' gcVartypeUltim = '' DO Izoleaza STRTOFILE('REZULTAT: ' + TRANSFORM(gnPass) + ' PASS / ' + TRANSFORM(gnFail) + ' FAIL' + CHR(13) + CHR(10), gcErrLog, 1) STRTOFILE('end ' + TRANSFORM(DATETIME()) + CHR(13) + CHR(10), gcErrLog, 1) QUIT *============================================================================================== PROCEDURE Izoleaza LOCAL lnIdV lnIdV = 630 LOCAL lnDiscRon, lnDiscVal lnDiscRon = 0 lnDiscVal = 0 poDate = IncarcaAntetFacturaEditare(lnIdV, @lnDiscRon, @lnDiscVal) DO asserteaza WITH '0a antetul documentului ' + TRANSFORM(lnIdV) + ' s-a incarcat', VARTYPE(poDate) = 'O', VARTYPE(poDate) IF VARTYPE(poDate) <> 'O' RETURN ENDIF IncarcaLiniiFacturaEditare(poDate, lnIdV) DO asserteaza WITH '0b liniile documentului ' + TRANSFORM(lnIdV) + ' s-au incarcat', ; USED('crsfactura') AND RECCOUNT('crsfactura') > 0, TRANSFORM(IIF(USED('crsfactura'), RECCOUNT('crsfactura'), -1)) IF !USED('crsfactura') OR RECCOUNT('crsfactura') = 0 RETURN ENDIF *!* curatare LOCALA (doar in RAM) doar pe ce trebuie ca sa nu strice testul de baza: *!* fara ")" accidental in text, toate randurile pe branch-ul adauga_articol_factura SELECT crsfactura REPLACE ALL explicatie WITH 'test-s11-izolare', serie WITH '', lot WITH '', opt_facturare WITH 0 IN crsfactura GO TOP IN crsfactura *!* PASUL 1: enumerare EFECTIVA (AFIELDS), nu lista ghicita - toate campurile, toate randurile LOCAL laFields[1], lnFields, lnI, lnRand lnFields = AFIELDS(laFields, 'crsfactura') STRTOFILE('--- PASUL 1: campuri NULL in crsfactura, pe fiecare rand (' + TRANSFORM(RECCOUNT('crsfactura')) + ' randuri) ---' + CHR(13) + CHR(10), gcErrLog, 1) SELECT crsfactura SCAN lnRand = RECNO('crsfactura') LOCAL lcNuleRand lcNuleRand = '' FOR lnI = 1 TO lnFields IF ISNULL(EVALUATE('crsfactura.' + laFields[lnI,1])) lcNuleRand = lcNuleRand + laFields[lnI,1] + '(' + laFields[lnI,2] + ') ' ENDIF ENDFOR IF EMPTY(lcNuleRand) STRTOFILE(' rand ' + TRANSFORM(lnRand) + ': niciun camp NULL' + CHR(13) + CHR(10), gcErrLog, 1) ELSE STRTOFILE(' rand ' + TRANSFORM(lnRand) + ': NULL pe -> ' + lcNuleRand + CHR(13) + CHR(10), gcErrLog, 1) ENDIF ENDSCAN GO TOP IN crsfactura *!* PASUL 2: rulare BRUTA (fara nicio curatare pe campuri numerice) - reproduce defectul DO RuleazaCuMock WITH 'PAS2-brut' DO asserteaza WITH 'PAS2 lcSql BRUT (fara curatare) e NULL (VARTYPE X) - reproduce defectul', gcVartypeUltim = 'X', gcVartypeUltim *!* PASUL 3: cauzalitate sens 1 - NVL DOAR pe candidatii NULL-capabili si nepaziti in cod *!* (id_articol: ofacturare.vc2:19979; id_jtva_coloana: ofacturare.vc2:19994) - singurii doi *!* campuri din lista nepazita 19978-20000 care sunt NULL-capabili in structura crsfactura *!* (ofacturare_comun.prg:1801-1811 - restul candidatilor nepaziti in SQL nu sunt marcati NULL *!* in CREATE CURSOR, deci nu pot ajunge NULL pe acest traseu, indiferent de sursa Oracle). SELECT crsfactura REPLACE ALL id_articol WITH NVL(id_articol,0), id_jtva_coloana WITH NVL(id_jtva_coloana,0) IN crsfactura GO TOP IN crsfactura DO RuleazaCuMock WITH 'PAS3-curat-id_articol-id_jtva_coloana' DO asserteaza WITH 'PAS3 dupa NVL DOAR pe id_articol+id_jtva_coloana, lcSql devine text (VARTYPE C)', gcVartypeUltim = 'C', gcVartypeUltim *!* PASUL 4: cauzalitate sens 2 - reintroduce NULL pe UN SINGUR camp (id_articol) pe primul rand SELECT crsfactura GO TOP IN crsfactura REPLACE id_articol WITH .NULL. IN crsfactura DO RuleazaCuMock WITH 'PAS4-regresie-doar-id_articol-null' DO asserteaza WITH 'PAS4 cu id_articol NULL pe un rand (restul curat), lcSql redevine NULL (VARTYPE X)', gcVartypeUltim = 'X', gcVartypeUltim *!* PASUL 5: control - NULL DOAR pe id_jtva_coloana (id_articol curat), separat de id_articol SELECT crsfactura GO TOP IN crsfactura REPLACE ALL id_articol WITH NVL(id_articol,0) IN crsfactura GO TOP IN crsfactura REPLACE id_jtva_coloana WITH .NULL. IN crsfactura DO RuleazaCuMock WITH 'PAS5-regresie-doar-id_jtva_coloana-null' DO asserteaza WITH 'PAS5 cu id_jtva_coloana NULL pe un rand (restul curat), lcSql redevine NULL (VARTYPE X)', gcVartypeUltim = 'X', gcVartypeUltim ENDPROC *============================================================================================== * RuleazaCuMock: apeleaza do_scrie_articole() cu goExecutor mockat, intoarce lcSql-ul (sau * diagnosticul VARTYPE) al APELULUI cu PCOUNT()=1 (adauga_articol_factura din Scan), plus * rollback pe conexiunea REALA dupa fiecare rulare (tranzactia ramane deschisa pe succes). *============================================================================================== PROCEDURE RuleazaCuMock LPARAMETERS tcPrefix LOCAL loForm, loMock, loExecutorReal, llReturn loForm = CREATEOBJECT('frm_facturare_articole2') IF TYPE('plRegenerareEditare') <> 'U' RELEASE plRegenerareEditare ENDIF loExecutorReal = goExecutor loMock = CREATEOBJECT('MockGoExecutorS11Izolare') goExecutor = loMock llReturn = loForm.do_scrie_articole() goExecutor = loExecutorReal SQLROLLBACK(gnHandle) SQLSetProp(gnHandle, 'Transactions', 1) gcSqlUltim = loMock.cSqlArticolCapturat gcVartypeUltim = loMock.cVartypeArticol STRTOFILE(tcPrefix + ' do_scrie_articole()=' + TRANSFORM(llReturn) + ' VARTYPE(tcSql pe apelul articol)=' + gcVartypeUltim + CHR(13) + CHR(10), gcErrLog, 1) STRTOFILE(tcPrefix + ' --- lcSql capturat (apelul articol) ---' + CHR(13) + CHR(10) + TRANSFORM(gcSqlUltim) + CHR(13) + CHR(10), gcErrLog, 1) RELEASE loForm 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: ' + TRANSFORM(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) IF TYPE('gnHandle') = 'N' AND gnHandle > 0 SQLROLLBACK(gnHandle) SQLSetProp(gnHandle, 'Transactions', 1) ENDIF gnFail = gnFail + 1 STRTOFILE('REZULTAT: ' + TRANSFORM(gnPass) + ' PASS / ' + TRANSFORM(gnFail) + ' FAIL' + CHR(13) + CHR(10), gcErrLog, 1) STRTOFILE('end (eroare) ' + TRANSFORM(DATETIME()) + CHR(13) + CHR(10), gcErrLog, 1) QUIT ENDPROC *============================================================================================== * MockGoExecutorS11Izolare: captureaza, pe FIECARE apel oExecute, PCOUNT()/VARTYPE(tcSql); retine * separat apelul cu PCOUNT()=1 (semnatura do_scrie_articole pentru adauga_articol_factura, spre * deosebire de apelul antetului care are 0 parametri suplimentari observabili din tcSql insusi - * de fapt toate apelurile din do_scrie_articole trec 1 parametru; distingem prin continut: apelul * articolului contine 'adauga_articol_factura' SAU e chiar cel NULL de pe randul cu bug-ul cautat - * pastram DOAR ultimul apel (cel mai relevant, cel din Scan) pentru comparatie inainte/dupa. *============================================================================================== DEFINE CLASS MockGoExecutorS11Izolare AS Custom cSqlArticolCapturat = '' cVartypeArticol = '' nApeluri = 0 cEroare = '' FUNCTION oExecute(tcSql) THIS.nApeluri = THIS.nApeluri + 1 LOCAL lcVartype lcVartype = VARTYPE(m.tcSql) *!* pastram ultimul apel care contine (in forma text) sau ar putea contine (NULL) linia de *!* articol - primele doua apeluri din do_scrie_articole sunt garantat antetul (nu contin *!* 'adauga_articol_factura'); din al treilea apel incolo, retinem fiecare, ultimul castiga IF THIS.nApeluri >= 3 THIS.cSqlArticolCapturat = TRANSFORM(m.tcSql) THIS.cVartypeArticol = m.lcVartype ENDIF RETURN 1 ENDFUNC FUNCTION oExecuta(tcSql, tcCursor) THIS.nApeluri = THIS.nApeluri + 1 IF VARTYPE(m.tcCursor) = 'C' AND !EMPTY(m.tcCursor) IF USED(m.tcCursor) USE IN (m.tcCursor) ENDIF CREATE CURSOR (m.tcCursor) (xdummy I) ENDIF RETURN .T. ENDFUNC FUNCTION oPrelucrareEroare() RETURN 'MockGoExecutorS11Izolare: ' + THIS.cEroare ENDFUNC ENDDEFINE