Na primeira parte desse post, vimos como utilizar o fuzz test para gerar payloads automaticamente em nossos testes, o que nos ajudou a encontrar problemas quando o payload não vinha no formato que esperávamos.
Nessa segunda parte, vamos utilizar o fuzz para gerar os inputs que serão utilizados nos campos de um payload correto.
Utilizando o mesmo código do post anterior, antes de começar, vamos fazer uma pequena mudança na validação de e-mail. Além de validar se ele foi preenchido, vamos validar se o e-mail é válido.
Para isso, vamos criar um novo erro e um else para quando o valor do e-mail não esteja vazio.
... ErrEmailRequired = errors.New("Email is required") ErrEmailInvalid = errors.New("Email is invalid") ... if p.Email == "" { return ErrEmailRequired } else { rgx := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:.[a-zA-Z0-9-]+)*$") if !rgx.MatchString(p.Email) { return ErrEmailInvalid } } ...
Agora, como boa parte do código do teste é igual ao anterior, vamos duplicar a função renomeando-a para FuzzValidateData.
func FuzzValidateData(f *testing.F) { testcases := []Person{ {"Tiago Temporin", "tiago@aprendagolang.com.br", "1234", 32}, {"Maria Castro", "maria.castro@algumdominio.com", "1Av6s#", 22}, {"Daniela Fernandez", "dani@teste.com.br", "1234", 16}, } for _, tc := range testcases { // seed data, _ := json.Marshal(tc) f.Add(data) } knowErrs := map[string]bool{ ErrNameRequired.Error(): true, ErrEmailRequired.Error(): true, ErrPwdRequired.Error(): true, ErrPwdMinChars.Error(): true, ErrAgeMin.Error(): true, ErrInvalidPayload.Error(): true, } srv := httptest.NewServer(http.HandlerFunc(validate)) defer srv.Close() f.Fuzz(func(t *testing.T, data []byte) { resp, err := http.DefaultClient.Post(srv.URL, "application/json", bytes.NewBuffer(data)) if err != nil { t.Errorf("Error: %v", err) } if resp.StatusCode != http.StatusOK { body, _ := ioutil.ReadAll(resp.Body) if _, ok := knowErrs[string(body)]; ok { t.Skip(fmt.Sprintf("skiping knowing error: %s", body)) } t.Errorf("Expected status code %d, got %d with error :%s", http.StatusOK, resp.StatusCode, body) } }) }
Modificando Fuzz Test
Como queremos que sejam gerados inputs para cada um dos campos que temos na nossa struct, precisamos modificar o looping onde fazemos o seed de dados para o fuzz.
for _, tc := range testcases { f.Add(tc.Name, tc.Email, tc.Password, tc.Age) }
Feito isso, o próximo passo é alterar a função do f.Fuzz
para “acomodar” os novos parâmetros.
f.Fuzz(func(t *testing.T, name, email, password string, age uint8) {
Dentro da função do f.Fuzz
, como ela não recebe mais um slice bytes como parâmetro, antes de realizar a request, precisamos criar uma struct com os inputs recebidos e em seguida fazer o json.Marshal
para gerar o slice de bytes que será enviado no POST.
p := Person{ Name: name, Email: email, Password: password, Age: age, } data, _ := json.Marshal(p)
O código completo ficará assim:
func FuzzValidateData(f *testing.F) { testcases := []Person{ {"Tiago Temporin", "tiago@aprendagolang.com.br", "1234", 32}, {"Maria Castro", "maria.castro@algumdominio.com", "1Av6s#", 22}, {"Daniela Fernandez", "dani@teste.com.br", "1234", 16}, } for _, tc := range testcases { f.Add(tc.Name, tc.Email, tc.Password, tc.Age) } knowErrs := map[string]bool{ ErrNameRequired.Error(): true, ErrEmailRequired.Error(): true, ErrPwdRequired.Error(): true, ErrPwdMinChars.Error(): true, ErrAgeMin.Error(): true, ErrInvalidPayload.Error(): true, } srv := httptest.NewServer(http.HandlerFunc(validate)) defer srv.Close() f.Fuzz(func(t *testing.T, name, email, password string, age uint8) { p := Person{ Name: name, Email: email, Password: password, Age: age, } data, _ := json.Marshal(p) resp, err := http.DefaultClient.Post(srv.URL, "application/json", bytes.NewBuffer(data)) if err != nil { t.Errorf("Error: %v", err) } if resp.StatusCode != http.StatusOK { body, _ := ioutil.ReadAll(resp.Body) if _, ok := knowErrs[string(body)]; ok { t.Skip(fmt.Sprintf("skiping knowing error: %s", body)) } t.Errorf("Expected status code %d, got %d with error :%s", http.StatusOK, resp.StatusCode, body) } }) }
Como temos 2 funções de fuzz e só podemos executar uma função por vez, precisamos adicionar um filtro na hora de executar o comando dos testes.
$ go test -fuzz ValidateData -fuzztime 5s .
Ao executar o comando acima, o seguinte erro foi exibido no terminal:
fuzz: elapsed: 0s, gathering baseline coverage: 0/65 completed fuzz: minimizing 78-byte failing input file fuzz: elapsed: 0s, gathering baseline coverage: 4/65 completed --- FAIL: FuzzValidateData (0.10s) --- FAIL: FuzzValidateData (0.00s) main_test.go:99: Expected status code 200, got 400 with error :Email is invalid Failing input written to testdata/fuzz/FuzzValidateData/7b49f646b184e97f52133f9fc761792ec19f6fe185c357510535b999ff53dcc4 To re-run: go test -run=FuzzValidateData/7b49f646b184e97f52133f9fc761792ec19f6fe185c357510535b999ff53dcc4 FAIL exit status 1 FAIL github.com/aprendagolang/httpfuzz 0.869s
Como podemos ver na mensagem de erro, o problema encontrado foi que o e-mail passado na request é inválido. Podemos confirmar isso ao abrir o arquivo que foi gerado em testdata/fuzz/FuzzValidateData/7b49f646b184e97f52133f9fc761792ec19f6fe185c357510535b999ff53dcc4
onde podemos ver os valores passados como Name, Email, Password e Age.
go test fuzz v1 string("0") string("0") string("0") byte('\\x10')
Para resolver esse problema, basta adicionar o erro que já havíamos definido à nossa lista de erros conhecidos na função FuzzValidateData
.
knowErrs := map[string]bool{ ErrNameRequired.Error(): true, ErrEmailRequired.Error(): true, ErrEmailInvalid.Error(): true, ErrPwdRequired.Error(): true, ErrPwdMinChars.Error(): true, ErrAgeMin.Error(): true, ErrInvalidPayload.Error(): true, }
Ao executar o comando novamente, o resultado foi o seguinte:
fuzz: elapsed: 0s, gathering baseline coverage: 0/82 completed fuzz: elapsed: 0s, gathering baseline coverage: 82/82 completed, now fuzzing with 4 workers fuzz: elapsed: 3s, execs: 6078 (2026/sec), new interesting: 7 (total: 89) fuzz: elapsed: 6s, execs: 6678 (200/sec), new interesting: 7 (total: 89) fuzz: elapsed: 6s, execs: 6678 (0/sec), new interesting: 7 (total: 89) PASS ok github.com/aprendagolang/httpfuzz 6.276s
Com isso fechamos os posts, pelo menos por enquanto, sobre como utilizar Fuzz Test em requests HTTP.
Espero que tenham gostado.
Até a próxima!