Skip to content

Commit 6d6937d

Browse files
committed
core application logic tests
1 parent 56a8384 commit 6d6937d

File tree

8 files changed

+256
-43
lines changed

8 files changed

+256
-43
lines changed

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ To use your MCP server with Claude Desktop, add it to your Claude configuration:
3030
"mcpServers": {
3131
"weather-mcp-server": {
3232
"command": "/path/to/weather-mcp-server",
33-
"args": [],
3433
"env": {
3534
"WEATHER_API_KEY": "your-api-key"
3635
}
@@ -63,7 +62,7 @@ go build -o weather-mcp-server ./cmd/weather-mcp-server
6362

6463
## Using MCP with Docker Containers
6564

66-
#### 1. Build the Docker image:
65+
#### 1. Build the Docker Image:
6766

6867
```shell
6968
docker build -t weather-mcp-server .

internal/server/handlers/weather_test.go

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,57 +10,57 @@ import (
1010
"github.com/stretchr/testify/require"
1111
"go.uber.org/mock/gomock"
1212

13-
mocks "github.com/TuanKiri/weather-mcp-server/internal/server/services/mock"
13+
"github.com/TuanKiri/weather-mcp-server/internal/server/services/mock"
1414
)
1515

1616
func TestCurrentWeather(t *testing.T) {
1717
testCases := map[string]struct {
18-
fillWeatherService func(mocksWeather *mocks.MockWeatherService)
19-
arguments map[string]any
20-
errString string
21-
wait string
18+
arguments map[string]any
19+
errString string
20+
wait string
21+
setupWeatherService func(mocksWeather *mock.MockWeatherService)
2222
}{
2323
"empty_city": {
2424
wait: "city must be a string",
2525
},
2626
"city_not_found": {
27-
fillWeatherService: func(mocksWeather *mocks.MockWeatherService) {
28-
mocksWeather.EXPECT().
29-
Current(context.Background(), "Tokyo").
30-
Return("", errors.New("weather API not available. Code: 400"))
31-
},
3227
arguments: map[string]any{
3328
"city": "Tokyo",
3429
},
3530
errString: "weather API not available. Code: 400",
36-
},
37-
"successful_request": {
38-
fillWeatherService: func(mocksWeather *mocks.MockWeatherService) {
31+
setupWeatherService: func(mocksWeather *mock.MockWeatherService) {
3932
mocksWeather.EXPECT().
40-
Current(context.Background(), "London").
41-
Return("<h1>London weather data</h1>", nil)
33+
Current(context.Background(), "Tokyo").
34+
Return("", errors.New("weather API not available. Code: 400"))
4235
},
36+
},
37+
"successful_request": {
4338
arguments: map[string]any{
4439
"city": "London",
4540
},
4641
wait: "<h1>London weather data</h1>",
42+
setupWeatherService: func(mocksWeather *mock.MockWeatherService) {
43+
mocksWeather.EXPECT().
44+
Current(context.Background(), "London").
45+
Return("<h1>London weather data</h1>", nil)
46+
},
4747
},
4848
}
4949

5050
ctrl := gomock.NewController(t)
5151
defer ctrl.Finish()
5252

53-
mocksWeather := mocks.NewMockWeatherService(ctrl)
53+
mocksWeather := mock.NewMockWeatherService(ctrl)
5454

55-
svc := mocks.NewMockServices(ctrl)
55+
svc := mock.NewMockServices(ctrl)
5656
svc.EXPECT().Weather().Return(mocksWeather).AnyTimes()
5757

5858
handler := CurrentWeather(svc)
5959

6060
for name, tc := range testCases {
6161
t.Run(name, func(t *testing.T) {
62-
if tc.fillWeatherService != nil {
63-
tc.fillWeatherService(mocksWeather)
62+
if tc.setupWeatherService != nil {
63+
tc.setupWeatherService(mocksWeather)
6464
}
6565

6666
var request mcp.CallToolRequest

internal/server/services/core/core.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
11
package core
22

3-
import (
4-
"html/template"
5-
6-
"github.com/TuanKiri/weather-mcp-server/internal/server/services"
7-
"github.com/TuanKiri/weather-mcp-server/pkg/weatherapi"
8-
)
3+
import "github.com/TuanKiri/weather-mcp-server/internal/server/services"
94

105
type CoreServices struct {
11-
renderer *template.Template
12-
weatherAPI *weatherapi.WeatherAPI
6+
renderer services.TemplateRenderer
7+
weatherAPI services.WeatherAPIProvider
138

149
weatherService *WeatherService
1510
}
1611

17-
func New(renderer *template.Template, weatherAPI *weatherapi.WeatherAPI) *CoreServices {
12+
func New(renderer services.TemplateRenderer, weatherAPI services.WeatherAPIProvider) *CoreServices {
1813
return &CoreServices{
1914
renderer: renderer,
2015
weatherAPI: weatherAPI,

internal/server/services/core/weather.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ package core
33
import (
44
"bytes"
55
"context"
6-
"fmt"
6+
7+
"github.com/TuanKiri/weather-mcp-server/internal/server/view/models"
78
)
89

910
type WeatherService struct {
@@ -16,16 +17,12 @@ func (ws *WeatherService) Current(ctx context.Context, city string) (string, err
1617
return "", err
1718
}
1819

20+
var current models.CurrentWeather
21+
current.FromWeatherAPI(data)
22+
1923
var buf bytes.Buffer
2024

21-
if err := ws.renderer.ExecuteTemplate(&buf, "weather.html", map[string]string{
22-
"Location": fmt.Sprintf("%s, %s", data.Location.Name, data.Location.Country),
23-
"Icon": "https:" + data.Current.Condition.Icon,
24-
"Condition": data.Current.Condition.Text,
25-
"Temperature": fmt.Sprintf("%.0f", data.Current.TempC),
26-
"Humidity": fmt.Sprintf("%d", data.Current.Humidity),
27-
"WindSpeed": fmt.Sprintf("%.0f", data.Current.WindKph),
28-
}); err != nil {
25+
if err := ws.renderer.ExecuteTemplate(&buf, "weather.html", current); err != nil {
2926
return "", err
3027
}
3128

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package core
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
"go.uber.org/mock/gomock"
13+
14+
"github.com/TuanKiri/weather-mcp-server/internal/server/services/mock"
15+
viewModels "github.com/TuanKiri/weather-mcp-server/internal/server/view/models"
16+
"github.com/TuanKiri/weather-mcp-server/pkg/weatherapi/models"
17+
)
18+
19+
func TestCurrentWeather(t *testing.T) {
20+
testCases := map[string]struct {
21+
city string
22+
errString string
23+
wait string
24+
setupWeatherAPI func(weatherAPI *mock.MockWeatherAPIProvider)
25+
setupRenderer func(renderer *mock.MockTemplateRenderer)
26+
}{
27+
"city_not_found": {
28+
city: "Tokyo",
29+
errString: "weather API not available. Code: 400",
30+
setupWeatherAPI: func(weatherAPI *mock.MockWeatherAPIProvider) {
31+
weatherAPI.EXPECT().
32+
Current(context.Background(), "Tokyo").
33+
Return(nil, errors.New("weather API not available. Code: 400"))
34+
},
35+
},
36+
"successful_result": {
37+
city: "London",
38+
wait: "{Location:London, United Kingdom " +
39+
"Icon:https://cdn.weatherapi.com/weather/64x64/day/113.png " +
40+
"Condition:Sunny " +
41+
"Temperature:18 " +
42+
"Humidity:45 " +
43+
"WindSpeed:4}",
44+
setupWeatherAPI: func(weatherAPI *mock.MockWeatherAPIProvider) {
45+
weatherAPI.EXPECT().
46+
Current(context.Background(), "London").
47+
Return(&models.CurrentResponse{
48+
Location: models.Location{
49+
Name: "London",
50+
Country: "United Kingdom",
51+
},
52+
Current: models.Current{
53+
TempC: 18.4,
54+
WindKph: 4,
55+
Humidity: 45,
56+
Condition: models.Condition{
57+
Text: "Sunny",
58+
Icon: "//cdn.weatherapi.com/weather/64x64/day/113.png",
59+
},
60+
},
61+
}, nil)
62+
},
63+
setupRenderer: func(renderer *mock.MockTemplateRenderer) {
64+
renderer.EXPECT().
65+
ExecuteTemplate(
66+
gomock.AssignableToTypeOf(&bytes.Buffer{}),
67+
"weather.html",
68+
gomock.AssignableToTypeOf(viewModels.CurrentWeather{}),
69+
).
70+
Do(func(wr io.Writer, _ string, data any) error {
71+
value, _ := data.(viewModels.CurrentWeather)
72+
wr.Write(fmt.Appendf([]byte{}, "%+v", value))
73+
return nil
74+
})
75+
},
76+
},
77+
}
78+
79+
ctrl := gomock.NewController(t)
80+
defer ctrl.Finish()
81+
82+
renderer := mock.NewMockTemplateRenderer(ctrl)
83+
weatherAPI := mock.NewMockWeatherAPIProvider(ctrl)
84+
85+
svc := New(renderer, weatherAPI)
86+
87+
for name, tc := range testCases {
88+
t.Run(name, func(t *testing.T) {
89+
if tc.setupWeatherAPI != nil {
90+
tc.setupWeatherAPI(weatherAPI)
91+
}
92+
93+
if tc.setupRenderer != nil {
94+
tc.setupRenderer(renderer)
95+
}
96+
97+
data, err := svc.Weather().Current(context.Background(), tc.city)
98+
if err != nil {
99+
assert.EqualError(t, err, tc.errString)
100+
}
101+
102+
assert.Equal(t, tc.wait, data)
103+
})
104+
}
105+
}

internal/server/services/mock/mock.go

Lines changed: 82 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/server/services/services.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
package services
22

3-
import "context"
3+
import (
4+
"context"
5+
"io"
6+
7+
"github.com/TuanKiri/weather-mcp-server/pkg/weatherapi/models"
8+
)
9+
10+
type TemplateRenderer interface {
11+
ExecuteTemplate(wr io.Writer, name string, data any) error
12+
}
13+
14+
type WeatherAPIProvider interface {
15+
Current(ctx context.Context, city string) (*models.CurrentResponse, error)
16+
}
417

518
type Services interface {
619
Weather() WeatherService

0 commit comments

Comments
 (0)