Skip to content

Commit

Permalink
test
Browse files Browse the repository at this point in the history
  • Loading branch information
mstgnz committed Dec 29, 2024
1 parent dce35dc commit 554e870
Show file tree
Hide file tree
Showing 6 changed files with 310 additions and 0 deletions.
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ require (
github.com/minio/minio-go/v7 v7.0.66
github.com/prometheus/client_golang v1.20.5
github.com/rs/zerolog v1.33.0
github.com/stretchr/testify v1.10.0
go.opentelemetry.io/otel v1.33.0
go.opentelemetry.io/otel/exporters/jaeger v1.17.0
go.opentelemetry.io/otel/sdk v1.33.0
Expand All @@ -45,6 +46,7 @@ require (
github.com/aws/smithy-go v1.19.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
Expand All @@ -66,6 +68,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/philhofer/fwd v1.1.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
Expand All @@ -85,4 +88,5 @@ require (
golang.org/x/text v0.16.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ib
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
Expand Down Expand Up @@ -137,6 +141,8 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
Expand Down Expand Up @@ -216,6 +222,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/gographics/imagick.v3 v3.5.1 h1:58JqK0UCx5RfvbRggF5FKuK6jHwAtTQopUxK8mzFa40=
gopkg.in/gographics/imagick.v3 v3.5.1/go.mod h1:+Q9nyA2xRZXrDyTtJ/eko+8V/5E7bWYs08ndkZp8UmA=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
Expand Down
61 changes: 61 additions & 0 deletions test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Testing Guide

This directory contains various tests for the CDN service.

## Test Types

### 1. Unit Tests
Located in `unit/` directory, these test individual components in isolation.
```bash
go test ./test/unit/... -v
```

### 2. Integration Tests
Located in `integration/` directory, these test API endpoints with real HTTP calls.
```bash
go test ./test/integration/... -v
```

### 3. Load Tests
Located in `performance/` directory, using k6 for load testing.
```bash
k6 run test/performance/load_test.js
```

## Prerequisites

- Go 1.21 or higher
- k6 for load testing
- Docker for integration tests
- `testify` package for assertions

## Running Tests

### All Tests
```bash
make test
```

### Unit Tests with Coverage
```bash
go test ./test/unit/... -coverprofile=coverage.out
go tool cover -html=coverage.out
```

### Load Testing Scenarios

1. Basic Load Test:
```bash
k6 run test/performance/load_test.js
```

2. Stress Test (modify options in script):
```bash
k6 run --vus 50 --duration 5m test/performance/load_test.js
```

## Test Data

- Sample test files are in `test/data/`
- Mock services are in respective test directories
- Environment variables for tests in `.env.test`
63 changes: 63 additions & 0 deletions test/integration/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package integration

import (
"bytes"
"encoding/json"
"net/http"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

const (
baseURL = "http://localhost:9090"
timeout = 10 * time.Second
)

func TestHealthEndpoint(t *testing.T) {
client := &http.Client{Timeout: timeout}

resp, err := client.Get(baseURL + "/health")
assert.NoError(t, err)
defer resp.Body.Close()

assert.Equal(t, http.StatusOK, resp.StatusCode)

var body map[string]interface{}
err = json.NewDecoder(resp.Body).Decode(&body)
assert.NoError(t, err)

assert.Equal(t, true, body["status"])
assert.Equal(t, "Healthy", body["message"])
}

func TestUploadEndpoint(t *testing.T) {
client := &http.Client{Timeout: timeout}

// Test file upload
fileContents := []byte("test image content")
req, err := http.NewRequest("POST", baseURL+"/upload", bytes.NewBuffer(fileContents))
assert.NoError(t, err)

req.Header.Set("Content-Type", "multipart/form-data")
req.Header.Set("Authorization", "test-token")

resp, err := client.Do(req)
assert.NoError(t, err)
defer resp.Body.Close()

// Should fail without proper form data
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
}

func TestMetricsEndpoint(t *testing.T) {
client := &http.Client{Timeout: timeout}

resp, err := client.Get(baseURL + "/metrics")
assert.NoError(t, err)
defer resp.Body.Close()

assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Contains(t, resp.Header.Get("Content-Type"), "text/plain")
}
45 changes: 45 additions & 0 deletions test/performance/load_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import http from 'k6/http';
import { check, sleep } from 'k6';

export let options = {
stages: [
{ duration: '30s', target: 20 }, // Ramp up to 20 users
{ duration: '1m', target: 20 }, // Stay at 20 users
{ duration: '30s', target: 0 }, // Ramp down to 0 users
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95% of requests should be below 500ms
http_req_failed: ['rate<0.01'], // Less than 1% of requests should fail
},
};

const BASE_URL = 'http://localhost:9090';

export default function () {
// Health check
let healthCheck = http.get(`${BASE_URL}/health`);
check(healthCheck, {
'health check status is 200': (r) => r.status === 200,
'health check response is healthy': (r) => r.json().status === true,
});

// Metrics check
let metrics = http.get(`${BASE_URL}/metrics`);
check(metrics, {
'metrics status is 200': (r) => r.status === 200,
});

// Upload test (with small file)
let testFile = open('./test.jpg', 'b');
let uploadData = {
file: http.file(testFile, 'test.jpg'),
bucket: 'test-bucket',
};

let upload = http.post(`${BASE_URL}/upload`, uploadData);
check(upload, {
'upload status is 200': (r) => r.status === 200,
});

sleep(1);
}
129 changes: 129 additions & 0 deletions test/unit/handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package unit

import (
"bytes"
"encoding/json"
"net/http/httptest"
"testing"

"github.com/gofiber/fiber/v2"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/mstgnz/cdn/handler"
"github.com/mstgnz/cdn/service"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func setupMockMinio() *minio.Client {
client, err := minio.New("localhost:9000", &minio.Options{
Creds: credentials.NewStaticV4("minioadmin", "minioadmin", ""),
Secure: false,
})
if err != nil {
return nil
}
return client
}

type MockAwsService struct {
mock.Mock
service.AwsService
}

type MockCacheService struct {
mock.Mock
service.CacheService
}

func TestHealthCheck(t *testing.T) {
// Setup
app := fiber.New()
mockMinio := setupMockMinio()
mockAws := &MockAwsService{}
mockCache := &MockCacheService{}

healthChecker := handler.NewHealthChecker(mockMinio, mockAws, mockCache)
app.Get("/health", healthChecker.HealthCheck)

// Test cases
tests := []struct {
name string
expectedStatus int
expectedBody map[string]interface{}
}{
{
name: "Success Response",
expectedStatus: fiber.StatusOK,
expectedBody: map[string]interface{}{
"status": true,
"message": "Healthy",
"data": map[string]interface{}{
"minio": "Connected",
"aws": "Connected",
"redis": "Connected",
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest("GET", "/health", nil)
resp, err := app.Test(req)

assert.NoError(t, err)
assert.Equal(t, tt.expectedStatus, resp.StatusCode)

var body map[string]interface{}
err = json.NewDecoder(resp.Body).Decode(&body)

assert.NoError(t, err)
assert.Equal(t, tt.expectedBody, body)
})
}
}

func TestUploadImage(t *testing.T) {
// Setup
app := fiber.New()
mockMinio := setupMockMinio()
mockAws := &MockAwsService{}

imageHandler := handler.NewImage(mockMinio, mockAws)
app.Post("/upload", imageHandler.UploadImage)

// Test cases
tests := []struct {
name string
payload []byte
expectedStatus int
expectedError string
}{
{
name: "Invalid Request",
payload: []byte(`{}`),
expectedStatus: fiber.StatusBadRequest,
expectedError: "Invalid request",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest("POST", "/upload", bytes.NewBuffer(tt.payload))
req.Header.Set("Content-Type", "application/json")

resp, err := app.Test(req)

assert.NoError(t, err)
assert.Equal(t, tt.expectedStatus, resp.StatusCode)

if tt.expectedError != "" {
var body map[string]interface{}
err = json.NewDecoder(resp.Body).Decode(&body)
assert.NoError(t, err)
assert.Equal(t, tt.expectedError, body["message"])
}
})
}
}

0 comments on commit 554e870

Please sign in to comment.