Skip to content

Commit 8b05ebd

Browse files
committed
Added tests
1 parent 9b96040 commit 8b05ebd

17 files changed

+475
-24
lines changed

.github/workflows/ci.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v5
15+
16+
- name: Set up Go 1.19
17+
uses: actions/setup-go@v6
18+
with:
19+
go-version: '1.19'
20+
21+
- name: Tidy modules
22+
run: go mod tidy
23+
24+
- name: Run tests with coverage (100% enforced)
25+
run: make test
26+
27+
- name: Upload coverage artifact
28+
uses: actions/upload-artifact@v4
29+
with:
30+
name: coverage
31+
path: coverage.out
32+
33+
- name: Upload to Codecov
34+
uses: codecov/codecov-action@v5
35+
with:
36+
files: coverage.out
37+
flags: unittests
38+
fail_ci_if_error: true
39+
env:
40+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@
55
fake-cli
66

77
dist/
8+
9+
coverage.out
10+
.DS_Store

Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,11 @@ run:
2121

2222
# Define the default command as the build command
2323
default: build
24+
25+
test:
26+
TESTING=1 go test ./tests -covermode=atomic -coverpkg=./app,./cache,./config,./handler -coverprofile=coverage.out
27+
@echo "\nCoverage by function/package:" && go tool cover -func=coverage.out | sed 's/^/ /'
28+
@echo "\nUncovered code blocks (file:line1.col-line2.col [statements]):"
29+
@awk 'NR>1 && $$3==0 {print " " $$1 " [" $$2 "]"}' coverage.out | sort
30+
@echo "Enforcing 100% coverage"
31+
@go tool cover -func=coverage.out | awk '/total:/ { if ($$3 != "100.0%") { print; exit 1 } }'

app/app.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package app
2+
3+
import (
4+
"log"
5+
"net/http"
6+
"strconv"
7+
8+
"github.com/brianvoe/gofakeit/v6"
9+
"github.com/paqstd-team/fake-cli/config"
10+
"github.com/paqstd-team/fake-cli/handler"
11+
)
12+
13+
// Run constructs the HTTP server using the provided config path and port.
14+
// It seeds the random generator to make responses deterministic across runs.
15+
func Run(configPath string, port int) (*http.Server, error) {
16+
gofakeit.Seed(0)
17+
18+
cfg, err := config.LoadConfigFromFile(configPath)
19+
if err != nil {
20+
return nil, err
21+
}
22+
23+
srv := &http.Server{
24+
Addr: ":" + strconv.Itoa(port),
25+
Handler: handler.MakeHandler(cfg),
26+
}
27+
28+
log.Printf("Starting server on %v", srv.Addr)
29+
return srv, nil
30+
}

docker-compose.example.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
version: '3'
2-
31
services:
42
# ...other services
53
fake-cli:

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module github.com/paqstd-team/fake-cli
22

3-
go 1.19
3+
go 1.21
44

5-
require github.com/brianvoe/gofakeit/v6 v6.20.1
5+
require github.com/brianvoe/gofakeit/v6 v6.28.0
66

7-
require github.com/gorilla/mux v1.8.0 // indirect
7+
require github.com/gorilla/mux v1.8.1

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
github.com/brianvoe/gofakeit/v6 v6.20.1 h1:8ihJ60OvPnPJ2W6wZR7M+TTeaZ9bml0z6oy4gvyJ/ek=
2-
github.com/brianvoe/gofakeit/v6 v6.20.1/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
3-
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
4-
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
1+
github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4=
2+
github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs=
3+
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
4+
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=

handler/handler.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import (
1010
"github.com/paqstd-team/fake-cli/config"
1111
)
1212

13+
// JSONMarshal is used to marshal response data. It is a variable to allow tests
14+
// to inject failures and exercise error-handling branches.
15+
var JSONMarshal = json.Marshal
16+
1317
func MakeHandler(config config.Config) http.Handler {
1418
mux := mux.NewRouter()
1519
cache := cache.NewCache(config.Cache)
@@ -41,7 +45,7 @@ func makeHandlerFunc(fields any, responseType string, cache *cache.Cache) http.H
4145
data = generateData(fields)
4246
}
4347

44-
jsonData, err := json.Marshal(data)
48+
jsonData, err := JSONMarshal(data)
4549
if err != nil {
4650
http.Error(w, fmt.Sprintf("Error generating JSON: %v", err), http.StatusInternalServerError)
4751
return

main.go

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,18 @@ package main
33
import (
44
"flag"
55
"log"
6-
"net/http"
76
"os"
87
"strconv"
98

10-
"github.com/brianvoe/gofakeit/v6"
11-
"github.com/paqstd-team/fake-cli/config"
12-
"github.com/paqstd-team/fake-cli/handler"
9+
"github.com/paqstd-team/fake-cli/app"
1310
)
1411

1512
func main() {
16-
// Seed the random number generator for reproducibility
17-
gofakeit.Seed(0)
13+
Main()
14+
}
1815

16+
// Main is the exported entrypoint to allow external tests to exercise startup logic.
17+
func Main() {
1918
// Define command-line flags
2019
port := flag.Int("p", 8080, "port number")
2120
configPath := flag.String("c", "config.json", "path to config file")
@@ -37,19 +36,18 @@ func main() {
3736
*configPath = envConfig
3837
}
3938

40-
config, err := config.LoadConfigFromFile(*configPath)
39+
server, err := app.Run(*configPath, *port)
4140
if err != nil {
42-
log.Fatalf("Error loading config: %v", err)
41+
log.Fatalf("Error starting server: %v", err)
4342
}
4443

45-
server := &http.Server{
46-
Addr: ":" + strconv.Itoa(*port),
47-
Handler: handler.MakeHandler(config),
44+
// Allow tests to execute main without blocking
45+
if os.Getenv("TESTING") == "1" {
46+
return
4847
}
4948

50-
log.Printf("Starting server on %v", server.Addr)
51-
err = server.ListenAndServe()
52-
if err != nil {
49+
// Block in production run
50+
if err := server.ListenAndServe(); err != nil {
5351
log.Fatalf("Error starting server: %v", err)
5452
}
5553
}

tests/test_app_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package tests
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"os"
7+
"path/filepath"
8+
"testing"
9+
"time"
10+
11+
"github.com/paqstd-team/fake-cli/app"
12+
)
13+
14+
func TestApp_RunLifecycle(t *testing.T) {
15+
cfgPath := filepath.Join(t.TempDir(), "config.json")
16+
if err := os.WriteFile(cfgPath, []byte(`{"endpoints":[{"url":"/ping","fields":{"id":"uuid"},"response":"single"}],"cache":1}`), 0o600); err != nil {
17+
t.Fatalf("write cfg: %v", err)
18+
}
19+
srv, err := app.Run(cfgPath, 0)
20+
if err != nil {
21+
t.Fatalf("Run: %v", err)
22+
}
23+
go func() { _ = srv.ListenAndServe() }()
24+
time.Sleep(30 * time.Millisecond)
25+
_ = srv.Close()
26+
_ = srv.Shutdown(context.Background())
27+
_ = http.ErrServerClosed
28+
}
29+
30+
func TestApp_RunMissingConfig(t *testing.T) {
31+
_, err := app.Run(filepath.Join(t.TempDir(), "missing.json"), 0)
32+
if err == nil {
33+
t.Fatalf("expected error for missing config file")
34+
}
35+
}

0 commit comments

Comments
 (0)