diff --git a/app_go/GO.md b/app_go/GO.md index dbed922119..74f9266837 100644 --- a/app_go/GO.md +++ b/app_go/GO.md @@ -18,3 +18,23 @@ external installation. - Explicit error handling is applied everywhere except for transmission errors (both in networking and when printing to console), which are explicitly ignored for there is nothing to do in those cases. + +## Tests + +For the project there are unit tests that cover key functionalities +of the web application. There are: + +- Unit tests: ensure that cat facts are queried without error and + do not repeat too often. + +- Integration tests: test that when whatever http request is sent, + the server responses with a non-empty catfact (which is not an + error). + +Tests are implemented using best practices: + +- A cannonical project structure proposed by the Go authors. + +- There are both tests for API and tests that interact with the app + in a way similar to user interface. + diff --git a/app_go/README.md b/app_go/README.md index ce6a627c5f..9148740957 100644 --- a/app_go/README.md +++ b/app_go/README.md @@ -1,5 +1,7 @@ # Cat fact web app +![CI badge](https://github.com/kolayne-IU-assignments/S24-core-course-labs/actions/workflows/python-app.yml/badge.svg) + A web application that shows random cat facts on its main page. ## One-shot run @@ -46,3 +48,7 @@ docker run --rm -d -p 5000 kolay0ne/app_go ``` Replace `kolay0ne/app_go` with your image/tag name if you built it manually. + +## Unit Tests + +To run unit tests, navigate to the project directory and run `go test`. diff --git a/app_go/catfact_test.go b/app_go/catfact_test.go new file mode 100644 index 0000000000..5668cb3d5d --- /dev/null +++ b/app_go/catfact_test.go @@ -0,0 +1,29 @@ +package main + +import "testing" + +// TestCatFact tests that `catFact` loads non-empty facts +// without errors and that those facts are different +func TestCatFact(t *testing.T) { + const TRIES = 5 + + options := map[string]int{} + + for i := 0; i < TRIES; i++ { + s, e := catFact() + if e != nil { + t.Fatal("Error quering a cat fact", e) + } else if s == "" { + t.Fatal("No error occurred but `catFact` returned" + + "an empty string") + } + + options[s] += 1 + } + + // Tests are repeating + t.Logf("Out of %d cat facts, %d are unique", TRIES, len(options)) + if len(options) <= (1+TRIES)/2 { + t.FailNow() + } +} diff --git a/app_go/main.go b/app_go/main.go index dd80eb776e..ee57e27cac 100644 --- a/app_go/main.go +++ b/app_go/main.go @@ -9,8 +9,10 @@ import ( func handler(w http.ResponseWriter, r *http.Request) { fact, err := catFact() if err == nil { + w.WriteHeader(http.StatusOK) _, _ = fmt.Fprintf(w, fact) } else { + w.WriteHeader(http.StatusInternalServerError) _, _ = fmt.Fprintf(w, "Failed to query a cat fact :(") } } diff --git a/app_go/main_test.go b/app_go/main_test.go new file mode 100644 index 0000000000..d78a0dfee2 --- /dev/null +++ b/app_go/main_test.go @@ -0,0 +1,34 @@ +package main + +import ( + "io" + "testing" + "net/http" + "net/http/httptest" + "strings" +) + +// TestFactLoads tests that the handler returns something that +// is neither empty nor an error +func TestFactLoads(t *testing.T) { + w := httptest.NewRecorder() + + handler(w, nil) + resp := w.Result() + + if resp.StatusCode != http.StatusOK { + t.Fatal("Server responded with exit code", w.Code) + } + + buf, err := io.ReadAll(w.Body) + body := string(buf) + if err != nil { + t.Fatal("Failed to read response body") + } + if len(buf) == 0 { + t.Fatal("Response body is empty") + } + if strings.Contains(body, "Fail") && strings.Contains(body, ":(") { + t.Fatal("An error occurred while retreiving cat fact") + } +}