Skip to content

Commit 2731dd6

Browse files
authored
Merge pull request #2407 from gofr-dev/release/v1.46.2
Release/v1.46.2
2 parents 4c31ca8 + 84023a4 commit 2731dd6

File tree

28 files changed

+945
-250
lines changed

28 files changed

+945
-250
lines changed

.github/workflows/typos.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ jobs:
1111
- name: Checkout Code
1212
uses: actions/checkout@v5
1313
- name: typos-action
14-
uses: crate-ci/typos@v1.36.3
14+
uses: crate-ci/typos@v1.38.0

CONTRIBUTING.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@
33
files a PR. This is only suitable for really small changes like: spelling fixes, variable name changes or error string
44
change etc. For larger commits, following steps are recommended.
55
* (Optional) If you want to discuss your implementation with the users of GoFr, use the GitHub discussions of this repo.
6-
* Configure your editor to use goimport and golangci-lint on file changes. Any code which is not formatted using these
6+
* Configure your editor to use goimports and golangci-lint on file changes. Any code which is not formatted using these
77
tools, will fail on the pipeline.
88
* Contributors should begin working on an issue only after it has been assigned to them. To get an issue assigned, please comment on the GitHub thread
99
and request assignment from a maintainer. This helps avoid duplicate or conflicting pull requests from multiple contributors.
1010
* Issues labeled triage are not open for direct contributions. If you're interested in working on a triage issue, please reach out to the maintainers
11-
to discuss it before proceeding in the Github thread.
11+
to discuss it before proceeding in the GitHub thread.
1212
<!-- spellchecker:off "favour" have to be ignored here -->
1313
* We follow **American English** conventions in this project (e.g., *"favor"* instead of *"favour"*). Please keep this consistent across all code comments, documentation, etc.
1414
<!-- spellchecker:on -->
15-
* All code contributions should have associated tests and all new line additions should be covered in those testcases.
15+
* All code contributions should have associated tests and all new line additions should be covered in those test cases.
1616
No PR should ever decrease the overall code coverage.
17-
* Once your code changes are done along with the testcases, submit a PR to development branch. Please note that all PRs
17+
* Once your code changes are done along with the test cases, submit a PR to development branch. Please note that all PRs
1818
are merged from feature branches to development first.
1919
* PR should be raised only when development is complete and the code is ready for review. This approach helps reduce the number of open pull requests and facilitates a more efficient review process for the team.
2020
* All PRs need to be reviewed by at least 2 GoFr developers. They might reach out to you for any clarification.
@@ -50,7 +50,6 @@ func TestFunctionName(t *testing.T) {
5050
```
5151

5252

53-
5453
4. **Table-Driven Tests:**
5554

5655
- Consider using table-driven tests for testing multiple scenarios.
@@ -61,6 +60,8 @@ func TestFunctionName(t *testing.T) {
6160
```console
6261
docker run --name mongodb -d -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=user -e MONGO_INITDB_ROOT_PASSWORD=password mongodb/mongodb-community-server:latest
6362
docker run -d -p 21:21 -p 21000-21010:21000-21010 -e USERS='user|password' delfer/alpine-ftp-server
63+
```
64+
6465
# the docker image is relatively unstable. Alternatively, refer to official guide of OpenTSDB to locally setup OpenTSDB env.
6566
# http://opentsdb.net/docs/build/html/installation.html#id1
6667
docker run -d --name gofr-opentsdb -p 4242:4242 petergrace/opentsdb-docker:latest

examples/http-server/main_test.go

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,24 @@ import (
55
"encoding/json"
66
"io"
77
"net/http"
8+
"net/http/httptest"
89
"os"
910
"strconv"
11+
"strings"
1012
"testing"
1113
"time"
1214

1315
"github.com/go-redis/redismock/v9"
1416
"github.com/stretchr/testify/assert"
1517
"github.com/stretchr/testify/require"
18+
"go.uber.org/mock/gomock"
1619

1720
"gofr.dev/pkg/gofr"
1821
"gofr.dev/pkg/gofr/config"
1922
"gofr.dev/pkg/gofr/container"
2023
"gofr.dev/pkg/gofr/datasource/redis"
2124
"gofr.dev/pkg/gofr/logging"
25+
"gofr.dev/pkg/gofr/service"
2226
"gofr.dev/pkg/gofr/testutil"
2327
)
2428

@@ -140,7 +144,7 @@ func TestIntegration_SimpleAPIServer_Health(t *testing.T) {
140144
statusCode int
141145
}{
142146
{"health handler", "/.well-known/health", http.StatusOK}, // Health check should be added by the framework.
143-
{"favicon handler", "/favicon.ico", http.StatusOK}, //Favicon should be added by the framework.
147+
{"favicon handler", "/favicon.ico", http.StatusOK}, // Favicon should be added by the framework.
144148
}
145149

146150
for i, tc := range tests {
@@ -180,3 +184,140 @@ func TestRedisHandler(t *testing.T) {
180184
assert.Nil(t, resp)
181185
require.Error(t, err)
182186
}
187+
188+
// MockRequest implements the Request interface for testing
189+
type MockRequest struct {
190+
*http.Request
191+
params map[string]string
192+
}
193+
194+
func (m *MockRequest) HostName() string {
195+
if m.Request != nil {
196+
return m.Request.Host
197+
}
198+
199+
return ""
200+
}
201+
202+
func (m *MockRequest) Params(s string) []string {
203+
if m.Request != nil {
204+
return m.Request.URL.Query()[s]
205+
}
206+
207+
return nil
208+
}
209+
210+
func NewMockRequest(req *http.Request) *MockRequest {
211+
// Parse query parameters
212+
queryParams := make(map[string]string)
213+
for k, v := range req.URL.Query() {
214+
if len(v) > 0 {
215+
queryParams[k] = v[0]
216+
}
217+
}
218+
219+
return &MockRequest{
220+
Request: req,
221+
params: queryParams,
222+
}
223+
}
224+
225+
// Param returns URL query parameters
226+
func (m *MockRequest) Param(key string) string {
227+
return m.params[key]
228+
}
229+
230+
// PathParam returns URL path parameters
231+
func (m *MockRequest) PathParam(key string) string {
232+
return ""
233+
}
234+
235+
// Bind implements the Bind method required by the Request interface
236+
func (m *MockRequest) Bind(i any) error {
237+
return nil
238+
}
239+
240+
// createTestContext sets up a GoFr context for unit tests with a given URL and optional mock container.
241+
func createTestContext(method, url string, mockContainer *container.Container) *gofr.Context {
242+
req := httptest.NewRequest(method, url, nil)
243+
mockReq := NewMockRequest(req)
244+
245+
var c *container.Container
246+
if mockContainer != nil {
247+
c = mockContainer
248+
} else {
249+
c = &container.Container{Logger: logging.NewLogger(logging.DEBUG)}
250+
}
251+
252+
logger := c.Logger
253+
254+
return &gofr.Context{
255+
Context: req.Context(),
256+
Request: mockReq,
257+
Container: c,
258+
ContextLogger: *logging.NewContextLogger(req.Context(), logger),
259+
}
260+
}
261+
262+
func TestHelloHandler(t *testing.T) {
263+
// With name parameter
264+
ctx := createTestContext(http.MethodGet, "/hello?name=test", nil)
265+
resp, err := HelloHandler(ctx)
266+
assert.NoError(t, err)
267+
assert.Equal(t, "Hello test!", resp)
268+
269+
// Without name parameter
270+
ctx = createTestContext(http.MethodGet, "/hello", nil)
271+
resp, err = HelloHandler(ctx)
272+
assert.NoError(t, err)
273+
assert.Equal(t, "Hello World!", resp)
274+
}
275+
276+
func TestErrorHandler(t *testing.T) {
277+
ctx := createTestContext(http.MethodGet, "/error", nil)
278+
279+
resp, err := ErrorHandler(ctx)
280+
assert.Nil(t, resp)
281+
assert.Error(t, err)
282+
assert.Equal(t, "some error occurred", err.Error())
283+
}
284+
285+
func TestMysqlHandler(t *testing.T) {
286+
mockContainer, mocks := container.NewMockContainer(t)
287+
288+
// Setup SQL mock to return 4
289+
mocks.SQL.ExpectQuery("select 2+2").
290+
WillReturnRows(mocks.SQL.NewRows([]string{"value"}).AddRow(4))
291+
292+
ctx := createTestContext(http.MethodGet, "/mysql", mockContainer)
293+
294+
resp, err := MysqlHandler(ctx)
295+
assert.NoError(t, err)
296+
assert.Equal(t, 4, resp)
297+
}
298+
299+
func TestTraceHandler(t *testing.T) {
300+
mockContainer, mocks := container.NewMockContainer(t, container.WithMockHTTPService())
301+
302+
// Redis expectations
303+
mocks.Redis.EXPECT().Ping(gomock.Any()).Return(nil).Times(5)
304+
305+
// HTTP service mock
306+
httpService := mocks.HTTPService
307+
mockResp := &http.Response{
308+
StatusCode: http.StatusOK,
309+
Body: io.NopCloser(strings.NewReader(`{"data":"mock data"}`)),
310+
}
311+
httpService.EXPECT().Get(gomock.Any(), "redis", gomock.Any()).Return(mockResp, nil)
312+
313+
// Attach service to container
314+
mockContainer.Services = map[string]service.HTTP{
315+
"anotherService": httpService,
316+
}
317+
318+
ctx := createTestContext(http.MethodGet, "/trace", mockContainer)
319+
320+
resp, err := TraceHandler(ctx)
321+
assert.NoError(t, err)
322+
assert.Equal(t, "mock data", resp)
323+
}

examples/sample-cmd/main_test.go

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,76 @@ func TestCMDRun_ProgressContextCancelled(t *testing.T) {
9898
ctx, cancel := context.WithCancel(context.Background())
9999
cancel()
100100

101-
// add an already canceled context
101+
// Create a proper context with logger to avoid nil pointer dereference
102+
container := &container.Container{
103+
Logger: logging.NewMockLogger(logging.ERROR),
104+
}
105+
102106
res, err := progress(&gofr.Context{
103-
Context: ctx,
104-
Request: cmd.NewRequest([]string{"command", "spinner"}),
105-
Container: &container.Container{
106-
Logger: logging.NewMockLogger(logging.ERROR),
107-
},
108-
Out: terminal.New(),
107+
Context: ctx,
108+
Request: cmd.NewRequest([]string{"command", "progress"}),
109+
Container: container,
110+
Out: terminal.New(),
111+
ContextLogger: *logging.NewContextLogger(ctx, container.Logger),
109112
})
110113

111114
assert.Empty(t, res)
112115
assert.ErrorIs(t, err, context.Canceled)
113116
}
117+
118+
// TestCMDRunWithInvalidCommand tests that invalid commands return appropriate error
119+
func TestCMDRunWithInvalidCommand(t *testing.T) {
120+
expErr := "No Command Found!\n"
121+
os.Args = []string{"command", "invalid"}
122+
output := testutil.StderrOutputForFunc(main)
123+
124+
assert.Equal(t, expErr, output, "TEST Failed.\n")
125+
}
126+
127+
// TestCMDRunWithEmptyParams tests the params command with empty name parameter
128+
func TestCMDRunWithEmptyParams(t *testing.T) {
129+
expResp := "Hello !\n"
130+
os.Args = []string{"command", "params", "-name="}
131+
output := testutil.StdoutOutputForFunc(main)
132+
133+
assert.Contains(t, output, expResp, "TEST Failed.\n")
134+
}
135+
136+
// TestCMDRunHelpCommand tests the help functionality
137+
func TestCMDRunHelpCommand(t *testing.T) {
138+
testCases := []struct {
139+
args []string
140+
expected []string
141+
}{
142+
{[]string{"command", "help"}, []string{"Available commands:", "hello", "params", "spinner", "progress"}},
143+
{[]string{"command", "-h"}, []string{"Available commands:", "hello", "params", "spinner", "progress"}},
144+
{[]string{"command", "--help"}, []string{"Available commands:", "hello", "params", "spinner", "progress"}},
145+
}
146+
147+
for i, tc := range testCases {
148+
os.Args = tc.args
149+
output := testutil.StdoutOutputForFunc(main)
150+
151+
for _, expected := range tc.expected {
152+
assert.Contains(t, output, expected, "TEST[%d] Failed. Expected to contain: %s\n", i, expected)
153+
}
154+
}
155+
}
156+
157+
// TestCMDRunHelpForSpecificCommand tests help for specific commands
158+
func TestCMDRunHelpForSpecificCommand(t *testing.T) {
159+
testCases := []struct {
160+
args []string
161+
expected string
162+
}{
163+
{[]string{"command", "hello", "-h"}, "hello world option"},
164+
{[]string{"command", "hello", "--help"}, "hello world option"},
165+
}
166+
167+
for i, tc := range testCases {
168+
os.Args = tc.args
169+
output := testutil.StdoutOutputForFunc(main)
170+
171+
assert.Contains(t, output, tc.expected, "TEST[%d] Failed.\n", i)
172+
}
173+
}

examples/using-custom-metrics/main_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ func TestIntegration(t *testing.T) {
5454

5555
assert.Equal(t, http.StatusOK, resp.StatusCode, "TEST[%d], Failed.\n%s")
5656

57-
assert.Contains(t, strBody, `product_stock{otel_scope_name="using-metrics",otel_scope_version="v0.1.0"} 50`)
58-
assert.Contains(t, strBody, `total_credit_day_sale{otel_scope_name="using-metrics",otel_scope_version="v0.1.0",sale_type="credit"} 1000`)
59-
assert.Contains(t, strBody, `total_credit_day_sale{otel_scope_name="using-metrics",otel_scope_version="v0.1.0",sale_type="credit_return"} -1000`)
60-
assert.Contains(t, strBody, `transaction_success_total{otel_scope_name="using-metrics",otel_scope_version="v0.1.0"} 1`)
57+
assert.Contains(t, strBody, `product_stock{otel_scope_name="using-metrics",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 50`)
58+
assert.Contains(t, strBody, `total_credit_day_sale{otel_scope_name="using-metrics",otel_scope_schema_url="",otel_scope_version="v0.1.0",sale_type="credit"} 1000`)
59+
assert.Contains(t, strBody, `total_credit_day_sale{otel_scope_name="using-metrics",otel_scope_schema_url="",otel_scope_version="v0.1.0",sale_type="credit_return"} -1000`)
60+
assert.Contains(t, strBody, `transaction_success{otel_scope_name="using-metrics",otel_scope_schema_url="",otel_scope_version="v0.1.0"} 1`)
6161
assert.Contains(t, strBody, "transaction_time")
6262
}

examples/using-http-service/main_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/stretchr/testify/assert"
1515
"github.com/stretchr/testify/require"
16+
"go.uber.org/mock/gomock"
1617

1718
"gofr.dev/pkg/gofr"
1819
"gofr.dev/pkg/gofr/container"
@@ -82,12 +83,15 @@ func TestHTTPHandlerURLError(t *testing.T) {
8283
fmt.Sprint("http://localhost:", port, "/handle"), bytes.NewBuffer([]byte(`{"key":"value"}`)))
8384
gofrReq := gofrHTTP.NewRequest(req)
8485

85-
mockContainer, _ := container.NewMockContainer(t)
86+
mockContainer, mocks := container.NewMockContainer(t)
8687

8788
ctx := &gofr.Context{Context: context.Background(), Request: gofrReq, Container: mockContainer}
8889

8990
ctx.Container.Services = map[string]service.HTTP{"cat-facts": service.NewHTTPService("http://invalid", ctx.Logger, mockContainer.Metrics())}
9091

92+
mocks.Metrics.EXPECT().RecordHistogram(gomock.Any(), "app_http_service_response", gomock.Any(), gomock.Any(),
93+
"http://invalid", "method", "GET", "status", gomock.Any())
94+
9195
resp, err := Handler(ctx)
9296

9397
assert.Nil(t, resp)

0 commit comments

Comments
 (0)