github.com/deadlysurgeon/weather@v0.0.0-20240402201029-3925d9f784b1/weather/impl_test.go (about) 1 package weather 2 3 import ( 4 "fmt" 5 "io" 6 "net/http" 7 "net/http/httptest" 8 "strings" 9 "testing" 10 ) 11 12 func TestFeelsLike(t *testing.T) { 13 for name, test := range map[string]float32{ 14 "freezing": -4, 15 "cold": 5, 16 "moderate": 11, 17 "hot": 22, 18 "burning": 42, 19 } { 20 test := test 21 t.Run(name, func(t *testing.T) { 22 if s := feelsLike(test); s != name { 23 t.Fatalf("Expected %s got %s", name, s) 24 } 25 }) 26 } 27 } 28 29 func TestFormRequest(t *testing.T) { 30 for name, test := range map[string]struct { 31 url string 32 expectError bool 33 }{ 34 "good url": { 35 url: "https://example.com", 36 }, 37 "bad url": { 38 url: "http://%41:8080/", 39 expectError: true, 40 }, 41 } { 42 test := test 43 t.Run(name, func(t *testing.T) { 44 _ = test 45 _, err := (&impl{endpoint: test.url}).formRequest("", "") 46 if (err != nil) != test.expectError { 47 t.Fatalf("Expected error (%v) got %v", test.expectError, err) 48 } 49 }) 50 } 51 } 52 53 func TestWeatherAt(t *testing.T) { 54 for name, test := range map[string]struct { 55 lat, lon string 56 mockResponse string 57 endpoint string 58 mockStatusCode int 59 clientErr error 60 wantErr bool 61 wantReport Report 62 }{ 63 "successful report": { 64 lat: "0", 65 lon: "0", 66 mockResponse: `{"current":{"feels_like":25.3,"weather":[{"main":"Clear"}]}}`, 67 mockStatusCode: http.StatusOK, 68 wantErr: false, 69 wantReport: Report{ 70 Condition: "Clear", 71 Temperature: "hot", 72 TemperatureRaw: 25.3, 73 }, 74 }, 75 "bad status code": { 76 lat: "invalid", 77 lon: "invalid", 78 mockResponse: `Bad Request`, 79 mockStatusCode: http.StatusBadRequest, 80 wantErr: true, 81 }, 82 "invalid JSON response": { 83 lat: "0", 84 lon: "0", 85 mockResponse: `Invalid JSON`, 86 mockStatusCode: http.StatusOK, 87 wantErr: true, 88 }, 89 "do client error": { 90 lat: "0", 91 lon: "0", 92 clientErr: fmt.Errorf("client error"), 93 mockStatusCode: http.StatusOK, 94 wantErr: true, 95 }, 96 "bad endpoint": { 97 lat: "0", 98 lon: "0", 99 endpoint: "http://%41:8080/", 100 wantErr: true, 101 }, 102 } { 103 t.Run(name, func(t *testing.T) { 104 // Setup mock HTTP server 105 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 106 w.WriteHeader(test.mockStatusCode) 107 fmt.Fprintln(w, test.mockResponse) 108 })) 109 defer server.Close() 110 111 // Create an instance of `impl` with a mocked client 112 s := &impl{ 113 endpoint: test.endpoint, 114 client: mockHTTPClient(func(req *http.Request) *http.Response { 115 return &http.Response{ 116 StatusCode: test.mockStatusCode, 117 Body: io.NopCloser(strings.NewReader(test.mockResponse)), 118 Header: make(http.Header), 119 } 120 }, test.clientErr), 121 } 122 123 // Execute WeatherAt 124 gotReport, err := s.At(test.lat, test.lon) 125 if (err != nil) != test.wantErr { 126 t.Errorf("WeatherAt() error = %v, wantErr %v", err, test.wantErr) 127 return 128 } 129 130 if !test.wantErr && !compareReports(gotReport, test.wantReport) { 131 t.Errorf("WeatherAt() gotReport = %v, want %v", gotReport, test.wantReport) 132 } 133 }) 134 } 135 } 136 137 type mockTransport struct { 138 URL string 139 } 140 141 func (m *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) { 142 req.URL.Scheme = "http" // Override scheme to match mock server 143 req.URL.Host = m.URL[7:] // Remove "http://" from mock server URL 144 return http.DefaultTransport.RoundTrip(req) 145 } 146 147 func compareReports(a, b Report) bool { 148 return a.Condition == b.Condition && 149 a.Temperature == b.Temperature && 150 a.TemperatureRaw == b.TemperatureRaw 151 } 152 153 // mockRoundTripper mocks the RoundTrip function for http.Client 154 type mockRoundTripper struct { 155 fun func(req *http.Request) *http.Response 156 err error 157 } 158 159 // RoundTrip executes the mock round trip function 160 func (m mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 161 return m.fun(req), m.err 162 } 163 164 // mockHTTPClient helps in setting up a client with a mock transport 165 func mockHTTPClient(fn func(req *http.Request) *http.Response, err error) *http.Client { 166 mrt := &mockRoundTripper{ 167 fun: fn, 168 err: err, 169 } 170 return &http.Client{ 171 Transport: mrt, 172 } 173 }