Fakin Hakin(g)

Jak dobrze jest znać się nieco na technologiach internetowych przekonałem się dziś po raz kolejny.

System komunikacji z moim promotorem nagle odmówił współpracy – ciągle twierdził, że data, którą wprowadziłem jest błędna. Mimo, że była taka jakiej potrzebował. Pomyślałem – zajrzę do źródła i będę widział co tam sprawdza.
Niestety byłem zbyt wielkim optymistą spodziewając się jakiegoś regexpa:

function CheckTime(cntrl, emptyok)
{
 var h = "";
 var m = "";
 var s = cntrl.value.replace(/^s*/, '').replace(/s*$/, '');

 if (s == "" && emptyok == 1)
  {
   cntrl.value = "";
   return 1;
  }

 var i = 0;
 while (s != "" && "0123456789".indexOf(s.charAt(0)) >= 0 && i < 6)
  {
   h = h + s.charAt(0);
   s = s.substr(1,s.length);
   i++;
  }
 if (h == "") return 0;
 var hn = parseInt(h,10);
 if (hn < 0 || hn > 23) return 0;
 if (hn < 10)
   h = "0" + hn;
 else
   h = "" + hn;

 s = s.substr(1,s.length);
 i = 0;
 while (s != "" && "0123456789".indexOf(s.charAt(0)) >= 0 && i < 6)
  {
   m = m + s.charAt(0);
   s = s.substr(1,s.length);
   i++;
  }
 if (m == "") return 0;
 var mn = parseInt(m,10);
 if (mn < 0 || mn > 59) return 0;
 if (mn < 10)
   m = "0" + mn;
 else
   m = "" + mn;

 cntrl.value = h + ":" + m;
 return 100*hn + mn + 1;
}

Stwierdziłem, że szkoda czasu na zgadywankę pod tytułem "co autor miał na myśli". Zastanawiając się jakie możliwości obejścia mam pod ręką myślałem, że może wywołam submit() dla tego formularza i po krzyku, niestety nie mogłem mieć pewności, że coś nie pójdzie nie tak, bo:

function multi()
 {
  str_kod = str_opis = ""
  for (var i = 0; i < document.forms[0].elements.length; i++)
    if (document.forms[0].elements[i].name.substring(0, 1) == "c" &&
        document.forms[0].elements[i].checked)
     {
      str_kod += document.forms[0].elements[i + 1].value;
      str_opis += document.forms[0].elements[i + 2].value;
     }
  if (str_kod == "")
   {
    if (document.forms[0].elements.length > 0 &&
        document.forms[1].R.value < 0 &&
	       document.forms[1].U.value != '0' &&
	       !confirm('Nie zaznaczono żadnego dodatkowego adresata.nnnCzy chcesz kontynuować wysyłanie?'))
      return;
    if (saved_W != "")
     {
      document.forms[1].W.value = saved_W;
      document.forms[1].Z.value = saved_Z;
     }
    document.forms[1].submit();
   }
  else
   {
    if (saved_W == "")
     {
      saved_W = document.forms[1].W.value;
      saved_Z = document.forms[1].Z.value;
     }

    var Y = document.forms[1].Y.value;
    var Y = RemoveBlanks(Y);
    if (Y == "pracownik") Y = "C";
    else if (Y == "nauczyciel") Y = "N";
    else if (Y == "student") Y = "T";
    else if (Y == "rozklad") Y = "Z";
    else if (Y == "rozklad2") Y = "Y";
    else if (Y == "przedmiot") Y = "P";
    else if (Y == "grupajez") Y = "J";
    else if (Y == "kandydat") Y = "K";
    else if (Y == "kursant") Y = "U";
    else if (Y.length == 1) Y = "";
    else
     {
      alert('Błąd SUSZI... [' + Y.length + ':' + Y + ']');
      exit;
     }

    str_kod = '#' + saved_Z + ':' + Y + ':' + document.forms[1].X.value + ':' +
              str_kod;
    str_opis = '#' + saved_W + str_opis;

    document.forms[1].Z.value = str_kod;
    document.forms[1].W.value = str_opis;
    document.forms[1].submit();
  }
 }

Przypomniało mi się na szczęście, że przecież nazwy funkcji są zwyczajnie zmiennymi w javascript. Wiedząc, że CheckDate wywołuje błąd, kiedy nie zwraca 1, postanowiłem pobawić się w hackera (przy pomocy niezastąpionego firebuga):

CheckTime = function() {return 1}

Zaczął mi marudzić, że datę mam z przeszłości (2009-11-14), no to jeszcze jeden mały hack:

FutureDate = function() {return 20;}

Teraz trzymam kciuki, żeby mój promotor dowiedział się, ze skończyłem pisać pracę. Jeśli mają w tym systemie takie same bugi po stronie serwera - nie uda mi się ich tak łatwo obejść ;)

build in validation vs. unit testing

If You want to depend on build in model validation, You need to watch out on one pitfall

Let’s assume that there’s Stuff model with this validation:

$validate = array("name" => "notempty");

It works perfectly in web forms, but when You are making a unit test:

$this->assertFalse( $this->Stuff->save( array("id"=> 1) ) ); //test fail

Unfortunately it wont work, and the save operation will occur. Why?

Because that validation means that
If You have “name” element in saving array, it will be checked if it’s not empty (not null, nor false or “”)

So, if You need the test to pass, You can do this:

$this->assertFalse( $this->Stuff->save( array("id"=> 1, "name"=>"") ) ); // test passed

Or fix the validation rule like below:

$validate = array("name" => array("rule" =>"notempty", "required"=> true),
);

In that case model will check if this element even exists in saving array (and if its not empty)

We spent about 30mins. wandering why validation works through form, and not working in unit test.

Hope that was helpful :)

wbudowana walidacja, a testy jednostkowe modeli

Jeśli piszesz unit testy i polegasz w nich na wbudowanej walidacji – uważaj na pewną drobnostkę.

Załóżmy, że masz model Stuff, a w nim

$validate = array("name" => "notempty");

Przy formularzach dodawania działa to ok, ale teraz chcesz wykonać test:

$this->assertFalse( $this->Stuff->save( array("id"=> 1) ) ); //test fail

Niestety test nie przejdzie (zapis się uda, a nie powinien). Dlaczego?

Otóż taka walidacja jak poniżej oznacza tylko, że
Jeśli w zapisywanych danych jest element “name” to nie może być pusty (null, false, “”).

Zatem, żeby ten zapis nie przeszedł przy tak zdefiniowanej walidacji, należy zrobić:

$this->assertFalse( $this->Stuff->save( array("id"=> 1, "name"=>"") ) ); // test passed

Albo poprawić walidację na

$validate = array("name" => array("rule" =>"notempty", "required"=> true),
);

W takim wypadku zostanie pole zostanie sprawdzone, czy jest niepuste i czy istnieje.

Spędziliśmy tutaj jakieś 30minut zastanawiając się dlaczego przez formularz nie mogę zapisać czegoś nie podając “name”, a w testach jednostkowych się dało.