Skip to content

Commit cfda1bf

Browse files
Feature/47 improve client (#48)
* added contexts + test * added more testing * add test ci * add badge * add badges * fixing linting issue * fixing another lint issue * split out to individual tests * d
1 parent 47cd675 commit cfda1bf

File tree

9 files changed

+260
-20
lines changed

9 files changed

+260
-20
lines changed

.github/workflows/ci.yml

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,32 @@ jobs:
4040
with:
4141
fetch-depth: 0
4242
- name: Install Templ
43-
run: go install github.com/a-h/templ/cmd/templ@latest
43+
run: go install github.com/a-h/templ/cmd/templ@latest
4444

4545
- name: lint
4646
run: |
4747
ls -la
4848
make lint
49-
shell: bash
49+
shell: bash
50+
51+
52+
test:
53+
name: Run ci-tests
54+
runs-on: ubuntu-latest
55+
steps:
56+
- name: Checkout Code
57+
uses: actions/checkout@v4
58+
with:
59+
fetch-depth: 0
60+
- name: setup Go
61+
uses: actions/setup-go@v5
62+
with:
63+
go-version: '1.22.2' #
64+
- name: Make repo safe
65+
run: git config --global --add safe.directory /__w/SOARCA-GUI/SOARCA-GUI
66+
- name: Install Templ
67+
run: go install github.com/a-h/templ/cmd/templ@latest
68+
timeout-minutes: 12
69+
- name: Run tests
70+
run: |
71+
make test

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: dev-server dev-tailwind dev-templ dev build-server build-tailwind build-templ build launch deploy clean
1+
.PHONY: dev-server dev-tailwind dev-templ dev build-server build-tailwind build-templ build launch deploy clean test
22

33

44
BINARY_NAME = soarca-gui
@@ -82,4 +82,7 @@ clean:
8282
run: docker
8383
GIT_VERSION=${VERSION} docker compose up --build --force-recreate -d
8484

85+
test: build-templ
86+
go test ./... -v
87+
8588
.DEFAULT_GOAL := dev

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@
22
<a href="https://cossas-project.org/cossas-software/soarca"><img src="img/soarca-logo.svg"/>
33
</div>
44

5+
56
# SOARCA-GUI
67

8+
9+
[![https://cossas-project.org/portfolio/SOARCA/](https://img.shields.io/badge/website-cossas.github.io-orange)](https://cossas.github.io/SOARCA/docs/)
10+
[![Pipeline status](https://github.com/cossas/soarca-gui/actions/workflows/ci.yml/badge.svg?development)](https://github.com/COSSAS/SOARCA/actions)
11+
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
12+
13+
714
> [!WARNING]
815
> SOARCA-GUI is still in development and features for the base version v0.1 are still being added.
916

backend/soarca/helper.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,60 @@
11
package soarca
22

33
import (
4+
"context"
45
"encoding/json"
56
"fmt"
67
"io"
78
"net/http"
9+
"time"
810
)
911

10-
func fetchToJson(client http.Client, url string, target interface{}) error {
11-
body, err := fetch(client, url)
12+
const (
13+
timeout time.Duration = 500
14+
)
15+
16+
func fetchToJson(client *http.Client, url string, target interface{}) error {
17+
18+
ctx, cancel := context.WithTimeout(context.Background(), timeout*time.Millisecond)
19+
defer cancel()
20+
21+
body, err := fetch(ctx, client, url)
1222
if err != nil {
13-
return err
23+
return fmt.Errorf("fetch failed: %w", err)
1424
}
25+
1526
err = json.Unmarshal(body, target)
1627
if err != nil {
1728
return fmt.Errorf("failed to unmarshal JSON object: %w", err)
1829
}
30+
1931
return nil
2032
}
2133

22-
func fetch(client http.Client, url string) ([]byte, error) {
23-
response, err := client.Get(url)
34+
func fetch(ctx context.Context, client *http.Client, url string) ([]byte, error) {
35+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
2436
if err != nil {
25-
return []byte{}, fmt.Errorf("failed to make GET request: %w", err)
37+
return nil, fmt.Errorf("failed to create request: %w", err)
38+
}
39+
40+
response, err := client.Do(req)
41+
if err != nil {
42+
return nil, fmt.Errorf("failed to make GET request: %w", err)
2643
}
2744
defer response.Body.Close()
2845

2946
if response.StatusCode != http.StatusOK {
30-
return []byte{}, fmt.Errorf("unexpected status code: %d", response.StatusCode)
47+
return nil, fmt.Errorf("unexpected status code: %d", response.StatusCode)
3148
}
3249

3350
body, err := io.ReadAll(response.Body)
3451
if err != nil {
35-
return []byte{}, fmt.Errorf("failed to read response body: %w", err)
52+
return nil, fmt.Errorf("failed to read response body: %w", err)
3653
}
3754

3855
if len(body) == 0 {
39-
return []byte{}, fmt.Errorf("empty response body")
56+
return nil, fmt.Errorf("empty response body")
4057
}
58+
4159
return body, nil
4260
}

backend/soarca/helper_test.go

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package soarca
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
"net/http/httptest"
8+
"reflect"
9+
"strings"
10+
"testing"
11+
"time"
12+
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
type TestData struct {
17+
Name string `json:"name"`
18+
Value int `json:"value"`
19+
}
20+
21+
func TestFetchToJsonSuccessful(t *testing.T) {
22+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
23+
w.Header().Set("Content-Type", "application/json")
24+
w.WriteHeader(http.StatusOK)
25+
err := json.NewEncoder(w).Encode(TestData{Name: "test", Value: 123})
26+
if err != nil {
27+
t.Fatalf("could not encode json: %v", err)
28+
}
29+
}))
30+
defer server.Close()
31+
32+
client := &http.Client{Timeout: 1 * time.Second}
33+
var result TestData
34+
err := fetchToJson(client, server.URL, &result)
35+
if err != nil {
36+
t.Fatalf("unexpected error: %v", err)
37+
}
38+
expected := TestData{Name: "test", Value: 123}
39+
if !reflect.DeepEqual(result, expected) {
40+
t.Errorf("expected data %+v, got %+v", expected, result)
41+
}
42+
}
43+
44+
func TestFetchToJsonNon200StatusCode(t *testing.T) {
45+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
46+
w.WriteHeader(http.StatusNotFound)
47+
}))
48+
defer server.Close()
49+
50+
client := &http.Client{Timeout: 1 * time.Second}
51+
var result TestData
52+
err := fetchToJson(client, server.URL, &result)
53+
54+
expectedErrMsg := "fetch failed: unexpected status code: 404"
55+
if err == nil {
56+
t.Fatalf("expected error containing %q, got nil", expectedErrMsg)
57+
}
58+
if !strings.Contains(err.Error(), expectedErrMsg) {
59+
t.Errorf("expected error containing %q, got %q", expectedErrMsg, err.Error())
60+
}
61+
}
62+
63+
func TestFetchToJsonInvalidJSON(t *testing.T) {
64+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
65+
w.Header().Set("Content-Type", "application/json")
66+
w.WriteHeader(http.StatusOK)
67+
_, err := w.Write([]byte("Success"))
68+
if err != nil {
69+
t.Fatalf("Failed to write response: %v", err)
70+
}
71+
}))
72+
defer server.Close()
73+
74+
client := &http.Client{Timeout: 1 * time.Second}
75+
var result TestData
76+
err := fetchToJson(client, server.URL, &result)
77+
78+
expectedErrMsg := "failed to unmarshal JSON object"
79+
if err == nil {
80+
t.Fatalf("expected error containing %q, got nil", expectedErrMsg)
81+
}
82+
if !strings.Contains(err.Error(), expectedErrMsg) {
83+
t.Errorf("expected error containing %q, got %q", expectedErrMsg, err.Error())
84+
}
85+
}
86+
87+
func TestFetchToJsonEmptyResponseBody(t *testing.T) {
88+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
89+
w.WriteHeader(http.StatusOK)
90+
}))
91+
defer server.Close()
92+
93+
client := &http.Client{Timeout: 1 * time.Second}
94+
var result TestData
95+
err := fetchToJson(client, server.URL, &result)
96+
97+
expectedErrMsg := "fetch failed: empty response body"
98+
if err == nil {
99+
t.Fatalf("expected error containing %q, got nil", expectedErrMsg)
100+
}
101+
if !strings.Contains(err.Error(), expectedErrMsg) {
102+
t.Errorf("expected error containing %q, got %q", expectedErrMsg, err.Error())
103+
}
104+
}
105+
106+
func TestFetchToJsonInvalidURL(t *testing.T) {
107+
client := &http.Client{}
108+
var result TestData
109+
err := fetchToJson(client, "invalid-url", &result)
110+
if err == nil {
111+
t.Fatal("expected error for invalid URL, got nil")
112+
}
113+
if !strings.Contains(err.Error(), "fetch failed") {
114+
t.Errorf("expected error to contain 'fetch failed', got %q", err.Error())
115+
}
116+
}
117+
118+
func TestFetchSuccessful(t *testing.T) {
119+
checkBody := "Success"
120+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
121+
w.WriteHeader(http.StatusOK)
122+
_, err := w.Write([]byte(checkBody))
123+
if err != nil {
124+
t.Fatalf("Failed to write response: %v", err)
125+
}
126+
}))
127+
defer server.Close()
128+
129+
client := server.Client()
130+
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
131+
defer cancel()
132+
133+
body, err := fetch(ctx, client, server.URL)
134+
135+
assert.Nil(t, err, "expected no error, got %v", err)
136+
assert.Equal(t, checkBody, string(body), "expected body to be 'Success', got %v", string(body))
137+
}
138+
139+
func TestFetchEmptyResponseBody(t *testing.T) {
140+
expectedErrMsg := "empty response body"
141+
142+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
143+
w.WriteHeader(http.StatusOK)
144+
}))
145+
defer server.Close()
146+
147+
client := server.Client()
148+
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
149+
defer cancel()
150+
151+
body, err := fetch(ctx, client, server.URL)
152+
153+
assert.NotNil(t, err, "expected an error, got nil")
154+
assert.Contains(t, err.Error(), expectedErrMsg, "expected error message to contain %q, got %q", expectedErrMsg, err.Error())
155+
assert.Empty(t, body, "expected body to be empty, got %v", string(body))
156+
}
157+
158+
func TestFetchContextTimeout(t *testing.T) {
159+
expectedErrMsg := "context deadline exceeded"
160+
161+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
162+
time.Sleep(200 * time.Millisecond)
163+
w.WriteHeader(http.StatusOK)
164+
_, err := w.Write([]byte("Too late"))
165+
if err != nil {
166+
t.Fatalf("Failed to write response: %v", err)
167+
}
168+
}))
169+
defer server.Close()
170+
171+
client := server.Client()
172+
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
173+
defer cancel()
174+
175+
body, err := fetch(ctx, client, server.URL)
176+
177+
assert.NotNil(t, err, "expected an error, got nil")
178+
assert.Contains(t, err.Error(), expectedErrMsg, "expected error message to contain %q, got %q", expectedErrMsg, err.Error())
179+
assert.Empty(t, body, "expected body to be empty, got %v", string(body))
180+
}

backend/soarca/report.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ const (
1414

1515
type Report struct {
1616
Host string
17-
client http.Client
17+
client *http.Client
1818
}
1919

20-
func NewReport(host string, client http.Client) *Report {
20+
func NewReport(host string, client *http.Client) *Report {
2121
return &Report{Host: host, client: client}
2222
}
2323

backend/soarca/status.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
package soarca
22

33
import (
4+
"context"
45
"fmt"
56
"net/http"
7+
"time"
68
)
79

810
type Status struct {
911
Host string
10-
client http.Client
12+
client *http.Client
1113
}
1214

13-
func NewStatus(host string, client http.Client) *Status {
15+
func NewStatus(host string, client *http.Client) *Status {
1416
return &Status{Host: host, client: client}
1517
}
1618

1719
func (status *Status) GetPongFromStatus() (string, error) {
1820
url := fmt.Sprintf("%s%s", status.Host, statusPingPath)
19-
20-
body, err := fetch(status.client, url)
21+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
22+
defer cancel()
23+
body, err := fetch(ctx, status.client, url)
2124
if err != nil {
2225
return "", fmt.Errorf("failed to read response body: %w", err)
2326
}

go.mod

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ go 1.22.2
55
require (
66
github.com/a-h/templ v0.2.747
77
github.com/gin-gonic/gin v1.10.0
8+
github.com/stretchr/testify v1.9.0
9+
)
10+
11+
require (
12+
github.com/davecgh/go-spew v1.1.1 // indirect
13+
github.com/pmezard/go-difflib v1.0.0 // indirect
814
)
915

1016
require (

routes/routes.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ func Setup(app *gin.Engine) {
1818
ctx.Redirect(http.StatusTemporaryRedirect, "/404-page")
1919
})
2020

21-
reporter := soarca.NewReport(utils.GetEnv("SOARCA_URI", "http://localhost:8080"), http.Client{})
22-
status := soarca.NewStatus(utils.GetEnv("SOARCA_URI", "http://localhost:8080"), http.Client{})
21+
reporter := soarca.NewReport(utils.GetEnv("SOARCA_URI", "http://localhost:8080"), &http.Client{})
22+
23+
status := soarca.NewStatus(utils.GetEnv("SOARCA_URI", "http://localhost:8080"), &http.Client{})
2324

2425
publicRoutes := app.Group("/")
2526

0 commit comments

Comments
 (0)