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!
Faça parte da comunidade!
Receba os melhores conteúdos sobre Go, Kubernetes, arquitetura de software, Cloud e esteja sempre atualizado com as tendências e práticas do mercado.
Livros Recomendados
Abaixo listei alguns dos melhores livros que já li sobre GO.

